summaryrefslogtreecommitdiffstats
path: root/src/tools/rust-analyzer/crates
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/rust-analyzer/crates')
-rw-r--r--src/tools/rust-analyzer/crates/base-db/Cargo.toml2
-rw-r--r--src/tools/rust-analyzer/crates/base-db/src/fixture.rs114
-rw-r--r--src/tools/rust-analyzer/crates/base-db/src/input.rs6
-rw-r--r--src/tools/rust-analyzer/crates/cfg/Cargo.toml6
-rw-r--r--src/tools/rust-analyzer/crates/cfg/src/lib.rs4
-rw-r--r--src/tools/rust-analyzer/crates/flycheck/Cargo.toml4
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/Cargo.toml8
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/attr.rs42
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/body.rs4
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs71
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/body/pretty.rs14
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/body/scope.rs5
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/body/tests.rs12
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/body/tests/block.rs77
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/child_by_source.rs23
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/data.rs47
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/data/adt.rs131
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/db.rs26
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/dyn_map/keys.rs8
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/expander.rs87
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/find_path.rs2
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/generics.rs16
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/hir.rs11
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/hir/type_ref.rs4
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/import_map.rs645
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs25
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs20
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs6
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/item_tree/pretty.rs8
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs8
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/lib.rs171
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/lower.rs8
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs76
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs22
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs91
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs53
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/nameres.rs21
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs120
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/nameres/diagnostics.rs15
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/nameres/path_resolution.rs124
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs18
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/path.rs7
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/path/lower.rs4
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/pretty.rs4
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/resolver.rs68
-rw-r--r--src/tools/rust-analyzer/crates/hir-expand/Cargo.toml6
-rw-r--r--src/tools/rust-analyzer/crates/hir-expand/src/ast_id_map.rs79
-rw-r--r--src/tools/rust-analyzer/crates/hir-expand/src/builtin_attr_macro.rs4
-rw-r--r--src/tools/rust-analyzer/crates/hir-expand/src/builtin_derive_macro.rs196
-rw-r--r--src/tools/rust-analyzer/crates/hir-expand/src/builtin_fn_macro.rs14
-rw-r--r--src/tools/rust-analyzer/crates/hir-expand/src/db.rs478
-rw-r--r--src/tools/rust-analyzer/crates/hir-expand/src/eager.rs237
-rw-r--r--src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs16
-rw-r--r--src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs50
-rw-r--r--src/tools/rust-analyzer/crates/hir-expand/src/lib.rs249
-rw-r--r--src/tools/rust-analyzer/crates/hir-expand/src/mod_path.rs4
-rw-r--r--src/tools/rust-analyzer/crates/hir-expand/src/name.rs23
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/Cargo.toml12
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/autoderef.rs13
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/chalk_db.rs68
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/chalk_ext.rs3
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs25
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests.rs453
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests/intrinsics.rs355
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/db.rs24
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/diagnostics.rs2
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs77
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/display.rs115
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/infer.rs44
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs46
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs54
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs6
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs47
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs20
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/infer/pat.rs4
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs21
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/layout.rs152
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs28
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs98
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/lib.rs11
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/lower.rs10
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs54
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/mir.rs41
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs19
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs1636
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim.rs651
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim/simd.rs177
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/tests.rs118
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs689
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/as_place.rs77
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/pattern_matching.rs7
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/mir/monomorphization.rs48
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/mir/pretty.rs38
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/tests.rs9
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/tests/display_source_code.rs19
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/tests/macros.rs12
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/tests/method_resolution.rs67
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/tests/never_type.rs6
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs26
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs22
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs68
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/traits.rs2
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/utils.rs14
-rw-r--r--src/tools/rust-analyzer/crates/hir/Cargo.toml2
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/attrs.rs41
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/db.rs11
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/diagnostics.rs2
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/display.rs26
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/from_id.rs5
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/has_source.rs36
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/lib.rs250
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/semantics.rs31
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs23
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs184
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_match_arms.rs158
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_turbo_fish.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/change_visibility.rs75
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_two_arm_bool_match_to_matches_macro.rs125
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_tuple_binding.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_doc_comment.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/expand_glob_import.rs164
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs38
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_module.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/fix_visibility.rs113
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_new.rs38
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs166
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs1051
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_derive.rs106
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs7
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter_or_setter.rs (renamed from src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter.rs)438
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_setter.rs175
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs429
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_call.rs143
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_macro.rs3
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_const_to_impl.rs22
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/promote_local_to_const.rs56
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/pull_assignment_up.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_unused_imports.rs739
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs16
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_result_return_type.rs16
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_return_type_in_result.rs31
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/lib.rs15
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/tests.rs93
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs140
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/utils.rs15
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/cfg.rs3
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/extern_abi.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/mod_.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/context.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs7
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/item.rs21
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/lib.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs1
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs23
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/Cargo.toml2
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/apply_change.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/defs.rs53
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/famous_defs.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs16
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/helpers.rs18
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs8
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/items_locator.rs60
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/lib.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/rename.rs18
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/search.rs18
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/source_change.rs233
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/Cargo.toml1
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/break_outside_of_loop.rs9
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/expected_function.rs9
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/field_shorthand.rs40
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/inactive_code.rs7
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incoherent_impl.rs10
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs208
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/invalid_derive_target.rs5
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs5
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/macro_error.rs21
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/malformed_derive.rs5
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_fields.rs6
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_match_arms.rs9
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_unsafe.rs9
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/moved_out_of_ref.rs9
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mutability_errors.rs181
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs9
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_assoc_item.rs21
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_field.rs9
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs41
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/typed_hole.rs5
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/undeclared_label.rs9
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unimplemented_builtin_macro.rs10
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unlinked_file.rs14
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unreachable_label.rs9
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_extern_crate.rs9
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_field.rs21
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_import.rs9
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_method.rs9
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_module.rs29
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_proc_macro.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/useless_braces.rs75
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs262
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/tests.rs18
-rw-r--r--src/tools/rust-analyzer/crates/ide-ssr/src/search.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/doc_links.rs11
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs35
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/goto_definition.rs12
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs88
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/highlight_related.rs9
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/hover.rs17
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/hover/render.rs23
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/hover/tests.rs89
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs12
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/interpret_function.rs6
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/lib.rs13
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/moniker.rs31
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/navigation_target.rs37
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/references.rs5
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/rename.rs327
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/runnables.rs61
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/ssr.rs57
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/static_index.rs72
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs23
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/escape.rs36
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs29
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/html.rs3
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tags.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html3
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_attributes.html3
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_crate_root.html3
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_default_library.html3
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html3
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_extern_crate.html6
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_general.html3
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html3
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_keywords.html5
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_lifetimes.html3
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html17
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_module_docs_inline.html3
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_module_docs_outline.html3
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_operators.html3
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_rainbow.html3
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html16
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html3
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs27
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/view_memory_layout.rs409
-rw-r--r--src/tools/rust-analyzer/crates/intern/Cargo.toml2
-rw-r--r--src/tools/rust-analyzer/crates/load-cargo/Cargo.toml25
-rw-r--r--src/tools/rust-analyzer/crates/load-cargo/src/lib.rs441
-rw-r--r--src/tools/rust-analyzer/crates/mbe/src/benchmark.rs11
-rw-r--r--src/tools/rust-analyzer/crates/mbe/src/expander.rs10
-rw-r--r--src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs12
-rw-r--r--src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs42
-rw-r--r--src/tools/rust-analyzer/crates/mbe/src/lib.rs84
-rw-r--r--src/tools/rust-analyzer/crates/mbe/src/syntax_bridge.rs148
-rw-r--r--src/tools/rust-analyzer/crates/mbe/src/token_map.rs11
-rw-r--r--src/tools/rust-analyzer/crates/mbe/src/tt_iter.rs2
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/grammar.rs136
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/grammar/generic_args.rs19
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs3
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/grammar/items.rs5
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/lib.rs3
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/shortcuts.rs5
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs1
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/tests/prefix_entries.rs1
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0021_recover_from_missing_assoc_item_binding.rast48
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0021_recover_from_missing_assoc_item_binding.rs1
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0022_recover_from_missing_const_default.rast44
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0022_recover_from_missing_const_default.rs1
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0040_crate_keyword_vis.rast63
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0040_crate_keyword_vis.rs3
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0125_crate_keyword_path.rast33
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0125_crate_keyword_path.rs1
-rw-r--r--src/tools/rust-analyzer/crates/paths/src/lib.rs15
-rw-r--r--src/tools/rust-analyzer/crates/proc-macro-api/Cargo.toml2
-rw-r--r--src/tools/rust-analyzer/crates/proc-macro-api/src/version.rs11
-rw-r--r--src/tools/rust-analyzer/crates/proc-macro-srv/Cargo.toml4
-rw-r--r--src/tools/rust-analyzer/crates/proc-macro-srv/src/server.rs21
-rw-r--r--src/tools/rust-analyzer/crates/profile/Cargo.toml2
-rw-r--r--src/tools/rust-analyzer/crates/profile/src/tree.rs2
-rw-r--r--src/tools/rust-analyzer/crates/project-model/Cargo.toml2
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/build_scripts.rs17
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs27
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/lib.rs23
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/manifest_path.rs8
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/rustc_cfg.rs6
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/sysroot.rs22
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/target_data_layout.rs3
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/workspace.rs92
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml13
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/bin/logger.rs94
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs64
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/cli.rs18
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs166
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs12
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/cli/flags.rs32
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/cli/load_cargo.rs205
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/cli/lsif.rs59
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/cli/run_tests.rs89
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs72
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs15
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs1
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/dispatch.rs30
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/from_proto.rs27
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs12
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs28
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs260
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs2
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs13
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/lsp_ext.rs27
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs18
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs301
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/semantic_tokens.rs1
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/to_proto.rs621
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs26
-rw-r--r--src/tools/rust-analyzer/crates/sourcegen/src/lib.rs4
-rw-r--r--src/tools/rust-analyzer/crates/stdx/Cargo.toml2
-rw-r--r--src/tools/rust-analyzer/crates/syntax/Cargo.toml2
-rw-r--r--src/tools/rust-analyzer/crates/syntax/rust.ungram6
-rw-r--r--src/tools/rust-analyzer/crates/syntax/src/ast/edit_in_place.rs85
-rw-r--r--src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs30
-rw-r--r--src/tools/rust-analyzer/crates/syntax/src/ast/make.rs93
-rw-r--r--src/tools/rust-analyzer/crates/syntax/src/ast/token_ext.rs61
-rw-r--r--src/tools/rust-analyzer/crates/syntax/src/lib.rs103
-rw-r--r--src/tools/rust-analyzer/crates/syntax/src/tests/ast_src.rs1
-rw-r--r--src/tools/rust-analyzer/crates/test-utils/src/fixture.rs95
-rw-r--r--src/tools/rust-analyzer/crates/test-utils/src/minicore.rs69
-rw-r--r--src/tools/rust-analyzer/crates/tt/src/buffer.rs6
-rw-r--r--src/tools/rust-analyzer/crates/tt/src/lib.rs18
-rw-r--r--src/tools/rust-analyzer/crates/vfs-notify/Cargo.toml3
-rw-r--r--src/tools/rust-analyzer/crates/vfs/Cargo.toml2
-rw-r--r--src/tools/rust-analyzer/crates/vfs/src/vfs_path.rs8
337 files changed, 15712 insertions, 5714 deletions
diff --git a/src/tools/rust-analyzer/crates/base-db/Cargo.toml b/src/tools/rust-analyzer/crates/base-db/Cargo.toml
index 6001772c8..171c113a9 100644
--- a/src/tools/rust-analyzer/crates/base-db/Cargo.toml
+++ b/src/tools/rust-analyzer/crates/base-db/Cargo.toml
@@ -17,7 +17,7 @@ rustc-hash = "1.1.0"
triomphe.workspace = true
-la-arena = { version = "0.3.0", path = "../../lib/la-arena" }
+la-arena.workspace = true
# local deps
cfg.workspace = true
diff --git a/src/tools/rust-analyzer/crates/base-db/src/fixture.rs b/src/tools/rust-analyzer/crates/base-db/src/fixture.rs
index d3abc3870..323ee4260 100644
--- a/src/tools/rust-analyzer/crates/base-db/src/fixture.rs
+++ b/src/tools/rust-analyzer/crates/base-db/src/fixture.rs
@@ -26,7 +26,7 @@ pub trait WithFixture: Default + SourceDatabaseExt + 'static {
let fixture = ChangeFixture::parse(ra_fixture);
let mut db = Self::default();
fixture.change.apply(&mut db);
- assert_eq!(fixture.files.len(), 1);
+ assert_eq!(fixture.files.len(), 1, "Multiple file found in the fixture");
(db, fixture.files[0])
}
@@ -102,6 +102,8 @@ pub struct ChangeFixture {
pub change: Change,
}
+const SOURCE_ROOT_PREFIX: &str = "/";
+
impl ChangeFixture {
pub fn parse(ra_fixture: &str) -> ChangeFixture {
Self::parse_with_proc_macros(ra_fixture, Vec::new())
@@ -131,7 +133,6 @@ impl ChangeFixture {
let mut file_set = FileSet::default();
let mut current_source_root_kind = SourceRootKind::Local;
- let source_root_prefix = "/".to_string();
let mut file_id = FileId(0);
let mut roots = Vec::new();
@@ -151,19 +152,23 @@ impl ChangeFixture {
entry.text.clone()
};
- let meta = FileMeta::from(entry);
- assert!(meta.path.starts_with(&source_root_prefix));
+ let meta = FileMeta::from_fixture(entry, current_source_root_kind);
+ assert!(meta.path.starts_with(SOURCE_ROOT_PREFIX));
if !meta.deps.is_empty() {
assert!(meta.krate.is_some(), "can't specify deps without naming the crate")
}
- if let Some(kind) = &meta.introduce_new_source_root {
- let root = match current_source_root_kind {
+ if let Some(kind) = meta.introduce_new_source_root {
+ assert!(
+ meta.krate.is_some(),
+ "new_source_root meta doesn't make sense without crate meta"
+ );
+ let prev_kind = mem::replace(&mut current_source_root_kind, kind);
+ let prev_root = match prev_kind {
SourceRootKind::Local => SourceRoot::new_local(mem::take(&mut file_set)),
SourceRootKind::Library => SourceRoot::new_library(mem::take(&mut file_set)),
};
- roots.push(root);
- current_source_root_kind = *kind;
+ roots.push(prev_root);
}
if let Some((krate, origin, version)) = meta.krate {
@@ -185,7 +190,7 @@ impl ChangeFixture {
Some(toolchain),
);
let prev = crates.insert(crate_name.clone(), crate_id);
- assert!(prev.is_none());
+ assert!(prev.is_none(), "multiple crates with same name: {}", crate_name);
for dep in meta.deps {
let prelude = meta.extern_prelude.contains(&dep);
let dep = CrateName::normalize_dashes(&dep);
@@ -219,7 +224,7 @@ impl ChangeFixture {
false,
CrateOrigin::Local { repo: None, name: None },
default_target_data_layout
- .map(|x| x.into())
+ .map(|it| it.into())
.ok_or_else(|| "target_data_layout unset".into()),
Some(toolchain),
);
@@ -442,51 +447,74 @@ struct FileMeta {
target_data_layout: Option<String>,
}
-fn parse_crate(crate_str: String) -> (String, CrateOrigin, Option<String>) {
- if let Some((a, b)) = crate_str.split_once('@') {
- let (version, origin) = match b.split_once(':') {
- Some(("CratesIo", data)) => match data.split_once(',') {
- Some((version, url)) => {
- (version, CrateOrigin::Local { repo: Some(url.to_owned()), name: None })
- }
- _ => panic!("Bad crates.io parameter: {data}"),
- },
- _ => panic!("Bad string for crate origin: {b}"),
- };
- (a.to_owned(), origin, Some(version.to_string()))
- } else {
- let crate_origin = match LangCrateOrigin::from(&*crate_str) {
- LangCrateOrigin::Other => CrateOrigin::Local { repo: None, name: None },
- origin => CrateOrigin::Lang(origin),
- };
- (crate_str, crate_origin, None)
- }
-}
-
-impl From<Fixture> for FileMeta {
- fn from(f: Fixture) -> FileMeta {
+impl FileMeta {
+ fn from_fixture(f: Fixture, current_source_root_kind: SourceRootKind) -> Self {
let mut cfg = CfgOptions::default();
- f.cfg_atoms.iter().for_each(|it| cfg.insert_atom(it.into()));
- f.cfg_key_values.iter().for_each(|(k, v)| cfg.insert_key_value(k.into(), v.into()));
+ for (k, v) in f.cfgs {
+ if let Some(v) = v {
+ cfg.insert_key_value(k.into(), v.into());
+ } else {
+ cfg.insert_atom(k.into());
+ }
+ }
+
+ let introduce_new_source_root = f.introduce_new_source_root.map(|kind| match &*kind {
+ "local" => SourceRootKind::Local,
+ "library" => SourceRootKind::Library,
+ invalid => panic!("invalid source root kind '{invalid}'"),
+ });
+ let current_source_root_kind =
+ introduce_new_source_root.unwrap_or(current_source_root_kind);
+
let deps = f.deps;
- FileMeta {
+ Self {
path: f.path,
- krate: f.krate.map(parse_crate),
+ krate: f.krate.map(|it| parse_crate(it, current_source_root_kind, f.library)),
extern_prelude: f.extern_prelude.unwrap_or_else(|| deps.clone()),
deps,
cfg,
- edition: f.edition.as_ref().map_or(Edition::CURRENT, |v| Edition::from_str(v).unwrap()),
+ edition: f.edition.map_or(Edition::CURRENT, |v| Edition::from_str(&v).unwrap()),
env: f.env.into_iter().collect(),
- introduce_new_source_root: f.introduce_new_source_root.map(|kind| match &*kind {
- "local" => SourceRootKind::Local,
- "library" => SourceRootKind::Library,
- invalid => panic!("invalid source root kind '{invalid}'"),
- }),
+ introduce_new_source_root,
target_data_layout: f.target_data_layout,
}
}
}
+fn parse_crate(
+ crate_str: String,
+ current_source_root_kind: SourceRootKind,
+ explicit_non_workspace_member: bool,
+) -> (String, CrateOrigin, Option<String>) {
+ // syntax:
+ // "my_awesome_crate"
+ // "my_awesome_crate@0.0.1,http://example.com"
+ let (name, repo, version) = if let Some((name, remain)) = crate_str.split_once('@') {
+ let (version, repo) =
+ remain.split_once(',').expect("crate meta: found '@' without version and url");
+ (name.to_owned(), Some(repo.to_owned()), Some(version.to_owned()))
+ } else {
+ (crate_str, None, None)
+ };
+
+ let non_workspace_member = explicit_non_workspace_member
+ || matches!(current_source_root_kind, SourceRootKind::Library);
+
+ let origin = match LangCrateOrigin::from(&*name) {
+ LangCrateOrigin::Other => {
+ let name = name.clone();
+ if non_workspace_member {
+ CrateOrigin::Library { repo, name }
+ } else {
+ CrateOrigin::Local { repo, name: Some(name) }
+ }
+ }
+ origin => CrateOrigin::Lang(origin),
+ };
+
+ (name, origin, version)
+}
+
// Identity mapping
#[derive(Debug)]
struct IdentityProcMacroExpander;
diff --git a/src/tools/rust-analyzer/crates/base-db/src/input.rs b/src/tools/rust-analyzer/crates/base-db/src/input.rs
index f2e523675..c47799f13 100644
--- a/src/tools/rust-analyzer/crates/base-db/src/input.rs
+++ b/src/tools/rust-analyzer/crates/base-db/src/input.rs
@@ -138,12 +138,12 @@ impl ops::Deref for CrateName {
}
}
-/// Origin of the crates. It is used in emitting monikers.
+/// Origin of the crates.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum CrateOrigin {
- /// Crates that are from the rustc workspace
+ /// Crates that are from the rustc workspace.
Rustc { name: String },
- /// Crates that are workspace members,
+ /// Crates that are workspace members.
Local { repo: Option<String>, name: Option<String> },
/// Crates that are non member libraries.
Library { repo: Option<String>, name: String },
diff --git a/src/tools/rust-analyzer/crates/cfg/Cargo.toml b/src/tools/rust-analyzer/crates/cfg/Cargo.toml
index 0880bc239..ed3808972 100644
--- a/src/tools/rust-analyzer/crates/cfg/Cargo.toml
+++ b/src/tools/rust-analyzer/crates/cfg/Cargo.toml
@@ -18,13 +18,13 @@ rustc-hash = "1.1.0"
tt.workspace = true
[dev-dependencies]
-expect-test = "1.4.0"
+expect-test = "1.4.1"
oorandom = "11.1.3"
# We depend on both individually instead of using `features = ["derive"]` to microoptimize the
# build graph: if the feature was enabled, syn would be built early on in the graph if `smolstr`
# supports `arbitrary`. This way, we avoid feature unification.
-arbitrary = "1.2.2"
-derive_arbitrary = "1.2.2"
+arbitrary = "1.3.0"
+derive_arbitrary = "1.3.1"
# local deps
mbe.workspace = true
diff --git a/src/tools/rust-analyzer/crates/cfg/src/lib.rs b/src/tools/rust-analyzer/crates/cfg/src/lib.rs
index 495119d55..183b9b7d2 100644
--- a/src/tools/rust-analyzer/crates/cfg/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/cfg/src/lib.rs
@@ -69,7 +69,7 @@ impl CfgOptions {
}
pub fn get_cfg_keys(&self) -> impl Iterator<Item = &SmolStr> {
- self.enabled.iter().map(|x| match x {
+ self.enabled.iter().map(|it| match it {
CfgAtom::Flag(key) => key,
CfgAtom::KeyValue { key, .. } => key,
})
@@ -79,7 +79,7 @@ impl CfgOptions {
&'a self,
cfg_key: &'a str,
) -> impl Iterator<Item = &'a SmolStr> + 'a {
- self.enabled.iter().filter_map(move |x| match x {
+ self.enabled.iter().filter_map(move |it| match it {
CfgAtom::KeyValue { key, value } if cfg_key == key => Some(value),
_ => None,
})
diff --git a/src/tools/rust-analyzer/crates/flycheck/Cargo.toml b/src/tools/rust-analyzer/crates/flycheck/Cargo.toml
index 3f6671b1c..e7f7adc78 100644
--- a/src/tools/rust-analyzer/crates/flycheck/Cargo.toml
+++ b/src/tools/rust-analyzer/crates/flycheck/Cargo.toml
@@ -12,9 +12,9 @@ rust-version.workspace = true
doctest = false
[dependencies]
-crossbeam-channel = "0.5.5"
+crossbeam-channel = "0.5.8"
tracing = "0.1.37"
-cargo_metadata = "0.15.0"
+cargo_metadata = "0.15.4"
rustc-hash = "1.1.0"
serde_json.workspace = true
serde.workspace = true
diff --git a/src/tools/rust-analyzer/crates/hir-def/Cargo.toml b/src/tools/rust-analyzer/crates/hir-def/Cargo.toml
index 83c705164..30307deb7 100644
--- a/src/tools/rust-analyzer/crates/hir-def/Cargo.toml
+++ b/src/tools/rust-analyzer/crates/hir-def/Cargo.toml
@@ -21,14 +21,14 @@ dashmap = { version = "=5.4.0", features = ["raw-api"] }
drop_bomb = "0.1.5"
either = "1.7.0"
fst = { version = "0.4.7", default-features = false }
-hashbrown = { version = "0.12.1", default-features = false }
-indexmap = "1.9.1"
+indexmap = "2.0.0"
itertools = "0.10.5"
-la-arena = { version = "0.3.0", path = "../../lib/la-arena" }
+la-arena.workspace = true
once_cell = "1.17.0"
rustc-hash = "1.1.0"
-smallvec.workspace = true
tracing = "0.1.35"
+smallvec.workspace = true
+hashbrown.workspace = true
triomphe.workspace = true
rustc_abi = { version = "0.0.20221221", package = "hkalbasi-rustc-ap-rustc_abi", default-features = false }
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attr.rs b/src/tools/rust-analyzer/crates/hir-def/src/attr.rs
index bab3bbc23..fae071118 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/attr.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/attr.rs
@@ -137,13 +137,16 @@ impl Attrs {
let cfg_options = &crate_graph[krate].cfg_options;
- let Some(variant) = enum_.variants.clone().filter(|variant| {
- let attrs = item_tree.attrs(db, krate, (*variant).into());
- attrs.is_cfg_enabled(cfg_options)
- })
- .zip(0u32..)
- .find(|(_variant, idx)| it.local_id == Idx::from_raw(RawIdx::from(*idx)))
- .map(|(variant, _idx)| variant)
+ let Some(variant) = enum_
+ .variants
+ .clone()
+ .filter(|variant| {
+ let attrs = item_tree.attrs(db, krate, (*variant).into());
+ attrs.is_cfg_enabled(cfg_options)
+ })
+ .zip(0u32..)
+ .find(|(_variant, idx)| it.local_id == Idx::from_raw(RawIdx::from(*idx)))
+ .map(|(variant, _idx)| variant)
else {
return Arc::new(res);
};
@@ -272,6 +275,25 @@ impl Attrs {
self.by_key("proc_macro_derive").exists()
}
+ pub fn is_test(&self) -> bool {
+ self.iter().any(|it| {
+ it.path()
+ .segments()
+ .iter()
+ .rev()
+ .zip(["core", "prelude", "v1", "test"].iter().rev())
+ .all(|it| it.0.as_str() == Some(it.1))
+ })
+ }
+
+ pub fn is_ignore(&self) -> bool {
+ self.by_key("ignore").exists()
+ }
+
+ pub fn is_bench(&self) -> bool {
+ self.by_key("bench").exists()
+ }
+
pub fn is_unstable(&self) -> bool {
self.by_key("unstable").exists()
}
@@ -282,7 +304,7 @@ use std::slice::Iter as SliceIter;
pub enum DocAtom {
/// eg. `#[doc(hidden)]`
Flag(SmolStr),
- /// eg. `#[doc(alias = "x")]`
+ /// eg. `#[doc(alias = "it")]`
///
/// Note that a key can have multiple values that are all considered "active" at the same time.
/// For example, `#[doc(alias = "x")]` and `#[doc(alias = "y")]`.
@@ -462,6 +484,8 @@ impl AttrsWithOwner {
}
},
AttrDefId::ExternBlockId(it) => attrs_from_item_tree_loc(db, it),
+ AttrDefId::ExternCrateId(it) => attrs_from_item_tree_loc(db, it),
+ AttrDefId::UseId(it) => attrs_from_item_tree_loc(db, it),
};
let attrs = raw_attrs.filter(db.upcast(), def.krate(db));
@@ -546,6 +570,8 @@ impl AttrsWithOwner {
.map(|source| ast::AnyHasAttrs::new(source[id.local_id].clone())),
},
AttrDefId::ExternBlockId(id) => any_has_attrs(db, id),
+ AttrDefId::ExternCrateId(id) => any_has_attrs(db, id),
+ AttrDefId::UseId(id) => any_has_attrs(db, id),
};
AttrSourceMap::new(owner.as_ref().map(|node| node as &dyn HasAttrs))
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body.rs b/src/tools/rust-analyzer/crates/hir-def/src/body.rs
index 94dc39b11..f8d492d0e 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/body.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/body.rs
@@ -273,10 +273,10 @@ impl Body {
pub fn is_binding_upvar(&self, binding: BindingId, relative_to: ExprId) -> bool {
match self.binding_owners.get(&binding) {
- Some(x) => {
+ Some(it) => {
// We assign expression ids in a way that outer closures will receive
// a lower id
- x.into_raw() < relative_to.into_raw()
+ it.into_raw() < relative_to.into_raw()
}
None => true,
}
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs
index b375ec63a..3853a6ab3 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs
@@ -297,11 +297,11 @@ impl ExprCollector<'_> {
let (result_expr_id, prev_binding_owner) =
this.initialize_binding_owner(syntax_ptr);
let inner_expr = this.collect_block(e);
- let x = this.db.intern_anonymous_const(ConstBlockLoc {
+ let it = this.db.intern_anonymous_const(ConstBlockLoc {
parent: this.owner,
root: inner_expr,
});
- this.body.exprs[result_expr_id] = Expr::Const(x);
+ this.body.exprs[result_expr_id] = Expr::Const(it);
this.current_binding_owner = prev_binding_owner;
result_expr_id
})
@@ -313,21 +313,15 @@ impl ExprCollector<'_> {
let body = self.collect_labelled_block_opt(label, e.loop_body());
self.alloc_expr(Expr::Loop { body, label }, syntax_ptr)
}
- ast::Expr::WhileExpr(e) => {
- let label = e.label().map(|label| self.collect_label(label));
- let body = self.collect_labelled_block_opt(label, e.loop_body());
- let condition = self.collect_expr_opt(e.condition());
-
- self.alloc_expr(Expr::While { condition, body, label }, syntax_ptr)
- }
+ ast::Expr::WhileExpr(e) => self.collect_while_loop(syntax_ptr, e),
ast::Expr::ForExpr(e) => self.collect_for_loop(syntax_ptr, e),
ast::Expr::CallExpr(e) => {
let is_rustc_box = {
let attrs = e.attrs();
- attrs.filter_map(|x| x.as_simple_atom()).any(|x| x == "rustc_box")
+ attrs.filter_map(|it| it.as_simple_atom()).any(|it| it == "rustc_box")
};
if is_rustc_box {
- let expr = self.collect_expr_opt(e.arg_list().and_then(|x| x.args().next()));
+ let expr = self.collect_expr_opt(e.arg_list().and_then(|it| it.args().next()));
self.alloc_expr(Expr::Box { expr }, syntax_ptr)
} else {
let callee = self.collect_expr_opt(e.expr());
@@ -731,6 +725,32 @@ impl ExprCollector<'_> {
expr_id
}
+ /// Desugar `ast::WhileExpr` from: `[opt_ident]: while <cond> <body>` into:
+ /// ```ignore (pseudo-rust)
+ /// [opt_ident]: loop {
+ /// if <cond> {
+ /// <body>
+ /// }
+ /// else {
+ /// break;
+ /// }
+ /// }
+ /// ```
+ /// FIXME: Rustc wraps the condition in a construct equivalent to `{ let _t = <cond>; _t }`
+ /// to preserve drop semantics. We should probably do the same in future.
+ fn collect_while_loop(&mut self, syntax_ptr: AstPtr<ast::Expr>, e: ast::WhileExpr) -> ExprId {
+ let label = e.label().map(|label| self.collect_label(label));
+ let body = self.collect_labelled_block_opt(label, e.loop_body());
+ let condition = self.collect_expr_opt(e.condition());
+ let break_expr =
+ self.alloc_expr(Expr::Break { expr: None, label: None }, syntax_ptr.clone());
+ let if_expr = self.alloc_expr(
+ Expr::If { condition, then_branch: body, else_branch: Some(break_expr) },
+ syntax_ptr.clone(),
+ );
+ self.alloc_expr(Expr::Loop { body: if_expr, label }, syntax_ptr)
+ }
+
/// Desugar `ast::ForExpr` from: `[opt_ident]: for <pat> in <head> <body>` into:
/// ```ignore (pseudo-rust)
/// match IntoIterator::into_iter(<head>) {
@@ -781,7 +801,7 @@ impl ExprCollector<'_> {
pat: self.alloc_pat_desugared(some_pat),
guard: None,
expr: self.with_opt_labeled_rib(label, |this| {
- this.collect_expr_opt(e.loop_body().map(|x| x.into()))
+ this.collect_expr_opt(e.loop_body().map(|it| it.into()))
}),
};
let iter_name = Name::generate_new_name();
@@ -874,10 +894,10 @@ impl ExprCollector<'_> {
}),
guard: None,
expr: {
- let x = self.alloc_expr(Expr::Path(Path::from(break_name)), syntax_ptr.clone());
+ let it = self.alloc_expr(Expr::Path(Path::from(break_name)), syntax_ptr.clone());
let callee = self.alloc_expr(Expr::Path(try_from_residual), syntax_ptr.clone());
let result = self.alloc_expr(
- Expr::Call { callee, args: Box::new([x]), is_assignee_expr: false },
+ Expr::Call { callee, args: Box::new([it]), is_assignee_expr: false },
syntax_ptr.clone(),
);
self.alloc_expr(
@@ -893,15 +913,14 @@ impl ExprCollector<'_> {
self.alloc_expr(Expr::Match { expr, arms }, syntax_ptr)
}
- fn collect_macro_call<F, T, U>(
+ fn collect_macro_call<T, U>(
&mut self,
mcall: ast::MacroCall,
syntax_ptr: AstPtr<ast::MacroCall>,
record_diagnostics: bool,
- collector: F,
+ collector: impl FnOnce(&mut Self, Option<T>) -> U,
) -> U
where
- F: FnOnce(&mut Self, Option<T>) -> U,
T: ast::AstNode,
{
// File containing the macro call. Expansion errors will be attached here.
@@ -1240,12 +1259,12 @@ impl ExprCollector<'_> {
pats.push(self.collect_pat(first, binding_list));
binding_list.reject_new = true;
for rest in it {
- for (_, x) in binding_list.is_used.iter_mut() {
- *x = false;
+ for (_, it) in binding_list.is_used.iter_mut() {
+ *it = false;
}
pats.push(self.collect_pat(rest, binding_list));
- for (&id, &x) in binding_list.is_used.iter() {
- if !x {
+ for (&id, &is_used) in binding_list.is_used.iter() {
+ if !is_used {
self.body.bindings[id].problems =
Some(BindingProblems::NotBoundAcrossAll);
}
@@ -1352,9 +1371,9 @@ impl ExprCollector<'_> {
// FIXME: implement in a way that also builds source map and calculates assoc resolutions in type inference.
ast::Pat::RangePat(p) => {
let mut range_part_lower = |p: Option<ast::Pat>| {
- p.and_then(|x| match &x {
- ast::Pat::LiteralPat(x) => {
- Some(Box::new(LiteralOrConst::Literal(pat_literal_to_hir(x)?.0)))
+ p.and_then(|it| match &it {
+ ast::Pat::LiteralPat(it) => {
+ Some(Box::new(LiteralOrConst::Literal(pat_literal_to_hir(it)?.0)))
}
ast::Pat::IdentPat(p) => {
let name =
@@ -1451,9 +1470,7 @@ impl ExprCollector<'_> {
&self,
lifetime: Option<ast::Lifetime>,
) -> Result<Option<LabelId>, BodyDiagnostic> {
- let Some(lifetime) = lifetime else {
- return Ok(None)
- };
+ let Some(lifetime) = lifetime else { return Ok(None) };
let name = Name::new_lifetime(&lifetime);
for (rib_idx, rib) in self.label_ribs.iter().enumerate().rev() {
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body/pretty.rs b/src/tools/rust-analyzer/crates/hir-def/src/body/pretty.rs
index cd6df0e63..5d71abe37 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/body/pretty.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/body/pretty.rs
@@ -105,7 +105,7 @@ struct Printer<'a> {
needs_indent: bool,
}
-impl<'a> Write for Printer<'a> {
+impl Write for Printer<'_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
for line in s.split_inclusive('\n') {
if self.needs_indent {
@@ -125,7 +125,7 @@ impl<'a> Write for Printer<'a> {
}
}
-impl<'a> Printer<'a> {
+impl Printer<'_> {
fn indented(&mut self, f: impl FnOnce(&mut Self)) {
self.indent_level += 1;
wln!(self);
@@ -178,14 +178,6 @@ impl<'a> Printer<'a> {
w!(self, "loop ");
self.print_expr(*body);
}
- Expr::While { condition, body, label } => {
- if let Some(lbl) = label {
- w!(self, "{}: ", self.body[*lbl].name.display(self.db));
- }
- w!(self, "while ");
- self.print_expr(*condition);
- self.print_expr(*body);
- }
Expr::Call { callee, args, is_assignee_expr: _ } => {
self.print_expr(*callee);
w!(self, "(");
@@ -634,7 +626,7 @@ impl<'a> Printer<'a> {
match literal {
Literal::String(it) => w!(self, "{:?}", it),
Literal::ByteString(it) => w!(self, "\"{}\"", it.escape_ascii()),
- Literal::CString(it) => w!(self, "\"{}\\0\"", it),
+ Literal::CString(it) => w!(self, "\"{}\\0\"", it.escape_ascii()),
Literal::Char(it) => w!(self, "'{}'", it.escape_debug()),
Literal::Bool(it) => w!(self, "{}", it),
Literal::Int(i, suffix) => {
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body/scope.rs b/src/tools/rust-analyzer/crates/hir-def/src/body/scope.rs
index 69741c445..2a90a09f2 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/body/scope.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/body/scope.rs
@@ -228,11 +228,6 @@ fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope
scopes.set_scope(expr, scope);
compute_block_scopes(statements, *tail, body, scopes, &mut scope);
}
- Expr::While { condition, body: body_expr, label } => {
- let mut scope = scopes.new_labeled_scope(*scope, make_label(label));
- compute_expr_scopes(*condition, body, scopes, &mut scope);
- compute_expr_scopes(*body_expr, body, scopes, &mut scope);
- }
Expr::Loop { body: body_expr, label } => {
let mut scope = scopes.new_labeled_scope(*scope, make_label(label));
compute_expr_scopes(*body_expr, body, scopes, &mut scope);
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body/tests.rs b/src/tools/rust-analyzer/crates/hir-def/src/body/tests.rs
index edee2c7ff..d55820116 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/body/tests.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/body/tests.rs
@@ -3,12 +3,12 @@ mod block;
use base_db::{fixture::WithFixture, SourceDatabase};
use expect_test::Expect;
-use crate::ModuleDefId;
+use crate::{test_db::TestDB, ModuleDefId};
use super::*;
fn lower(ra_fixture: &str) -> Arc<Body> {
- let db = crate::test_db::TestDB::with_files(ra_fixture);
+ let db = TestDB::with_files(ra_fixture);
let krate = db.crate_graph().iter().next().unwrap();
let def_map = db.crate_def_map(krate);
@@ -25,15 +25,15 @@ fn lower(ra_fixture: &str) -> Arc<Body> {
db.body(fn_def.unwrap().into())
}
-fn block_def_map_at(ra_fixture: &str) -> String {
- let (db, position) = crate::test_db::TestDB::with_position(ra_fixture);
+fn def_map_at(ra_fixture: &str) -> String {
+ let (db, position) = TestDB::with_position(ra_fixture);
let module = db.module_at_position(position);
module.def_map(&db).dump(&db)
}
fn check_block_scopes_at(ra_fixture: &str, expect: Expect) {
- let (db, position) = crate::test_db::TestDB::with_position(ra_fixture);
+ let (db, position) = TestDB::with_position(ra_fixture);
let module = db.module_at_position(position);
let actual = module.def_map(&db).dump_block_scopes(&db);
@@ -41,7 +41,7 @@ fn check_block_scopes_at(ra_fixture: &str, expect: Expect) {
}
fn check_at(ra_fixture: &str, expect: Expect) {
- let actual = block_def_map_at(ra_fixture);
+ let actual = def_map_at(ra_fixture);
expect.assert_eq(&actual);
}
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body/tests/block.rs b/src/tools/rust-analyzer/crates/hir-def/src/body/tests/block.rs
index 6e77744f2..4e015a7fb 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/body/tests/block.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/body/tests/block.rs
@@ -134,6 +134,47 @@ struct Struct {}
}
#[test]
+fn super_imports_2() {
+ check_at(
+ r#"
+fn outer() {
+ mod m {
+ struct ResolveMe {}
+ fn middle() {
+ mod m2 {
+ fn inner() {
+ use super::ResolveMe;
+ $0
+ }
+ }
+ }
+ }
+}
+"#,
+ expect![[r#"
+ block scope
+ ResolveMe: t
+
+ block scope
+ m2: t
+
+ block scope::m2
+ inner: v
+
+ block scope
+ m: t
+
+ block scope::m
+ ResolveMe: t
+ middle: v
+
+ crate
+ outer: v
+ "#]],
+ );
+}
+
+#[test]
fn nested_module_scoping() {
check_block_scopes_at(
r#"
@@ -156,6 +197,42 @@ fn f() {
}
#[test]
+fn self_imports() {
+ check_at(
+ r#"
+fn f() {
+ mod m {
+ struct ResolveMe {}
+ fn g() {
+ fn h() {
+ use self::ResolveMe;
+ $0
+ }
+ }
+ }
+}
+"#,
+ expect![[r#"
+ block scope
+ ResolveMe: t
+
+ block scope
+ h: v
+
+ block scope
+ m: t
+
+ block scope::m
+ ResolveMe: t
+ g: v
+
+ crate
+ f: v
+ "#]],
+ );
+}
+
+#[test]
fn legacy_macro_items() {
// Checks that legacy-scoped `macro_rules!` from parent namespaces are resolved and expanded
// correctly.
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/child_by_source.rs b/src/tools/rust-analyzer/crates/hir-def/src/child_by_source.rs
index bb79e28f2..4cfd318a4 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/child_by_source.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/child_by_source.rs
@@ -14,8 +14,8 @@ use crate::{
item_scope::ItemScope,
nameres::DefMap,
src::{HasChildSource, HasSource},
- AdtId, AssocItemId, DefWithBodyId, EnumId, EnumVariantId, FieldId, ImplId, Lookup, MacroId,
- ModuleDefId, ModuleId, TraitId, VariantId,
+ AdtId, AssocItemId, DefWithBodyId, EnumId, EnumVariantId, ExternCrateId, FieldId, ImplId,
+ Lookup, MacroId, ModuleDefId, ModuleId, TraitId, UseId, VariantId,
};
pub trait ChildBySource {
@@ -91,6 +91,8 @@ impl ChildBySource for ItemScope {
fn child_by_source_to(&self, db: &dyn DefDatabase, res: &mut DynMap, file_id: HirFileId) {
self.declarations().for_each(|item| add_module_def(db, res, file_id, item));
self.impls().for_each(|imp| add_impl(db, res, file_id, imp));
+ self.extern_crate_decls().for_each(|ext| add_extern_crate(db, res, file_id, ext));
+ self.use_decls().for_each(|ext| add_use(db, res, file_id, ext));
self.unnamed_consts().for_each(|konst| {
let loc = konst.lookup(db);
if loc.id.file_id() == file_id {
@@ -167,6 +169,23 @@ impl ChildBySource for ItemScope {
map[keys::IMPL].insert(loc.source(db).value, imp)
}
}
+ fn add_extern_crate(
+ db: &dyn DefDatabase,
+ map: &mut DynMap,
+ file_id: HirFileId,
+ ext: ExternCrateId,
+ ) {
+ let loc = ext.lookup(db);
+ if loc.id.file_id() == file_id {
+ map[keys::EXTERN_CRATE].insert(loc.source(db).value, ext)
+ }
+ }
+ fn add_use(db: &dyn DefDatabase, map: &mut DynMap, file_id: HirFileId, ext: UseId) {
+ let loc = ext.lookup(db);
+ if loc.id.file_id() == file_id {
+ map[keys::USE].insert(loc.source(db).value, ext)
+ }
+ }
}
}
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/data.rs b/src/tools/rust-analyzer/crates/hir-def/src/data.rs
index 40e6a4308..91db68058 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/data.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/data.rs
@@ -2,6 +2,7 @@
pub mod adt;
+use base_db::CrateId;
use hir_expand::{
name::Name, AstId, ExpandResult, HirFileId, InFile, MacroCallId, MacroCallKind, MacroDefKind,
};
@@ -24,11 +25,12 @@ use crate::{
proc_macro::{parse_macro_name_and_helper_attrs, ProcMacroKind},
DefMap, MacroSubNs,
},
+ path::ImportAlias,
type_ref::{TraitRef, TypeBound, TypeRef},
visibility::RawVisibility,
- AssocItemId, AstIdWithPath, ConstId, ConstLoc, FunctionId, FunctionLoc, HasModule, ImplId,
- Intern, ItemContainerId, ItemLoc, Lookup, Macro2Id, MacroRulesId, ModuleId, ProcMacroId,
- StaticId, TraitAliasId, TraitId, TypeAliasId, TypeAliasLoc,
+ AssocItemId, AstIdWithPath, ConstId, ConstLoc, ExternCrateId, FunctionId, FunctionLoc,
+ HasModule, ImplId, Intern, ItemContainerId, ItemLoc, Lookup, Macro2Id, MacroRulesId, ModuleId,
+ ProcMacroId, StaticId, TraitAliasId, TraitId, TypeAliasId, TypeAliasLoc,
};
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -424,6 +426,7 @@ impl MacroRulesData {
Arc::new(MacroRulesData { name: makro.name.clone(), macro_export })
}
}
+
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProcMacroData {
pub name: Name,
@@ -461,6 +464,42 @@ impl ProcMacroData {
}
#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct ExternCrateDeclData {
+ pub name: Name,
+ pub alias: Option<ImportAlias>,
+ pub visibility: RawVisibility,
+ pub crate_id: Option<CrateId>,
+}
+
+impl ExternCrateDeclData {
+ pub(crate) fn extern_crate_decl_data_query(
+ db: &dyn DefDatabase,
+ extern_crate: ExternCrateId,
+ ) -> Arc<ExternCrateDeclData> {
+ let loc = extern_crate.lookup(db);
+ let item_tree = loc.id.item_tree(db);
+ let extern_crate = &item_tree[loc.id.value];
+
+ let name = extern_crate.name.clone();
+ let crate_id = if name == hir_expand::name![self] {
+ Some(loc.container.krate())
+ } else {
+ db.crate_def_map(loc.container.krate())
+ .extern_prelude()
+ .find(|&(prelude_name, ..)| *prelude_name == name)
+ .map(|(_, root)| root.krate())
+ };
+
+ Arc::new(Self {
+ name: extern_crate.name.clone(),
+ visibility: item_tree[extern_crate.visibility].clone(),
+ alias: extern_crate.alias.clone(),
+ crate_id,
+ })
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConstData {
/// `None` for `const _: () = ();`
pub name: Option<Name>,
@@ -573,7 +612,7 @@ impl<'a> AssocItemCollector<'a> {
if !attrs.is_cfg_enabled(self.expander.cfg_options()) {
self.diagnostics.push(DefDiagnostic::unconfigured_code(
self.module_id.local_id,
- InFile::new(self.expander.current_file_id(), item.ast_id(item_tree).upcast()),
+ InFile::new(self.expander.current_file_id(), item.ast_id(item_tree).erase()),
attrs.cfg().unwrap(),
self.expander.cfg_options().clone(),
));
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/data/adt.rs b/src/tools/rust-analyzer/crates/hir-def/src/data/adt.rs
index 6db5abccc..c8df3f3f9 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/data/adt.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/data/adt.rs
@@ -18,7 +18,6 @@ use triomphe::Arc;
use crate::{
builtin_type::{BuiltinInt, BuiltinUint},
db::DefDatabase,
- expander::CfgExpander,
item_tree::{AttrOwner, Field, FieldAstId, Fields, ItemTree, ModItem, RawVisibilityId},
lang_item::LangItem,
lower::LowerCtx,
@@ -29,8 +28,8 @@ use crate::{
tt::{Delimiter, DelimiterKind, Leaf, Subtree, TokenTree},
type_ref::TypeRef,
visibility::RawVisibility,
- EnumId, LocalEnumVariantId, LocalFieldId, LocalModuleId, Lookup, ModuleId, StructId, UnionId,
- VariantId,
+ EnumId, EnumLoc, LocalEnumVariantId, LocalFieldId, LocalModuleId, Lookup, ModuleId, StructId,
+ UnionId, VariantId,
};
/// Note that we use `StructData` for unions as well!
@@ -76,6 +75,7 @@ pub struct EnumData {
pub struct EnumVariantData {
pub name: Name,
pub variant_data: Arc<VariantData>,
+ pub tree_id: la_arena::Idx<crate::item_tree::Variant>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -147,6 +147,7 @@ fn parse_repr_tt(tt: &Subtree) -> Option<ReprOptions> {
}
"C" => ReprFlags::IS_C,
"transparent" => ReprFlags::IS_TRANSPARENT,
+ "simd" => ReprFlags::IS_SIMD,
repr => {
if let Some(builtin) = BuiltinInt::from_suffix(repr)
.map(Either::Left)
@@ -325,11 +326,12 @@ impl EnumData {
variants.alloc(EnumVariantData {
name: var.name.clone(),
variant_data: Arc::new(var_data),
+ tree_id,
});
} else {
diagnostics.push(DefDiagnostic::unconfigured_code(
loc.container.local_id,
- InFile::new(loc.id.file_id(), var.ast_id.upcast()),
+ InFile::new(loc.id.file_id(), var.ast_id.erase()),
attrs.cfg().unwrap(),
cfg_options.clone(),
))
@@ -367,9 +369,10 @@ impl HasChildSource<LocalEnumVariantId> for EnumId {
&self,
db: &dyn DefDatabase,
) -> InFile<ArenaMap<LocalEnumVariantId, Self::Value>> {
- let src = self.lookup(db).source(db);
+ let loc = &self.lookup(db);
+ let src = loc.source(db);
let mut trace = Trace::new_for_map();
- lower_enum(db, &mut trace, &src, self.lookup(db).container);
+ lower_enum(db, &mut trace, &src, loc);
src.with_value(trace.into_map())
}
}
@@ -378,31 +381,58 @@ fn lower_enum(
db: &dyn DefDatabase,
trace: &mut Trace<EnumVariantData, ast::Variant>,
ast: &InFile<ast::Enum>,
- module_id: ModuleId,
+ loc: &EnumLoc,
) {
- let expander = CfgExpander::new(db, ast.file_id, module_id.krate);
+ let item_tree = loc.id.item_tree(db);
+ let krate = loc.container.krate;
+
+ let item_tree_variants = item_tree[loc.id.value].variants.clone();
+
+ let cfg_options = &db.crate_graph()[krate].cfg_options;
let variants = ast
.value
.variant_list()
.into_iter()
.flat_map(|it| it.variants())
- .filter(|var| expander.is_cfg_enabled(db, var));
- for var in variants {
+ .zip(item_tree_variants)
+ .filter(|&(_, item_tree_id)| {
+ item_tree.attrs(db, krate, item_tree_id.into()).is_cfg_enabled(cfg_options)
+ });
+ for (var, item_tree_id) in variants {
trace.alloc(
|| var.clone(),
|| EnumVariantData {
name: var.name().map_or_else(Name::missing, |it| it.as_name()),
- variant_data: Arc::new(VariantData::new(db, ast.with_value(var.kind()), module_id)),
+ variant_data: Arc::new(VariantData::new(
+ db,
+ ast.with_value(var.kind()),
+ loc.container,
+ &item_tree,
+ item_tree_id,
+ )),
+ tree_id: item_tree_id,
},
);
}
}
impl VariantData {
- fn new(db: &dyn DefDatabase, flavor: InFile<ast::StructKind>, module_id: ModuleId) -> Self {
- let mut expander = CfgExpander::new(db, flavor.file_id, module_id.krate);
+ fn new(
+ db: &dyn DefDatabase,
+ flavor: InFile<ast::StructKind>,
+ module_id: ModuleId,
+ item_tree: &ItemTree,
+ variant: la_arena::Idx<crate::item_tree::Variant>,
+ ) -> Self {
let mut trace = Trace::new_for_arena();
- match lower_struct(db, &mut expander, &mut trace, &flavor) {
+ match lower_struct(
+ db,
+ &mut trace,
+ &flavor,
+ module_id.krate,
+ item_tree,
+ &item_tree[variant].fields,
+ ) {
StructKind::Tuple => VariantData::Tuple(trace.into_arena()),
StructKind::Record => VariantData::Record(trace.into_arena()),
StructKind::Unit => VariantData::Unit,
@@ -434,28 +464,43 @@ impl HasChildSource<LocalFieldId> for VariantId {
type Value = Either<ast::TupleField, ast::RecordField>;
fn child_source(&self, db: &dyn DefDatabase) -> InFile<ArenaMap<LocalFieldId, Self::Value>> {
- let (src, module_id) = match self {
+ let item_tree;
+ let (src, fields, container) = match *self {
VariantId::EnumVariantId(it) => {
// I don't really like the fact that we call into parent source
// here, this might add to more queries then necessary.
+ let lookup = it.parent.lookup(db);
+ item_tree = lookup.id.item_tree(db);
let src = it.parent.child_source(db);
- (src.map(|map| map[it.local_id].kind()), it.parent.lookup(db).container)
+ let tree_id = db.enum_data(it.parent).variants[it.local_id].tree_id;
+ let fields = &item_tree[tree_id].fields;
+ (src.map(|map| map[it.local_id].kind()), fields, lookup.container)
}
VariantId::StructId(it) => {
- (it.lookup(db).source(db).map(|it| it.kind()), it.lookup(db).container)
+ let lookup = it.lookup(db);
+ item_tree = lookup.id.item_tree(db);
+ (
+ lookup.source(db).map(|it| it.kind()),
+ &item_tree[lookup.id.value].fields,
+ lookup.container,
+ )
+ }
+ VariantId::UnionId(it) => {
+ let lookup = it.lookup(db);
+ item_tree = lookup.id.item_tree(db);
+ (
+ lookup.source(db).map(|it| {
+ it.record_field_list()
+ .map(ast::StructKind::Record)
+ .unwrap_or(ast::StructKind::Unit)
+ }),
+ &item_tree[lookup.id.value].fields,
+ lookup.container,
+ )
}
- VariantId::UnionId(it) => (
- it.lookup(db).source(db).map(|it| {
- it.record_field_list()
- .map(ast::StructKind::Record)
- .unwrap_or(ast::StructKind::Unit)
- }),
- it.lookup(db).container,
- ),
};
- let mut expander = CfgExpander::new(db, src.file_id, module_id.krate);
let mut trace = Trace::new_for_map();
- lower_struct(db, &mut expander, &mut trace, &src);
+ lower_struct(db, &mut trace, &src, container.krate, &item_tree, fields);
src.with_value(trace.into_map())
}
}
@@ -469,16 +514,19 @@ pub enum StructKind {
fn lower_struct(
db: &dyn DefDatabase,
- expander: &mut CfgExpander,
trace: &mut Trace<FieldData, Either<ast::TupleField, ast::RecordField>>,
ast: &InFile<ast::StructKind>,
+ krate: CrateId,
+ item_tree: &ItemTree,
+ fields: &Fields,
) -> StructKind {
- let ctx = LowerCtx::new(db, &expander.hygiene(), ast.file_id);
+ let ctx = LowerCtx::with_file_id(db, ast.file_id);
- match &ast.value {
- ast::StructKind::Tuple(fl) => {
- for (i, fd) in fl.fields().enumerate() {
- if !expander.is_cfg_enabled(db, &fd) {
+ match (&ast.value, fields) {
+ (ast::StructKind::Tuple(fl), Fields::Tuple(fields)) => {
+ let cfg_options = &db.crate_graph()[krate].cfg_options;
+ for ((i, fd), item_tree_id) in fl.fields().enumerate().zip(fields.clone()) {
+ if !item_tree.attrs(db, krate, item_tree_id.into()).is_cfg_enabled(cfg_options) {
continue;
}
@@ -493,9 +541,10 @@ fn lower_struct(
}
StructKind::Tuple
}
- ast::StructKind::Record(fl) => {
- for fd in fl.fields() {
- if !expander.is_cfg_enabled(db, &fd) {
+ (ast::StructKind::Record(fl), Fields::Record(fields)) => {
+ let cfg_options = &db.crate_graph()[krate].cfg_options;
+ for (fd, item_tree_id) in fl.fields().zip(fields.clone()) {
+ if !item_tree.attrs(db, krate, item_tree_id.into()).is_cfg_enabled(cfg_options) {
continue;
}
@@ -510,7 +559,7 @@ fn lower_struct(
}
StructKind::Record
}
- ast::StructKind::Unit => StructKind::Unit,
+ _ => StructKind::Unit,
}
}
@@ -539,8 +588,8 @@ fn lower_fields(
InFile::new(
current_file_id,
match field.ast_id {
- FieldAstId::Record(it) => it.upcast(),
- FieldAstId::Tuple(it) => it.upcast(),
+ FieldAstId::Record(it) => it.erase(),
+ FieldAstId::Tuple(it) => it.erase(),
},
),
attrs.cfg().unwrap(),
@@ -563,8 +612,8 @@ fn lower_fields(
InFile::new(
current_file_id,
match field.ast_id {
- FieldAstId::Record(it) => it.upcast(),
- FieldAstId::Tuple(it) => it.upcast(),
+ FieldAstId::Record(it) => it.erase(),
+ FieldAstId::Tuple(it) => it.erase(),
},
),
attrs.cfg().unwrap(),
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/db.rs b/src/tools/rust-analyzer/crates/hir-def/src/db.rs
index 04ec47f84..e34a6768f 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/db.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/db.rs
@@ -12,27 +12,31 @@ use crate::{
body::{scope::ExprScopes, Body, BodySourceMap},
data::{
adt::{EnumData, StructData},
- ConstData, FunctionData, ImplData, Macro2Data, MacroRulesData, ProcMacroData, StaticData,
- TraitAliasData, TraitData, TypeAliasData,
+ ConstData, ExternCrateDeclData, FunctionData, ImplData, Macro2Data, MacroRulesData,
+ ProcMacroData, StaticData, TraitAliasData, TraitData, TypeAliasData,
},
generics::GenericParams,
import_map::ImportMap,
item_tree::{AttrOwner, ItemTree},
- lang_item::{LangItem, LangItemTarget, LangItems},
+ lang_item::{self, LangItem, LangItemTarget, LangItems},
nameres::{diagnostics::DefDiagnostic, DefMap},
visibility::{self, Visibility},
AttrDefId, BlockId, BlockLoc, ConstBlockId, ConstBlockLoc, ConstId, ConstLoc, DefWithBodyId,
- EnumId, EnumLoc, ExternBlockId, ExternBlockLoc, FunctionId, FunctionLoc, GenericDefId, ImplId,
- ImplLoc, InTypeConstId, InTypeConstLoc, LocalEnumVariantId, LocalFieldId, Macro2Id, Macro2Loc,
- MacroRulesId, MacroRulesLoc, ProcMacroId, ProcMacroLoc, StaticId, StaticLoc, StructId,
- StructLoc, TraitAliasId, TraitAliasLoc, TraitId, TraitLoc, TypeAliasId, TypeAliasLoc, UnionId,
- UnionLoc, VariantId,
+ EnumId, EnumLoc, ExternBlockId, ExternBlockLoc, ExternCrateId, ExternCrateLoc, FunctionId,
+ FunctionLoc, GenericDefId, ImplId, ImplLoc, InTypeConstId, InTypeConstLoc, LocalEnumVariantId,
+ LocalFieldId, Macro2Id, Macro2Loc, MacroRulesId, MacroRulesLoc, ProcMacroId, ProcMacroLoc,
+ StaticId, StaticLoc, StructId, StructLoc, TraitAliasId, TraitAliasLoc, TraitId, TraitLoc,
+ TypeAliasId, TypeAliasLoc, UnionId, UnionLoc, UseId, UseLoc, VariantId,
};
#[salsa::query_group(InternDatabaseStorage)]
pub trait InternDatabase: SourceDatabase {
// region: items
#[salsa::interned]
+ fn intern_use(&self, loc: UseLoc) -> UseId;
+ #[salsa::interned]
+ fn intern_extern_crate(&self, loc: ExternCrateLoc) -> ExternCrateId;
+ #[salsa::interned]
fn intern_function(&self, loc: FunctionLoc) -> FunctionId;
#[salsa::interned]
fn intern_struct(&self, loc: StructLoc) -> StructId;
@@ -160,6 +164,9 @@ pub trait DefDatabase: InternDatabase + ExpandDatabase + Upcast<dyn ExpandDataba
#[salsa::invoke(ProcMacroData::proc_macro_data_query)]
fn proc_macro_data(&self, makro: ProcMacroId) -> Arc<ProcMacroData>;
+ #[salsa::invoke(ExternCrateDeclData::extern_crate_decl_data_query)]
+ fn extern_crate_decl_data(&self, extern_crate: ExternCrateId) -> Arc<ExternCrateDeclData>;
+
// endregion:data
#[salsa::invoke(Body::body_with_source_map_query)]
@@ -197,6 +204,9 @@ pub trait DefDatabase: InternDatabase + ExpandDatabase + Upcast<dyn ExpandDataba
#[salsa::invoke(AttrsWithOwner::attrs_query)]
fn attrs(&self, def: AttrDefId) -> Attrs;
+ #[salsa::invoke(lang_item::lang_attr_query)]
+ fn lang_attr(&self, def: AttrDefId) -> Option<LangItem>;
+
#[salsa::transparent]
#[salsa::invoke(AttrsWithOwner::attrs_with_owner)]
fn attrs_with_owner(&self, def: AttrDefId) -> AttrsWithOwner;
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/dyn_map/keys.rs b/src/tools/rust-analyzer/crates/hir-def/src/dyn_map/keys.rs
index f30be6b64..d0f2bfab4 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/dyn_map/keys.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/dyn_map/keys.rs
@@ -8,9 +8,9 @@ use syntax::{ast, AstNode, AstPtr};
use crate::{
dyn_map::{DynMap, Policy},
- ConstId, EnumId, EnumVariantId, FieldId, FunctionId, ImplId, LifetimeParamId, Macro2Id,
- MacroRulesId, ProcMacroId, StaticId, StructId, TraitAliasId, TraitId, TypeAliasId,
- TypeOrConstParamId, UnionId,
+ ConstId, EnumId, EnumVariantId, ExternCrateId, FieldId, FunctionId, ImplId, LifetimeParamId,
+ Macro2Id, MacroRulesId, ProcMacroId, StaticId, StructId, TraitAliasId, TraitId, TypeAliasId,
+ TypeOrConstParamId, UnionId, UseId,
};
pub type Key<K, V> = crate::dyn_map::Key<K, V, AstPtrPolicy<K, V>>;
@@ -25,6 +25,8 @@ pub const TRAIT_ALIAS: Key<ast::TraitAlias, TraitAliasId> = Key::new();
pub const STRUCT: Key<ast::Struct, StructId> = Key::new();
pub const UNION: Key<ast::Union, UnionId> = Key::new();
pub const ENUM: Key<ast::Enum, EnumId> = Key::new();
+pub const EXTERN_CRATE: Key<ast::ExternCrate, ExternCrateId> = Key::new();
+pub const USE: Key<ast::Use, UseId> = Key::new();
pub const VARIANT: Key<ast::Variant, EnumVariantId> = Key::new();
pub const TUPLE_FIELD: Key<ast::TupleField, FieldId> = Key::new();
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expander.rs b/src/tools/rust-analyzer/crates/hir-def/src/expander.rs
index a588827c8..6db8398bc 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/expander.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/expander.rs
@@ -15,18 +15,11 @@ use crate::{
MacroId, ModuleId,
};
-/// A subset of Expander that only deals with cfg attributes. We only need it to
-/// avoid cyclic queries in crate def map during enum processing.
#[derive(Debug)]
-pub(crate) struct CfgExpander {
+pub struct Expander {
cfg_options: CfgOptions,
hygiene: Hygiene,
krate: CrateId,
-}
-
-#[derive(Debug)]
-pub struct Expander {
- cfg_expander: CfgExpander,
pub(crate) current_file_id: HirFileId,
pub(crate) module: ModuleId,
/// `recursion_depth == usize::MAX` indicates that the recursion limit has been reached.
@@ -34,41 +27,23 @@ pub struct Expander {
recursion_limit: Limit,
}
-impl CfgExpander {
- pub(crate) fn new(
- db: &dyn DefDatabase,
- current_file_id: HirFileId,
- krate: CrateId,
- ) -> CfgExpander {
- let hygiene = Hygiene::new(db.upcast(), current_file_id);
- let cfg_options = db.crate_graph()[krate].cfg_options.clone();
- CfgExpander { cfg_options, hygiene, krate }
- }
-
- pub(crate) fn parse_attrs(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> Attrs {
- Attrs::filter(db, self.krate, RawAttrs::new(db.upcast(), owner, &self.hygiene))
- }
-
- pub(crate) fn is_cfg_enabled(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> bool {
- let attrs = self.parse_attrs(db, owner);
- attrs.is_cfg_enabled(&self.cfg_options)
- }
-
- pub(crate) fn hygiene(&self) -> &Hygiene {
- &self.hygiene
- }
-}
-
impl Expander {
pub fn new(db: &dyn DefDatabase, current_file_id: HirFileId, module: ModuleId) -> Expander {
- let cfg_expander = CfgExpander::new(db, current_file_id, module.krate);
let recursion_limit = db.recursion_limit(module.krate);
#[cfg(not(test))]
let recursion_limit = Limit::new(recursion_limit as usize);
// Without this, `body::tests::your_stack_belongs_to_me` stack-overflows in debug
#[cfg(test)]
let recursion_limit = Limit::new(std::cmp::min(32, recursion_limit as usize));
- Expander { cfg_expander, current_file_id, module, recursion_depth: 0, recursion_limit }
+ Expander {
+ current_file_id,
+ module,
+ recursion_depth: 0,
+ recursion_limit,
+ cfg_options: db.crate_graph()[module.krate].cfg_options.clone(),
+ hygiene: Hygiene::new(db.upcast(), current_file_id),
+ krate: module.krate,
+ }
}
pub fn enter_expand<T: ast::AstNode>(
@@ -120,7 +95,7 @@ impl Expander {
}
pub fn exit(&mut self, db: &dyn DefDatabase, mut mark: Mark) {
- self.cfg_expander.hygiene = Hygiene::new(db.upcast(), mark.file_id);
+ self.hygiene = Hygiene::new(db.upcast(), mark.file_id);
self.current_file_id = mark.file_id;
if self.recursion_depth == u32::MAX {
// Recursion limit has been reached somewhere in the macro expansion tree. Reset the
@@ -135,7 +110,7 @@ impl Expander {
}
pub fn ctx<'a>(&self, db: &'a dyn DefDatabase) -> LowerCtx<'a> {
- LowerCtx::new(db, &self.cfg_expander.hygiene, self.current_file_id)
+ LowerCtx::new(db, &self.hygiene, self.current_file_id)
}
pub(crate) fn to_source<T>(&self, value: T) -> InFile<T> {
@@ -143,11 +118,11 @@ impl Expander {
}
pub(crate) fn parse_attrs(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> Attrs {
- self.cfg_expander.parse_attrs(db, owner)
+ Attrs::filter(db, self.krate, RawAttrs::new(db.upcast(), owner, &self.hygiene))
}
pub(crate) fn cfg_options(&self) -> &CfgOptions {
- &self.cfg_expander.cfg_options
+ &self.cfg_options
}
pub fn current_file_id(&self) -> HirFileId {
@@ -155,7 +130,7 @@ impl Expander {
}
pub(crate) fn parse_path(&mut self, db: &dyn DefDatabase, path: ast::Path) -> Option<Path> {
- let ctx = LowerCtx::new(db, &self.cfg_expander.hygiene, self.current_file_id);
+ let ctx = LowerCtx::new(db, &self.hygiene, self.current_file_id);
Path::from_src(path, &ctx)
}
@@ -189,18 +164,26 @@ impl Expander {
return ExpandResult { value: None, err };
};
- Self::enter_expand_inner(db, call_id, err).map(|value| {
- value.and_then(|InFile { file_id, value }| {
- let parse = value.cast::<T>()?;
-
- self.recursion_depth += 1;
- self.cfg_expander.hygiene = Hygiene::new(db.upcast(), file_id);
- let old_file_id = std::mem::replace(&mut self.current_file_id, file_id);
- let mark =
- Mark { file_id: old_file_id, bomb: DropBomb::new("expansion mark dropped") };
- Some((mark, parse))
- })
- })
+ let res = Self::enter_expand_inner(db, call_id, err);
+ match res.err {
+ // If proc-macro is disabled or unresolved, we want to expand to a missing expression
+ // instead of an empty tree which might end up in an empty block.
+ Some(ExpandError::UnresolvedProcMacro(_)) => res.map(|_| None),
+ _ => res.map(|value| {
+ value.and_then(|InFile { file_id, value }| {
+ let parse = value.cast::<T>()?;
+
+ self.recursion_depth += 1;
+ self.hygiene = Hygiene::new(db.upcast(), file_id);
+ let old_file_id = std::mem::replace(&mut self.current_file_id, file_id);
+ let mark = Mark {
+ file_id: old_file_id,
+ bomb: DropBomb::new("expansion mark dropped"),
+ };
+ Some((mark, parse))
+ })
+ }),
+ }
}
}
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/find_path.rs b/src/tools/rust-analyzer/crates/hir-def/src/find_path.rs
index 8c49ae1c4..df2af4c89 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/find_path.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/find_path.rs
@@ -360,7 +360,7 @@ fn calculate_best_path(
prefer_no_std,
)?;
cov_mark::hit!(partially_imported);
- path.push_segment(info.path.segments.last()?.clone());
+ path.push_segment(info.name.clone());
Some(path)
})
});
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/generics.rs b/src/tools/rust-analyzer/crates/hir-def/src/generics.rs
index f19c3f028..d7d44e413 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/generics.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/generics.rs
@@ -67,21 +67,21 @@ pub enum TypeOrConstParamData {
impl TypeOrConstParamData {
pub fn name(&self) -> Option<&Name> {
match self {
- TypeOrConstParamData::TypeParamData(x) => x.name.as_ref(),
- TypeOrConstParamData::ConstParamData(x) => Some(&x.name),
+ TypeOrConstParamData::TypeParamData(it) => it.name.as_ref(),
+ TypeOrConstParamData::ConstParamData(it) => Some(&it.name),
}
}
pub fn has_default(&self) -> bool {
match self {
- TypeOrConstParamData::TypeParamData(x) => x.default.is_some(),
- TypeOrConstParamData::ConstParamData(x) => x.has_default,
+ TypeOrConstParamData::TypeParamData(it) => it.default.is_some(),
+ TypeOrConstParamData::ConstParamData(it) => it.has_default,
}
}
pub fn type_param(&self) -> Option<&TypeParamData> {
match self {
- TypeOrConstParamData::TypeParamData(x) => Some(x),
+ TypeOrConstParamData::TypeParamData(it) => Some(it),
TypeOrConstParamData::ConstParamData(_) => None,
}
}
@@ -89,14 +89,14 @@ impl TypeOrConstParamData {
pub fn const_param(&self) -> Option<&ConstParamData> {
match self {
TypeOrConstParamData::TypeParamData(_) => None,
- TypeOrConstParamData::ConstParamData(x) => Some(x),
+ TypeOrConstParamData::ConstParamData(it) => Some(it),
}
}
pub fn is_trait_self(&self) -> bool {
match self {
- TypeOrConstParamData::TypeParamData(x) => {
- x.provenance == TypeParamProvenance::TraitSelf
+ TypeOrConstParamData::TypeParamData(it) => {
+ it.provenance == TypeParamProvenance::TraitSelf
}
TypeOrConstParamData::ConstParamData(_) => false,
}
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/hir.rs b/src/tools/rust-analyzer/crates/hir-def/src/hir.rs
index 500e88006..6591c92ac 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/hir.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/hir.rs
@@ -85,7 +85,7 @@ impl fmt::Display for FloatTypeWrapper {
pub enum Literal {
String(Box<str>),
ByteString(Box<[u8]>),
- CString(Box<str>),
+ CString(Box<[u8]>),
Char(char),
Bool(bool),
Int(i128, Option<BuiltinInt>),
@@ -191,11 +191,6 @@ pub enum Expr {
body: ExprId,
label: Option<LabelId>,
},
- While {
- condition: ExprId,
- body: ExprId,
- label: Option<LabelId>,
- },
Call {
callee: ExprId,
args: Box<[ExprId]>,
@@ -379,10 +374,6 @@ impl Expr {
}
}
Expr::Loop { body, .. } => f(*body),
- Expr::While { condition, body, .. } => {
- f(*condition);
- f(*body);
- }
Expr::Call { callee, args, .. } => {
f(*callee);
args.iter().copied().for_each(f);
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/hir/type_ref.rs b/src/tools/rust-analyzer/crates/hir-def/src/hir/type_ref.rs
index fa1f4933a..57f023ef3 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/hir/type_ref.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/hir/type_ref.rs
@@ -425,8 +425,8 @@ impl ConstRef {
}
match expr {
ast::Expr::PathExpr(p) if is_path_ident(&p) => {
- match p.path().and_then(|x| x.segment()).and_then(|x| x.name_ref()) {
- Some(x) => Self::Path(x.as_name()),
+ match p.path().and_then(|it| it.segment()).and_then(|it| it.name_ref()) {
+ Some(it) => Self::Path(it.as_name()),
None => Self::Scalar(LiteralConstRef::Unknown),
}
}
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/import_map.rs b/src/tools/rust-analyzer/crates/hir-def/src/import_map.rs
index 48532655e..4b2e5041a 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/import_map.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/import_map.rs
@@ -1,13 +1,14 @@
//! A map of all publicly exported items in a crate.
+use std::collections::hash_map::Entry;
use std::{fmt, hash::BuildHasherDefault};
use base_db::CrateId;
use fst::{self, Streamer};
use hir_expand::name::Name;
-use indexmap::{map::Entry, IndexMap};
+use indexmap::IndexMap;
use itertools::Itertools;
-use rustc_hash::{FxHashSet, FxHasher};
+use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
use triomphe::Arc;
use crate::{
@@ -17,52 +18,23 @@ use crate::{
type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>;
+// FIXME: Support aliases: an item may be exported under multiple names, so `ImportInfo` should
+// have `Vec<(Name, ModuleId)>` instead of `(Name, ModuleId)`.
/// Item import details stored in the `ImportMap`.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ImportInfo {
- /// A path that can be used to import the item, relative to the crate's root.
- pub path: ImportPath,
+ /// A name that can be used to import the item, relative to the crate's root.
+ pub name: Name,
/// The module containing this item.
pub container: ModuleId,
/// Whether the import is a trait associated item or not.
pub is_trait_assoc_item: bool,
}
-#[derive(Debug, Clone, Eq, PartialEq)]
-pub struct ImportPath {
- pub segments: Vec<Name>,
-}
-
-impl ImportPath {
- pub fn display<'a>(&'a self, db: &'a dyn DefDatabase) -> impl fmt::Display + 'a {
- struct Display<'a> {
- db: &'a dyn DefDatabase,
- path: &'a ImportPath,
- }
- impl fmt::Display for Display<'_> {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- fmt::Display::fmt(
- &self.path.segments.iter().map(|it| it.display(self.db.upcast())).format("::"),
- f,
- )
- }
- }
- Display { db, path: self }
- }
-
- fn len(&self) -> usize {
- self.segments.len()
- }
-}
-
-/// A map from publicly exported items to the path needed to import/name them from a downstream
-/// crate.
+/// A map from publicly exported items to its name.
///
/// Reexports of items are taken into account, ie. if something is exported under multiple
/// names, the one with the shortest import path will be used.
-///
-/// Note that all paths are relative to the containing crate's root, so the crate name still needs
-/// to be prepended to the `ModPath` before the path is valid.
#[derive(Default)]
pub struct ImportMap {
map: FxIndexMap<ItemInNs, ImportInfo>,
@@ -70,122 +42,58 @@ pub struct ImportMap {
/// List of keys stored in `map`, sorted lexicographically by their `ModPath`. Indexed by the
/// values returned by running `fst`.
///
- /// Since a path can refer to multiple items due to namespacing, we store all items with the
- /// same path right after each other. This allows us to find all items after the FST gives us
+ /// Since a name can refer to multiple items due to namespacing, we store all items with the
+ /// same name right after each other. This allows us to find all items after the FST gives us
/// the index of the first one.
importables: Vec<ItemInNs>,
fst: fst::Map<Vec<u8>>,
}
impl ImportMap {
- pub fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<Self> {
+ pub(crate) fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<Self> {
let _p = profile::span("import_map_query");
- let mut import_map = collect_import_map(db, krate);
+ let map = collect_import_map(db, krate);
- let mut importables = import_map
- .map
+ let mut importables: Vec<_> = map
.iter()
- .map(|(item, info)| (item, fst_path(db, &info.path)))
- .collect::<Vec<_>>();
- importables.sort_by(|(_, fst_path), (_, fst_path2)| fst_path.cmp(fst_path2));
+ // We've only collected items, whose name cannot be tuple field.
+ .map(|(&item, info)| (item, info.name.as_str().unwrap().to_ascii_lowercase()))
+ .collect();
+ importables.sort_by(|(_, lhs_name), (_, rhs_name)| lhs_name.cmp(rhs_name));
// Build the FST, taking care not to insert duplicate values.
-
let mut builder = fst::MapBuilder::memory();
- let mut last_batch_start = 0;
-
- for idx in 0..importables.len() {
- let key = &importables[last_batch_start].1;
- if let Some((_, fst_path)) = importables.get(idx + 1) {
- if key == fst_path {
- continue;
- }
- }
-
- let _ = builder.insert(key, last_batch_start as u64);
-
- last_batch_start = idx + 1;
+ let iter = importables.iter().enumerate().dedup_by(|lhs, rhs| lhs.1 .1 == rhs.1 .1);
+ for (start_idx, (_, name)) in iter {
+ let _ = builder.insert(name, start_idx as u64);
}
- import_map.fst = builder.into_map();
- import_map.importables = importables.iter().map(|&(&item, _)| item).collect();
-
- Arc::new(import_map)
- }
-
- /// Returns the `ModPath` needed to import/mention `item`, relative to this crate's root.
- pub fn path_of(&self, item: ItemInNs) -> Option<&ImportPath> {
- self.import_info_for(item).map(|it| &it.path)
+ Arc::new(ImportMap {
+ map,
+ fst: builder.into_map(),
+ importables: importables.into_iter().map(|(item, _)| item).collect(),
+ })
}
pub fn import_info_for(&self, item: ItemInNs) -> Option<&ImportInfo> {
self.map.get(&item)
}
-
- #[cfg(test)]
- fn fmt_for_test(&self, db: &dyn DefDatabase) -> String {
- let mut importable_paths: Vec<_> = self
- .map
- .iter()
- .map(|(item, info)| {
- let ns = match item {
- ItemInNs::Types(_) => "t",
- ItemInNs::Values(_) => "v",
- ItemInNs::Macros(_) => "m",
- };
- format!("- {} ({ns})", info.path.display(db))
- })
- .collect();
-
- importable_paths.sort();
- importable_paths.join("\n")
- }
-
- fn collect_trait_assoc_items(
- &mut self,
- db: &dyn DefDatabase,
- tr: TraitId,
- is_type_in_ns: bool,
- original_import_info: &ImportInfo,
- ) {
- let _p = profile::span("collect_trait_assoc_items");
- for (assoc_item_name, item) in &db.trait_data(tr).items {
- let module_def_id = match item {
- AssocItemId::FunctionId(f) => ModuleDefId::from(*f),
- AssocItemId::ConstId(c) => ModuleDefId::from(*c),
- // cannot use associated type aliases directly: need a `<Struct as Trait>::TypeAlias`
- // qualifier, ergo no need to store it for imports in import_map
- AssocItemId::TypeAliasId(_) => {
- cov_mark::hit!(type_aliases_ignored);
- continue;
- }
- };
- let assoc_item = if is_type_in_ns {
- ItemInNs::Types(module_def_id)
- } else {
- ItemInNs::Values(module_def_id)
- };
-
- let mut assoc_item_info = original_import_info.clone();
- assoc_item_info.path.segments.push(assoc_item_name.to_owned());
- assoc_item_info.is_trait_assoc_item = true;
- self.map.insert(assoc_item, assoc_item_info);
- }
- }
}
-fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> ImportMap {
+fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> FxIndexMap<ItemInNs, ImportInfo> {
let _p = profile::span("collect_import_map");
let def_map = db.crate_def_map(krate);
- let mut import_map = ImportMap::default();
+ let mut map = FxIndexMap::default();
// We look only into modules that are public(ly reexported), starting with the crate root.
- let empty = ImportPath { segments: vec![] };
let root = def_map.module_id(DefMap::ROOT);
- let mut worklist = vec![(root, empty)];
- while let Some((module, mod_path)) = worklist.pop() {
+ let mut worklist = vec![(root, 0)];
+ // Records items' minimum module depth.
+ let mut depth_map = FxHashMap::default();
+
+ while let Some((module, depth)) = worklist.pop() {
let ext_def_map;
let mod_data = if module.krate == krate {
&def_map[module.local_id]
@@ -201,52 +109,83 @@ fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> ImportMap {
});
for (name, per_ns) in visible_items {
- let mk_path = || {
- let mut path = mod_path.clone();
- path.segments.push(name.clone());
- path
- };
-
for item in per_ns.iter_items() {
- let path = mk_path();
- let path_len = path.len();
- let import_info =
- ImportInfo { path, container: module, is_trait_assoc_item: false };
-
- if let Some(ModuleDefId::TraitId(tr)) = item.as_module_def_id() {
- import_map.collect_trait_assoc_items(
- db,
- tr,
- matches!(item, ItemInNs::Types(_)),
- &import_info,
- );
- }
+ let import_info = ImportInfo {
+ name: name.clone(),
+ container: module,
+ is_trait_assoc_item: false,
+ };
- match import_map.map.entry(item) {
+ match depth_map.entry(item) {
Entry::Vacant(entry) => {
- entry.insert(import_info);
+ entry.insert(depth);
}
Entry::Occupied(mut entry) => {
- // If the new path is shorter, prefer that one.
- if path_len < entry.get().path.len() {
- *entry.get_mut() = import_info;
+ if depth < *entry.get() {
+ entry.insert(depth);
} else {
continue;
}
}
}
- // If we've just added a path to a module, descend into it. We might traverse
- // modules multiple times, but only if the new path to it is shorter than the
- // first (else we `continue` above).
+ if let Some(ModuleDefId::TraitId(tr)) = item.as_module_def_id() {
+ collect_trait_assoc_items(
+ db,
+ &mut map,
+ tr,
+ matches!(item, ItemInNs::Types(_)),
+ &import_info,
+ );
+ }
+
+ map.insert(item, import_info);
+
+ // If we've just added a module, descend into it. We might traverse modules
+ // multiple times, but only if the module depth is smaller (else we `continue`
+ // above).
if let Some(ModuleDefId::ModuleId(mod_id)) = item.as_module_def_id() {
- worklist.push((mod_id, mk_path()));
+ worklist.push((mod_id, depth + 1));
}
}
}
}
- import_map
+ map
+}
+
+fn collect_trait_assoc_items(
+ db: &dyn DefDatabase,
+ map: &mut FxIndexMap<ItemInNs, ImportInfo>,
+ tr: TraitId,
+ is_type_in_ns: bool,
+ trait_import_info: &ImportInfo,
+) {
+ let _p = profile::span("collect_trait_assoc_items");
+ for (assoc_item_name, item) in &db.trait_data(tr).items {
+ let module_def_id = match item {
+ AssocItemId::FunctionId(f) => ModuleDefId::from(*f),
+ AssocItemId::ConstId(c) => ModuleDefId::from(*c),
+ // cannot use associated type aliases directly: need a `<Struct as Trait>::TypeAlias`
+ // qualifier, ergo no need to store it for imports in import_map
+ AssocItemId::TypeAliasId(_) => {
+ cov_mark::hit!(type_aliases_ignored);
+ continue;
+ }
+ };
+ let assoc_item = if is_type_in_ns {
+ ItemInNs::Types(module_def_id)
+ } else {
+ ItemInNs::Values(module_def_id)
+ };
+
+ let assoc_item_info = ImportInfo {
+ container: trait_import_info.container,
+ name: assoc_item_name.clone(),
+ is_trait_assoc_item: true,
+ };
+ map.insert(assoc_item, assoc_item_info);
+ }
}
impl PartialEq for ImportMap {
@@ -260,7 +199,7 @@ impl Eq for ImportMap {}
impl fmt::Debug for ImportMap {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- let mut importable_paths: Vec<_> = self
+ let mut importable_names: Vec<_> = self
.map
.iter()
.map(|(item, _)| match item {
@@ -270,56 +209,40 @@ impl fmt::Debug for ImportMap {
})
.collect();
- importable_paths.sort();
- f.write_str(&importable_paths.join("\n"))
+ importable_names.sort();
+ f.write_str(&importable_names.join("\n"))
}
}
-fn fst_path(db: &dyn DefDatabase, path: &ImportPath) -> String {
- let _p = profile::span("fst_path");
- let mut s = path.display(db).to_string();
- s.make_ascii_lowercase();
- s
-}
-
-#[derive(Debug, Eq, PartialEq, Hash)]
-pub enum ImportKind {
- Module,
- Function,
- Adt,
- EnumVariant,
- Const,
- Static,
- Trait,
- TraitAlias,
- TypeAlias,
- BuiltinType,
- AssociatedItem,
- Macro,
-}
-
/// A way to match import map contents against the search query.
#[derive(Debug)]
-pub enum SearchMode {
+enum SearchMode {
/// Import map entry should strictly match the query string.
- Equals,
- /// Import map entry should contain the query string.
- Contains,
+ Exact,
/// Import map entry should contain all letters from the query string,
/// in the same order, but not necessary adjacent.
Fuzzy,
}
+/// Three possible ways to search for the name in associated and/or other items.
+#[derive(Debug, Clone, Copy)]
+pub enum AssocSearchMode {
+ /// 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,
+}
+
#[derive(Debug)]
pub struct Query {
query: String,
lowercased: String,
- name_only: bool,
- assoc_items_only: bool,
search_mode: SearchMode,
+ assoc_mode: AssocSearchMode,
case_sensitive: bool,
limit: usize,
- exclude_import_kinds: FxHashSet<ImportKind>,
}
impl Query {
@@ -328,30 +251,21 @@ impl Query {
Self {
query,
lowercased,
- name_only: false,
- assoc_items_only: false,
- search_mode: SearchMode::Contains,
+ search_mode: SearchMode::Exact,
+ assoc_mode: AssocSearchMode::Include,
case_sensitive: false,
- limit: usize::max_value(),
- exclude_import_kinds: FxHashSet::default(),
+ limit: usize::MAX,
}
}
- /// Matches entries' names only, ignoring the rest of
- /// the qualifier.
- /// Example: for `std::marker::PhantomData`, the name is `PhantomData`.
- pub fn name_only(self) -> Self {
- Self { name_only: true, ..self }
- }
-
- /// Matches only the entries that are associated items, ignoring the rest.
- pub fn assoc_items_only(self) -> Self {
- Self { assoc_items_only: true, ..self }
+ /// Fuzzy finds items instead of exact matching.
+ pub fn fuzzy(self) -> Self {
+ Self { search_mode: SearchMode::Fuzzy, ..self }
}
- /// Specifies the way to search for the entries using the query.
- pub fn search_mode(self, search_mode: SearchMode) -> Self {
- Self { search_mode, ..self }
+ /// Specifies whether we want to include associated items in the result.
+ pub fn assoc_search_mode(self, assoc_mode: AssocSearchMode) -> Self {
+ Self { assoc_mode, ..self }
}
/// Limits the returned number of items to `limit`.
@@ -364,12 +278,6 @@ impl Query {
Self { case_sensitive: true, ..self }
}
- /// Do not include imports of the specified kind in the search results.
- pub fn exclude_import_kind(mut self, import_kind: ImportKind) -> Self {
- self.exclude_import_kinds.insert(import_kind);
- self
- }
-
fn import_matches(
&self,
db: &dyn DefDatabase,
@@ -377,49 +285,36 @@ impl Query {
enforce_lowercase: bool,
) -> bool {
let _p = profile::span("import_map::Query::import_matches");
- if import.is_trait_assoc_item {
- if self.exclude_import_kinds.contains(&ImportKind::AssociatedItem) {
- return false;
- }
- } else if self.assoc_items_only {
- return false;
+ match (import.is_trait_assoc_item, self.assoc_mode) {
+ (true, AssocSearchMode::Exclude) => return false,
+ (false, AssocSearchMode::AssocItemsOnly) => return false,
+ _ => {}
}
- let mut input = if import.is_trait_assoc_item || self.name_only {
- import.path.segments.last().unwrap().display(db.upcast()).to_string()
- } else {
- import.path.display(db).to_string()
- };
- if enforce_lowercase || !self.case_sensitive {
+ let mut input = import.name.display(db.upcast()).to_string();
+ let case_insensitive = enforce_lowercase || !self.case_sensitive;
+ if case_insensitive {
input.make_ascii_lowercase();
}
- let query_string =
- if !enforce_lowercase && self.case_sensitive { &self.query } else { &self.lowercased };
+ let query_string = if case_insensitive { &self.lowercased } else { &self.query };
match self.search_mode {
- SearchMode::Equals => &input == query_string,
- SearchMode::Contains => input.contains(query_string),
+ SearchMode::Exact => &input == query_string,
SearchMode::Fuzzy => {
- let mut unchecked_query_chars = query_string.chars();
- let mut mismatching_query_char = unchecked_query_chars.next();
-
- for input_char in input.chars() {
- match mismatching_query_char {
- None => return true,
- Some(matching_query_char) if matching_query_char == input_char => {
- mismatching_query_char = unchecked_query_chars.next();
- }
- _ => (),
+ let mut input_chars = input.chars();
+ for query_char in query_string.chars() {
+ if input_chars.find(|&it| it == query_char).is_none() {
+ return false;
}
}
- mismatching_query_char.is_none()
+ true
}
}
}
}
-/// Searches dependencies of `krate` for an importable path matching `query`.
+/// Searches dependencies of `krate` for an importable name matching `query`.
///
/// This returns a list of items that could be imported from dependencies of `krate`.
pub fn search_dependencies(
@@ -442,65 +337,44 @@ pub fn search_dependencies(
let mut stream = op.union();
- let mut all_indexed_values = FxHashSet::default();
- while let Some((_, indexed_values)) = stream.next() {
- all_indexed_values.extend(indexed_values.iter().copied());
- }
-
let mut res = FxHashSet::default();
- for indexed_value in all_indexed_values {
- let import_map = &import_maps[indexed_value.index];
- let importables = &import_map.importables[indexed_value.value as usize..];
+ while let Some((_, indexed_values)) = stream.next() {
+ for indexed_value in indexed_values {
+ let import_map = &import_maps[indexed_value.index];
+ let importables = &import_map.importables[indexed_value.value as usize..];
- let common_importable_data = &import_map.map[&importables[0]];
- if !query.import_matches(db, common_importable_data, true) {
- continue;
- }
+ let common_importable_data = &import_map.map[&importables[0]];
+ if !query.import_matches(db, common_importable_data, true) {
+ continue;
+ }
- // Path shared by the importable items in this group.
- let common_importables_path_fst = fst_path(db, &common_importable_data.path);
- // Add the items from this `ModPath` group. Those are all subsequent items in
- // `importables` whose paths match `path`.
- let iter = importables
- .iter()
- .copied()
- .take_while(|item| {
- common_importables_path_fst == fst_path(db, &import_map.map[item].path)
- })
- .filter(|&item| match item_import_kind(item) {
- Some(import_kind) => !query.exclude_import_kinds.contains(&import_kind),
- None => true,
- })
- .filter(|item| {
- !query.case_sensitive // we've already checked the common importables path case-insensitively
+ // Name shared by the importable items in this group.
+ let common_importable_name =
+ common_importable_data.name.to_smol_str().to_ascii_lowercase();
+ // Add the items from this name group. Those are all subsequent items in
+ // `importables` whose name match `common_importable_name`.
+ let iter = importables
+ .iter()
+ .copied()
+ .take_while(|item| {
+ common_importable_name
+ == import_map.map[item].name.to_smol_str().to_ascii_lowercase()
+ })
+ .filter(|item| {
+ !query.case_sensitive // we've already checked the common importables name case-insensitively
|| query.import_matches(db, &import_map.map[item], false)
- });
- res.extend(iter);
+ });
+ res.extend(iter);
- if res.len() >= query.limit {
- return res;
+ if res.len() >= query.limit {
+ return res;
+ }
}
}
res
}
-fn item_import_kind(item: ItemInNs) -> Option<ImportKind> {
- Some(match item.as_module_def_id()? {
- ModuleDefId::ModuleId(_) => ImportKind::Module,
- ModuleDefId::FunctionId(_) => ImportKind::Function,
- ModuleDefId::AdtId(_) => ImportKind::Adt,
- ModuleDefId::EnumVariantId(_) => ImportKind::EnumVariant,
- ModuleDefId::ConstId(_) => ImportKind::Const,
- ModuleDefId::StaticId(_) => ImportKind::Static,
- ModuleDefId::TraitId(_) => ImportKind::Trait,
- ModuleDefId::TraitAliasId(_) => ImportKind::TraitAlias,
- ModuleDefId::TypeAliasId(_) => ImportKind::TypeAlias,
- ModuleDefId::BuiltinType(_) => ImportKind::BuiltinType,
- ModuleDefId::MacroId(_) => ImportKind::Macro,
- })
-}
-
#[cfg(test)]
mod tests {
use base_db::{fixture::WithFixture, SourceDatabase, Upcast};
@@ -510,16 +384,39 @@ mod tests {
use super::*;
+ impl ImportMap {
+ fn fmt_for_test(&self, db: &dyn DefDatabase) -> String {
+ let mut importable_paths: Vec<_> = self
+ .map
+ .iter()
+ .map(|(item, info)| {
+ let path = render_path(db, info);
+ let ns = match item {
+ ItemInNs::Types(_) => "t",
+ ItemInNs::Values(_) => "v",
+ ItemInNs::Macros(_) => "m",
+ };
+ format!("- {path} ({ns})")
+ })
+ .collect();
+
+ importable_paths.sort();
+ importable_paths.join("\n")
+ }
+ }
+
fn check_search(ra_fixture: &str, crate_name: &str, query: Query, expect: Expect) {
let db = TestDB::with_files(ra_fixture);
let crate_graph = db.crate_graph();
let krate = crate_graph
.iter()
- .find(|krate| {
- crate_graph[*krate].display_name.as_ref().map(|n| n.to_string())
- == Some(crate_name.to_string())
+ .find(|&krate| {
+ crate_graph[krate]
+ .display_name
+ .as_ref()
+ .is_some_and(|it| &**it.crate_name() == crate_name)
})
- .unwrap();
+ .expect("could not find crate");
let actual = search_dependencies(db.upcast(), krate, query)
.into_iter()
@@ -530,7 +427,7 @@ mod tests {
let (path, mark) = match assoc_item_path(&db, &dependency_imports, dependency) {
Some(assoc_item_path) => (assoc_item_path, "a"),
None => (
- dependency_imports.path_of(dependency)?.display(&db).to_string(),
+ render_path(&db, dependency_imports.import_info_for(dependency)?),
match dependency {
ItemInNs::Types(ModuleDefId::FunctionId(_))
| ItemInNs::Values(ModuleDefId::FunctionId(_)) => "f",
@@ -560,57 +457,25 @@ mod tests {
dependency_imports: &ImportMap,
dependency: ItemInNs,
) -> Option<String> {
- let dependency_assoc_item_id = match dependency {
- ItemInNs::Types(ModuleDefId::FunctionId(id))
- | ItemInNs::Values(ModuleDefId::FunctionId(id)) => AssocItemId::from(id),
- ItemInNs::Types(ModuleDefId::ConstId(id))
- | ItemInNs::Values(ModuleDefId::ConstId(id)) => AssocItemId::from(id),
- ItemInNs::Types(ModuleDefId::TypeAliasId(id))
- | ItemInNs::Values(ModuleDefId::TypeAliasId(id)) => AssocItemId::from(id),
+ let (dependency_assoc_item_id, container) = match dependency.as_module_def_id()? {
+ ModuleDefId::FunctionId(id) => (AssocItemId::from(id), id.lookup(db).container),
+ ModuleDefId::ConstId(id) => (AssocItemId::from(id), id.lookup(db).container),
+ ModuleDefId::TypeAliasId(id) => (AssocItemId::from(id), id.lookup(db).container),
_ => return None,
};
- let trait_ = assoc_to_trait(db, dependency)?;
- if let ModuleDefId::TraitId(tr) = trait_.as_module_def_id()? {
- let trait_data = db.trait_data(tr);
- let assoc_item_name =
- trait_data.items.iter().find_map(|(assoc_item_name, assoc_item_id)| {
- if &dependency_assoc_item_id == assoc_item_id {
- Some(assoc_item_name)
- } else {
- None
- }
- })?;
- return Some(format!(
- "{}::{}",
- dependency_imports.path_of(trait_)?.display(db),
- assoc_item_name.display(db.upcast())
- ));
- }
- None
- }
-
- fn assoc_to_trait(db: &dyn DefDatabase, item: ItemInNs) -> Option<ItemInNs> {
- let assoc: AssocItemId = match item {
- ItemInNs::Types(it) | ItemInNs::Values(it) => match it {
- ModuleDefId::TypeAliasId(it) => it.into(),
- ModuleDefId::FunctionId(it) => it.into(),
- ModuleDefId::ConstId(it) => it.into(),
- _ => return None,
- },
- _ => return None,
+ let ItemContainerId::TraitId(trait_id) = container else {
+ return None;
};
- let container = match assoc {
- AssocItemId::FunctionId(it) => it.lookup(db).container,
- AssocItemId::ConstId(it) => it.lookup(db).container,
- AssocItemId::TypeAliasId(it) => it.lookup(db).container,
- };
+ let trait_info = dependency_imports.import_info_for(ItemInNs::Types(trait_id.into()))?;
- match container {
- ItemContainerId::TraitId(it) => Some(ItemInNs::Types(it.into())),
- _ => None,
- }
+ let trait_data = db.trait_data(trait_id);
+ let (assoc_item_name, _) = trait_data
+ .items
+ .iter()
+ .find(|(_, assoc_item_id)| &dependency_assoc_item_id == assoc_item_id)?;
+ Some(format!("{}::{}", render_path(db, trait_info), assoc_item_name.display(db.upcast())))
}
fn check(ra_fixture: &str, expect: Expect) {
@@ -633,6 +498,24 @@ mod tests {
expect.assert_eq(&actual)
}
+ fn render_path(db: &dyn DefDatabase, info: &ImportInfo) -> String {
+ let mut module = info.container;
+ let mut segments = vec![&info.name];
+
+ let def_map = module.def_map(db);
+ assert!(def_map.block_id().is_none(), "block local items should not be in `ImportMap`");
+
+ while let Some(parent) = module.containing_module(db) {
+ let parent_data = &def_map[parent.local_id];
+ let (name, _) =
+ parent_data.children.iter().find(|(_, id)| **id == module.local_id).unwrap();
+ segments.push(name);
+ module = parent;
+ }
+
+ segments.iter().rev().map(|it| it.display(db.upcast())).join("::")
+ }
+
#[test]
fn smoke() {
check(
@@ -749,6 +632,7 @@ mod tests {
#[test]
fn module_reexport() {
// Reexporting modules from a dependency adds all contents to the import map.
+ // XXX: The rendered paths are relative to the defining crate.
check(
r"
//- /main.rs crate:main deps:lib
@@ -764,9 +648,9 @@ mod tests {
- module::S (t)
- module::S (v)
main:
+ - module::S (t)
+ - module::S (v)
- reexported_module (t)
- - reexported_module::S (t)
- - reexported_module::S (v)
"#]],
);
}
@@ -868,10 +752,9 @@ mod tests {
check_search(
ra_fixture,
"main",
- Query::new("fmt".to_string()).search_mode(SearchMode::Fuzzy),
+ Query::new("fmt".to_string()).fuzzy(),
expect![[r#"
dep::fmt (t)
- dep::fmt::Display (t)
dep::fmt::Display::FMT_CONST (a)
dep::fmt::Display::format_function (a)
dep::fmt::Display::format_method (a)
@@ -898,7 +781,9 @@ mod tests {
check_search(
ra_fixture,
"main",
- Query::new("fmt".to_string()).search_mode(SearchMode::Fuzzy).assoc_items_only(),
+ Query::new("fmt".to_string())
+ .fuzzy()
+ .assoc_search_mode(AssocSearchMode::AssocItemsOnly),
expect![[r#"
dep::fmt::Display::FMT_CONST (a)
dep::fmt::Display::format_function (a)
@@ -909,23 +794,10 @@ mod tests {
check_search(
ra_fixture,
"main",
- Query::new("fmt".to_string())
- .search_mode(SearchMode::Fuzzy)
- .exclude_import_kind(ImportKind::AssociatedItem),
+ Query::new("fmt".to_string()).fuzzy().assoc_search_mode(AssocSearchMode::Exclude),
expect![[r#"
- dep::fmt (t)
- dep::fmt::Display (t)
- "#]],
- );
-
- check_search(
- ra_fixture,
- "main",
- Query::new("fmt".to_string())
- .search_mode(SearchMode::Fuzzy)
- .assoc_items_only()
- .exclude_import_kind(ImportKind::AssociatedItem),
- expect![[r#""#]],
+ dep::fmt (t)
+ "#]],
);
}
@@ -958,13 +830,12 @@ mod tests {
check_search(
ra_fixture,
"main",
- Query::new("fmt".to_string()).search_mode(SearchMode::Fuzzy),
+ Query::new("fmt".to_string()).fuzzy(),
expect![[r#"
dep::Fmt (m)
dep::Fmt (t)
dep::Fmt (v)
dep::fmt (t)
- dep::fmt::Display (t)
dep::fmt::Display::fmt (a)
dep::format (f)
"#]],
@@ -973,26 +844,12 @@ mod tests {
check_search(
ra_fixture,
"main",
- Query::new("fmt".to_string()).search_mode(SearchMode::Equals),
- expect![[r#"
- dep::Fmt (m)
- dep::Fmt (t)
- dep::Fmt (v)
- dep::fmt (t)
- dep::fmt::Display::fmt (a)
- "#]],
- );
-
- check_search(
- ra_fixture,
- "main",
- Query::new("fmt".to_string()).search_mode(SearchMode::Contains),
+ Query::new("fmt".to_string()),
expect![[r#"
dep::Fmt (m)
dep::Fmt (t)
dep::Fmt (v)
dep::fmt (t)
- dep::fmt::Display (t)
dep::fmt::Display::fmt (a)
"#]],
);
@@ -1033,7 +890,6 @@ mod tests {
dep::Fmt (t)
dep::Fmt (v)
dep::fmt (t)
- dep::fmt::Display (t)
dep::fmt::Display::fmt (a)
"#]],
);
@@ -1041,7 +897,7 @@ mod tests {
check_search(
ra_fixture,
"main",
- Query::new("fmt".to_string()).name_only(),
+ Query::new("fmt".to_string()),
expect![[r#"
dep::Fmt (m)
dep::Fmt (t)
@@ -1106,43 +962,10 @@ mod tests {
pub fn no() {}
"#,
"main",
- Query::new("".to_string()).limit(2),
- expect![[r#"
- dep::Fmt (m)
- dep::Fmt (t)
- dep::Fmt (v)
- dep::fmt (t)
- "#]],
- );
- }
-
- #[test]
- fn search_exclusions() {
- let ra_fixture = r#"
- //- /main.rs crate:main deps:dep
- //- /dep.rs crate:dep
-
- pub struct fmt;
- pub struct FMT;
- "#;
-
- check_search(
- ra_fixture,
- "main",
- Query::new("FMT".to_string()),
+ Query::new("".to_string()).fuzzy().limit(1),
expect![[r#"
- dep::FMT (t)
- dep::FMT (v)
- dep::fmt (t)
- dep::fmt (v)
+ dep::fmt::Display (t)
"#]],
);
-
- check_search(
- ra_fixture,
- "main",
- Query::new("FMT".to_string()).exclude_import_kind(ImportKind::Adt),
- expect![[r#""#]],
- );
}
}
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs
index 2001fb29a..873accafb 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs
@@ -14,8 +14,9 @@ use stdx::format_to;
use syntax::ast;
use crate::{
- db::DefDatabase, per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, ConstId, HasModule,
- ImplId, LocalModuleId, MacroId, ModuleDefId, ModuleId, TraitId,
+ db::DefDatabase, per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, ConstId,
+ ExternCrateId, HasModule, ImplId, LocalModuleId, MacroId, ModuleDefId, ModuleId, TraitId,
+ UseId,
};
#[derive(Copy, Clone, Debug)]
@@ -50,6 +51,7 @@ pub struct ItemScope {
unnamed_consts: Vec<ConstId>,
/// Traits imported via `use Trait as _;`.
unnamed_trait_imports: FxHashMap<TraitId, Visibility>,
+ extern_crate_decls: Vec<ExternCrateId>,
/// Macros visible in current module in legacy textual scope
///
/// For macros invoked by an unqualified identifier like `bar!()`, `legacy_macros` will be searched in first.
@@ -112,6 +114,17 @@ impl ItemScope {
self.declarations.iter().copied()
}
+ pub fn extern_crate_decls(
+ &self,
+ ) -> impl Iterator<Item = ExternCrateId> + ExactSizeIterator + '_ {
+ self.extern_crate_decls.iter().copied()
+ }
+
+ pub fn use_decls(&self) -> impl Iterator<Item = UseId> + ExactSizeIterator + '_ {
+ // FIXME: to be implemented
+ std::iter::empty()
+ }
+
pub fn impls(&self) -> impl Iterator<Item = ImplId> + ExactSizeIterator + '_ {
self.impls.iter().copied()
}
@@ -188,7 +201,11 @@ impl ItemScope {
}
pub(crate) fn define_impl(&mut self, imp: ImplId) {
- self.impls.push(imp)
+ self.impls.push(imp);
+ }
+
+ pub(crate) fn define_extern_crate_decl(&mut self, extern_crate: ExternCrateId) {
+ self.extern_crate_decls.push(extern_crate);
}
pub(crate) fn define_unnamed_const(&mut self, konst: ConstId) {
@@ -397,6 +414,7 @@ impl ItemScope {
legacy_macros,
attr_macros,
derive_macros,
+ extern_crate_decls,
} = self;
types.shrink_to_fit();
values.shrink_to_fit();
@@ -409,6 +427,7 @@ impl ItemScope {
legacy_macros.shrink_to_fit();
attr_macros.shrink_to_fit();
derive_macros.shrink_to_fit();
+ extern_crate_decls.shrink_to_fit();
}
}
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs
index e74b71888..c9b0f75f1 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs
@@ -46,7 +46,7 @@ use ast::{AstNode, HasName, StructKind};
use base_db::CrateId;
use either::Either;
use hir_expand::{
- ast_id_map::FileAstId,
+ ast_id_map::{AstIdNode, FileAstId},
attrs::RawAttrs,
hygiene::Hygiene,
name::{name, AsName, Name},
@@ -188,7 +188,7 @@ impl ItemTree {
fn shrink_to_fit(&mut self) {
if let Some(data) = &mut self.data {
let ItemTreeData {
- imports,
+ uses,
extern_crates,
extern_blocks,
functions,
@@ -211,7 +211,7 @@ impl ItemTree {
vis,
} = &mut **data;
- imports.shrink_to_fit();
+ uses.shrink_to_fit();
extern_crates.shrink_to_fit();
extern_blocks.shrink_to_fit();
functions.shrink_to_fit();
@@ -262,7 +262,7 @@ static VIS_PUB_CRATE: RawVisibility = RawVisibility::Module(ModPath::from_kind(P
#[derive(Default, Debug, Eq, PartialEq)]
struct ItemTreeData {
- imports: Arena<Import>,
+ uses: Arena<Use>,
extern_crates: Arena<ExternCrate>,
extern_blocks: Arena<ExternBlock>,
functions: Arena<Function>,
@@ -314,7 +314,7 @@ from_attrs!(ModItem(ModItem), Variant(Idx<Variant>), Field(Idx<Field>), Param(Id
/// Trait implemented by all item nodes in the item tree.
pub trait ItemTreeNode: Clone {
- type Source: AstNode + Into<ast::Item>;
+ type Source: AstIdNode + Into<ast::Item>;
fn ast_id(&self) -> FileAstId<Self::Source>;
@@ -486,7 +486,7 @@ macro_rules! mod_items {
}
mod_items! {
- Import in imports -> ast::Use,
+ Use in uses -> ast::Use,
ExternCrate in extern_crates -> ast::ExternCrate,
ExternBlock in extern_blocks -> ast::ExternBlock,
Function in functions -> ast::Fn,
@@ -541,7 +541,7 @@ impl<N: ItemTreeNode> Index<FileItemTreeId<N>> for ItemTree {
}
#[derive(Debug, Clone, Eq, PartialEq)]
-pub struct Import {
+pub struct Use {
pub visibility: RawVisibilityId,
pub ast_id: FileAstId<ast::Use>,
pub use_tree: UseTree,
@@ -744,7 +744,7 @@ pub struct MacroDef {
pub ast_id: FileAstId<ast::MacroDef>,
}
-impl Import {
+impl Use {
/// Maps a `UseTree` contained in this import back to its AST node.
pub fn use_tree_to_ast(
&self,
@@ -870,7 +870,7 @@ macro_rules! impl_froms {
impl ModItem {
pub fn as_assoc_item(&self) -> Option<AssocItem> {
match self {
- ModItem::Import(_)
+ ModItem::Use(_)
| ModItem::ExternCrate(_)
| ModItem::ExternBlock(_)
| ModItem::Struct(_)
@@ -892,7 +892,7 @@ impl ModItem {
pub fn ast_id(&self, tree: &ItemTree) -> FileAstId<ast::Item> {
match self {
- ModItem::Import(it) => tree[it.index].ast_id().upcast(),
+ ModItem::Use(it) => tree[it.index].ast_id().upcast(),
ModItem::ExternCrate(it) => tree[it.index].ast_id().upcast(),
ModItem::ExternBlock(it) => tree[it.index].ast_id().upcast(),
ModItem::Function(it) => tree[it.index].ast_id().upcast(),
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs
index 46633667e..7b898e62d 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs
@@ -502,13 +502,13 @@ impl<'a> Ctx<'a> {
Some(id(self.data().impls.alloc(res)))
}
- fn lower_use(&mut self, use_item: &ast::Use) -> Option<FileItemTreeId<Import>> {
+ fn lower_use(&mut self, use_item: &ast::Use) -> Option<FileItemTreeId<Use>> {
let visibility = self.lower_visibility(use_item);
let ast_id = self.source_ast_id_map.ast_id(use_item);
let (use_tree, _) = lower_use_tree(self.db, self.hygiene(), use_item.use_tree()?)?;
- let res = Import { visibility, ast_id, use_tree };
- Some(id(self.data().imports.alloc(res)))
+ let res = Use { visibility, ast_id, use_tree };
+ Some(id(self.data().uses.alloc(res)))
}
fn lower_extern_crate(
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/pretty.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/pretty.rs
index e873316a5..da30830fe 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/pretty.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/pretty.rs
@@ -52,7 +52,7 @@ struct Printer<'a> {
needs_indent: bool,
}
-impl<'a> Printer<'a> {
+impl Printer<'_> {
fn indented(&mut self, f: impl FnOnce(&mut Self)) {
self.indent_level += 1;
wln!(self);
@@ -198,8 +198,8 @@ impl<'a> Printer<'a> {
self.print_attrs_of(item);
match item {
- ModItem::Import(it) => {
- let Import { visibility, use_tree, ast_id: _ } = &self.tree[it];
+ ModItem::Use(it) => {
+ let Use { visibility, use_tree, ast_id: _ } = &self.tree[it];
self.print_visibility(*visibility);
w!(self, "use ");
self.print_use_tree(use_tree);
@@ -572,7 +572,7 @@ impl<'a> Printer<'a> {
}
}
-impl<'a> Write for Printer<'a> {
+impl Write for Printer<'_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
for line in s.split_inclusive('\n') {
if self.needs_indent {
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs b/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs
index 0e9ac58fb..627479bb7 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs
@@ -180,15 +180,15 @@ impl LangItems {
T: Into<AttrDefId> + Copy,
{
let _p = profile::span("collect_lang_item");
- if let Some(lang_item) = lang_attr(db, item) {
+ if let Some(lang_item) = db.lang_attr(item.into()) {
self.items.entry(lang_item).or_insert_with(|| constructor(item));
}
}
}
-pub fn lang_attr(db: &dyn DefDatabase, item: impl Into<AttrDefId> + Copy) -> Option<LangItem> {
- let attrs = db.attrs(item.into());
- attrs.by_key("lang").string_value().cloned().and_then(|it| LangItem::from_str(&it))
+pub(crate) fn lang_attr_query(db: &dyn DefDatabase, item: AttrDefId) -> Option<LangItem> {
+ let attrs = db.attrs(item);
+ attrs.by_key("lang").string_value().and_then(|it| LangItem::from_str(&it))
}
pub enum GenericRequirement {
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/lib.rs b/src/tools/rust-analyzer/crates/hir-def/src/lib.rs
index 9d8b57a0d..1901db8a0 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/lib.rs
@@ -64,7 +64,7 @@ use std::{
use base_db::{impl_intern_key, salsa, CrateId, ProcMacroKind};
use hir_expand::{
- ast_id_map::FileAstId,
+ ast_id_map::{AstIdNode, FileAstId},
attrs::{Attr, AttrId, AttrInput},
builtin_attr_macro::BuiltinAttrExpander,
builtin_derive_macro::BuiltinDeriveExpander,
@@ -88,8 +88,8 @@ use crate::{
builtin_type::BuiltinType,
data::adt::VariantData,
item_tree::{
- Const, Enum, Function, Impl, ItemTreeId, ItemTreeNode, MacroDef, MacroRules, Static,
- Struct, Trait, TraitAlias, TypeAlias, Union,
+ Const, Enum, ExternCrate, Function, Impl, ItemTreeId, ItemTreeNode, MacroDef, MacroRules,
+ Static, Struct, Trait, TraitAlias, TypeAlias, Union, Use,
},
};
@@ -121,6 +121,12 @@ impl From<CrateRootModuleId> for ModuleDefId {
}
}
+impl From<CrateId> for CrateRootModuleId {
+ fn from(krate: CrateId) -> Self {
+ CrateRootModuleId { krate }
+ }
+}
+
impl TryFrom<ModuleId> for CrateRootModuleId {
type Error = ();
@@ -145,24 +151,28 @@ pub struct ModuleId {
}
impl ModuleId {
- pub fn def_map(&self, db: &dyn db::DefDatabase) -> Arc<DefMap> {
+ pub fn def_map(self, db: &dyn db::DefDatabase) -> Arc<DefMap> {
match self.block {
Some(block) => db.block_def_map(block),
None => db.crate_def_map(self.krate),
}
}
- pub fn krate(&self) -> CrateId {
+ pub fn krate(self) -> CrateId {
self.krate
}
- pub fn containing_module(&self, db: &dyn db::DefDatabase) -> Option<ModuleId> {
+ pub fn containing_module(self, db: &dyn db::DefDatabase) -> Option<ModuleId> {
self.def_map(db).containing_module(self.local_id)
}
- pub fn containing_block(&self) -> Option<BlockId> {
+ pub fn containing_block(self) -> Option<BlockId> {
self.block
}
+
+ pub fn is_block_module(self) -> bool {
+ self.block.is_some() && self.local_id == DefMap::ROOT
+ }
}
/// An ID of a module, **local** to a `DefMap`.
@@ -314,6 +324,16 @@ type ImplLoc = ItemLoc<Impl>;
impl_intern!(ImplId, ImplLoc, intern_impl, lookup_intern_impl);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
+pub struct UseId(salsa::InternId);
+type UseLoc = ItemLoc<Use>;
+impl_intern!(UseId, UseLoc, intern_use, lookup_intern_use);
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
+pub struct ExternCrateId(salsa::InternId);
+type ExternCrateLoc = ItemLoc<ExternCrate>;
+impl_intern!(ExternCrateId, ExternCrateLoc, intern_extern_crate, lookup_intern_extern_crate);
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub struct ExternBlockId(salsa::InternId);
type ExternBlockLoc = ItemLoc<ExternBlock>;
impl_intern!(ExternBlockId, ExternBlockLoc, intern_extern_block, lookup_intern_extern_block);
@@ -392,14 +412,14 @@ impl TypeParamId {
impl TypeParamId {
/// Caller should check if this toc id really belongs to a type
- pub fn from_unchecked(x: TypeOrConstParamId) -> Self {
- Self(x)
+ pub fn from_unchecked(it: TypeOrConstParamId) -> Self {
+ Self(it)
}
}
impl From<TypeParamId> for TypeOrConstParamId {
- fn from(x: TypeParamId) -> Self {
- x.0
+ fn from(it: TypeParamId) -> Self {
+ it.0
}
}
@@ -418,14 +438,14 @@ impl ConstParamId {
impl ConstParamId {
/// Caller should check if this toc id really belongs to a const
- pub fn from_unchecked(x: TypeOrConstParamId) -> Self {
- Self(x)
+ pub fn from_unchecked(it: TypeOrConstParamId) -> Self {
+ Self(it)
}
}
impl From<ConstParamId> for TypeOrConstParamId {
- fn from(x: ConstParamId) -> Self {
- x.0
+ fn from(it: ConstParamId) -> Self {
+ it.0
}
}
@@ -548,14 +568,14 @@ pub enum TypeOwnerId {
impl TypeOwnerId {
fn as_generic_def_id(self) -> Option<GenericDefId> {
Some(match self {
- TypeOwnerId::FunctionId(x) => GenericDefId::FunctionId(x),
- TypeOwnerId::ConstId(x) => GenericDefId::ConstId(x),
- TypeOwnerId::AdtId(x) => GenericDefId::AdtId(x),
- TypeOwnerId::TraitId(x) => GenericDefId::TraitId(x),
- TypeOwnerId::TraitAliasId(x) => GenericDefId::TraitAliasId(x),
- TypeOwnerId::TypeAliasId(x) => GenericDefId::TypeAliasId(x),
- TypeOwnerId::ImplId(x) => GenericDefId::ImplId(x),
- TypeOwnerId::EnumVariantId(x) => GenericDefId::EnumVariantId(x),
+ TypeOwnerId::FunctionId(it) => GenericDefId::FunctionId(it),
+ TypeOwnerId::ConstId(it) => GenericDefId::ConstId(it),
+ TypeOwnerId::AdtId(it) => GenericDefId::AdtId(it),
+ TypeOwnerId::TraitId(it) => GenericDefId::TraitId(it),
+ TypeOwnerId::TraitAliasId(it) => GenericDefId::TraitAliasId(it),
+ TypeOwnerId::TypeAliasId(it) => GenericDefId::TypeAliasId(it),
+ TypeOwnerId::ImplId(it) => GenericDefId::ImplId(it),
+ TypeOwnerId::EnumVariantId(it) => GenericDefId::EnumVariantId(it),
TypeOwnerId::InTypeConstId(_) | TypeOwnerId::ModuleId(_) | TypeOwnerId::StaticId(_) => {
return None
}
@@ -578,15 +598,15 @@ impl_from!(
for TypeOwnerId
);
-// Every `DefWithBodyId` is a type owner, since bodies can contain type (e.g. `{ let x: Type = _; }`)
+// Every `DefWithBodyId` is a type owner, since bodies can contain type (e.g. `{ let it: Type = _; }`)
impl From<DefWithBodyId> for TypeOwnerId {
fn from(value: DefWithBodyId) -> Self {
match value {
- DefWithBodyId::FunctionId(x) => x.into(),
- DefWithBodyId::StaticId(x) => x.into(),
- DefWithBodyId::ConstId(x) => x.into(),
- DefWithBodyId::InTypeConstId(x) => x.into(),
- DefWithBodyId::VariantId(x) => x.into(),
+ DefWithBodyId::FunctionId(it) => it.into(),
+ DefWithBodyId::StaticId(it) => it.into(),
+ DefWithBodyId::ConstId(it) => it.into(),
+ DefWithBodyId::InTypeConstId(it) => it.into(),
+ DefWithBodyId::VariantId(it) => it.into(),
}
}
}
@@ -594,14 +614,14 @@ impl From<DefWithBodyId> for TypeOwnerId {
impl From<GenericDefId> for TypeOwnerId {
fn from(value: GenericDefId) -> Self {
match value {
- GenericDefId::FunctionId(x) => x.into(),
- GenericDefId::AdtId(x) => x.into(),
- GenericDefId::TraitId(x) => x.into(),
- GenericDefId::TraitAliasId(x) => x.into(),
- GenericDefId::TypeAliasId(x) => x.into(),
- GenericDefId::ImplId(x) => x.into(),
- GenericDefId::EnumVariantId(x) => x.into(),
- GenericDefId::ConstId(x) => x.into(),
+ GenericDefId::FunctionId(it) => it.into(),
+ GenericDefId::AdtId(it) => it.into(),
+ GenericDefId::TraitId(it) => it.into(),
+ GenericDefId::TraitAliasId(it) => it.into(),
+ GenericDefId::TypeAliasId(it) => it.into(),
+ GenericDefId::ImplId(it) => it.into(),
+ GenericDefId::EnumVariantId(it) => it.into(),
+ GenericDefId::ConstId(it) => it.into(),
}
}
}
@@ -716,7 +736,7 @@ impl GeneralConstId {
.const_data(const_id)
.name
.as_ref()
- .and_then(|x| x.as_str())
+ .and_then(|it| it.as_str())
.unwrap_or("_")
.to_owned(),
GeneralConstId::ConstBlockId(id) => format!("{{anonymous const {id:?}}}"),
@@ -821,6 +841,8 @@ pub enum AttrDefId {
ImplId(ImplId),
GenericParamId(GenericParamId),
ExternBlockId(ExternBlockId),
+ ExternCrateId(ExternCrateId),
+ UseId(UseId),
}
impl_from!(
@@ -835,7 +857,8 @@ impl_from!(
TypeAliasId,
MacroId(Macro2Id, MacroRulesId, ProcMacroId),
ImplId,
- GenericParamId
+ GenericParamId,
+ ExternCrateId
for AttrDefId
);
@@ -927,6 +950,12 @@ impl HasModule for AdtId {
}
}
+impl HasModule for ExternCrateId {
+ fn module(&self, db: &dyn db::DefDatabase) -> ModuleId {
+ self.lookup(db).container
+ }
+}
+
impl HasModule for VariantId {
fn module(&self, db: &dyn db::DefDatabase) -> ModuleId {
match self {
@@ -950,17 +979,17 @@ impl HasModule for MacroId {
impl HasModule for TypeOwnerId {
fn module(&self, db: &dyn db::DefDatabase) -> ModuleId {
match self {
- TypeOwnerId::FunctionId(x) => x.lookup(db).module(db),
- TypeOwnerId::StaticId(x) => x.lookup(db).module(db),
- TypeOwnerId::ConstId(x) => x.lookup(db).module(db),
- TypeOwnerId::InTypeConstId(x) => x.lookup(db).owner.module(db),
- TypeOwnerId::AdtId(x) => x.module(db),
- TypeOwnerId::TraitId(x) => x.lookup(db).container,
- TypeOwnerId::TraitAliasId(x) => x.lookup(db).container,
- TypeOwnerId::TypeAliasId(x) => x.lookup(db).module(db),
- TypeOwnerId::ImplId(x) => x.lookup(db).container,
- TypeOwnerId::EnumVariantId(x) => x.parent.lookup(db).container,
- TypeOwnerId::ModuleId(x) => *x,
+ TypeOwnerId::FunctionId(it) => it.lookup(db).module(db),
+ TypeOwnerId::StaticId(it) => it.lookup(db).module(db),
+ TypeOwnerId::ConstId(it) => it.lookup(db).module(db),
+ TypeOwnerId::InTypeConstId(it) => it.lookup(db).owner.module(db),
+ TypeOwnerId::AdtId(it) => it.module(db),
+ TypeOwnerId::TraitId(it) => it.lookup(db).container,
+ TypeOwnerId::TraitAliasId(it) => it.lookup(db).container,
+ TypeOwnerId::TypeAliasId(it) => it.lookup(db).module(db),
+ TypeOwnerId::ImplId(it) => it.lookup(db).container,
+ TypeOwnerId::EnumVariantId(it) => it.parent.lookup(db).container,
+ TypeOwnerId::ModuleId(it) => *it,
}
}
}
@@ -1050,6 +1079,8 @@ impl AttrDefId {
.krate
}
AttrDefId::MacroId(it) => it.module(db).krate,
+ AttrDefId::ExternCrateId(it) => it.lookup(db).container.krate,
+ AttrDefId::UseId(it) => it.lookup(db).container.krate,
}
}
}
@@ -1060,7 +1091,7 @@ pub trait AsMacroCall {
&self,
db: &dyn ExpandDatabase,
krate: CrateId,
- resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
+ resolver: impl Fn(path::ModPath) -> Option<MacroDefId> + Copy,
) -> Option<MacroCallId> {
self.as_call_id_with_errors(db, krate, resolver).ok()?.value
}
@@ -1069,7 +1100,7 @@ pub trait AsMacroCall {
&self,
db: &dyn ExpandDatabase,
krate: CrateId,
- resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
+ resolver: impl Fn(path::ModPath) -> Option<MacroDefId> + Copy,
) -> Result<ExpandResult<Option<MacroCallId>>, UnresolvedMacro>;
}
@@ -1078,7 +1109,7 @@ impl AsMacroCall for InFile<&ast::MacroCall> {
&self,
db: &dyn ExpandDatabase,
krate: CrateId,
- resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
+ resolver: impl Fn(path::ModPath) -> Option<MacroDefId> + Copy,
) -> Result<ExpandResult<Option<MacroCallId>>, UnresolvedMacro> {
let expands_to = hir_expand::ExpandTo::from_call_site(self.value);
let ast_id = AstId::new(self.file_id, db.ast_id_map(self.file_id).ast_id(self.value));
@@ -1089,24 +1120,25 @@ impl AsMacroCall for InFile<&ast::MacroCall> {
return Ok(ExpandResult::only_err(ExpandError::other("malformed macro invocation")));
};
- macro_call_as_call_id_(
+ macro_call_as_call_id_with_eager(
db,
&AstIdWithPath::new(ast_id.file_id, ast_id.value, path),
expands_to,
krate,
resolver,
+ resolver,
)
}
}
/// Helper wrapper for `AstId` with `ModPath`
#[derive(Clone, Debug, Eq, PartialEq)]
-struct AstIdWithPath<T: ast::AstNode> {
+struct AstIdWithPath<T: AstIdNode> {
ast_id: AstId<T>,
path: path::ModPath,
}
-impl<T: ast::AstNode> AstIdWithPath<T> {
+impl<T: AstIdNode> AstIdWithPath<T> {
fn new(file_id: HirFileId, ast_id: FileAstId<T>, path: path::ModPath) -> AstIdWithPath<T> {
AstIdWithPath { ast_id: AstId::new(file_id, ast_id), path }
}
@@ -1117,33 +1149,39 @@ fn macro_call_as_call_id(
call: &AstIdWithPath<ast::MacroCall>,
expand_to: ExpandTo,
krate: CrateId,
- resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
+ resolver: impl Fn(path::ModPath) -> Option<MacroDefId> + Copy,
) -> Result<Option<MacroCallId>, UnresolvedMacro> {
- macro_call_as_call_id_(db, call, expand_to, krate, resolver).map(|res| res.value)
+ macro_call_as_call_id_with_eager(db, call, expand_to, krate, resolver, resolver)
+ .map(|res| res.value)
}
-fn macro_call_as_call_id_(
+fn macro_call_as_call_id_with_eager(
db: &dyn ExpandDatabase,
call: &AstIdWithPath<ast::MacroCall>,
expand_to: ExpandTo,
krate: CrateId,
- resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
+ resolver: impl FnOnce(path::ModPath) -> Option<MacroDefId>,
+ eager_resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
) -> Result<ExpandResult<Option<MacroCallId>>, UnresolvedMacro> {
let def =
resolver(call.path.clone()).ok_or_else(|| UnresolvedMacro { path: call.path.clone() })?;
- let res = if let MacroDefKind::BuiltInEager(..) = def.kind {
- let macro_call = InFile::new(call.ast_id.file_id, call.ast_id.to_node(db));
- expand_eager_macro_input(db, krate, macro_call, def, &resolver)?
- } else {
- ExpandResult {
+ let res = match def.kind {
+ MacroDefKind::BuiltInEager(..) => {
+ let macro_call = InFile::new(call.ast_id.file_id, call.ast_id.to_node(db));
+ expand_eager_macro_input(db, krate, macro_call, def, &|path| {
+ eager_resolver(path).filter(MacroDefId::is_fn_like)
+ })
+ }
+ _ if def.is_fn_like() => ExpandResult {
value: Some(def.as_lazy_macro(
db,
krate,
MacroCallKind::FnLike { ast_id: call.ast_id, expand_to },
)),
err: None,
- }
+ },
+ _ => return Err(UnresolvedMacro { path: call.path.clone() }),
};
Ok(res)
}
@@ -1228,6 +1266,7 @@ fn derive_macro_as_call_id(
resolver: impl Fn(path::ModPath) -> Option<(MacroId, MacroDefId)>,
) -> Result<(MacroId, MacroDefId, MacroCallId), UnresolvedMacro> {
let (macro_id, def_id) = resolver(item_attr.path.clone())
+ .filter(|(_, def_id)| def_id.is_derive())
.ok_or_else(|| UnresolvedMacro { path: item_attr.path.clone() })?;
let call_id = def_id.as_lazy_macro(
db.upcast(),
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/lower.rs
index af623fd0e..e523c2291 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/lower.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/lower.rs
@@ -1,5 +1,9 @@
//! Context for lowering paths.
-use hir_expand::{ast_id_map::AstIdMap, hygiene::Hygiene, AstId, HirFileId, InFile};
+use hir_expand::{
+ ast_id_map::{AstIdMap, AstIdNode},
+ hygiene::Hygiene,
+ AstId, HirFileId, InFile,
+};
use once_cell::unsync::OnceCell;
use syntax::ast;
use triomphe::Arc;
@@ -37,7 +41,7 @@ impl<'a> LowerCtx<'a> {
Path::from_src(ast, self)
}
- pub(crate) fn ast_id<N: syntax::AstNode>(&self, item: &N) -> Option<AstId<N>> {
+ pub(crate) fn ast_id<N: AstIdNode>(&self, item: &N) -> Option<AstId<N>> {
let &(file_id, ref ast_id_map) = self.ast_id_map.as_ref()?;
let ast_id_map = ast_id_map.get_or_init(|| self.db.ast_id_map(file_id));
Some(InFile::new(file_id, ast_id_map.ast_id(item)))
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs
index f41f97190..abd84c6a4 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs
@@ -279,6 +279,44 @@ impl < > core::cmp::Eq for Command< > where {}"#]],
}
#[test]
+fn test_partial_eq_expand_with_derive_const() {
+ // FIXME: actually expand with const
+ check(
+ r#"
+//- minicore: derive, eq
+#[derive_const(PartialEq, Eq)]
+enum Command {
+ Move { x: i32, y: i32 },
+ Do(&'static str),
+ Jump,
+}
+"#,
+ expect![[r#"
+#[derive_const(PartialEq, Eq)]
+enum Command {
+ Move { x: i32, y: i32 },
+ Do(&'static str),
+ Jump,
+}
+
+impl < > core::cmp::PartialEq for Command< > where {
+ fn eq(&self , other: &Self ) -> bool {
+ match (self , other) {
+ (Command::Move {
+ x: x_self, y: y_self,
+ }
+ , Command::Move {
+ x: x_other, y: y_other,
+ }
+ )=>x_self.eq(x_other) && y_self.eq(y_other), (Command::Do(f0_self, ), Command::Do(f0_other, ))=>f0_self.eq(f0_other), (Command::Jump, Command::Jump)=>true , _unused=>false
+ }
+ }
+}
+impl < > core::cmp::Eq for Command< > where {}"#]],
+ );
+}
+
+#[test]
fn test_partial_ord_expand() {
check(
r#"
@@ -379,6 +417,44 @@ fn test_hash_expand() {
use core::hash::Hash;
#[derive(Hash)]
+struct Foo {
+ x: i32,
+ y: u64,
+ z: (i32, u64),
+}
+"#,
+ expect![[r#"
+use core::hash::Hash;
+
+#[derive(Hash)]
+struct Foo {
+ x: i32,
+ y: u64,
+ z: (i32, u64),
+}
+
+impl < > core::hash::Hash for Foo< > where {
+ fn hash<H: core::hash::Hasher>(&self , ra_expand_state: &mut H) {
+ match self {
+ Foo {
+ x: x, y: y, z: z,
+ }
+ => {
+ x.hash(ra_expand_state);
+ y.hash(ra_expand_state);
+ z.hash(ra_expand_state);
+ }
+ ,
+ }
+ }
+}"#]],
+ );
+ check(
+ r#"
+//- minicore: derive, hash
+use core::hash::Hash;
+
+#[derive(Hash)]
enum Command {
Move { x: i32, y: i32 },
Do(&'static str),
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs
index 07d9baa58..1250cbb74 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs
@@ -201,7 +201,7 @@ macro_rules! format_args {
}
fn main() {
- ::core::fmt::Arguments::new_v1(&["", " ", ], &[::core::fmt::Argument::new(&(arg1(a, b, c)), ::core::fmt::Display::fmt), ::core::fmt::Argument::new(&(arg2), ::core::fmt::Debug::fmt), ]);
+ ::core::fmt::Arguments::new_v1(&["", " ", ], &[::core::fmt::ArgumentV1::new(&(arg1(a, b, c)), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(arg2), ::core::fmt::Debug::fmt), ]);
}
"##]],
);
@@ -235,11 +235,11 @@ macro_rules! format_args {
fn main() {
/* error: no rule matches input tokens */;
- /* error: no rule matches input tokens */;
- /* error: no rule matches input tokens */;
- /* error: no rule matches input tokens */::core::fmt::Arguments::new_v1(&["", ], &[::core::fmt::Argument::new(&(), ::core::fmt::Display::fmt), ]);
- /* error: no rule matches input tokens */;
- ::core::fmt::Arguments::new_v1(&["", ], &[::core::fmt::Argument::new(&(5), ::core::fmt::Display::fmt), ]);
+ /* error: expected expression */;
+ /* error: expected expression, expected COMMA */;
+ /* error: expected expression */::core::fmt::Arguments::new_v1(&["", ], &[::core::fmt::ArgumentV1::new(&(), ::core::fmt::Display::fmt), ]);
+ /* error: expected expression, expected expression */;
+ ::core::fmt::Arguments::new_v1(&["", ], &[::core::fmt::ArgumentV1::new(&(5), ::core::fmt::Display::fmt), ]);
}
"##]],
);
@@ -267,7 +267,7 @@ macro_rules! format_args {
}
fn main() {
- ::core::fmt::Arguments::new_v1(&["", " ", ], &[::core::fmt::Argument::new(&(a::<A, B>()), ::core::fmt::Display::fmt), ::core::fmt::Argument::new(&(b), ::core::fmt::Debug::fmt), ]);
+ ::core::fmt::Arguments::new_v1(&["", " ", ], &[::core::fmt::ArgumentV1::new(&(a::<A, B>()), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(b), ::core::fmt::Debug::fmt), ]);
}
"##]],
);
@@ -300,7 +300,7 @@ macro_rules! format_args {
}
fn main() {
- ::core::fmt::Arguments::new_v1(&[r#""#, r#",mismatch,""#, r#"",""#, r#"""#, ], &[::core::fmt::Argument::new(&(location_csv_pat(db, &analysis, vfs, &sm, pat_id)), ::core::fmt::Display::fmt), ::core::fmt::Argument::new(&(mismatch.expected.display(db)), ::core::fmt::Display::fmt), ::core::fmt::Argument::new(&(mismatch.actual.display(db)), ::core::fmt::Display::fmt), ]);
+ ::core::fmt::Arguments::new_v1(&[r#""#, r#",mismatch,""#, r#"",""#, r#"""#, ], &[::core::fmt::ArgumentV1::new(&(location_csv_pat(db, &analysis, vfs, &sm, pat_id)), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(mismatch.expected.display(db)), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(mismatch.actual.display(db)), ::core::fmt::Display::fmt), ]);
}
"##]],
);
@@ -334,7 +334,7 @@ macro_rules! format_args {
}
fn main() {
- ::core::fmt::Arguments::new_v1(&["xxx", "y", "zzz", ], &[::core::fmt::Argument::new(&(2), ::core::fmt::Display::fmt), ::core::fmt::Argument::new(&(b), ::core::fmt::Debug::fmt), ]);
+ ::core::fmt::Arguments::new_v1(&["xxx", "y", "zzz", ], &[::core::fmt::ArgumentV1::new(&(2), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(b), ::core::fmt::Debug::fmt), ]);
}
"##]],
);
@@ -364,8 +364,8 @@ macro_rules! format_args {
fn main() {
let _ =
- /* error: no rule matches input tokens *//* parse error: expected field name or number */
-::core::fmt::Arguments::new_v1(&["", " ", ], &[::core::fmt::Argument::new(&(a.), ::core::fmt::Display::fmt), ::core::fmt::Argument::new(&(), ::core::fmt::Debug::fmt), ]);
+ /* error: expected field name or number *//* parse error: expected field name or number */
+::core::fmt::Arguments::new_v1(&["", " ", ], &[::core::fmt::ArgumentV1::new(&(a.), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(), ::core::fmt::Debug::fmt), ]);
}
"##]],
);
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs
index 553ffe3d0..2170cadcf 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs
@@ -98,6 +98,66 @@ fn#19 main#20(#21)#21 {#22
"##]],
);
}
+
+#[test]
+fn eager_expands_with_unresolved_within() {
+ check(
+ r#"
+#[rustc_builtin_macro]
+#[macro_export]
+macro_rules! format_args {}
+
+fn main(foo: ()) {
+ format_args!("{} {} {}", format_args!("{}", 0), foo, identity!(10), "bar")
+}
+"#,
+ expect![[r##"
+#[rustc_builtin_macro]
+#[macro_export]
+macro_rules! format_args {}
+
+fn main(foo: ()) {
+ /* error: unresolved macro identity */::core::fmt::Arguments::new_v1(&["", " ", " ", ], &[::core::fmt::ArgumentV1::new(&(::core::fmt::Arguments::new_v1(&["", ], &[::core::fmt::ArgumentV1::new(&(0), ::core::fmt::Display::fmt), ])), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(foo), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(identity!(10)), ::core::fmt::Display::fmt), ])
+}
+"##]],
+ );
+}
+
+#[test]
+fn token_mapping_eager() {
+ check(
+ r#"
+#[rustc_builtin_macro]
+#[macro_export]
+macro_rules! format_args {}
+
+macro_rules! identity {
+ ($expr:expr) => { $expr };
+}
+
+fn main(foo: ()) {
+ format_args/*+tokenids*/!("{} {} {}", format_args!("{}", 0), foo, identity!(10), "bar")
+}
+
+"#,
+ expect![[r##"
+#[rustc_builtin_macro]
+#[macro_export]
+macro_rules! format_args {}
+
+macro_rules! identity {
+ ($expr:expr) => { $expr };
+}
+
+fn main(foo: ()) {
+ // format_args/*+tokenids*/!("{} {} {}"#1,#3 format_args!("{}", 0#10),#12 foo#13,#14 identity!(10#18),#21 "bar"#22)
+::core#4294967295::fmt#4294967295::Arguments#4294967295::new_v1#4294967295(&#4294967295[#4294967295""#4294967295,#4294967295 " "#4294967295,#4294967295 " "#4294967295,#4294967295 ]#4294967295,#4294967295 &#4294967295[::core#4294967295::fmt#4294967295::ArgumentV1#4294967295::new#4294967295(&#4294967295(::core#4294967295::fmt#4294967295::Arguments#4294967295::new_v1#4294967295(&#4294967295[#4294967295""#4294967295,#4294967295 ]#4294967295,#4294967295 &#4294967295[::core#4294967295::fmt#4294967295::ArgumentV1#4294967295::new#4294967295(&#4294967295(#42949672950#10)#4294967295,#4294967295 ::core#4294967295::fmt#4294967295::Display#4294967295::fmt#4294967295)#4294967295,#4294967295 ]#4294967295)#4294967295)#4294967295,#4294967295 ::core#4294967295::fmt#4294967295::Display#4294967295::fmt#4294967295)#4294967295,#4294967295 ::core#4294967295::fmt#4294967295::ArgumentV1#4294967295::new#4294967295(&#4294967295(#4294967295foo#13)#4294967295,#4294967295 ::core#4294967295::fmt#4294967295::Display#4294967295::fmt#4294967295)#4294967295,#4294967295 ::core#4294967295::fmt#4294967295::ArgumentV1#4294967295::new#4294967295(&#4294967295(#429496729510#18)#4294967295,#4294967295 ::core#4294967295::fmt#4294967295::Display#4294967295::fmt#4294967295)#4294967295,#4294967295 ]#4294967295)#4294967295
+}
+
+"##]],
+ );
+}
+
#[test]
fn float_field_access_macro_input() {
check(
@@ -813,6 +873,37 @@ fn foo() {
}
#[test]
+fn test_type_path_is_transcribed_as_expr_path() {
+ check(
+ r#"
+macro_rules! m {
+ ($p:path) => { let $p; }
+}
+fn test() {
+ m!(S)
+ m!(S<i32>)
+ m!(S<S<i32>>)
+ m!(S<{ module::CONST < 42 }>)
+}
+"#,
+ expect![[r#"
+macro_rules! m {
+ ($p:path) => { let $p; }
+}
+fn test() {
+ let S;
+ let S:: <i32> ;
+ let S:: <S:: <i32>> ;
+ let S:: < {
+ module::CONST<42
+ }
+ > ;
+}
+"#]],
+ );
+}
+
+#[test]
fn test_expr() {
check(
r#"
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs
index 4a62696df..7a87e61c6 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs
@@ -20,8 +20,8 @@ use ::mbe::TokenMap;
use base_db::{fixture::WithFixture, ProcMacro, SourceDatabase};
use expect_test::Expect;
use hir_expand::{
- db::{ExpandDatabase, TokenExpander},
- AstId, InFile, MacroDefId, MacroDefKind, MacroFile,
+ db::{DeclarativeMacroExpander, ExpandDatabase},
+ AstId, InFile, MacroFile,
};
use stdx::format_to;
use syntax::{
@@ -100,32 +100,29 @@ pub fn identity_when_valid(_attr: TokenStream, item: TokenStream) -> TokenStream
let call_offset = macro_.syntax().text_range().start().into();
let file_ast_id = db.ast_id_map(source.file_id).ast_id(&macro_);
let ast_id = AstId::new(source.file_id, file_ast_id.upcast());
- let kind = MacroDefKind::Declarative(ast_id);
- let macro_def = db
- .macro_def(MacroDefId { krate, kind, local_inner: false, allow_internal_unsafe: false })
- .unwrap();
- if let TokenExpander::DeclarativeMacro { mac, def_site_token_map } = &*macro_def {
- let tt = match &macro_ {
- ast::Macro::MacroRules(mac) => mac.token_tree().unwrap(),
- ast::Macro::MacroDef(_) => unimplemented!(""),
- };
+ let DeclarativeMacroExpander { mac, def_site_token_map } =
+ &*db.decl_macro_expander(krate, ast_id);
+ assert_eq!(mac.err(), None);
+ let tt = match &macro_ {
+ ast::Macro::MacroRules(mac) => mac.token_tree().unwrap(),
+ ast::Macro::MacroDef(_) => unimplemented!(""),
+ };
- let tt_start = tt.syntax().text_range().start();
- tt.syntax().descendants_with_tokens().filter_map(SyntaxElement::into_token).for_each(
- |token| {
- let range = token.text_range().checked_sub(tt_start).unwrap();
- if let Some(id) = def_site_token_map.token_by_range(range) {
- let offset = (range.end() + tt_start).into();
- text_edits.push((offset..offset, format!("#{}", id.0)));
- }
- },
- );
- text_edits.push((
- call_offset..call_offset,
- format!("// call ids will be shifted by {:?}\n", mac.shift()),
- ));
- }
+ let tt_start = tt.syntax().text_range().start();
+ tt.syntax().descendants_with_tokens().filter_map(SyntaxElement::into_token).for_each(
+ |token| {
+ let range = token.text_range().checked_sub(tt_start).unwrap();
+ if let Some(id) = def_site_token_map.token_by_range(range) {
+ let offset = (range.end() + tt_start).into();
+ text_edits.push((offset..offset, format!("#{}", id.0)));
+ }
+ },
+ );
+ text_edits.push((
+ call_offset..call_offset,
+ format!("// call ids will be shifted by {:?}\n", mac.shift()),
+ ));
}
for macro_call in source_file.syntax().descendants().filter_map(ast::MacroCall::cast) {
@@ -190,7 +187,7 @@ pub fn identity_when_valid(_attr: TokenStream, item: TokenStream) -> TokenStream
let range: Range<usize> = range.into();
if show_token_ids {
- if let Some((tree, map, _)) = arg.as_deref() {
+ if let Some((tree, map, _)) = arg.value.as_deref() {
let tt_range = call.token_tree().unwrap().syntax().text_range();
let mut ranges = Vec::new();
extract_id_ranges(&mut ranges, map, tree);
@@ -239,7 +236,7 @@ pub fn identity_when_valid(_attr: TokenStream, item: TokenStream) -> TokenStream
for impl_id in def_map[local_id].scope.impls() {
let src = impl_id.lookup(&db).source(&db);
- if src.file_id.is_builtin_derive(&db).is_some() {
+ if src.file_id.is_builtin_derive(&db) {
let pp = pretty_print_macro_expansion(src.value.syntax().clone(), None);
format_to!(expanded_text, "\n{}", pp)
}
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs
index 0ab1bd849..86818ce26 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs
@@ -60,7 +60,7 @@ mod tests;
use std::{cmp::Ord, ops::Deref};
use base_db::{CrateId, Edition, FileId, ProcMacroKind};
-use hir_expand::{name::Name, InFile, MacroCallId, MacroDefId};
+use hir_expand::{name::Name, HirFileId, InFile, MacroCallId, MacroDefId};
use itertools::Itertools;
use la_arena::Arena;
use profile::Count;
@@ -196,6 +196,10 @@ impl BlockRelativeModuleId {
fn into_module(self, krate: CrateId) -> ModuleId {
ModuleId { krate, block: self.block, local_id: self.local_id }
}
+
+ fn is_block_module(self) -> bool {
+ self.block.is_some() && self.local_id == DefMap::ROOT
+ }
}
impl std::ops::Index<LocalModuleId> for DefMap {
@@ -278,7 +282,9 @@ pub struct ModuleData {
pub origin: ModuleOrigin,
/// Declared visibility of this module.
pub visibility: Visibility,
- /// Always [`None`] for block modules
+ /// Parent module in the same `DefMap`.
+ ///
+ /// [`None`] for block modules because they are always its `DefMap`'s root.
pub parent: Option<LocalModuleId>,
pub children: FxHashMap<Name, LocalModuleId>,
pub scope: ItemScope,
@@ -626,6 +632,17 @@ impl ModuleData {
self.origin.definition_source(db)
}
+ /// Same as [`definition_source`] but only returns the file id to prevent parsing the ASt.
+ pub fn definition_source_file_id(&self) -> HirFileId {
+ match self.origin {
+ ModuleOrigin::File { definition, .. } | ModuleOrigin::CrateRoot { definition } => {
+ definition.into()
+ }
+ ModuleOrigin::Inline { definition, .. } => definition.file_id,
+ ModuleOrigin::BlockExpr { block } => block.file_id,
+ }
+ }
+
/// Returns a node which declares this module, either a `mod foo;` or a `mod foo {}`.
/// `None` for the crate root or block.
pub fn declaration_source(&self, db: &dyn DefDatabase) -> Option<InFile<ast::Module>> {
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs
index 62fb3c788..eef54fc49 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs
@@ -38,7 +38,7 @@ use crate::{
self, ExternCrate, Fields, FileItemTreeId, ImportKind, ItemTree, ItemTreeId, ItemTreeNode,
MacroCall, MacroDef, MacroRules, Mod, ModItem, ModKind, TreeId,
},
- macro_call_as_call_id, macro_id_to_def_id,
+ macro_call_as_call_id, macro_call_as_call_id_with_eager, macro_id_to_def_id,
nameres::{
diagnostics::DefDiagnostic,
mod_resolution::ModDir,
@@ -52,10 +52,10 @@ use crate::{
tt,
visibility::{RawVisibility, Visibility},
AdtId, AstId, AstIdWithPath, ConstLoc, CrateRootModuleId, EnumLoc, EnumVariantId,
- ExternBlockLoc, FunctionId, FunctionLoc, ImplLoc, Intern, ItemContainerId, LocalModuleId,
- Macro2Id, Macro2Loc, MacroExpander, MacroId, MacroRulesId, MacroRulesLoc, ModuleDefId,
- ModuleId, ProcMacroId, ProcMacroLoc, StaticLoc, StructLoc, TraitAliasLoc, TraitLoc,
- TypeAliasLoc, UnionLoc, UnresolvedMacro,
+ ExternBlockLoc, ExternCrateLoc, FunctionId, FunctionLoc, ImplLoc, Intern, ItemContainerId,
+ LocalModuleId, Macro2Id, Macro2Loc, MacroExpander, MacroId, MacroRulesId, MacroRulesLoc,
+ ModuleDefId, ModuleId, ProcMacroId, ProcMacroLoc, StaticLoc, StructLoc, TraitAliasLoc,
+ TraitLoc, TypeAliasLoc, UnionLoc, UnresolvedMacro, UseLoc,
};
static GLOB_RECURSION_LIMIT: Limit = Limit::new(100);
@@ -146,7 +146,7 @@ impl PartialResolvedImport {
#[derive(Clone, Debug, Eq, PartialEq)]
enum ImportSource {
- Import { id: ItemTreeId<item_tree::Import>, use_tree: Idx<ast::UseTree> },
+ Use { id: ItemTreeId<item_tree::Use>, use_tree: Idx<ast::UseTree> },
ExternCrate(ItemTreeId<item_tree::ExternCrate>),
}
@@ -156,10 +156,9 @@ struct Import {
alias: Option<ImportAlias>,
visibility: RawVisibility,
kind: ImportKind,
+ source: ImportSource,
is_prelude: bool,
- is_extern_crate: bool,
is_macro_use: bool,
- source: ImportSource,
}
impl Import {
@@ -167,27 +166,24 @@ impl Import {
db: &dyn DefDatabase,
krate: CrateId,
tree: &ItemTree,
- id: ItemTreeId<item_tree::Import>,
- ) -> Vec<Self> {
+ id: ItemTreeId<item_tree::Use>,
+ mut cb: impl FnMut(Self),
+ ) {
let it = &tree[id.value];
let attrs = &tree.attrs(db, krate, ModItem::from(id.value).into());
let visibility = &tree[it.visibility];
let is_prelude = attrs.by_key("prelude_import").exists();
-
- let mut res = Vec::new();
it.use_tree.expand(|idx, path, kind, alias| {
- res.push(Self {
+ cb(Self {
path,
alias,
visibility: visibility.clone(),
kind,
is_prelude,
- is_extern_crate: false,
is_macro_use: false,
- source: ImportSource::Import { id, use_tree: idx },
+ source: ImportSource::Use { id, use_tree: idx },
});
});
- res
}
fn from_extern_crate(
@@ -205,7 +201,6 @@ impl Import {
visibility: visibility.clone(),
kind: ImportKind::Plain,
is_prelude: false,
- is_extern_crate: true,
is_macro_use: attrs.by_key("macro_use").exists(),
source: ImportSource::ExternCrate(id),
}
@@ -776,7 +771,7 @@ impl DefCollector<'_> {
let _p = profile::span("resolve_import")
.detail(|| format!("{}", import.path.display(self.db.upcast())));
tracing::debug!("resolving import: {:?} ({:?})", import, self.def_map.data.edition);
- if import.is_extern_crate {
+ if matches!(import.source, ImportSource::ExternCrate { .. }) {
let name = import
.path
.as_ident()
@@ -813,11 +808,8 @@ impl DefCollector<'_> {
}
}
- // Check whether all namespace is resolved
- if def.take_types().is_some()
- && def.take_values().is_some()
- && def.take_macros().is_some()
- {
+ // Check whether all namespaces are resolved.
+ if def.is_full() {
PartialResolvedImport::Resolved(def)
} else {
PartialResolvedImport::Indeterminate(def)
@@ -826,7 +818,7 @@ impl DefCollector<'_> {
}
fn resolve_extern_crate(&self, name: &Name) -> Option<CrateRootModuleId> {
- if *name == name!(self) {
+ if *name == name![self] {
cov_mark::hit!(extern_crate_self_as);
Some(self.def_map.crate_root())
} else {
@@ -867,7 +859,7 @@ impl DefCollector<'_> {
tracing::debug!("resolved import {:?} ({:?}) to {:?}", name, import, def);
// extern crates in the crate root are special-cased to insert entries into the extern prelude: rust-lang/rust#54658
- if import.is_extern_crate
+ if matches!(import.source, ImportSource::ExternCrate { .. })
&& self.def_map.block.is_none()
&& module_id == DefMap::ROOT
{
@@ -1482,7 +1474,7 @@ impl DefCollector<'_> {
}
for directive in &self.unresolved_imports {
- if let ImportSource::Import { id: import, use_tree } = directive.import.source {
+ if let ImportSource::Use { id: import, use_tree } = directive.import.source {
if matches!(
(directive.import.path.segments().first(), &directive.import.path.kind),
(Some(krate), PathKind::Plain | PathKind::Abs) if diagnosed_extern_crates.contains(krate)
@@ -1584,22 +1576,33 @@ impl ModCollector<'_, '_> {
match item {
ModItem::Mod(m) => self.collect_module(m, &attrs),
- ModItem::Import(import_id) => {
- let imports = Import::from_use(
+ ModItem::Use(import_id) => {
+ let _import_id =
+ UseLoc { container: module, id: ItemTreeId::new(self.tree_id, import_id) }
+ .intern(db);
+ Import::from_use(
db,
krate,
self.item_tree,
ItemTreeId::new(self.tree_id, import_id),
- );
- self.def_collector.unresolved_imports.extend(imports.into_iter().map(
- |import| ImportDirective {
- module_id: self.module_id,
- import,
- status: PartialResolvedImport::Unresolved,
+ |import| {
+ self.def_collector.unresolved_imports.push(ImportDirective {
+ module_id: self.module_id,
+ import,
+ status: PartialResolvedImport::Unresolved,
+ });
},
- ));
+ )
}
ModItem::ExternCrate(import_id) => {
+ let extern_crate_id = ExternCrateLoc {
+ container: module,
+ id: ItemTreeId::new(self.tree_id, import_id),
+ }
+ .intern(db);
+ self.def_collector.def_map.modules[self.module_id]
+ .scope
+ .define_extern_crate_decl(extern_crate_id);
self.def_collector.unresolved_imports.push(ImportDirective {
module_id: self.module_id,
import: Import::from_extern_crate(
@@ -2182,7 +2185,7 @@ impl ModCollector<'_, '_> {
// scopes without eager expansion.
// Case 1: try to resolve macro calls with single-segment name and expand macro_rules
- if let Ok(res) = macro_call_as_call_id(
+ if let Ok(res) = macro_call_as_call_id_with_eager(
db.upcast(),
&ast_id,
mac.expand_to,
@@ -2205,19 +2208,34 @@ impl ModCollector<'_, '_> {
.map(|it| macro_id_to_def_id(self.def_collector.db, it))
})
},
- ) {
- // Legacy macros need to be expanded immediately, so that any macros they produce
- // are in scope.
- if let Some(val) = res {
- self.def_collector.collect_macro_expansion(
+ |path| {
+ let resolved_res = self.def_collector.def_map.resolve_path_fp_with_macro(
+ db,
+ ResolveMode::Other,
self.module_id,
- val,
- self.macro_depth + 1,
- container,
+ &path,
+ BuiltinShadowMode::Module,
+ Some(MacroSubNs::Bang),
);
- }
+ resolved_res.resolved_def.take_macros().map(|it| macro_id_to_def_id(db, it))
+ },
+ ) {
+ // FIXME: if there were errors, this mightve been in the eager expansion from an
+ // unresolved macro, so we need to push this into late macro resolution. see fixme above
+ if res.err.is_none() {
+ // Legacy macros need to be expanded immediately, so that any macros they produce
+ // are in scope.
+ if let Some(val) = res.value {
+ self.def_collector.collect_macro_expansion(
+ self.module_id,
+ val,
+ self.macro_depth + 1,
+ container,
+ );
+ }
- return;
+ return;
+ }
}
// Case 2: resolve in module scope, expand during name resolution.
@@ -2230,8 +2248,12 @@ impl ModCollector<'_, '_> {
}
fn import_all_legacy_macros(&mut self, module_id: LocalModuleId) {
- let Some((source, target)) = Self::borrow_modules(self.def_collector.def_map.modules.as_mut(), module_id, self.module_id) else {
- return
+ let Some((source, target)) = Self::borrow_modules(
+ self.def_collector.def_map.modules.as_mut(),
+ module_id,
+ self.module_id,
+ ) else {
+ return;
};
for (name, macs) in source.scope.legacy_macros() {
@@ -2271,7 +2293,7 @@ impl ModCollector<'_, '_> {
fn emit_unconfigured_diagnostic(&mut self, item: ModItem, cfg: &CfgExpr) {
let ast_id = item.ast_id(self.item_tree);
- let ast_id = InFile::new(self.file_id(), ast_id.upcast());
+ let ast_id = InFile::new(self.file_id(), ast_id.erase());
self.def_collector.def_map.diagnostics.push(DefDiagnostic::unconfigured_code(
self.module_id,
ast_id,
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/diagnostics.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/diagnostics.rs
index 18b424255..9cffb3c9f 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/diagnostics.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/diagnostics.rs
@@ -2,12 +2,9 @@
use base_db::CrateId;
use cfg::{CfgExpr, CfgOptions};
-use hir_expand::{attrs::AttrId, MacroCallKind};
+use hir_expand::{attrs::AttrId, ErasedAstId, MacroCallKind};
use la_arena::Idx;
-use syntax::{
- ast::{self, AnyHasAttrs},
- SyntaxError,
-};
+use syntax::{ast, SyntaxError};
use crate::{
item_tree::{self, ItemTreeId},
@@ -22,9 +19,9 @@ pub enum DefDiagnosticKind {
UnresolvedExternCrate { ast: AstId<ast::ExternCrate> },
- UnresolvedImport { id: ItemTreeId<item_tree::Import>, index: Idx<ast::UseTree> },
+ UnresolvedImport { id: ItemTreeId<item_tree::Use>, index: Idx<ast::UseTree> },
- UnconfiguredCode { ast: AstId<AnyHasAttrs>, cfg: CfgExpr, opts: CfgOptions },
+ UnconfiguredCode { ast: ErasedAstId, cfg: CfgExpr, opts: CfgOptions },
UnresolvedProcMacro { ast: MacroCallKind, krate: CrateId },
@@ -73,7 +70,7 @@ impl DefDiagnostic {
pub(super) fn unresolved_import(
container: LocalModuleId,
- id: ItemTreeId<item_tree::Import>,
+ id: ItemTreeId<item_tree::Use>,
index: Idx<ast::UseTree>,
) -> Self {
Self { in_module: container, kind: DefDiagnosticKind::UnresolvedImport { id, index } }
@@ -81,7 +78,7 @@ impl DefDiagnostic {
pub fn unconfigured_code(
container: LocalModuleId,
- ast: AstId<ast::AnyHasAttrs>,
+ ast: ErasedAstId,
cfg: CfgExpr,
opts: CfgOptions,
) -> Self {
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/path_resolution.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/path_resolution.rs
index 5f6163175..de22ea101 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/path_resolution.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/path_resolution.rs
@@ -12,11 +12,12 @@
use base_db::Edition;
use hir_expand::name::Name;
+use triomphe::Arc;
use crate::{
db::DefDatabase,
item_scope::BUILTIN_SCOPE,
- nameres::{sub_namespace_match, BuiltinShadowMode, DefMap, MacroSubNs},
+ nameres::{sub_namespace_match, BlockInfo, BuiltinShadowMode, DefMap, MacroSubNs},
path::{ModPath, PathKind},
per_ns::PerNs,
visibility::{RawVisibility, Visibility},
@@ -159,13 +160,15 @@ impl DefMap {
(None, new) => new,
};
- match &current_map.block {
- Some(block) => {
+ match current_map.block {
+ Some(block) if original_module == Self::ROOT => {
+ // Block modules "inherit" names from its parent module.
original_module = block.parent.local_id;
arc = block.parent.def_map(db, current_map.krate);
- current_map = &*arc;
+ current_map = &arc;
}
- None => return result,
+ // Proper (non-block) modules, including those in block `DefMap`s, don't.
+ _ => return result,
}
}
}
@@ -189,7 +192,7 @@ impl DefMap {
));
let mut segments = path.segments().iter().enumerate();
- let mut curr_per_ns: PerNs = match path.kind {
+ let mut curr_per_ns = match path.kind {
PathKind::DollarCrate(krate) => {
if krate == self.krate {
cov_mark::hit!(macro_dollar_crate_self);
@@ -241,51 +244,54 @@ impl DefMap {
)
}
PathKind::Super(lvl) => {
- let mut module = original_module;
- for i in 0..lvl {
- match self.modules[module].parent {
- Some(it) => module = it,
- None => match &self.block {
- Some(block) => {
- // Look up remaining path in parent `DefMap`
- let new_path = ModPath::from_segments(
- PathKind::Super(lvl - i),
- path.segments().to_vec(),
- );
- tracing::debug!(
- "`super` path: {} -> {} in parent map",
- path.display(db.upcast()),
- new_path.display(db.upcast())
- );
- return block
- .parent
- .def_map(db, self.krate)
- .resolve_path_fp_with_macro(
- db,
- mode,
- block.parent.local_id,
- &new_path,
- shadow,
- expected_macro_subns,
- );
- }
- None => {
- tracing::debug!("super path in root module");
- return ResolvePathResult::empty(ReachedFixedPoint::Yes);
- }
- },
- }
+ let mut local_id = original_module;
+ let mut ext;
+ let mut def_map = self;
+
+ // Adjust `local_id` to `self`, i.e. the nearest non-block module.
+ if def_map.module_id(local_id).is_block_module() {
+ (ext, local_id) = adjust_to_nearest_non_block_module(db, def_map, local_id);
+ def_map = &ext;
}
- // Resolve `self` to the containing crate-rooted module if we're a block
- self.with_ancestor_maps(db, module, &mut |def_map, module| {
- if def_map.block.is_some() {
- None // keep ascending
+ // Go up the module tree but skip block modules as `super` always refers to the
+ // nearest non-block module.
+ for _ in 0..lvl {
+ // Loop invariant: at the beginning of each loop, `local_id` must refer to a
+ // non-block module.
+ if let Some(parent) = def_map.modules[local_id].parent {
+ local_id = parent;
+ if def_map.module_id(local_id).is_block_module() {
+ (ext, local_id) =
+ adjust_to_nearest_non_block_module(db, def_map, local_id);
+ def_map = &ext;
+ }
} else {
- Some(PerNs::types(def_map.module_id(module).into(), Visibility::Public))
+ stdx::always!(def_map.block.is_none());
+ tracing::debug!("super path in root module");
+ return ResolvePathResult::empty(ReachedFixedPoint::Yes);
}
- })
- .expect("block DefMap not rooted in crate DefMap")
+ }
+
+ let module = def_map.module_id(local_id);
+ stdx::never!(module.is_block_module());
+
+ if self.block != def_map.block {
+ // If we have a different `DefMap` from `self` (the orignal `DefMap` we started
+ // with), resolve the remaining path segments in that `DefMap`.
+ let path =
+ ModPath::from_segments(PathKind::Super(0), path.segments().iter().cloned());
+ return def_map.resolve_path_fp_with_macro(
+ db,
+ mode,
+ local_id,
+ &path,
+ shadow,
+ expected_macro_subns,
+ );
+ }
+
+ PerNs::types(module.into(), Visibility::Public)
}
PathKind::Abs => {
// 2018-style absolute path -- only extern prelude
@@ -508,3 +514,27 @@ impl DefMap {
}
}
}
+
+/// Given a block module, returns its nearest non-block module and the `DefMap` it blongs to.
+fn adjust_to_nearest_non_block_module(
+ db: &dyn DefDatabase,
+ def_map: &DefMap,
+ mut local_id: LocalModuleId,
+) -> (Arc<DefMap>, LocalModuleId) {
+ // INVARIANT: `local_id` in `def_map` must be a block module.
+ stdx::always!(def_map.module_id(local_id).is_block_module());
+
+ let mut ext;
+ // This needs to be a local variable due to our mighty lifetime.
+ let mut def_map = def_map;
+ loop {
+ let BlockInfo { parent, .. } = def_map.block.expect("block module without parent module");
+
+ ext = parent.def_map(db, def_map.krate);
+ def_map = &ext;
+ local_id = parent.local_id;
+ if !parent.is_block_module() {
+ return (ext, local_id);
+ }
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs
index 4931c36bb..40d3a1654 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs
@@ -213,17 +213,17 @@ pub type Ty = ();
for (_, res) in module_data.scope.resolutions() {
match res.values.or(res.types).unwrap().0 {
- ModuleDefId::FunctionId(f) => drop(db.function_data(f)),
+ ModuleDefId::FunctionId(f) => _ = db.function_data(f),
ModuleDefId::AdtId(adt) => match adt {
- AdtId::StructId(it) => drop(db.struct_data(it)),
- AdtId::UnionId(it) => drop(db.union_data(it)),
- AdtId::EnumId(it) => drop(db.enum_data(it)),
+ AdtId::StructId(it) => _ = db.struct_data(it),
+ AdtId::UnionId(it) => _ = db.union_data(it),
+ AdtId::EnumId(it) => _ = db.enum_data(it),
},
- ModuleDefId::ConstId(it) => drop(db.const_data(it)),
- ModuleDefId::StaticId(it) => drop(db.static_data(it)),
- ModuleDefId::TraitId(it) => drop(db.trait_data(it)),
- ModuleDefId::TraitAliasId(it) => drop(db.trait_alias_data(it)),
- ModuleDefId::TypeAliasId(it) => drop(db.type_alias_data(it)),
+ ModuleDefId::ConstId(it) => _ = db.const_data(it),
+ ModuleDefId::StaticId(it) => _ = db.static_data(it),
+ ModuleDefId::TraitId(it) => _ = db.trait_data(it),
+ ModuleDefId::TraitAliasId(it) => _ = db.trait_alias_data(it),
+ ModuleDefId::TypeAliasId(it) => _ = db.type_alias_data(it),
ModuleDefId::EnumVariantId(_)
| ModuleDefId::ModuleId(_)
| ModuleDefId::MacroId(_)
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/path.rs b/src/tools/rust-analyzer/crates/hir-def/src/path.rs
index ff4ae6954..06530cc7e 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/path.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/path.rs
@@ -45,7 +45,7 @@ pub enum Path {
/// Invariant: the same len as `self.mod_path.segments` or `None` if all segments are `None`.
generic_args: Option<Box<[Option<Interned<GenericArgs>>]>>,
},
- /// A link to a lang item. It is used in desugaring of things like `x?`. We can show these
+ /// A link to a lang item. It is used in desugaring of things like `it?`. We can show these
/// links via a normal path since they might be private and not accessible in the usage place.
LangItem(LangItemTarget),
}
@@ -135,10 +135,7 @@ impl Path {
pub fn segments(&self) -> PathSegments<'_> {
let Path::Normal { mod_path, generic_args, .. } = self else {
- return PathSegments {
- segments: &[],
- generic_args: None,
- };
+ return PathSegments { segments: &[], generic_args: None };
};
let s =
PathSegments { segments: mod_path.segments(), generic_args: generic_args.as_deref() };
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/path/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/path/lower.rs
index 1cb17ff0d..abd817893 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/path/lower.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/path/lower.rs
@@ -74,8 +74,8 @@ pub(super) fn lower_path(mut path: ast::Path, ctx: &LowerCtx<'_>) -> Option<Path
// <T as Trait<A>>::Foo desugars to Trait<Self=T, A>::Foo
Some(trait_ref) => {
let Path::Normal { mod_path, generic_args: path_generic_args, .. } =
- Path::from_src(trait_ref.path()?, ctx)? else
- {
+ Path::from_src(trait_ref.path()?, ctx)?
+ else {
return None;
};
let num_segments = mod_path.segments().len();
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/pretty.rs b/src/tools/rust-analyzer/crates/hir-def/src/pretty.rs
index 0aead6f37..11d58a6ba 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/pretty.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/pretty.rs
@@ -12,8 +12,8 @@ use crate::{
};
pub(crate) fn print_path(db: &dyn ExpandDatabase, path: &Path, buf: &mut dyn Write) -> fmt::Result {
- if let Path::LangItem(x) = path {
- return write!(buf, "$lang_item::{x:?}");
+ if let Path::LangItem(it) = path {
+ return write!(buf, "$lang_item::{it:?}");
}
match path.type_anchor() {
Some(anchor) => {
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs b/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs
index 0d6f55411..b112c1070 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs
@@ -22,10 +22,10 @@ use crate::{
per_ns::PerNs,
visibility::{RawVisibility, Visibility},
AdtId, AssocItemId, ConstId, ConstParamId, CrateRootModuleId, DefWithBodyId, EnumId,
- EnumVariantId, ExternBlockId, FunctionId, GenericDefId, GenericParamId, HasModule, ImplId,
- ItemContainerId, LifetimeParamId, LocalModuleId, Lookup, Macro2Id, MacroId, MacroRulesId,
- ModuleDefId, ModuleId, ProcMacroId, StaticId, StructId, TraitAliasId, TraitId, TypeAliasId,
- TypeOrConstParamId, TypeOwnerId, TypeParamId, VariantId,
+ EnumVariantId, ExternBlockId, ExternCrateId, FunctionId, GenericDefId, GenericParamId,
+ HasModule, ImplId, ItemContainerId, LifetimeParamId, LocalModuleId, Lookup, Macro2Id, MacroId,
+ MacroRulesId, ModuleDefId, ModuleId, ProcMacroId, StaticId, StructId, TraitAliasId, TraitId,
+ TypeAliasId, TypeOrConstParamId, TypeOwnerId, TypeParamId, UseId, VariantId,
};
#[derive(Debug, Clone)]
@@ -186,12 +186,12 @@ impl Resolver {
Path::LangItem(l) => {
return Some((
match *l {
- LangItemTarget::Union(x) => TypeNs::AdtId(x.into()),
- LangItemTarget::TypeAlias(x) => TypeNs::TypeAliasId(x),
- LangItemTarget::Struct(x) => TypeNs::AdtId(x.into()),
- LangItemTarget::EnumVariant(x) => TypeNs::EnumVariantId(x),
- LangItemTarget::EnumId(x) => TypeNs::AdtId(x.into()),
- LangItemTarget::Trait(x) => TypeNs::TraitId(x),
+ LangItemTarget::Union(it) => TypeNs::AdtId(it.into()),
+ LangItemTarget::TypeAlias(it) => TypeNs::TypeAliasId(it),
+ LangItemTarget::Struct(it) => TypeNs::AdtId(it.into()),
+ LangItemTarget::EnumVariant(it) => TypeNs::EnumVariantId(it),
+ LangItemTarget::EnumId(it) => TypeNs::AdtId(it.into()),
+ LangItemTarget::Trait(it) => TypeNs::TraitId(it),
LangItemTarget::Function(_)
| LangItemTarget::ImplDef(_)
| LangItemTarget::Static(_) => return None,
@@ -273,10 +273,10 @@ impl Resolver {
Path::Normal { mod_path, .. } => mod_path,
Path::LangItem(l) => {
return Some(ResolveValueResult::ValueNs(match *l {
- LangItemTarget::Function(x) => ValueNs::FunctionId(x),
- LangItemTarget::Static(x) => ValueNs::StaticId(x),
- LangItemTarget::Struct(x) => ValueNs::StructId(x),
- LangItemTarget::EnumVariant(x) => ValueNs::EnumVariantId(x),
+ LangItemTarget::Function(it) => ValueNs::FunctionId(it),
+ LangItemTarget::Static(it) => ValueNs::StaticId(it),
+ LangItemTarget::Struct(it) => ValueNs::StructId(it),
+ LangItemTarget::EnumVariant(it) => ValueNs::EnumVariantId(it),
LangItemTarget::Union(_)
| LangItemTarget::ImplDef(_)
| LangItemTarget::TypeAlias(_)
@@ -425,14 +425,14 @@ impl Resolver {
/// The shadowing is accounted for: in
///
/// ```
- /// let x = 92;
+ /// let it = 92;
/// {
- /// let x = 92;
+ /// let it = 92;
/// $0
/// }
/// ```
///
- /// there will be only one entry for `x` in the result.
+ /// there will be only one entry for `it` in the result.
///
/// The result is ordered *roughly* from the innermost scope to the
/// outermost: when the name is introduced in two namespaces in two scopes,
@@ -1018,20 +1018,32 @@ impl HasResolver for ExternBlockId {
}
}
+impl HasResolver for ExternCrateId {
+ fn resolver(self, db: &dyn DefDatabase) -> Resolver {
+ self.lookup(db).container.resolver(db)
+ }
+}
+
+impl HasResolver for UseId {
+ fn resolver(self, db: &dyn DefDatabase) -> Resolver {
+ self.lookup(db).container.resolver(db)
+ }
+}
+
impl HasResolver for TypeOwnerId {
fn resolver(self, db: &dyn DefDatabase) -> Resolver {
match self {
- TypeOwnerId::FunctionId(x) => x.resolver(db),
- TypeOwnerId::StaticId(x) => x.resolver(db),
- TypeOwnerId::ConstId(x) => x.resolver(db),
- TypeOwnerId::InTypeConstId(x) => x.lookup(db).owner.resolver(db),
- TypeOwnerId::AdtId(x) => x.resolver(db),
- TypeOwnerId::TraitId(x) => x.resolver(db),
- TypeOwnerId::TraitAliasId(x) => x.resolver(db),
- TypeOwnerId::TypeAliasId(x) => x.resolver(db),
- TypeOwnerId::ImplId(x) => x.resolver(db),
- TypeOwnerId::EnumVariantId(x) => x.resolver(db),
- TypeOwnerId::ModuleId(x) => x.resolver(db),
+ TypeOwnerId::FunctionId(it) => it.resolver(db),
+ TypeOwnerId::StaticId(it) => it.resolver(db),
+ TypeOwnerId::ConstId(it) => it.resolver(db),
+ TypeOwnerId::InTypeConstId(it) => it.lookup(db).owner.resolver(db),
+ TypeOwnerId::AdtId(it) => it.resolver(db),
+ TypeOwnerId::TraitId(it) => it.resolver(db),
+ TypeOwnerId::TraitAliasId(it) => it.resolver(db),
+ TypeOwnerId::TypeAliasId(it) => it.resolver(db),
+ TypeOwnerId::ImplId(it) => it.resolver(db),
+ TypeOwnerId::EnumVariantId(it) => it.resolver(db),
+ TypeOwnerId::ModuleId(it) => it.resolver(db),
}
}
}
diff --git a/src/tools/rust-analyzer/crates/hir-expand/Cargo.toml b/src/tools/rust-analyzer/crates/hir-expand/Cargo.toml
index 40d8659f2..1f27204c1 100644
--- a/src/tools/rust-analyzer/crates/hir-expand/Cargo.toml
+++ b/src/tools/rust-analyzer/crates/hir-expand/Cargo.toml
@@ -16,11 +16,9 @@ cov-mark = "2.0.0-pre.1"
tracing = "0.1.35"
either = "1.7.0"
rustc-hash = "1.1.0"
-la-arena = { version = "0.3.0", path = "../../lib/la-arena" }
+la-arena.workspace = true
itertools = "0.10.5"
-hashbrown = { version = "0.12.1", features = [
- "inline-more",
-], default-features = false }
+hashbrown.workspace = true
smallvec.workspace = true
triomphe.workspace = true
diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/ast_id_map.rs b/src/tools/rust-analyzer/crates/hir-expand/src/ast_id_map.rs
index c2b0d5985..1906ed15b 100644
--- a/src/tools/rust-analyzer/crates/hir-expand/src/ast_id_map.rs
+++ b/src/tools/rust-analyzer/crates/hir-expand/src/ast_id_map.rs
@@ -18,47 +18,89 @@ use rustc_hash::FxHasher;
use syntax::{ast, AstNode, AstPtr, SyntaxNode, SyntaxNodePtr};
/// `AstId` points to an AST node in a specific file.
-pub struct FileAstId<N: AstNode> {
+pub struct FileAstId<N: AstIdNode> {
raw: ErasedFileAstId,
covariant: PhantomData<fn() -> N>,
}
-impl<N: AstNode> Clone for FileAstId<N> {
+impl<N: AstIdNode> Clone for FileAstId<N> {
fn clone(&self) -> FileAstId<N> {
*self
}
}
-impl<N: AstNode> Copy for FileAstId<N> {}
+impl<N: AstIdNode> Copy for FileAstId<N> {}
-impl<N: AstNode> PartialEq for FileAstId<N> {
+impl<N: AstIdNode> PartialEq for FileAstId<N> {
fn eq(&self, other: &Self) -> bool {
self.raw == other.raw
}
}
-impl<N: AstNode> Eq for FileAstId<N> {}
-impl<N: AstNode> Hash for FileAstId<N> {
+impl<N: AstIdNode> Eq for FileAstId<N> {}
+impl<N: AstIdNode> Hash for FileAstId<N> {
fn hash<H: Hasher>(&self, hasher: &mut H) {
self.raw.hash(hasher);
}
}
-impl<N: AstNode> fmt::Debug for FileAstId<N> {
+impl<N: AstIdNode> fmt::Debug for FileAstId<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "FileAstId::<{}>({})", type_name::<N>(), self.raw.into_raw())
}
}
-impl<N: AstNode> FileAstId<N> {
+impl<N: AstIdNode> FileAstId<N> {
// Can't make this a From implementation because of coherence
- pub fn upcast<M: AstNode>(self) -> FileAstId<M>
+ pub fn upcast<M: AstIdNode>(self) -> FileAstId<M>
where
N: Into<M>,
{
FileAstId { raw: self.raw, covariant: PhantomData }
}
+
+ pub fn erase(self) -> ErasedFileAstId {
+ self.raw
+ }
}
-type ErasedFileAstId = Idx<SyntaxNodePtr>;
+pub type ErasedFileAstId = Idx<SyntaxNodePtr>;
+
+pub trait AstIdNode: AstNode {}
+macro_rules! register_ast_id_node {
+ (impl AstIdNode for $($ident:ident),+ ) => {
+ $(
+ impl AstIdNode for ast::$ident {}
+ )+
+ fn should_alloc_id(kind: syntax::SyntaxKind) -> bool {
+ $(
+ ast::$ident::can_cast(kind)
+ )||+
+ }
+ };
+}
+register_ast_id_node! {
+ impl AstIdNode for
+ Item,
+ Adt,
+ Enum,
+ Struct,
+ Union,
+ Const,
+ ExternBlock,
+ ExternCrate,
+ Fn,
+ Impl,
+ Macro,
+ MacroDef,
+ MacroRules,
+ MacroCall,
+ Module,
+ Static,
+ Trait,
+ TraitAlias,
+ TypeAlias,
+ Use,
+ AssocItem, BlockExpr, Variant, RecordField, TupleField, ConstArg
+}
/// Maps items' `SyntaxNode`s to `ErasedFileAstId`s and back.
#[derive(Default)]
@@ -92,14 +134,7 @@ impl AstIdMap {
// change parent's id. This means that, say, adding a new function to a
// trait does not change ids of top-level items, which helps caching.
bdfs(node, |it| {
- let kind = it.kind();
- if ast::Item::can_cast(kind)
- || ast::BlockExpr::can_cast(kind)
- || ast::Variant::can_cast(kind)
- || ast::RecordField::can_cast(kind)
- || ast::TupleField::can_cast(kind)
- || ast::ConstArg::can_cast(kind)
- {
+ if should_alloc_id(it.kind()) {
res.alloc(&it);
true
} else {
@@ -120,15 +155,19 @@ impl AstIdMap {
res
}
- pub fn ast_id<N: AstNode>(&self, item: &N) -> FileAstId<N> {
+ pub fn ast_id<N: AstIdNode>(&self, item: &N) -> FileAstId<N> {
let raw = self.erased_ast_id(item.syntax());
FileAstId { raw, covariant: PhantomData }
}
- pub fn get<N: AstNode>(&self, id: FileAstId<N>) -> AstPtr<N> {
+ pub fn get<N: AstIdNode>(&self, id: FileAstId<N>) -> AstPtr<N> {
AstPtr::try_from_raw(self.arena[id.raw].clone()).unwrap()
}
+ pub(crate) fn get_raw(&self, id: ErasedFileAstId) -> SyntaxNodePtr {
+ self.arena[id].clone()
+ }
+
fn erased_ast_id(&self, item: &SyntaxNode) -> ErasedFileAstId {
let ptr = SyntaxNodePtr::new(item);
let hash = hash_ptr(&ptr);
diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_attr_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_attr_macro.rs
index 80695bc06..4ee12e2f2 100644
--- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_attr_macro.rs
+++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_attr_macro.rs
@@ -35,7 +35,7 @@ macro_rules! register_builtin {
impl BuiltinAttrExpander {
pub fn is_derive(self) -> bool {
- matches!(self, BuiltinAttrExpander::Derive)
+ matches!(self, BuiltinAttrExpander::Derive | BuiltinAttrExpander::DeriveConst)
}
pub fn is_test(self) -> bool {
matches!(self, BuiltinAttrExpander::Test)
@@ -50,6 +50,8 @@ register_builtin! {
(cfg_accessible, CfgAccessible) => dummy_attr_expand,
(cfg_eval, CfgEval) => dummy_attr_expand,
(derive, Derive) => derive_attr_expand,
+ // derive const is equivalent to derive for our proposes.
+ (derive_const, DeriveConst) => derive_attr_expand,
(global_allocator, GlobalAllocator) => dummy_attr_expand,
(test, Test) => dummy_attr_expand,
(test_case, TestCase) => dummy_attr_expand
diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_derive_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_derive_macro.rs
index 3d1e272b9..ecc8b407a 100644
--- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_derive_macro.rs
+++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_derive_macro.rs
@@ -12,9 +12,7 @@ use crate::{
name::{AsName, Name},
tt::{self, TokenId},
};
-use syntax::ast::{
- self, AstNode, FieldList, HasAttrs, HasGenericParams, HasModuleItem, HasName, HasTypeBounds,
-};
+use syntax::ast::{self, AstNode, FieldList, HasAttrs, HasGenericParams, HasName, HasTypeBounds};
use crate::{db::ExpandDatabase, name, quote, ExpandError, ExpandResult, MacroCallId};
@@ -30,12 +28,13 @@ macro_rules! register_builtin {
&self,
db: &dyn ExpandDatabase,
id: MacroCallId,
- tt: &tt::Subtree,
+ tt: &ast::Adt,
+ token_map: &TokenMap,
) -> ExpandResult<tt::Subtree> {
let expander = match *self {
$( BuiltinDeriveExpander::$trait => $expand, )*
};
- expander(db, id, tt)
+ expander(db, id, tt, token_map)
}
fn find_by_name(name: &name::Name) -> Option<Self> {
@@ -72,12 +71,12 @@ enum VariantShape {
}
fn tuple_field_iterator(n: usize) -> impl Iterator<Item = tt::Ident> {
- (0..n).map(|x| Ident::new(format!("f{x}"), tt::TokenId::unspecified()))
+ (0..n).map(|it| Ident::new(format!("f{it}"), tt::TokenId::unspecified()))
}
impl VariantShape {
fn as_pattern(&self, path: tt::Subtree) -> tt::Subtree {
- self.as_pattern_map(path, |x| quote!(#x))
+ self.as_pattern_map(path, |it| quote!(#it))
}
fn field_names(&self) -> Vec<tt::Ident> {
@@ -95,17 +94,17 @@ impl VariantShape {
) -> tt::Subtree {
match self {
VariantShape::Struct(fields) => {
- let fields = fields.iter().map(|x| {
- let mapped = field_map(x);
- quote! { #x : #mapped , }
+ let fields = fields.iter().map(|it| {
+ let mapped = field_map(it);
+ quote! { #it : #mapped , }
});
quote! {
#path { ##fields }
}
}
&VariantShape::Tuple(n) => {
- let fields = tuple_field_iterator(n).map(|x| {
- let mapped = field_map(&x);
+ let fields = tuple_field_iterator(n).map(|it| {
+ let mapped = field_map(&it);
quote! {
#mapped ,
}
@@ -118,16 +117,16 @@ impl VariantShape {
}
}
- fn from(value: Option<FieldList>, token_map: &TokenMap) -> Result<Self, ExpandError> {
+ fn from(tm: &TokenMap, value: Option<FieldList>) -> Result<Self, ExpandError> {
let r = match value {
None => VariantShape::Unit,
- Some(FieldList::RecordFieldList(x)) => VariantShape::Struct(
- x.fields()
- .map(|x| x.name())
- .map(|x| name_to_token(token_map, x))
+ Some(FieldList::RecordFieldList(it)) => VariantShape::Struct(
+ it.fields()
+ .map(|it| it.name())
+ .map(|it| name_to_token(tm, it))
.collect::<Result<_, _>>()?,
),
- Some(FieldList::TupleFieldList(x)) => VariantShape::Tuple(x.fields().count()),
+ Some(FieldList::TupleFieldList(it)) => VariantShape::Tuple(it.fields().count()),
};
Ok(r)
}
@@ -141,7 +140,7 @@ enum AdtShape {
impl AdtShape {
fn as_pattern(&self, name: &tt::Ident) -> Vec<tt::Subtree> {
- self.as_pattern_map(name, |x| quote!(#x))
+ self.as_pattern_map(name, |it| quote!(#it))
}
fn field_names(&self) -> Vec<Vec<tt::Ident>> {
@@ -190,32 +189,19 @@ struct BasicAdtInfo {
associated_types: Vec<tt::Subtree>,
}
-fn parse_adt(tt: &tt::Subtree) -> Result<BasicAdtInfo, ExpandError> {
- let (parsed, token_map) = mbe::token_tree_to_syntax_node(tt, mbe::TopEntryPoint::MacroItems);
- let macro_items = ast::MacroItems::cast(parsed.syntax_node()).ok_or_else(|| {
- debug!("derive node didn't parse");
- ExpandError::other("invalid item definition")
- })?;
- let item = macro_items.items().next().ok_or_else(|| {
- debug!("no module item parsed");
- ExpandError::other("no item found")
- })?;
- let adt = ast::Adt::cast(item.syntax().clone()).ok_or_else(|| {
- debug!("expected adt, found: {:?}", item);
- ExpandError::other("expected struct, enum or union")
- })?;
+fn parse_adt(tm: &TokenMap, adt: &ast::Adt) -> Result<BasicAdtInfo, ExpandError> {
let (name, generic_param_list, shape) = match &adt {
ast::Adt::Struct(it) => (
it.name(),
it.generic_param_list(),
- AdtShape::Struct(VariantShape::from(it.field_list(), &token_map)?),
+ AdtShape::Struct(VariantShape::from(tm, it.field_list())?),
),
ast::Adt::Enum(it) => {
let default_variant = it
.variant_list()
.into_iter()
- .flat_map(|x| x.variants())
- .position(|x| x.attrs().any(|x| x.simple_name() == Some("default".into())));
+ .flat_map(|it| it.variants())
+ .position(|it| it.attrs().any(|it| it.simple_name() == Some("default".into())));
(
it.name(),
it.generic_param_list(),
@@ -224,11 +210,11 @@ fn parse_adt(tt: &tt::Subtree) -> Result<BasicAdtInfo, ExpandError> {
variants: it
.variant_list()
.into_iter()
- .flat_map(|x| x.variants())
- .map(|x| {
+ .flat_map(|it| it.variants())
+ .map(|it| {
Ok((
- name_to_token(&token_map, x.name())?,
- VariantShape::from(x.field_list(), &token_map)?,
+ name_to_token(tm, it.name())?,
+ VariantShape::from(tm, it.field_list())?,
))
})
.collect::<Result<_, ExpandError>>()?,
@@ -246,16 +232,16 @@ fn parse_adt(tt: &tt::Subtree) -> Result<BasicAdtInfo, ExpandError> {
let name = {
let this = param.name();
match this {
- Some(x) => {
- param_type_set.insert(x.as_name());
- mbe::syntax_node_to_token_tree(x.syntax()).0
+ Some(it) => {
+ param_type_set.insert(it.as_name());
+ mbe::syntax_node_to_token_tree(it.syntax()).0
}
None => tt::Subtree::empty(),
}
};
let bounds = match &param {
- ast::TypeOrConstParam::Type(x) => {
- x.type_bound_list().map(|x| mbe::syntax_node_to_token_tree(x.syntax()).0)
+ ast::TypeOrConstParam::Type(it) => {
+ it.type_bound_list().map(|it| mbe::syntax_node_to_token_tree(it.syntax()).0)
}
ast::TypeOrConstParam::Const(_) => None,
};
@@ -296,9 +282,9 @@ fn parse_adt(tt: &tt::Subtree) -> Result<BasicAdtInfo, ExpandError> {
let name = p.path()?.qualifier()?.as_single_name_ref()?.as_name();
param_type_set.contains(&name).then_some(p)
})
- .map(|x| mbe::syntax_node_to_token_tree(x.syntax()).0)
+ .map(|it| mbe::syntax_node_to_token_tree(it.syntax()).0)
.collect();
- let name_token = name_to_token(&token_map, name)?;
+ let name_token = name_to_token(&tm, name)?;
Ok(BasicAdtInfo { name: name_token, shape, param_types, associated_types })
}
@@ -345,11 +331,12 @@ fn name_to_token(token_map: &TokenMap, name: Option<ast::Name>) -> Result<tt::Id
/// where B1, ..., BN are the bounds given by `bounds_paths`. Z is a phantom type, and
/// therefore does not get bound by the derived trait.
fn expand_simple_derive(
- tt: &tt::Subtree,
+ tt: &ast::Adt,
+ tm: &TokenMap,
trait_path: tt::Subtree,
make_trait_body: impl FnOnce(&BasicAdtInfo) -> tt::Subtree,
) -> ExpandResult<tt::Subtree> {
- let info = match parse_adt(tt) {
+ let info = match parse_adt(tm, tt) {
Ok(info) => info,
Err(e) => return ExpandResult::new(tt::Subtree::empty(), e),
};
@@ -373,10 +360,10 @@ fn expand_simple_derive(
})
.unzip();
- where_block.extend(info.associated_types.iter().map(|x| {
- let x = x.clone();
+ where_block.extend(info.associated_types.iter().map(|it| {
+ let it = it.clone();
let bound = trait_path.clone();
- quote! { #x : #bound , }
+ quote! { #it : #bound , }
}));
let name = info.name;
@@ -405,19 +392,21 @@ fn find_builtin_crate(db: &dyn ExpandDatabase, id: MacroCallId) -> tt::TokenTree
fn copy_expand(
db: &dyn ExpandDatabase,
id: MacroCallId,
- tt: &tt::Subtree,
+ tt: &ast::Adt,
+ tm: &TokenMap,
) -> ExpandResult<tt::Subtree> {
let krate = find_builtin_crate(db, id);
- expand_simple_derive(tt, quote! { #krate::marker::Copy }, |_| quote! {})
+ expand_simple_derive(tt, tm, quote! { #krate::marker::Copy }, |_| quote! {})
}
fn clone_expand(
db: &dyn ExpandDatabase,
id: MacroCallId,
- tt: &tt::Subtree,
+ tt: &ast::Adt,
+ tm: &TokenMap,
) -> ExpandResult<tt::Subtree> {
let krate = find_builtin_crate(db, id);
- expand_simple_derive(tt, quote! { #krate::clone::Clone }, |adt| {
+ expand_simple_derive(tt, tm, quote! { #krate::clone::Clone }, |adt| {
if matches!(adt.shape, AdtShape::Union) {
let star = tt::Punct {
char: '*',
@@ -444,7 +433,7 @@ fn clone_expand(
}
let name = &adt.name;
let patterns = adt.shape.as_pattern(name);
- let exprs = adt.shape.as_pattern_map(name, |x| quote! { #x .clone() });
+ let exprs = adt.shape.as_pattern_map(name, |it| quote! { #it .clone() });
let arms = patterns.into_iter().zip(exprs.into_iter()).map(|(pat, expr)| {
let fat_arrow = fat_arrow();
quote! {
@@ -479,10 +468,11 @@ fn and_and() -> ::tt::Subtree<TokenId> {
fn default_expand(
db: &dyn ExpandDatabase,
id: MacroCallId,
- tt: &tt::Subtree,
+ tt: &ast::Adt,
+ tm: &TokenMap,
) -> ExpandResult<tt::Subtree> {
let krate = &find_builtin_crate(db, id);
- expand_simple_derive(tt, quote! { #krate::default::Default }, |adt| {
+ expand_simple_derive(tt, tm, quote! { #krate::default::Default }, |adt| {
let body = match &adt.shape {
AdtShape::Struct(fields) => {
let name = &adt.name;
@@ -518,16 +508,17 @@ fn default_expand(
fn debug_expand(
db: &dyn ExpandDatabase,
id: MacroCallId,
- tt: &tt::Subtree,
+ tt: &ast::Adt,
+ tm: &TokenMap,
) -> ExpandResult<tt::Subtree> {
let krate = &find_builtin_crate(db, id);
- expand_simple_derive(tt, quote! { #krate::fmt::Debug }, |adt| {
+ expand_simple_derive(tt, tm, quote! { #krate::fmt::Debug }, |adt| {
let for_variant = |name: String, v: &VariantShape| match v {
VariantShape::Struct(fields) => {
- let for_fields = fields.iter().map(|x| {
- let x_string = x.to_string();
+ let for_fields = fields.iter().map(|it| {
+ let x_string = it.to_string();
quote! {
- .field(#x_string, & #x)
+ .field(#x_string, & #it)
}
});
quote! {
@@ -535,9 +526,9 @@ fn debug_expand(
}
}
VariantShape::Tuple(n) => {
- let for_fields = tuple_field_iterator(*n).map(|x| {
+ let for_fields = tuple_field_iterator(*n).map(|it| {
quote! {
- .field( & #x)
+ .field( & #it)
}
});
quote! {
@@ -598,10 +589,11 @@ fn debug_expand(
fn hash_expand(
db: &dyn ExpandDatabase,
id: MacroCallId,
- tt: &tt::Subtree,
+ tt: &ast::Adt,
+ tm: &TokenMap,
) -> ExpandResult<tt::Subtree> {
let krate = &find_builtin_crate(db, id);
- expand_simple_derive(tt, quote! { #krate::hash::Hash }, |adt| {
+ expand_simple_derive(tt, tm, quote! { #krate::hash::Hash }, |adt| {
if matches!(adt.shape, AdtShape::Union) {
// FIXME: Return expand error here
return quote! {};
@@ -621,7 +613,7 @@ fn hash_expand(
let arms = adt.shape.as_pattern(&adt.name).into_iter().zip(adt.shape.field_names()).map(
|(pat, names)| {
let expr = {
- let it = names.iter().map(|x| quote! { #x . hash(ra_expand_state); });
+ let it = names.iter().map(|it| quote! { #it . hash(ra_expand_state); });
quote! { {
##it
} }
@@ -632,9 +624,14 @@ fn hash_expand(
}
},
);
+ let check_discriminant = if matches!(&adt.shape, AdtShape::Enum { .. }) {
+ quote! { #krate::mem::discriminant(self).hash(ra_expand_state); }
+ } else {
+ quote! {}
+ };
quote! {
fn hash<H: #krate::hash::Hasher>(&self, ra_expand_state: &mut H) {
- #krate::mem::discriminant(self).hash(ra_expand_state);
+ #check_discriminant
match self {
##arms
}
@@ -646,19 +643,21 @@ fn hash_expand(
fn eq_expand(
db: &dyn ExpandDatabase,
id: MacroCallId,
- tt: &tt::Subtree,
+ tt: &ast::Adt,
+ tm: &TokenMap,
) -> ExpandResult<tt::Subtree> {
let krate = find_builtin_crate(db, id);
- expand_simple_derive(tt, quote! { #krate::cmp::Eq }, |_| quote! {})
+ expand_simple_derive(tt, tm, quote! { #krate::cmp::Eq }, |_| quote! {})
}
fn partial_eq_expand(
db: &dyn ExpandDatabase,
id: MacroCallId,
- tt: &tt::Subtree,
+ tt: &ast::Adt,
+ tm: &TokenMap,
) -> ExpandResult<tt::Subtree> {
let krate = find_builtin_crate(db, id);
- expand_simple_derive(tt, quote! { #krate::cmp::PartialEq }, |adt| {
+ expand_simple_derive(tt, tm, quote! { #krate::cmp::PartialEq }, |adt| {
if matches!(adt.shape, AdtShape::Union) {
// FIXME: Return expand error here
return quote! {};
@@ -674,9 +673,9 @@ fn partial_eq_expand(
quote!(true)
}
[first, rest @ ..] => {
- let rest = rest.iter().map(|x| {
- let t1 = Ident::new(format!("{}_self", x.text), x.span);
- let t2 = Ident::new(format!("{}_other", x.text), x.span);
+ let rest = rest.iter().map(|it| {
+ let t1 = Ident::new(format!("{}_self", it.text), it.span);
+ let t2 = Ident::new(format!("{}_other", it.text), it.span);
let and_and = and_and();
quote!(#and_and #t1 .eq( #t2 ))
});
@@ -708,12 +707,12 @@ fn self_and_other_patterns(
adt: &BasicAdtInfo,
name: &tt::Ident,
) -> (Vec<tt::Subtree>, Vec<tt::Subtree>) {
- let self_patterns = adt.shape.as_pattern_map(name, |x| {
- let t = Ident::new(format!("{}_self", x.text), x.span);
+ let self_patterns = adt.shape.as_pattern_map(name, |it| {
+ let t = Ident::new(format!("{}_self", it.text), it.span);
quote!(#t)
});
- let other_patterns = adt.shape.as_pattern_map(name, |x| {
- let t = Ident::new(format!("{}_other", x.text), x.span);
+ let other_patterns = adt.shape.as_pattern_map(name, |it| {
+ let t = Ident::new(format!("{}_other", it.text), it.span);
quote!(#t)
});
(self_patterns, other_patterns)
@@ -722,10 +721,11 @@ fn self_and_other_patterns(
fn ord_expand(
db: &dyn ExpandDatabase,
id: MacroCallId,
- tt: &tt::Subtree,
+ tt: &ast::Adt,
+ tm: &TokenMap,
) -> ExpandResult<tt::Subtree> {
let krate = &find_builtin_crate(db, id);
- expand_simple_derive(tt, quote! { #krate::cmp::Ord }, |adt| {
+ expand_simple_derive(tt, tm, quote! { #krate::cmp::Ord }, |adt| {
fn compare(
krate: &tt::TokenTree,
left: tt::Subtree,
@@ -747,9 +747,6 @@ fn ord_expand(
// FIXME: Return expand error here
return quote!();
}
- let left = quote!(#krate::intrinsics::discriminant_value(self));
- let right = quote!(#krate::intrinsics::discriminant_value(other));
-
let (self_patterns, other_patterns) = self_and_other_patterns(adt, &adt.name);
let arms = izip!(self_patterns, other_patterns, adt.shape.field_names()).map(
|(pat1, pat2, fields)| {
@@ -764,17 +761,17 @@ fn ord_expand(
},
);
let fat_arrow = fat_arrow();
- let body = compare(
- krate,
- left,
- right,
- quote! {
- match (self, other) {
- ##arms
- _unused #fat_arrow #krate::cmp::Ordering::Equal
- }
- },
- );
+ let mut body = quote! {
+ match (self, other) {
+ ##arms
+ _unused #fat_arrow #krate::cmp::Ordering::Equal
+ }
+ };
+ if matches!(&adt.shape, AdtShape::Enum { .. }) {
+ let left = quote!(#krate::intrinsics::discriminant_value(self));
+ let right = quote!(#krate::intrinsics::discriminant_value(other));
+ body = compare(krate, left, right, body);
+ }
quote! {
fn cmp(&self, other: &Self) -> #krate::cmp::Ordering {
#body
@@ -786,10 +783,11 @@ fn ord_expand(
fn partial_ord_expand(
db: &dyn ExpandDatabase,
id: MacroCallId,
- tt: &tt::Subtree,
+ tt: &ast::Adt,
+ tm: &TokenMap,
) -> ExpandResult<tt::Subtree> {
let krate = &find_builtin_crate(db, id);
- expand_simple_derive(tt, quote! { #krate::cmp::PartialOrd }, |adt| {
+ expand_simple_derive(tt, tm, quote! { #krate::cmp::PartialOrd }, |adt| {
fn compare(
krate: &tt::TokenTree,
left: tt::Subtree,
diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_fn_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_fn_macro.rs
index a9f0c154b..95c6baf42 100644
--- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_fn_macro.rs
+++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_fn_macro.rs
@@ -339,7 +339,7 @@ fn format_args_expand_general(
parts.push(mem::take(&mut last_part));
let arg_tree = if argument.is_empty() {
match args.next() {
- Some(x) => x,
+ Some(it) => it,
None => {
err = Some(mbe::ExpandError::NoMatchingRule.into());
tt::Subtree::empty()
@@ -361,7 +361,7 @@ fn format_args_expand_general(
quote!(::core::fmt::Display::fmt)
}
};
- arg_tts.push(quote! { ::core::fmt::Argument::new(&(#arg_tree), #formatter), });
+ arg_tts.push(quote! { ::core::fmt::ArgumentV1::new(&(#arg_tree), #formatter), });
}
'}' => {
if format_iter.peek() == Some(&'}') {
@@ -378,11 +378,11 @@ fn format_args_expand_general(
if !last_part.is_empty() {
parts.push(last_part);
}
- let part_tts = parts.into_iter().map(|x| {
+ let part_tts = parts.into_iter().map(|it| {
let text = if let Some(raw) = &raw_sharps {
- format!("r{raw}\"{}\"{raw}", x).into()
+ format!("r{raw}\"{}\"{raw}", it).into()
} else {
- format!("\"{}\"", x).into()
+ format!("\"{}\"", it).into()
};
let l = tt::Literal { span: tt::TokenId::unspecified(), text };
quote!(#l ,)
@@ -574,7 +574,7 @@ fn concat_bytes_expand(
syntax::SyntaxKind::BYTE => bytes.push(token.text().to_string()),
syntax::SyntaxKind::BYTE_STRING => {
let components = unquote_byte_string(lit).unwrap_or_default();
- components.into_iter().for_each(|x| bytes.push(x.to_string()));
+ components.into_iter().for_each(|it| bytes.push(it.to_string()));
}
_ => {
err.get_or_insert(mbe::ExpandError::UnexpectedToken.into());
@@ -692,7 +692,7 @@ pub(crate) fn include_arg_to_tt(
arg_id: MacroCallId,
) -> Result<(triomphe::Arc<(::tt::Subtree<::tt::TokenId>, TokenMap)>, FileId), ExpandError> {
let loc = db.lookup_intern_macro_call(arg_id);
- let Some(EagerCallInfo {arg, arg_id: Some(arg_id), .. }) = loc.eager.as_deref() else {
+ let Some(EagerCallInfo { arg,arg_id, .. }) = loc.eager.as_deref() else {
panic!("include_arg_to_tt called on non include macro call: {:?}", &loc.eager);
};
let path = parse_string(&arg.0)?;
diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/db.rs b/src/tools/rust-analyzer/crates/hir-expand/src/db.rs
index 78b2db730..5292a5fa1 100644
--- a/src/tools/rust-analyzer/crates/hir-expand/src/db.rs
+++ b/src/tools/rust-analyzer/crates/hir-expand/src/db.rs
@@ -1,9 +1,9 @@
//! Defines database & queries for macro expansion.
-use base_db::{salsa, Edition, SourceDatabase};
+use base_db::{salsa, CrateId, Edition, SourceDatabase};
use either::Either;
use limit::Limit;
-use mbe::syntax_node_to_token_tree;
+use mbe::{syntax_node_to_token_tree, ValueResult};
use rustc_hash::FxHashSet;
use syntax::{
ast::{self, HasAttrs, HasDocComments},
@@ -13,7 +13,7 @@ use triomphe::Arc;
use crate::{
ast_id_map::AstIdMap, builtin_attr_macro::pseudo_derive_attr_expansion,
- builtin_fn_macro::EagerExpander, fixup, hygiene::HygieneFrame, tt, BuiltinAttrExpander,
+ builtin_fn_macro::EagerExpander, fixup, hygiene::HygieneFrame, tt, AstId, BuiltinAttrExpander,
BuiltinDeriveExpander, BuiltinFnLikeExpander, EagerCallInfo, ExpandError, ExpandResult,
ExpandTo, HirFileId, HirFileIdRepr, MacroCallId, MacroCallKind, MacroCallLoc, MacroDefId,
MacroDefKind, MacroFile, ProcMacroExpander,
@@ -28,61 +28,67 @@ use crate::{
static TOKEN_LIMIT: Limit = Limit::new(1_048_576);
#[derive(Debug, Clone, Eq, PartialEq)]
+/// Old-style `macro_rules` or the new macros 2.0
+pub struct DeclarativeMacroExpander {
+ pub mac: mbe::DeclarativeMacro,
+ pub def_site_token_map: mbe::TokenMap,
+}
+
+impl DeclarativeMacroExpander {
+ pub fn expand(&self, tt: tt::Subtree) -> ExpandResult<tt::Subtree> {
+ match self.mac.err() {
+ Some(e) => ExpandResult::new(
+ tt::Subtree::empty(),
+ ExpandError::other(format!("invalid macro definition: {e}")),
+ ),
+ None => self.mac.expand(tt).map_err(Into::into),
+ }
+ }
+
+ pub fn map_id_down(&self, token_id: tt::TokenId) -> tt::TokenId {
+ self.mac.map_id_down(token_id)
+ }
+
+ pub fn map_id_up(&self, token_id: tt::TokenId) -> (tt::TokenId, mbe::Origin) {
+ self.mac.map_id_up(token_id)
+ }
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
pub enum TokenExpander {
- /// Old-style `macro_rules` or the new macros 2.0
- DeclarativeMacro { mac: mbe::DeclarativeMacro, def_site_token_map: mbe::TokenMap },
+ DeclarativeMacro(Arc<DeclarativeMacroExpander>),
/// Stuff like `line!` and `file!`.
- Builtin(BuiltinFnLikeExpander),
+ BuiltIn(BuiltinFnLikeExpander),
/// Built-in eagerly expanded fn-like macros (`include!`, `concat!`, etc.)
- BuiltinEager(EagerExpander),
+ BuiltInEager(EagerExpander),
/// `global_allocator` and such.
- BuiltinAttr(BuiltinAttrExpander),
+ BuiltInAttr(BuiltinAttrExpander),
/// `derive(Copy)` and such.
- BuiltinDerive(BuiltinDeriveExpander),
+ BuiltInDerive(BuiltinDeriveExpander),
/// The thing we love the most here in rust-analyzer -- procedural macros.
ProcMacro(ProcMacroExpander),
}
+// FIXME: Get rid of these methods
impl TokenExpander {
- fn expand(
- &self,
- db: &dyn ExpandDatabase,
- id: MacroCallId,
- tt: &tt::Subtree,
- ) -> ExpandResult<tt::Subtree> {
- match self {
- TokenExpander::DeclarativeMacro { mac, .. } => mac.expand(tt).map_err(Into::into),
- TokenExpander::Builtin(it) => it.expand(db, id, tt).map_err(Into::into),
- TokenExpander::BuiltinEager(it) => it.expand(db, id, tt).map_err(Into::into),
- TokenExpander::BuiltinAttr(it) => it.expand(db, id, tt),
- TokenExpander::BuiltinDerive(it) => it.expand(db, id, tt),
- TokenExpander::ProcMacro(_) => {
- // We store the result in salsa db to prevent non-deterministic behavior in
- // some proc-macro implementation
- // See #4315 for details
- db.expand_proc_macro(id)
- }
- }
- }
-
pub(crate) fn map_id_down(&self, id: tt::TokenId) -> tt::TokenId {
match self {
- TokenExpander::DeclarativeMacro { mac, .. } => mac.map_id_down(id),
- TokenExpander::Builtin(..)
- | TokenExpander::BuiltinEager(..)
- | TokenExpander::BuiltinAttr(..)
- | TokenExpander::BuiltinDerive(..)
+ TokenExpander::DeclarativeMacro(expander) => expander.map_id_down(id),
+ TokenExpander::BuiltIn(..)
+ | TokenExpander::BuiltInEager(..)
+ | TokenExpander::BuiltInAttr(..)
+ | TokenExpander::BuiltInDerive(..)
| TokenExpander::ProcMacro(..) => id,
}
}
pub(crate) fn map_id_up(&self, id: tt::TokenId) -> (tt::TokenId, mbe::Origin) {
match self {
- TokenExpander::DeclarativeMacro { mac, .. } => mac.map_id_up(id),
- TokenExpander::Builtin(..)
- | TokenExpander::BuiltinEager(..)
- | TokenExpander::BuiltinAttr(..)
- | TokenExpander::BuiltinDerive(..)
+ TokenExpander::DeclarativeMacro(expander) => expander.map_id_up(id),
+ TokenExpander::BuiltIn(..)
+ | TokenExpander::BuiltInEager(..)
+ | TokenExpander::BuiltInAttr(..)
+ | TokenExpander::BuiltInDerive(..)
| TokenExpander::ProcMacro(..) => (id, mbe::Origin::Call),
}
}
@@ -118,14 +124,26 @@ pub trait ExpandDatabase: SourceDatabase {
fn macro_arg(
&self,
id: MacroCallId,
- ) -> Option<Arc<(tt::Subtree, mbe::TokenMap, fixup::SyntaxFixupUndoInfo)>>;
+ ) -> ValueResult<
+ Option<Arc<(tt::Subtree, mbe::TokenMap, fixup::SyntaxFixupUndoInfo)>>,
+ Arc<Box<[SyntaxError]>>,
+ >;
/// Extracts syntax node, corresponding to a macro call. That's a firewall
/// query, only typing in the macro call itself changes the returned
/// subtree.
- fn macro_arg_text(&self, id: MacroCallId) -> Option<GreenNode>;
- /// Gets the expander for this macro. This compiles declarative macros, and
- /// just fetches procedural ones.
- fn macro_def(&self, id: MacroDefId) -> Result<Arc<TokenExpander>, mbe::ParseError>;
+ fn macro_arg_node(
+ &self,
+ id: MacroCallId,
+ ) -> ValueResult<Option<GreenNode>, Arc<Box<[SyntaxError]>>>;
+ /// Fetches the expander for this macro.
+ #[salsa::transparent]
+ fn macro_expander(&self, id: MacroDefId) -> TokenExpander;
+ /// Fetches (and compiles) the expander of this decl macro.
+ fn decl_macro_expander(
+ &self,
+ def_crate: CrateId,
+ id: AstId<ast::Macro>,
+ ) -> Arc<DeclarativeMacroExpander>;
/// Expand macro call to a token tree.
// This query is LRU cached
@@ -141,8 +159,8 @@ pub trait ExpandDatabase: SourceDatabase {
/// Special case of the previous query for procedural macros. We can't LRU
/// proc macros, since they are not deterministic in general, and
/// non-determinism breaks salsa in a very, very, very bad way.
- /// @edwin0cheng heroically debugged this once!
- fn expand_proc_macro(&self, call: MacroCallId) -> ExpandResult<tt::Subtree>;
+ /// @edwin0cheng heroically debugged this once! See #4315 for details
+ fn expand_proc_macro(&self, call: MacroCallId) -> ExpandResult<Arc<tt::Subtree>>;
/// Firewall query that returns the errors from the `parse_macro_expansion` query.
fn parse_macro_expansion_error(
&self,
@@ -163,7 +181,6 @@ pub fn expand_speculative(
token_to_map: SyntaxToken,
) -> Option<(SyntaxNode, SyntaxToken)> {
let loc = db.lookup_intern_macro_call(actual_macro_call);
- let macro_def = db.macro_def(loc.def).ok()?;
let token_range = token_to_map.text_range();
// Build the subtree and token mapping for the speculative args
@@ -221,7 +238,12 @@ pub fn expand_speculative(
None => {
let range = token_range.checked_sub(speculative_args.text_range().start())?;
let token_id = spec_args_tmap.token_by_range(range)?;
- macro_def.map_id_down(token_id)
+ match loc.def.kind {
+ MacroDefKind::Declarative(it) => {
+ db.decl_macro_expander(loc.krate, it).map_id_down(token_id)
+ }
+ _ => token_id,
+ }
}
};
@@ -235,7 +257,17 @@ pub fn expand_speculative(
MacroDefKind::BuiltInAttr(BuiltinAttrExpander::Derive, _) => {
pseudo_derive_attr_expansion(&tt, attr_arg.as_ref()?)
}
- _ => macro_def.expand(db, actual_macro_call, &tt),
+ MacroDefKind::BuiltInDerive(expander, ..) => {
+ // this cast is a bit sus, can we avoid losing the typedness here?
+ let adt = ast::Adt::cast(speculative_args.clone()).unwrap();
+ expander.expand(db, actual_macro_call, &adt, &spec_args_tmap)
+ }
+ MacroDefKind::Declarative(it) => db.decl_macro_expander(loc.krate, it).expand(tt),
+ MacroDefKind::BuiltIn(it, _) => it.expand(db, actual_macro_call, &tt).map_err(Into::into),
+ MacroDefKind::BuiltInEager(it, _) => {
+ it.expand(db, actual_macro_call, &tt).map_err(Into::into)
+ }
+ MacroDefKind::BuiltInAttr(it, _) => it.expand(db, actual_macro_call, &tt),
};
let expand_to = macro_expand_to(db, actual_macro_call);
@@ -297,17 +329,31 @@ fn parse_macro_expansion(
ExpandResult { value: (parse, Arc::new(rev_token_map)), err }
}
+fn parse_macro_expansion_error(
+ db: &dyn ExpandDatabase,
+ macro_call_id: MacroCallId,
+) -> ExpandResult<Box<[SyntaxError]>> {
+ db.parse_macro_expansion(MacroFile { macro_call_id })
+ .map(|it| it.0.errors().to_vec().into_boxed_slice())
+}
+
fn macro_arg(
db: &dyn ExpandDatabase,
id: MacroCallId,
-) -> Option<Arc<(tt::Subtree, mbe::TokenMap, fixup::SyntaxFixupUndoInfo)>> {
+) -> ValueResult<
+ Option<Arc<(tt::Subtree, mbe::TokenMap, fixup::SyntaxFixupUndoInfo)>>,
+ Arc<Box<[SyntaxError]>>,
+> {
let loc = db.lookup_intern_macro_call(id);
- if let Some(EagerCallInfo { arg, arg_id: Some(_), error: _ }) = loc.eager.as_deref() {
- return Some(Arc::new((arg.0.clone(), arg.1.clone(), Default::default())));
+ if let Some(EagerCallInfo { arg, arg_id: _, error: _ }) = loc.eager.as_deref() {
+ return ValueResult::ok(Some(Arc::new((arg.0.clone(), arg.1.clone(), Default::default()))));
}
- let arg = db.macro_arg_text(id)?;
+ let ValueResult { value, err } = db.macro_arg_node(id);
+ let Some(arg) = value else {
+ return ValueResult { value: None, err };
+ };
let node = SyntaxNode::new_root(arg);
let censor = censor_for_macro_input(&loc, &node);
@@ -325,9 +371,16 @@ fn macro_arg(
// proc macros expect their inputs without parentheses, MBEs expect it with them included
tt.delimiter = tt::Delimiter::unspecified();
}
- Some(Arc::new((tt, tmap, fixups.undo_info)))
+ let val = Some(Arc::new((tt, tmap, fixups.undo_info)));
+ match err {
+ Some(err) => ValueResult::new(val, err),
+ None => ValueResult::ok(val),
+ }
}
+/// Certain macro calls expect some nodes in the input to be preprocessed away, namely:
+/// - derives expect all `#[derive(..)]` invocations up to the currently invoked one to be stripped
+/// - attributes expect the invoking attribute to be stripped
fn censor_for_macro_input(loc: &MacroCallLoc, node: &SyntaxNode) -> FxHashSet<SyntaxNode> {
// FIXME: handle `cfg_attr`
(|| {
@@ -364,9 +417,43 @@ fn censor_for_macro_input(loc: &MacroCallLoc, node: &SyntaxNode) -> FxHashSet<Sy
.unwrap_or_default()
}
-fn macro_arg_text(db: &dyn ExpandDatabase, id: MacroCallId) -> Option<GreenNode> {
+fn macro_arg_node(
+ db: &dyn ExpandDatabase,
+ id: MacroCallId,
+) -> ValueResult<Option<GreenNode>, Arc<Box<[SyntaxError]>>> {
+ let err = || -> Arc<Box<[_]>> {
+ Arc::new(Box::new([SyntaxError::new_at_offset(
+ "invalid macro call".to_owned(),
+ syntax::TextSize::from(0),
+ )]))
+ };
let loc = db.lookup_intern_macro_call(id);
- let arg = loc.kind.arg(db)?;
+ let arg = if let MacroDefKind::BuiltInEager(..) = loc.def.kind {
+ let res = if let Some(EagerCallInfo { arg, .. }) = loc.eager.as_deref() {
+ Some(mbe::token_tree_to_syntax_node(&arg.0, mbe::TopEntryPoint::MacroEagerInput).0)
+ } else {
+ loc.kind
+ .arg(db)
+ .and_then(|arg| ast::TokenTree::cast(arg.value))
+ .map(|tt| tt.reparse_as_comma_separated_expr().to_syntax())
+ };
+ match res {
+ Some(res) if res.errors().is_empty() => res.syntax_node(),
+ Some(res) => {
+ return ValueResult::new(
+ Some(res.syntax_node().green().into()),
+ // Box::<[_]>::from(res.errors()), not stable yet
+ Arc::new(res.errors().to_vec().into_boxed_slice()),
+ );
+ }
+ None => return ValueResult::only_err(err()),
+ }
+ } else {
+ match loc.kind.arg(db) {
+ Some(res) => res.value,
+ None => return ValueResult::only_err(err()),
+ }
+ };
if matches!(loc.kind, MacroCallKind::FnLike { .. }) {
let first = arg.first_child_or_token().map_or(T![.], |it| it.kind());
let last = arg.last_child_or_token().map_or(T![.], |it| it.kind());
@@ -381,101 +468,146 @@ fn macro_arg_text(db: &dyn ExpandDatabase, id: MacroCallId) -> Option<GreenNode>
// Some day, we'll have explicit recursion counters for all
// recursive things, at which point this code might be removed.
cov_mark::hit!(issue9358_bad_macro_stack_overflow);
- return None;
+ return ValueResult::only_err(Arc::new(Box::new([SyntaxError::new(
+ "unbalanced token tree".to_owned(),
+ arg.text_range(),
+ )])));
}
}
- if let Some(EagerCallInfo { arg, .. }) = loc.eager.as_deref() {
- Some(
- mbe::token_tree_to_syntax_node(&arg.0, mbe::TopEntryPoint::Expr)
- .0
- .syntax_node()
- .green()
- .into(),
- )
- } else {
- Some(arg.green().into())
- }
+ ValueResult::ok(Some(arg.green().into()))
}
-fn macro_def(
+fn decl_macro_expander(
db: &dyn ExpandDatabase,
- id: MacroDefId,
-) -> Result<Arc<TokenExpander>, mbe::ParseError> {
+ def_crate: CrateId,
+ id: AstId<ast::Macro>,
+) -> Arc<DeclarativeMacroExpander> {
+ let is_2021 = db.crate_graph()[def_crate].edition >= Edition::Edition2021;
+ let (mac, def_site_token_map) = match id.to_node(db) {
+ ast::Macro::MacroRules(macro_rules) => match macro_rules.token_tree() {
+ Some(arg) => {
+ let (tt, def_site_token_map) = mbe::syntax_node_to_token_tree(arg.syntax());
+ let mac = mbe::DeclarativeMacro::parse_macro_rules(&tt, is_2021);
+ (mac, def_site_token_map)
+ }
+ None => (
+ mbe::DeclarativeMacro::from_err(
+ mbe::ParseError::Expected("expected a token tree".into()),
+ is_2021,
+ ),
+ Default::default(),
+ ),
+ },
+ ast::Macro::MacroDef(macro_def) => match macro_def.body() {
+ Some(arg) => {
+ let (tt, def_site_token_map) = mbe::syntax_node_to_token_tree(arg.syntax());
+ let mac = mbe::DeclarativeMacro::parse_macro2(&tt, is_2021);
+ (mac, def_site_token_map)
+ }
+ None => (
+ mbe::DeclarativeMacro::from_err(
+ mbe::ParseError::Expected("expected a token tree".into()),
+ is_2021,
+ ),
+ Default::default(),
+ ),
+ },
+ };
+ Arc::new(DeclarativeMacroExpander { mac, def_site_token_map })
+}
+
+fn macro_expander(db: &dyn ExpandDatabase, id: MacroDefId) -> TokenExpander {
match id.kind {
MacroDefKind::Declarative(ast_id) => {
- let is_2021 = db.crate_graph()[id.krate].edition >= Edition::Edition2021;
- let (mac, def_site_token_map) = match ast_id.to_node(db) {
- ast::Macro::MacroRules(macro_rules) => {
- let arg = macro_rules
- .token_tree()
- .ok_or_else(|| mbe::ParseError::Expected("expected a token tree".into()))?;
- let (tt, def_site_token_map) = mbe::syntax_node_to_token_tree(arg.syntax());
- let mac = mbe::DeclarativeMacro::parse_macro_rules(&tt, is_2021)?;
- (mac, def_site_token_map)
- }
- ast::Macro::MacroDef(macro_def) => {
- let arg = macro_def
- .body()
- .ok_or_else(|| mbe::ParseError::Expected("expected a token tree".into()))?;
- let (tt, def_site_token_map) = mbe::syntax_node_to_token_tree(arg.syntax());
- let mac = mbe::DeclarativeMacro::parse_macro2(&tt, is_2021)?;
- (mac, def_site_token_map)
- }
- };
- Ok(Arc::new(TokenExpander::DeclarativeMacro { mac, def_site_token_map }))
- }
- MacroDefKind::BuiltIn(expander, _) => Ok(Arc::new(TokenExpander::Builtin(expander))),
- MacroDefKind::BuiltInAttr(expander, _) => {
- Ok(Arc::new(TokenExpander::BuiltinAttr(expander)))
+ TokenExpander::DeclarativeMacro(db.decl_macro_expander(id.krate, ast_id))
}
- MacroDefKind::BuiltInDerive(expander, _) => {
- Ok(Arc::new(TokenExpander::BuiltinDerive(expander)))
- }
- MacroDefKind::BuiltInEager(expander, ..) => {
- Ok(Arc::new(TokenExpander::BuiltinEager(expander)))
- }
- MacroDefKind::ProcMacro(expander, ..) => Ok(Arc::new(TokenExpander::ProcMacro(expander))),
+ MacroDefKind::BuiltIn(expander, _) => TokenExpander::BuiltIn(expander),
+ MacroDefKind::BuiltInAttr(expander, _) => TokenExpander::BuiltInAttr(expander),
+ MacroDefKind::BuiltInDerive(expander, _) => TokenExpander::BuiltInDerive(expander),
+ MacroDefKind::BuiltInEager(expander, ..) => TokenExpander::BuiltInEager(expander),
+ MacroDefKind::ProcMacro(expander, ..) => TokenExpander::ProcMacro(expander),
}
}
fn macro_expand(db: &dyn ExpandDatabase, id: MacroCallId) -> ExpandResult<Arc<tt::Subtree>> {
let _p = profile::span("macro_expand");
let loc = db.lookup_intern_macro_call(id);
- if let Some(EagerCallInfo { arg, arg_id: None, error }) = loc.eager.as_deref() {
- // This is an input expansion for an eager macro. These are already pre-expanded
- return ExpandResult { value: Arc::new(arg.0.clone()), err: error.clone() };
- }
- let expander = match db.macro_def(loc.def) {
- Ok(it) => it,
- // FIXME: We should make sure to enforce a variant that invalid macro
- // definitions do not get expanders that could reach this call path!
- Err(err) => {
- return ExpandResult {
- value: Arc::new(tt::Subtree {
- delimiter: tt::Delimiter::UNSPECIFIED,
- token_trees: vec![],
- }),
- err: Some(ExpandError::other(format!("invalid macro definition: {err}"))),
- }
+
+ let ExpandResult { value: tt, mut err } = match loc.def.kind {
+ MacroDefKind::ProcMacro(..) => return db.expand_proc_macro(id),
+ MacroDefKind::BuiltInDerive(expander, ..) => {
+ let arg = db.macro_arg_node(id).value.unwrap();
+
+ let node = SyntaxNode::new_root(arg);
+ let censor = censor_for_macro_input(&loc, &node);
+ let mut fixups = fixup::fixup_syntax(&node);
+ fixups.replace.extend(censor.into_iter().map(|node| (node.into(), Vec::new())));
+ let (tmap, _) = mbe::syntax_node_to_token_map_with_modifications(
+ &node,
+ fixups.token_map,
+ fixups.next_id,
+ fixups.replace,
+ fixups.append,
+ );
+
+ // this cast is a bit sus, can we avoid losing the typedness here?
+ let adt = ast::Adt::cast(node).unwrap();
+ let mut res = expander.expand(db, id, &adt, &tmap);
+ fixup::reverse_fixups(&mut res.value, &tmap, &fixups.undo_info);
+ res
+ }
+ _ => {
+ let ValueResult { value, err } = db.macro_arg(id);
+ let Some(macro_arg) = value else {
+ return ExpandResult {
+ value: Arc::new(tt::Subtree {
+ delimiter: tt::Delimiter::UNSPECIFIED,
+ token_trees: Vec::new(),
+ }),
+ // FIXME: We should make sure to enforce an invariant that invalid macro
+ // calls do not reach this call path!
+ err: Some(ExpandError::other("invalid token tree")),
+ };
+ };
+
+ let (arg, arg_tm, undo_info) = &*macro_arg;
+ let mut res = match loc.def.kind {
+ MacroDefKind::Declarative(id) => {
+ db.decl_macro_expander(loc.def.krate, id).expand(arg.clone())
+ }
+ MacroDefKind::BuiltIn(it, _) => it.expand(db, id, &arg).map_err(Into::into),
+ // This might look a bit odd, but we do not expand the inputs to eager macros here.
+ // Eager macros inputs are expanded, well, eagerly when we collect the macro calls.
+ // That kind of expansion uses the ast id map of an eager macros input though which goes through
+ // the HirFileId machinery. As eager macro inputs are assigned a macro file id that query
+ // will end up going through here again, whereas we want to just want to inspect the raw input.
+ // As such we just return the input subtree here.
+ MacroDefKind::BuiltInEager(..) if loc.eager.is_none() => {
+ let mut arg = arg.clone();
+ fixup::reverse_fixups(&mut arg, arg_tm, undo_info);
+
+ return ExpandResult {
+ value: Arc::new(arg),
+ err: err.map(|err| {
+ let mut buf = String::new();
+ for err in &**err {
+ use std::fmt::Write;
+ _ = write!(buf, "{}, ", err);
+ }
+ buf.pop();
+ buf.pop();
+ ExpandError::other(buf)
+ }),
+ };
+ }
+ MacroDefKind::BuiltInEager(it, _) => it.expand(db, id, &arg).map_err(Into::into),
+ MacroDefKind::BuiltInAttr(it, _) => it.expand(db, id, &arg),
+ _ => unreachable!(),
+ };
+ fixup::reverse_fixups(&mut res.value, arg_tm, undo_info);
+ res
}
};
- let Some(macro_arg) = db.macro_arg(id) else {
- return ExpandResult {
- value: Arc::new(
- tt::Subtree {
- delimiter: tt::Delimiter::UNSPECIFIED,
- token_trees: Vec::new(),
- },
- ),
- // FIXME: We should make sure to enforce a variant that invalid macro
- // calls do not reach this call path!
- err: Some(ExpandError::other(
- "invalid token tree"
- )),
- };
- };
- let (arg_tt, arg_tm, undo_info) = &*macro_arg;
- let ExpandResult { value: mut tt, mut err } = expander.expand(db, id, arg_tt);
if let Some(EagerCallInfo { error, .. }) = loc.eager.as_deref() {
// FIXME: We should report both errors!
@@ -483,48 +615,29 @@ fn macro_expand(db: &dyn ExpandDatabase, id: MacroCallId) -> ExpandResult<Arc<tt
}
// Set a hard limit for the expanded tt
- let count = tt.count();
- if TOKEN_LIMIT.check(count).is_err() {
- return ExpandResult {
- value: Arc::new(tt::Subtree {
- delimiter: tt::Delimiter::UNSPECIFIED,
- token_trees: vec![],
- }),
- err: Some(ExpandError::other(format!(
- "macro invocation exceeds token limit: produced {} tokens, limit is {}",
- count,
- TOKEN_LIMIT.inner(),
- ))),
- };
+ if let Err(value) = check_tt_count(&tt) {
+ return value;
}
- fixup::reverse_fixups(&mut tt, arg_tm, undo_info);
-
ExpandResult { value: Arc::new(tt), err }
}
-fn parse_macro_expansion_error(
- db: &dyn ExpandDatabase,
- macro_call_id: MacroCallId,
-) -> ExpandResult<Box<[SyntaxError]>> {
- db.parse_macro_expansion(MacroFile { macro_call_id })
- .map(|it| it.0.errors().to_vec().into_boxed_slice())
-}
-
-fn expand_proc_macro(db: &dyn ExpandDatabase, id: MacroCallId) -> ExpandResult<tt::Subtree> {
+fn expand_proc_macro(db: &dyn ExpandDatabase, id: MacroCallId) -> ExpandResult<Arc<tt::Subtree>> {
let loc = db.lookup_intern_macro_call(id);
- let Some(macro_arg) = db.macro_arg(id) else {
+ let Some(macro_arg) = db.macro_arg(id).value else {
return ExpandResult {
- value: tt::Subtree {
+ value: Arc::new(tt::Subtree {
delimiter: tt::Delimiter::UNSPECIFIED,
token_trees: Vec::new(),
- },
- err: Some(ExpandError::other(
- "invalid token tree"
- )),
+ }),
+ // FIXME: We should make sure to enforce an invariant that invalid macro
+ // calls do not reach this call path!
+ err: Some(ExpandError::other("invalid token tree")),
};
};
+ let (arg_tt, arg_tm, undo_info) = &*macro_arg;
+
let expander = match loc.def.kind {
MacroDefKind::ProcMacro(expander, ..) => expander,
_ => unreachable!(),
@@ -533,13 +646,23 @@ fn expand_proc_macro(db: &dyn ExpandDatabase, id: MacroCallId) -> ExpandResult<t
let attr_arg = match &loc.kind {
MacroCallKind::Attr { attr_args, .. } => {
let mut attr_args = attr_args.0.clone();
- mbe::Shift::new(&macro_arg.0).shift_all(&mut attr_args);
+ mbe::Shift::new(arg_tt).shift_all(&mut attr_args);
Some(attr_args)
}
_ => None,
};
- expander.expand(db, loc.def.krate, loc.krate, &macro_arg.0, attr_arg.as_ref())
+ let ExpandResult { value: mut tt, err } =
+ expander.expand(db, loc.def.krate, loc.krate, arg_tt, attr_arg.as_ref());
+
+ // Set a hard limit for the expanded tt
+ if let Err(value) = check_tt_count(&tt) {
+ return value;
+ }
+
+ fixup::reverse_fixups(&mut tt, arg_tm, undo_info);
+
+ ExpandResult { value: Arc::new(tt), err }
}
fn hygiene_frame(db: &dyn ExpandDatabase, file_id: HirFileId) -> Arc<HygieneFrame> {
@@ -563,3 +686,22 @@ fn token_tree_to_syntax_node(
};
mbe::token_tree_to_syntax_node(tt, entry_point)
}
+
+fn check_tt_count(tt: &tt::Subtree) -> Result<(), ExpandResult<Arc<tt::Subtree>>> {
+ let count = tt.count();
+ if TOKEN_LIMIT.check(count).is_err() {
+ Err(ExpandResult {
+ value: Arc::new(tt::Subtree {
+ delimiter: tt::Delimiter::UNSPECIFIED,
+ token_trees: vec![],
+ }),
+ err: Some(ExpandError::other(format!(
+ "macro invocation exceeds token limit: produced {} tokens, limit is {}",
+ count,
+ TOKEN_LIMIT.inner(),
+ ))),
+ })
+ } else {
+ Ok(())
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/eager.rs b/src/tools/rust-analyzer/crates/hir-expand/src/eager.rs
index 7ee3fd375..4110f2847 100644
--- a/src/tools/rust-analyzer/crates/hir-expand/src/eager.rs
+++ b/src/tools/rust-analyzer/crates/hir-expand/src/eager.rs
@@ -19,7 +19,8 @@
//!
//! See the full discussion : <https://rust-lang.zulipchat.com/#narrow/stream/131828-t-compiler/topic/Eager.20expansion.20of.20built-in.20macros>
use base_db::CrateId;
-use syntax::{ted, Parse, SyntaxNode};
+use rustc_hash::{FxHashMap, FxHashSet};
+use syntax::{ted, Parse, SyntaxNode, TextRange, TextSize, WalkEvent};
use triomphe::Arc;
use crate::{
@@ -28,7 +29,7 @@ use crate::{
hygiene::Hygiene,
mod_path::ModPath,
EagerCallInfo, ExpandError, ExpandResult, ExpandTo, InFile, MacroCallId, MacroCallKind,
- MacroCallLoc, MacroDefId, MacroDefKind, UnresolvedMacro,
+ MacroCallLoc, MacroDefId, MacroDefKind,
};
pub fn expand_eager_macro_input(
@@ -37,20 +38,9 @@ pub fn expand_eager_macro_input(
macro_call: InFile<ast::MacroCall>,
def: MacroDefId,
resolver: &dyn Fn(ModPath) -> Option<MacroDefId>,
-) -> Result<ExpandResult<Option<MacroCallId>>, UnresolvedMacro> {
- assert!(matches!(def.kind, MacroDefKind::BuiltInEager(..)));
- let token_tree = macro_call.value.token_tree();
-
- let Some(token_tree) = token_tree else {
- return Ok(ExpandResult { value: None, err:
- Some(ExpandError::other(
- "invalid token tree"
- )),
- });
- };
- let (parsed_args, arg_token_map) = mbe::syntax_node_to_token_tree(token_tree.syntax());
-
+) -> ExpandResult<Option<MacroCallId>> {
let ast_map = db.ast_id_map(macro_call.file_id);
+ // the expansion which the ast id map is built upon has no whitespace, so the offsets are wrong as macro_call is from the token tree that has whitespace!
let call_id = InFile::new(macro_call.file_id, ast_map.ast_id(&macro_call.value));
let expand_to = ExpandTo::from_call_site(&macro_call.value);
@@ -61,47 +51,80 @@ pub fn expand_eager_macro_input(
let arg_id = db.intern_macro_call(MacroCallLoc {
def,
krate,
- eager: Some(Box::new(EagerCallInfo {
- arg: Arc::new((parsed_args, arg_token_map)),
- arg_id: None,
- error: None,
- })),
+ eager: None,
kind: MacroCallKind::FnLike { ast_id: call_id, expand_to: ExpandTo::Expr },
});
- let arg_as_expr = match db.macro_arg_text(arg_id) {
- Some(it) => it,
- None => {
- return Ok(ExpandResult {
- value: None,
- err: Some(ExpandError::other("invalid token tree")),
- })
- }
+ let ExpandResult { value: (arg_exp, arg_exp_map), err: parse_err } =
+ db.parse_macro_expansion(arg_id.as_macro_file());
+ // we need this map here as the expansion of the eager input fake file loses whitespace ...
+ let mut ws_mapping = FxHashMap::default();
+ if let Some((_, tm, _)) = db.macro_arg(arg_id).value.as_deref() {
+ ws_mapping.extend(tm.entries().filter_map(|(id, range)| {
+ Some((arg_exp_map.first_range_by_token(id, syntax::SyntaxKind::TOMBSTONE)?, range))
+ }));
+ }
+
+ let ExpandResult { value: expanded_eager_input, err } = {
+ eager_macro_recur(
+ db,
+ &Hygiene::new(db, macro_call.file_id),
+ InFile::new(arg_id.as_file(), arg_exp.syntax_node()),
+ krate,
+ resolver,
+ )
};
- let ExpandResult { value: expanded_eager_input, err } = eager_macro_recur(
- db,
- &Hygiene::new(db, macro_call.file_id),
- InFile::new(arg_id.as_file(), SyntaxNode::new_root(arg_as_expr)),
- krate,
- resolver,
- )?;
- let Some(expanded_eager_input) = expanded_eager_input else {
- return Ok(ExpandResult { value: None, err })
+ let err = parse_err.or(err);
+
+ let Some((expanded_eager_input, mapping)) = expanded_eager_input else {
+ return ExpandResult { value: None, err };
+ };
+
+ let (mut subtree, expanded_eager_input_token_map) =
+ mbe::syntax_node_to_token_tree(&expanded_eager_input);
+
+ let og_tmap = if let Some(tt) = macro_call.value.token_tree() {
+ let mut ids_used = FxHashSet::default();
+ let mut og_tmap = mbe::syntax_node_to_token_map(tt.syntax());
+ // The tokenmap and ids of subtree point into the expanded syntax node, but that is inaccessible from the outside
+ // so we need to remap them to the original input of the eager macro.
+ subtree.visit_ids(&mut |id| {
+ // Note: we discard all token ids of braces and the like here, but that's not too bad and only a temporary fix
+
+ if let Some(range) = expanded_eager_input_token_map
+ .first_range_by_token(id, syntax::SyntaxKind::TOMBSTONE)
+ {
+ // remap from expanded eager input to eager input expansion
+ if let Some(og_range) = mapping.get(&range) {
+ // remap from eager input expansion to original eager input
+ if let Some(&og_range) = ws_mapping.get(og_range) {
+ if let Some(og_token) = og_tmap.token_by_range(og_range) {
+ ids_used.insert(og_token);
+ return og_token;
+ }
+ }
+ }
+ }
+ tt::TokenId::UNSPECIFIED
+ });
+ og_tmap.filter(|id| ids_used.contains(&id));
+ og_tmap
+ } else {
+ Default::default()
};
- let (mut subtree, token_map) = mbe::syntax_node_to_token_tree(&expanded_eager_input);
subtree.delimiter = crate::tt::Delimiter::unspecified();
let loc = MacroCallLoc {
def,
krate,
eager: Some(Box::new(EagerCallInfo {
- arg: Arc::new((subtree, token_map)),
- arg_id: Some(arg_id),
+ arg: Arc::new((subtree, og_tmap)),
+ arg_id,
error: err.clone(),
})),
kind: MacroCallKind::FnLike { ast_id: call_id, expand_to },
};
- Ok(ExpandResult { value: Some(db.intern_macro_call(loc)), err })
+ ExpandResult { value: Some(db.intern_macro_call(loc)), err }
}
fn lazy_expand(
@@ -109,19 +132,16 @@ fn lazy_expand(
def: &MacroDefId,
macro_call: InFile<ast::MacroCall>,
krate: CrateId,
-) -> ExpandResult<InFile<Parse<SyntaxNode>>> {
+) -> ExpandResult<(InFile<Parse<SyntaxNode>>, Arc<mbe::TokenMap>)> {
let ast_id = db.ast_id_map(macro_call.file_id).ast_id(&macro_call.value);
let expand_to = ExpandTo::from_call_site(&macro_call.value);
- let id = def.as_lazy_macro(
- db,
- krate,
- MacroCallKind::FnLike { ast_id: macro_call.with_value(ast_id), expand_to },
- );
-
+ let ast_id = macro_call.with_value(ast_id);
+ let id = def.as_lazy_macro(db, krate, MacroCallKind::FnLike { ast_id, expand_to });
let macro_file = id.as_macro_file();
- db.parse_macro_expansion(macro_file).map(|parse| InFile::new(macro_file.into(), parse.0))
+ db.parse_macro_expansion(macro_file)
+ .map(|parse| (InFile::new(macro_file.into(), parse.0), parse.1))
}
fn eager_macro_recur(
@@ -130,19 +150,51 @@ fn eager_macro_recur(
curr: InFile<SyntaxNode>,
krate: CrateId,
macro_resolver: &dyn Fn(ModPath) -> Option<MacroDefId>,
-) -> Result<ExpandResult<Option<SyntaxNode>>, UnresolvedMacro> {
+) -> ExpandResult<Option<(SyntaxNode, FxHashMap<TextRange, TextRange>)>> {
let original = curr.value.clone_for_update();
+ let mut mapping = FxHashMap::default();
- let children = original.descendants().filter_map(ast::MacroCall::cast);
let mut replacements = Vec::new();
- // Note: We only report a single error inside of eager expansions
+ // FIXME: We only report a single error inside of eager expansions
let mut error = None;
+ let mut offset = 0i32;
+ let apply_offset = |it: TextSize, offset: i32| {
+ TextSize::from(u32::try_from(offset + u32::from(it) as i32).unwrap_or_default())
+ };
+ let mut children = original.preorder_with_tokens();
// Collect replacement
- for child in children {
- let def = match child.path().and_then(|path| ModPath::from_src(db, path, hygiene)) {
- Some(path) => macro_resolver(path.clone()).ok_or(UnresolvedMacro { path })?,
+ while let Some(child) = children.next() {
+ let WalkEvent::Enter(child) = child else { continue };
+ let call = match child {
+ syntax::NodeOrToken::Node(node) => match ast::MacroCall::cast(node) {
+ Some(it) => {
+ children.skip_subtree();
+ it
+ }
+ None => continue,
+ },
+ syntax::NodeOrToken::Token(t) => {
+ mapping.insert(
+ TextRange::new(
+ apply_offset(t.text_range().start(), offset),
+ apply_offset(t.text_range().end(), offset),
+ ),
+ t.text_range(),
+ );
+ continue;
+ }
+ };
+ let def = match call.path().and_then(|path| ModPath::from_src(db, path, hygiene)) {
+ Some(path) => match macro_resolver(path.clone()) {
+ Some(def) => def,
+ None => {
+ error =
+ Some(ExpandError::other(format!("unresolved macro {}", path.display(db))));
+ continue;
+ }
+ },
None => {
error = Some(ExpandError::other("malformed macro invocation"));
continue;
@@ -150,20 +202,32 @@ fn eager_macro_recur(
};
let ExpandResult { value, err } = match def.kind {
MacroDefKind::BuiltInEager(..) => {
- let ExpandResult { value, err } = match expand_eager_macro_input(
+ let ExpandResult { value, err } = expand_eager_macro_input(
db,
krate,
- curr.with_value(child.clone()),
+ curr.with_value(call.clone()),
def,
macro_resolver,
- ) {
- Ok(it) => it,
- Err(err) => return Err(err),
- };
+ );
match value {
- Some(call) => {
+ Some(call_id) => {
let ExpandResult { value, err: err2 } =
- db.parse_macro_expansion(call.as_macro_file());
+ db.parse_macro_expansion(call_id.as_macro_file());
+
+ if let Some(tt) = call.token_tree() {
+ let call_tt_start = tt.syntax().text_range().start();
+ let call_start =
+ apply_offset(call.syntax().text_range().start(), offset);
+ if let Some((_, arg_map, _)) = db.macro_arg(call_id).value.as_deref() {
+ mapping.extend(arg_map.entries().filter_map(|(tid, range)| {
+ value
+ .1
+ .first_range_by_token(tid, syntax::SyntaxKind::TOMBSTONE)
+ .map(|r| (r + call_start, range + call_tt_start))
+ }));
+ }
+ }
+
ExpandResult {
value: Some(value.0.syntax_node().clone_for_update()),
err: err.or(err2),
@@ -177,36 +241,63 @@ fn eager_macro_recur(
| MacroDefKind::BuiltInAttr(..)
| MacroDefKind::BuiltInDerive(..)
| MacroDefKind::ProcMacro(..) => {
- let ExpandResult { value, err } =
- lazy_expand(db, &def, curr.with_value(child.clone()), krate);
+ let ExpandResult { value: (parse, tm), err } =
+ lazy_expand(db, &def, curr.with_value(call.clone()), krate);
+ let decl_mac = if let MacroDefKind::Declarative(ast_id) = def.kind {
+ Some(db.decl_macro_expander(def.krate, ast_id))
+ } else {
+ None
+ };
// replace macro inside
- let hygiene = Hygiene::new(db, value.file_id);
+ let hygiene = Hygiene::new(db, parse.file_id);
let ExpandResult { value, err: error } = eager_macro_recur(
db,
&hygiene,
// FIXME: We discard parse errors here
- value.map(|it| it.syntax_node()),
+ parse.as_ref().map(|it| it.syntax_node()),
krate,
macro_resolver,
- )?;
+ );
let err = err.or(error);
- ExpandResult { value, err }
+
+ if let Some(tt) = call.token_tree() {
+ let call_tt_start = tt.syntax().text_range().start();
+ let call_start = apply_offset(call.syntax().text_range().start(), offset);
+ if let Some((_tt, arg_map, _)) = parse
+ .file_id
+ .macro_file()
+ .and_then(|id| db.macro_arg(id.macro_call_id).value)
+ .as_deref()
+ {
+ mapping.extend(arg_map.entries().filter_map(|(tid, range)| {
+ tm.first_range_by_token(
+ decl_mac.as_ref().map(|it| it.map_id_down(tid)).unwrap_or(tid),
+ syntax::SyntaxKind::TOMBSTONE,
+ )
+ .map(|r| (r + call_start, range + call_tt_start))
+ }));
+ }
+ }
+ // FIXME: Do we need to re-use _m here?
+ ExpandResult { value: value.map(|(n, _m)| n), err }
}
};
if err.is_some() {
error = err;
}
// check if the whole original syntax is replaced
- if child.syntax() == &original {
- return Ok(ExpandResult { value, err: error });
+ if call.syntax() == &original {
+ return ExpandResult { value: value.zip(Some(mapping)), err: error };
}
if let Some(insert) = value {
- replacements.push((child, insert));
+ offset += u32::from(insert.text_range().len()) as i32
+ - u32::from(call.syntax().text_range().len()) as i32;
+ replacements.push((call, insert));
}
}
replacements.into_iter().rev().for_each(|(old, new)| ted::replace(old.syntax(), new));
- Ok(ExpandResult { value: Some(original), err: error })
+ ExpandResult { value: Some((original, mapping)), err: error }
}
diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs b/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs
index 00796e7c0..e6e8d8c02 100644
--- a/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs
+++ b/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs
@@ -26,7 +26,7 @@ pub(crate) struct SyntaxFixups {
/// This is the information needed to reverse the fixups.
#[derive(Debug, Default, PartialEq, Eq)]
pub struct SyntaxFixupUndoInfo {
- original: Vec<Subtree>,
+ original: Box<[Subtree]>,
}
const EMPTY_ID: SyntheticTokenId = SyntheticTokenId(!0);
@@ -272,7 +272,7 @@ pub(crate) fn fixup_syntax(node: &SyntaxNode) -> SyntaxFixups {
replace,
token_map,
next_id,
- undo_info: SyntaxFixupUndoInfo { original },
+ undo_info: SyntaxFixupUndoInfo { original: original.into_boxed_slice() },
}
}
@@ -472,13 +472,13 @@ fn foo () {match __ra_fixup {}}
check(
r#"
fn foo() {
- match x {
+ match it {
}
}
"#,
expect![[r#"
-fn foo () {match x {}}
+fn foo () {match it {}}
"#]],
)
}
@@ -547,11 +547,11 @@ fn foo () {a . __ra_fixup ; bar () ;}
check(
r#"
fn foo() {
- let x = a
+ let it = a
}
"#,
expect![[r#"
-fn foo () {let x = a ;}
+fn foo () {let it = a ;}
"#]],
)
}
@@ -561,11 +561,11 @@ fn foo () {let x = a ;}
check(
r#"
fn foo() {
- let x = a.
+ let it = a.
}
"#,
expect![[r#"
-fn foo () {let x = a . __ra_fixup ;}
+fn foo () {let it = a . __ra_fixup ;}
"#]],
)
}
diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs b/src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs
index 10f8fe9ce..ade4a5928 100644
--- a/src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs
+++ b/src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs
@@ -126,7 +126,7 @@ struct HygieneInfo {
/// The start offset of the `macro_rules!` arguments or attribute input.
attr_input_or_mac_def_start: Option<InFile<TextSize>>,
- macro_def: Arc<TokenExpander>,
+ macro_def: TokenExpander,
macro_arg: Arc<(crate::tt::Subtree, mbe::TokenMap, fixup::SyntaxFixupUndoInfo)>,
macro_arg_shift: mbe::Shift,
exp_map: Arc<mbe::TokenMap>,
@@ -149,19 +149,15 @@ impl HygieneInfo {
token_id = unshifted;
(&attr_args.1, self.attr_input_or_mac_def_start?)
}
- None => (
- &self.macro_arg.1,
- InFile::new(loc.kind.file_id(), loc.kind.arg(db)?.text_range().start()),
- ),
+ None => (&self.macro_arg.1, loc.kind.arg(db)?.map(|it| it.text_range().start())),
},
_ => match origin {
- mbe::Origin::Call => (
- &self.macro_arg.1,
- InFile::new(loc.kind.file_id(), loc.kind.arg(db)?.text_range().start()),
- ),
- mbe::Origin::Def => match (&*self.macro_def, &self.attr_input_or_mac_def_start) {
- (TokenExpander::DeclarativeMacro { def_site_token_map, .. }, Some(tt)) => {
- (def_site_token_map, *tt)
+ mbe::Origin::Call => {
+ (&self.macro_arg.1, loc.kind.arg(db)?.map(|it| it.text_range().start()))
+ }
+ mbe::Origin::Def => match (&self.macro_def, &self.attr_input_or_mac_def_start) {
+ (TokenExpander::DeclarativeMacro(expander), Some(tt)) => {
+ (&expander.def_site_token_map, *tt)
}
_ => panic!("`Origin::Def` used with non-`macro_rules!` macro"),
},
@@ -177,7 +173,7 @@ fn make_hygiene_info(
db: &dyn ExpandDatabase,
macro_file: MacroFile,
loc: &MacroCallLoc,
-) -> Option<HygieneInfo> {
+) -> HygieneInfo {
let def = loc.def.ast_id().left().and_then(|id| {
let def_tt = match id.to_node(db) {
ast::Macro::MacroRules(mac) => mac.token_tree()?,
@@ -198,9 +194,9 @@ fn make_hygiene_info(
_ => None,
});
- let macro_def = db.macro_def(loc.def).ok()?;
+ let macro_def = db.macro_expander(loc.def);
let (_, exp_map) = db.parse_macro_expansion(macro_file).value;
- let macro_arg = db.macro_arg(macro_file.macro_call_id).unwrap_or_else(|| {
+ let macro_arg = db.macro_arg(macro_file.macro_call_id).value.unwrap_or_else(|| {
Arc::new((
tt::Subtree { delimiter: tt::Delimiter::UNSPECIFIED, token_trees: Vec::new() },
Default::default(),
@@ -208,7 +204,7 @@ fn make_hygiene_info(
))
});
- Some(HygieneInfo {
+ HygieneInfo {
file: macro_file,
attr_input_or_mac_def_start: attr_input_or_mac_def
.map(|it| it.map(|tt| tt.syntax().text_range().start())),
@@ -216,7 +212,7 @@ fn make_hygiene_info(
macro_arg,
macro_def,
exp_map,
- })
+ }
}
impl HygieneFrame {
@@ -225,8 +221,7 @@ impl HygieneFrame {
None => (None, None, false),
Some(macro_file) => {
let loc = db.lookup_intern_macro_call(macro_file.macro_call_id);
- let info =
- make_hygiene_info(db, macro_file, &loc).map(|info| (loc.kind.file_id(), info));
+ let info = Some((make_hygiene_info(db, macro_file, &loc), loc.kind.file_id()));
match loc.def.kind {
MacroDefKind::Declarative(_) => {
(info, Some(loc.def.krate), loc.def.local_inner)
@@ -240,17 +235,14 @@ impl HygieneFrame {
}
};
- let (calling_file, info) = match info {
- None => {
- return HygieneFrame {
- expansion: None,
- local_inner,
- krate,
- call_site: None,
- def_site: None,
- };
+ let Some((info, calling_file)) = info else {
+ return HygieneFrame {
+ expansion: None,
+ local_inner,
+ krate,
+ call_site: None,
+ def_site: None,
}
- Some(it) => it,
};
let def_site = info.attr_input_or_mac_def_start.map(|it| db.hygiene_frame(it.file_id));
diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs
index e0c199328..1f1e20f49 100644
--- a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs
@@ -37,11 +37,11 @@ use either::Either;
use syntax::{
algo::{self, skip_trivia_token},
ast::{self, AstNode, HasDocComments},
- Direction, SyntaxNode, SyntaxToken,
+ AstPtr, Direction, SyntaxNode, SyntaxNodePtr, SyntaxToken,
};
use crate::{
- ast_id_map::FileAstId,
+ ast_id_map::{AstIdNode, ErasedFileAstId, FileAstId},
attrs::AttrId,
builtin_attr_macro::BuiltinAttrExpander,
builtin_derive_macro::BuiltinDeriveExpander,
@@ -127,7 +127,8 @@ impl_intern_key!(MacroCallId);
pub struct MacroCallLoc {
pub def: MacroDefId,
pub(crate) krate: CrateId,
- /// Some if `def` is a builtin eager macro.
+ /// Some if this is a macro call for an eager macro. Note that this is `None`
+ /// for the eager input macro file.
eager: Option<Box<EagerCallInfo>>,
pub kind: MacroCallKind,
}
@@ -152,11 +153,10 @@ pub enum MacroDefKind {
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct EagerCallInfo {
- /// NOTE: This can be *either* the expansion result, *or* the argument to the eager macro!
+ /// The expanded argument of the eager macro.
arg: Arc<(tt::Subtree, TokenMap)>,
- /// call id of the eager macro's input file. If this is none, macro call containing this call info
- /// is an eager macro's input, otherwise it is its output.
- arg_id: Option<MacroCallId>,
+ /// Call id of the eager macro's input file (this is the macro file for its fully expanded input).
+ arg_id: MacroCallId,
error: Option<ExpandError>,
}
@@ -221,11 +221,7 @@ impl HirFileId {
HirFileIdRepr::FileId(id) => break id,
HirFileIdRepr::MacroFile(MacroFile { macro_call_id }) => {
let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_call_id);
- let is_include_expansion = loc.def.is_include()
- && matches!(
- loc.eager.as_deref(),
- Some(EagerCallInfo { arg_id: Some(_), .. })
- );
+ let is_include_expansion = loc.def.is_include() && loc.eager.is_some();
file_id = match is_include_expansion.then(|| db.include_expand(macro_call_id)) {
Some(Ok((_, file))) => file.into(),
_ => loc.kind.file_id(),
@@ -270,57 +266,13 @@ impl HirFileId {
/// Return expansion information if it is a macro-expansion file
pub fn expansion_info(self, db: &dyn db::ExpandDatabase) -> Option<ExpansionInfo> {
let macro_file = self.macro_file()?;
- let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id);
-
- let arg_tt = loc.kind.arg(db)?;
-
- let macro_def = db.macro_def(loc.def).ok()?;
- let (parse, exp_map) = db.parse_macro_expansion(macro_file).value;
- let macro_arg = db.macro_arg(macro_file.macro_call_id).unwrap_or_else(|| {
- Arc::new((
- tt::Subtree { delimiter: tt::Delimiter::UNSPECIFIED, token_trees: Vec::new() },
- Default::default(),
- Default::default(),
- ))
- });
-
- let def = loc.def.ast_id().left().and_then(|id| {
- let def_tt = match id.to_node(db) {
- ast::Macro::MacroRules(mac) => mac.token_tree()?,
- ast::Macro::MacroDef(_) if matches!(*macro_def, TokenExpander::BuiltinAttr(_)) => {
- return None
- }
- ast::Macro::MacroDef(mac) => mac.body()?,
- };
- Some(InFile::new(id.file_id, def_tt))
- });
- let attr_input_or_mac_def = def.or_else(|| match loc.kind {
- MacroCallKind::Attr { ast_id, invoc_attr_index, .. } => {
- // FIXME: handle `cfg_attr`
- let tt = ast_id
- .to_node(db)
- .doc_comments_and_attrs()
- .nth(invoc_attr_index.ast_index())
- .and_then(Either::left)?
- .token_tree()?;
- Some(InFile::new(ast_id.file_id, tt))
- }
- _ => None,
- });
-
- Some(ExpansionInfo {
- expanded: InFile::new(self, parse.syntax_node()),
- arg: InFile::new(loc.kind.file_id(), arg_tt),
- attr_input_or_mac_def,
- macro_arg_shift: mbe::Shift::new(&macro_arg.0),
- macro_arg,
- macro_def,
- exp_map,
- })
+ ExpansionInfo::new(db, macro_file)
}
- /// Indicate it is macro file generated for builtin derive
- pub fn is_builtin_derive(&self, db: &dyn db::ExpandDatabase) -> Option<InFile<ast::Attr>> {
+ pub fn as_builtin_derive_attr_node(
+ &self,
+ db: &dyn db::ExpandDatabase,
+ ) -> Option<InFile<ast::Attr>> {
let macro_file = self.macro_file()?;
let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id);
let attr = match loc.def.kind {
@@ -333,8 +285,22 @@ impl HirFileId {
pub fn is_custom_derive(&self, db: &dyn db::ExpandDatabase) -> bool {
match self.macro_file() {
Some(macro_file) => {
- let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id);
- matches!(loc.def.kind, MacroDefKind::ProcMacro(_, ProcMacroKind::CustomDerive, _))
+ matches!(
+ db.lookup_intern_macro_call(macro_file.macro_call_id).def.kind,
+ MacroDefKind::ProcMacro(_, ProcMacroKind::CustomDerive, _)
+ )
+ }
+ None => false,
+ }
+ }
+
+ pub fn is_builtin_derive(&self, db: &dyn db::ExpandDatabase) -> bool {
+ match self.macro_file() {
+ Some(macro_file) => {
+ matches!(
+ db.lookup_intern_macro_call(macro_file.macro_call_id).def.kind,
+ MacroDefKind::BuiltInDerive(..)
+ )
}
None => false,
}
@@ -344,8 +310,7 @@ impl HirFileId {
pub fn is_include_macro(&self, db: &dyn db::ExpandDatabase) -> bool {
match self.macro_file() {
Some(macro_file) => {
- let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id);
- loc.def.is_include()
+ db.lookup_intern_macro_call(macro_file.macro_call_id).def.is_include()
}
_ => false,
}
@@ -355,7 +320,7 @@ impl HirFileId {
match self.macro_file() {
Some(macro_file) => {
let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id);
- matches!(loc.eager.as_deref(), Some(EagerCallInfo { .. }))
+ matches!(loc.def.kind, MacroDefKind::BuiltInEager(..))
}
_ => false,
}
@@ -450,6 +415,24 @@ impl MacroDefId {
)
}
+ pub fn is_derive(&self) -> bool {
+ matches!(
+ self.kind,
+ MacroDefKind::BuiltInDerive(..)
+ | MacroDefKind::ProcMacro(_, ProcMacroKind::CustomDerive, _)
+ )
+ }
+
+ pub fn is_fn_like(&self) -> bool {
+ matches!(
+ self.kind,
+ MacroDefKind::BuiltIn(..)
+ | MacroDefKind::ProcMacro(_, ProcMacroKind::FuncLike, _)
+ | MacroDefKind::BuiltInEager(..)
+ | MacroDefKind::Declarative(..)
+ )
+ }
+
pub fn is_attribute_derive(&self) -> bool {
matches!(self.kind, MacroDefKind::BuiltInAttr(expander, ..) if expander.is_derive())
}
@@ -536,9 +519,9 @@ impl MacroCallKind {
};
let range = match kind {
- MacroCallKind::FnLike { ast_id, .. } => ast_id.to_node(db).syntax().text_range(),
- MacroCallKind::Derive { ast_id, .. } => ast_id.to_node(db).syntax().text_range(),
- MacroCallKind::Attr { ast_id, .. } => ast_id.to_node(db).syntax().text_range(),
+ MacroCallKind::FnLike { ast_id, .. } => ast_id.to_ptr(db).text_range(),
+ MacroCallKind::Derive { ast_id, .. } => ast_id.to_ptr(db).text_range(),
+ MacroCallKind::Attr { ast_id, .. } => ast_id.to_ptr(db).text_range(),
};
FileRange { range, file_id }
@@ -588,13 +571,18 @@ impl MacroCallKind {
FileRange { range, file_id }
}
- fn arg(&self, db: &dyn db::ExpandDatabase) -> Option<SyntaxNode> {
+ fn arg(&self, db: &dyn db::ExpandDatabase) -> Option<InFile<SyntaxNode>> {
match self {
- MacroCallKind::FnLike { ast_id, .. } => {
- Some(ast_id.to_node(db).token_tree()?.syntax().clone())
+ MacroCallKind::FnLike { ast_id, .. } => ast_id
+ .to_in_file_node(db)
+ .map(|it| Some(it.token_tree()?.syntax().clone()))
+ .transpose(),
+ MacroCallKind::Derive { ast_id, .. } => {
+ Some(ast_id.to_in_file_node(db).syntax().cloned())
+ }
+ MacroCallKind::Attr { ast_id, .. } => {
+ Some(ast_id.to_in_file_node(db).syntax().cloned())
}
- MacroCallKind::Derive { ast_id, .. } => Some(ast_id.to_node(db).syntax().clone()),
- MacroCallKind::Attr { ast_id, .. } => Some(ast_id.to_node(db).syntax().clone()),
}
}
}
@@ -612,13 +600,13 @@ impl MacroCallId {
/// ExpansionInfo mainly describes how to map text range between src and expanded macro
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExpansionInfo {
- expanded: InFile<SyntaxNode>,
+ expanded: InMacroFile<SyntaxNode>,
/// The argument TokenTree or item for attributes
arg: InFile<SyntaxNode>,
/// The `macro_rules!` or attribute input.
attr_input_or_mac_def: Option<InFile<ast::TokenTree>>,
- macro_def: Arc<TokenExpander>,
+ macro_def: TokenExpander,
macro_arg: Arc<(tt::Subtree, mbe::TokenMap, fixup::SyntaxFixupUndoInfo)>,
/// A shift built from `macro_arg`'s subtree, relevant for attributes as the item is the macro arg
/// and as such we need to shift tokens if they are part of an attributes input instead of their item.
@@ -628,7 +616,7 @@ pub struct ExpansionInfo {
impl ExpansionInfo {
pub fn expanded(&self) -> InFile<SyntaxNode> {
- self.expanded.clone()
+ self.expanded.clone().into()
}
pub fn call_node(&self) -> Option<InFile<SyntaxNode>> {
@@ -659,7 +647,7 @@ impl ExpansionInfo {
let token_id_in_attr_input = if let Some(item) = item {
// check if we are mapping down in an attribute input
// this is a special case as attributes can have two inputs
- let call_id = self.expanded.file_id.macro_file()?.macro_call_id;
+ let call_id = self.expanded.file_id.macro_call_id;
let loc = db.lookup_intern_macro_call(call_id);
let token_range = token.value.text_range();
@@ -705,7 +693,7 @@ impl ExpansionInfo {
let relative_range =
token.value.text_range().checked_sub(self.arg.value.text_range().start())?;
let token_id = self.macro_arg.1.token_by_range(relative_range)?;
- // conditionally shift the id by a declaratives macro definition
+ // conditionally shift the id by a declarative macro definition
self.macro_def.map_id_down(token_id)
}
};
@@ -715,7 +703,7 @@ impl ExpansionInfo {
.ranges_by_token(token_id, token.value.kind())
.flat_map(move |range| self.expanded.value.covering_element(range).into_token());
- Some(tokens.map(move |token| self.expanded.with_value(token)))
+ Some(tokens.map(move |token| InFile::new(self.expanded.file_id.into(), token)))
}
/// Map a token up out of the expansion it resides in into the arguments of the macro call of the expansion.
@@ -724,18 +712,17 @@ impl ExpansionInfo {
db: &dyn db::ExpandDatabase,
token: InFile<&SyntaxToken>,
) -> Option<(InFile<SyntaxToken>, Origin)> {
+ assert_eq!(token.file_id, self.expanded.file_id.into());
// Fetch the id through its text range,
let token_id = self.exp_map.token_by_range(token.value.text_range())?;
// conditionally unshifting the id to accommodate for macro-rules def site
let (mut token_id, origin) = self.macro_def.map_id_up(token_id);
- let call_id = self.expanded.file_id.macro_file()?.macro_call_id;
+ let call_id = self.expanded.file_id.macro_call_id;
let loc = db.lookup_intern_macro_call(call_id);
// Special case: map tokens from `include!` expansions to the included file
- if loc.def.is_include()
- && matches!(loc.eager.as_deref(), Some(EagerCallInfo { arg_id: Some(_), .. }))
- {
+ if loc.def.is_include() {
if let Ok((tt_and_map, file_id)) = db.include_expand(call_id) {
let range = tt_and_map.1.first_range_by_token(token_id, token.value.kind())?;
let source = db.parse(file_id);
@@ -765,9 +752,9 @@ impl ExpansionInfo {
}
_ => match origin {
mbe::Origin::Call => (&self.macro_arg.1, self.arg.clone()),
- mbe::Origin::Def => match (&*self.macro_def, &self.attr_input_or_mac_def) {
- (TokenExpander::DeclarativeMacro { def_site_token_map, .. }, Some(tt)) => {
- (def_site_token_map, tt.syntax().cloned())
+ mbe::Origin::Def => match (&self.macro_def, &self.attr_input_or_mac_def) {
+ (TokenExpander::DeclarativeMacro(expander), Some(tt)) => {
+ (&expander.def_site_token_map, tt.syntax().cloned())
}
_ => panic!("`Origin::Def` used with non-`macro_rules!` macro"),
},
@@ -779,6 +766,58 @@ impl ExpansionInfo {
tt.value.covering_element(range + tt.value.text_range().start()).into_token()?;
Some((tt.with_value(token), origin))
}
+
+ fn new(db: &dyn db::ExpandDatabase, macro_file: MacroFile) -> Option<ExpansionInfo> {
+ let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id);
+
+ let arg_tt = loc.kind.arg(db)?;
+
+ let macro_def = db.macro_expander(loc.def);
+ let (parse, exp_map) = db.parse_macro_expansion(macro_file).value;
+ let expanded = InMacroFile { file_id: macro_file, value: parse.syntax_node() };
+
+ let macro_arg = db.macro_arg(macro_file.macro_call_id).value.unwrap_or_else(|| {
+ Arc::new((
+ tt::Subtree { delimiter: tt::Delimiter::UNSPECIFIED, token_trees: Vec::new() },
+ Default::default(),
+ Default::default(),
+ ))
+ });
+
+ let def = loc.def.ast_id().left().and_then(|id| {
+ let def_tt = match id.to_node(db) {
+ ast::Macro::MacroRules(mac) => mac.token_tree()?,
+ ast::Macro::MacroDef(_) if matches!(macro_def, TokenExpander::BuiltInAttr(_)) => {
+ return None
+ }
+ ast::Macro::MacroDef(mac) => mac.body()?,
+ };
+ Some(InFile::new(id.file_id, def_tt))
+ });
+ let attr_input_or_mac_def = def.or_else(|| match loc.kind {
+ MacroCallKind::Attr { ast_id, invoc_attr_index, .. } => {
+ // FIXME: handle `cfg_attr`
+ let tt = ast_id
+ .to_node(db)
+ .doc_comments_and_attrs()
+ .nth(invoc_attr_index.ast_index())
+ .and_then(Either::left)?
+ .token_tree()?;
+ Some(InFile::new(ast_id.file_id, tt))
+ }
+ _ => None,
+ });
+
+ Some(ExpansionInfo {
+ expanded,
+ arg: arg_tt,
+ attr_input_or_mac_def,
+ macro_arg_shift: mbe::Shift::new(&macro_arg.0),
+ macro_arg,
+ macro_def,
+ exp_map,
+ })
+ }
}
/// `AstId` points to an AST node in any file.
@@ -786,10 +825,26 @@ impl ExpansionInfo {
/// It is stable across reparses, and can be used as salsa key/value.
pub type AstId<N> = InFile<FileAstId<N>>;
-impl<N: AstNode> AstId<N> {
+impl<N: AstIdNode> AstId<N> {
pub fn to_node(&self, db: &dyn db::ExpandDatabase) -> N {
- let root = db.parse_or_expand(self.file_id);
- db.ast_id_map(self.file_id).get(self.value).to_node(&root)
+ self.to_ptr(db).to_node(&db.parse_or_expand(self.file_id))
+ }
+ pub fn to_in_file_node(&self, db: &dyn db::ExpandDatabase) -> InFile<N> {
+ InFile::new(self.file_id, self.to_ptr(db).to_node(&db.parse_or_expand(self.file_id)))
+ }
+ pub fn to_ptr(&self, db: &dyn db::ExpandDatabase) -> AstPtr<N> {
+ db.ast_id_map(self.file_id).get(self.value)
+ }
+}
+
+pub type ErasedAstId = InFile<ErasedFileAstId>;
+
+impl ErasedAstId {
+ pub fn to_node(&self, db: &dyn db::ExpandDatabase) -> SyntaxNode {
+ self.to_ptr(db).to_node(&db.parse_or_expand(self.file_id))
+ }
+ pub fn to_ptr(&self, db: &dyn db::ExpandDatabase) -> SyntaxNodePtr {
+ db.ast_id_map(self.file_id).get_raw(self.value)
}
}
@@ -850,7 +905,7 @@ impl<L, R> InFile<Either<L, R>> {
}
}
-impl<'a> InFile<&'a SyntaxNode> {
+impl InFile<&SyntaxNode> {
pub fn ancestors_with_macros(
self,
db: &dyn db::ExpandDatabase,
@@ -1011,6 +1066,18 @@ impl InFile<SyntaxToken> {
}
}
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
+pub struct InMacroFile<T> {
+ pub file_id: MacroFile,
+ pub value: T,
+}
+
+impl<T> From<InMacroFile<T>> for InFile<T> {
+ fn from(macro_file: InMacroFile<T>) -> Self {
+ InFile { file_id: macro_file.file_id.into(), value: macro_file.value }
+ }
+}
+
fn ascend_node_border_tokens(
db: &dyn db::ExpandDatabase,
InFile { file_id, value: node }: InFile<&SyntaxNode>,
diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/mod_path.rs b/src/tools/rust-analyzer/crates/hir-expand/src/mod_path.rs
index 47a8ab7de..69aa09c4a 100644
--- a/src/tools/rust-analyzer/crates/hir-expand/src/mod_path.rs
+++ b/src/tools/rust-analyzer/crates/hir-expand/src/mod_path.rs
@@ -126,7 +126,7 @@ struct Display<'a> {
path: &'a ModPath,
}
-impl<'a> fmt::Display for Display<'a> {
+impl fmt::Display for Display<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
display_fmt_path(self.db, self.path, f, true)
}
@@ -137,7 +137,7 @@ struct UnescapedDisplay<'a> {
path: &'a UnescapedModPath<'a>,
}
-impl<'a> fmt::Display for UnescapedDisplay<'a> {
+impl fmt::Display for UnescapedDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
display_fmt_path(self.db, self.path.0, f, false)
}
diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/name.rs b/src/tools/rust-analyzer/crates/hir-expand/src/name.rs
index f8dbb8427..7c179c0cf 100644
--- a/src/tools/rust-analyzer/crates/hir-expand/src/name.rs
+++ b/src/tools/rust-analyzer/crates/hir-expand/src/name.rs
@@ -24,7 +24,7 @@ enum Repr {
TupleField(usize),
}
-impl<'a> UnescapedName<'a> {
+impl UnescapedName<'_> {
/// Returns the textual representation of this name as a [`SmolStr`]. Prefer using this over
/// [`ToString::to_string`] if possible as this conversion is cheaper in the general case.
pub fn to_smol_str(&self) -> SmolStr {
@@ -40,7 +40,7 @@ impl<'a> UnescapedName<'a> {
}
}
- pub fn display(&'a self, db: &dyn crate::db::ExpandDatabase) -> impl fmt::Display + 'a {
+ pub fn display(&self, db: &dyn crate::db::ExpandDatabase) -> impl fmt::Display + '_ {
_ = db;
UnescapedDisplay { name: self }
}
@@ -96,6 +96,15 @@ impl Name {
Name::new_inline("[missing name]")
}
+ /// Returns true if this is a fake name for things missing in the source code. See
+ /// [`missing()`][Self::missing] for details.
+ ///
+ /// Use this method instead of comparing with `Self::missing()` as missing names
+ /// (ideally should) have a `gensym` semantics.
+ pub fn is_missing(&self) -> bool {
+ self == &Name::missing()
+ }
+
/// Generates a new name which is only equal to itself, by incrementing a counter. Due
/// its implementation, it should not be used in things that salsa considers, like
/// type names or field names, and it should be only used in names of local variables
@@ -162,7 +171,7 @@ struct Display<'a> {
name: &'a Name,
}
-impl<'a> fmt::Display for Display<'a> {
+impl fmt::Display for Display<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.name.0 {
Repr::Text(text) => fmt::Display::fmt(&text, f),
@@ -175,7 +184,7 @@ struct UnescapedDisplay<'a> {
name: &'a UnescapedName<'a>,
}
-impl<'a> fmt::Display for UnescapedDisplay<'a> {
+impl fmt::Display for UnescapedDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.name.0 .0 {
Repr::Text(text) => {
@@ -282,8 +291,10 @@ pub mod known {
alloc,
iter,
ops,
+ fmt,
future,
result,
+ string,
boxed,
option,
prelude,
@@ -311,6 +322,7 @@ pub mod known {
RangeToInclusive,
RangeTo,
Range,
+ String,
Neg,
Not,
None,
@@ -321,6 +333,7 @@ pub mod known {
iter_mut,
len,
is_empty,
+ as_str,
new,
// Builtin macros
asm,
@@ -334,6 +347,7 @@ pub mod known {
core_panic,
env,
file,
+ format,
format_args_nl,
format_args,
global_asm,
@@ -365,6 +379,7 @@ pub mod known {
cfg_eval,
crate_type,
derive,
+ derive_const,
global_allocator,
no_core,
no_std,
diff --git a/src/tools/rust-analyzer/crates/hir-ty/Cargo.toml b/src/tools/rust-analyzer/crates/hir-ty/Cargo.toml
index c8bea3450..abc19d63a 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/Cargo.toml
+++ b/src/tools/rust-analyzer/crates/hir-ty/Cargo.toml
@@ -19,14 +19,15 @@ bitflags = "2.1.0"
smallvec.workspace = true
ena = "0.14.0"
either = "1.7.0"
+oorandom = "11.1.3"
tracing = "0.1.35"
rustc-hash = "1.1.0"
scoped-tls = "1.0.0"
-chalk-solve = { version = "0.91.0", default-features = false }
-chalk-ir = "0.91.0"
-chalk-recursive = { version = "0.91.0", default-features = false }
-chalk-derive = "0.91.0"
-la-arena = { version = "0.3.0", path = "../../lib/la-arena" }
+chalk-solve = { version = "0.92.0", default-features = false }
+chalk-ir = "0.92.0"
+chalk-recursive = { version = "0.92.0", default-features = false }
+chalk-derive = "0.92.0"
+la-arena.workspace = true
once_cell = "1.17.0"
triomphe.workspace = true
nohash-hasher.workspace = true
@@ -47,7 +48,6 @@ limit.workspace = true
expect-test = "1.4.0"
tracing = "0.1.35"
tracing-subscriber = { version = "0.3.16", default-features = false, features = [
- "env-filter",
"registry",
] }
tracing-tree = "0.2.1"
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/autoderef.rs b/src/tools/rust-analyzer/crates/hir-ty/src/autoderef.rs
index 3860bccec..4625a3b01 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/autoderef.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/autoderef.rs
@@ -36,7 +36,7 @@ pub fn autoderef(
) -> impl Iterator<Item = Ty> {
let mut table = InferenceTable::new(db, env);
let ty = table.instantiate_canonical(ty);
- let mut autoderef = Autoderef::new(&mut table, ty);
+ let mut autoderef = Autoderef::new(&mut table, ty, false);
let mut v = Vec::new();
while let Some((ty, _steps)) = autoderef.next() {
// `ty` may contain unresolved inference variables. Since there's no chance they would be
@@ -63,12 +63,13 @@ pub(crate) struct Autoderef<'a, 'db> {
ty: Ty,
at_start: bool,
steps: Vec<(AutoderefKind, Ty)>,
+ explicit: bool,
}
impl<'a, 'db> Autoderef<'a, 'db> {
- pub(crate) fn new(table: &'a mut InferenceTable<'db>, ty: Ty) -> Self {
+ pub(crate) fn new(table: &'a mut InferenceTable<'db>, ty: Ty, explicit: bool) -> Self {
let ty = table.resolve_ty_shallow(&ty);
- Autoderef { table, ty, at_start: true, steps: Vec::new() }
+ Autoderef { table, ty, at_start: true, steps: Vec::new(), explicit }
}
pub(crate) fn step_count(&self) -> usize {
@@ -97,7 +98,7 @@ impl Iterator for Autoderef<'_, '_> {
return None;
}
- let (kind, new_ty) = autoderef_step(self.table, self.ty.clone())?;
+ let (kind, new_ty) = autoderef_step(self.table, self.ty.clone(), self.explicit)?;
self.steps.push((kind, self.ty.clone()));
self.ty = new_ty;
@@ -109,8 +110,9 @@ impl Iterator for Autoderef<'_, '_> {
pub(crate) fn autoderef_step(
table: &mut InferenceTable<'_>,
ty: Ty,
+ explicit: bool,
) -> Option<(AutoderefKind, Ty)> {
- if let Some(derefed) = builtin_deref(table, &ty, false) {
+ if let Some(derefed) = builtin_deref(table, &ty, explicit) {
Some((AutoderefKind::Builtin, table.resolve_ty_shallow(derefed)))
} else {
Some((AutoderefKind::Overloaded, deref_by_trait(table, ty)?))
@@ -124,7 +126,6 @@ pub(crate) fn builtin_deref<'ty>(
) -> Option<&'ty Ty> {
match ty.kind(Interner) {
TyKind::Ref(.., ty) => Some(ty),
- // FIXME: Maybe accept this but diagnose if its not explicit?
TyKind::Raw(.., ty) if explicit => Some(ty),
&TyKind::Adt(chalk_ir::AdtId(adt), ref substs) => {
if crate::lang_items::is_box(table.db, adt) {
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/chalk_db.rs b/src/tools/rust-analyzer/crates/hir-ty/src/chalk_db.rs
index 5dd8e2719..f4fbace19 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/chalk_db.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/chalk_db.rs
@@ -5,13 +5,13 @@ use std::{iter, sync::Arc};
use tracing::debug;
-use chalk_ir::{cast::Cast, fold::shift::Shift, CanonicalVarKinds};
+use chalk_ir::{cast::Caster, fold::shift::Shift, CanonicalVarKinds};
use chalk_solve::rust_ir::{self, OpaqueTyDatumBound, WellKnownTrait};
use base_db::CrateId;
use hir_def::{
hir::Movability,
- lang_item::{lang_attr, LangItem, LangItemTarget},
+ lang_item::{LangItem, LangItemTarget},
AssocItemId, BlockId, GenericDefId, HasModule, ItemContainerId, Lookup, TypeAliasId,
};
use hir_expand::name::name;
@@ -46,7 +46,7 @@ pub(crate) type AssociatedTyValue = chalk_solve::rust_ir::AssociatedTyValue<Inte
pub(crate) type FnDefDatum = chalk_solve::rust_ir::FnDefDatum<Interner>;
pub(crate) type Variances = chalk_ir::Variances<Interner>;
-impl<'a> chalk_solve::RustIrDatabase<Interner> for ChalkContext<'a> {
+impl chalk_solve::RustIrDatabase<Interner> for ChalkContext<'_> {
fn associated_ty_data(&self, id: AssocTypeId) -> Arc<AssociatedTyDatum> {
self.db.associated_ty_data(id)
}
@@ -60,9 +60,37 @@ impl<'a> chalk_solve::RustIrDatabase<Interner> for ChalkContext<'a> {
// FIXME: keep track of these
Arc::new(rust_ir::AdtRepr { c: false, packed: false, int: None })
}
- fn discriminant_type(&self, _ty: chalk_ir::Ty<Interner>) -> chalk_ir::Ty<Interner> {
- // FIXME: keep track of this
- chalk_ir::TyKind::Scalar(chalk_ir::Scalar::Uint(chalk_ir::UintTy::U32)).intern(Interner)
+ fn discriminant_type(&self, ty: chalk_ir::Ty<Interner>) -> chalk_ir::Ty<Interner> {
+ if let chalk_ir::TyKind::Adt(id, _) = ty.kind(Interner) {
+ if let hir_def::AdtId::EnumId(e) = id.0 {
+ let enum_data = self.db.enum_data(e);
+ let ty = enum_data.repr.unwrap_or_default().discr_type();
+ return chalk_ir::TyKind::Scalar(match ty {
+ hir_def::layout::IntegerType::Pointer(is_signed) => match is_signed {
+ true => chalk_ir::Scalar::Int(chalk_ir::IntTy::Isize),
+ false => chalk_ir::Scalar::Uint(chalk_ir::UintTy::Usize),
+ },
+ hir_def::layout::IntegerType::Fixed(size, is_signed) => match is_signed {
+ true => chalk_ir::Scalar::Int(match size {
+ hir_def::layout::Integer::I8 => chalk_ir::IntTy::I8,
+ hir_def::layout::Integer::I16 => chalk_ir::IntTy::I16,
+ hir_def::layout::Integer::I32 => chalk_ir::IntTy::I32,
+ hir_def::layout::Integer::I64 => chalk_ir::IntTy::I64,
+ hir_def::layout::Integer::I128 => chalk_ir::IntTy::I128,
+ }),
+ false => chalk_ir::Scalar::Uint(match size {
+ hir_def::layout::Integer::I8 => chalk_ir::UintTy::U8,
+ hir_def::layout::Integer::I16 => chalk_ir::UintTy::U16,
+ hir_def::layout::Integer::I32 => chalk_ir::UintTy::U32,
+ hir_def::layout::Integer::I64 => chalk_ir::UintTy::U64,
+ hir_def::layout::Integer::I128 => chalk_ir::UintTy::U128,
+ }),
+ },
+ })
+ .intern(Interner);
+ }
+ }
+ chalk_ir::TyKind::Scalar(chalk_ir::Scalar::Uint(chalk_ir::UintTy::U8)).intern(Interner)
}
fn impl_datum(&self, impl_id: ImplId) -> Arc<ImplDatum> {
self.db.impl_datum(self.krate, impl_id)
@@ -565,7 +593,7 @@ pub(crate) fn trait_datum_query(
let where_clauses = convert_where_clauses(db, trait_.into(), &bound_vars);
let associated_ty_ids = trait_data.associated_types().map(to_assoc_type_id).collect();
let trait_datum_bound = rust_ir::TraitDatumBound { where_clauses };
- let well_known = lang_attr(db.upcast(), trait_).and_then(well_known_trait_from_lang_item);
+ let well_known = db.lang_attr(trait_.into()).and_then(well_known_trait_from_lang_item);
let trait_datum = TraitDatum {
id: trait_id,
binders: make_binders(db, &generic_params, trait_datum_bound),
@@ -593,6 +621,7 @@ fn well_known_trait_from_lang_item(item: LangItem) -> Option<WellKnownTrait> {
LangItem::Unsize => WellKnownTrait::Unsize,
LangItem::Tuple => WellKnownTrait::Tuple,
LangItem::PointeeTrait => WellKnownTrait::Pointee,
+ LangItem::FnPtrTrait => WellKnownTrait::FnPtr,
_ => return None,
})
}
@@ -614,6 +643,7 @@ fn lang_item_from_well_known_trait(trait_: WellKnownTrait) -> LangItem {
WellKnownTrait::Unpin => LangItem::Unpin,
WellKnownTrait::Unsize => LangItem::Unsize,
WellKnownTrait::Pointee => LangItem::PointeeTrait,
+ WellKnownTrait::FnPtr => LangItem::FnPtrTrait,
}
}
@@ -844,28 +874,34 @@ pub(super) fn generic_predicate_to_inline_bound(
}
let args_no_self = trait_ref.substitution.as_slice(Interner)[1..]
.iter()
- .map(|ty| ty.clone().cast(Interner))
+ .cloned()
+ .casted(Interner)
.collect();
let trait_bound = rust_ir::TraitBound { trait_id: trait_ref.trait_id, args_no_self };
Some(chalk_ir::Binders::new(binders, rust_ir::InlineBound::TraitBound(trait_bound)))
}
WhereClause::AliasEq(AliasEq { alias: AliasTy::Projection(projection_ty), ty }) => {
- let trait_ = projection_ty.trait_(db);
- if projection_ty.self_type_parameter(db) != self_ty_shifted_in {
+ let generics =
+ generics(db.upcast(), from_assoc_type_id(projection_ty.associated_ty_id).into());
+ let (assoc_args, trait_args) =
+ projection_ty.substitution.as_slice(Interner).split_at(generics.len_self());
+ let (self_ty, args_no_self) =
+ trait_args.split_first().expect("projection without trait self type");
+ if self_ty.assert_ty_ref(Interner) != &self_ty_shifted_in {
return None;
}
- let args_no_self = projection_ty.substitution.as_slice(Interner)[1..]
- .iter()
- .map(|ty| ty.clone().cast(Interner))
- .collect();
+
+ let args_no_self = args_no_self.iter().cloned().casted(Interner).collect();
+ let parameters = assoc_args.to_vec();
+
let alias_eq_bound = rust_ir::AliasEqBound {
value: ty.clone(),
trait_bound: rust_ir::TraitBound {
- trait_id: to_chalk_trait_id(trait_),
+ trait_id: to_chalk_trait_id(projection_ty.trait_(db)),
args_no_self,
},
associated_ty_id: projection_ty.associated_ty_id,
- parameters: Vec::new(), // FIXME we don't support generic associated types yet
+ parameters,
};
Some(chalk_ir::Binders::new(
binders,
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/chalk_ext.rs b/src/tools/rust-analyzer/crates/hir-ty/src/chalk_ext.rs
index a8071591a..c0b243ea2 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/chalk_ext.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/chalk_ext.rs
@@ -343,7 +343,8 @@ impl TyExt for Ty {
fn is_copy(self, db: &dyn HirDatabase, owner: DefWithBodyId) -> bool {
let crate_id = owner.module(db.upcast()).krate();
- let Some(copy_trait) = db.lang_item(crate_id, LangItem::Copy).and_then(|x| x.as_trait()) else {
+ let Some(copy_trait) = db.lang_item(crate_id, LangItem::Copy).and_then(|it| it.as_trait())
+ else {
return false;
};
let trait_ref = TyBuilder::trait_ref(db, copy_trait).push(self).build();
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs b/src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs
index 262341c6e..1c0f7b08d 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs
@@ -16,7 +16,8 @@ use triomphe::Arc;
use crate::{
db::HirDatabase, infer::InferenceContext, lower::ParamLoweringMode,
mir::monomorphize_mir_body_bad, to_placeholder_idx, utils::Generics, Const, ConstData,
- ConstScalar, ConstValue, GenericArg, Interner, MemoryMap, Substitution, Ty, TyBuilder,
+ ConstScalar, ConstValue, GenericArg, Interner, MemoryMap, Substitution, TraitEnvironment, Ty,
+ TyBuilder,
};
use super::mir::{interpret_mir, lower_to_mir, pad16, MirEvalError, MirLowerError};
@@ -88,7 +89,7 @@ pub(crate) fn path_to_const(
ConstValue::Placeholder(to_placeholder_idx(db, p.into()))
}
ParamLoweringMode::Variable => match args.param_idx(p.into()) {
- Some(x) => ConstValue::BoundVar(BoundVar::new(debruijn, x)),
+ Some(it) => ConstValue::BoundVar(BoundVar::new(debruijn, it)),
None => {
never!(
"Generic list doesn't contain this param: {:?}, {:?}, {:?}",
@@ -135,15 +136,15 @@ pub fn intern_const_ref(
ty: Ty,
krate: CrateId,
) -> Const {
- let layout = db.layout_of_ty(ty.clone(), krate);
+ let layout = db.layout_of_ty(ty.clone(), Arc::new(TraitEnvironment::empty(krate)));
let bytes = match value {
LiteralConstRef::Int(i) => {
// FIXME: We should handle failure of layout better.
- let size = layout.map(|x| x.size.bytes_usize()).unwrap_or(16);
+ let size = layout.map(|it| it.size.bytes_usize()).unwrap_or(16);
ConstScalar::Bytes(i.to_le_bytes()[0..size].to_vec(), MemoryMap::default())
}
LiteralConstRef::UInt(i) => {
- let size = layout.map(|x| x.size.bytes_usize()).unwrap_or(16);
+ let size = layout.map(|it| it.size.bytes_usize()).unwrap_or(16);
ConstScalar::Bytes(i.to_le_bytes()[0..size].to_vec(), MemoryMap::default())
}
LiteralConstRef::Bool(b) => ConstScalar::Bytes(vec![*b as u8], MemoryMap::default()),
@@ -171,9 +172,9 @@ pub fn try_const_usize(db: &dyn HirDatabase, c: &Const) -> Option<u128> {
chalk_ir::ConstValue::InferenceVar(_) => None,
chalk_ir::ConstValue::Placeholder(_) => None,
chalk_ir::ConstValue::Concrete(c) => match &c.interned {
- ConstScalar::Bytes(x, _) => Some(u128::from_le_bytes(pad16(&x, false))),
+ ConstScalar::Bytes(it, _) => Some(u128::from_le_bytes(pad16(&it, false))),
ConstScalar::UnevaluatedConst(c, subst) => {
- let ec = db.const_eval(*c, subst.clone()).ok()?;
+ let ec = db.const_eval(*c, subst.clone(), None).ok()?;
try_const_usize(db, &ec)
}
_ => None,
@@ -186,6 +187,7 @@ pub(crate) fn const_eval_recover(
_: &[String],
_: &GeneralConstId,
_: &Substitution,
+ _: &Option<Arc<TraitEnvironment>>,
) -> Result<Const, ConstEvalError> {
Err(ConstEvalError::MirLowerError(MirLowerError::Loop))
}
@@ -210,6 +212,7 @@ pub(crate) fn const_eval_query(
db: &dyn HirDatabase,
def: GeneralConstId,
subst: Substitution,
+ trait_env: Option<Arc<TraitEnvironment>>,
) -> Result<Const, ConstEvalError> {
let body = match def {
GeneralConstId::ConstId(c) => {
@@ -228,7 +231,7 @@ pub(crate) fn const_eval_query(
}
GeneralConstId::InTypeConstId(c) => db.mir_body(c.into())?,
};
- let c = interpret_mir(db, &body, false).0?;
+ let c = interpret_mir(db, body, false, trait_env).0?;
Ok(c)
}
@@ -241,7 +244,7 @@ pub(crate) fn const_eval_static_query(
Substitution::empty(Interner),
db.trait_environment_for_body(def.into()),
)?;
- let c = interpret_mir(db, &body, false).0?;
+ let c = interpret_mir(db, body, false, None).0?;
Ok(c)
}
@@ -268,7 +271,7 @@ pub(crate) fn const_eval_discriminant_variant(
Substitution::empty(Interner),
db.trait_environment_for_body(def),
)?;
- let c = interpret_mir(db, &mir_body, false).0?;
+ let c = interpret_mir(db, mir_body, false, None).0?;
let c = try_const_usize(db, &c).unwrap() as i128;
Ok(c)
}
@@ -293,7 +296,7 @@ pub(crate) fn eval_to_const(
}
let infer = ctx.clone().resolve_all();
if let Ok(mir_body) = lower_to_mir(ctx.db, ctx.owner, &ctx.body, &infer, expr) {
- if let Ok(result) = interpret_mir(db, &mir_body, true).0 {
+ if let Ok(result) = interpret_mir(db, Arc::new(mir_body), true, None).0 {
return result;
}
}
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests.rs b/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests.rs
index 0db1fefbf..666955fa1 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests.rs
@@ -1,10 +1,11 @@
use base_db::{fixture::WithFixture, FileId};
use chalk_ir::Substitution;
use hir_def::db::DefDatabase;
+use test_utils::skip_slow_tests;
use crate::{
consteval::try_const_usize, db::HirDatabase, mir::pad16, test_db::TestDB, Const, ConstScalar,
- Interner,
+ Interner, MemoryMap,
};
use super::{
@@ -16,7 +17,7 @@ mod intrinsics;
fn simplify(e: ConstEvalError) -> ConstEvalError {
match e {
- ConstEvalError::MirEvalError(MirEvalError::InFunction(_, e, _, _)) => {
+ ConstEvalError::MirEvalError(MirEvalError::InFunction(e, _)) => {
simplify(ConstEvalError::MirEvalError(*e))
}
_ => e,
@@ -36,7 +37,37 @@ fn check_fail(ra_fixture: &str, error: impl FnOnce(ConstEvalError) -> bool) {
#[track_caller]
fn check_number(ra_fixture: &str, answer: i128) {
- let (db, file_id) = TestDB::with_single_file(ra_fixture);
+ check_answer(ra_fixture, |b, _| {
+ assert_eq!(
+ b,
+ &answer.to_le_bytes()[0..b.len()],
+ "Bytes differ. In decimal form: actual = {}, expected = {answer}",
+ i128::from_le_bytes(pad16(b, true))
+ );
+ });
+}
+
+#[track_caller]
+fn check_str(ra_fixture: &str, answer: &str) {
+ check_answer(ra_fixture, |b, mm| {
+ let addr = usize::from_le_bytes(b[0..b.len() / 2].try_into().unwrap());
+ let size = usize::from_le_bytes(b[b.len() / 2..].try_into().unwrap());
+ let Some(bytes) = mm.get(addr, size) else {
+ panic!("string data missed in the memory map");
+ };
+ assert_eq!(
+ bytes,
+ answer.as_bytes(),
+ "Bytes differ. In string form: actual = {}, expected = {answer}",
+ String::from_utf8_lossy(bytes)
+ );
+ });
+}
+
+#[track_caller]
+fn check_answer(ra_fixture: &str, check: impl FnOnce(&[u8], &MemoryMap)) {
+ let (db, file_ids) = TestDB::with_many_files(ra_fixture);
+ let file_id = *file_ids.last().unwrap();
let r = match eval_goal(&db, file_id) {
Ok(t) => t,
Err(e) => {
@@ -46,13 +77,8 @@ fn check_number(ra_fixture: &str, answer: i128) {
};
match &r.data(Interner).value {
chalk_ir::ConstValue::Concrete(c) => match &c.interned {
- ConstScalar::Bytes(b, _) => {
- assert_eq!(
- b,
- &answer.to_le_bytes()[0..b.len()],
- "Bytes differ. In decimal form: actual = {}, expected = {answer}",
- i128::from_le_bytes(pad16(b, true))
- );
+ ConstScalar::Bytes(b, mm) => {
+ check(b, mm);
}
x => panic!("Expected number but found {:?}", x),
},
@@ -87,8 +113,8 @@ fn eval_goal(db: &TestDB, file_id: FileId) -> Result<Const, ConstEvalError> {
}
_ => None,
})
- .unwrap();
- db.const_eval(const_id.into(), Substitution::empty(Interner))
+ .expect("No const named GOAL found in the test");
+ db.const_eval(const_id.into(), Substitution::empty(Interner), None)
}
#[test]
@@ -108,6 +134,7 @@ fn bit_op() {
check_fail(r#"const GOAL: i8 = 1 << 8"#, |e| {
e == ConstEvalError::MirEvalError(MirEvalError::Panic("Overflow in Shl".to_string()))
});
+ check_number(r#"const GOAL: i32 = 100000000i32 << 11"#, (100000000i32 << 11) as i128);
}
#[test]
@@ -166,14 +193,21 @@ fn casts() {
check_number(
r#"
//- minicore: coerce_unsized, index, slice
+ struct X {
+ unsize_field: [u8],
+ }
+
const GOAL: usize = {
let a = [10, 20, 3, 15];
let x: &[i32] = &a;
- let y: *const [i32] = x;
- let z = y as *const [u8]; // slice fat pointer cast don't touch metadata
- let q = z as *const str;
- let p = q as *const [u8];
- let w = unsafe { &*z };
+ let x: *const [i32] = x;
+ let x = x as *const [u8]; // slice fat pointer cast don't touch metadata
+ let x = x as *const str;
+ let x = x as *const X;
+ let x = x as *const [i16];
+ let x = x as *const X;
+ let x = x as *const [u8];
+ let w = unsafe { &*x };
w.len()
};
"#,
@@ -199,6 +233,30 @@ fn raw_pointer_equality() {
}
#[test]
+fn alignment() {
+ check_answer(
+ r#"
+//- minicore: transmute
+use core::mem::transmute;
+const GOAL: usize = {
+ let x: i64 = 2;
+ transmute(&x)
+}
+ "#,
+ |b, _| assert_eq!(b[0] % 8, 0),
+ );
+ check_answer(
+ r#"
+//- minicore: transmute
+use core::mem::transmute;
+static X: i64 = 12;
+const GOAL: usize = transmute(&X);
+ "#,
+ |b, _| assert_eq!(b[0] % 8, 0),
+ );
+}
+
+#[test]
fn locals() {
check_number(
r#"
@@ -1129,6 +1187,25 @@ fn pattern_matching_ergonomics() {
}
#[test]
+fn destructing_assignment() {
+ check_number(
+ r#"
+ //- minicore: add
+ const fn f(i: &mut u8) -> &mut u8 {
+ *i += 1;
+ i
+ }
+ const GOAL: u8 = {
+ let mut i = 4;
+ _ = f(&mut i);
+ i
+ };
+ "#,
+ 5,
+ );
+}
+
+#[test]
fn let_else() {
check_number(
r#"
@@ -1370,14 +1447,14 @@ fn builtin_derive_macro() {
#[derive(Clone)]
struct Y {
field1: i32,
- field2: u8,
+ field2: ((i32, u8), i64),
}
const GOAL: u8 = {
- let x = X(2, Z::Foo(Y { field1: 4, field2: 5 }), 8);
+ let x = X(2, Z::Foo(Y { field1: 4, field2: ((32, 5), 12) }), 8);
let x = x.clone();
let Z::Foo(t) = x.1;
- t.field2
+ t.field2.0 .1
};
"#,
5,
@@ -1551,6 +1628,58 @@ fn closures() {
}
#[test]
+fn manual_fn_trait_impl() {
+ check_number(
+ r#"
+//- minicore: fn, copy
+struct S(i32);
+
+impl FnOnce<(i32, i32)> for S {
+ type Output = i32;
+
+ extern "rust-call" fn call_once(self, arg: (i32, i32)) -> i32 {
+ arg.0 + arg.1 + self.0
+ }
+}
+
+const GOAL: i32 = {
+ let s = S(1);
+ s(2, 3)
+};
+"#,
+ 6,
+ );
+}
+
+#[test]
+fn closure_capture_unsized_type() {
+ check_number(
+ r#"
+ //- minicore: fn, copy, slice, index, coerce_unsized
+ fn f<T: A>(x: &<T as A>::Ty) -> &<T as A>::Ty {
+ let c = || &*x;
+ c()
+ }
+
+ trait A {
+ type Ty;
+ }
+
+ impl A for i32 {
+ type Ty = [u8];
+ }
+
+ const GOAL: u8 = {
+ let k: &[u8] = &[1, 2, 3];
+ let k = f::<i32>(k);
+ k[0] + k[1] + k[2]
+ }
+ "#,
+ 6,
+ );
+}
+
+#[test]
fn closure_and_impl_fn() {
check_number(
r#"
@@ -1636,6 +1765,24 @@ fn function_pointer_in_constants() {
}
#[test]
+fn function_pointer_and_niche_optimization() {
+ check_number(
+ r#"
+ //- minicore: option
+ const GOAL: i32 = {
+ let f: fn(i32) -> i32 = |x| x + 2;
+ let init = Some(f);
+ match init {
+ Some(t) => t(3),
+ None => 222,
+ }
+ };
+ "#,
+ 5,
+ );
+}
+
+#[test]
fn function_pointer() {
check_number(
r#"
@@ -1663,6 +1810,18 @@ fn function_pointer() {
);
check_number(
r#"
+ fn add2(x: u8) -> u8 {
+ x + 2
+ }
+ const GOAL: u8 = {
+ let plus2 = add2 as fn(u8) -> u8;
+ plus2(3)
+ };
+ "#,
+ 5,
+ );
+ check_number(
+ r#"
//- minicore: coerce_unsized, index, slice
fn add2(x: u8) -> u8 {
x + 2
@@ -1847,6 +2006,65 @@ fn dyn_trait() {
"#,
900,
);
+ check_number(
+ r#"
+ //- minicore: coerce_unsized, index, slice
+ trait A {
+ fn x(&self) -> i32;
+ }
+
+ trait B: A {}
+
+ impl A for i32 {
+ fn x(&self) -> i32 {
+ 5
+ }
+ }
+
+ impl B for i32 {
+
+ }
+
+ const fn f(x: &dyn B) -> i32 {
+ x.x()
+ }
+
+ const GOAL: i32 = f(&2i32);
+ "#,
+ 5,
+ );
+}
+
+#[test]
+fn coerce_unsized() {
+ check_number(
+ r#"
+//- minicore: coerce_unsized, deref_mut, slice, index, transmute, non_null
+use core::ops::{Deref, DerefMut, CoerceUnsized};
+use core::{marker::Unsize, mem::transmute, ptr::NonNull};
+
+struct ArcInner<T: ?Sized> {
+ strong: usize,
+ weak: usize,
+ data: T,
+}
+
+pub struct Arc<T: ?Sized> {
+ inner: NonNull<ArcInner<T>>,
+}
+
+impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<Arc<U>> for Arc<T> {}
+
+const GOAL: usize = {
+ let x = transmute::<usize, Arc<[i32; 3]>>(12);
+ let y: Arc<[i32]> = x;
+ let z = transmute::<Arc<[i32]>, (usize, usize)>(y);
+ z.1
+};
+
+ "#,
+ 3,
+ );
}
#[test]
@@ -1961,6 +2179,17 @@ fn array_and_index() {
}
#[test]
+fn string() {
+ check_str(
+ r#"
+ //- minicore: coerce_unsized, index, slice
+ const GOAL: &str = "hello";
+ "#,
+ "hello",
+ );
+}
+
+#[test]
fn byte_string() {
check_number(
r#"
@@ -2018,6 +2247,57 @@ fn consts() {
"#,
6,
);
+
+ check_number(
+ r#"
+ const F1: i32 = 2147483647;
+ const F2: i32 = F1 - 25;
+ const GOAL: i32 = F2;
+ "#,
+ 2147483622,
+ );
+
+ check_number(
+ r#"
+ const F1: i32 = -2147483648;
+ const F2: i32 = F1 + 18;
+ const GOAL: i32 = F2;
+ "#,
+ -2147483630,
+ );
+
+ check_number(
+ r#"
+ const F1: i32 = 10;
+ const F2: i32 = F1 - 20;
+ const GOAL: i32 = F2;
+ "#,
+ -10,
+ );
+
+ check_number(
+ r#"
+ const F1: i32 = 25;
+ const F2: i32 = F1 - 25;
+ const GOAL: i32 = F2;
+ "#,
+ 0,
+ );
+
+ check_number(
+ r#"
+ const A: i32 = -2147483648;
+ const GOAL: bool = A > 0;
+ "#,
+ 0,
+ );
+
+ check_number(
+ r#"
+ const GOAL: i64 = (-2147483648_i32) as i64;
+ "#,
+ -2147483648,
+ );
}
#[test]
@@ -2116,11 +2396,14 @@ fn const_loop() {
fn const_transfer_memory() {
check_number(
r#"
- const A1: &i32 = &2;
- const A2: &i32 = &5;
- const GOAL: i32 = *A1 + *A2;
+ //- minicore: slice, index, coerce_unsized
+ const A1: &i32 = &1;
+ const A2: &i32 = &10;
+ const A3: [&i32; 3] = [&1, &2, &100];
+ const A4: (i32, &i32) = (1, &1000);
+ const GOAL: i32 = *A1 + *A2 + *A3[2] + *A4.1;
"#,
- 7,
+ 1111,
);
}
@@ -2287,6 +2570,51 @@ fn const_trait_assoc() {
);
check_number(
r#"
+ //- /a/lib.rs crate:a
+ pub trait ToConst {
+ const VAL: usize;
+ }
+ pub const fn to_const<T: ToConst>() -> usize {
+ T::VAL
+ }
+ //- /main.rs crate:main deps:a
+ use a::{ToConst, to_const};
+ struct U0;
+ impl ToConst for U0 {
+ const VAL: usize = 5;
+ }
+ const GOAL: usize = to_const::<U0>();
+ "#,
+ 5,
+ );
+ check_number(
+ r#"
+ //- minicore: size_of, fn
+ //- /a/lib.rs crate:a
+ use core::mem::size_of;
+ pub struct S<T>(T);
+ impl<T> S<T> {
+ pub const X: usize = {
+ let k: T;
+ let f = || core::mem::size_of::<T>();
+ f()
+ };
+ }
+ //- /main.rs crate:main deps:a
+ use a::{S};
+ trait Tr {
+ type Ty;
+ }
+ impl Tr for i32 {
+ type Ty = u64;
+ }
+ struct K<T: Tr>(<T as Tr>::Ty);
+ const GOAL: usize = S::<K<i32>>::X;
+ "#,
+ 8,
+ );
+ check_number(
+ r#"
struct S<T>(*mut T);
trait MySized: Sized {
@@ -2311,21 +2639,11 @@ fn const_trait_assoc() {
}
#[test]
-fn panic_messages() {
- check_fail(
- r#"
- //- minicore: panic
- const GOAL: u8 = {
- let x: u16 = 2;
- panic!("hello");
- };
- "#,
- |e| e == ConstEvalError::MirEvalError(MirEvalError::Panic("hello".to_string())),
- );
-}
-
-#[test]
fn exec_limits() {
+ if skip_slow_tests() {
+ return;
+ }
+
check_fail(
r#"
const GOAL: usize = loop {};
@@ -2339,7 +2657,7 @@ fn exec_limits() {
}
const GOAL: i32 = f(0);
"#,
- |e| e == ConstEvalError::MirEvalError(MirEvalError::StackOverflow),
+ |e| e == ConstEvalError::MirEvalError(MirEvalError::ExecutionLimitExceeded),
);
// Reasonable code should still work
check_number(
@@ -2356,9 +2674,31 @@ fn exec_limits() {
}
sum
}
- const GOAL: i32 = f(10000);
+ const GOAL: i32 = f(1000);
"#,
- 10000 * 10000,
+ 1000 * 1000,
+ );
+}
+
+#[test]
+fn memory_limit() {
+ check_fail(
+ r#"
+ extern "Rust" {
+ #[rustc_allocator]
+ fn __rust_alloc(size: usize, align: usize) -> *mut u8;
+ }
+
+ const GOAL: u8 = unsafe {
+ __rust_alloc(30_000_000_000, 1); // 30GB
+ 2
+ };
+ "#,
+ |e| {
+ e == ConstEvalError::MirEvalError(MirEvalError::Panic(
+ "Memory allocation of 30000000000 bytes failed".to_string(),
+ ))
+ },
);
}
@@ -2377,6 +2717,37 @@ fn type_error() {
}
#[test]
+fn unsized_field() {
+ check_number(
+ r#"
+ //- minicore: coerce_unsized, index, slice, transmute
+ use core::mem::transmute;
+
+ struct Slice([usize]);
+ struct Slice2(Slice);
+
+ impl Slice2 {
+ fn as_inner(&self) -> &Slice {
+ &self.0
+ }
+
+ fn as_bytes(&self) -> &[usize] {
+ &self.as_inner().0
+ }
+ }
+
+ const GOAL: usize = unsafe {
+ let x: &[usize] = &[1, 2, 3];
+ let x: &Slice2 = transmute(x);
+ let x = x.as_bytes();
+ x[0] + x[1] + x[2] + x.len() * 100
+ };
+ "#,
+ 306,
+ );
+}
+
+#[test]
fn unsized_local() {
check_fail(
r#"
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests/intrinsics.rs b/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests/intrinsics.rs
index e05d824db..2855f7890 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests/intrinsics.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests/intrinsics.rs
@@ -15,6 +15,171 @@ fn size_of() {
}
#[test]
+fn size_of_val() {
+ check_number(
+ r#"
+ //- minicore: coerce_unsized
+ extern "rust-intrinsic" {
+ pub fn size_of_val<T: ?Sized>(_: *const T) -> usize;
+ }
+
+ struct X(i32, u8);
+
+ const GOAL: usize = size_of_val(&X(1, 2));
+ "#,
+ 8,
+ );
+ check_number(
+ r#"
+ //- minicore: coerce_unsized
+ extern "rust-intrinsic" {
+ pub fn size_of_val<T: ?Sized>(_: *const T) -> usize;
+ }
+
+ const GOAL: usize = {
+ let it: &[i32] = &[1, 2, 3];
+ size_of_val(it)
+ };
+ "#,
+ 12,
+ );
+ check_number(
+ r#"
+ //- minicore: coerce_unsized, transmute
+ use core::mem::transmute;
+
+ extern "rust-intrinsic" {
+ pub fn size_of_val<T: ?Sized>(_: *const T) -> usize;
+ }
+
+ struct X {
+ x: i64,
+ y: u8,
+ t: [i32],
+ }
+
+ const GOAL: usize = unsafe {
+ let y: &X = transmute([0usize, 3]);
+ size_of_val(y)
+ };
+ "#,
+ 24,
+ );
+ check_number(
+ r#"
+ //- minicore: coerce_unsized, transmute
+ use core::mem::transmute;
+
+ extern "rust-intrinsic" {
+ pub fn size_of_val<T: ?Sized>(_: *const T) -> usize;
+ }
+
+ struct X {
+ x: i32,
+ y: i64,
+ t: [u8],
+ }
+
+ const GOAL: usize = unsafe {
+ let y: &X = transmute([0usize, 15]);
+ size_of_val(y)
+ };
+ "#,
+ 32,
+ );
+ check_number(
+ r#"
+ //- minicore: coerce_unsized, fmt, builtin_impls
+ extern "rust-intrinsic" {
+ pub fn size_of_val<T: ?Sized>(_: *const T) -> usize;
+ }
+
+ const GOAL: usize = {
+ let x: &i16 = &5;
+ let y: &dyn core::fmt::Debug = x;
+ let z: &dyn core::fmt::Debug = &y;
+ size_of_val(x) + size_of_val(y) * 10 + size_of_val(z) * 100
+ };
+ "#,
+ 1622,
+ );
+ check_number(
+ r#"
+ //- minicore: coerce_unsized
+ extern "rust-intrinsic" {
+ pub fn size_of_val<T: ?Sized>(_: *const T) -> usize;
+ }
+
+ const GOAL: usize = {
+ size_of_val("salam")
+ };
+ "#,
+ 5,
+ );
+}
+
+#[test]
+fn min_align_of_val() {
+ check_number(
+ r#"
+ //- minicore: coerce_unsized
+ extern "rust-intrinsic" {
+ pub fn min_align_of_val<T: ?Sized>(_: *const T) -> usize;
+ }
+
+ struct X(i32, u8);
+
+ const GOAL: usize = min_align_of_val(&X(1, 2));
+ "#,
+ 4,
+ );
+ check_number(
+ r#"
+ //- minicore: coerce_unsized
+ extern "rust-intrinsic" {
+ pub fn min_align_of_val<T: ?Sized>(_: *const T) -> usize;
+ }
+
+ const GOAL: usize = {
+ let x: &[i32] = &[1, 2, 3];
+ min_align_of_val(x)
+ };
+ "#,
+ 4,
+ );
+}
+
+#[test]
+fn type_name() {
+ check_str(
+ r#"
+ extern "rust-intrinsic" {
+ pub fn type_name<T: ?Sized>() -> &'static str;
+ }
+
+ const GOAL: &str = type_name::<i32>();
+ "#,
+ "i32",
+ );
+ check_str(
+ r#"
+ extern "rust-intrinsic" {
+ pub fn type_name<T: ?Sized>() -> &'static str;
+ }
+
+ mod mod1 {
+ pub mod mod2 {
+ pub struct Ty;
+ }
+ }
+
+ const GOAL: &str = type_name::<mod1::mod2::Ty>();
+ "#,
+ "mod1::mod2::Ty",
+ );
+}
+
+#[test]
fn transmute() {
check_number(
r#"
@@ -29,9 +194,28 @@ fn transmute() {
}
#[test]
+fn read_via_copy() {
+ check_number(
+ r#"
+ extern "rust-intrinsic" {
+ pub fn read_via_copy<T>(e: *const T) -> T;
+ pub fn volatile_load<T>(e: *const T) -> T;
+ }
+
+ const GOAL: i32 = {
+ let x = 2;
+ read_via_copy(&x) + volatile_load(&x)
+ };
+ "#,
+ 4,
+ );
+}
+
+#[test]
fn const_eval_select() {
check_number(
r#"
+ //- minicore: fn
extern "rust-intrinsic" {
pub fn const_eval_select<ARG, F, G, RET>(arg: ARG, called_in_const: F, called_at_rt: G) -> RET
where
@@ -68,7 +252,29 @@ fn wrapping_add() {
}
#[test]
-fn saturating_add() {
+fn ptr_offset_from() {
+ check_number(
+ r#"
+ //- minicore: index, slice, coerce_unsized
+ extern "rust-intrinsic" {
+ pub fn ptr_offset_from<T>(ptr: *const T, base: *const T) -> isize;
+ pub fn ptr_offset_from_unsigned<T>(ptr: *const T, base: *const T) -> usize;
+ }
+
+ const GOAL: isize = {
+ let x = [1, 2, 3, 4, 5i32];
+ let r1 = -ptr_offset_from(&x[0], &x[4]);
+ let r2 = ptr_offset_from(&x[3], &x[1]);
+ let r3 = ptr_offset_from_unsigned(&x[3], &x[0]) as isize;
+ r3 * 100 + r2 * 10 + r1
+ };
+ "#,
+ 324,
+ );
+}
+
+#[test]
+fn saturating() {
check_number(
r#"
extern "rust-intrinsic" {
@@ -82,6 +288,16 @@ fn saturating_add() {
check_number(
r#"
extern "rust-intrinsic" {
+ pub fn saturating_sub<T>(a: T, b: T) -> T;
+ }
+
+ const GOAL: bool = saturating_sub(5u8, 7) == 0 && saturating_sub(8u8, 4) == 4;
+ "#,
+ 1,
+ );
+ check_number(
+ r#"
+ extern "rust-intrinsic" {
pub fn saturating_add<T>(a: T, b: T) -> T;
}
@@ -112,6 +328,7 @@ fn allocator() {
*ptr = 23;
*ptr2 = 32;
let ptr = __rust_realloc(ptr, 4, 1, 8);
+ let ptr = __rust_realloc(ptr, 8, 1, 3);
let ptr2 = ((ptr as usize) + 1) as *mut u8;
*ptr + *ptr2
};
@@ -160,6 +377,24 @@ fn needs_drop() {
}
#[test]
+fn discriminant_value() {
+ check_number(
+ r#"
+ //- minicore: discriminant, option
+ use core::marker::DiscriminantKind;
+ extern "rust-intrinsic" {
+ pub fn discriminant_value<T>(v: &T) -> <T as DiscriminantKind>::Discriminant;
+ }
+ const GOAL: bool = {
+ discriminant_value(&Some(2i32)) == discriminant_value(&Some(5i32))
+ && discriminant_value(&Some(2i32)) != discriminant_value(&None::<i32>)
+ };
+ "#,
+ 1,
+ );
+}
+
+#[test]
fn likely() {
check_number(
r#"
@@ -225,6 +460,8 @@ fn atomic() {
pub fn atomic_nand_seqcst<T: Copy>(dst: *mut T, src: T) -> T;
pub fn atomic_or_release<T: Copy>(dst: *mut T, src: T) -> T;
pub fn atomic_xor_seqcst<T: Copy>(dst: *mut T, src: T) -> T;
+ pub fn atomic_fence_seqcst();
+ pub fn atomic_singlethreadfence_acqrel();
}
fn should_not_reach() {
@@ -239,6 +476,7 @@ fn atomic() {
if (30, true) != atomic_cxchg_release_seqcst(&mut y, 30, 40) {
should_not_reach();
}
+ atomic_fence_seqcst();
if (40, false) != atomic_cxchg_release_seqcst(&mut y, 30, 50) {
should_not_reach();
}
@@ -246,6 +484,7 @@ fn atomic() {
should_not_reach();
}
let mut z = atomic_xsub_seqcst(&mut x, -200);
+ atomic_singlethreadfence_acqrel();
atomic_xor_seqcst(&mut x, 1024);
atomic_load_seqcst(&x) + z * 3 + atomic_load_seqcst(&y) * 2
};
@@ -328,6 +567,24 @@ fn copy_nonoverlapping() {
}
#[test]
+fn write_bytes() {
+ check_number(
+ r#"
+ extern "rust-intrinsic" {
+ fn write_bytes<T>(dst: *mut T, val: u8, count: usize);
+ }
+
+ const GOAL: i32 = unsafe {
+ let mut x = 2;
+ write_bytes(&mut x, 5, 1);
+ x
+ };
+ "#,
+ 0x05050505,
+ );
+}
+
+#[test]
fn copy() {
check_number(
r#"
@@ -363,6 +620,20 @@ fn ctpop() {
}
#[test]
+fn ctlz() {
+ check_number(
+ r#"
+ extern "rust-intrinsic" {
+ pub fn ctlz<T: Copy>(x: T) -> T;
+ }
+
+ const GOAL: u8 = ctlz(0b0001_1100_u8);
+ "#,
+ 3,
+ );
+}
+
+#[test]
fn cttz() {
check_number(
r#"
@@ -375,3 +646,85 @@ fn cttz() {
3,
);
}
+
+#[test]
+fn rotate() {
+ check_number(
+ r#"
+ extern "rust-intrinsic" {
+ pub fn rotate_left<T: Copy>(x: T, y: T) -> T;
+ }
+
+ const GOAL: i64 = rotate_left(0xaa00000000006e1i64, 12);
+ "#,
+ 0x6e10aa,
+ );
+ check_number(
+ r#"
+ extern "rust-intrinsic" {
+ pub fn rotate_right<T: Copy>(x: T, y: T) -> T;
+ }
+
+ const GOAL: i64 = rotate_right(0x6e10aa, 12);
+ "#,
+ 0xaa00000000006e1,
+ );
+ check_number(
+ r#"
+ extern "rust-intrinsic" {
+ pub fn rotate_left<T: Copy>(x: T, y: T) -> T;
+ }
+
+ const GOAL: i8 = rotate_left(129, 2);
+ "#,
+ 6,
+ );
+ check_number(
+ r#"
+ extern "rust-intrinsic" {
+ pub fn rotate_right<T: Copy>(x: T, y: T) -> T;
+ }
+
+ const GOAL: i32 = rotate_right(10006016, 1020315);
+ "#,
+ 320192512,
+ );
+}
+
+#[test]
+fn simd() {
+ check_number(
+ r#"
+ pub struct i8x16(
+ i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,
+ );
+ extern "platform-intrinsic" {
+ pub fn simd_bitmask<T, U>(x: T) -> U;
+ }
+ const GOAL: u16 = simd_bitmask(i8x16(
+ 0, 1, 0, 0, 2, 255, 100, 0, 50, 0, 1, 1, 0, 0, 0, 0
+ ));
+ "#,
+ 0b0000110101110010,
+ );
+ check_number(
+ r#"
+ pub struct i8x16(
+ i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,
+ );
+ extern "platform-intrinsic" {
+ pub fn simd_lt<T, U>(x: T, y: T) -> U;
+ pub fn simd_bitmask<T, U>(x: T) -> U;
+ }
+ const GOAL: u16 = simd_bitmask(simd_lt::<i8x16, i8x16>(
+ i8x16(
+ -105, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
+ ),
+ i8x16(
+ -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
+ ),
+ ));
+ "#,
+ 0xFFFF,
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/db.rs b/src/tools/rust-analyzer/crates/hir-ty/src/db.rs
index 9dd810f84..9c96b5ab8 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/db.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/db.rs
@@ -77,8 +77,12 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
#[salsa::invoke(crate::consteval::const_eval_query)]
#[salsa::cycle(crate::consteval::const_eval_recover)]
- fn const_eval(&self, def: GeneralConstId, subst: Substitution)
- -> Result<Const, ConstEvalError>;
+ fn const_eval(
+ &self,
+ def: GeneralConstId,
+ subst: Substitution,
+ trait_env: Option<Arc<crate::TraitEnvironment>>,
+ ) -> Result<Const, ConstEvalError>;
#[salsa::invoke(crate::consteval::const_eval_static_query)]
#[salsa::cycle(crate::consteval::const_eval_static_recover)]
@@ -100,16 +104,28 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
&self,
def: AdtId,
subst: Substitution,
- krate: CrateId,
+ env: Arc<crate::TraitEnvironment>,
) -> Result<Arc<Layout>, LayoutError>;
#[salsa::invoke(crate::layout::layout_of_ty_query)]
#[salsa::cycle(crate::layout::layout_of_ty_recover)]
- fn layout_of_ty(&self, ty: Ty, krate: CrateId) -> Result<Arc<Layout>, LayoutError>;
+ fn layout_of_ty(
+ &self,
+ ty: Ty,
+ env: Arc<crate::TraitEnvironment>,
+ ) -> Result<Arc<Layout>, LayoutError>;
#[salsa::invoke(crate::layout::target_data_layout_query)]
fn target_data_layout(&self, krate: CrateId) -> Option<Arc<TargetDataLayout>>;
+ #[salsa::invoke(crate::method_resolution::lookup_impl_method_query)]
+ fn lookup_impl_method(
+ &self,
+ env: Arc<crate::TraitEnvironment>,
+ func: FunctionId,
+ fn_subst: Substitution,
+ ) -> (FunctionId, Substitution);
+
#[salsa::invoke(crate::lower::callable_item_sig)]
fn callable_item_signature(&self, def: CallableDefId) -> PolyFnSig;
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics.rs
index 4b147b997..ef43ed5c4 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics.rs
@@ -5,7 +5,7 @@ mod unsafe_check;
mod decl_check;
pub use crate::diagnostics::{
- decl_check::{incorrect_case, IncorrectCase},
+ decl_check::{incorrect_case, CaseType, IncorrectCase},
expr::{
record_literal_missing_fields, record_pattern_missing_fields, BodyValidationDiagnostic,
},
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs
index 1233469b9..a94a962c1 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs
@@ -14,13 +14,12 @@ mod case_conv;
use std::fmt;
-use base_db::CrateId;
use hir_def::{
data::adt::VariantData,
hir::{Pat, PatId},
src::HasSource,
- AdtId, AttrDefId, ConstId, EnumId, FunctionId, ItemContainerId, Lookup, ModuleDefId, StaticId,
- StructId,
+ AdtId, AttrDefId, ConstId, DefWithBodyId, EnumId, EnumVariantId, FunctionId, ItemContainerId,
+ Lookup, ModuleDefId, StaticId, StructId,
};
use hir_expand::{
name::{AsName, Name},
@@ -44,24 +43,20 @@ mod allow {
pub(super) const NON_CAMEL_CASE_TYPES: &str = "non_camel_case_types";
}
-pub fn incorrect_case(
- db: &dyn HirDatabase,
- krate: CrateId,
- owner: ModuleDefId,
-) -> Vec<IncorrectCase> {
+pub fn incorrect_case(db: &dyn HirDatabase, owner: ModuleDefId) -> Vec<IncorrectCase> {
let _p = profile::span("validate_module_item");
- let mut validator = DeclValidator::new(db, krate);
+ let mut validator = DeclValidator::new(db);
validator.validate_item(owner);
validator.sink
}
#[derive(Debug)]
pub enum CaseType {
- // `some_var`
+ /// `some_var`
LowerSnakeCase,
- // `SOME_CONST`
+ /// `SOME_CONST`
UpperSnakeCase,
- // `SomeStruct`
+ /// `SomeStruct`
UpperCamelCase,
}
@@ -120,7 +115,6 @@ pub struct IncorrectCase {
pub(super) struct DeclValidator<'a> {
db: &'a dyn HirDatabase,
- krate: CrateId,
pub(super) sink: Vec<IncorrectCase>,
}
@@ -132,8 +126,8 @@ struct Replacement {
}
impl<'a> DeclValidator<'a> {
- pub(super) fn new(db: &'a dyn HirDatabase, krate: CrateId) -> DeclValidator<'a> {
- DeclValidator { db, krate, sink: Vec::new() }
+ pub(super) fn new(db: &'a dyn HirDatabase) -> DeclValidator<'a> {
+ DeclValidator { db, sink: Vec::new() }
}
pub(super) fn validate_item(&mut self, item: ModuleDefId) {
@@ -181,6 +175,8 @@ impl<'a> DeclValidator<'a> {
AttrDefId::TraitAliasId(taid) => Some(taid.lookup(self.db.upcast()).container.into()),
AttrDefId::ImplId(iid) => Some(iid.lookup(self.db.upcast()).container.into()),
AttrDefId::ExternBlockId(id) => Some(id.lookup(self.db.upcast()).container.into()),
+ AttrDefId::ExternCrateId(id) => Some(id.lookup(self.db.upcast()).container.into()),
+ AttrDefId::UseId(id) => Some(id.lookup(self.db.upcast()).container.into()),
// These warnings should not explore macro definitions at all
AttrDefId::MacroId(_) => None,
AttrDefId::AdtId(aid) => match aid {
@@ -194,8 +190,7 @@ impl<'a> DeclValidator<'a> {
AttrDefId::TypeAliasId(_) => None,
AttrDefId::GenericParamId(_) => None,
}
- .map(|mid| self.allowed(mid, allow_name, true))
- .unwrap_or(false)
+ .is_some_and(|mid| self.allowed(mid, allow_name, true))
}
fn validate_func(&mut self, func: FunctionId) {
@@ -205,17 +200,7 @@ impl<'a> DeclValidator<'a> {
return;
}
- let body = self.db.body(func.into());
-
- // Recursively validate inner scope items, such as static variables and constants.
- for (_, block_def_map) in body.blocks(self.db.upcast()) {
- for (_, module) in block_def_map.modules() {
- for def_id in module.scope.declarations() {
- let mut validator = DeclValidator::new(self.db, self.krate);
- validator.validate_item(def_id);
- }
- }
- }
+ self.validate_body_inner_items(func.into());
// Check whether non-snake case identifiers are allowed for this function.
if self.allowed(func.into(), allow::NON_SNAKE_CASE, false) {
@@ -230,6 +215,8 @@ impl<'a> DeclValidator<'a> {
expected_case: CaseType::LowerSnakeCase,
});
+ let body = self.db.body(func.into());
+
// Check the patterns inside the function body.
// This includes function parameters.
let pats_replacements = body
@@ -495,6 +482,11 @@ impl<'a> DeclValidator<'a> {
fn validate_enum(&mut self, enum_id: EnumId) {
let data = self.db.enum_data(enum_id);
+ for (local_id, _) in data.variants.iter() {
+ let variant_id = EnumVariantId { parent: enum_id, local_id };
+ self.validate_body_inner_items(variant_id.into());
+ }
+
// Check whether non-camel case names are allowed for this enum.
if self.allowed(enum_id.into(), allow::NON_CAMEL_CASE_TYPES, false) {
return;
@@ -511,13 +503,11 @@ impl<'a> DeclValidator<'a> {
// Check the field names.
let enum_fields_replacements = data
.variants
- .iter()
- .filter_map(|(_, variant)| {
+ .values()
+ .filter_map(|variant| {
Some(Replacement {
current_name: variant.name.clone(),
- suggested_text: to_camel_case(
- &variant.name.display(self.db.upcast()).to_string(),
- )?,
+ suggested_text: to_camel_case(&variant.name.to_smol_str())?,
expected_case: CaseType::UpperCamelCase,
})
})
@@ -621,6 +611,8 @@ impl<'a> DeclValidator<'a> {
fn validate_const(&mut self, const_id: ConstId) {
let data = self.db.const_data(const_id);
+ self.validate_body_inner_items(const_id.into());
+
if self.allowed(const_id.into(), allow::NON_UPPER_CASE_GLOBAL, false) {
return;
}
@@ -630,7 +622,7 @@ impl<'a> DeclValidator<'a> {
None => return,
};
- let const_name = name.display(self.db.upcast()).to_string();
+ let const_name = name.to_smol_str();
let replacement = if let Some(new_name) = to_upper_snake_case(&const_name) {
Replacement {
current_name: name.clone(),
@@ -669,13 +661,15 @@ impl<'a> DeclValidator<'a> {
return;
}
+ self.validate_body_inner_items(static_id.into());
+
if self.allowed(static_id.into(), allow::NON_UPPER_CASE_GLOBAL, false) {
return;
}
let name = &data.name;
- let static_name = name.display(self.db.upcast()).to_string();
+ let static_name = name.to_smol_str();
let replacement = if let Some(new_name) = to_upper_snake_case(&static_name) {
Replacement {
current_name: name.clone(),
@@ -706,4 +700,17 @@ impl<'a> DeclValidator<'a> {
self.sink.push(diagnostic);
}
+
+ // FIXME: We don't currently validate names within `DefWithBodyId::InTypeConstId`.
+ /// Recursively validates inner scope items, such as static variables and constants.
+ fn validate_body_inner_items(&mut self, body_id: DefWithBodyId) {
+ let body = self.db.body(body_id);
+ for (_, block_def_map) in body.blocks(self.db.upcast()) {
+ for (_, module) in block_def_map.modules() {
+ for def_id in module.scope.declarations() {
+ self.validate_item(def_id);
+ }
+ }
+ }
+ }
}
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/display.rs b/src/tools/rust-analyzer/crates/hir-ty/src/display.rs
index c1df24d17..1b4ee4613 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/display.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/display.rs
@@ -29,6 +29,7 @@ use itertools::Itertools;
use la_arena::ArenaMap;
use smallvec::SmallVec;
use stdx::never;
+use triomphe::Arc;
use crate::{
consteval::try_const_usize,
@@ -43,26 +44,19 @@ use crate::{
AdtId, AliasEq, AliasTy, Binders, CallableDefId, CallableSig, Const, ConstScalar, ConstValue,
DomainGoal, GenericArg, ImplTraitId, Interner, Lifetime, LifetimeData, LifetimeOutlives,
MemoryMap, Mutability, OpaqueTy, ProjectionTy, ProjectionTyExt, QuantifiedWhereClause, Scalar,
- Substitution, TraitRef, TraitRefExt, Ty, TyExt, WhereClause,
+ Substitution, TraitEnvironment, TraitRef, TraitRefExt, Ty, TyExt, WhereClause,
};
pub trait HirWrite: fmt::Write {
- fn start_location_link(&mut self, location: ModuleDefId);
- fn end_location_link(&mut self);
+ fn start_location_link(&mut self, _location: ModuleDefId) {}
+ fn end_location_link(&mut self) {}
}
// String will ignore link metadata
-impl HirWrite for String {
- fn start_location_link(&mut self, _: ModuleDefId) {}
-
- fn end_location_link(&mut self) {}
-}
+impl HirWrite for String {}
// `core::Formatter` will ignore metadata
-impl HirWrite for fmt::Formatter<'_> {
- fn start_location_link(&mut self, _: ModuleDefId) {}
- fn end_location_link(&mut self) {}
-}
+impl HirWrite for fmt::Formatter<'_> {}
pub struct HirFormatter<'a> {
pub db: &'a dyn HirDatabase,
@@ -192,7 +186,7 @@ pub trait HirDisplay {
}
}
-impl<'a> HirFormatter<'a> {
+impl HirFormatter<'_> {
pub fn write_joined<T: HirDisplay>(
&mut self,
iter: impl IntoIterator<Item = T>,
@@ -342,7 +336,7 @@ impl<T: HirDisplay> HirDisplayWrapper<'_, T> {
}
}
-impl<'a, T> fmt::Display for HirDisplayWrapper<'a, T>
+impl<T> fmt::Display for HirDisplayWrapper<'_, T>
where
T: HirDisplay,
{
@@ -360,7 +354,7 @@ where
const TYPE_HINT_TRUNCATION: &str = "…";
-impl<T: HirDisplay> HirDisplay for &'_ T {
+impl<T: HirDisplay> HirDisplay for &T {
fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
HirDisplay::hir_fmt(*self, f)
}
@@ -446,28 +440,6 @@ impl HirDisplay for Const {
}
}
-pub struct HexifiedConst(pub Const);
-
-impl HirDisplay for HexifiedConst {
- fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
- let data = &self.0.data(Interner);
- if let TyKind::Scalar(s) = data.ty.kind(Interner) {
- if matches!(s, Scalar::Int(_) | Scalar::Uint(_)) {
- if let ConstValue::Concrete(c) = &data.value {
- if let ConstScalar::Bytes(b, m) = &c.interned {
- let value = u128::from_le_bytes(pad16(b, false));
- if value >= 10 {
- render_const_scalar(f, &b, m, &data.ty)?;
- return write!(f, " ({:#X})", value);
- }
- }
- }
- }
- }
- self.0.hir_fmt(f)
- }
-}
-
fn render_const_scalar(
f: &mut HirFormatter<'_>,
b: &[u8],
@@ -476,33 +448,35 @@ fn render_const_scalar(
) -> Result<(), HirDisplayError> {
// FIXME: We need to get krate from the final callers of the hir display
// infrastructure and have it here as a field on `f`.
- let krate = *f.db.crate_graph().crates_in_topological_order().last().unwrap();
+ let trait_env = Arc::new(TraitEnvironment::empty(
+ *f.db.crate_graph().crates_in_topological_order().last().unwrap(),
+ ));
match ty.kind(Interner) {
TyKind::Scalar(s) => match s {
Scalar::Bool => write!(f, "{}", if b[0] == 0 { false } else { true }),
Scalar::Char => {
- let x = u128::from_le_bytes(pad16(b, false)) as u32;
- let Ok(c) = char::try_from(x) else {
+ let it = u128::from_le_bytes(pad16(b, false)) as u32;
+ let Ok(c) = char::try_from(it) else {
return f.write_str("<unicode-error>");
};
write!(f, "{c:?}")
}
Scalar::Int(_) => {
- let x = i128::from_le_bytes(pad16(b, true));
- write!(f, "{x}")
+ let it = i128::from_le_bytes(pad16(b, true));
+ write!(f, "{it}")
}
Scalar::Uint(_) => {
- let x = u128::from_le_bytes(pad16(b, false));
- write!(f, "{x}")
+ let it = u128::from_le_bytes(pad16(b, false));
+ write!(f, "{it}")
}
Scalar::Float(fl) => match fl {
chalk_ir::FloatTy::F32 => {
- let x = f32::from_le_bytes(b.try_into().unwrap());
- write!(f, "{x:?}")
+ let it = f32::from_le_bytes(b.try_into().unwrap());
+ write!(f, "{it:?}")
}
chalk_ir::FloatTy::F64 => {
- let x = f64::from_le_bytes(b.try_into().unwrap());
- write!(f, "{x:?}")
+ let it = f64::from_le_bytes(b.try_into().unwrap());
+ write!(f, "{it:?}")
}
},
},
@@ -519,7 +493,7 @@ fn render_const_scalar(
TyKind::Slice(ty) => {
let addr = usize::from_le_bytes(b[0..b.len() / 2].try_into().unwrap());
let count = usize::from_le_bytes(b[b.len() / 2..].try_into().unwrap());
- let Ok(layout) = f.db.layout_of_ty(ty.clone(), krate) else {
+ let Ok(layout) = f.db.layout_of_ty(ty.clone(), trait_env) else {
return f.write_str("<layout-error>");
};
let size_one = layout.size.bytes_usize();
@@ -545,7 +519,7 @@ fn render_const_scalar(
let Ok(t) = memory_map.vtable.ty(ty_id) else {
return f.write_str("<ty-missing-in-vtable-map>");
};
- let Ok(layout) = f.db.layout_of_ty(t.clone(), krate) else {
+ let Ok(layout) = f.db.layout_of_ty(t.clone(), trait_env) else {
return f.write_str("<layout-error>");
};
let size = layout.size.bytes_usize();
@@ -577,7 +551,7 @@ fn render_const_scalar(
return f.write_str("<layout-error>");
}
});
- let Ok(layout) = f.db.layout_of_ty(t.clone(), krate) else {
+ let Ok(layout) = f.db.layout_of_ty(t.clone(), trait_env) else {
return f.write_str("<layout-error>");
};
let size = layout.size.bytes_usize();
@@ -589,7 +563,7 @@ fn render_const_scalar(
}
},
TyKind::Tuple(_, subst) => {
- let Ok(layout) = f.db.layout_of_ty(ty.clone(), krate) else {
+ let Ok(layout) = f.db.layout_of_ty(ty.clone(), trait_env.clone()) else {
return f.write_str("<layout-error>");
};
f.write_str("(")?;
@@ -602,7 +576,7 @@ fn render_const_scalar(
}
let ty = ty.assert_ty_ref(Interner); // Tuple only has type argument
let offset = layout.fields.offset(id).bytes_usize();
- let Ok(layout) = f.db.layout_of_ty(ty.clone(), krate) else {
+ let Ok(layout) = f.db.layout_of_ty(ty.clone(), trait_env.clone()) else {
f.write_str("<layout-error>")?;
continue;
};
@@ -612,7 +586,7 @@ fn render_const_scalar(
f.write_str(")")
}
TyKind::Adt(adt, subst) => {
- let Ok(layout) = f.db.layout_of_adt(adt.0, subst.clone(), krate) else {
+ let Ok(layout) = f.db.layout_of_adt(adt.0, subst.clone(), trait_env.clone()) else {
return f.write_str("<layout-error>");
};
match adt.0 {
@@ -624,7 +598,7 @@ fn render_const_scalar(
&data.variant_data,
f,
&field_types,
- adt.0.module(f.db.upcast()).krate(),
+ f.db.trait_environment(adt.0.into()),
&layout,
subst,
b,
@@ -636,7 +610,8 @@ fn render_const_scalar(
}
hir_def::AdtId::EnumId(e) => {
let Some((var_id, var_layout)) =
- detect_variant_from_bytes(&layout, f.db, krate, b, e) else {
+ detect_variant_from_bytes(&layout, f.db, trait_env.clone(), b, e)
+ else {
return f.write_str("<failed-to-detect-variant>");
};
let data = &f.db.enum_data(e).variants[var_id];
@@ -647,7 +622,7 @@ fn render_const_scalar(
&data.variant_data,
f,
&field_types,
- adt.0.module(f.db.upcast()).krate(),
+ f.db.trait_environment(adt.0.into()),
&var_layout,
subst,
b,
@@ -658,15 +633,15 @@ fn render_const_scalar(
}
TyKind::FnDef(..) => ty.hir_fmt(f),
TyKind::Function(_) | TyKind::Raw(_, _) => {
- let x = u128::from_le_bytes(pad16(b, false));
- write!(f, "{:#X} as ", x)?;
+ let it = u128::from_le_bytes(pad16(b, false));
+ write!(f, "{:#X} as ", it)?;
ty.hir_fmt(f)
}
TyKind::Array(ty, len) => {
let Some(len) = try_const_usize(f.db, len) else {
return f.write_str("<unknown-array-len>");
};
- let Ok(layout) = f.db.layout_of_ty(ty.clone(), krate) else {
+ let Ok(layout) = f.db.layout_of_ty(ty.clone(), trait_env) else {
return f.write_str("<layout-error>");
};
let size_one = layout.size.bytes_usize();
@@ -705,7 +680,7 @@ fn render_variant_after_name(
data: &VariantData,
f: &mut HirFormatter<'_>,
field_types: &ArenaMap<LocalFieldId, Binders<Ty>>,
- krate: CrateId,
+ trait_env: Arc<TraitEnvironment>,
layout: &Layout,
subst: &Substitution,
b: &[u8],
@@ -716,7 +691,7 @@ fn render_variant_after_name(
let render_field = |f: &mut HirFormatter<'_>, id: LocalFieldId| {
let offset = layout.fields.offset(u32::from(id.into_raw()) as usize).bytes_usize();
let ty = field_types[id].clone().substitute(Interner, subst);
- let Ok(layout) = f.db.layout_of_ty(ty.clone(), krate) else {
+ let Ok(layout) = f.db.layout_of_ty(ty.clone(), trait_env.clone()) else {
return f.write_str("<layout-error>");
};
let size = layout.size.bytes_usize();
@@ -735,7 +710,7 @@ fn render_variant_after_name(
}
write!(f, " }}")?;
} else {
- let mut it = it.map(|x| x.0);
+ let mut it = it.map(|it| it.0);
write!(f, "(")?;
if let Some(id) = it.next() {
render_field(f, id)?;
@@ -903,6 +878,13 @@ impl HirDisplay for Ty {
TyKind::FnDef(def, parameters) => {
let def = from_chalk(db, *def);
let sig = db.callable_item_signature(def).substitute(Interner, parameters);
+
+ if f.display_target.is_source_code() {
+ // `FnDef` is anonymous and there's no surface syntax for it. Show it as a
+ // function pointer type.
+ return sig.hir_fmt(f);
+ }
+
f.start_location_link(def.into());
match def {
CallableDefId::FunctionId(ff) => {
@@ -1277,19 +1259,20 @@ fn hir_fmt_generics(
i: usize,
parameters: &Substitution,
) -> bool {
- if parameter.ty(Interner).map(|x| x.kind(Interner)) == Some(&TyKind::Error)
+ if parameter.ty(Interner).map(|it| it.kind(Interner))
+ == Some(&TyKind::Error)
{
return true;
}
if let Some(ConstValue::Concrete(c)) =
- parameter.constant(Interner).map(|x| &x.data(Interner).value)
+ parameter.constant(Interner).map(|it| &it.data(Interner).value)
{
if c.interned == ConstScalar::Unknown {
return true;
}
}
let default_parameter = match default_parameters.get(i) {
- Some(x) => x,
+ Some(it) => it,
None => return true,
};
let actual_default =
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs
index 1ac0837b5..b4915dbf0 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs
@@ -13,6 +13,15 @@
//! to certain types. To record this, we use the union-find implementation from
//! the `ena` crate, which is extracted from rustc.
+mod cast;
+pub(crate) mod closure;
+mod coerce;
+mod expr;
+mod mutability;
+mod pat;
+mod path;
+pub(crate) mod unify;
+
use std::{convert::identity, ops::Index};
use chalk_ir::{
@@ -60,15 +69,8 @@ pub use coerce::could_coerce;
#[allow(unreachable_pub)]
pub use unify::could_unify;
-pub(crate) use self::closure::{CaptureKind, CapturedItem, CapturedItemWithoutTy};
-
-pub(crate) mod unify;
-mod path;
-mod expr;
-mod pat;
-mod coerce;
-pub(crate) mod closure;
-mod mutability;
+use cast::CastCheck;
+pub(crate) use closure::{CaptureKind, CapturedItem, CapturedItemWithoutTy};
/// The entry point of type inference.
pub(crate) fn infer_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Arc<InferenceResult> {
@@ -290,7 +292,7 @@ impl Default for InternedStandardTypes {
/// ```
///
/// Note that for a struct, the 'deep' unsizing of the struct is not recorded.
-/// E.g., `struct Foo<T> { x: T }` we can coerce &Foo<[i32; 4]> to &Foo<[i32]>
+/// E.g., `struct Foo<T> { it: T }` we can coerce &Foo<[i32; 4]> to &Foo<[i32]>
/// The autoderef and -ref are the same as in the above example, but the type
/// stored in `unsize` is `Foo<[i32]>`, we don't store any further detail about
/// the underlying conversions from `[i32; 4]` to `[i32]`.
@@ -508,6 +510,8 @@ pub(crate) struct InferenceContext<'a> {
diverges: Diverges,
breakables: Vec<BreakableContext>,
+ deferred_cast_checks: Vec<CastCheck>,
+
// fields related to closure capture
current_captures: Vec<CapturedItemWithoutTy>,
current_closure: Option<ClosureId>,
@@ -582,7 +586,8 @@ impl<'a> InferenceContext<'a> {
resolver,
diverges: Diverges::Maybe,
breakables: Vec::new(),
- current_captures: vec![],
+ deferred_cast_checks: Vec::new(),
+ current_captures: Vec::new(),
current_closure: None,
deferred_closures: FxHashMap::default(),
closure_dependencies: FxHashMap::default(),
@@ -594,7 +599,7 @@ impl<'a> InferenceContext<'a> {
// used this function for another workaround, mention it here. If you really need this function and believe that
// there is no problem in it being `pub(crate)`, remove this comment.
pub(crate) fn resolve_all(self) -> InferenceResult {
- let InferenceContext { mut table, mut result, .. } = self;
+ let InferenceContext { mut table, mut result, deferred_cast_checks, .. } = self;
// Destructure every single field so whenever new fields are added to `InferenceResult` we
// don't forget to handle them here.
let InferenceResult {
@@ -622,6 +627,13 @@ impl<'a> InferenceContext<'a> {
table.fallback_if_possible();
+ // Comment from rustc:
+ // Even though coercion casts provide type hints, we check casts after fallback for
+ // backwards compatibility. This makes fallback a stronger type hint than a cast coercion.
+ for cast in deferred_cast_checks {
+ cast.check(&mut table);
+ }
+
// FIXME resolve obligations as well (use Guidance if necessary)
table.resolve_obligations_as_possible();
@@ -1172,7 +1184,7 @@ impl<'a> InferenceContext<'a> {
unresolved: Option<usize>,
path: &ModPath,
) -> (Ty, Option<VariantId>) {
- let remaining = unresolved.map(|x| path.segments()[x..].len()).filter(|x| x > &0);
+ let remaining = unresolved.map(|it| path.segments()[it..].len()).filter(|it| it > &0);
match remaining {
None => {
let variant = ty.as_adt().and_then(|(adt_id, _)| match adt_id {
@@ -1232,7 +1244,9 @@ impl<'a> InferenceContext<'a> {
.as_function()?
.lookup(self.db.upcast())
.container
- else { return None };
+ else {
+ return None;
+ };
self.resolve_output_on(trait_)
}
@@ -1322,7 +1336,7 @@ impl Expectation {
/// The primary use case is where the expected type is a fat pointer,
/// like `&[isize]`. For example, consider the following statement:
///
- /// let x: &[isize] = &[1, 2, 3];
+ /// let it: &[isize] = &[1, 2, 3];
///
/// In this case, the expected type for the `&[1, 2, 3]` expression is
/// `&[isize]`. If however we were to say that `[1, 2, 3]` has the
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs
new file mode 100644
index 000000000..9e1c74b16
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs
@@ -0,0 +1,46 @@
+//! Type cast logic. Basically coercion + additional casts.
+
+use crate::{infer::unify::InferenceTable, Interner, Ty, TyExt, TyKind};
+
+#[derive(Clone, Debug)]
+pub(super) struct CastCheck {
+ expr_ty: Ty,
+ cast_ty: Ty,
+}
+
+impl CastCheck {
+ pub(super) fn new(expr_ty: Ty, cast_ty: Ty) -> Self {
+ Self { expr_ty, cast_ty }
+ }
+
+ pub(super) fn check(self, table: &mut InferenceTable<'_>) {
+ // FIXME: This function currently only implements the bits that influence the type
+ // inference. We should return the adjustments on success and report diagnostics on error.
+ let expr_ty = table.resolve_ty_shallow(&self.expr_ty);
+ let cast_ty = table.resolve_ty_shallow(&self.cast_ty);
+
+ if expr_ty.contains_unknown() || cast_ty.contains_unknown() {
+ return;
+ }
+
+ if table.coerce(&expr_ty, &cast_ty).is_ok() {
+ return;
+ }
+
+ if check_ref_to_ptr_cast(expr_ty, cast_ty, table) {
+ // Note that this type of cast is actually split into a coercion to a
+ // pointer type and a cast:
+ // &[T; N] -> *[T; N] -> *T
+ return;
+ }
+
+ // FIXME: Check other kinds of non-coercion casts and report error if any?
+ }
+}
+
+fn check_ref_to_ptr_cast(expr_ty: Ty, cast_ty: Ty, table: &mut InferenceTable<'_>) -> bool {
+ let Some((expr_inner_ty, _, _)) = expr_ty.as_reference() else { return false; };
+ let Some((cast_inner_ty, _)) = cast_ty.as_raw_ptr() else { return false; };
+ let TyKind::Array(expr_elt_ty, _) = expr_inner_ty.kind(Interner) else { return false; };
+ table.coerce(expr_elt_ty, cast_inner_ty).is_ok()
+}
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs
index ff64ae252..1781f6c58 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs
@@ -139,7 +139,7 @@ impl HirPlace {
) -> CaptureKind {
match current_capture {
CaptureKind::ByRef(BorrowKind::Mut { .. }) => {
- if self.projections[len..].iter().any(|x| *x == ProjectionElem::Deref) {
+ if self.projections[len..].iter().any(|it| *it == ProjectionElem::Deref) {
current_capture = CaptureKind::ByRef(BorrowKind::Unique);
}
}
@@ -199,7 +199,7 @@ impl CapturedItem {
.to_string(),
VariantData::Tuple(fields) => fields
.iter()
- .position(|x| x.0 == f.local_id)
+ .position(|it| it.0 == f.local_id)
.unwrap_or_default()
.to_string(),
VariantData::Unit => "[missing field]".to_string(),
@@ -439,10 +439,10 @@ impl InferenceContext<'_> {
}
fn walk_expr(&mut self, tgt_expr: ExprId) {
- if let Some(x) = self.result.expr_adjustments.get_mut(&tgt_expr) {
+ if let Some(it) = self.result.expr_adjustments.get_mut(&tgt_expr) {
// FIXME: this take is completely unneeded, and just is here to make borrow checker
// happy. Remove it if you can.
- let x_taken = mem::take(x);
+ let x_taken = mem::take(it);
self.walk_expr_with_adjust(tgt_expr, &x_taken);
*self.result.expr_adjustments.get_mut(&tgt_expr).unwrap() = x_taken;
} else {
@@ -488,10 +488,6 @@ impl InferenceContext<'_> {
self.consume_expr(*tail);
}
}
- Expr::While { condition, body, label: _ } => {
- self.consume_expr(*condition);
- self.consume_expr(*body);
- }
Expr::Call { callee, args, is_assignee_expr: _ } => {
self.consume_expr(*callee);
self.consume_exprs(args.iter().copied());
@@ -536,7 +532,7 @@ impl InferenceContext<'_> {
if let &Some(expr) = spread {
self.consume_expr(expr);
}
- self.consume_exprs(fields.iter().map(|x| x.expr));
+ self.consume_exprs(fields.iter().map(|it| it.expr));
}
Expr::Field { expr, name: _ } => self.select_from_expr(*expr),
Expr::UnaryOp { expr, op: UnaryOp::Deref } => {
@@ -548,7 +544,7 @@ impl InferenceContext<'_> {
} else if let Some((f, _)) = self.result.method_resolution(tgt_expr) {
let mutability = 'b: {
if let Some(deref_trait) =
- self.resolve_lang_item(LangItem::DerefMut).and_then(|x| x.as_trait())
+ self.resolve_lang_item(LangItem::DerefMut).and_then(|it| it.as_trait())
{
if let Some(deref_fn) =
self.db.trait_data(deref_trait).method_by_name(&name![deref_mut])
@@ -615,8 +611,8 @@ impl InferenceContext<'_> {
"We sort closures, so we should always have data for inner closures",
);
let mut cc = mem::take(&mut self.current_captures);
- cc.extend(captures.iter().filter(|x| self.is_upvar(&x.place)).map(|x| {
- CapturedItemWithoutTy { place: x.place.clone(), kind: x.kind, span: x.span }
+ cc.extend(captures.iter().filter(|it| self.is_upvar(&it.place)).map(|it| {
+ CapturedItemWithoutTy { place: it.place.clone(), kind: it.kind, span: it.span }
}));
self.current_captures = cc;
}
@@ -694,7 +690,7 @@ impl InferenceContext<'_> {
},
},
}
- if self.result.pat_adjustments.get(&p).map_or(false, |x| !x.is_empty()) {
+ if self.result.pat_adjustments.get(&p).map_or(false, |it| !it.is_empty()) {
for_mut = BorrowKind::Unique;
}
self.body.walk_pats_shallow(p, |p| self.walk_pat_inner(p, update_result, for_mut));
@@ -706,9 +702,9 @@ impl InferenceContext<'_> {
fn expr_ty_after_adjustments(&self, e: ExprId) -> Ty {
let mut ty = None;
- if let Some(x) = self.result.expr_adjustments.get(&e) {
- if let Some(x) = x.last() {
- ty = Some(x.target.clone());
+ if let Some(it) = self.result.expr_adjustments.get(&e) {
+ if let Some(it) = it.last() {
+ ty = Some(it.target.clone());
}
}
ty.unwrap_or_else(|| self.expr_ty(e))
@@ -727,7 +723,7 @@ impl InferenceContext<'_> {
// FIXME: We handle closure as a special case, since chalk consider every closure as copy. We
// should probably let chalk know which closures are copy, but I don't know how doing it
// without creating query cycles.
- return self.result.closure_info.get(id).map(|x| x.1 == FnTrait::Fn).unwrap_or(true);
+ return self.result.closure_info.get(id).map(|it| it.1 == FnTrait::Fn).unwrap_or(true);
}
self.table.resolve_completely(ty).is_copy(self.db, self.owner)
}
@@ -748,7 +744,7 @@ impl InferenceContext<'_> {
}
fn minimize_captures(&mut self) {
- self.current_captures.sort_by_key(|x| x.place.projections.len());
+ self.current_captures.sort_by_key(|it| it.place.projections.len());
let mut hash_map = HashMap::<HirPlace, usize>::new();
let result = mem::take(&mut self.current_captures);
for item in result {
@@ -759,7 +755,7 @@ impl InferenceContext<'_> {
break Some(*k);
}
match it.next() {
- Some(x) => lookup_place.projections.push(x.clone()),
+ Some(it) => lookup_place.projections.push(it.clone()),
None => break None,
}
};
@@ -780,7 +776,7 @@ impl InferenceContext<'_> {
}
fn consume_with_pat(&mut self, mut place: HirPlace, pat: PatId) {
- let cnt = self.result.pat_adjustments.get(&pat).map(|x| x.len()).unwrap_or_default();
+ let cnt = self.result.pat_adjustments.get(&pat).map(|it| it.len()).unwrap_or_default();
place.projections = place
.projections
.iter()
@@ -894,10 +890,10 @@ impl InferenceContext<'_> {
fn closure_kind(&self) -> FnTrait {
let mut r = FnTrait::Fn;
- for x in &self.current_captures {
+ for it in &self.current_captures {
r = cmp::min(
r,
- match &x.kind {
+ match &it.kind {
CaptureKind::ByRef(BorrowKind::Unique | BorrowKind::Mut { .. }) => {
FnTrait::FnMut
}
@@ -933,7 +929,7 @@ impl InferenceContext<'_> {
}
self.minimize_captures();
let result = mem::take(&mut self.current_captures);
- let captures = result.into_iter().map(|x| x.with_ty(self)).collect::<Vec<_>>();
+ let captures = result.into_iter().map(|it| it.with_ty(self)).collect::<Vec<_>>();
self.result.closure_info.insert(closure, (captures, closure_kind));
closure_kind
}
@@ -973,20 +969,20 @@ impl InferenceContext<'_> {
fn sort_closures(&mut self) -> Vec<(ClosureId, Vec<(Ty, Ty, Vec<Ty>, ExprId)>)> {
let mut deferred_closures = mem::take(&mut self.deferred_closures);
let mut dependents_count: FxHashMap<ClosureId, usize> =
- deferred_closures.keys().map(|x| (*x, 0)).collect();
+ deferred_closures.keys().map(|it| (*it, 0)).collect();
for (_, deps) in &self.closure_dependencies {
for dep in deps {
*dependents_count.entry(*dep).or_default() += 1;
}
}
let mut queue: Vec<_> =
- deferred_closures.keys().copied().filter(|x| dependents_count[x] == 0).collect();
+ deferred_closures.keys().copied().filter(|it| dependents_count[it] == 0).collect();
let mut result = vec![];
- while let Some(x) = queue.pop() {
- if let Some(d) = deferred_closures.remove(&x) {
- result.push((x, d));
+ while let Some(it) = queue.pop() {
+ if let Some(d) = deferred_closures.remove(&it) {
+ result.push((it, d));
}
- for dep in self.closure_dependencies.get(&x).into_iter().flat_map(|x| x.iter()) {
+ for dep in self.closure_dependencies.get(&it).into_iter().flat_map(|it| it.iter()) {
let cnt = dependents_count.get_mut(dep).unwrap();
*cnt -= 1;
if *cnt == 0 {
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs
index 05a476f63..8e7e62c49 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs
@@ -220,7 +220,7 @@ pub(crate) fn coerce(
Ok((adjustments, table.resolve_with_fallback(ty, &fallback)))
}
-impl<'a> InferenceContext<'a> {
+impl InferenceContext<'_> {
/// Unify two types, but may coerce the first one to the second one
/// using "implicit coercion rules" if needed.
pub(super) fn coerce(
@@ -239,7 +239,7 @@ impl<'a> InferenceContext<'a> {
}
}
-impl<'a> InferenceTable<'a> {
+impl InferenceTable<'_> {
/// Unify two types, but may coerce the first one to the second one
/// using "implicit coercion rules" if needed.
pub(crate) fn coerce(
@@ -377,7 +377,7 @@ impl<'a> InferenceTable<'a> {
let snapshot = self.snapshot();
- let mut autoderef = Autoderef::new(self, from_ty.clone());
+ let mut autoderef = Autoderef::new(self, from_ty.clone(), false);
let mut first_error = None;
let mut found = None;
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs
index 194471f00..8cbdae625 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs
@@ -46,11 +46,11 @@ use crate::{
};
use super::{
- coerce::auto_deref_adjust_steps, find_breakable, BreakableContext, Diverges, Expectation,
- InferenceContext, InferenceDiagnostic, TypeMismatch,
+ cast::CastCheck, coerce::auto_deref_adjust_steps, find_breakable, BreakableContext, Diverges,
+ Expectation, InferenceContext, InferenceDiagnostic, TypeMismatch,
};
-impl<'a> InferenceContext<'a> {
+impl InferenceContext<'_> {
pub(crate) fn infer_expr(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty {
let ty = self.infer_expr_inner(tgt_expr, expected);
if let Some(expected_ty) = expected.only_has_type(&mut self.table) {
@@ -198,19 +198,6 @@ impl<'a> InferenceContext<'a> {
None => self.result.standard_types.never.clone(),
}
}
- &Expr::While { condition, body, label } => {
- self.with_breakable_ctx(BreakableKind::Loop, None, label, |this| {
- this.infer_expr(
- condition,
- &Expectation::HasType(this.result.standard_types.bool_.clone()),
- );
- this.infer_expr(body, &Expectation::HasType(TyBuilder::unit()));
- });
-
- // the body may not run, so it diverging doesn't mean we diverge
- self.diverges = Diverges::Maybe;
- TyBuilder::unit()
- }
Expr::Closure { body, args, ret_type, arg_types, closure_kind, capture_by: _ } => {
assert_eq!(args.len(), arg_types.len());
@@ -316,7 +303,7 @@ impl<'a> InferenceContext<'a> {
}
Expr::Call { callee, args, .. } => {
let callee_ty = self.infer_expr(*callee, &Expectation::none());
- let mut derefs = Autoderef::new(&mut self.table, callee_ty.clone());
+ let mut derefs = Autoderef::new(&mut self.table, callee_ty.clone(), false);
let (res, derefed_callee) = 'b: {
// manual loop to be able to access `derefs.table`
while let Some((callee_deref_ty, _)) = derefs.next() {
@@ -574,16 +561,8 @@ impl<'a> InferenceContext<'a> {
}
Expr::Cast { expr, type_ref } => {
let cast_ty = self.make_ty(type_ref);
- // FIXME: propagate the "castable to" expectation
- let inner_ty = self.infer_expr_no_expect(*expr);
- match (inner_ty.kind(Interner), cast_ty.kind(Interner)) {
- (TyKind::Ref(_, _, inner), TyKind::Raw(_, cast)) => {
- // FIXME: record invalid cast diagnostic in case of mismatch
- self.unify(inner, cast);
- }
- // FIXME check the other kinds of cast...
- _ => (),
- }
+ let expr_ty = self.infer_expr(*expr, &Expectation::Castable(cast_ty.clone()));
+ self.deferred_cast_checks.push(CastCheck::new(expr_ty, cast_ty.clone()));
cast_ty
}
Expr::Ref { expr, rawness, mutability } => {
@@ -928,7 +907,7 @@ impl<'a> InferenceContext<'a> {
if let TyKind::Ref(Mutability::Mut, _, inner) = derefed_callee.kind(Interner) {
if adjustments
.last()
- .map(|x| matches!(x.kind, Adjust::Borrow(_)))
+ .map(|it| matches!(it.kind, Adjust::Borrow(_)))
.unwrap_or(true)
{
// prefer reborrow to move
@@ -1385,7 +1364,7 @@ impl<'a> InferenceContext<'a> {
receiver_ty: &Ty,
name: &Name,
) -> Option<(Ty, Option<FieldId>, Vec<Adjustment>, bool)> {
- let mut autoderef = Autoderef::new(&mut self.table, receiver_ty.clone());
+ let mut autoderef = Autoderef::new(&mut self.table, receiver_ty.clone(), false);
let mut private_field = None;
let res = autoderef.by_ref().find_map(|(derefed_ty, _)| {
let (field_id, parameters) = match derefed_ty.kind(Interner) {
@@ -1449,6 +1428,13 @@ impl<'a> InferenceContext<'a> {
fn infer_field_access(&mut self, tgt_expr: ExprId, receiver: ExprId, name: &Name) -> Ty {
let receiver_ty = self.infer_expr_inner(receiver, &Expectation::none());
+
+ if name.is_missing() {
+ // Bail out early, don't even try to look up field. Also, we don't issue an unresolved
+ // field diagnostic because this is a syntax error rather than a semantic error.
+ return self.err_ty();
+ }
+
match self.lookup_field(&receiver_ty, name) {
Some((ty, field_id, adjustments, is_public)) => {
self.write_expr_adj(receiver, adjustments);
@@ -1585,7 +1571,7 @@ impl<'a> InferenceContext<'a> {
output: Ty,
inputs: Vec<Ty>,
) -> Vec<Ty> {
- if let Some(expected_ty) = expected_output.to_option(&mut self.table) {
+ if let Some(expected_ty) = expected_output.only_has_type(&mut self.table) {
self.table.fudge_inference(|table| {
if table.try_unify(&expected_ty, &output).is_ok() {
table.resolve_with_fallback(inputs, &|var, kind, _, _| match kind {
@@ -1658,6 +1644,7 @@ impl<'a> InferenceContext<'a> {
// the parameter to coerce to the expected type (for example in
// `coerce_unsize_expected_type_4`).
let param_ty = self.normalize_associated_types_in(param_ty);
+ let expected_ty = self.normalize_associated_types_in(expected_ty);
let expected = Expectation::rvalue_hint(self, expected_ty);
// infer with the expected type we have...
let ty = self.infer_expr_inner(arg, &expected);
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs
index 46f2e1d7d..396ca0044 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs
@@ -12,7 +12,7 @@ use crate::{lower::lower_to_chalk_mutability, Adjust, Adjustment, AutoBorrow, Ov
use super::InferenceContext;
-impl<'a> InferenceContext<'a> {
+impl InferenceContext<'_> {
pub(crate) fn infer_mut_body(&mut self) {
self.infer_mut_expr(self.body.body_expr, Mutability::Not);
}
@@ -69,16 +69,12 @@ impl<'a> InferenceContext<'a> {
self.infer_mut_expr(*tail, Mutability::Not);
}
}
- &Expr::While { condition: c, body, label: _ } => {
- self.infer_mut_expr(c, Mutability::Not);
- self.infer_mut_expr(body, Mutability::Not);
- }
- Expr::MethodCall { receiver: x, method_name: _, args, generic_args: _ }
- | Expr::Call { callee: x, args, is_assignee_expr: _ } => {
- self.infer_mut_not_expr_iter(args.iter().copied().chain(Some(*x)));
+ Expr::MethodCall { receiver: it, method_name: _, args, generic_args: _ }
+ | Expr::Call { callee: it, args, is_assignee_expr: _ } => {
+ self.infer_mut_not_expr_iter(args.iter().copied().chain(Some(*it)));
}
Expr::Match { expr, arms } => {
- let m = self.pat_iter_bound_mutability(arms.iter().map(|x| x.pat));
+ let m = self.pat_iter_bound_mutability(arms.iter().map(|it| it.pat));
self.infer_mut_expr(*expr, m);
for arm in arms.iter() {
self.infer_mut_expr(arm.expr, Mutability::Not);
@@ -96,7 +92,7 @@ impl<'a> InferenceContext<'a> {
}
}
Expr::RecordLit { path: _, fields, spread, ellipsis: _, is_assignee_expr: _ } => {
- self.infer_mut_not_expr_iter(fields.iter().map(|x| x.expr).chain(*spread))
+ self.infer_mut_not_expr_iter(fields.iter().map(|it| it.expr).chain(*spread))
}
&Expr::Index { base, index } => {
if mutability == Mutability::Mut {
@@ -204,8 +200,8 @@ impl<'a> InferenceContext<'a> {
}
/// Checks if the pat contains a `ref mut` binding. Such paths makes the context of bounded expressions
- /// mutable. For example in `let (ref mut x0, ref x1) = *x;` we need to use `DerefMut` for `*x` but in
- /// `let (ref x0, ref x1) = *x;` we should use `Deref`.
+ /// mutable. For example in `let (ref mut x0, ref x1) = *it;` we need to use `DerefMut` for `*it` but in
+ /// `let (ref x0, ref x1) = *it;` we should use `Deref`.
fn pat_bound_mutability(&self, pat: PatId) -> Mutability {
let mut r = Mutability::Not;
self.body.walk_bindings_in_pat(pat, |b| {
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/pat.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/pat.rs
index 2480f8bab..5da0ab76b 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/pat.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/pat.rs
@@ -56,7 +56,7 @@ impl PatLike for PatId {
}
}
-impl<'a> InferenceContext<'a> {
+impl InferenceContext<'_> {
/// Infers type for tuple struct pattern or its corresponding assignee expression.
///
/// Ellipses found in the original pattern or expression must be filtered out.
@@ -306,7 +306,7 @@ impl<'a> InferenceContext<'a> {
self.result
.pat_adjustments
.get(&pat)
- .and_then(|x| x.first())
+ .and_then(|it| it.first())
.unwrap_or(&self.result.type_of_pat[pat])
.clone()
}
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs
index e33d8f179..0fb71135b 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs
@@ -22,7 +22,7 @@ use crate::{
TraitEnvironment, Ty, TyBuilder, TyExt, TyKind, VariableKind,
};
-impl<'a> InferenceContext<'a> {
+impl InferenceContext<'_> {
pub(super) fn canonicalize<T: TypeFoldable<Interner> + HasInterner<Interner = Interner>>(
&mut self,
t: T,
@@ -91,7 +91,7 @@ pub(crate) fn unify(
let mut table = InferenceTable::new(db, env);
let vars = Substitution::from_iter(
Interner,
- tys.binders.iter(Interner).map(|x| match &x.kind {
+ tys.binders.iter(Interner).map(|it| match &it.kind {
chalk_ir::VariableKind::Ty(_) => {
GenericArgData::Ty(table.new_type_var()).intern(Interner)
}
@@ -252,7 +252,8 @@ impl<'a> InferenceTable<'a> {
// and registering an obligation. But it needs chalk support, so we handle the most basic
// case (a non associated const without generic parameters) manually.
if subst.len(Interner) == 0 {
- if let Ok(eval) = self.db.const_eval((*c_id).into(), subst.clone())
+ if let Ok(eval) =
+ self.db.const_eval((*c_id).into(), subst.clone(), None)
{
eval
} else {
@@ -547,7 +548,7 @@ impl<'a> InferenceTable<'a> {
table: &'a mut InferenceTable<'b>,
highest_known_var: InferenceVar,
}
- impl<'a, 'b> TypeFolder<Interner> for VarFudger<'a, 'b> {
+ impl TypeFolder<Interner> for VarFudger<'_, '_> {
fn as_dyn(&mut self) -> &mut dyn TypeFolder<Interner, Error = Self::Error> {
self
}
@@ -686,8 +687,8 @@ impl<'a> InferenceTable<'a> {
let mut arg_tys = vec![];
let arg_ty = TyBuilder::tuple(num_args)
- .fill(|x| {
- let arg = match x {
+ .fill(|it| {
+ let arg = match it {
ParamKind::Type => self.new_type_var(),
ParamKind::Const(ty) => {
never!("Tuple with const parameter");
@@ -753,7 +754,7 @@ impl<'a> InferenceTable<'a> {
{
fold_tys_and_consts(
ty,
- |x, _| match x {
+ |it, _| match it {
Either::Left(ty) => Either::Left(self.insert_type_vars_shallow(ty)),
Either::Right(c) => Either::Right(self.insert_const_vars_shallow(c)),
},
@@ -785,7 +786,7 @@ impl<'a> InferenceTable<'a> {
crate::ConstScalar::Unknown => self.new_const_var(data.ty.clone()),
// try to evaluate unevaluated const. Replace with new var if const eval failed.
crate::ConstScalar::UnevaluatedConst(id, subst) => {
- if let Ok(eval) = self.db.const_eval(*id, subst.clone()) {
+ if let Ok(eval) = self.db.const_eval(*id, subst.clone(), None) {
eval
} else {
self.new_const_var(data.ty.clone())
@@ -798,7 +799,7 @@ impl<'a> InferenceTable<'a> {
}
}
-impl<'a> fmt::Debug for InferenceTable<'a> {
+impl fmt::Debug for InferenceTable<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("InferenceTable").field("num_vars", &self.type_variable_table.len()).finish()
}
@@ -826,7 +827,7 @@ mod resolve {
pub(super) var_stack: &'a mut Vec<InferenceVar>,
pub(super) fallback: F,
}
- impl<'a, 'b, F> TypeFolder<Interner> for Resolver<'a, 'b, F>
+ impl<F> TypeFolder<Interner> for Resolver<'_, '_, F>
where
F: Fn(InferenceVar, VariableKind, GenericArg, DebruijnIndex) -> GenericArg,
{
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs
index 35d3407c1..b15339d44 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs
@@ -1,13 +1,12 @@
//! Compute the binary representation of a type
-use base_db::CrateId;
use chalk_ir::{AdtId, FloatTy, IntTy, TyKind, UintTy};
use hir_def::{
layout::{
Abi, FieldsShape, Integer, LayoutCalculator, LayoutS, Primitive, ReprOptions, Scalar, Size,
StructKind, TargetDataLayout, WrappingRange,
},
- LocalEnumVariantId, LocalFieldId,
+ LocalEnumVariantId, LocalFieldId, StructId,
};
use la_arena::{Idx, RawIdx};
use stdx::never;
@@ -15,7 +14,7 @@ use triomphe::Arc;
use crate::{
consteval::try_const_usize, db::HirDatabase, infer::normalize, layout::adt::struct_variant_idx,
- utils::ClosureSubst, Interner, Substitution, TraitEnvironment, Ty,
+ utils::ClosureSubst, Interner, ProjectionTy, Substitution, TraitEnvironment, Ty,
};
pub use self::{
@@ -24,8 +23,8 @@ pub use self::{
};
macro_rules! user_error {
- ($x: expr) => {
- return Err(LayoutError::UserError(format!($x)))
+ ($it: expr) => {
+ return Err(LayoutError::UserError(format!($it)))
};
}
@@ -61,7 +60,6 @@ pub enum LayoutError {
}
struct LayoutCx<'a> {
- krate: CrateId,
target: &'a TargetDataLayout,
}
@@ -77,18 +75,101 @@ impl<'a> LayoutCalculator for LayoutCx<'a> {
}
}
+// FIXME: move this to the `rustc_abi`.
+fn layout_of_simd_ty(
+ db: &dyn HirDatabase,
+ id: StructId,
+ subst: &Substitution,
+ env: Arc<TraitEnvironment>,
+ dl: &TargetDataLayout,
+) -> Result<Arc<Layout>, LayoutError> {
+ let fields = db.field_types(id.into());
+
+ // Supported SIMD vectors are homogeneous ADTs with at least one field:
+ //
+ // * #[repr(simd)] struct S(T, T, T, T);
+ // * #[repr(simd)] struct S { it: T, y: T, z: T, w: T }
+ // * #[repr(simd)] struct S([T; 4])
+ //
+ // where T is a primitive scalar (integer/float/pointer).
+
+ let f0_ty = match fields.iter().next() {
+ Some(it) => it.1.clone().substitute(Interner, subst),
+ None => {
+ user_error!("simd type with zero fields");
+ }
+ };
+
+ // The element type and number of elements of the SIMD vector
+ // are obtained from:
+ //
+ // * the element type and length of the single array field, if
+ // the first field is of array type, or
+ //
+ // * the homogeneous field type and the number of fields.
+ let (e_ty, e_len, is_array) = if let TyKind::Array(e_ty, _) = f0_ty.kind(Interner) {
+ // Extract the number of elements from the layout of the array field:
+ let FieldsShape::Array { count, .. } = db.layout_of_ty(f0_ty.clone(), env.clone())?.fields else {
+ user_error!("Array with non array layout");
+ };
+
+ (e_ty.clone(), count, true)
+ } else {
+ // First ADT field is not an array:
+ (f0_ty, fields.iter().count() as u64, false)
+ };
+
+ // Compute the ABI of the element type:
+ let e_ly = db.layout_of_ty(e_ty, env.clone())?;
+ let Abi::Scalar(e_abi) = e_ly.abi else {
+ user_error!("simd type with inner non scalar type");
+ };
+
+ // Compute the size and alignment of the vector:
+ let size = e_ly.size.checked_mul(e_len, dl).ok_or(LayoutError::SizeOverflow)?;
+ let align = dl.vector_align(size);
+ let size = size.align_to(align.abi);
+
+ // Compute the placement of the vector fields:
+ let fields = if is_array {
+ FieldsShape::Arbitrary { offsets: [Size::ZERO].into(), memory_index: [0].into() }
+ } else {
+ FieldsShape::Array { stride: e_ly.size, count: e_len }
+ };
+
+ Ok(Arc::new(Layout {
+ variants: Variants::Single { index: struct_variant_idx() },
+ fields,
+ abi: Abi::Vector { element: e_abi, count: e_len },
+ largest_niche: e_ly.largest_niche,
+ size,
+ align,
+ }))
+}
+
pub fn layout_of_ty_query(
db: &dyn HirDatabase,
ty: Ty,
- krate: CrateId,
+ trait_env: Arc<TraitEnvironment>,
) -> Result<Arc<Layout>, LayoutError> {
- let Some(target) = db.target_data_layout(krate) else { return Err(LayoutError::TargetLayoutNotAvailable) };
- let cx = LayoutCx { krate, target: &target };
+ let krate = trait_env.krate;
+ let Some(target) = db.target_data_layout(krate) else {
+ return Err(LayoutError::TargetLayoutNotAvailable);
+ };
+ let cx = LayoutCx { target: &target };
let dl = &*cx.current_data_layout();
- let trait_env = Arc::new(TraitEnvironment::empty(krate));
- let ty = normalize(db, trait_env, ty.clone());
+ let ty = normalize(db, trait_env.clone(), ty.clone());
let result = match ty.kind(Interner) {
- TyKind::Adt(AdtId(def), subst) => return db.layout_of_adt(*def, subst.clone(), krate),
+ TyKind::Adt(AdtId(def), subst) => {
+ if let hir_def::AdtId::StructId(s) = def {
+ let data = db.struct_data(*s);
+ let repr = data.repr.unwrap_or_default();
+ if repr.simd() {
+ return layout_of_simd_ty(db, *s, subst, trait_env.clone(), &target);
+ }
+ };
+ return db.layout_of_adt(*def, subst.clone(), trait_env.clone());
+ }
TyKind::Scalar(s) => match s {
chalk_ir::Scalar::Bool => Layout::scalar(
dl,
@@ -145,9 +226,9 @@ pub fn layout_of_ty_query(
let fields = tys
.iter(Interner)
- .map(|k| db.layout_of_ty(k.assert_ty_ref(Interner).clone(), krate))
+ .map(|k| db.layout_of_ty(k.assert_ty_ref(Interner).clone(), trait_env.clone()))
.collect::<Result<Vec<_>, _>>()?;
- let fields = fields.iter().map(|x| &**x).collect::<Vec<_>>();
+ let fields = fields.iter().map(|it| &**it).collect::<Vec<_>>();
let fields = fields.iter().collect::<Vec<_>>();
cx.univariant(dl, &fields, &ReprOptions::default(), kind).ok_or(LayoutError::Unknown)?
}
@@ -155,7 +236,7 @@ pub fn layout_of_ty_query(
let count = try_const_usize(db, &count).ok_or(LayoutError::UserError(
"unevaluated or mistyped const generic parameter".to_string(),
))? as u64;
- let element = db.layout_of_ty(element.clone(), krate)?;
+ let element = db.layout_of_ty(element.clone(), trait_env.clone())?;
let size = element.size.checked_mul(count, dl).ok_or(LayoutError::SizeOverflow)?;
let abi = if count != 0 && matches!(element.abi, Abi::Uninhabited) {
@@ -176,7 +257,7 @@ pub fn layout_of_ty_query(
}
}
TyKind::Slice(element) => {
- let element = db.layout_of_ty(element.clone(), krate)?;
+ let element = db.layout_of_ty(element.clone(), trait_env.clone())?;
Layout {
variants: Variants::Single { index: struct_variant_idx() },
fields: FieldsShape::Array { stride: element.size, count: 0 },
@@ -198,7 +279,15 @@ pub fn layout_of_ty_query(
// return Ok(tcx.mk_layout(LayoutS::scalar(cx, data_ptr)));
// }
- let unsized_part = struct_tail_erasing_lifetimes(db, pointee.clone());
+ let mut unsized_part = struct_tail_erasing_lifetimes(db, pointee.clone());
+ if let TyKind::AssociatedType(id, subst) = unsized_part.kind(Interner) {
+ unsized_part = TyKind::Alias(chalk_ir::AliasTy::Projection(ProjectionTy {
+ associated_ty_id: *id,
+ substitution: subst.clone(),
+ }))
+ .intern(Interner);
+ }
+ unsized_part = normalize(db, trait_env.clone(), unsized_part);
let metadata = match unsized_part.kind(Interner) {
TyKind::Slice(_) | TyKind::Str => {
scalar_unit(dl, Primitive::Int(dl.ptr_sized_integer(), false))
@@ -252,7 +341,7 @@ pub fn layout_of_ty_query(
match impl_trait_id {
crate::ImplTraitId::ReturnTypeImplTrait(func, idx) => {
let infer = db.infer(func.into());
- return db.layout_of_ty(infer.type_of_rpit[idx].clone(), krate);
+ return db.layout_of_ty(infer.type_of_rpit[idx].clone(), trait_env.clone());
}
crate::ImplTraitId::AsyncBlockTypeImplTrait(_, _) => {
return Err(LayoutError::NotImplemented)
@@ -265,14 +354,14 @@ pub fn layout_of_ty_query(
let (captures, _) = infer.closure_info(c);
let fields = captures
.iter()
- .map(|x| {
+ .map(|it| {
db.layout_of_ty(
- x.ty.clone().substitute(Interner, ClosureSubst(subst).parent_subst()),
- krate,
+ it.ty.clone().substitute(Interner, ClosureSubst(subst).parent_subst()),
+ trait_env.clone(),
)
})
.collect::<Result<Vec<_>, _>>()?;
- let fields = fields.iter().map(|x| &**x).collect::<Vec<_>>();
+ let fields = fields.iter().map(|it| &**it).collect::<Vec<_>>();
let fields = fields.iter().collect::<Vec<_>>();
cx.univariant(dl, &fields, &ReprOptions::default(), StructKind::AlwaysSized)
.ok_or(LayoutError::Unknown)?
@@ -281,8 +370,16 @@ pub fn layout_of_ty_query(
return Err(LayoutError::NotImplemented)
}
TyKind::Error => return Err(LayoutError::HasErrorType),
- TyKind::AssociatedType(_, _)
- | TyKind::Alias(_)
+ TyKind::AssociatedType(id, subst) => {
+ // Try again with `TyKind::Alias` to normalize the associated type.
+ let ty = TyKind::Alias(chalk_ir::AliasTy::Projection(ProjectionTy {
+ associated_ty_id: *id,
+ substitution: subst.clone(),
+ }))
+ .intern(Interner);
+ return db.layout_of_ty(ty, trait_env);
+ }
+ TyKind::Alias(_)
| TyKind::Placeholder(_)
| TyKind::BoundVar(_)
| TyKind::InferenceVar(_, _) => return Err(LayoutError::HasPlaceholder),
@@ -294,7 +391,7 @@ pub fn layout_of_ty_recover(
_: &dyn HirDatabase,
_: &[String],
_: &Ty,
- _: &CrateId,
+ _: &Arc<TraitEnvironment>,
) -> Result<Arc<Layout>, LayoutError> {
user_error!("infinite sized recursive type");
}
@@ -315,7 +412,10 @@ fn struct_tail_erasing_lifetimes(db: &dyn HirDatabase, pointee: Ty) -> Ty {
let data = db.struct_data(*i);
let mut it = data.variant_data.fields().iter().rev();
match it.next() {
- Some((f, _)) => field_ty(db, (*i).into(), f, subst),
+ Some((f, _)) => {
+ let last_field_ty = field_ty(db, (*i).into(), f, subst);
+ struct_tail_erasing_lifetimes(db, last_field_ty)
+ }
None => pointee,
}
}
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs
index bd2752a71..1c92e80f3 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs
@@ -2,7 +2,6 @@
use std::{cmp, ops::Bound};
-use base_db::CrateId;
use hir_def::{
data::adt::VariantData,
layout::{Integer, LayoutCalculator, ReprOptions, TargetDataLayout},
@@ -16,7 +15,7 @@ use crate::{
db::HirDatabase,
lang_items::is_unsafe_cell,
layout::{field_ty, Layout, LayoutError, RustcEnumVariantIdx},
- Substitution,
+ Substitution, TraitEnvironment,
};
use super::LayoutCx;
@@ -29,15 +28,18 @@ pub fn layout_of_adt_query(
db: &dyn HirDatabase,
def: AdtId,
subst: Substitution,
- krate: CrateId,
+ trait_env: Arc<TraitEnvironment>,
) -> Result<Arc<Layout>, LayoutError> {
- let Some(target) = db.target_data_layout(krate) else { return Err(LayoutError::TargetLayoutNotAvailable) };
- let cx = LayoutCx { krate, target: &target };
+ let krate = trait_env.krate;
+ let Some(target) = db.target_data_layout(krate) else {
+ return Err(LayoutError::TargetLayoutNotAvailable);
+ };
+ let cx = LayoutCx { target: &target };
let dl = cx.current_data_layout();
let handle_variant = |def: VariantId, var: &VariantData| {
var.fields()
.iter()
- .map(|(fd, _)| db.layout_of_ty(field_ty(db, def, fd, &subst), cx.krate))
+ .map(|(fd, _)| db.layout_of_ty(field_ty(db, def, fd, &subst), trait_env.clone()))
.collect::<Result<Vec<_>, _>>()
};
let (variants, repr) = match def {
@@ -70,9 +72,9 @@ pub fn layout_of_adt_query(
};
let variants = variants
.iter()
- .map(|x| x.iter().map(|x| &**x).collect::<Vec<_>>())
+ .map(|it| it.iter().map(|it| &**it).collect::<Vec<_>>())
.collect::<SmallVec<[_; 1]>>();
- let variants = variants.iter().map(|x| x.iter().collect()).collect();
+ let variants = variants.iter().map(|it| it.iter().collect()).collect();
let result = if matches!(def, AdtId::UnionId(..)) {
cx.layout_of_union(&repr, &variants).ok_or(LayoutError::Unknown)?
} else {
@@ -103,7 +105,7 @@ pub fn layout_of_adt_query(
&& variants
.iter()
.next()
- .and_then(|x| x.last().map(|x| x.is_unsized()))
+ .and_then(|it| it.last().map(|it| !it.is_unsized()))
.unwrap_or(true),
)
.ok_or(LayoutError::SizeOverflow)?
@@ -116,9 +118,9 @@ fn layout_scalar_valid_range(db: &dyn HirDatabase, def: AdtId) -> (Bound<u128>,
let get = |name| {
let attr = attrs.by_key(name).tt_values();
for tree in attr {
- if let Some(x) = tree.token_trees.first() {
- if let Ok(x) = x.to_string().parse() {
- return Bound::Included(x);
+ if let Some(it) = tree.token_trees.first() {
+ if let Ok(it) = it.to_string().parse() {
+ return Bound::Included(it);
}
}
}
@@ -132,7 +134,7 @@ pub fn layout_of_adt_recover(
_: &[String],
_: &AdtId,
_: &Substitution,
- _: &CrateId,
+ _: &Arc<TraitEnvironment>,
) -> Result<Arc<Layout>, LayoutError> {
user_error!("infinite sized recursive type");
}
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs
index 0ff8c532d..333ad473a 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs
@@ -26,7 +26,7 @@ fn eval_goal(ra_fixture: &str, minicore: &str) -> Result<Arc<Layout>, LayoutErro
);
let (db, file_ids) = TestDB::with_many_files(&ra_fixture);
- let (adt_or_type_alias_id, module_id) = file_ids
+ let adt_or_type_alias_id = file_ids
.into_iter()
.find_map(|file_id| {
let module_id = db.module_for_file(file_id);
@@ -47,7 +47,7 @@ fn eval_goal(ra_fixture: &str, minicore: &str) -> Result<Arc<Layout>, LayoutErro
}
_ => None,
})?;
- Some((adt_or_type_alias_id, module_id))
+ Some(adt_or_type_alias_id)
})
.unwrap();
let goal_ty = match adt_or_type_alias_id {
@@ -58,7 +58,13 @@ fn eval_goal(ra_fixture: &str, minicore: &str) -> Result<Arc<Layout>, LayoutErro
db.ty(ty_id.into()).substitute(Interner, &Substitution::empty(Interner))
}
};
- db.layout_of_ty(goal_ty, module_id.krate())
+ db.layout_of_ty(
+ goal_ty,
+ db.trait_environment(match adt_or_type_alias_id {
+ Either::Left(adt) => hir_def::GenericDefId::AdtId(adt),
+ Either::Right(ty) => hir_def::GenericDefId::TypeAliasId(ty),
+ }),
+ )
}
/// A version of `eval_goal` for types that can not be expressed in ADTs, like closures and `impl Trait`
@@ -72,7 +78,7 @@ fn eval_expr(ra_fixture: &str, minicore: &str) -> Result<Arc<Layout>, LayoutErro
let module_id = db.module_for_file(file_id);
let def_map = module_id.def_map(&db);
let scope = &def_map[module_id.local_id].scope;
- let adt_id = scope
+ let function_id = scope
.declarations()
.find_map(|x| match x {
hir_def::ModuleDefId::FunctionId(x) => {
@@ -82,11 +88,11 @@ fn eval_expr(ra_fixture: &str, minicore: &str) -> Result<Arc<Layout>, LayoutErro
_ => None,
})
.unwrap();
- let hir_body = db.body(adt_id.into());
+ let hir_body = db.body(function_id.into());
let b = hir_body.bindings.iter().find(|x| x.1.name.to_smol_str() == "goal").unwrap().0;
- let infer = db.infer(adt_id.into());
+ let infer = db.infer(function_id.into());
let goal_ty = infer.type_of_binding[b].clone();
- db.layout_of_ty(goal_ty, module_id.krate())
+ db.layout_of_ty(goal_ty, db.trait_environment(function_id.into()))
}
#[track_caller]
@@ -271,6 +277,20 @@ struct Goal(Foo<S>);
}
#[test]
+fn simd_types() {
+ check_size_and_align(
+ r#"
+ #[repr(simd)]
+ struct SimdType(i64, i64);
+ struct Goal(SimdType);
+ "#,
+ "",
+ 16,
+ 16,
+ );
+}
+
+#[test]
fn return_position_impl_trait() {
size_and_align_expr! {
trait T {}
@@ -344,6 +364,24 @@ fn return_position_impl_trait() {
}
#[test]
+fn unsized_ref() {
+ size_and_align! {
+ struct S1([u8]);
+ struct S2(S1);
+ struct S3(i32, str);
+ struct S4(u64, S3);
+ #[allow(dead_code)]
+ struct S5 {
+ field1: u8,
+ field2: i16,
+ field_last: S4,
+ }
+
+ struct Goal(&'static S1, &'static S2, &'static S3, &'static S4, &'static S5);
+ }
+}
+
+#[test]
fn enums() {
size_and_align! {
enum Goal {
@@ -369,11 +407,11 @@ fn tuple() {
}
#[test]
-fn non_zero() {
+fn non_zero_and_non_null() {
size_and_align! {
- minicore: non_zero, option;
- use core::num::NonZeroU8;
- struct Goal(Option<NonZeroU8>);
+ minicore: non_zero, non_null, option;
+ use core::{num::NonZeroU8, ptr::NonNull};
+ struct Goal(Option<NonZeroU8>, Option<NonNull<i32>>);
}
}
@@ -432,3 +470,41 @@ fn enums_with_discriminants() {
}
}
}
+
+#[test]
+fn core_mem_discriminant() {
+ size_and_align! {
+ minicore: discriminant;
+ struct S(i32, u64);
+ struct Goal(core::mem::Discriminant<S>);
+ }
+ size_and_align! {
+ minicore: discriminant;
+ #[repr(u32)]
+ enum S {
+ A,
+ B,
+ C,
+ }
+ struct Goal(core::mem::Discriminant<S>);
+ }
+ size_and_align! {
+ minicore: discriminant;
+ enum S {
+ A(i32),
+ B(i64),
+ C(u8),
+ }
+ struct Goal(core::mem::Discriminant<S>);
+ }
+ size_and_align! {
+ minicore: discriminant;
+ #[repr(C, u16)]
+ enum S {
+ A(i32),
+ B(i64) = 200,
+ C = 1000,
+ }
+ struct Goal(core::mem::Discriminant<S>);
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs
index 1a4d003bf..b3ca2a222 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs
@@ -180,9 +180,16 @@ impl MemoryMap {
/// allocator function as `f` and it will return a mapping of old addresses to new addresses.
fn transform_addresses(
&self,
- mut f: impl FnMut(&[u8]) -> Result<usize, MirEvalError>,
+ mut f: impl FnMut(&[u8], usize) -> Result<usize, MirEvalError>,
) -> Result<HashMap<usize, usize>, MirEvalError> {
- self.memory.iter().map(|x| Ok((*x.0, f(x.1)?))).collect()
+ self.memory
+ .iter()
+ .map(|x| {
+ let addr = *x.0;
+ let align = if addr == 0 { 64 } else { (addr - (addr & (addr - 1))).min(64) };
+ Ok((addr, f(x.1, align)?))
+ })
+ .collect()
}
fn get<'a>(&'a self, addr: usize, size: usize) -> Option<&'a [u8]> {
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs
index 9951a1c75..2837f400b 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs
@@ -23,7 +23,7 @@ use hir_def::{
generics::{
TypeOrConstParamData, TypeParamProvenance, WherePredicate, WherePredicateTypeTarget,
},
- lang_item::{lang_attr, LangItem},
+ lang_item::LangItem,
nameres::MacroSubNs,
path::{GenericArg, GenericArgs, ModPath, Path, PathKind, PathSegment, PathSegments},
resolver::{HasResolver, Resolver, TypeNs},
@@ -959,10 +959,10 @@ impl<'a> TyLoweringContext<'a> {
}
pub(crate) fn lower_where_predicate(
- &'a self,
- where_predicate: &'a WherePredicate,
+ &self,
+ where_predicate: &WherePredicate,
ignore_bindings: bool,
- ) -> impl Iterator<Item = QuantifiedWhereClause> + 'a {
+ ) -> impl Iterator<Item = QuantifiedWhereClause> {
match where_predicate {
WherePredicate::ForLifetime { target, bound, .. }
| WherePredicate::TypeBound { target, bound } => {
@@ -1012,7 +1012,7 @@ impl<'a> TyLoweringContext<'a> {
// (So ideally, we'd only ignore `~const Drop` here)
// - `Destruct` impls are built-in in 1.62 (current nightly as of 08-04-2022), so until
// the builtin impls are supported by Chalk, we ignore them here.
- if let Some(lang) = lang_attr(self.db.upcast(), tr.hir_trait_id()) {
+ if let Some(lang) = self.db.lang_attr(tr.hir_trait_id().into()) {
if matches!(lang, LangItem::Drop | LangItem::Destruct) {
return false;
}
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs b/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs
index ab6430e8f..f3a5f69b2 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs
@@ -534,7 +534,7 @@ impl ReceiverAdjustments {
let mut ty = table.resolve_ty_shallow(&ty);
let mut adjust = Vec::new();
for _ in 0..self.autoderefs {
- match autoderef::autoderef_step(table, ty.clone()) {
+ match autoderef::autoderef_step(table, ty.clone(), true) {
None => {
never!("autoderef not possible for {:?}", ty);
ty = TyKind::Error.intern(Interner);
@@ -559,10 +559,10 @@ impl ReceiverAdjustments {
adjust.push(a);
}
if self.unsize_array {
- ty = 'x: {
+ ty = 'it: {
if let TyKind::Ref(m, l, inner) = ty.kind(Interner) {
if let TyKind::Array(inner, _) = inner.kind(Interner) {
- break 'x TyKind::Ref(
+ break 'it TyKind::Ref(
m.clone(),
l.clone(),
TyKind::Slice(inner.clone()).intern(Interner),
@@ -665,13 +665,21 @@ pub fn is_dyn_method(
};
let self_ty = trait_ref.self_type_parameter(Interner);
if let TyKind::Dyn(d) = self_ty.kind(Interner) {
- let is_my_trait_in_bounds =
- d.bounds.skip_binders().as_slice(Interner).iter().any(|x| match x.skip_binders() {
- // rustc doesn't accept `impl Foo<2> for dyn Foo<5>`, so if the trait id is equal, no matter
- // what the generics are, we are sure that the method is come from the vtable.
- WhereClause::Implemented(tr) => tr.trait_id == trait_ref.trait_id,
- _ => false,
- });
+ let is_my_trait_in_bounds = d
+ .bounds
+ .skip_binders()
+ .as_slice(Interner)
+ .iter()
+ .map(|it| it.skip_binders())
+ .flat_map(|it| match it {
+ WhereClause::Implemented(tr) => {
+ all_super_traits(db.upcast(), from_chalk_trait_id(tr.trait_id))
+ }
+ _ => smallvec![],
+ })
+ // rustc doesn't accept `impl Foo<2> for dyn Foo<5>`, so if the trait id is equal, no matter
+ // what the generics are, we are sure that the method is come from the vtable.
+ .any(|x| x == trait_id);
if is_my_trait_in_bounds {
return Some(fn_params);
}
@@ -682,14 +690,14 @@ pub fn is_dyn_method(
/// Looks up the impl method that actually runs for the trait method `func`.
///
/// Returns `func` if it's not a method defined in a trait or the lookup failed.
-pub fn lookup_impl_method(
+pub(crate) fn lookup_impl_method_query(
db: &dyn HirDatabase,
env: Arc<TraitEnvironment>,
func: FunctionId,
fn_subst: Substitution,
) -> (FunctionId, Substitution) {
let ItemContainerId::TraitId(trait_id) = func.lookup(db.upcast()).container else {
- return (func, fn_subst)
+ return (func, fn_subst);
};
let trait_params = db.generic_params(trait_id.into()).type_or_consts.len();
let fn_params = fn_subst.len(Interner) - trait_params;
@@ -699,8 +707,8 @@ pub fn lookup_impl_method(
};
let name = &db.function_data(func).name;
- let Some((impl_fn, impl_subst)) = lookup_impl_assoc_item_for_trait_ref(trait_ref, db, env, name)
- .and_then(|assoc| {
+ let Some((impl_fn, impl_subst)) =
+ lookup_impl_assoc_item_for_trait_ref(trait_ref, db, env, name).and_then(|assoc| {
if let (AssocItemId::FunctionId(id), subst) = assoc {
Some((id, subst))
} else {
@@ -731,7 +739,7 @@ fn lookup_impl_assoc_item_for_trait_ref(
let impls = db.trait_impls_in_deps(env.krate);
let self_impls = match self_ty.kind(Interner) {
TyKind::Adt(id, _) => {
- id.0.module(db.upcast()).containing_block().map(|x| db.trait_impls_in_block(x))
+ id.0.module(db.upcast()).containing_block().map(|it| db.trait_impls_in_block(it))
}
_ => None,
};
@@ -895,8 +903,8 @@ pub fn iterate_method_candidates_dyn(
// (just as rustc does an autoderef and then autoref again).
// We have to be careful about the order we're looking at candidates
- // in here. Consider the case where we're resolving `x.clone()`
- // where `x: &Vec<_>`. This resolves to the clone method with self
+ // in here. Consider the case where we're resolving `it.clone()`
+ // where `it: &Vec<_>`. This resolves to the clone method with self
// type `Vec<_>`, *not* `&_`. I.e. we need to consider methods where
// the receiver type exactly matches before cases where we have to
// do autoref. But in the autoderef steps, the `&_` self type comes
@@ -1012,8 +1020,8 @@ fn iterate_method_candidates_by_receiver(
let snapshot = table.snapshot();
// We're looking for methods with *receiver* type receiver_ty. These could
// be found in any of the derefs of receiver_ty, so we have to go through
- // that.
- let mut autoderef = autoderef::Autoderef::new(&mut table, receiver_ty.clone());
+ // that, including raw derefs.
+ let mut autoderef = autoderef::Autoderef::new(&mut table, receiver_ty.clone(), true);
while let Some((self_ty, _)) = autoderef.next() {
iterate_inherent_methods(
&self_ty,
@@ -1028,7 +1036,7 @@ fn iterate_method_candidates_by_receiver(
table.rollback_to(snapshot);
- let mut autoderef = autoderef::Autoderef::new(&mut table, receiver_ty.clone());
+ let mut autoderef = autoderef::Autoderef::new(&mut table, receiver_ty.clone(), true);
while let Some((self_ty, _)) = autoderef.next() {
iterate_trait_method_candidates(
&self_ty,
@@ -1480,8 +1488,8 @@ fn generic_implements_goal(
.push(self_ty.value.clone())
.fill_with_bound_vars(DebruijnIndex::INNERMOST, kinds.len())
.build();
- kinds.extend(trait_ref.substitution.iter(Interner).skip(1).map(|x| {
- let vk = match x.data(Interner) {
+ kinds.extend(trait_ref.substitution.iter(Interner).skip(1).map(|it| {
+ let vk = match it.data(Interner) {
chalk_ir::GenericArgData::Ty(_) => {
chalk_ir::VariableKind::Ty(chalk_ir::TyVariableKind::General)
}
@@ -1504,7 +1512,7 @@ fn autoderef_method_receiver(
ty: Ty,
) -> Vec<(Canonical<Ty>, ReceiverAdjustments)> {
let mut deref_chain: Vec<_> = Vec::new();
- let mut autoderef = autoderef::Autoderef::new(table, ty);
+ let mut autoderef = autoderef::Autoderef::new(table, ty, false);
while let Some((ty, derefs)) = autoderef.next() {
deref_chain.push((
autoderef.table.canonicalize(ty).value,
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs
index 2345bab0b..4723c25ed 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs
@@ -3,9 +3,14 @@
use std::{fmt::Display, iter};
use crate::{
- consteval::usize_const, db::HirDatabase, display::HirDisplay, infer::PointerCast,
- lang_items::is_box, mapping::ToChalk, CallableDefId, ClosureId, Const, ConstScalar,
- InferenceResult, Interner, MemoryMap, Substitution, Ty, TyKind,
+ consteval::usize_const,
+ db::HirDatabase,
+ display::HirDisplay,
+ infer::{normalize, PointerCast},
+ lang_items::is_box,
+ mapping::ToChalk,
+ CallableDefId, ClosureId, Const, ConstScalar, InferenceResult, Interner, MemoryMap,
+ Substitution, TraitEnvironment, Ty, TyKind,
};
use base_db::CrateId;
use chalk_ir::Mutability;
@@ -22,7 +27,9 @@ mod pretty;
mod monomorphization;
pub use borrowck::{borrowck_query, BorrowckResult, MutabilityReason};
-pub use eval::{interpret_mir, pad16, Evaluator, MirEvalError, VTableMap};
+pub use eval::{
+ interpret_mir, pad16, render_const_using_debug_impl, Evaluator, MirEvalError, VTableMap,
+};
pub use lower::{
lower_to_mir, mir_body_for_closure_query, mir_body_query, mir_body_recover, MirLowerError,
};
@@ -32,6 +39,7 @@ pub use monomorphization::{
};
use smallvec::{smallvec, SmallVec};
use stdx::{impl_from, never};
+use triomphe::Arc;
use super::consteval::{intern_const_scalar, try_const_usize};
@@ -129,13 +137,21 @@ pub enum ProjectionElem<V, T> {
impl<V, T> ProjectionElem<V, T> {
pub fn projected_ty(
&self,
- base: Ty,
+ mut base: Ty,
db: &dyn HirDatabase,
closure_field: impl FnOnce(ClosureId, &Substitution, usize) -> Ty,
krate: CrateId,
) -> Ty {
+ if matches!(base.kind(Interner), TyKind::Alias(_) | TyKind::AssociatedType(..)) {
+ base = normalize(
+ db,
+ // FIXME: we should get this from caller
+ Arc::new(TraitEnvironment::empty(krate)),
+ base,
+ );
+ }
match self {
- ProjectionElem::Deref => match &base.data(Interner).kind {
+ ProjectionElem::Deref => match &base.kind(Interner) {
TyKind::Raw(_, inner) | TyKind::Ref(_, _, inner) => inner.clone(),
TyKind::Adt(adt, subst) if is_box(db, adt.0) => {
subst.at(Interner, 0).assert_ty_ref(Interner).clone()
@@ -145,7 +161,7 @@ impl<V, T> ProjectionElem<V, T> {
return TyKind::Error.intern(Interner);
}
},
- ProjectionElem::Field(f) => match &base.data(Interner).kind {
+ ProjectionElem::Field(f) => match &base.kind(Interner) {
TyKind::Adt(_, subst) => {
db.field_types(f.parent)[f.local_id].clone().substitute(Interner, subst)
}
@@ -154,7 +170,7 @@ impl<V, T> ProjectionElem<V, T> {
return TyKind::Error.intern(Interner);
}
},
- ProjectionElem::TupleOrClosureField(f) => match &base.data(Interner).kind {
+ ProjectionElem::TupleOrClosureField(f) => match &base.kind(Interner) {
TyKind::Tuple(_, subst) => subst
.as_slice(Interner)
.get(*f)
@@ -171,7 +187,7 @@ impl<V, T> ProjectionElem<V, T> {
}
},
ProjectionElem::ConstantIndex { .. } | ProjectionElem::Index(_) => {
- match &base.data(Interner).kind {
+ match &base.kind(Interner) {
TyKind::Array(inner, _) | TyKind::Slice(inner) => inner.clone(),
_ => {
never!("Overloaded index is not a projection");
@@ -179,7 +195,7 @@ impl<V, T> ProjectionElem<V, T> {
}
}
}
- &ProjectionElem::Subslice { from, to } => match &base.data(Interner).kind {
+ &ProjectionElem::Subslice { from, to } => match &base.kind(Interner) {
TyKind::Array(inner, c) => {
let next_c = usize_const(
db,
@@ -218,6 +234,7 @@ impl Place {
self.local == child.local && child.projection.starts_with(&self.projection)
}
+ /// The place itself is not included
fn iterate_over_parents(&self) -> impl Iterator<Item = Place> + '_ {
(0..self.projection.len())
.map(|x| &self.projection[0..x])
@@ -321,8 +338,8 @@ impl SwitchTargets {
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Terminator {
- span: MirSpan,
- kind: TerminatorKind,
+ pub span: MirSpan,
+ pub kind: TerminatorKind,
}
#[derive(Debug, PartialEq, Eq, Clone)]
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs
index a5dd0182e..ad98e8fa1 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs
@@ -52,7 +52,7 @@ fn all_mir_bodies(
let closures = body.closures.clone();
Box::new(
iter::once(Ok(body))
- .chain(closures.into_iter().flat_map(|x| for_closure(db, x))),
+ .chain(closures.into_iter().flat_map(|it| for_closure(db, it))),
)
}
Err(e) => Box::new(iter::once(Err(e))),
@@ -62,7 +62,7 @@ fn all_mir_bodies(
Ok(body) => {
let closures = body.closures.clone();
Box::new(
- iter::once(Ok(body)).chain(closures.into_iter().flat_map(|x| for_closure(db, x))),
+ iter::once(Ok(body)).chain(closures.into_iter().flat_map(|it| for_closure(db, it))),
)
}
Err(e) => Box::new(iter::once(Err(e))),
@@ -171,7 +171,7 @@ fn moved_out_of_ref(db: &dyn HirDatabase, body: &MirBody) -> Vec<MovedOutOfRef>
}
TerminatorKind::Call { func, args, .. } => {
for_operand(func, terminator.span);
- args.iter().for_each(|x| for_operand(x, terminator.span));
+ args.iter().for_each(|it| for_operand(it, terminator.span));
}
TerminatorKind::Assert { cond, .. } => {
for_operand(cond, terminator.span);
@@ -245,7 +245,7 @@ fn ever_initialized_map(
body: &MirBody,
) -> ArenaMap<BasicBlockId, ArenaMap<LocalId, bool>> {
let mut result: ArenaMap<BasicBlockId, ArenaMap<LocalId, bool>> =
- body.basic_blocks.iter().map(|x| (x.0, ArenaMap::default())).collect();
+ body.basic_blocks.iter().map(|it| (it.0, ArenaMap::default())).collect();
fn dfs(
db: &dyn HirDatabase,
body: &MirBody,
@@ -271,7 +271,10 @@ fn ever_initialized_map(
}
}
let Some(terminator) = &block.terminator else {
- never!("Terminator should be none only in construction.\nThe body:\n{}", body.pretty_print(db));
+ never!(
+ "Terminator should be none only in construction.\nThe body:\n{}",
+ body.pretty_print(db)
+ );
return;
};
let targets = match &terminator.kind {
@@ -311,7 +314,7 @@ fn ever_initialized_map(
result[body.start_block].insert(l, true);
dfs(db, body, body.start_block, l, &mut result);
}
- for l in body.locals.iter().map(|x| x.0) {
+ for l in body.locals.iter().map(|it| it.0) {
if !result[body.start_block].contains_idx(l) {
result[body.start_block].insert(l, false);
dfs(db, body, body.start_block, l, &mut result);
@@ -325,10 +328,10 @@ fn mutability_of_locals(
body: &MirBody,
) -> ArenaMap<LocalId, MutabilityReason> {
let mut result: ArenaMap<LocalId, MutabilityReason> =
- body.locals.iter().map(|x| (x.0, MutabilityReason::Not)).collect();
+ body.locals.iter().map(|it| (it.0, MutabilityReason::Not)).collect();
let mut push_mut_span = |local, span| match &mut result[local] {
MutabilityReason::Mut { spans } => spans.push(span),
- x @ MutabilityReason::Not => *x = MutabilityReason::Mut { spans: vec![span] },
+ it @ MutabilityReason::Not => *it = MutabilityReason::Mut { spans: vec![span] },
};
let ever_init_maps = ever_initialized_map(db, body);
for (block_id, mut ever_init_map) in ever_init_maps.into_iter() {
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs
index 9acf9d39e..9e30eed56 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs
@@ -1,6 +1,13 @@
//! This module provides a MIR interpreter, which is used in const eval.
-use std::{borrow::Cow, collections::HashMap, fmt::Write, iter, ops::Range};
+use std::{
+ borrow::Cow,
+ cell::RefCell,
+ collections::{HashMap, HashSet},
+ fmt::Write,
+ iter, mem,
+ ops::Range,
+};
use base_db::{CrateId, FileId};
use chalk_ir::Mutability;
@@ -8,12 +15,13 @@ use either::Either;
use hir_def::{
builtin_type::BuiltinType,
data::adt::{StructFlags, VariantData},
- lang_item::{lang_attr, LangItem},
+ lang_item::LangItem,
layout::{TagEncoding, Variants},
- AdtId, DefWithBodyId, EnumVariantId, FunctionId, HasModule, ItemContainerId, Lookup, StaticId,
- VariantId,
+ resolver::{HasResolver, TypeNs, ValueNs},
+ AdtId, ConstId, DefWithBodyId, EnumVariantId, FunctionId, HasModule, ItemContainerId, Lookup,
+ StaticId, VariantId,
};
-use hir_expand::InFile;
+use hir_expand::{mod_path::ModPath, InFile};
use intern::Interned;
use la_arena::ArenaMap;
use rustc_hash::{FxHashMap, FxHashSet};
@@ -28,7 +36,7 @@ use crate::{
infer::PointerCast,
layout::{Layout, LayoutError, RustcEnumVariantIdx},
mapping::from_chalk,
- method_resolution::{is_dyn_method, lookup_impl_method},
+ method_resolution::{is_dyn_method, lookup_impl_const},
name, static_lifetime,
traits::FnTrait,
utils::{detect_variant_from_bytes, ClosureSubst},
@@ -37,8 +45,9 @@ use crate::{
};
use super::{
- return_slot, AggregateKind, BinOp, CastKind, LocalId, MirBody, MirLowerError, MirSpan, Operand,
- Place, ProjectionElem, Rvalue, StatementKind, TerminatorKind, UnOp,
+ return_slot, AggregateKind, BasicBlockId, BinOp, CastKind, LocalId, MirBody, MirLowerError,
+ MirSpan, Operand, Place, PlaceElem, ProjectionElem, Rvalue, StatementKind, TerminatorKind,
+ UnOp,
};
mod shim;
@@ -48,15 +57,15 @@ mod tests;
macro_rules! from_bytes {
($ty:tt, $value:expr) => {
($ty::from_le_bytes(match ($value).try_into() {
- Ok(x) => x,
+ Ok(it) => it,
Err(_) => return Err(MirEvalError::TypeError(stringify!(mismatched size in constructing $ty))),
}))
};
}
macro_rules! not_supported {
- ($x: expr) => {
- return Err(MirEvalError::NotSupported(format!($x)))
+ ($it: expr) => {
+ return Err(MirEvalError::NotSupported(format!($it)))
};
}
@@ -67,18 +76,22 @@ pub struct VTableMap {
}
impl VTableMap {
+ const OFFSET: usize = 1000; // We should add some offset to ids to make 0 (null) an invalid id.
+
fn id(&mut self, ty: Ty) -> usize {
- if let Some(x) = self.ty_to_id.get(&ty) {
- return *x;
+ if let Some(it) = self.ty_to_id.get(&ty) {
+ return *it;
}
- let id = self.id_to_ty.len();
+ let id = self.id_to_ty.len() + VTableMap::OFFSET;
self.id_to_ty.push(ty.clone());
self.ty_to_id.insert(ty, id);
id
}
pub(crate) fn ty(&self, id: usize) -> Result<&Ty> {
- self.id_to_ty.get(id).ok_or(MirEvalError::InvalidVTableId(id))
+ id.checked_sub(VTableMap::OFFSET)
+ .and_then(|id| self.id_to_ty.get(id))
+ .ok_or(MirEvalError::InvalidVTableId(id))
}
fn ty_of_bytes(&self, bytes: &[u8]) -> Result<&Ty> {
@@ -114,11 +127,25 @@ impl TlsData {
}
}
+struct StackFrame {
+ locals: Locals,
+ destination: Option<BasicBlockId>,
+ prev_stack_ptr: usize,
+ span: (MirSpan, DefWithBodyId),
+}
+
+#[derive(Clone)]
+enum MirOrDynIndex {
+ Mir(Arc<MirBody>),
+ Dyn(usize),
+}
+
pub struct Evaluator<'a> {
db: &'a dyn HirDatabase,
trait_env: Arc<TraitEnvironment>,
stack: Vec<u8>,
heap: Vec<u8>,
+ code_stack: Vec<StackFrame>,
/// Stores the global location of the statics. We const evaluate every static first time we need it
/// and see it's missing, then we add it to this to reuse.
static_locations: FxHashMap<StaticId, Address>,
@@ -127,8 +154,21 @@ pub struct Evaluator<'a> {
/// time of use.
vtable_map: VTableMap,
thread_local_storage: TlsData,
+ random_state: oorandom::Rand64,
stdout: Vec<u8>,
stderr: Vec<u8>,
+ layout_cache: RefCell<FxHashMap<Ty, Arc<Layout>>>,
+ projected_ty_cache: RefCell<FxHashMap<(Ty, PlaceElem), Ty>>,
+ not_special_fn_cache: RefCell<FxHashSet<FunctionId>>,
+ mir_or_dyn_index_cache: RefCell<FxHashMap<(FunctionId, Substitution), MirOrDynIndex>>,
+ /// Constantly dropping and creating `Locals` is very costly. We store
+ /// old locals that we normaly want to drop here, to reuse their allocations
+ /// later.
+ unused_locals_store: RefCell<FxHashMap<DefWithBodyId, Vec<Locals>>>,
+ cached_ptr_size: usize,
+ cached_fn_trait_func: Option<FunctionId>,
+ cached_fn_mut_trait_func: Option<FunctionId>,
+ cached_fn_once_trait_func: Option<FunctionId>,
crate_id: CrateId,
// FIXME: This is a workaround, see the comment on `interpret_mir`
assert_placeholder_ty_is_unused: bool,
@@ -136,6 +176,8 @@ pub struct Evaluator<'a> {
execution_limit: usize,
/// An additional limit on stack depth, to prevent stack overflow
stack_depth_limit: usize,
+ /// Maximum count of bytes that heap and stack can grow
+ memory_limit: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -192,7 +234,7 @@ impl IntervalAndTy {
addr: Address,
ty: Ty,
evaluator: &Evaluator<'_>,
- locals: &Locals<'_>,
+ locals: &Locals,
) -> Result<IntervalAndTy> {
let size = evaluator.size_of_sized(&ty, locals, "type of interval")?;
Ok(IntervalAndTy { interval: Interval { addr, size }, ty })
@@ -226,18 +268,28 @@ impl IntervalOrOwned {
}
}
+#[cfg(target_pointer_width = "64")]
+const STACK_OFFSET: usize = 1 << 60;
+#[cfg(target_pointer_width = "64")]
+const HEAP_OFFSET: usize = 1 << 59;
+
+#[cfg(target_pointer_width = "32")]
+const STACK_OFFSET: usize = 1 << 30;
+#[cfg(target_pointer_width = "32")]
+const HEAP_OFFSET: usize = 1 << 29;
+
impl Address {
- fn from_bytes(x: &[u8]) -> Result<Self> {
- Ok(Address::from_usize(from_bytes!(usize, x)))
+ fn from_bytes(it: &[u8]) -> Result<Self> {
+ Ok(Address::from_usize(from_bytes!(usize, it)))
}
- fn from_usize(x: usize) -> Self {
- if x > usize::MAX / 2 {
- Stack(x - usize::MAX / 2)
- } else if x > usize::MAX / 4 {
- Heap(x - usize::MAX / 4)
+ fn from_usize(it: usize) -> Self {
+ if it > STACK_OFFSET {
+ Stack(it - STACK_OFFSET)
+ } else if it > HEAP_OFFSET {
+ Heap(it - HEAP_OFFSET)
} else {
- Invalid(x)
+ Invalid(it)
}
}
@@ -247,23 +299,23 @@ impl Address {
fn to_usize(&self) -> usize {
let as_num = match self {
- Stack(x) => *x + usize::MAX / 2,
- Heap(x) => *x + usize::MAX / 4,
- Invalid(x) => *x,
+ Stack(it) => *it + STACK_OFFSET,
+ Heap(it) => *it + HEAP_OFFSET,
+ Invalid(it) => *it,
};
as_num
}
fn map(&self, f: impl FnOnce(usize) -> usize) -> Address {
match self {
- Stack(x) => Stack(f(*x)),
- Heap(x) => Heap(f(*x)),
- Invalid(x) => Invalid(f(*x)),
+ Stack(it) => Stack(f(*it)),
+ Heap(it) => Heap(f(*it)),
+ Invalid(it) => Invalid(f(*it)),
}
}
fn offset(&self, offset: usize) -> Address {
- self.map(|x| x + offset)
+ self.map(|it| it + offset)
}
}
@@ -282,13 +334,14 @@ pub enum MirEvalError {
TypeIsUnsized(Ty, &'static str),
NotSupported(String),
InvalidConst(Const),
- InFunction(Either<FunctionId, ClosureId>, Box<MirEvalError>, MirSpan, DefWithBodyId),
+ InFunction(Box<MirEvalError>, Vec<(Either<FunctionId, ClosureId>, MirSpan, DefWithBodyId)>),
ExecutionLimitExceeded,
StackOverflow,
TargetDataLayoutNotAvailable,
InvalidVTableId(usize),
CoerceUnsizedError(Ty),
LangItemNotFound(LangItem),
+ BrokenLayout(Layout),
}
impl MirEvalError {
@@ -300,40 +353,42 @@ impl MirEvalError {
) -> std::result::Result<(), std::fmt::Error> {
writeln!(f, "Mir eval error:")?;
let mut err = self;
- while let MirEvalError::InFunction(func, e, span, def) = err {
+ while let MirEvalError::InFunction(e, stack) = err {
err = e;
- match func {
- Either::Left(func) => {
- let function_name = db.function_data(*func);
- writeln!(
- f,
- "In function {} ({:?})",
- function_name.name.display(db.upcast()),
- func
- )?;
- }
- Either::Right(clos) => {
- writeln!(f, "In {:?}", clos)?;
+ for (func, span, def) in stack.iter().take(30).rev() {
+ match func {
+ Either::Left(func) => {
+ let function_name = db.function_data(*func);
+ writeln!(
+ f,
+ "In function {} ({:?})",
+ function_name.name.display(db.upcast()),
+ func
+ )?;
+ }
+ Either::Right(clos) => {
+ writeln!(f, "In {:?}", clos)?;
+ }
}
+ let source_map = db.body_with_source_map(*def).1;
+ let span: InFile<SyntaxNodePtr> = match span {
+ MirSpan::ExprId(e) => match source_map.expr_syntax(*e) {
+ Ok(s) => s.map(|it| it.into()),
+ Err(_) => continue,
+ },
+ MirSpan::PatId(p) => match source_map.pat_syntax(*p) {
+ Ok(s) => s.map(|it| match it {
+ Either::Left(e) => e.into(),
+ Either::Right(e) => e.into(),
+ }),
+ Err(_) => continue,
+ },
+ MirSpan::Unknown => continue,
+ };
+ let file_id = span.file_id.original_file(db.upcast());
+ let text_range = span.value.text_range();
+ writeln!(f, "{}", span_formatter(file_id, text_range))?;
}
- let source_map = db.body_with_source_map(*def).1;
- let span: InFile<SyntaxNodePtr> = match span {
- MirSpan::ExprId(e) => match source_map.expr_syntax(*e) {
- Ok(s) => s.map(|x| x.into()),
- Err(_) => continue,
- },
- MirSpan::PatId(p) => match source_map.pat_syntax(*p) {
- Ok(s) => s.map(|x| match x {
- Either::Left(e) => e.into(),
- Either::Right(e) => e.into(),
- }),
- Err(_) => continue,
- },
- MirSpan::Unknown => continue,
- };
- let file_id = span.file_id.original_file(db.upcast());
- let text_range = span.value.text_range();
- writeln!(f, "{}", span_formatter(file_id, text_range))?;
}
match err {
MirEvalError::InFunction(..) => unreachable!(),
@@ -373,6 +428,7 @@ impl MirEvalError {
| MirEvalError::TargetDataLayoutNotAvailable
| MirEvalError::CoerceUnsizedError(_)
| MirEvalError::LangItemNotFound(_)
+ | MirEvalError::BrokenLayout(_)
| MirEvalError::InvalidVTableId(_) => writeln!(f, "{:?}", err)?,
}
Ok(())
@@ -407,19 +463,14 @@ impl std::fmt::Debug for MirEvalError {
Self::CoerceUnsizedError(arg0) => {
f.debug_tuple("CoerceUnsizedError").field(arg0).finish()
}
+ Self::BrokenLayout(arg0) => f.debug_tuple("BrokenLayout").field(arg0).finish(),
Self::InvalidVTableId(arg0) => f.debug_tuple("InvalidVTableId").field(arg0).finish(),
Self::NotSupported(arg0) => f.debug_tuple("NotSupported").field(arg0).finish(),
Self::InvalidConst(arg0) => {
let data = &arg0.data(Interner);
f.debug_struct("InvalidConst").field("ty", &data.ty).field("value", &arg0).finish()
}
- Self::InFunction(func, e, span, _) => {
- let mut e = &**e;
- let mut stack = vec![(*func, *span)];
- while let Self::InFunction(f, next_e, span, _) = e {
- e = &next_e;
- stack.push((*f, *span));
- }
+ Self::InFunction(e, stack) => {
f.debug_struct("WithStack").field("error", e).field("stack", &stack).finish()
}
}
@@ -435,85 +486,126 @@ struct DropFlags {
impl DropFlags {
fn add_place(&mut self, p: Place) {
- if p.iterate_over_parents().any(|x| self.need_drop.contains(&x)) {
+ if p.iterate_over_parents().any(|it| self.need_drop.contains(&it)) {
return;
}
- self.need_drop.retain(|x| !p.is_parent(x));
+ self.need_drop.retain(|it| !p.is_parent(it));
self.need_drop.insert(p);
}
fn remove_place(&mut self, p: &Place) -> bool {
// FIXME: replace parents with parts
+ if let Some(parent) = p.iterate_over_parents().find(|it| self.need_drop.contains(&it)) {
+ self.need_drop.remove(&parent);
+ return true;
+ }
self.need_drop.remove(p)
}
+
+ fn clear(&mut self) {
+ self.need_drop.clear();
+ }
}
#[derive(Debug)]
-struct Locals<'a> {
- ptr: &'a ArenaMap<LocalId, Interval>,
- body: &'a MirBody,
+struct Locals {
+ ptr: ArenaMap<LocalId, Interval>,
+ body: Arc<MirBody>,
drop_flags: DropFlags,
}
pub fn interpret_mir(
db: &dyn HirDatabase,
- body: &MirBody,
+ body: Arc<MirBody>,
// FIXME: This is workaround. Ideally, const generics should have a separate body (issue #7434), but now
// they share their body with their parent, so in MIR lowering we have locals of the parent body, which
// might have placeholders. With this argument, we (wrongly) assume that every placeholder type has
// a zero size, hoping that they are all outside of our current body. Even without a fix for #7434, we can
// (and probably should) do better here, for example by excluding bindings outside of the target expression.
assert_placeholder_ty_is_unused: bool,
+ trait_env: Option<Arc<TraitEnvironment>>,
) -> (Result<Const>, String, String) {
let ty = body.locals[return_slot()].ty.clone();
- let mut evaluator = Evaluator::new(db, body, assert_placeholder_ty_is_unused);
- let x: Result<Const> = (|| {
- let bytes = evaluator.interpret_mir(&body, None.into_iter())?;
+ let mut evaluator = Evaluator::new(db, body.owner, assert_placeholder_ty_is_unused, trait_env);
+ let it: Result<Const> = (|| {
+ if evaluator.ptr_size() != std::mem::size_of::<usize>() {
+ not_supported!("targets with different pointer size from host");
+ }
+ let bytes = evaluator.interpret_mir(body.clone(), None.into_iter())?;
let mut memory_map = evaluator.create_memory_map(
&bytes,
&ty,
- &Locals { ptr: &ArenaMap::new(), body: &body, drop_flags: DropFlags::default() },
+ &Locals { ptr: ArenaMap::new(), body, drop_flags: DropFlags::default() },
)?;
memory_map.vtable = evaluator.vtable_map.clone();
return Ok(intern_const_scalar(ConstScalar::Bytes(bytes, memory_map), ty));
})();
(
- x,
+ it,
String::from_utf8_lossy(&evaluator.stdout).into_owned(),
String::from_utf8_lossy(&evaluator.stderr).into_owned(),
)
}
+#[cfg(test)]
+const EXECUTION_LIMIT: usize = 100_000;
+#[cfg(not(test))]
+const EXECUTION_LIMIT: usize = 10_000_000;
+
impl Evaluator<'_> {
pub fn new<'a>(
db: &'a dyn HirDatabase,
- body: &MirBody,
+ owner: DefWithBodyId,
assert_placeholder_ty_is_unused: bool,
+ trait_env: Option<Arc<TraitEnvironment>>,
) -> Evaluator<'a> {
- let crate_id = body.owner.module(db.upcast()).krate();
- let trait_env = db.trait_environment_for_body(body.owner);
+ let crate_id = owner.module(db.upcast()).krate();
Evaluator {
stack: vec![0],
heap: vec![0],
+ code_stack: vec![],
vtable_map: VTableMap::default(),
thread_local_storage: TlsData::default(),
static_locations: HashMap::default(),
db,
- trait_env,
+ random_state: oorandom::Rand64::new(0),
+ trait_env: trait_env.unwrap_or_else(|| db.trait_environment_for_body(owner)),
crate_id,
stdout: vec![],
stderr: vec![],
assert_placeholder_ty_is_unused,
stack_depth_limit: 100,
- execution_limit: 1000_000,
+ execution_limit: EXECUTION_LIMIT,
+ memory_limit: 1000_000_000, // 2GB, 1GB for stack and 1GB for heap
+ layout_cache: RefCell::new(HashMap::default()),
+ projected_ty_cache: RefCell::new(HashMap::default()),
+ not_special_fn_cache: RefCell::new(HashSet::default()),
+ mir_or_dyn_index_cache: RefCell::new(HashMap::default()),
+ unused_locals_store: RefCell::new(HashMap::default()),
+ cached_ptr_size: match db.target_data_layout(crate_id) {
+ Some(it) => it.pointer_size.bytes_usize(),
+ None => 8,
+ },
+ cached_fn_trait_func: db
+ .lang_item(crate_id, LangItem::Fn)
+ .and_then(|x| x.as_trait())
+ .and_then(|x| db.trait_data(x).method_by_name(&name![call])),
+ cached_fn_mut_trait_func: db
+ .lang_item(crate_id, LangItem::FnMut)
+ .and_then(|x| x.as_trait())
+ .and_then(|x| db.trait_data(x).method_by_name(&name![call_mut])),
+ cached_fn_once_trait_func: db
+ .lang_item(crate_id, LangItem::FnOnce)
+ .and_then(|x| x.as_trait())
+ .and_then(|x| db.trait_data(x).method_by_name(&name![call_once])),
}
}
- fn place_addr(&self, p: &Place, locals: &Locals<'_>) -> Result<Address> {
+ fn place_addr(&self, p: &Place, locals: &Locals) -> Result<Address> {
Ok(self.place_addr_and_ty_and_metadata(p, locals)?.0)
}
- fn place_interval(&self, p: &Place, locals: &Locals<'_>) -> Result<Interval> {
+ fn place_interval(&self, p: &Place, locals: &Locals) -> Result<Interval> {
let place_addr_and_ty = self.place_addr_and_ty_and_metadata(p, locals)?;
Ok(Interval {
addr: place_addr_and_ty.0,
@@ -526,39 +618,47 @@ impl Evaluator<'_> {
}
fn ptr_size(&self) -> usize {
- match self.db.target_data_layout(self.crate_id) {
- Some(x) => x.pointer_size.bytes_usize(),
- None => 8,
+ self.cached_ptr_size
+ }
+
+ fn projected_ty(&self, ty: Ty, proj: PlaceElem) -> Ty {
+ let pair = (ty, proj);
+ if let Some(r) = self.projected_ty_cache.borrow().get(&pair) {
+ return r.clone();
}
+ let (ty, proj) = pair;
+ let r = proj.projected_ty(
+ ty.clone(),
+ self.db,
+ |c, subst, f| {
+ let (def, _) = self.db.lookup_intern_closure(c.into());
+ let infer = self.db.infer(def);
+ let (captures, _) = infer.closure_info(&c);
+ let parent_subst = ClosureSubst(subst).parent_subst();
+ captures
+ .get(f)
+ .expect("broken closure field")
+ .ty
+ .clone()
+ .substitute(Interner, parent_subst)
+ },
+ self.crate_id,
+ );
+ self.projected_ty_cache.borrow_mut().insert((ty, proj), r.clone());
+ r
}
fn place_addr_and_ty_and_metadata<'a>(
&'a self,
p: &Place,
- locals: &'a Locals<'a>,
+ locals: &'a Locals,
) -> Result<(Address, Ty, Option<IntervalOrOwned>)> {
let mut addr = locals.ptr[p.local].addr;
let mut ty: Ty = locals.body.locals[p.local].ty.clone();
let mut metadata: Option<IntervalOrOwned> = None; // locals are always sized
for proj in &*p.projection {
let prev_ty = ty.clone();
- ty = proj.projected_ty(
- ty,
- self.db,
- |c, subst, f| {
- let (def, _) = self.db.lookup_intern_closure(c.into());
- let infer = self.db.infer(def);
- let (captures, _) = infer.closure_info(&c);
- let parent_subst = ClosureSubst(subst).parent_subst();
- captures
- .get(f)
- .expect("broken closure field")
- .ty
- .clone()
- .substitute(Interner, parent_subst)
- },
- self.crate_id,
- );
+ ty = self.projected_ty(ty, proj.clone());
match proj {
ProjectionElem::Deref => {
metadata = if self.size_align_of(&ty, locals)?.is_none() {
@@ -569,8 +669,8 @@ impl Evaluator<'_> {
} else {
None
};
- let x = from_bytes!(usize, self.read_memory(addr, self.ptr_size())?);
- addr = Address::from_usize(x);
+ let it = from_bytes!(usize, self.read_memory(addr, self.ptr_size())?);
+ addr = Address::from_usize(it);
}
ProjectionElem::Index(op) => {
let offset = from_bytes!(
@@ -586,13 +686,13 @@ impl Evaluator<'_> {
let offset = if from_end {
let len = match prev_ty.kind(Interner) {
TyKind::Array(_, c) => match try_const_usize(self.db, c) {
- Some(x) => x as u64,
+ Some(it) => it as u64,
None => {
not_supported!("indexing array with unknown const from end")
}
},
TyKind::Slice(_) => match metadata {
- Some(x) => from_bytes!(u64, x.get(self)?),
+ Some(it) => from_bytes!(u64, it.get(self)?),
None => not_supported!("slice place without metadata"),
},
_ => not_supported!("bad type for const index"),
@@ -607,13 +707,13 @@ impl Evaluator<'_> {
addr = addr.offset(ty_size * offset);
}
&ProjectionElem::Subslice { from, to } => {
- let inner_ty = match &ty.data(Interner).kind {
+ let inner_ty = match &ty.kind(Interner) {
TyKind::Array(inner, _) | TyKind::Slice(inner) => inner.clone(),
_ => TyKind::Error.intern(Interner),
};
metadata = match metadata {
- Some(x) => {
- let prev_len = from_bytes!(u64, x.get(self)?);
+ Some(it) => {
+ let prev_len = from_bytes!(u64, it.get(self)?);
Some(IntervalOrOwned::Owned(
(prev_len - from - to).to_le_bytes().to_vec(),
))
@@ -636,8 +736,8 @@ impl Evaluator<'_> {
Variants::Single { .. } => &layout,
Variants::Multiple { variants, .. } => {
&variants[match f.parent {
- hir_def::VariantId::EnumVariantId(x) => {
- RustcEnumVariantIdx(x.local_id)
+ hir_def::VariantId::EnumVariantId(it) => {
+ RustcEnumVariantIdx(it.local_id)
}
_ => {
return Err(MirEvalError::TypeError(
@@ -652,8 +752,10 @@ impl Evaluator<'_> {
.offset(u32::from(f.local_id.into_raw()) as usize)
.bytes_usize();
addr = addr.offset(offset);
- // FIXME: support structs with unsized fields
- metadata = None;
+ // Unsized field metadata is equal to the metadata of the struct
+ if self.size_align_of(&ty, locals)?.is_some() {
+ metadata = None;
+ }
}
ProjectionElem::OpaqueCast(_) => not_supported!("opaque cast"),
}
@@ -662,22 +764,26 @@ impl Evaluator<'_> {
}
fn layout(&self, ty: &Ty) -> Result<Arc<Layout>> {
- self.db
- .layout_of_ty(ty.clone(), self.crate_id)
- .map_err(|e| MirEvalError::LayoutError(e, ty.clone()))
+ if let Some(x) = self.layout_cache.borrow().get(ty) {
+ return Ok(x.clone());
+ }
+ let r = self
+ .db
+ .layout_of_ty(ty.clone(), self.trait_env.clone())
+ .map_err(|e| MirEvalError::LayoutError(e, ty.clone()))?;
+ self.layout_cache.borrow_mut().insert(ty.clone(), r.clone());
+ Ok(r)
}
fn layout_adt(&self, adt: AdtId, subst: Substitution) -> Result<Arc<Layout>> {
- self.db.layout_of_adt(adt, subst.clone(), self.crate_id).map_err(|e| {
- MirEvalError::LayoutError(e, TyKind::Adt(chalk_ir::AdtId(adt), subst).intern(Interner))
- })
+ self.layout(&TyKind::Adt(chalk_ir::AdtId(adt), subst).intern(Interner))
}
- fn place_ty<'a>(&'a self, p: &Place, locals: &'a Locals<'a>) -> Result<Ty> {
+ fn place_ty<'a>(&'a self, p: &Place, locals: &'a Locals) -> Result<Ty> {
Ok(self.place_addr_and_ty_and_metadata(p, locals)?.1)
}
- fn operand_ty(&self, o: &Operand, locals: &Locals<'_>) -> Result<Ty> {
+ fn operand_ty(&self, o: &Operand, locals: &Locals) -> Result<Ty> {
Ok(match o {
Operand::Copy(p) | Operand::Move(p) => self.place_ty(p, locals)?,
Operand::Constant(c) => c.data(Interner).ty.clone(),
@@ -688,11 +794,7 @@ impl Evaluator<'_> {
})
}
- fn operand_ty_and_eval(
- &mut self,
- o: &Operand,
- locals: &mut Locals<'_>,
- ) -> Result<IntervalAndTy> {
+ fn operand_ty_and_eval(&mut self, o: &Operand, locals: &mut Locals) -> Result<IntervalAndTy> {
Ok(IntervalAndTy {
interval: self.eval_operand(o, locals)?,
ty: self.operand_ty(o, locals)?,
@@ -701,39 +803,178 @@ impl Evaluator<'_> {
fn interpret_mir(
&mut self,
- body: &MirBody,
- args: impl Iterator<Item = Vec<u8>>,
+ body: Arc<MirBody>,
+ args: impl Iterator<Item = IntervalOrOwned>,
) -> Result<Vec<u8>> {
- if let Some(x) = self.stack_depth_limit.checked_sub(1) {
- self.stack_depth_limit = x;
+ if let Some(it) = self.stack_depth_limit.checked_sub(1) {
+ self.stack_depth_limit = it;
} else {
return Err(MirEvalError::StackOverflow);
}
let mut current_block_idx = body.start_block;
- let mut locals =
- Locals { ptr: &ArenaMap::new(), body: &body, drop_flags: DropFlags::default() };
- let (locals_ptr, stack_size) = {
- let mut stack_ptr = self.stack.len();
- let addr = body
- .locals
- .iter()
- .map(|(id, x)| {
- let size =
- self.size_of_sized(&x.ty, &locals, "no unsized local in extending stack")?;
- let my_ptr = stack_ptr;
- stack_ptr += size;
- Ok((id, Interval { addr: Stack(my_ptr), size }))
- })
- .collect::<Result<ArenaMap<LocalId, _>>>()?;
- let stack_size = stack_ptr - self.stack.len();
- (addr, stack_size)
- };
- locals.ptr = &locals_ptr;
- self.stack.extend(iter::repeat(0).take(stack_size));
+ let (mut locals, prev_stack_ptr) = self.create_locals_for_body(&body, None)?;
+ self.fill_locals_for_body(&body, &mut locals, args)?;
+ let prev_code_stack = mem::take(&mut self.code_stack);
+ let span = (MirSpan::Unknown, body.owner);
+ self.code_stack.push(StackFrame { locals, destination: None, prev_stack_ptr, span });
+ 'stack: loop {
+ let Some(mut my_stack_frame) = self.code_stack.pop() else {
+ not_supported!("missing stack frame");
+ };
+ let e = (|| {
+ let mut locals = &mut my_stack_frame.locals;
+ let body = locals.body.clone();
+ loop {
+ let current_block = &body.basic_blocks[current_block_idx];
+ if let Some(it) = self.execution_limit.checked_sub(1) {
+ self.execution_limit = it;
+ } else {
+ return Err(MirEvalError::ExecutionLimitExceeded);
+ }
+ for statement in &current_block.statements {
+ match &statement.kind {
+ StatementKind::Assign(l, r) => {
+ let addr = self.place_addr(l, &locals)?;
+ let result = self.eval_rvalue(r, &mut locals)?.to_vec(&self)?;
+ self.write_memory(addr, &result)?;
+ locals.drop_flags.add_place(l.clone());
+ }
+ StatementKind::Deinit(_) => not_supported!("de-init statement"),
+ StatementKind::StorageLive(_)
+ | StatementKind::StorageDead(_)
+ | StatementKind::Nop => (),
+ }
+ }
+ let Some(terminator) = current_block.terminator.as_ref() else {
+ not_supported!("block without terminator");
+ };
+ match &terminator.kind {
+ TerminatorKind::Goto { target } => {
+ current_block_idx = *target;
+ }
+ TerminatorKind::Call {
+ func,
+ args,
+ destination,
+ target,
+ cleanup: _,
+ from_hir_call: _,
+ } => {
+ let destination_interval = self.place_interval(destination, &locals)?;
+ let fn_ty = self.operand_ty(func, &locals)?;
+ let args = args
+ .iter()
+ .map(|it| self.operand_ty_and_eval(it, &mut locals))
+ .collect::<Result<Vec<_>>>()?;
+ let stack_frame = match &fn_ty.kind(Interner) {
+ TyKind::Function(_) => {
+ let bytes = self.eval_operand(func, &mut locals)?;
+ self.exec_fn_pointer(
+ bytes,
+ destination_interval,
+ &args,
+ &locals,
+ *target,
+ terminator.span,
+ )?
+ }
+ TyKind::FnDef(def, generic_args) => self.exec_fn_def(
+ *def,
+ generic_args,
+ destination_interval,
+ &args,
+ &locals,
+ *target,
+ terminator.span,
+ )?,
+ it => not_supported!("unknown function type {it:?}"),
+ };
+ locals.drop_flags.add_place(destination.clone());
+ if let Some(stack_frame) = stack_frame {
+ self.code_stack.push(my_stack_frame);
+ current_block_idx = stack_frame.locals.body.start_block;
+ self.code_stack.push(stack_frame);
+ return Ok(None);
+ } else {
+ current_block_idx =
+ target.ok_or(MirEvalError::UndefinedBehavior(
+ "Diverging function returned".to_owned(),
+ ))?;
+ }
+ }
+ TerminatorKind::SwitchInt { discr, targets } => {
+ let val = u128::from_le_bytes(pad16(
+ self.eval_operand(discr, &mut locals)?.get(&self)?,
+ false,
+ ));
+ current_block_idx = targets.target_for_value(val);
+ }
+ TerminatorKind::Return => {
+ break;
+ }
+ TerminatorKind::Unreachable => {
+ return Err(MirEvalError::UndefinedBehavior(
+ "unreachable executed".to_owned(),
+ ));
+ }
+ TerminatorKind::Drop { place, target, unwind: _ } => {
+ self.drop_place(place, &mut locals, terminator.span)?;
+ current_block_idx = *target;
+ }
+ _ => not_supported!("unknown terminator"),
+ }
+ }
+ Ok(Some(my_stack_frame))
+ })();
+ let my_stack_frame = match e {
+ Ok(None) => continue 'stack,
+ Ok(Some(x)) => x,
+ Err(e) => {
+ let my_code_stack = mem::replace(&mut self.code_stack, prev_code_stack);
+ let mut error_stack = vec![];
+ for frame in my_code_stack.into_iter().rev() {
+ if let DefWithBodyId::FunctionId(f) = frame.locals.body.owner {
+ error_stack.push((Either::Left(f), frame.span.0, frame.span.1));
+ }
+ }
+ return Err(MirEvalError::InFunction(Box::new(e), error_stack));
+ }
+ };
+ let return_interval = my_stack_frame.locals.ptr[return_slot()];
+ self.unused_locals_store
+ .borrow_mut()
+ .entry(my_stack_frame.locals.body.owner)
+ .or_default()
+ .push(my_stack_frame.locals);
+ match my_stack_frame.destination {
+ None => {
+ self.code_stack = prev_code_stack;
+ self.stack_depth_limit += 1;
+ return Ok(return_interval.get(self)?.to_vec());
+ }
+ Some(bb) => {
+ // We don't support const promotion, so we can't truncate the stack yet.
+ let _ = my_stack_frame.prev_stack_ptr;
+ // self.stack.truncate(my_stack_frame.prev_stack_ptr);
+ current_block_idx = bb;
+ }
+ }
+ }
+ }
+
+ fn fill_locals_for_body(
+ &mut self,
+ body: &MirBody,
+ locals: &mut Locals,
+ args: impl Iterator<Item = IntervalOrOwned>,
+ ) -> Result<()> {
let mut remain_args = body.param_locals.len();
- for ((l, interval), value) in locals_ptr.iter().skip(1).zip(args) {
+ for ((l, interval), value) in locals.ptr.iter().skip(1).zip(args) {
locals.drop_flags.add_place(l.into());
- interval.write_from_bytes(self, &value)?;
+ match value {
+ IntervalOrOwned::Owned(value) => interval.write_from_bytes(self, &value)?,
+ IntervalOrOwned::Borrowed(value) => interval.write_from_interval(self, value)?,
+ }
if remain_args == 0 {
return Err(MirEvalError::TypeError("more arguments provided"));
}
@@ -742,101 +983,64 @@ impl Evaluator<'_> {
if remain_args > 0 {
return Err(MirEvalError::TypeError("not enough arguments provided"));
}
- loop {
- let current_block = &body.basic_blocks[current_block_idx];
- if let Some(x) = self.execution_limit.checked_sub(1) {
- self.execution_limit = x;
- } else {
- return Err(MirEvalError::ExecutionLimitExceeded);
- }
- for statement in &current_block.statements {
- match &statement.kind {
- StatementKind::Assign(l, r) => {
- let addr = self.place_addr(l, &locals)?;
- let result = self.eval_rvalue(r, &mut locals)?.to_vec(&self)?;
- self.write_memory(addr, &result)?;
- locals.drop_flags.add_place(l.clone());
- }
- StatementKind::Deinit(_) => not_supported!("de-init statement"),
- StatementKind::StorageLive(_)
- | StatementKind::StorageDead(_)
- | StatementKind::Nop => (),
+ Ok(())
+ }
+
+ fn create_locals_for_body(
+ &mut self,
+ body: &Arc<MirBody>,
+ destination: Option<Interval>,
+ ) -> Result<(Locals, usize)> {
+ let mut locals =
+ match self.unused_locals_store.borrow_mut().entry(body.owner).or_default().pop() {
+ None => Locals {
+ ptr: ArenaMap::new(),
+ body: body.clone(),
+ drop_flags: DropFlags::default(),
+ },
+ Some(mut l) => {
+ l.drop_flags.clear();
+ l.body = body.clone();
+ l
}
- }
- let Some(terminator) = current_block.terminator.as_ref() else {
- not_supported!("block without terminator");
};
- match &terminator.kind {
- TerminatorKind::Goto { target } => {
- current_block_idx = *target;
- }
- TerminatorKind::Call {
- func,
- args,
- destination,
- target,
- cleanup: _,
- from_hir_call: _,
- } => {
- let destination_interval = self.place_interval(destination, &locals)?;
- let fn_ty = self.operand_ty(func, &locals)?;
- let args = args
- .iter()
- .map(|x| self.operand_ty_and_eval(x, &mut locals))
- .collect::<Result<Vec<_>>>()?;
- match &fn_ty.data(Interner).kind {
- TyKind::Function(_) => {
- let bytes = self.eval_operand(func, &mut locals)?;
- self.exec_fn_pointer(
- bytes,
- destination_interval,
- &args,
- &locals,
- terminator.span,
- )?;
- }
- TyKind::FnDef(def, generic_args) => {
- self.exec_fn_def(
- *def,
- generic_args,
- destination_interval,
- &args,
- &locals,
- terminator.span,
- )?;
- }
- x => not_supported!("unknown function type {x:?}"),
+ let stack_size = {
+ let mut stack_ptr = self.stack.len();
+ for (id, it) in body.locals.iter() {
+ if id == return_slot() {
+ if let Some(destination) = destination {
+ locals.ptr.insert(id, destination);
+ continue;
}
- locals.drop_flags.add_place(destination.clone());
- current_block_idx = target.expect("broken mir, function without target");
- }
- TerminatorKind::SwitchInt { discr, targets } => {
- let val = u128::from_le_bytes(pad16(
- self.eval_operand(discr, &mut locals)?.get(&self)?,
- false,
- ));
- current_block_idx = targets.target_for_value(val);
- }
- TerminatorKind::Return => {
- self.stack_depth_limit += 1;
- return Ok(locals.ptr[return_slot()].get(self)?.to_vec());
}
- TerminatorKind::Unreachable => {
- return Err(MirEvalError::UndefinedBehavior("unreachable executed".to_owned()));
- }
- TerminatorKind::Drop { place, target, unwind: _ } => {
- self.drop_place(place, &mut locals, terminator.span)?;
- current_block_idx = *target;
+ let (size, align) = self.size_align_of_sized(
+ &it.ty,
+ &locals,
+ "no unsized local in extending stack",
+ )?;
+ while stack_ptr % align != 0 {
+ stack_ptr += 1;
}
- _ => not_supported!("unknown terminator"),
+ let my_ptr = stack_ptr;
+ stack_ptr += size;
+ locals.ptr.insert(id, Interval { addr: Stack(my_ptr), size });
}
+ stack_ptr - self.stack.len()
+ };
+ let prev_stack_pointer = self.stack.len();
+ if stack_size > self.memory_limit {
+ return Err(MirEvalError::Panic(format!(
+ "Stack overflow. Tried to grow stack to {stack_size} bytes"
+ )));
}
+ self.stack.extend(iter::repeat(0).take(stack_size));
+ Ok((locals, prev_stack_pointer))
}
- fn eval_rvalue(&mut self, r: &Rvalue, locals: &mut Locals<'_>) -> Result<IntervalOrOwned> {
+ fn eval_rvalue(&mut self, r: &Rvalue, locals: &mut Locals) -> Result<IntervalOrOwned> {
use IntervalOrOwned::*;
Ok(match r {
- Rvalue::Use(x) => Borrowed(self.eval_operand(x, locals)?),
+ Rvalue::Use(it) => Borrowed(self.eval_operand(it, locals)?),
Rvalue::Ref(_, p) => {
let (addr, _, metadata) = self.place_addr_and_ty_and_metadata(p, locals)?;
let mut r = addr.to_bytes();
@@ -881,9 +1085,9 @@ impl Evaluator<'_> {
c[0] = 1 - c[0];
} else {
match op {
- UnOp::Not => c.iter_mut().for_each(|x| *x = !*x),
+ UnOp::Not => c.iter_mut().for_each(|it| *it = !*it),
UnOp::Neg => {
- c.iter_mut().for_each(|x| *x = !*x);
+ c.iter_mut().for_each(|it| *it = !*it);
for k in c.iter_mut() {
let o;
(*k, o) = k.overflowing_add(1);
@@ -948,8 +1152,8 @@ impl Evaluator<'_> {
};
Owned(r.to_le_bytes().into())
}
- x => not_supported!(
- "invalid binop {x:?} on floating point operators"
+ it => not_supported!(
+ "invalid binop {it:?} on floating point operators"
),
}
}
@@ -976,8 +1180,8 @@ impl Evaluator<'_> {
};
Owned(r.to_le_bytes().into())
}
- x => not_supported!(
- "invalid binop {x:?} on floating point operators"
+ it => not_supported!(
+ "invalid binop {it:?} on floating point operators"
),
}
}
@@ -1034,13 +1238,18 @@ impl Evaluator<'_> {
BinOp::Shr => l128.checked_shr(shift_amount),
_ => unreachable!(),
};
+ if shift_amount as usize >= lc.len() * 8 {
+ return Err(MirEvalError::Panic(format!(
+ "Overflow in {op:?}"
+ )));
+ }
if let Some(r) = r {
break 'b r;
}
};
return Err(MirEvalError::Panic(format!("Overflow in {op:?}")));
};
- check_overflow(r)?
+ Owned(r.to_le_bytes()[..lc.len()].to_vec())
}
BinOp::Offset => not_supported!("offset binop"),
}
@@ -1049,64 +1258,15 @@ impl Evaluator<'_> {
Rvalue::Discriminant(p) => {
let ty = self.place_ty(p, locals)?;
let bytes = self.eval_place(p, locals)?.get(&self)?;
- let layout = self.layout(&ty)?;
- let enum_id = 'b: {
- match ty.kind(Interner) {
- TyKind::Adt(e, _) => match e.0 {
- AdtId::EnumId(e) => break 'b e,
- _ => (),
- },
- _ => (),
- }
- return Ok(Owned(0u128.to_le_bytes().to_vec()));
- };
- match &layout.variants {
- Variants::Single { index } => {
- let r = self.const_eval_discriminant(EnumVariantId {
- parent: enum_id,
- local_id: index.0,
- })?;
- Owned(r.to_le_bytes().to_vec())
- }
- Variants::Multiple { tag, tag_encoding, variants, .. } => {
- let Some(target_data_layout) = self.db.target_data_layout(self.crate_id) else {
- not_supported!("missing target data layout");
- };
- let size = tag.size(&*target_data_layout).bytes_usize();
- let offset = layout.fields.offset(0).bytes_usize(); // The only field on enum variants is the tag field
- match tag_encoding {
- TagEncoding::Direct => {
- let tag = &bytes[offset..offset + size];
- Owned(pad16(tag, false).to_vec())
- }
- TagEncoding::Niche { untagged_variant, niche_start, .. } => {
- let tag = &bytes[offset..offset + size];
- let candidate_tag = i128::from_le_bytes(pad16(tag, false))
- .wrapping_sub(*niche_start as i128)
- as usize;
- let variant = variants
- .iter_enumerated()
- .map(|(x, _)| x)
- .filter(|x| x != untagged_variant)
- .nth(candidate_tag)
- .unwrap_or(*untagged_variant)
- .0;
- let result = self.const_eval_discriminant(EnumVariantId {
- parent: enum_id,
- local_id: variant,
- })?;
- Owned(result.to_le_bytes().to_vec())
- }
- }
- }
- }
+ let result = self.compute_discriminant(ty, bytes)?;
+ Owned(result.to_le_bytes().to_vec())
}
- Rvalue::Repeat(x, len) => {
+ Rvalue::Repeat(it, len) => {
let len = match try_const_usize(self.db, &len) {
- Some(x) => x as usize,
+ Some(it) => it as usize,
None => not_supported!("non evaluatable array len in repeat Rvalue"),
};
- let val = self.eval_operand(x, locals)?.get(self)?;
+ let val = self.eval_operand(it, locals)?.get(self)?;
let size = len * val.len();
Owned(val.iter().copied().cycle().take(size).collect())
}
@@ -1115,20 +1275,20 @@ impl Evaluator<'_> {
let Some((size, align)) = self.size_align_of(ty, locals)? else {
not_supported!("unsized box initialization");
};
- let addr = self.heap_allocate(size, align);
+ let addr = self.heap_allocate(size, align)?;
Owned(addr.to_bytes())
}
Rvalue::CopyForDeref(_) => not_supported!("copy for deref"),
Rvalue::Aggregate(kind, values) => {
let values = values
.iter()
- .map(|x| self.eval_operand(x, locals))
+ .map(|it| self.eval_operand(it, locals))
.collect::<Result<Vec<_>>>()?;
match kind {
AggregateKind::Array(_) => {
let mut r = vec![];
- for x in values {
- let value = x.get(&self)?;
+ for it in values {
+ let value = it.get(&self)?;
r.extend(value);
}
Owned(r)
@@ -1139,11 +1299,12 @@ impl Evaluator<'_> {
layout.size.bytes_usize(),
&layout,
None,
- values.iter().map(|&x| x.into()),
+ values.iter().map(|&it| it.into()),
)?)
}
- AggregateKind::Union(x, f) => {
- let layout = self.layout_adt((*x).into(), Substitution::empty(Interner))?;
+ AggregateKind::Union(it, f) => {
+ let layout =
+ self.layout_adt((*it).into(), Substitution::empty(Interner))?;
let offset = layout
.fields
.offset(u32::from(f.local_id.into_raw()) as usize)
@@ -1153,14 +1314,14 @@ impl Evaluator<'_> {
result[offset..offset + op.len()].copy_from_slice(op);
Owned(result)
}
- AggregateKind::Adt(x, subst) => {
+ AggregateKind::Adt(it, subst) => {
let (size, variant_layout, tag) =
- self.layout_of_variant(*x, subst.clone(), locals)?;
+ self.layout_of_variant(*it, subst.clone(), locals)?;
Owned(self.make_by_layout(
size,
&variant_layout,
tag,
- values.iter().map(|&x| x.into()),
+ values.iter().map(|&it| it.into()),
)?)
}
AggregateKind::Closure(ty) => {
@@ -1169,7 +1330,7 @@ impl Evaluator<'_> {
layout.size.bytes_usize(),
&layout,
None,
- values.iter().map(|&x| x.into()),
+ values.iter().map(|&it| it.into()),
)?)
}
}
@@ -1179,7 +1340,7 @@ impl Evaluator<'_> {
PointerCast::ReifyFnPointer | PointerCast::ClosureFnPointer(_) => {
let current_ty = self.operand_ty(operand, locals)?;
if let TyKind::FnDef(_, _) | TyKind::Closure(_, _) =
- &current_ty.data(Interner).kind
+ &current_ty.kind(Interner)
{
let id = self.vtable_map.id(current_ty);
let ptr_size = self.ptr_size();
@@ -1229,21 +1390,75 @@ impl Evaluator<'_> {
})
}
+ fn compute_discriminant(&self, ty: Ty, bytes: &[u8]) -> Result<i128> {
+ let layout = self.layout(&ty)?;
+ let enum_id = 'b: {
+ match ty.kind(Interner) {
+ TyKind::Adt(e, _) => match e.0 {
+ AdtId::EnumId(e) => break 'b e,
+ _ => (),
+ },
+ _ => (),
+ }
+ return Ok(0);
+ };
+ match &layout.variants {
+ Variants::Single { index } => {
+ let r = self.const_eval_discriminant(EnumVariantId {
+ parent: enum_id,
+ local_id: index.0,
+ })?;
+ Ok(r)
+ }
+ Variants::Multiple { tag, tag_encoding, variants, .. } => {
+ let Some(target_data_layout) = self.db.target_data_layout(self.crate_id) else {
+ not_supported!("missing target data layout");
+ };
+ let size = tag.size(&*target_data_layout).bytes_usize();
+ let offset = layout.fields.offset(0).bytes_usize(); // The only field on enum variants is the tag field
+ match tag_encoding {
+ TagEncoding::Direct => {
+ let tag = &bytes[offset..offset + size];
+ Ok(i128::from_le_bytes(pad16(tag, false)))
+ }
+ TagEncoding::Niche { untagged_variant, niche_start, .. } => {
+ let tag = &bytes[offset..offset + size];
+ let candidate_tag = i128::from_le_bytes(pad16(tag, false))
+ .wrapping_sub(*niche_start as i128)
+ as usize;
+ let variant = variants
+ .iter_enumerated()
+ .map(|(it, _)| it)
+ .filter(|it| it != untagged_variant)
+ .nth(candidate_tag)
+ .unwrap_or(*untagged_variant)
+ .0;
+ let result = self.const_eval_discriminant(EnumVariantId {
+ parent: enum_id,
+ local_id: variant,
+ })?;
+ Ok(result)
+ }
+ }
+ }
+ }
+ }
+
fn coerce_unsized_look_through_fields<T>(
&self,
ty: &Ty,
goal: impl Fn(&TyKind) -> Option<T>,
) -> Result<T> {
let kind = ty.kind(Interner);
- if let Some(x) = goal(kind) {
- return Ok(x);
+ if let Some(it) = goal(kind) {
+ return Ok(it);
}
if let TyKind::Adt(id, subst) = kind {
if let AdtId::StructId(struct_id) = id.0 {
let field_types = self.db.field_types(struct_id.into());
let mut field_types = field_types.iter();
if let Some(ty) =
- field_types.next().map(|x| x.1.clone().substitute(Interner, subst))
+ field_types.next().map(|it| it.1.clone().substitute(Interner, subst))
{
return self.coerce_unsized_look_through_fields(&ty, goal);
}
@@ -1258,66 +1473,99 @@ impl Evaluator<'_> {
current_ty: &Ty,
target_ty: &Ty,
) -> Result<IntervalOrOwned> {
- use IntervalOrOwned::*;
- fn for_ptr(x: &TyKind) -> Option<Ty> {
- match x {
+ fn for_ptr(it: &TyKind) -> Option<Ty> {
+ match it {
TyKind::Raw(_, ty) | TyKind::Ref(_, _, ty) => Some(ty.clone()),
_ => None,
}
}
- Ok(match self.coerce_unsized_look_through_fields(target_ty, for_ptr)? {
- ty => match &ty.data(Interner).kind {
- TyKind::Slice(_) => {
- match self.coerce_unsized_look_through_fields(current_ty, for_ptr)? {
- ty => match &ty.data(Interner).kind {
- TyKind::Array(_, size) => {
- let len = match try_const_usize(self.db, size) {
- None => not_supported!(
- "unevaluatble len of array in coerce unsized"
- ),
- Some(x) => x as usize,
- };
- let mut r = Vec::with_capacity(16);
- let addr = addr.get(self)?;
- r.extend(addr.iter().copied());
- r.extend(len.to_le_bytes().into_iter());
- Owned(r)
- }
- t => {
- not_supported!("slice unsizing from non array type {t:?}")
- }
- },
- }
+ let target_ty = self.coerce_unsized_look_through_fields(target_ty, for_ptr)?;
+ let current_ty = self.coerce_unsized_look_through_fields(current_ty, for_ptr)?;
+
+ self.unsizing_ptr_from_addr(target_ty, current_ty, addr)
+ }
+
+ /// Adds metadata to the address and create the fat pointer result of the unsizing operation.
+ fn unsizing_ptr_from_addr(
+ &mut self,
+ target_ty: Ty,
+ current_ty: Ty,
+ addr: Interval,
+ ) -> Result<IntervalOrOwned> {
+ use IntervalOrOwned::*;
+ Ok(match &target_ty.kind(Interner) {
+ TyKind::Slice(_) => match &current_ty.kind(Interner) {
+ TyKind::Array(_, size) => {
+ let len = match try_const_usize(self.db, size) {
+ None => {
+ not_supported!("unevaluatble len of array in coerce unsized")
+ }
+ Some(it) => it as usize,
+ };
+ let mut r = Vec::with_capacity(16);
+ let addr = addr.get(self)?;
+ r.extend(addr.iter().copied());
+ r.extend(len.to_le_bytes().into_iter());
+ Owned(r)
}
- TyKind::Dyn(_) => match &current_ty.data(Interner).kind {
- TyKind::Raw(_, ty) | TyKind::Ref(_, _, ty) => {
- let vtable = self.vtable_map.id(ty.clone());
- let mut r = Vec::with_capacity(16);
- let addr = addr.get(self)?;
- r.extend(addr.iter().copied());
- r.extend(vtable.to_le_bytes().into_iter());
- Owned(r)
+ t => {
+ not_supported!("slice unsizing from non array type {t:?}")
+ }
+ },
+ TyKind::Dyn(_) => {
+ let vtable = self.vtable_map.id(current_ty.clone());
+ let mut r = Vec::with_capacity(16);
+ let addr = addr.get(self)?;
+ r.extend(addr.iter().copied());
+ r.extend(vtable.to_le_bytes().into_iter());
+ Owned(r)
+ }
+ TyKind::Adt(id, target_subst) => match &current_ty.kind(Interner) {
+ TyKind::Adt(current_id, current_subst) => {
+ if id != current_id {
+ not_supported!("unsizing struct with different type");
}
- _ => not_supported!("dyn unsizing from non pointers"),
- },
- _ => not_supported!("unknown unsized cast"),
+ let id = match id.0 {
+ AdtId::StructId(s) => s,
+ AdtId::UnionId(_) => not_supported!("unsizing unions"),
+ AdtId::EnumId(_) => not_supported!("unsizing enums"),
+ };
+ let Some((last_field, _)) =
+ self.db.struct_data(id).variant_data.fields().iter().rev().next()
+ else {
+ not_supported!("unsizing struct without field");
+ };
+ let target_last_field = self.db.field_types(id.into())[last_field]
+ .clone()
+ .substitute(Interner, target_subst);
+ let current_last_field = self.db.field_types(id.into())[last_field]
+ .clone()
+ .substitute(Interner, current_subst);
+ return self.unsizing_ptr_from_addr(
+ target_last_field,
+ current_last_field,
+ addr,
+ );
+ }
+ _ => not_supported!("unsizing struct with non adt type"),
},
+ _ => not_supported!("unknown unsized cast"),
})
}
fn layout_of_variant(
&mut self,
- x: VariantId,
+ it: VariantId,
subst: Substitution,
- locals: &Locals<'_>,
+ locals: &Locals,
) -> Result<(usize, Arc<Layout>, Option<(usize, usize, i128)>)> {
- let adt = x.adt_id();
+ let adt = it.adt_id();
if let DefWithBodyId::VariantId(f) = locals.body.owner {
- if let VariantId::EnumVariantId(x) = x {
+ if let VariantId::EnumVariantId(it) = it {
if AdtId::from(f.parent) == adt {
// Computing the exact size of enums require resolving the enum discriminants. In order to prevent loops (and
// infinite sized type errors) we use a dummy layout
- let i = self.const_eval_discriminant(x)?;
+ let i = self.const_eval_discriminant(it)?;
return Ok((16, self.layout(&TyBuilder::unit())?, Some((0, 16, i))));
}
}
@@ -1330,8 +1578,8 @@ impl Evaluator<'_> {
.db
.target_data_layout(self.crate_id)
.ok_or(MirEvalError::TargetDataLayoutNotAvailable)?;
- let enum_variant_id = match x {
- VariantId::EnumVariantId(x) => x,
+ let enum_variant_id = match it {
+ VariantId::EnumVariantId(it) => it,
_ => not_supported!("multi variant layout for non-enums"),
};
let rustc_enum_variant_idx = RustcEnumVariantIdx(enum_variant_id.local_id);
@@ -1345,8 +1593,8 @@ impl Evaluator<'_> {
} else {
discriminant = (variants
.iter_enumerated()
- .filter(|(x, _)| x != untagged_variant)
- .position(|(x, _)| x == rustc_enum_variant_idx)
+ .filter(|(it, _)| it != untagged_variant)
+ .position(|(it, _)| it == rustc_enum_variant_idx)
.unwrap() as i128)
.wrapping_add(*niche_start as i128);
true
@@ -1379,18 +1627,24 @@ impl Evaluator<'_> {
) -> Result<Vec<u8>> {
let mut result = vec![0; size];
if let Some((offset, size, value)) = tag {
- result[offset..offset + size].copy_from_slice(&value.to_le_bytes()[0..size]);
+ match result.get_mut(offset..offset + size) {
+ Some(it) => it.copy_from_slice(&value.to_le_bytes()[0..size]),
+ None => return Err(MirEvalError::BrokenLayout(variant_layout.clone())),
+ }
}
for (i, op) in values.enumerate() {
let offset = variant_layout.fields.offset(i).bytes_usize();
let op = op.get(&self)?;
- result[offset..offset + op.len()].copy_from_slice(op);
+ match result.get_mut(offset..offset + op.len()) {
+ Some(it) => it.copy_from_slice(op),
+ None => return Err(MirEvalError::BrokenLayout(variant_layout.clone())),
+ }
}
Ok(result)
}
- fn eval_operand(&mut self, x: &Operand, locals: &mut Locals<'_>) -> Result<Interval> {
- Ok(match x {
+ fn eval_operand(&mut self, it: &Operand, locals: &mut Locals) -> Result<Interval> {
+ Ok(match it {
Operand::Copy(p) | Operand::Move(p) => {
locals.drop_flags.remove_place(p);
self.eval_place(p, locals)?
@@ -1399,61 +1653,66 @@ impl Evaluator<'_> {
let addr = self.eval_static(*st, locals)?;
Interval::new(addr, self.ptr_size())
}
- Operand::Constant(konst) => {
- let data = &konst.data(Interner);
- match &data.value {
- chalk_ir::ConstValue::BoundVar(_) => not_supported!("bound var constant"),
- chalk_ir::ConstValue::InferenceVar(_) => {
- not_supported!("inference var constant")
- }
- chalk_ir::ConstValue::Placeholder(_) => not_supported!("placeholder constant"),
- chalk_ir::ConstValue::Concrete(c) => {
- self.allocate_const_in_heap(c, &data.ty, locals, konst)?
- }
- }
- }
+ Operand::Constant(konst) => self.allocate_const_in_heap(locals, konst)?,
})
}
- fn allocate_const_in_heap(
- &mut self,
- c: &chalk_ir::ConcreteConst<Interner>,
- ty: &Ty,
- locals: &Locals<'_>,
- konst: &chalk_ir::Const<Interner>,
- ) -> Result<Interval> {
- Ok(match &c.interned {
- ConstScalar::Bytes(v, memory_map) => {
- let mut v: Cow<'_, [u8]> = Cow::Borrowed(v);
- let patch_map = memory_map.transform_addresses(|b| {
- let addr = self.heap_allocate(b.len(), 1); // FIXME: align is wrong
- self.write_memory(addr, b)?;
- Ok(addr.to_usize())
- })?;
- let (size, align) = self.size_align_of(ty, locals)?.unwrap_or((v.len(), 1));
- if size != v.len() {
- // Handle self enum
- if size == 16 && v.len() < 16 {
- v = Cow::Owned(pad16(&v, false).to_vec());
- } else if size < 16 && v.len() == 16 {
- v = Cow::Owned(v[0..size].to_vec());
- } else {
- return Err(MirEvalError::InvalidConst(konst.clone()));
+ fn allocate_const_in_heap(&mut self, locals: &Locals, konst: &Const) -> Result<Interval> {
+ let ty = &konst.data(Interner).ty;
+ let chalk_ir::ConstValue::Concrete(c) = &konst.data(Interner).value else {
+ not_supported!("evaluating non concrete constant");
+ };
+ let result_owner;
+ let (v, memory_map) = match &c.interned {
+ ConstScalar::Bytes(v, mm) => (v, mm),
+ ConstScalar::UnevaluatedConst(const_id, subst) => 'b: {
+ let mut const_id = *const_id;
+ let mut subst = subst.clone();
+ if let hir_def::GeneralConstId::ConstId(c) = const_id {
+ let (c, s) = lookup_impl_const(self.db, self.trait_env.clone(), c, subst);
+ const_id = hir_def::GeneralConstId::ConstId(c);
+ subst = s;
+ }
+ result_owner = self
+ .db
+ .const_eval(const_id.into(), subst, Some(self.trait_env.clone()))
+ .map_err(|e| {
+ let name = const_id.name(self.db.upcast());
+ MirEvalError::ConstEvalError(name, Box::new(e))
+ })?;
+ if let chalk_ir::ConstValue::Concrete(c) = &result_owner.data(Interner).value {
+ if let ConstScalar::Bytes(v, mm) = &c.interned {
+ break 'b (v, mm);
}
}
- let addr = self.heap_allocate(size, align);
- self.write_memory(addr, &v)?;
- self.patch_addresses(&patch_map, &memory_map.vtable, addr, ty, locals)?;
- Interval::new(addr, size)
- }
- ConstScalar::UnevaluatedConst(..) => {
- not_supported!("unevaluated const present in monomorphized mir");
+ not_supported!("unevaluatable constant");
}
ConstScalar::Unknown => not_supported!("evaluating unknown const"),
- })
+ };
+ let mut v: Cow<'_, [u8]> = Cow::Borrowed(v);
+ let patch_map = memory_map.transform_addresses(|b, align| {
+ let addr = self.heap_allocate(b.len(), align)?;
+ self.write_memory(addr, b)?;
+ Ok(addr.to_usize())
+ })?;
+ let (size, align) = self.size_align_of(ty, locals)?.unwrap_or((v.len(), 1));
+ if size != v.len() {
+ // Handle self enum
+ if size == 16 && v.len() < 16 {
+ v = Cow::Owned(pad16(&v, false).to_vec());
+ } else if size < 16 && v.len() == 16 {
+ v = Cow::Owned(v[0..size].to_vec());
+ } else {
+ return Err(MirEvalError::InvalidConst(konst.clone()));
+ }
+ }
+ let addr = self.heap_allocate(size, align)?;
+ self.write_memory(addr, &v)?;
+ self.patch_addresses(&patch_map, &memory_map.vtable, addr, ty, locals)?;
+ Ok(Interval::new(addr, size))
}
- fn eval_place(&mut self, p: &Place, locals: &Locals<'_>) -> Result<Interval> {
+ fn eval_place(&mut self, p: &Place, locals: &Locals) -> Result<Interval> {
let addr = self.place_addr(p, locals)?;
Ok(Interval::new(
addr,
@@ -1466,11 +1725,11 @@ impl Evaluator<'_> {
return Ok(&[]);
}
let (mem, pos) = match addr {
- Stack(x) => (&self.stack, x),
- Heap(x) => (&self.heap, x),
- Invalid(x) => {
+ Stack(it) => (&self.stack, it),
+ Heap(it) => (&self.heap, it),
+ Invalid(it) => {
return Err(MirEvalError::UndefinedBehavior(format!(
- "read invalid memory address {x} with size {size}"
+ "read invalid memory address {it} with size {size}"
)));
}
};
@@ -1478,28 +1737,35 @@ impl Evaluator<'_> {
.ok_or_else(|| MirEvalError::UndefinedBehavior("out of bound memory read".to_string()))
}
- fn write_memory(&mut self, addr: Address, r: &[u8]) -> Result<()> {
- if r.is_empty() {
- return Ok(());
- }
+ fn write_memory_using_ref(&mut self, addr: Address, size: usize) -> Result<&mut [u8]> {
let (mem, pos) = match addr {
- Stack(x) => (&mut self.stack, x),
- Heap(x) => (&mut self.heap, x),
- Invalid(x) => {
+ Stack(it) => (&mut self.stack, it),
+ Heap(it) => (&mut self.heap, it),
+ Invalid(it) => {
return Err(MirEvalError::UndefinedBehavior(format!(
- "write invalid memory address {x} with content {r:?}"
+ "write invalid memory address {it} with size {size}"
)));
}
};
- mem.get_mut(pos..pos + r.len())
- .ok_or_else(|| {
- MirEvalError::UndefinedBehavior("out of bound memory write".to_string())
- })?
- .copy_from_slice(r);
+ Ok(mem.get_mut(pos..pos + size).ok_or_else(|| {
+ MirEvalError::UndefinedBehavior("out of bound memory write".to_string())
+ })?)
+ }
+
+ fn write_memory(&mut self, addr: Address, r: &[u8]) -> Result<()> {
+ if r.is_empty() {
+ return Ok(());
+ }
+ self.write_memory_using_ref(addr, r.len())?.copy_from_slice(r);
Ok(())
}
- fn size_align_of(&self, ty: &Ty, locals: &Locals<'_>) -> Result<Option<(usize, usize)>> {
+ fn size_align_of(&self, ty: &Ty, locals: &Locals) -> Result<Option<(usize, usize)>> {
+ if let Some(layout) = self.layout_cache.borrow().get(ty) {
+ return Ok(layout
+ .is_sized()
+ .then(|| (layout.size.bytes_usize(), layout.align.abi.bytes() as usize)));
+ }
if let DefWithBodyId::VariantId(f) = locals.body.owner {
if let Some((adt, _)) = ty.as_adt() {
if AdtId::from(f.parent) == adt {
@@ -1523,39 +1789,61 @@ impl Evaluator<'_> {
/// A version of `self.size_of` which returns error if the type is unsized. `what` argument should
/// be something that complete this: `error: type {ty} was unsized. {what} should be sized`
- fn size_of_sized(&self, ty: &Ty, locals: &Locals<'_>, what: &'static str) -> Result<usize> {
+ fn size_of_sized(&self, ty: &Ty, locals: &Locals, what: &'static str) -> Result<usize> {
match self.size_align_of(ty, locals)? {
- Some(x) => Ok(x.0),
+ Some(it) => Ok(it.0),
None => Err(MirEvalError::TypeIsUnsized(ty.clone(), what)),
}
}
- fn heap_allocate(&mut self, size: usize, _align: usize) -> Address {
+ /// A version of `self.size_align_of` which returns error if the type is unsized. `what` argument should
+ /// be something that complete this: `error: type {ty} was unsized. {what} should be sized`
+ fn size_align_of_sized(
+ &self,
+ ty: &Ty,
+ locals: &Locals,
+ what: &'static str,
+ ) -> Result<(usize, usize)> {
+ match self.size_align_of(ty, locals)? {
+ Some(it) => Ok(it),
+ None => Err(MirEvalError::TypeIsUnsized(ty.clone(), what)),
+ }
+ }
+
+ fn heap_allocate(&mut self, size: usize, align: usize) -> Result<Address> {
+ if !align.is_power_of_two() || align > 10000 {
+ return Err(MirEvalError::UndefinedBehavior(format!("Alignment {align} is invalid")));
+ }
+ while self.heap.len() % align != 0 {
+ self.heap.push(0);
+ }
+ if size.checked_add(self.heap.len()).map_or(true, |x| x > self.memory_limit) {
+ return Err(MirEvalError::Panic(format!("Memory allocation of {size} bytes failed")));
+ }
let pos = self.heap.len();
self.heap.extend(iter::repeat(0).take(size));
- Address::Heap(pos)
+ Ok(Address::Heap(pos))
}
fn detect_fn_trait(&self, def: FunctionId) -> Option<FnTrait> {
- use LangItem::*;
- let ItemContainerId::TraitId(parent) = self.db.lookup_intern_function(def).container else {
- return None;
- };
- let l = lang_attr(self.db.upcast(), parent)?;
- match l {
- FnOnce => Some(FnTrait::FnOnce),
- FnMut => Some(FnTrait::FnMut),
- Fn => Some(FnTrait::Fn),
- _ => None,
+ let def = Some(def);
+ if def == self.cached_fn_trait_func {
+ Some(FnTrait::Fn)
+ } else if def == self.cached_fn_mut_trait_func {
+ Some(FnTrait::FnMut)
+ } else if def == self.cached_fn_once_trait_func {
+ Some(FnTrait::FnOnce)
+ } else {
+ None
}
}
- fn create_memory_map(&self, bytes: &[u8], ty: &Ty, locals: &Locals<'_>) -> Result<MemoryMap> {
+ fn create_memory_map(&self, bytes: &[u8], ty: &Ty, locals: &Locals) -> Result<MemoryMap> {
fn rec(
this: &Evaluator<'_>,
bytes: &[u8],
ty: &Ty,
- locals: &Locals<'_>,
+ locals: &Locals,
mm: &mut MemoryMap,
) -> Result<()> {
match ty.kind(Interner) {
@@ -1602,6 +1890,17 @@ impl Evaluator<'_> {
}
}
}
+ chalk_ir::TyKind::Array(inner, len) => {
+ let len = match try_const_usize(this.db, &len) {
+ Some(it) => it as usize,
+ None => not_supported!("non evaluatable array len in patching addresses"),
+ };
+ let size = this.size_of_sized(inner, locals, "inner of array")?;
+ for i in 0..len {
+ let offset = i * size;
+ rec(this, &bytes[offset..offset + size], inner, locals, mm)?;
+ }
+ }
chalk_ir::TyKind::Tuple(_, subst) => {
let layout = this.layout(ty)?;
for (id, ty) in subst.iter(Interner).enumerate() {
@@ -1628,9 +1927,13 @@ impl Evaluator<'_> {
}
AdtId::EnumId(e) => {
let layout = this.layout(ty)?;
- if let Some((v, l)) =
- detect_variant_from_bytes(&layout, this.db, this.crate_id, bytes, e)
- {
+ if let Some((v, l)) = detect_variant_from_bytes(
+ &layout,
+ this.db,
+ this.trait_env.clone(),
+ bytes,
+ e,
+ ) {
let data = &this.db.enum_data(e).variants[v].variant_data;
let field_types = this
.db
@@ -1661,7 +1964,7 @@ impl Evaluator<'_> {
old_vtable: &VTableMap,
addr: Address,
ty: &Ty,
- locals: &Locals<'_>,
+ locals: &Locals,
) -> Result<()> {
// FIXME: support indirect references
let layout = self.layout(ty)?;
@@ -1672,14 +1975,14 @@ impl Evaluator<'_> {
match size {
Some(_) => {
let current = from_bytes!(usize, self.read_memory(addr, my_size)?);
- if let Some(x) = patch_map.get(&current) {
- self.write_memory(addr, &x.to_le_bytes())?;
+ if let Some(it) = patch_map.get(&current) {
+ self.write_memory(addr, &it.to_le_bytes())?;
}
}
None => {
let current = from_bytes!(usize, self.read_memory(addr, my_size / 2)?);
- if let Some(x) = patch_map.get(&current) {
- self.write_memory(addr, &x.to_le_bytes())?;
+ if let Some(it) = patch_map.get(&current) {
+ self.write_memory(addr, &it.to_le_bytes())?;
}
}
}
@@ -1706,10 +2009,31 @@ impl Evaluator<'_> {
AdtId::UnionId(_) => (),
AdtId::EnumId(_) => (),
},
+ TyKind::Tuple(_, subst) => {
+ for (id, ty) in subst.iter(Interner).enumerate() {
+ let ty = ty.assert_ty_ref(Interner); // Tuple only has type argument
+ let offset = layout.fields.offset(id).bytes_usize();
+ self.patch_addresses(patch_map, old_vtable, addr.offset(offset), ty, locals)?;
+ }
+ }
+ TyKind::Array(inner, len) => {
+ let len = match try_const_usize(self.db, &len) {
+ Some(it) => it as usize,
+ None => not_supported!("non evaluatable array len in patching addresses"),
+ };
+ let size = self.size_of_sized(inner, locals, "inner of array")?;
+ for i in 0..len {
+ self.patch_addresses(
+ patch_map,
+ old_vtable,
+ addr.offset(i * size),
+ inner,
+ locals,
+ )?;
+ }
+ }
TyKind::AssociatedType(_, _)
| TyKind::Scalar(_)
- | TyKind::Tuple(_, _)
- | TyKind::Array(_, _)
| TyKind::Slice(_)
| TyKind::Raw(_, _)
| TyKind::OpaqueType(_, _)
@@ -1735,21 +2059,21 @@ impl Evaluator<'_> {
bytes: Interval,
destination: Interval,
args: &[IntervalAndTy],
- locals: &Locals<'_>,
+ locals: &Locals,
+ target_bb: Option<BasicBlockId>,
span: MirSpan,
- ) -> Result<()> {
+ ) -> Result<Option<StackFrame>> {
let id = from_bytes!(usize, bytes.get(self)?);
let next_ty = self.vtable_map.ty(id)?.clone();
- match &next_ty.data(Interner).kind {
+ match &next_ty.kind(Interner) {
TyKind::FnDef(def, generic_args) => {
- self.exec_fn_def(*def, generic_args, destination, args, &locals, span)?;
+ self.exec_fn_def(*def, generic_args, destination, args, &locals, target_bb, span)
}
TyKind::Closure(id, subst) => {
- self.exec_closure(*id, bytes.slice(0..0), subst, destination, args, locals, span)?;
+ self.exec_closure(*id, bytes.slice(0..0), subst, destination, args, locals, span)
}
- _ => return Err(MirEvalError::TypeError("function pointer to non function")),
+ _ => Err(MirEvalError::TypeError("function pointer to non function")),
}
- Ok(())
}
fn exec_closure(
@@ -1759,9 +2083,9 @@ impl Evaluator<'_> {
generic_args: &Substitution,
destination: Interval,
args: &[IntervalAndTy],
- locals: &Locals<'_>,
+ locals: &Locals,
span: MirSpan,
- ) -> Result<()> {
+ ) -> Result<Option<StackFrame>> {
let mir_body = self
.db
.monomorphized_mir_body_for_closure(
@@ -1769,7 +2093,7 @@ impl Evaluator<'_> {
generic_args.clone(),
self.trait_env.clone(),
)
- .map_err(|x| MirEvalError::MirLowerErrorForClosure(closure, x))?;
+ .map_err(|it| MirEvalError::MirLowerErrorForClosure(closure, it))?;
let closure_data = if mir_body.locals[mir_body.param_locals[0]].ty.as_reference().is_some()
{
closure_data.addr.to_bytes()
@@ -1777,12 +2101,18 @@ impl Evaluator<'_> {
closure_data.get(self)?.to_owned()
};
let arg_bytes = iter::once(Ok(closure_data))
- .chain(args.iter().map(|x| Ok(x.get(&self)?.to_owned())))
+ .chain(args.iter().map(|it| Ok(it.get(&self)?.to_owned())))
.collect::<Result<Vec<_>>>()?;
- let bytes = self.interpret_mir(&mir_body, arg_bytes.into_iter()).map_err(|e| {
- MirEvalError::InFunction(Either::Right(closure), Box::new(e), span, locals.body.owner)
- })?;
- destination.write_from_bytes(self, &bytes)
+ let bytes = self
+ .interpret_mir(mir_body, arg_bytes.into_iter().map(IntervalOrOwned::Owned))
+ .map_err(|e| {
+ MirEvalError::InFunction(
+ Box::new(e),
+ vec![(Either::Right(closure), span, locals.body.owner)],
+ )
+ })?;
+ destination.write_from_bytes(self, &bytes)?;
+ Ok(None)
}
fn exec_fn_def(
@@ -1791,18 +2121,34 @@ impl Evaluator<'_> {
generic_args: &Substitution,
destination: Interval,
args: &[IntervalAndTy],
- locals: &Locals<'_>,
+ locals: &Locals,
+ target_bb: Option<BasicBlockId>,
span: MirSpan,
- ) -> Result<()> {
+ ) -> Result<Option<StackFrame>> {
let def: CallableDefId = from_chalk(self.db, def);
let generic_args = generic_args.clone();
match def {
CallableDefId::FunctionId(def) => {
if let Some(_) = self.detect_fn_trait(def) {
- self.exec_fn_trait(&args, destination, locals, span)?;
- return Ok(());
+ return self.exec_fn_trait(
+ def,
+ args,
+ generic_args,
+ locals,
+ destination,
+ target_bb,
+ span,
+ );
}
- self.exec_fn_with_args(def, args, generic_args, locals, destination, span)?;
+ self.exec_fn_with_args(
+ def,
+ args,
+ generic_args,
+ locals,
+ destination,
+ target_bb,
+ span,
+ )
}
CallableDefId::StructId(id) => {
let (size, variant_layout, tag) =
@@ -1811,9 +2157,10 @@ impl Evaluator<'_> {
size,
&variant_layout,
tag,
- args.iter().map(|x| x.interval.into()),
+ args.iter().map(|it| it.interval.into()),
)?;
destination.write_from_bytes(self, &result)?;
+ Ok(None)
}
CallableDefId::EnumVariantId(id) => {
let (size, variant_layout, tag) =
@@ -1822,12 +2169,46 @@ impl Evaluator<'_> {
size,
&variant_layout,
tag,
- args.iter().map(|x| x.interval.into()),
+ args.iter().map(|it| it.interval.into()),
)?;
destination.write_from_bytes(self, &result)?;
+ Ok(None)
}
}
- Ok(())
+ }
+
+ fn get_mir_or_dyn_index(
+ &self,
+ def: FunctionId,
+ generic_args: Substitution,
+ locals: &Locals,
+ span: MirSpan,
+ ) -> Result<MirOrDynIndex> {
+ let pair = (def, generic_args);
+ if let Some(r) = self.mir_or_dyn_index_cache.borrow().get(&pair) {
+ return Ok(r.clone());
+ }
+ let (def, generic_args) = pair;
+ let r = if let Some(self_ty_idx) =
+ is_dyn_method(self.db, self.trait_env.clone(), def, generic_args.clone())
+ {
+ MirOrDynIndex::Dyn(self_ty_idx)
+ } else {
+ let (imp, generic_args) =
+ self.db.lookup_impl_method(self.trait_env.clone(), def, generic_args.clone());
+ let mir_body = self
+ .db
+ .monomorphized_mir_body(imp.into(), generic_args, self.trait_env.clone())
+ .map_err(|e| {
+ MirEvalError::InFunction(
+ Box::new(MirEvalError::MirLowerError(imp, e)),
+ vec![(Either::Left(imp), span, locals.body.owner)],
+ )
+ })?;
+ MirOrDynIndex::Mir(mir_body)
+ };
+ self.mir_or_dyn_index_cache.borrow_mut().insert((def, generic_args), r.clone());
+ Ok(r)
}
fn exec_fn_with_args(
@@ -1835,10 +2216,11 @@ impl Evaluator<'_> {
def: FunctionId,
args: &[IntervalAndTy],
generic_args: Substitution,
- locals: &Locals<'_>,
+ locals: &Locals,
destination: Interval,
+ target_bb: Option<BasicBlockId>,
span: MirSpan,
- ) -> Result<()> {
+ ) -> Result<Option<StackFrame>> {
if self.detect_and_exec_special_function(
def,
args,
@@ -1847,85 +2229,96 @@ impl Evaluator<'_> {
destination,
span,
)? {
- return Ok(());
+ return Ok(None);
}
- let arg_bytes =
- args.iter().map(|x| Ok(x.get(&self)?.to_owned())).collect::<Result<Vec<_>>>()?;
- if let Some(self_ty_idx) =
- is_dyn_method(self.db, self.trait_env.clone(), def, generic_args.clone())
- {
- // In the layout of current possible receiver, which at the moment of writing this code is one of
- // `&T`, `&mut T`, `Box<T>`, `Rc<T>`, `Arc<T>`, and `Pin<P>` where `P` is one of possible recievers,
- // the vtable is exactly in the `[ptr_size..2*ptr_size]` bytes. So we can use it without branching on
- // the type.
- let ty =
- self.vtable_map.ty_of_bytes(&arg_bytes[0][self.ptr_size()..self.ptr_size() * 2])?;
- let mut args_for_target = args.to_vec();
- args_for_target[0] = IntervalAndTy {
- interval: args_for_target[0].interval.slice(0..self.ptr_size()),
- ty: ty.clone(),
- };
- let ty = GenericArgData::Ty(ty.clone()).intern(Interner);
- let generics_for_target =
- Substitution::from_iter(
+ let arg_bytes = args.iter().map(|it| IntervalOrOwned::Borrowed(it.interval));
+ match self.get_mir_or_dyn_index(def, generic_args.clone(), locals, span)? {
+ MirOrDynIndex::Dyn(self_ty_idx) => {
+ // In the layout of current possible receiver, which at the moment of writing this code is one of
+ // `&T`, `&mut T`, `Box<T>`, `Rc<T>`, `Arc<T>`, and `Pin<P>` where `P` is one of possible recievers,
+ // the vtable is exactly in the `[ptr_size..2*ptr_size]` bytes. So we can use it without branching on
+ // the type.
+ let first_arg = arg_bytes.clone().next().unwrap();
+ let first_arg = first_arg.get(self)?;
+ let ty = self
+ .vtable_map
+ .ty_of_bytes(&first_arg[self.ptr_size()..self.ptr_size() * 2])?;
+ let mut args_for_target = args.to_vec();
+ args_for_target[0] = IntervalAndTy {
+ interval: args_for_target[0].interval.slice(0..self.ptr_size()),
+ ty: ty.clone(),
+ };
+ let ty = GenericArgData::Ty(ty.clone()).intern(Interner);
+ let generics_for_target = Substitution::from_iter(
Interner,
- generic_args.iter(Interner).enumerate().map(|(i, x)| {
+ generic_args.iter(Interner).enumerate().map(|(i, it)| {
if i == self_ty_idx {
&ty
} else {
- x
+ it
}
}),
);
- return self.exec_fn_with_args(
- def,
- &args_for_target,
- generics_for_target,
+ return self.exec_fn_with_args(
+ def,
+ &args_for_target,
+ generics_for_target,
+ locals,
+ destination,
+ target_bb,
+ span,
+ );
+ }
+ MirOrDynIndex::Mir(body) => self.exec_looked_up_function(
+ body,
locals,
- destination,
+ def,
+ arg_bytes,
span,
- );
+ destination,
+ target_bb,
+ ),
}
- let (imp, generic_args) =
- lookup_impl_method(self.db, self.trait_env.clone(), def, generic_args);
- self.exec_looked_up_function(generic_args, locals, imp, arg_bytes, span, destination)
}
fn exec_looked_up_function(
&mut self,
- generic_args: Substitution,
- locals: &Locals<'_>,
- imp: FunctionId,
- arg_bytes: Vec<Vec<u8>>,
+ mir_body: Arc<MirBody>,
+ locals: &Locals,
+ def: FunctionId,
+ arg_bytes: impl Iterator<Item = IntervalOrOwned>,
span: MirSpan,
destination: Interval,
- ) -> Result<()> {
- let def = imp.into();
- let mir_body = self
- .db
- .monomorphized_mir_body(def, generic_args, self.trait_env.clone())
- .map_err(|e| {
+ target_bb: Option<BasicBlockId>,
+ ) -> Result<Option<StackFrame>> {
+ Ok(if let Some(target_bb) = target_bb {
+ let (mut locals, prev_stack_ptr) =
+ self.create_locals_for_body(&mir_body, Some(destination))?;
+ self.fill_locals_for_body(&mir_body, &mut locals, arg_bytes.into_iter())?;
+ let span = (span, locals.body.owner);
+ Some(StackFrame { locals, destination: Some(target_bb), prev_stack_ptr, span })
+ } else {
+ let result = self.interpret_mir(mir_body, arg_bytes).map_err(|e| {
MirEvalError::InFunction(
- Either::Left(imp),
- Box::new(MirEvalError::MirLowerError(imp, e)),
- span,
- locals.body.owner,
+ Box::new(e),
+ vec![(Either::Left(def), span, locals.body.owner)],
)
})?;
- let result = self.interpret_mir(&mir_body, arg_bytes.iter().cloned()).map_err(|e| {
- MirEvalError::InFunction(Either::Left(imp), Box::new(e), span, locals.body.owner)
- })?;
- destination.write_from_bytes(self, &result)?;
- Ok(())
+ destination.write_from_bytes(self, &result)?;
+ None
+ })
}
fn exec_fn_trait(
&mut self,
+ def: FunctionId,
args: &[IntervalAndTy],
+ generic_args: Substitution,
+ locals: &Locals,
destination: Interval,
- locals: &Locals<'_>,
+ target_bb: Option<BasicBlockId>,
span: MirSpan,
- ) -> Result<()> {
+ ) -> Result<Option<StackFrame>> {
let func = args.get(0).ok_or(MirEvalError::TypeError("fn trait with no arg"))?;
let mut func_ty = func.ty.clone();
let mut func_data = func.interval;
@@ -1940,15 +2333,30 @@ impl Evaluator<'_> {
let size = self.size_of_sized(&func_ty, locals, "self type of fn trait")?;
func_data = Interval { addr: Address::from_bytes(func_data.get(self)?)?, size };
}
- match &func_ty.data(Interner).kind {
+ match &func_ty.kind(Interner) {
TyKind::FnDef(def, subst) => {
- self.exec_fn_def(*def, subst, destination, &args[1..], locals, span)?;
+ return self.exec_fn_def(
+ *def,
+ subst,
+ destination,
+ &args[1..],
+ locals,
+ target_bb,
+ span,
+ );
}
TyKind::Function(_) => {
- self.exec_fn_pointer(func_data, destination, &args[1..], locals, span)?;
+ return self.exec_fn_pointer(
+ func_data,
+ destination,
+ &args[1..],
+ locals,
+ target_bb,
+ span,
+ );
}
TyKind::Closure(closure, subst) => {
- self.exec_closure(
+ return self.exec_closure(
*closure,
func_data,
&Substitution::from_iter(Interner, ClosureSubst(subst).parent_subst()),
@@ -1956,14 +2364,45 @@ impl Evaluator<'_> {
&args[1..],
locals,
span,
- )?;
+ );
+ }
+ _ => {
+ // try to execute the manual impl of `FnTrait` for structs (nightly feature used in std)
+ let arg0 = func;
+ let args = &args[1..];
+ let arg1 = {
+ let ty = TyKind::Tuple(
+ args.len(),
+ Substitution::from_iter(Interner, args.iter().map(|it| it.ty.clone())),
+ )
+ .intern(Interner);
+ let layout = self.layout(&ty)?;
+ let result = self.make_by_layout(
+ layout.size.bytes_usize(),
+ &layout,
+ None,
+ args.iter().map(|it| IntervalOrOwned::Borrowed(it.interval)),
+ )?;
+ // FIXME: there is some leak here
+ let size = layout.size.bytes_usize();
+ let addr = self.heap_allocate(size, layout.align.abi.bytes() as usize)?;
+ self.write_memory(addr, &result)?;
+ IntervalAndTy { interval: Interval { addr, size }, ty }
+ };
+ return self.exec_fn_with_args(
+ def,
+ &[arg0.clone(), arg1],
+ generic_args,
+ locals,
+ destination,
+ target_bb,
+ span,
+ );
}
- x => not_supported!("Call FnTrait methods with type {x:?}"),
}
- Ok(())
}
- fn eval_static(&mut self, st: StaticId, locals: &Locals<'_>) -> Result<Address> {
+ fn eval_static(&mut self, st: StaticId, locals: &Locals) -> Result<Address> {
if let Some(o) = self.static_locations.get(&st) {
return Ok(*o);
};
@@ -1975,21 +2414,16 @@ impl Evaluator<'_> {
Box::new(e),
)
})?;
- let data = &konst.data(Interner);
- if let chalk_ir::ConstValue::Concrete(c) = &data.value {
- self.allocate_const_in_heap(&c, &data.ty, locals, &konst)?
- } else {
- not_supported!("unevaluatable static");
- }
+ self.allocate_const_in_heap(locals, &konst)?
} else {
let ty = &self.db.infer(st.into())[self.db.body(st.into()).body_expr];
let Some((size, align)) = self.size_align_of(&ty, locals)? else {
not_supported!("unsized extern static");
};
- let addr = self.heap_allocate(size, align);
+ let addr = self.heap_allocate(size, align)?;
Interval::new(addr, size)
};
- let addr = self.heap_allocate(self.ptr_size(), self.ptr_size());
+ let addr = self.heap_allocate(self.ptr_size(), self.ptr_size())?;
self.write_memory(addr, &result.addr.to_bytes())?;
self.static_locations.insert(st, addr);
Ok(addr)
@@ -2011,13 +2445,13 @@ impl Evaluator<'_> {
}
}
- fn drop_place(&mut self, place: &Place, locals: &mut Locals<'_>, span: MirSpan) -> Result<()> {
+ fn drop_place(&mut self, place: &Place, locals: &mut Locals, span: MirSpan) -> Result<()> {
let (addr, ty, metadata) = self.place_addr_and_ty_and_metadata(place, locals)?;
if !locals.drop_flags.remove_place(place) {
return Ok(());
}
let metadata = match metadata {
- Some(x) => x.get(self)?.to_vec(),
+ Some(it) => it.get(self)?.to_vec(),
None => vec![],
};
self.run_drop_glue_deep(ty, locals, addr, &metadata, span)
@@ -2026,7 +2460,7 @@ impl Evaluator<'_> {
fn run_drop_glue_deep(
&mut self,
ty: Ty,
- locals: &Locals<'_>,
+ locals: &Locals,
addr: Address,
_metadata: &[u8],
span: MirSpan,
@@ -2039,20 +2473,19 @@ impl Evaluator<'_> {
// we can ignore drop in them.
return Ok(());
};
- let (impl_drop_candidate, subst) = lookup_impl_method(
- self.db,
- self.trait_env.clone(),
- drop_fn,
- Substitution::from1(Interner, ty.clone()),
- );
- if impl_drop_candidate != drop_fn {
+
+ let generic_args = Substitution::from1(Interner, ty.clone());
+ if let Ok(MirOrDynIndex::Mir(body)) =
+ self.get_mir_or_dyn_index(drop_fn, generic_args, locals, span)
+ {
self.exec_looked_up_function(
- subst,
+ body,
locals,
- impl_drop_candidate,
- vec![addr.to_bytes()],
+ drop_fn,
+ [IntervalOrOwned::Owned(addr.to_bytes())].into_iter(),
span,
Interval { addr: Address::Invalid(0), size: 0 },
+ None,
)?;
}
match ty.kind(Interner) {
@@ -2121,10 +2554,77 @@ impl Evaluator<'_> {
}
}
-pub fn pad16(x: &[u8], is_signed: bool) -> [u8; 16] {
- let is_negative = is_signed && x.last().unwrap_or(&0) > &128;
+pub fn render_const_using_debug_impl(
+ db: &dyn HirDatabase,
+ owner: ConstId,
+ c: &Const,
+) -> Result<String> {
+ let mut evaluator = Evaluator::new(db, owner.into(), false, None);
+ let locals = &Locals {
+ ptr: ArenaMap::new(),
+ body: db
+ .mir_body(owner.into())
+ .map_err(|_| MirEvalError::NotSupported("unreachable".to_string()))?,
+ drop_flags: DropFlags::default(),
+ };
+ let data = evaluator.allocate_const_in_heap(locals, c)?;
+ let resolver = owner.resolver(db.upcast());
+ let Some(TypeNs::TraitId(debug_trait)) = resolver.resolve_path_in_type_ns_fully(
+ db.upcast(),
+ &hir_def::path::Path::from_known_path_with_no_generic(ModPath::from_segments(
+ hir_expand::mod_path::PathKind::Abs,
+ [name![core], name![fmt], name![Debug]].into_iter(),
+ )),
+ ) else {
+ not_supported!("core::fmt::Debug not found");
+ };
+ let Some(debug_fmt_fn) = db.trait_data(debug_trait).method_by_name(&name![fmt]) else {
+ not_supported!("core::fmt::Debug::fmt not found");
+ };
+ // a1 = &[""]
+ let a1 = evaluator.heap_allocate(evaluator.ptr_size() * 2, evaluator.ptr_size())?;
+ // a2 = &[::core::fmt::ArgumentV1::new(&(THE_CONST), ::core::fmt::Debug::fmt)]
+ // FIXME: we should call the said function, but since its name is going to break in the next rustc version
+ // and its ABI doesn't break yet, we put it in memory manually.
+ let a2 = evaluator.heap_allocate(evaluator.ptr_size() * 2, evaluator.ptr_size())?;
+ evaluator.write_memory(a2, &data.addr.to_bytes())?;
+ let debug_fmt_fn_ptr = evaluator.vtable_map.id(TyKind::FnDef(
+ db.intern_callable_def(debug_fmt_fn.into()).into(),
+ Substitution::from1(Interner, c.data(Interner).ty.clone()),
+ )
+ .intern(Interner));
+ evaluator.write_memory(a2.offset(evaluator.ptr_size()), &debug_fmt_fn_ptr.to_le_bytes())?;
+ // a3 = ::core::fmt::Arguments::new_v1(a1, a2)
+ // FIXME: similarly, we should call function here, not directly working with memory.
+ let a3 = evaluator.heap_allocate(evaluator.ptr_size() * 6, evaluator.ptr_size())?;
+ evaluator.write_memory(a3.offset(2 * evaluator.ptr_size()), &a1.to_bytes())?;
+ evaluator.write_memory(a3.offset(3 * evaluator.ptr_size()), &[1])?;
+ evaluator.write_memory(a3.offset(4 * evaluator.ptr_size()), &a2.to_bytes())?;
+ evaluator.write_memory(a3.offset(5 * evaluator.ptr_size()), &[1])?;
+ let Some(ValueNs::FunctionId(format_fn)) = resolver.resolve_path_in_value_ns_fully(
+ db.upcast(),
+ &hir_def::path::Path::from_known_path_with_no_generic(ModPath::from_segments(
+ hir_expand::mod_path::PathKind::Abs,
+ [name![std], name![fmt], name![format]].into_iter(),
+ )),
+ ) else {
+ not_supported!("std::fmt::format not found");
+ };
+ let message_string = evaluator.interpret_mir(
+ db.mir_body(format_fn.into()).map_err(|e| MirEvalError::MirLowerError(format_fn, e))?,
+ [IntervalOrOwned::Borrowed(Interval { addr: a3, size: evaluator.ptr_size() * 6 })]
+ .into_iter(),
+ )?;
+ let addr =
+ Address::from_bytes(&message_string[evaluator.ptr_size()..2 * evaluator.ptr_size()])?;
+ let size = from_bytes!(usize, message_string[2 * evaluator.ptr_size()..]);
+ Ok(std::string::String::from_utf8_lossy(evaluator.read_memory(addr, size)?).into_owned())
+}
+
+pub fn pad16(it: &[u8], is_signed: bool) -> [u8; 16] {
+ let is_negative = is_signed && it.last().unwrap_or(&0) > &127;
let fill_with = if is_negative { 255 } else { 0 };
- x.iter()
+ it.iter()
.copied()
.chain(iter::repeat(fill_with))
.take(16)
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim.rs
index 3b9ef03c3..b2e29fd34 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim.rs
@@ -3,20 +3,26 @@
use std::cmp;
+use chalk_ir::TyKind;
+use hir_def::resolver::HasResolver;
+use hir_expand::mod_path::ModPath;
+
use super::*;
+mod simd;
+
macro_rules! from_bytes {
($ty:tt, $value:expr) => {
($ty::from_le_bytes(match ($value).try_into() {
- Ok(x) => x,
+ Ok(it) => it,
Err(_) => return Err(MirEvalError::TypeError("mismatched size")),
}))
};
}
macro_rules! not_supported {
- ($x: expr) => {
- return Err(MirEvalError::NotSupported(format!($x)))
+ ($it: expr) => {
+ return Err(MirEvalError::NotSupported(format!($it)))
};
}
@@ -26,10 +32,13 @@ impl Evaluator<'_> {
def: FunctionId,
args: &[IntervalAndTy],
generic_args: &Substitution,
- locals: &Locals<'_>,
+ locals: &Locals,
destination: Interval,
span: MirSpan,
) -> Result<bool> {
+ if self.not_special_fn_cache.borrow().contains(&def) {
+ return Ok(false);
+ }
let function_data = self.db.function_data(def);
let is_intrinsic = match &function_data.abi {
Some(abi) => *abi == Interned::new_str("rust-intrinsic"),
@@ -53,6 +62,28 @@ impl Evaluator<'_> {
)?;
return Ok(true);
}
+ let is_platform_intrinsic = match &function_data.abi {
+ Some(abi) => *abi == Interned::new_str("platform-intrinsic"),
+ None => match def.lookup(self.db.upcast()).container {
+ hir_def::ItemContainerId::ExternBlockId(block) => {
+ let id = block.lookup(self.db.upcast()).id;
+ id.item_tree(self.db.upcast())[id.value].abi.as_deref()
+ == Some("platform-intrinsic")
+ }
+ _ => false,
+ },
+ };
+ if is_platform_intrinsic {
+ self.exec_platform_intrinsic(
+ function_data.name.as_text().unwrap_or_default().as_str(),
+ args,
+ generic_args,
+ destination,
+ &locals,
+ span,
+ )?;
+ return Ok(true);
+ }
let is_extern_c = match def.lookup(self.db.upcast()).container {
hir_def::ItemContainerId::ExternBlockId(block) => {
let id = block.lookup(self.db.upcast()).id;
@@ -74,31 +105,110 @@ impl Evaluator<'_> {
let alloc_fn = function_data
.attrs
.iter()
- .filter_map(|x| x.path().as_ident())
- .filter_map(|x| x.as_str())
- .find(|x| {
+ .filter_map(|it| it.path().as_ident())
+ .filter_map(|it| it.as_str())
+ .find(|it| {
[
"rustc_allocator",
"rustc_deallocator",
"rustc_reallocator",
"rustc_allocator_zeroed",
]
- .contains(x)
+ .contains(it)
});
if let Some(alloc_fn) = alloc_fn {
self.exec_alloc_fn(alloc_fn, args, destination)?;
return Ok(true);
}
- if let Some(x) = self.detect_lang_function(def) {
+ if let Some(it) = self.detect_lang_function(def) {
let arg_bytes =
- args.iter().map(|x| Ok(x.get(&self)?.to_owned())).collect::<Result<Vec<_>>>()?;
- let result = self.exec_lang_item(x, generic_args, &arg_bytes, locals, span)?;
+ args.iter().map(|it| Ok(it.get(&self)?.to_owned())).collect::<Result<Vec<_>>>()?;
+ let result = self.exec_lang_item(it, generic_args, &arg_bytes, locals, span)?;
destination.write_from_bytes(self, &result)?;
return Ok(true);
}
+ if let ItemContainerId::TraitId(t) = def.lookup(self.db.upcast()).container {
+ if self.db.lang_attr(t.into()) == Some(LangItem::Clone) {
+ let [self_ty] = generic_args.as_slice(Interner) else {
+ not_supported!("wrong generic arg count for clone");
+ };
+ let Some(self_ty) = self_ty.ty(Interner) else {
+ not_supported!("wrong generic arg kind for clone");
+ };
+ // Clone has special impls for tuples and function pointers
+ if matches!(self_ty.kind(Interner), TyKind::Function(_) | TyKind::Tuple(..)) {
+ self.exec_clone(def, args, self_ty.clone(), locals, destination, span)?;
+ return Ok(true);
+ }
+ // Return early to prevent caching clone as non special fn.
+ return Ok(false);
+ }
+ }
+ self.not_special_fn_cache.borrow_mut().insert(def);
Ok(false)
}
+ /// Clone has special impls for tuples and function pointers
+ fn exec_clone(
+ &mut self,
+ def: FunctionId,
+ args: &[IntervalAndTy],
+ self_ty: Ty,
+ locals: &Locals,
+ destination: Interval,
+ span: MirSpan,
+ ) -> Result<()> {
+ match self_ty.kind(Interner) {
+ TyKind::Function(_) => {
+ let [arg] = args else {
+ not_supported!("wrong arg count for clone");
+ };
+ let addr = Address::from_bytes(arg.get(self)?)?;
+ return destination
+ .write_from_interval(self, Interval { addr, size: destination.size });
+ }
+ TyKind::Tuple(_, subst) => {
+ let [arg] = args else {
+ not_supported!("wrong arg count for clone");
+ };
+ let addr = Address::from_bytes(arg.get(self)?)?;
+ let layout = self.layout(&self_ty)?;
+ for (i, ty) in subst.iter(Interner).enumerate() {
+ let ty = ty.assert_ty_ref(Interner);
+ let size = self.layout(ty)?.size.bytes_usize();
+ let tmp = self.heap_allocate(self.ptr_size(), self.ptr_size())?;
+ let arg = IntervalAndTy {
+ interval: Interval { addr: tmp, size: self.ptr_size() },
+ ty: TyKind::Ref(Mutability::Not, static_lifetime(), ty.clone())
+ .intern(Interner),
+ };
+ let offset = layout.fields.offset(i).bytes_usize();
+ self.write_memory(tmp, &addr.offset(offset).to_bytes())?;
+ self.exec_clone(
+ def,
+ &[arg],
+ ty.clone(),
+ locals,
+ destination.slice(offset..offset + size),
+ span,
+ )?;
+ }
+ }
+ _ => {
+ self.exec_fn_with_args(
+ def,
+ args,
+ Substitution::from1(Interner, self_ty),
+ locals,
+ destination,
+ None,
+ span,
+ )?;
+ }
+ }
+ Ok(())
+ }
+
fn exec_alloc_fn(
&mut self,
alloc_fn: &str,
@@ -112,7 +222,7 @@ impl Evaluator<'_> {
};
let size = from_bytes!(usize, size.get(self)?);
let align = from_bytes!(usize, align.get(self)?);
- let result = self.heap_allocate(size, align);
+ let result = self.heap_allocate(size, align)?;
destination.write_from_bytes(self, &result.to_bytes())?;
}
"rustc_deallocator" => { /* no-op for now */ }
@@ -120,14 +230,18 @@ impl Evaluator<'_> {
let [ptr, old_size, align, new_size] = args else {
return Err(MirEvalError::TypeError("rustc_allocator args are not provided"));
};
- let ptr = Address::from_bytes(ptr.get(self)?)?;
let old_size = from_bytes!(usize, old_size.get(self)?);
let new_size = from_bytes!(usize, new_size.get(self)?);
- let align = from_bytes!(usize, align.get(self)?);
- let result = self.heap_allocate(new_size, align);
- Interval { addr: result, size: old_size }
- .write_from_interval(self, Interval { addr: ptr, size: old_size })?;
- destination.write_from_bytes(self, &result.to_bytes())?;
+ if old_size >= new_size {
+ destination.write_from_interval(self, ptr.interval)?;
+ } else {
+ let ptr = Address::from_bytes(ptr.get(self)?)?;
+ let align = from_bytes!(usize, align.get(self)?);
+ let result = self.heap_allocate(new_size, align)?;
+ Interval { addr: result, size: old_size }
+ .write_from_interval(self, Interval { addr: ptr, size: old_size })?;
+ destination.write_from_bytes(self, &result.to_bytes())?;
+ }
}
_ => not_supported!("unknown alloc function"),
}
@@ -136,7 +250,7 @@ impl Evaluator<'_> {
fn detect_lang_function(&self, def: FunctionId) -> Option<LangItem> {
use LangItem::*;
- let candidate = lang_attr(self.db.upcast(), def)?;
+ let candidate = self.db.lang_attr(def.into())?;
// We want to execute these functions with special logic
if [PanicFmt, BeginPanic, SliceLen, DropInPlace].contains(&candidate) {
return Some(candidate);
@@ -146,56 +260,35 @@ impl Evaluator<'_> {
fn exec_lang_item(
&mut self,
- x: LangItem,
+ it: LangItem,
generic_args: &Substitution,
args: &[Vec<u8>],
- locals: &Locals<'_>,
+ locals: &Locals,
span: MirSpan,
) -> Result<Vec<u8>> {
use LangItem::*;
let mut args = args.iter();
- match x {
+ match it {
BeginPanic => Err(MirEvalError::Panic("<unknown-panic-payload>".to_string())),
PanicFmt => {
let message = (|| {
- let arguments_struct =
- self.db.lang_item(self.crate_id, LangItem::FormatArguments)?.as_struct()?;
- let arguments_layout = self
- .layout_adt(arguments_struct.into(), Substitution::empty(Interner))
- .ok()?;
- let arguments_field_pieces =
- self.db.struct_data(arguments_struct).variant_data.field(&name![pieces])?;
- let pieces_offset = arguments_layout
- .fields
- .offset(u32::from(arguments_field_pieces.into_raw()) as usize)
- .bytes_usize();
- let ptr_size = self.ptr_size();
- let arg = args.next()?;
- let pieces_array_addr =
- Address::from_bytes(&arg[pieces_offset..pieces_offset + ptr_size]).ok()?;
- let pieces_array_len = usize::from_le_bytes(
- (&arg[pieces_offset + ptr_size..pieces_offset + 2 * ptr_size])
- .try_into()
- .ok()?,
- );
- let mut message = "".to_string();
- for i in 0..pieces_array_len {
- let piece_ptr_addr = pieces_array_addr.offset(2 * i * ptr_size);
- let piece_addr =
- Address::from_bytes(self.read_memory(piece_ptr_addr, ptr_size).ok()?)
- .ok()?;
- let piece_len = usize::from_le_bytes(
- self.read_memory(piece_ptr_addr.offset(ptr_size), ptr_size)
- .ok()?
- .try_into()
- .ok()?,
- );
- let piece_data = self.read_memory(piece_addr, piece_len).ok()?;
- message += &std::string::String::from_utf8_lossy(piece_data);
- }
- Some(message)
+ let resolver = self.db.crate_def_map(self.crate_id).crate_root().resolver(self.db.upcast());
+ let Some(format_fn) = resolver.resolve_path_in_value_ns_fully(
+ self.db.upcast(),
+ &hir_def::path::Path::from_known_path_with_no_generic(ModPath::from_segments(
+ hir_expand::mod_path::PathKind::Abs,
+ [name![std], name![fmt], name![format]].into_iter(),
+ )),
+ ) else {
+ not_supported!("std::fmt::format not found");
+ };
+ let hir_def::resolver::ValueNs::FunctionId(format_fn) = format_fn else { not_supported!("std::fmt::format is not a function") };
+ let message_string = self.interpret_mir(self.db.mir_body(format_fn.into()).map_err(|e| MirEvalError::MirLowerError(format_fn, e))?, args.map(|x| IntervalOrOwned::Owned(x.clone())))?;
+ let addr = Address::from_bytes(&message_string[self.ptr_size()..2 * self.ptr_size()])?;
+ let size = from_bytes!(usize, message_string[2 * self.ptr_size()..]);
+ Ok(std::string::String::from_utf8_lossy(self.read_memory(addr, size)?).into_owned())
})()
- .unwrap_or_else(|| "<format-args-evaluation-failed>".to_string());
+ .unwrap_or_else(|e| format!("Failed to render panic format args: {e:?}"));
Err(MirEvalError::Panic(message))
}
SliceLen => {
@@ -207,7 +300,7 @@ impl Evaluator<'_> {
}
DropInPlace => {
let ty =
- generic_args.as_slice(Interner).get(0).and_then(|x| x.ty(Interner)).ok_or(
+ generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)).ok_or(
MirEvalError::TypeError(
"generic argument of drop_in_place is not provided",
),
@@ -224,7 +317,35 @@ impl Evaluator<'_> {
)?;
Ok(vec![])
}
- x => not_supported!("Executing lang item {x:?}"),
+ it => not_supported!("Executing lang item {it:?}"),
+ }
+ }
+
+ fn exec_syscall(
+ &mut self,
+ id: i64,
+ args: &[IntervalAndTy],
+ destination: Interval,
+ _locals: &Locals,
+ _span: MirSpan,
+ ) -> Result<()> {
+ match id {
+ 318 => {
+ // SYS_getrandom
+ let [buf, len, _flags] = args else {
+ return Err(MirEvalError::TypeError("SYS_getrandom args are not provided"));
+ };
+ let addr = Address::from_bytes(buf.get(self)?)?;
+ let size = from_bytes!(usize, len.get(self)?);
+ for i in 0..size {
+ let rand_byte = self.random_state.rand_u64() as u8;
+ self.write_memory(addr.offset(i), &[rand_byte])?;
+ }
+ destination.write_from_interval(self, len.interval)
+ }
+ _ => {
+ not_supported!("Unknown syscall id {id:?}")
+ }
}
}
@@ -234,8 +355,8 @@ impl Evaluator<'_> {
args: &[IntervalAndTy],
_generic_args: &Substitution,
destination: Interval,
- locals: &Locals<'_>,
- _span: MirSpan,
+ locals: &Locals,
+ span: MirSpan,
) -> Result<()> {
match as_str {
"memcmp" => {
@@ -299,7 +420,9 @@ impl Evaluator<'_> {
}
"pthread_getspecific" => {
let Some(arg0) = args.get(0) else {
- return Err(MirEvalError::TypeError("pthread_getspecific arg0 is not provided"));
+ return Err(MirEvalError::TypeError(
+ "pthread_getspecific arg0 is not provided",
+ ));
};
let key = from_bytes!(usize, &pad16(arg0.get(self)?, false)[0..8]);
let value = self.thread_local_storage.get_key(key)?;
@@ -308,11 +431,15 @@ impl Evaluator<'_> {
}
"pthread_setspecific" => {
let Some(arg0) = args.get(0) else {
- return Err(MirEvalError::TypeError("pthread_setspecific arg0 is not provided"));
+ return Err(MirEvalError::TypeError(
+ "pthread_setspecific arg0 is not provided",
+ ));
};
let key = from_bytes!(usize, &pad16(arg0.get(self)?, false)[0..8]);
let Some(arg1) = args.get(1) else {
- return Err(MirEvalError::TypeError("pthread_setspecific arg1 is not provided"));
+ return Err(MirEvalError::TypeError(
+ "pthread_setspecific arg1 is not provided",
+ ));
};
let value = from_bytes!(u128, pad16(arg1.get(self)?, false));
self.thread_local_storage.set_key(key, value)?;
@@ -326,17 +453,52 @@ impl Evaluator<'_> {
destination.write_from_bytes(self, &0u64.to_le_bytes()[0..destination.size])?;
Ok(())
}
+ "syscall" => {
+ let Some((id, rest)) = args.split_first() else {
+ return Err(MirEvalError::TypeError(
+ "syscall arg1 is not provided",
+ ));
+ };
+ let id = from_bytes!(i64, id.get(self)?);
+ self.exec_syscall(id, rest, destination, locals, span)
+ }
+ "sched_getaffinity" => {
+ let [_pid, _set_size, set] = args else {
+ return Err(MirEvalError::TypeError("libc::write args are not provided"));
+ };
+ let set = Address::from_bytes(set.get(self)?)?;
+ // Only enable core 0 (we are single threaded anyway), which is bitset 0x0000001
+ self.write_memory(set, &[1])?;
+ // return 0 as success
+ self.write_memory_using_ref(destination.addr, destination.size)?.fill(0);
+ Ok(())
+ }
_ => not_supported!("unknown external function {as_str}"),
}
}
+ fn exec_platform_intrinsic(
+ &mut self,
+ name: &str,
+ args: &[IntervalAndTy],
+ generic_args: &Substitution,
+ destination: Interval,
+ locals: &Locals,
+ span: MirSpan,
+ ) -> Result<()> {
+ if let Some(name) = name.strip_prefix("simd_") {
+ return self.exec_simd_intrinsic(name, args, generic_args, destination, locals, span);
+ }
+ not_supported!("unknown platform intrinsic {name}");
+ }
+
fn exec_intrinsic(
&mut self,
name: &str,
args: &[IntervalAndTy],
generic_args: &Substitution,
destination: Interval,
- locals: &Locals<'_>,
+ locals: &Locals,
span: MirSpan,
) -> Result<()> {
if let Some(name) = name.strip_prefix("atomic_") {
@@ -347,7 +509,9 @@ impl Evaluator<'_> {
"sqrt" | "sin" | "cos" | "exp" | "exp2" | "log" | "log10" | "log2" | "fabs"
| "floor" | "ceil" | "trunc" | "rint" | "nearbyint" | "round" | "roundeven" => {
let [arg] = args else {
- return Err(MirEvalError::TypeError("f64 intrinsic signature doesn't match fn (f64) -> f64"));
+ return Err(MirEvalError::TypeError(
+ "f64 intrinsic signature doesn't match fn (f64) -> f64",
+ ));
};
let arg = from_bytes!(f64, arg.get(self)?);
match name {
@@ -373,7 +537,9 @@ impl Evaluator<'_> {
}
"pow" | "minnum" | "maxnum" | "copysign" => {
let [arg1, arg2] = args else {
- return Err(MirEvalError::TypeError("f64 intrinsic signature doesn't match fn (f64, f64) -> f64"));
+ return Err(MirEvalError::TypeError(
+ "f64 intrinsic signature doesn't match fn (f64, f64) -> f64",
+ ));
};
let arg1 = from_bytes!(f64, arg1.get(self)?);
let arg2 = from_bytes!(f64, arg2.get(self)?);
@@ -387,7 +553,9 @@ impl Evaluator<'_> {
}
"powi" => {
let [arg1, arg2] = args else {
- return Err(MirEvalError::TypeError("powif64 signature doesn't match fn (f64, i32) -> f64"));
+ return Err(MirEvalError::TypeError(
+ "powif64 signature doesn't match fn (f64, i32) -> f64",
+ ));
};
let arg1 = from_bytes!(f64, arg1.get(self)?);
let arg2 = from_bytes!(i32, arg2.get(self)?);
@@ -395,7 +563,9 @@ impl Evaluator<'_> {
}
"fma" => {
let [arg1, arg2, arg3] = args else {
- return Err(MirEvalError::TypeError("fmaf64 signature doesn't match fn (f64, f64, f64) -> f64"));
+ return Err(MirEvalError::TypeError(
+ "fmaf64 signature doesn't match fn (f64, f64, f64) -> f64",
+ ));
};
let arg1 = from_bytes!(f64, arg1.get(self)?);
let arg2 = from_bytes!(f64, arg2.get(self)?);
@@ -411,7 +581,9 @@ impl Evaluator<'_> {
"sqrt" | "sin" | "cos" | "exp" | "exp2" | "log" | "log10" | "log2" | "fabs"
| "floor" | "ceil" | "trunc" | "rint" | "nearbyint" | "round" | "roundeven" => {
let [arg] = args else {
- return Err(MirEvalError::TypeError("f32 intrinsic signature doesn't match fn (f32) -> f32"));
+ return Err(MirEvalError::TypeError(
+ "f32 intrinsic signature doesn't match fn (f32) -> f32",
+ ));
};
let arg = from_bytes!(f32, arg.get(self)?);
match name {
@@ -437,7 +609,9 @@ impl Evaluator<'_> {
}
"pow" | "minnum" | "maxnum" | "copysign" => {
let [arg1, arg2] = args else {
- return Err(MirEvalError::TypeError("f32 intrinsic signature doesn't match fn (f32, f32) -> f32"));
+ return Err(MirEvalError::TypeError(
+ "f32 intrinsic signature doesn't match fn (f32, f32) -> f32",
+ ));
};
let arg1 = from_bytes!(f32, arg1.get(self)?);
let arg2 = from_bytes!(f32, arg2.get(self)?);
@@ -451,7 +625,9 @@ impl Evaluator<'_> {
}
"powi" => {
let [arg1, arg2] = args else {
- return Err(MirEvalError::TypeError("powif32 signature doesn't match fn (f32, i32) -> f32"));
+ return Err(MirEvalError::TypeError(
+ "powif32 signature doesn't match fn (f32, i32) -> f32",
+ ));
};
let arg1 = from_bytes!(f32, arg1.get(self)?);
let arg2 = from_bytes!(i32, arg2.get(self)?);
@@ -459,7 +635,9 @@ impl Evaluator<'_> {
}
"fma" => {
let [arg1, arg2, arg3] = args else {
- return Err(MirEvalError::TypeError("fmaf32 signature doesn't match fn (f32, f32, f32) -> f32"));
+ return Err(MirEvalError::TypeError(
+ "fmaf32 signature doesn't match fn (f32, f32, f32) -> f32",
+ ));
};
let arg1 = from_bytes!(f32, arg1.get(self)?);
let arg2 = from_bytes!(f32, arg2.get(self)?);
@@ -472,21 +650,77 @@ impl Evaluator<'_> {
}
match name {
"size_of" => {
- let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|x| x.ty(Interner)) else {
+ let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner))
+ else {
return Err(MirEvalError::TypeError("size_of generic arg is not provided"));
};
let size = self.size_of_sized(ty, locals, "size_of arg")?;
destination.write_from_bytes(self, &size.to_le_bytes()[0..destination.size])
}
"min_align_of" | "pref_align_of" => {
- let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|x| x.ty(Interner)) else {
+ let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) else {
return Err(MirEvalError::TypeError("align_of generic arg is not provided"));
};
let align = self.layout(ty)?.align.abi.bytes();
destination.write_from_bytes(self, &align.to_le_bytes()[0..destination.size])
}
+ "size_of_val" => {
+ let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner))
+ else {
+ return Err(MirEvalError::TypeError("size_of_val generic arg is not provided"));
+ };
+ let [arg] = args else {
+ return Err(MirEvalError::TypeError("size_of_val args are not provided"));
+ };
+ if let Some((size, _)) = self.size_align_of(ty, locals)? {
+ destination.write_from_bytes(self, &size.to_le_bytes())
+ } else {
+ let metadata = arg.interval.slice(self.ptr_size()..self.ptr_size() * 2);
+ let (size, _) = self.size_align_of_unsized(ty, metadata, locals)?;
+ destination.write_from_bytes(self, &size.to_le_bytes())
+ }
+ }
+ "min_align_of_val" => {
+ let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) else {
+ return Err(MirEvalError::TypeError("min_align_of_val generic arg is not provided"));
+ };
+ let [arg] = args else {
+ return Err(MirEvalError::TypeError("min_align_of_val args are not provided"));
+ };
+ if let Some((_, align)) = self.size_align_of(ty, locals)? {
+ destination.write_from_bytes(self, &align.to_le_bytes())
+ } else {
+ let metadata = arg.interval.slice(self.ptr_size()..self.ptr_size() * 2);
+ let (_, align) = self.size_align_of_unsized(ty, metadata, locals)?;
+ destination.write_from_bytes(self, &align.to_le_bytes())
+ }
+ }
+ "type_name" => {
+ let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner))
+ else {
+ return Err(MirEvalError::TypeError("type_name generic arg is not provided"));
+ };
+ let ty_name = match ty.display_source_code(
+ self.db,
+ locals.body.owner.module(self.db.upcast()),
+ true,
+ ) {
+ Ok(ty_name) => ty_name,
+ // Fallback to human readable display in case of `Err`. Ideally we want to use `display_source_code` to
+ // render full paths.
+ Err(_) => ty.display(self.db).to_string(),
+ };
+ let len = ty_name.len();
+ let addr = self.heap_allocate(len, 1)?;
+ self.write_memory(addr, ty_name.as_bytes())?;
+ destination.slice(0..self.ptr_size()).write_from_bytes(self, &addr.to_bytes())?;
+ destination
+ .slice(self.ptr_size()..2 * self.ptr_size())
+ .write_from_bytes(self, &len.to_le_bytes())
+ }
"needs_drop" => {
- let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|x| x.ty(Interner)) else {
+ let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner))
+ else {
return Err(MirEvalError::TypeError("size_of generic arg is not provided"));
};
let result = !ty.clone().is_copy(self.db, locals.body.owner);
@@ -501,13 +735,17 @@ impl Evaluator<'_> {
let ans = lhs.get(self)? == rhs.get(self)?;
destination.write_from_bytes(self, &[u8::from(ans)])
}
- "saturating_add" => {
+ "saturating_add" | "saturating_sub" => {
let [lhs, rhs] = args else {
return Err(MirEvalError::TypeError("saturating_add args are not provided"));
};
let lhs = u128::from_le_bytes(pad16(lhs.get(self)?, false));
let rhs = u128::from_le_bytes(pad16(rhs.get(self)?, false));
- let ans = lhs.saturating_add(rhs);
+ let ans = match name {
+ "saturating_add" => lhs.saturating_add(rhs),
+ "saturating_sub" => lhs.saturating_sub(rhs),
+ _ => unreachable!(),
+ };
let bits = destination.size * 8;
// FIXME: signed
let is_signed = false;
@@ -526,7 +764,22 @@ impl Evaluator<'_> {
let ans = lhs.wrapping_add(rhs);
destination.write_from_bytes(self, &ans.to_le_bytes()[0..destination.size])
}
- "wrapping_sub" | "unchecked_sub" | "ptr_offset_from_unsigned" | "ptr_offset_from" => {
+ "ptr_offset_from_unsigned" | "ptr_offset_from" => {
+ let [lhs, rhs] = args else {
+ return Err(MirEvalError::TypeError("wrapping_sub args are not provided"));
+ };
+ let lhs = i128::from_le_bytes(pad16(lhs.get(self)?, false));
+ let rhs = i128::from_le_bytes(pad16(rhs.get(self)?, false));
+ let ans = lhs.wrapping_sub(rhs);
+ let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner))
+ else {
+ return Err(MirEvalError::TypeError("ptr_offset_from generic arg is not provided"));
+ };
+ let size = self.size_of_sized(ty, locals, "ptr_offset_from arg")? as i128;
+ let ans = ans / size;
+ destination.write_from_bytes(self, &ans.to_le_bytes()[0..destination.size])
+ }
+ "wrapping_sub" | "unchecked_sub" => {
let [lhs, rhs] = args else {
return Err(MirEvalError::TypeError("wrapping_sub args are not provided"));
};
@@ -544,6 +797,26 @@ impl Evaluator<'_> {
let ans = lhs.wrapping_mul(rhs);
destination.write_from_bytes(self, &ans.to_le_bytes()[0..destination.size])
}
+ "wrapping_shl" | "unchecked_shl" => {
+ // FIXME: signed
+ let [lhs, rhs] = args else {
+ return Err(MirEvalError::TypeError("unchecked_shl args are not provided"));
+ };
+ let lhs = u128::from_le_bytes(pad16(lhs.get(self)?, false));
+ let rhs = u128::from_le_bytes(pad16(rhs.get(self)?, false));
+ let ans = lhs.wrapping_shl(rhs as u32);
+ destination.write_from_bytes(self, &ans.to_le_bytes()[0..destination.size])
+ }
+ "wrapping_shr" | "unchecked_shr" => {
+ // FIXME: signed
+ let [lhs, rhs] = args else {
+ return Err(MirEvalError::TypeError("unchecked_shr args are not provided"));
+ };
+ let lhs = u128::from_le_bytes(pad16(lhs.get(self)?, false));
+ let rhs = u128::from_le_bytes(pad16(rhs.get(self)?, false));
+ let ans = lhs.wrapping_shr(rhs as u32);
+ destination.write_from_bytes(self, &ans.to_le_bytes()[0..destination.size])
+ }
"unchecked_rem" => {
// FIXME: signed
let [lhs, rhs] = args else {
@@ -588,7 +861,7 @@ impl Evaluator<'_> {
_ => unreachable!(),
};
let is_overflow = u128overflow
- || ans.to_le_bytes()[op_size..].iter().any(|&x| x != 0 && x != 255);
+ || ans.to_le_bytes()[op_size..].iter().any(|&it| it != 0 && it != 255);
let is_overflow = vec![u8::from(is_overflow)];
let layout = self.layout(&result_ty)?;
let result = self.make_by_layout(
@@ -603,10 +876,15 @@ impl Evaluator<'_> {
}
"copy" | "copy_nonoverlapping" => {
let [src, dst, offset] = args else {
- return Err(MirEvalError::TypeError("copy_nonoverlapping args are not provided"));
+ return Err(MirEvalError::TypeError(
+ "copy_nonoverlapping args are not provided",
+ ));
};
- let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|x| x.ty(Interner)) else {
- return Err(MirEvalError::TypeError("copy_nonoverlapping generic arg is not provided"));
+ let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner))
+ else {
+ return Err(MirEvalError::TypeError(
+ "copy_nonoverlapping generic arg is not provided",
+ ));
};
let src = Address::from_bytes(src.get(self)?)?;
let dst = Address::from_bytes(dst.get(self)?)?;
@@ -621,7 +899,8 @@ impl Evaluator<'_> {
let [ptr, offset] = args else {
return Err(MirEvalError::TypeError("offset args are not provided"));
};
- let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|x| x.ty(Interner)) else {
+ let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner))
+ else {
return Err(MirEvalError::TypeError("offset generic arg is not provided"));
};
let ptr = u128::from_le_bytes(pad16(ptr.get(self)?, false));
@@ -652,20 +931,106 @@ impl Evaluator<'_> {
}
"ctpop" => {
let [arg] = args else {
- return Err(MirEvalError::TypeError("likely arg is not provided"));
+ return Err(MirEvalError::TypeError("ctpop arg is not provided"));
};
let result = u128::from_le_bytes(pad16(arg.get(self)?, false)).count_ones();
destination
.write_from_bytes(self, &(result as u128).to_le_bytes()[0..destination.size])
}
+ "ctlz" | "ctlz_nonzero" => {
+ let [arg] = args else {
+ return Err(MirEvalError::TypeError("cttz arg is not provided"));
+ };
+ let result =
+ u128::from_le_bytes(pad16(arg.get(self)?, false)).leading_zeros() as usize;
+ let result = result - (128 - arg.interval.size * 8);
+ destination
+ .write_from_bytes(self, &(result as u128).to_le_bytes()[0..destination.size])
+ }
"cttz" | "cttz_nonzero" => {
let [arg] = args else {
- return Err(MirEvalError::TypeError("likely arg is not provided"));
+ return Err(MirEvalError::TypeError("cttz arg is not provided"));
};
let result = u128::from_le_bytes(pad16(arg.get(self)?, false)).trailing_zeros();
destination
.write_from_bytes(self, &(result as u128).to_le_bytes()[0..destination.size])
}
+ "rotate_left" => {
+ let [lhs, rhs] = args else {
+ return Err(MirEvalError::TypeError("rotate_left args are not provided"));
+ };
+ let lhs = &lhs.get(self)?[0..destination.size];
+ let rhs = rhs.get(self)?[0] as u32;
+ match destination.size {
+ 1 => {
+ let r = from_bytes!(u8, lhs).rotate_left(rhs);
+ destination.write_from_bytes(self, &r.to_le_bytes())
+ }
+ 2 => {
+ let r = from_bytes!(u16, lhs).rotate_left(rhs);
+ destination.write_from_bytes(self, &r.to_le_bytes())
+ }
+ 4 => {
+ let r = from_bytes!(u32, lhs).rotate_left(rhs);
+ destination.write_from_bytes(self, &r.to_le_bytes())
+ }
+ 8 => {
+ let r = from_bytes!(u64, lhs).rotate_left(rhs);
+ destination.write_from_bytes(self, &r.to_le_bytes())
+ }
+ 16 => {
+ let r = from_bytes!(u128, lhs).rotate_left(rhs);
+ destination.write_from_bytes(self, &r.to_le_bytes())
+ }
+ s => not_supported!("destination with size {s} for rotate_left"),
+ }
+ }
+ "rotate_right" => {
+ let [lhs, rhs] = args else {
+ return Err(MirEvalError::TypeError("rotate_right args are not provided"));
+ };
+ let lhs = &lhs.get(self)?[0..destination.size];
+ let rhs = rhs.get(self)?[0] as u32;
+ match destination.size {
+ 1 => {
+ let r = from_bytes!(u8, lhs).rotate_right(rhs);
+ destination.write_from_bytes(self, &r.to_le_bytes())
+ }
+ 2 => {
+ let r = from_bytes!(u16, lhs).rotate_right(rhs);
+ destination.write_from_bytes(self, &r.to_le_bytes())
+ }
+ 4 => {
+ let r = from_bytes!(u32, lhs).rotate_right(rhs);
+ destination.write_from_bytes(self, &r.to_le_bytes())
+ }
+ 8 => {
+ let r = from_bytes!(u64, lhs).rotate_right(rhs);
+ destination.write_from_bytes(self, &r.to_le_bytes())
+ }
+ 16 => {
+ let r = from_bytes!(u128, lhs).rotate_right(rhs);
+ destination.write_from_bytes(self, &r.to_le_bytes())
+ }
+ s => not_supported!("destination with size {s} for rotate_right"),
+ }
+ }
+ "discriminant_value" => {
+ let [arg] = args else {
+ return Err(MirEvalError::TypeError("discriminant_value arg is not provided"));
+ };
+ let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner))
+ else {
+ return Err(MirEvalError::TypeError(
+ "discriminant_value generic arg is not provided",
+ ));
+ };
+ let addr = Address::from_bytes(arg.get(self)?)?;
+ let size = self.size_of_sized(ty, locals, "discriminant_value ptr type")?;
+ let interval = Interval { addr, size };
+ let r = self.compute_discriminant(ty.clone(), interval.get(self)?)?;
+ destination.write_from_bytes(self, &r.to_le_bytes()[0..destination.size])
+ }
"const_eval_select" => {
let [tuple, const_fn, _] = args else {
return Err(MirEvalError::TypeError("const_eval_select args are not provided"));
@@ -681,24 +1046,126 @@ impl Evaluator<'_> {
let addr = tuple.interval.addr.offset(offset);
args.push(IntervalAndTy::new(addr, field, self, locals)?);
}
- self.exec_fn_trait(&args, destination, locals, span)
+ if let Some(target) = self.db.lang_item(self.crate_id, LangItem::FnOnce) {
+ if let Some(def) = target
+ .as_trait()
+ .and_then(|it| self.db.trait_data(it).method_by_name(&name![call_once]))
+ {
+ self.exec_fn_trait(
+ def,
+ &args,
+ // FIXME: wrong for manual impls of `FnOnce`
+ Substitution::empty(Interner),
+ locals,
+ destination,
+ None,
+ span,
+ )?;
+ return Ok(());
+ }
+ }
+ not_supported!("FnOnce was not available for executing const_eval_select");
+ }
+ "read_via_copy" | "volatile_load" => {
+ let [arg] = args else {
+ return Err(MirEvalError::TypeError("read_via_copy args are not provided"));
+ };
+ let addr = Address::from_bytes(arg.interval.get(self)?)?;
+ destination.write_from_interval(self, Interval { addr, size: destination.size })
+ }
+ "write_bytes" => {
+ let [dst, val, count] = args else {
+ return Err(MirEvalError::TypeError("write_bytes args are not provided"));
+ };
+ let count = from_bytes!(usize, count.get(self)?);
+ let val = from_bytes!(u8, val.get(self)?);
+ let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner))
+ else {
+ return Err(MirEvalError::TypeError(
+ "write_bytes generic arg is not provided",
+ ));
+ };
+ let dst = Address::from_bytes(dst.get(self)?)?;
+ let size = self.size_of_sized(ty, locals, "copy_nonoverlapping ptr type")?;
+ let size = count * size;
+ self.write_memory_using_ref(dst, size)?.fill(val);
+ Ok(())
}
_ => not_supported!("unknown intrinsic {name}"),
}
}
+ fn size_align_of_unsized(
+ &mut self,
+ ty: &Ty,
+ metadata: Interval,
+ locals: &Locals,
+ ) -> Result<(usize, usize)> {
+ Ok(match ty.kind(Interner) {
+ TyKind::Str => (from_bytes!(usize, metadata.get(self)?), 1),
+ TyKind::Slice(inner) => {
+ let len = from_bytes!(usize, metadata.get(self)?);
+ let (size, align) = self.size_align_of_sized(inner, locals, "slice inner type")?;
+ (size * len, align)
+ }
+ TyKind::Dyn(_) => self.size_align_of_sized(
+ self.vtable_map.ty_of_bytes(metadata.get(self)?)?,
+ locals,
+ "dyn concrete type",
+ )?,
+ TyKind::Adt(id, subst) => {
+ let id = id.0;
+ let layout = self.layout_adt(id, subst.clone())?;
+ let id = match id {
+ AdtId::StructId(s) => s,
+ _ => not_supported!("unsized enum or union"),
+ };
+ let field_types = &self.db.field_types(id.into());
+ let last_field_ty =
+ field_types.iter().rev().next().unwrap().1.clone().substitute(Interner, subst);
+ let sized_part_size =
+ layout.fields.offset(field_types.iter().count() - 1).bytes_usize();
+ let sized_part_align = layout.align.abi.bytes() as usize;
+ let (unsized_part_size, unsized_part_align) =
+ self.size_align_of_unsized(&last_field_ty, metadata, locals)?;
+ let align = sized_part_align.max(unsized_part_align) as isize;
+ let size = (sized_part_size + unsized_part_size) as isize;
+ // Must add any necessary padding to `size`
+ // (to make it a multiple of `align`) before returning it.
+ //
+ // Namely, the returned size should be, in C notation:
+ //
+ // `size + ((size & (align-1)) ? align : 0)`
+ //
+ // emulated via the semi-standard fast bit trick:
+ //
+ // `(size + (align-1)) & -align`
+ let size = (size + (align - 1)) & (-align);
+ (size as usize, align as usize)
+ }
+ _ => not_supported!("unsized type other than str, slice, struct and dyn"),
+ })
+ }
+
fn exec_atomic_intrinsic(
&mut self,
name: &str,
args: &[IntervalAndTy],
generic_args: &Substitution,
destination: Interval,
- locals: &Locals<'_>,
+ locals: &Locals,
_span: MirSpan,
) -> Result<()> {
// We are a single threaded runtime with no UB checking and no optimization, so
- // we can implement these as normal functions.
- let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|x| x.ty(Interner)) else {
+ // we can implement atomic intrinsics as normal functions.
+
+ if name.starts_with("singlethreadfence_") || name.starts_with("fence_") {
+ return Ok(());
+ }
+
+ // The rest of atomic intrinsics have exactly one generic arg
+
+ let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) else {
return Err(MirEvalError::TypeError("atomic intrinsic generic arg is not provided"));
};
let Some(arg0) = args.get(0) else {
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim/simd.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim/simd.rs
new file mode 100644
index 000000000..ec7463104
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim/simd.rs
@@ -0,0 +1,177 @@
+//! Shim implementation for simd intrinsics
+
+use std::cmp::Ordering;
+
+use crate::TyKind;
+
+use super::*;
+
+macro_rules! from_bytes {
+ ($ty:tt, $value:expr) => {
+ ($ty::from_le_bytes(match ($value).try_into() {
+ Ok(it) => it,
+ Err(_) => return Err(MirEvalError::TypeError("mismatched size")),
+ }))
+ };
+}
+
+macro_rules! not_supported {
+ ($it: expr) => {
+ return Err(MirEvalError::NotSupported(format!($it)))
+ };
+}
+
+impl Evaluator<'_> {
+ fn detect_simd_ty(&self, ty: &Ty) -> Result<(usize, Ty)> {
+ match ty.kind(Interner) {
+ TyKind::Adt(id, subst) => {
+ let len = match subst.as_slice(Interner).get(1).and_then(|it| it.constant(Interner))
+ {
+ Some(len) => len,
+ _ => {
+ if let AdtId::StructId(id) = id.0 {
+ let struct_data = self.db.struct_data(id);
+ let fields = struct_data.variant_data.fields();
+ let Some((first_field, _)) = fields.iter().next() else {
+ not_supported!("simd type with no field");
+ };
+ let field_ty = self.db.field_types(id.into())[first_field]
+ .clone()
+ .substitute(Interner, subst);
+ return Ok((fields.len(), field_ty));
+ }
+ return Err(MirEvalError::TypeError("simd type with no len param"));
+ }
+ };
+ match try_const_usize(self.db, len) {
+ Some(len) => {
+ let Some(ty) = subst.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) else {
+ return Err(MirEvalError::TypeError("simd type with no ty param"));
+ };
+ Ok((len as usize, ty.clone()))
+ }
+ None => Err(MirEvalError::TypeError("simd type with unevaluatable len param")),
+ }
+ }
+ _ => Err(MirEvalError::TypeError("simd type which is not a struct")),
+ }
+ }
+
+ pub(super) fn exec_simd_intrinsic(
+ &mut self,
+ name: &str,
+ args: &[IntervalAndTy],
+ _generic_args: &Substitution,
+ destination: Interval,
+ _locals: &Locals,
+ _span: MirSpan,
+ ) -> Result<()> {
+ match name {
+ "and" | "or" | "xor" => {
+ let [left, right] = args else {
+ return Err(MirEvalError::TypeError("simd bit op args are not provided"));
+ };
+ let result = left
+ .get(self)?
+ .iter()
+ .zip(right.get(self)?)
+ .map(|(&it, &y)| match name {
+ "and" => it & y,
+ "or" => it | y,
+ "xor" => it ^ y,
+ _ => unreachable!(),
+ })
+ .collect::<Vec<_>>();
+ destination.write_from_bytes(self, &result)
+ }
+ "eq" | "ne" | "lt" | "le" | "gt" | "ge" => {
+ let [left, right] = args else {
+ return Err(MirEvalError::TypeError("simd args are not provided"));
+ };
+ let (len, ty) = self.detect_simd_ty(&left.ty)?;
+ let is_signed = matches!(ty.as_builtin(), Some(BuiltinType::Int(_)));
+ let size = left.interval.size / len;
+ let dest_size = destination.size / len;
+ let mut destination_bytes = vec![];
+ let vector = left.get(self)?.chunks(size).zip(right.get(self)?.chunks(size));
+ for (l, r) in vector {
+ let mut result = Ordering::Equal;
+ for (l, r) in l.iter().zip(r).rev() {
+ let it = l.cmp(r);
+ if it != Ordering::Equal {
+ result = it;
+ break;
+ }
+ }
+ if is_signed {
+ if let Some((&l, &r)) = l.iter().zip(r).rev().next() {
+ if l != r {
+ result = (l as i8).cmp(&(r as i8));
+ }
+ }
+ }
+ let result = match result {
+ Ordering::Less => ["lt", "le", "ne"].contains(&name),
+ Ordering::Equal => ["ge", "le", "eq"].contains(&name),
+ Ordering::Greater => ["ge", "gt", "ne"].contains(&name),
+ };
+ let result = if result { 255 } else { 0 };
+ destination_bytes.extend(std::iter::repeat(result).take(dest_size));
+ }
+
+ destination.write_from_bytes(self, &destination_bytes)
+ }
+ "bitmask" => {
+ let [op] = args else {
+ return Err(MirEvalError::TypeError("simd_bitmask args are not provided"));
+ };
+ let (op_len, _) = self.detect_simd_ty(&op.ty)?;
+ let op_count = op.interval.size / op_len;
+ let mut result: u64 = 0;
+ for (i, val) in op.get(self)?.chunks(op_count).enumerate() {
+ if !val.iter().all(|&it| it == 0) {
+ result |= 1 << i;
+ }
+ }
+ destination.write_from_bytes(self, &result.to_le_bytes()[0..destination.size])
+ }
+ "shuffle" => {
+ let [left, right, index] = args else {
+ return Err(MirEvalError::TypeError("simd_shuffle args are not provided"));
+ };
+ let TyKind::Array(_, index_len) = index.ty.kind(Interner) else {
+ return Err(MirEvalError::TypeError(
+ "simd_shuffle index argument has non-array type",
+ ));
+ };
+ let index_len = match try_const_usize(self.db, index_len) {
+ Some(it) => it as usize,
+ None => {
+ return Err(MirEvalError::TypeError(
+ "simd type with unevaluatable len param",
+ ))
+ }
+ };
+ let (left_len, _) = self.detect_simd_ty(&left.ty)?;
+ let left_size = left.interval.size / left_len;
+ let vector =
+ left.get(self)?.chunks(left_size).chain(right.get(self)?.chunks(left_size));
+ let mut result = vec![];
+ for index in index.get(self)?.chunks(index.interval.size / index_len) {
+ let index = from_bytes!(u32, index) as usize;
+ let val = match vector.clone().nth(index) {
+ Some(it) => it,
+ None => {
+ return Err(MirEvalError::TypeError(
+ "out of bound access in simd shuffle",
+ ))
+ }
+ };
+ result.extend(val);
+ }
+ destination.write_from_bytes(self, &result)
+ }
+ _ => not_supported!("unknown simd intrinsic {name}"),
+ }
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/tests.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/tests.rs
index ca4268b8f..46165cf3d 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/tests.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/tests.rs
@@ -30,7 +30,7 @@ fn eval_main(db: &TestDB, file_id: FileId) -> Result<(String, String), MirEvalEr
db.trait_environment(func_id.into()),
)
.map_err(|e| MirEvalError::MirLowerError(func_id.into(), e))?;
- let (result, stdout, stderr) = interpret_mir(db, &body, false);
+ let (result, stdout, stderr) = interpret_mir(db, body, false, None);
result?;
Ok((stdout, stderr))
}
@@ -183,6 +183,50 @@ fn main() {
}
#[test]
+fn drop_struct_field() {
+ check_pass(
+ r#"
+//- minicore: drop, add, option, cell, builtin_impls
+
+use core::cell::Cell;
+
+fn should_not_reach() {
+ _ // FIXME: replace this function with panic when that works
+}
+
+struct X<'a>(&'a Cell<i32>);
+impl<'a> Drop for X<'a> {
+ fn drop(&mut self) {
+ self.0.set(self.0.get() + 1)
+ }
+}
+
+struct Tuple<'a>(X<'a>, X<'a>, X<'a>);
+
+fn main() {
+ let s = Cell::new(0);
+ {
+ let x0 = X(&s);
+ let xt = Tuple(x0, X(&s), X(&s));
+ let x1 = xt.1;
+ if s.get() != 0 {
+ should_not_reach();
+ }
+ drop(xt.0);
+ if s.get() != 1 {
+ should_not_reach();
+ }
+ }
+ // FIXME: this should be 3
+ if s.get() != 2 {
+ should_not_reach();
+ }
+}
+"#,
+ );
+}
+
+#[test]
fn drop_in_place() {
check_pass(
r#"
@@ -614,6 +658,78 @@ fn main() {
}
#[test]
+fn self_with_capital_s() {
+ check_pass(
+ r#"
+//- minicore: fn, add, copy
+
+struct S1;
+
+impl S1 {
+ fn f() {
+ Self;
+ }
+}
+
+struct S2 {
+ f1: i32,
+}
+
+impl S2 {
+ fn f() {
+ Self { f1: 5 };
+ }
+}
+
+struct S3(i32);
+
+impl S3 {
+ fn f() {
+ Self(2);
+ Self;
+ let this = Self;
+ this(2);
+ }
+}
+
+fn main() {
+ S1::f();
+ S2::f();
+ S3::f();
+}
+ "#,
+ );
+}
+
+#[test]
+fn syscalls() {
+ check_pass(
+ r#"
+//- minicore: option
+
+extern "C" {
+ pub unsafe extern "C" fn syscall(num: i64, ...) -> i64;
+}
+
+const SYS_getrandom: i64 = 318;
+
+fn should_not_reach() {
+ _ // FIXME: replace this function with panic when that works
+}
+
+fn main() {
+ let mut x: i32 = 0;
+ let r = syscall(SYS_getrandom, &mut x, 4usize, 0);
+ if r != 4 {
+ should_not_reach();
+ }
+}
+
+"#,
+ )
+}
+
+#[test]
fn posix_tls() {
check_pass(
r#"
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs
index 2cb29b4ab..718df8331 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs
@@ -146,12 +146,12 @@ impl MirLowerError {
ConstEvalError::MirEvalError(e) => e.pretty_print(f, db, span_formatter)?,
}
}
- MirLowerError::MissingFunctionDefinition(owner, x) => {
+ MirLowerError::MissingFunctionDefinition(owner, it) => {
let body = db.body(*owner);
writeln!(
f,
"Missing function definition for {}",
- body.pretty_print_expr(db.upcast(), *owner, *x)
+ body.pretty_print_expr(db.upcast(), *owner, *it)
)?;
}
MirLowerError::TypeMismatch(e) => {
@@ -202,15 +202,15 @@ impl MirLowerError {
}
macro_rules! not_supported {
- ($x: expr) => {
- return Err(MirLowerError::NotSupported(format!($x)))
+ ($it: expr) => {
+ return Err(MirLowerError::NotSupported(format!($it)))
};
}
macro_rules! implementation_error {
- ($x: expr) => {{
- ::stdx::never!("MIR lower implementation bug: {}", format!($x));
- return Err(MirLowerError::ImplementationError(format!($x)));
+ ($it: expr) => {{
+ ::stdx::never!("MIR lower implementation bug: {}", format!($it));
+ return Err(MirLowerError::ImplementationError(format!($it)));
}};
}
@@ -310,24 +310,30 @@ impl<'ctx> MirLowerCtx<'ctx> {
self.lower_expr_to_place_with_adjust(expr_id, temp.into(), current, rest)
}
Adjust::Deref(_) => {
- let Some((p, current)) = self.lower_expr_as_place_with_adjust(current, expr_id, true, adjustments)? else {
- return Ok(None);
- };
+ let Some((p, current)) =
+ self.lower_expr_as_place_with_adjust(current, expr_id, true, adjustments)?
+ else {
+ return Ok(None);
+ };
self.push_assignment(current, place, Operand::Copy(p).into(), expr_id.into());
Ok(Some(current))
}
Adjust::Borrow(AutoBorrow::Ref(m) | AutoBorrow::RawPtr(m)) => {
- let Some((p, current)) = self.lower_expr_as_place_with_adjust(current, expr_id, true, rest)? else {
- return Ok(None);
- };
+ let Some((p, current)) =
+ self.lower_expr_as_place_with_adjust(current, expr_id, true, rest)?
+ else {
+ return Ok(None);
+ };
let bk = BorrowKind::from_chalk(*m);
self.push_assignment(current, place, Rvalue::Ref(bk, p), expr_id.into());
Ok(Some(current))
}
Adjust::Pointer(cast) => {
- let Some((p, current)) = self.lower_expr_as_place_with_adjust(current, expr_id, true, rest)? else {
- return Ok(None);
- };
+ let Some((p, current)) =
+ self.lower_expr_as_place_with_adjust(current, expr_id, true, rest)?
+ else {
+ return Ok(None);
+ };
self.push_assignment(
current,
place,
@@ -373,45 +379,49 @@ impl<'ctx> MirLowerCtx<'ctx> {
}
}
Err(MirLowerError::IncompleteExpr)
- },
+ }
Expr::Path(p) => {
- let pr = if let Some((assoc, subst)) = self
- .infer
- .assoc_resolutions_for_expr(expr_id)
- {
- match assoc {
- hir_def::AssocItemId::ConstId(c) => {
- self.lower_const(c.into(), current, place, subst, expr_id.into(), self.expr_ty_without_adjust(expr_id))?;
- return Ok(Some(current))
- },
- hir_def::AssocItemId::FunctionId(_) => {
- // FnDefs are zero sized, no action is needed.
- return Ok(Some(current))
+ let pr =
+ if let Some((assoc, subst)) = self.infer.assoc_resolutions_for_expr(expr_id) {
+ match assoc {
+ hir_def::AssocItemId::ConstId(c) => {
+ self.lower_const(
+ c.into(),
+ current,
+ place,
+ subst,
+ expr_id.into(),
+ self.expr_ty_without_adjust(expr_id),
+ )?;
+ return Ok(Some(current));
+ }
+ hir_def::AssocItemId::FunctionId(_) => {
+ // FnDefs are zero sized, no action is needed.
+ return Ok(Some(current));
+ }
+ hir_def::AssocItemId::TypeAliasId(_) => {
+ // FIXME: If it is unreachable, use proper error instead of `not_supported`.
+ not_supported!("associated functions and types")
+ }
}
- hir_def::AssocItemId::TypeAliasId(_) => {
- // FIXME: If it is unreachable, use proper error instead of `not_supported`.
- not_supported!("associated functions and types")
- },
- }
- } else if let Some(variant) = self
- .infer
- .variant_resolution_for_expr(expr_id)
- {
- match variant {
- VariantId::EnumVariantId(e) => ValueNs::EnumVariantId(e),
- VariantId::StructId(s) => ValueNs::StructId(s),
- VariantId::UnionId(_) => implementation_error!("Union variant as path"),
- }
- } else {
- let unresolved_name = || MirLowerError::unresolved_path(self.db, p);
- let resolver = resolver_for_expr(self.db.upcast(), self.owner, expr_id);
- resolver
- .resolve_path_in_value_ns_fully(self.db.upcast(), p)
- .ok_or_else(unresolved_name)?
- };
+ } else if let Some(variant) = self.infer.variant_resolution_for_expr(expr_id) {
+ match variant {
+ VariantId::EnumVariantId(e) => ValueNs::EnumVariantId(e),
+ VariantId::StructId(s) => ValueNs::StructId(s),
+ VariantId::UnionId(_) => implementation_error!("Union variant as path"),
+ }
+ } else {
+ let unresolved_name = || MirLowerError::unresolved_path(self.db, p);
+ let resolver = resolver_for_expr(self.db.upcast(), self.owner, expr_id);
+ resolver
+ .resolve_path_in_value_ns_fully(self.db.upcast(), p)
+ .ok_or_else(unresolved_name)?
+ };
match pr {
ValueNs::LocalBinding(_) | ValueNs::StaticId(_) => {
- let Some((temp, current)) = self.lower_expr_as_place_without_adjust(current, expr_id, false)? else {
+ let Some((temp, current)) =
+ self.lower_expr_as_place_without_adjust(current, expr_id, false)?
+ else {
return Ok(None);
};
self.push_assignment(
@@ -423,11 +433,19 @@ impl<'ctx> MirLowerCtx<'ctx> {
Ok(Some(current))
}
ValueNs::ConstId(const_id) => {
- self.lower_const(const_id.into(), current, place, Substitution::empty(Interner), expr_id.into(), self.expr_ty_without_adjust(expr_id))?;
+ self.lower_const(
+ const_id.into(),
+ current,
+ place,
+ Substitution::empty(Interner),
+ expr_id.into(),
+ self.expr_ty_without_adjust(expr_id),
+ )?;
Ok(Some(current))
}
ValueNs::EnumVariantId(variant_id) => {
- let variant_data = &self.db.enum_data(variant_id.parent).variants[variant_id.local_id];
+ let variant_data =
+ &self.db.enum_data(variant_id.parent).variants[variant_id.local_id];
if variant_data.variant_data.kind() == StructKind::Unit {
let ty = self.infer.type_of_expr[expr_id].clone();
current = self.lower_enum_variant(
@@ -468,17 +486,16 @@ impl<'ctx> MirLowerCtx<'ctx> {
);
Ok(Some(current))
}
- ValueNs::FunctionId(_) | ValueNs::StructId(_) => {
+ ValueNs::FunctionId(_) | ValueNs::StructId(_) | ValueNs::ImplSelf(_) => {
// It's probably a unit struct or a zero sized function, so no action is needed.
Ok(Some(current))
}
- x => {
- not_supported!("unknown name {x:?} in value name space");
- }
}
}
Expr::If { condition, then_branch, else_branch } => {
- let Some((discr, current)) = self.lower_expr_to_some_operand(*condition, current)? else {
+ let Some((discr, current)) =
+ self.lower_expr_to_some_operand(*condition, current)?
+ else {
return Ok(None);
};
let start_of_then = self.new_basic_block();
@@ -501,15 +518,12 @@ impl<'ctx> MirLowerCtx<'ctx> {
Ok(self.merge_blocks(end_of_then, end_of_else, expr_id.into()))
}
Expr::Let { pat, expr } => {
- let Some((cond_place, current)) = self.lower_expr_as_place(current, *expr, true)? else {
+ let Some((cond_place, current)) = self.lower_expr_as_place(current, *expr, true)?
+ else {
return Ok(None);
};
- let (then_target, else_target) = self.pattern_match(
- current,
- None,
- cond_place,
- *pat,
- )?;
+ let (then_target, else_target) =
+ self.pattern_match(current, None, cond_place, *pat)?;
self.write_bytes_to_place(
then_target,
place.clone(),
@@ -533,49 +547,35 @@ impl<'ctx> MirLowerCtx<'ctx> {
}
Expr::Block { id: _, statements, tail, label } => {
if let Some(label) = label {
- self.lower_loop(current, place.clone(), Some(*label), expr_id.into(), |this, begin| {
- if let Some(current) = this.lower_block_to_place(statements, begin, *tail, place, expr_id.into())? {
- let end = this.current_loop_end()?;
- this.set_goto(current, end, expr_id.into());
- }
- Ok(())
- })
+ self.lower_loop(
+ current,
+ place.clone(),
+ Some(*label),
+ expr_id.into(),
+ |this, begin| {
+ if let Some(current) = this.lower_block_to_place(
+ statements,
+ begin,
+ *tail,
+ place,
+ expr_id.into(),
+ )? {
+ let end = this.current_loop_end()?;
+ this.set_goto(current, end, expr_id.into());
+ }
+ Ok(())
+ },
+ )
} else {
self.lower_block_to_place(statements, current, *tail, place, expr_id.into())
}
}
- Expr::Loop { body, label } => self.lower_loop(current, place, *label, expr_id.into(), |this, begin| {
- let scope = this.push_drop_scope();
- if let Some((_, mut current)) = this.lower_expr_as_place(begin, *body, true)? {
- current = scope.pop_and_drop(this, current);
- this.set_goto(current, begin, expr_id.into());
- } else {
- scope.pop_assume_dropped(this);
- }
- Ok(())
- }),
- Expr::While { condition, body, label } => {
- self.lower_loop(current, place, *label, expr_id.into(),|this, begin| {
+ Expr::Loop { body, label } => {
+ self.lower_loop(current, place, *label, expr_id.into(), |this, begin| {
let scope = this.push_drop_scope();
- let Some((discr, to_switch)) = this.lower_expr_to_some_operand(*condition, begin)? else {
- return Ok(());
- };
- let fail_cond = this.new_basic_block();
- let after_cond = this.new_basic_block();
- this.set_terminator(
- to_switch,
- TerminatorKind::SwitchInt {
- discr,
- targets: SwitchTargets::static_if(1, after_cond, fail_cond),
- },
- expr_id.into(),
- );
- let fail_cond = this.drop_until_scope(this.drop_scopes.len() - 1, fail_cond);
- let end = this.current_loop_end()?;
- this.set_goto(fail_cond, end, expr_id.into());
- if let Some((_, block)) = this.lower_expr_as_place(after_cond, *body, true)? {
- let block = scope.pop_and_drop(this, block);
- this.set_goto(block, begin, expr_id.into());
+ if let Some((_, mut current)) = this.lower_expr_as_place(begin, *body, true)? {
+ current = scope.pop_and_drop(this, current);
+ this.set_goto(current, begin, expr_id.into());
} else {
scope.pop_assume_dropped(this);
}
@@ -583,8 +583,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
})
}
Expr::Call { callee, args, .. } => {
- if let Some((func_id, generic_args)) =
- self.infer.method_resolution(expr_id) {
+ if let Some((func_id, generic_args)) = self.infer.method_resolution(expr_id) {
let ty = chalk_ir::TyKind::FnDef(
CallableDefId::FunctionId(func_id).to_chalk(self.db),
generic_args,
@@ -601,24 +600,51 @@ impl<'ctx> MirLowerCtx<'ctx> {
);
}
let callee_ty = self.expr_ty_after_adjustments(*callee);
- match &callee_ty.data(Interner).kind {
+ match &callee_ty.kind(Interner) {
chalk_ir::TyKind::FnDef(..) => {
let func = Operand::from_bytes(vec![], callee_ty.clone());
- self.lower_call_and_args(func, args.iter().copied(), place, current, self.is_uninhabited(expr_id), expr_id.into())
+ self.lower_call_and_args(
+ func,
+ args.iter().copied(),
+ place,
+ current,
+ self.is_uninhabited(expr_id),
+ expr_id.into(),
+ )
}
chalk_ir::TyKind::Function(_) => {
- let Some((func, current)) = self.lower_expr_to_some_operand(*callee, current)? else {
+ let Some((func, current)) =
+ self.lower_expr_to_some_operand(*callee, current)?
+ else {
return Ok(None);
};
- self.lower_call_and_args(func, args.iter().copied(), place, current, self.is_uninhabited(expr_id), expr_id.into())
+ self.lower_call_and_args(
+ func,
+ args.iter().copied(),
+ place,
+ current,
+ self.is_uninhabited(expr_id),
+ expr_id.into(),
+ )
+ }
+ TyKind::Closure(_, _) => {
+ not_supported!(
+ "method resolution not emitted for closure (Are Fn traits available?)"
+ );
+ }
+ TyKind::Error => {
+ return Err(MirLowerError::MissingFunctionDefinition(self.owner, expr_id))
}
- TyKind::Error => return Err(MirLowerError::MissingFunctionDefinition(self.owner, expr_id)),
_ => return Err(MirLowerError::TypeError("function call on bad type")),
}
}
Expr::MethodCall { receiver, args, method_name, .. } => {
let (func_id, generic_args) =
- self.infer.method_resolution(expr_id).ok_or_else(|| MirLowerError::UnresolvedMethod(method_name.display(self.db.upcast()).to_string()))?;
+ self.infer.method_resolution(expr_id).ok_or_else(|| {
+ MirLowerError::UnresolvedMethod(
+ method_name.display(self.db.upcast()).to_string(),
+ )
+ })?;
let func = Operand::from_fn(self.db, func_id, generic_args);
self.lower_call_and_args(
func,
@@ -630,23 +656,27 @@ impl<'ctx> MirLowerCtx<'ctx> {
)
}
Expr::Match { expr, arms } => {
- let Some((cond_place, mut current)) = self.lower_expr_as_place(current, *expr, true)?
+ let Some((cond_place, mut current)) =
+ self.lower_expr_as_place(current, *expr, true)?
else {
return Ok(None);
};
let mut end = None;
for MatchArm { pat, guard, expr } in arms.iter() {
- let (then, mut otherwise) = self.pattern_match(
- current,
- None,
- cond_place.clone(),
- *pat,
- )?;
+ let (then, mut otherwise) =
+ self.pattern_match(current, None, cond_place.clone(), *pat)?;
let then = if let &Some(guard) = guard {
let next = self.new_basic_block();
let o = otherwise.get_or_insert_with(|| self.new_basic_block());
if let Some((discr, c)) = self.lower_expr_to_some_operand(guard, then)? {
- self.set_terminator(c, TerminatorKind::SwitchInt { discr, targets: SwitchTargets::static_if(1, next, *o) }, expr_id.into());
+ self.set_terminator(
+ c,
+ TerminatorKind::SwitchInt {
+ discr,
+ targets: SwitchTargets::static_if(1, next, *o),
+ },
+ expr_id.into(),
+ );
}
next
} else {
@@ -672,33 +702,53 @@ impl<'ctx> MirLowerCtx<'ctx> {
}
Expr::Continue { label } => {
let loop_data = match label {
- Some(l) => self.labeled_loop_blocks.get(l).ok_or(MirLowerError::UnresolvedLabel)?,
- None => self.current_loop_blocks.as_ref().ok_or(MirLowerError::ContinueWithoutLoop)?,
+ Some(l) => {
+ self.labeled_loop_blocks.get(l).ok_or(MirLowerError::UnresolvedLabel)?
+ }
+ None => self
+ .current_loop_blocks
+ .as_ref()
+ .ok_or(MirLowerError::ContinueWithoutLoop)?,
};
let begin = loop_data.begin;
current = self.drop_until_scope(loop_data.drop_scope_index, current);
self.set_goto(current, begin, expr_id.into());
Ok(None)
- },
+ }
&Expr::Break { expr, label } => {
if let Some(expr) = expr {
let loop_data = match label {
- Some(l) => self.labeled_loop_blocks.get(&l).ok_or(MirLowerError::UnresolvedLabel)?,
- None => self.current_loop_blocks.as_ref().ok_or(MirLowerError::BreakWithoutLoop)?,
+ Some(l) => self
+ .labeled_loop_blocks
+ .get(&l)
+ .ok_or(MirLowerError::UnresolvedLabel)?,
+ None => self
+ .current_loop_blocks
+ .as_ref()
+ .ok_or(MirLowerError::BreakWithoutLoop)?,
};
- let Some(c) = self.lower_expr_to_place(expr, loop_data.place.clone(), current)? else {
+ let Some(c) =
+ self.lower_expr_to_place(expr, loop_data.place.clone(), current)?
+ else {
return Ok(None);
};
current = c;
}
let (end, drop_scope) = match label {
Some(l) => {
- let loop_blocks = self.labeled_loop_blocks.get(&l).ok_or(MirLowerError::UnresolvedLabel)?;
- (loop_blocks.end.expect("We always generate end for labeled loops"), loop_blocks.drop_scope_index)
- },
- None => {
- (self.current_loop_end()?, self.current_loop_blocks.as_ref().unwrap().drop_scope_index)
- },
+ let loop_blocks = self
+ .labeled_loop_blocks
+ .get(&l)
+ .ok_or(MirLowerError::UnresolvedLabel)?;
+ (
+ loop_blocks.end.expect("We always generate end for labeled loops"),
+ loop_blocks.drop_scope_index,
+ )
+ }
+ None => (
+ self.current_loop_end()?,
+ self.current_loop_blocks.as_ref().unwrap().drop_scope_index,
+ ),
};
current = self.drop_until_scope(drop_scope, current);
self.set_goto(current, end, expr_id.into());
@@ -706,7 +756,9 @@ impl<'ctx> MirLowerCtx<'ctx> {
}
Expr::Return { expr } => {
if let Some(expr) = expr {
- if let Some(c) = self.lower_expr_to_place(*expr, return_slot().into(), current)? {
+ if let Some(c) =
+ self.lower_expr_to_place(*expr, return_slot().into(), current)?
+ {
current = c;
} else {
return Ok(None);
@@ -719,19 +771,17 @@ impl<'ctx> MirLowerCtx<'ctx> {
Expr::Yield { .. } => not_supported!("yield"),
Expr::RecordLit { fields, path, spread, ellipsis: _, is_assignee_expr: _ } => {
let spread_place = match spread {
- &Some(x) => {
- let Some((p, c)) = self.lower_expr_as_place(current, x, true)? else {
+ &Some(it) => {
+ let Some((p, c)) = self.lower_expr_as_place(current, it, true)? else {
return Ok(None);
};
current = c;
Some(p)
- },
+ }
None => None,
};
- let variant_id = self
- .infer
- .variant_resolution_for_expr(expr_id)
- .ok_or_else(|| match path {
+ let variant_id =
+ self.infer.variant_resolution_for_expr(expr_id).ok_or_else(|| match path {
Some(p) => MirLowerError::UnresolvedName(p.display(self.db).to_string()),
None => MirLowerError::RecordLiteralWithoutPath,
})?;
@@ -746,7 +796,8 @@ impl<'ctx> MirLowerCtx<'ctx> {
for RecordLitField { name, expr } in fields.iter() {
let field_id =
variant_data.field(name).ok_or(MirLowerError::UnresolvedField)?;
- let Some((op, c)) = self.lower_expr_to_some_operand(*expr, current)? else {
+ let Some((op, c)) = self.lower_expr_to_some_operand(*expr, current)?
+ else {
return Ok(None);
};
current = c;
@@ -758,18 +809,23 @@ impl<'ctx> MirLowerCtx<'ctx> {
Rvalue::Aggregate(
AggregateKind::Adt(variant_id, subst),
match spread_place {
- Some(sp) => operands.into_iter().enumerate().map(|(i, x)| {
- match x {
- Some(x) => x,
+ Some(sp) => operands
+ .into_iter()
+ .enumerate()
+ .map(|(i, it)| match it {
+ Some(it) => it,
None => {
- let p = sp.project(ProjectionElem::Field(FieldId {
- parent: variant_id,
- local_id: LocalFieldId::from_raw(RawIdx::from(i as u32)),
- }));
+ let p =
+ sp.project(ProjectionElem::Field(FieldId {
+ parent: variant_id,
+ local_id: LocalFieldId::from_raw(
+ RawIdx::from(i as u32),
+ ),
+ }));
Operand::Copy(p)
- },
- }
- }).collect(),
+ }
+ })
+ .collect(),
None => operands.into_iter().collect::<Option<_>>().ok_or(
MirLowerError::TypeError("missing field in record literal"),
)?,
@@ -785,7 +841,10 @@ impl<'ctx> MirLowerCtx<'ctx> {
};
let local_id =
variant_data.field(name).ok_or(MirLowerError::UnresolvedField)?;
- let place = place.project(PlaceElem::Field(FieldId { parent: union_id.into(), local_id }));
+ let place = place.project(PlaceElem::Field(FieldId {
+ parent: union_id.into(),
+ local_id,
+ }));
self.lower_expr_to_place(*expr, place, current)
}
}
@@ -795,11 +854,18 @@ impl<'ctx> MirLowerCtx<'ctx> {
Expr::Async { .. } => not_supported!("async block"),
&Expr::Const(id) => {
let subst = self.placeholder_subst();
- self.lower_const(id.into(), current, place, subst, expr_id.into(), self.expr_ty_without_adjust(expr_id))?;
+ self.lower_const(
+ id.into(),
+ current,
+ place,
+ subst,
+ expr_id.into(),
+ self.expr_ty_without_adjust(expr_id),
+ )?;
Ok(Some(current))
- },
+ }
Expr::Cast { expr, type_ref: _ } => {
- let Some((x, current)) = self.lower_expr_to_some_operand(*expr, current)? else {
+ let Some((it, current)) = self.lower_expr_to_some_operand(*expr, current)? else {
return Ok(None);
};
let source_ty = self.infer[*expr].clone();
@@ -807,7 +873,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
self.push_assignment(
current,
place,
- Rvalue::Cast(cast_kind(&source_ty, &target_ty)?, x, target_ty),
+ Rvalue::Cast(cast_kind(&source_ty, &target_ty)?, it, target_ty),
expr_id.into(),
);
Ok(Some(current))
@@ -822,23 +888,37 @@ impl<'ctx> MirLowerCtx<'ctx> {
}
Expr::Box { expr } => {
let ty = self.expr_ty_after_adjustments(*expr);
- self.push_assignment(current, place.clone(), Rvalue::ShallowInitBoxWithAlloc(ty), expr_id.into());
- let Some((operand, current)) = self.lower_expr_to_some_operand(*expr, current)? else {
+ self.push_assignment(
+ current,
+ place.clone(),
+ Rvalue::ShallowInitBoxWithAlloc(ty),
+ expr_id.into(),
+ );
+ let Some((operand, current)) = self.lower_expr_to_some_operand(*expr, current)?
+ else {
return Ok(None);
};
let p = place.project(ProjectionElem::Deref);
self.push_assignment(current, p, operand.into(), expr_id.into());
Ok(Some(current))
- },
- Expr::Field { .. } | Expr::Index { .. } | Expr::UnaryOp { op: hir_def::hir::UnaryOp::Deref, .. } => {
- let Some((p, current)) = self.lower_expr_as_place_without_adjust(current, expr_id, true)? else {
+ }
+ Expr::Field { .. }
+ | Expr::Index { .. }
+ | Expr::UnaryOp { op: hir_def::hir::UnaryOp::Deref, .. } => {
+ let Some((p, current)) =
+ self.lower_expr_as_place_without_adjust(current, expr_id, true)?
+ else {
return Ok(None);
};
self.push_assignment(current, place, Operand::Copy(p).into(), expr_id.into());
Ok(Some(current))
}
- Expr::UnaryOp { expr, op: op @ (hir_def::hir::UnaryOp::Not | hir_def::hir::UnaryOp::Neg) } => {
- let Some((operand, current)) = self.lower_expr_to_some_operand(*expr, current)? else {
+ Expr::UnaryOp {
+ expr,
+ op: op @ (hir_def::hir::UnaryOp::Not | hir_def::hir::UnaryOp::Neg),
+ } => {
+ let Some((operand, current)) = self.lower_expr_to_some_operand(*expr, current)?
+ else {
return Ok(None);
};
let operation = match op {
@@ -853,7 +933,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
expr_id.into(),
);
Ok(Some(current))
- },
+ }
Expr::BinaryOp { lhs, rhs, op } => {
let op = op.ok_or(MirLowerError::IncompleteExpr)?;
let is_builtin = 'b: {
@@ -861,16 +941,19 @@ impl<'ctx> MirLowerCtx<'ctx> {
// for binary operator, and use without adjust to simplify our conditions.
let lhs_ty = self.expr_ty_without_adjust(*lhs);
let rhs_ty = self.expr_ty_without_adjust(*rhs);
- if matches!(op ,BinaryOp::CmpOp(syntax::ast::CmpOp::Eq { .. })) {
+ if matches!(op, BinaryOp::CmpOp(syntax::ast::CmpOp::Eq { .. })) {
if lhs_ty.as_raw_ptr().is_some() && rhs_ty.as_raw_ptr().is_some() {
break 'b true;
}
}
let builtin_inequal_impls = matches!(
op,
- BinaryOp::ArithOp(ArithOp::Shl | ArithOp::Shr) | BinaryOp::Assignment { op: Some(ArithOp::Shl | ArithOp::Shr) }
+ BinaryOp::ArithOp(ArithOp::Shl | ArithOp::Shr)
+ | BinaryOp::Assignment { op: Some(ArithOp::Shl | ArithOp::Shr) }
);
- lhs_ty.is_scalar() && rhs_ty.is_scalar() && (lhs_ty == rhs_ty || builtin_inequal_impls)
+ lhs_ty.is_scalar()
+ && rhs_ty.is_scalar()
+ && (lhs_ty == rhs_ty || builtin_inequal_impls)
};
if !is_builtin {
if let Some((func_id, generic_args)) = self.infer.method_resolution(expr_id) {
@@ -892,34 +975,34 @@ impl<'ctx> MirLowerCtx<'ctx> {
.infer
.expr_adjustments
.get(lhs)
- .and_then(|x| x.split_last())
- .map(|x| x.1)
- .ok_or(MirLowerError::TypeError("adjustment of binary op was missing"))?;
+ .and_then(|it| it.split_last())
+ .map(|it| it.1)
+ .ok_or(MirLowerError::TypeError(
+ "adjustment of binary op was missing",
+ ))?;
let Some((lhs_place, current)) =
self.lower_expr_as_place_with_adjust(current, *lhs, false, adjusts)?
else {
return Ok(None);
};
- let Some((rhs_op, current)) = self.lower_expr_to_some_operand(*rhs, current)? else {
+ let Some((rhs_op, current)) =
+ self.lower_expr_to_some_operand(*rhs, current)?
+ else {
return Ok(None);
};
- let r_value = Rvalue::CheckedBinaryOp(op.into(), Operand::Copy(lhs_place.clone()), rhs_op);
+ let r_value = Rvalue::CheckedBinaryOp(
+ op.into(),
+ Operand::Copy(lhs_place.clone()),
+ rhs_op,
+ );
self.push_assignment(current, lhs_place, r_value, expr_id.into());
return Ok(Some(current));
} else {
- let Some((lhs_place, current)) =
- self.lower_expr_as_place(current, *lhs, false)?
- else {
- return Ok(None);
- };
- let Some((rhs_op, current)) = self.lower_expr_to_some_operand(*rhs, current)? else {
- return Ok(None);
- };
- self.push_assignment(current, lhs_place, rhs_op.into(), expr_id.into());
- return Ok(Some(current));
+ return self.lower_assignment(current, *lhs, *rhs, expr_id.into());
}
}
- let Some((lhs_op, current)) = self.lower_expr_to_some_operand(*lhs, current)? else {
+ let Some((lhs_op, current)) = self.lower_expr_to_some_operand(*lhs, current)?
+ else {
return Ok(None);
};
if let hir_def::hir::BinaryOp::LogicOp(op) = op {
@@ -928,22 +1011,31 @@ impl<'ctx> MirLowerCtx<'ctx> {
syntax::ast::LogicOp::Or => 1,
};
let start_of_then = self.new_basic_block();
- self.push_assignment(start_of_then, place.clone(), lhs_op.clone().into(), expr_id.into());
+ self.push_assignment(
+ start_of_then,
+ place.clone(),
+ lhs_op.clone().into(),
+ expr_id.into(),
+ );
let end_of_then = Some(start_of_then);
let start_of_else = self.new_basic_block();
- let end_of_else =
- self.lower_expr_to_place(*rhs, place, start_of_else)?;
+ let end_of_else = self.lower_expr_to_place(*rhs, place, start_of_else)?;
self.set_terminator(
current,
TerminatorKind::SwitchInt {
discr: lhs_op,
- targets: SwitchTargets::static_if(value_to_short, start_of_then, start_of_else),
+ targets: SwitchTargets::static_if(
+ value_to_short,
+ start_of_then,
+ start_of_else,
+ ),
},
expr_id.into(),
);
return Ok(self.merge_blocks(end_of_then, end_of_else, expr_id.into()));
}
- let Some((rhs_op, current)) = self.lower_expr_to_some_operand(*rhs, current)? else {
+ let Some((rhs_op, current)) = self.lower_expr_to_some_operand(*rhs, current)?
+ else {
return Ok(None);
};
self.push_assignment(
@@ -976,15 +1068,15 @@ impl<'ctx> MirLowerCtx<'ctx> {
};
let mut lp = None;
let mut rp = None;
- if let Some(x) = lhs {
- let Some((o, c)) = self.lower_expr_to_some_operand(x, current)? else {
+ if let Some(it) = lhs {
+ let Some((o, c)) = self.lower_expr_to_some_operand(it, current)? else {
return Ok(None);
};
lp = Some(o);
current = c;
}
- if let Some(x) = rhs {
- let Some((o, c)) = self.lower_expr_to_some_operand(x, current)? else {
+ if let Some(it) = rhs {
+ let Some((o, c)) = self.lower_expr_to_some_operand(it, current)? else {
return Ok(None);
};
rp = Some(o);
@@ -995,20 +1087,28 @@ impl<'ctx> MirLowerCtx<'ctx> {
place,
Rvalue::Aggregate(
AggregateKind::Adt(st.into(), subst.clone()),
- self.db.struct_data(st).variant_data.fields().iter().map(|x| {
- let o = match x.1.name.as_str() {
- Some("start") => lp.take(),
- Some("end") => rp.take(),
- Some("exhausted") => Some(Operand::from_bytes(vec![0], TyBuilder::bool())),
- _ => None,
- };
- o.ok_or(MirLowerError::UnresolvedField)
- }).collect::<Result<_>>()?,
+ self.db
+ .struct_data(st)
+ .variant_data
+ .fields()
+ .iter()
+ .map(|it| {
+ let o = match it.1.name.as_str() {
+ Some("start") => lp.take(),
+ Some("end") => rp.take(),
+ Some("exhausted") => {
+ Some(Operand::from_bytes(vec![0], TyBuilder::bool()))
+ }
+ _ => None,
+ };
+ o.ok_or(MirLowerError::UnresolvedField)
+ })
+ .collect::<Result<_>>()?,
),
expr_id.into(),
);
Ok(Some(current))
- },
+ }
Expr::Closure { .. } => {
let ty = self.expr_ty_without_adjust(expr_id);
let TyKind::Closure(id, _) = ty.kind(Interner) else {
@@ -1020,22 +1120,33 @@ impl<'ctx> MirLowerCtx<'ctx> {
for capture in captures.iter() {
let p = Place {
local: self.binding_local(capture.place.local)?,
- projection: capture.place.projections.clone().into_iter().map(|x| {
- match x {
+ projection: capture
+ .place
+ .projections
+ .clone()
+ .into_iter()
+ .map(|it| match it {
ProjectionElem::Deref => ProjectionElem::Deref,
- ProjectionElem::Field(x) => ProjectionElem::Field(x),
- ProjectionElem::TupleOrClosureField(x) => ProjectionElem::TupleOrClosureField(x),
- ProjectionElem::ConstantIndex { offset, from_end } => ProjectionElem::ConstantIndex { offset, from_end },
- ProjectionElem::Subslice { from, to } => ProjectionElem::Subslice { from, to },
- ProjectionElem::OpaqueCast(x) => ProjectionElem::OpaqueCast(x),
- ProjectionElem::Index(x) => match x { },
- }
- }).collect(),
+ ProjectionElem::Field(it) => ProjectionElem::Field(it),
+ ProjectionElem::TupleOrClosureField(it) => {
+ ProjectionElem::TupleOrClosureField(it)
+ }
+ ProjectionElem::ConstantIndex { offset, from_end } => {
+ ProjectionElem::ConstantIndex { offset, from_end }
+ }
+ ProjectionElem::Subslice { from, to } => {
+ ProjectionElem::Subslice { from, to }
+ }
+ ProjectionElem::OpaqueCast(it) => ProjectionElem::OpaqueCast(it),
+ ProjectionElem::Index(it) => match it {},
+ })
+ .collect(),
};
match &capture.kind {
CaptureKind::ByRef(bk) => {
let placeholder_subst = self.placeholder_subst();
- let tmp_ty = capture.ty.clone().substitute(Interner, &placeholder_subst);
+ let tmp_ty =
+ capture.ty.clone().substitute(Interner, &placeholder_subst);
let tmp: Place = self.temp(tmp_ty, current, capture.span)?.into();
self.push_assignment(
current,
@@ -1044,7 +1155,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
capture.span,
);
operands.push(Operand::Move(tmp));
- },
+ }
CaptureKind::ByValue => operands.push(Operand::Move(p)),
}
}
@@ -1055,18 +1166,18 @@ impl<'ctx> MirLowerCtx<'ctx> {
expr_id.into(),
);
Ok(Some(current))
- },
+ }
Expr::Tuple { exprs, is_assignee_expr: _ } => {
let Some(values) = exprs
- .iter()
- .map(|x| {
- let Some((o, c)) = self.lower_expr_to_some_operand(*x, current)? else {
- return Ok(None);
- };
- current = c;
- Ok(Some(o))
- })
- .collect::<Result<Option<_>>>()?
+ .iter()
+ .map(|it| {
+ let Some((o, c)) = self.lower_expr_to_some_operand(*it, current)? else {
+ return Ok(None);
+ };
+ current = c;
+ Ok(Some(o))
+ })
+ .collect::<Result<Option<_>>>()?
else {
return Ok(None);
};
@@ -1079,7 +1190,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
}
Expr::Array(l) => match l {
Array::ElementList { elements, .. } => {
- let elem_ty = match &self.expr_ty_without_adjust(expr_id).data(Interner).kind {
+ let elem_ty = match &self.expr_ty_without_adjust(expr_id).kind(Interner) {
TyKind::Array(ty, _) => ty.clone(),
_ => {
return Err(MirLowerError::TypeError(
@@ -1088,30 +1199,29 @@ impl<'ctx> MirLowerCtx<'ctx> {
}
};
let Some(values) = elements
- .iter()
- .map(|x| {
- let Some((o, c)) = self.lower_expr_to_some_operand(*x, current)? else {
- return Ok(None);
- };
- current = c;
- Ok(Some(o))
- })
- .collect::<Result<Option<_>>>()?
+ .iter()
+ .map(|it| {
+ let Some((o, c)) = self.lower_expr_to_some_operand(*it, current)? else {
+ return Ok(None);
+ };
+ current = c;
+ Ok(Some(o))
+ })
+ .collect::<Result<Option<_>>>()?
else {
return Ok(None);
};
- let r = Rvalue::Aggregate(
- AggregateKind::Array(elem_ty),
- values,
- );
+ let r = Rvalue::Aggregate(AggregateKind::Array(elem_ty), values);
self.push_assignment(current, place, r, expr_id.into());
Ok(Some(current))
}
Array::Repeat { initializer, .. } => {
- let Some((init, current)) = self.lower_expr_to_some_operand(*initializer, current)? else {
+ let Some((init, current)) =
+ self.lower_expr_to_some_operand(*initializer, current)?
+ else {
return Ok(None);
};
- let len = match &self.expr_ty_without_adjust(expr_id).data(Interner).kind {
+ let len = match &self.expr_ty_without_adjust(expr_id).kind(Interner) {
TyKind::Array(_, len) => len.clone(),
_ => {
return Err(MirLowerError::TypeError(
@@ -1122,7 +1232,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
let r = Rvalue::Repeat(init, len);
self.push_assignment(current, place, r, expr_id.into());
Ok(Some(current))
- },
+ }
},
Expr::Literal(l) => {
let ty = self.expr_ty_without_adjust(expr_id);
@@ -1134,9 +1244,33 @@ impl<'ctx> MirLowerCtx<'ctx> {
}
}
+ fn lower_assignment(
+ &mut self,
+ current: BasicBlockId,
+ lhs: ExprId,
+ rhs: ExprId,
+ span: MirSpan,
+ ) -> Result<Option<BasicBlockId>> {
+ let Some((rhs_op, current)) =
+ self.lower_expr_to_some_operand(rhs, current)?
+ else {
+ return Ok(None);
+ };
+ if matches!(&self.body.exprs[lhs], Expr::Underscore) {
+ return Ok(Some(current));
+ }
+ let Some((lhs_place, current)) =
+ self.lower_expr_as_place(current, lhs, false)?
+ else {
+ return Ok(None);
+ };
+ self.push_assignment(current, lhs_place, rhs_op.into(), span);
+ Ok(Some(current))
+ }
+
fn placeholder_subst(&mut self) -> Substitution {
let placeholder_subst = match self.owner.as_generic_def_id() {
- Some(x) => TyBuilder::placeholder_subst(self.db, x),
+ Some(it) => TyBuilder::placeholder_subst(self.db, it),
None => Substitution::empty(Interner),
};
placeholder_subst
@@ -1192,7 +1326,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
fn lower_literal_to_operand(&mut self, ty: Ty, l: &Literal) -> Result<Operand> {
let size = self
.db
- .layout_of_ty(ty.clone(), self.owner.module(self.db.upcast()).krate())?
+ .layout_of_ty(ty.clone(), self.db.trait_environment_for_body(self.owner))?
.size
.bytes_usize();
let bytes = match l {
@@ -1206,7 +1340,6 @@ impl<'ctx> MirLowerCtx<'ctx> {
return Ok(Operand::from_concrete_const(data, mm, ty));
}
hir_def::hir::Literal::CString(b) => {
- let b = b.as_bytes();
let bytes = b.iter().copied().chain(iter::once(0)).collect::<Vec<_>>();
let mut data = Vec::with_capacity(mem::size_of::<usize>() * 2);
@@ -1226,8 +1359,8 @@ impl<'ctx> MirLowerCtx<'ctx> {
}
hir_def::hir::Literal::Char(c) => u32::from(*c).to_le_bytes().into(),
hir_def::hir::Literal::Bool(b) => vec![*b as u8],
- hir_def::hir::Literal::Int(x, _) => x.to_le_bytes()[0..size].into(),
- hir_def::hir::Literal::Uint(x, _) => x.to_le_bytes()[0..size].into(),
+ hir_def::hir::Literal::Int(it, _) => it.to_le_bytes()[0..size].into(),
+ hir_def::hir::Literal::Uint(it, _) => it.to_le_bytes()[0..size].into(),
hir_def::hir::Literal::Float(f, _) => match size {
8 => f.into_f64().to_le_bytes().into(),
4 => f.into_f32().to_le_bytes().into(),
@@ -1269,7 +1402,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
} else {
let name = const_id.name(self.db.upcast());
self.db
- .const_eval(const_id.into(), subst)
+ .const_eval(const_id.into(), subst, None)
.map_err(|e| MirLowerError::ConstEvalError(name, Box::new(e)))?
};
Ok(Operand::Constant(c))
@@ -1377,9 +1510,9 @@ impl<'ctx> MirLowerCtx<'ctx> {
fn expr_ty_after_adjustments(&self, e: ExprId) -> Ty {
let mut ty = None;
- if let Some(x) = self.infer.expr_adjustments.get(&e) {
- if let Some(x) = x.last() {
- ty = Some(x.target.clone());
+ if let Some(it) = self.infer.expr_adjustments.get(&e) {
+ if let Some(it) = it.last() {
+ ty = Some(it.target.clone());
}
}
ty.unwrap_or_else(|| self.expr_ty_without_adjust(e))
@@ -1401,7 +1534,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
fn discr_temp_place(&mut self, current: BasicBlockId) -> Place {
match &self.discr_temp {
- Some(x) => x.clone(),
+ Some(it) => it.clone(),
None => {
let tmp: Place = self
.temp(TyBuilder::discr_ty(), current, MirSpan::Unknown)
@@ -1448,7 +1581,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
}
fn has_adjustments(&self, expr_id: ExprId) -> bool {
- !self.infer.expr_adjustments.get(&expr_id).map(|x| x.is_empty()).unwrap_or(true)
+ !self.infer.expr_adjustments.get(&expr_id).map(|it| it.is_empty()).unwrap_or(true)
}
fn merge_blocks(
@@ -1478,7 +1611,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
))?
.end
{
- Some(x) => x,
+ Some(it) => it,
None => {
let s = self.new_basic_block();
self.current_loop_blocks
@@ -1602,10 +1735,10 @@ impl<'ctx> MirLowerCtx<'ctx> {
pick_binding: impl Fn(BindingId) -> bool,
) -> Result<BasicBlockId> {
let base_param_count = self.result.param_locals.len();
- self.result.param_locals.extend(params.clone().map(|(x, ty)| {
+ self.result.param_locals.extend(params.clone().map(|(it, ty)| {
let local_id = self.result.locals.alloc(Local { ty });
self.drop_scopes.last_mut().unwrap().locals.push(local_id);
- if let Pat::Bind { id, subpat: None } = self.body[x] {
+ if let Pat::Bind { id, subpat: None } = self.body[it] {
if matches!(
self.body.bindings[id].mode,
BindingAnnotation::Unannotated | BindingAnnotation::Mutable
@@ -1646,7 +1779,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
fn binding_local(&self, b: BindingId) -> Result<LocalId> {
match self.result.binding_locals.get(b) {
- Some(x) => Ok(*x),
+ Some(it) => Ok(*it),
None => {
// FIXME: It should never happens, but currently it will happen in `const_dependent_on_local` test, which
// is a hir lowering problem IMO.
@@ -1731,6 +1864,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
fn cast_kind(source_ty: &Ty, target_ty: &Ty) -> Result<CastKind> {
Ok(match (source_ty.kind(Interner), target_ty.kind(Interner)) {
+ (TyKind::FnDef(..), TyKind::Function(_)) => CastKind::Pointer(PointerCast::ReifyFnPointer),
(TyKind::Scalar(s), TyKind::Scalar(t)) => match (s, t) {
(chalk_ir::Scalar::Float(_), chalk_ir::Scalar::Float(_)) => CastKind::FloatToFloat,
(chalk_ir::Scalar::Float(_), _) => CastKind::FloatToInt,
@@ -1742,17 +1876,17 @@ fn cast_kind(source_ty: &Ty, target_ty: &Ty) -> Result<CastKind> {
(TyKind::Raw(_, a) | TyKind::Ref(_, _, a), TyKind::Raw(_, b) | TyKind::Ref(_, _, b)) => {
CastKind::Pointer(if a == b {
PointerCast::MutToConstPointer
- } else if matches!(a.kind(Interner), TyKind::Slice(_) | TyKind::Str)
- && matches!(b.kind(Interner), TyKind::Slice(_) | TyKind::Str)
+ } else if matches!(b.kind(Interner), TyKind::Slice(_))
+ && matches!(a.kind(Interner), TyKind::Array(_, _))
+ || matches!(b.kind(Interner), TyKind::Dyn(_))
{
- // slice to slice cast is no-op (metadata is not touched), so we use this
- PointerCast::MutToConstPointer
- } else if matches!(b.kind(Interner), TyKind::Slice(_) | TyKind::Dyn(_)) {
PointerCast::Unsize
} else if matches!(a.kind(Interner), TyKind::Slice(s) if s == b) {
PointerCast::ArrayToPointer
} else {
- // cast between two sized pointer, like *const i32 to *const i8. There is no specific variant
+ // cast between two sized pointer, like *const i32 to *const i8, or two unsized pointer, like
+ // slice to slice, slice to str, ... . These are no-ops (even in the unsized case, no metadata
+ // will be touched) but there is no specific variant
// for it in `PointerCast` so we use `MutToConstPointer`
PointerCast::MutToConstPointer
})
@@ -1796,7 +1930,7 @@ pub fn mir_body_for_closure_query(
implementation_error!("closure has not callable sig");
};
let current = ctx.lower_params_and_bindings(
- args.iter().zip(sig.params().iter()).map(|(x, y)| (*x, y.clone())),
+ args.iter().zip(sig.params().iter()).map(|(it, y)| (*it, y.clone())),
|_| true,
)?;
if let Some(current) = ctx.lower_expr_to_place(*root, return_slot().into(), current)? {
@@ -1815,34 +1949,35 @@ pub fn mir_body_for_closure_query(
FnTrait::FnMut | FnTrait::Fn => vec![ProjectionElem::Deref],
};
ctx.result.walk_places(|p| {
- if let Some(x) = upvar_map.get(&p.local) {
- let r = x.iter().find(|x| {
- if p.projection.len() < x.0.place.projections.len() {
+ if let Some(it) = upvar_map.get(&p.local) {
+ let r = it.iter().find(|it| {
+ if p.projection.len() < it.0.place.projections.len() {
return false;
}
- for (x, y) in p.projection.iter().zip(x.0.place.projections.iter()) {
- match (x, y) {
+ for (it, y) in p.projection.iter().zip(it.0.place.projections.iter()) {
+ match (it, y) {
(ProjectionElem::Deref, ProjectionElem::Deref) => (),
- (ProjectionElem::Field(x), ProjectionElem::Field(y)) if x == y => (),
+ (ProjectionElem::Field(it), ProjectionElem::Field(y)) if it == y => (),
(
- ProjectionElem::TupleOrClosureField(x),
+ ProjectionElem::TupleOrClosureField(it),
ProjectionElem::TupleOrClosureField(y),
- ) if x == y => (),
+ ) if it == y => (),
_ => return false,
}
}
true
});
match r {
- Some(x) => {
+ Some(it) => {
p.local = closure_local;
let mut next_projs = closure_projection.clone();
- next_projs.push(PlaceElem::TupleOrClosureField(x.1));
+ next_projs.push(PlaceElem::TupleOrClosureField(it.1));
let prev_projs = mem::take(&mut p.projection);
- if x.0.kind != CaptureKind::ByValue {
+ if it.0.kind != CaptureKind::ByValue {
next_projs.push(ProjectionElem::Deref);
}
- next_projs.extend(prev_projs.iter().cloned().skip(x.0.place.projections.len()));
+ next_projs
+ .extend(prev_projs.iter().cloned().skip(it.0.place.projections.len()));
p.projection = next_projs.into();
}
None => err = Some(p.clone()),
@@ -1902,8 +2037,8 @@ pub fn lower_to_mir(
// need to take this input explicitly.
root_expr: ExprId,
) -> Result<MirBody> {
- if let Some((_, x)) = infer.type_mismatches().next() {
- return Err(MirLowerError::TypeMismatch(x.clone()));
+ if let Some((_, it)) = infer.type_mismatches().next() {
+ return Err(MirLowerError::TypeMismatch(it.clone()));
}
let mut ctx = MirLowerCtx::new(db, owner, body, infer);
// 0 is return local
@@ -1929,7 +2064,7 @@ pub fn lower_to_mir(
body.params
.iter()
.zip(callable_sig.params().iter())
- .map(|(x, y)| (*x, y.clone())),
+ .map(|(it, y)| (*it, y.clone())),
binding_picker,
)?;
}
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/as_place.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/as_place.rs
index d2c8d9a08..213f151ab 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/as_place.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/as_place.rs
@@ -1,12 +1,12 @@
//! MIR lowering for places
use super::*;
-use hir_def::{lang_item::lang_attr, FunctionId};
+use hir_def::FunctionId;
use hir_expand::name;
macro_rules! not_supported {
- ($x: expr) => {
- return Err(MirLowerError::NotSupported(format!($x)))
+ ($it: expr) => {
+ return Err(MirLowerError::NotSupported(format!($it)))
};
}
@@ -18,7 +18,9 @@ impl MirLowerCtx<'_> {
) -> Result<Option<(Place, BasicBlockId)>> {
let ty = self.expr_ty_without_adjust(expr_id);
let place = self.temp(ty, prev_block, expr_id.into())?;
- let Some(current) = self.lower_expr_to_place_without_adjust(expr_id, place.into(), prev_block)? else {
+ let Some(current) =
+ self.lower_expr_to_place_without_adjust(expr_id, place.into(), prev_block)?
+ else {
return Ok(None);
};
Ok(Some((place.into(), current)))
@@ -32,10 +34,12 @@ impl MirLowerCtx<'_> {
) -> Result<Option<(Place, BasicBlockId)>> {
let ty = adjustments
.last()
- .map(|x| x.target.clone())
+ .map(|it| it.target.clone())
.unwrap_or_else(|| self.expr_ty_without_adjust(expr_id));
let place = self.temp(ty, prev_block, expr_id.into())?;
- let Some(current) = self.lower_expr_to_place_with_adjust(expr_id, place.into(), prev_block, adjustments)? else {
+ let Some(current) =
+ self.lower_expr_to_place_with_adjust(expr_id, place.into(), prev_block, adjustments)?
+ else {
return Ok(None);
};
Ok(Some((place.into(), current)))
@@ -57,16 +61,17 @@ impl MirLowerCtx<'_> {
if let Some((last, rest)) = adjustments.split_last() {
match last.kind {
Adjust::Deref(None) => {
- let Some(mut x) = self.lower_expr_as_place_with_adjust(
+ let Some(mut it) = self.lower_expr_as_place_with_adjust(
current,
expr_id,
upgrade_rvalue,
rest,
- )? else {
+ )?
+ else {
return Ok(None);
};
- x.0 = x.0.project(ProjectionElem::Deref);
- Ok(Some(x))
+ it.0 = it.0.project(ProjectionElem::Deref);
+ Ok(Some(it))
}
Adjust::Deref(Some(od)) => {
let Some((r, current)) = self.lower_expr_as_place_with_adjust(
@@ -74,14 +79,15 @@ impl MirLowerCtx<'_> {
expr_id,
upgrade_rvalue,
rest,
- )? else {
+ )?
+ else {
return Ok(None);
};
self.lower_overloaded_deref(
current,
r,
rest.last()
- .map(|x| x.target.clone())
+ .map(|it| it.target.clone())
.unwrap_or_else(|| self.expr_ty_without_adjust(expr_id)),
last.target.clone(),
expr_id.into(),
@@ -156,7 +162,7 @@ impl MirLowerCtx<'_> {
let is_builtin = match self.expr_ty_without_adjust(*expr).kind(Interner) {
TyKind::Ref(..) | TyKind::Raw(..) => true,
TyKind::Adt(id, _) => {
- if let Some(lang_item) = lang_attr(self.db.upcast(), id.0) {
+ if let Some(lang_item) = self.db.lang_attr(id.0.into()) {
lang_item == LangItem::OwnedBox
} else {
false
@@ -165,7 +171,8 @@ impl MirLowerCtx<'_> {
_ => false,
};
if !is_builtin {
- let Some((p, current)) = self.lower_expr_as_place(current, *expr, true)? else {
+ let Some((p, current)) = self.lower_expr_as_place(current, *expr, true)?
+ else {
return Ok(None);
};
return self.lower_overloaded_deref(
@@ -192,7 +199,8 @@ impl MirLowerCtx<'_> {
},
);
}
- let Some((mut r, current)) = self.lower_expr_as_place(current, *expr, true)? else {
+ let Some((mut r, current)) = self.lower_expr_as_place(current, *expr, true)?
+ else {
return Ok(None);
};
r = r.project(ProjectionElem::Deref);
@@ -217,12 +225,18 @@ impl MirLowerCtx<'_> {
)
{
let Some(index_fn) = self.infer.method_resolution(expr_id) else {
- return Err(MirLowerError::UnresolvedMethod("[overloaded index]".to_string()));
+ return Err(MirLowerError::UnresolvedMethod(
+ "[overloaded index]".to_string(),
+ ));
};
- let Some((base_place, current)) = self.lower_expr_as_place(current, *base, true)? else {
+ let Some((base_place, current)) =
+ self.lower_expr_as_place(current, *base, true)?
+ else {
return Ok(None);
};
- let Some((index_operand, current)) = self.lower_expr_to_some_operand(*index, current)? else {
+ let Some((index_operand, current)) =
+ self.lower_expr_to_some_operand(*index, current)?
+ else {
return Ok(None);
};
return self.lower_overloaded_index(
@@ -239,8 +253,8 @@ impl MirLowerCtx<'_> {
.infer
.expr_adjustments
.get(base)
- .and_then(|x| x.split_last())
- .map(|x| x.1)
+ .and_then(|it| it.split_last())
+ .map(|it| it.1)
.unwrap_or(&[]);
let Some((mut p_base, current)) =
self.lower_expr_as_place_with_adjust(current, *base, true, adjusts)?
@@ -249,7 +263,8 @@ impl MirLowerCtx<'_> {
};
let l_index =
self.temp(self.expr_ty_after_adjustments(*index), current, expr_id.into())?;
- let Some(current) = self.lower_expr_to_place(*index, l_index.into(), current)? else {
+ let Some(current) = self.lower_expr_to_place(*index, l_index.into(), current)?
+ else {
return Ok(None);
};
p_base = p_base.project(ProjectionElem::Index(l_index));
@@ -282,7 +297,15 @@ impl MirLowerCtx<'_> {
)
.intern(Interner),
);
- let Some(current) = self.lower_call(index_fn_op, Box::new([Operand::Copy(place), index_operand]), result.clone(), current, false, span)? else {
+ let Some(current) = self.lower_call(
+ index_fn_op,
+ Box::new([Operand::Copy(place), index_operand]),
+ result.clone(),
+ current,
+ false,
+ span,
+ )?
+ else {
return Ok(None);
};
result = result.project(ProjectionElem::Deref);
@@ -329,7 +352,15 @@ impl MirLowerCtx<'_> {
.intern(Interner),
);
let mut result: Place = self.temp(target_ty_ref, current, span)?.into();
- let Some(current) = self.lower_call(deref_fn_op, Box::new([Operand::Copy(ref_place)]), result.clone(), current, false, span)? else {
+ let Some(current) = self.lower_call(
+ deref_fn_op,
+ Box::new([Operand::Copy(ref_place)]),
+ result.clone(),
+ current,
+ false,
+ span,
+ )?
+ else {
return Ok(None);
};
result = result.project(ProjectionElem::Deref);
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/pattern_matching.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/pattern_matching.rs
index ff43c64a9..3354cbd76 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/pattern_matching.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/pattern_matching.rs
@@ -307,6 +307,11 @@ impl MirLowerCtx<'_> {
mode,
)?,
None => {
+ // The path is not a variant, so it is a const
+ if mode != MatchingMode::Check {
+ // A const don't bind anything. Only needs check.
+ return Ok((current, current_else));
+ }
let unresolved_name = || MirLowerError::unresolved_path(self.db, p);
let resolver = self.owner.resolver(self.db.upcast());
let pr = resolver
@@ -362,8 +367,8 @@ impl MirLowerCtx<'_> {
},
Pat::Lit(l) => match &self.body.exprs[*l] {
Expr::Literal(l) => {
- let c = self.lower_literal_to_operand(self.infer[pattern].clone(), l)?;
if mode == MatchingMode::Check {
+ let c = self.lower_literal_to_operand(self.infer[pattern].clone(), l)?;
self.pattern_match_const(current_else, current, c, cond_place, pattern)?
} else {
(current, current_else)
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/monomorphization.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/monomorphization.rs
index ce3f7a8e5..c565228d9 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/monomorphization.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/monomorphization.rs
@@ -13,15 +13,14 @@ use chalk_ir::{
fold::{FallibleTypeFolder, TypeFoldable, TypeSuperFoldable},
ConstData, DebruijnIndex,
};
-use hir_def::{DefWithBodyId, GeneralConstId};
+use hir_def::DefWithBodyId;
use triomphe::Arc;
use crate::{
- consteval::unknown_const,
+ consteval::{intern_const_scalar, unknown_const},
db::HirDatabase,
from_placeholder_idx,
infer::normalize,
- method_resolution::lookup_impl_const,
utils::{generics, Generics},
ClosureId, Const, Interner, ProjectionTy, Substitution, TraitEnvironment, Ty, TyKind,
};
@@ -29,8 +28,8 @@ use crate::{
use super::{MirBody, MirLowerError, Operand, Rvalue, StatementKind, TerminatorKind};
macro_rules! not_supported {
- ($x: expr) => {
- return Err(MirLowerError::NotSupported(format!($x)))
+ ($it: expr) => {
+ return Err(MirLowerError::NotSupported(format!($it)))
};
}
@@ -97,16 +96,16 @@ impl FallibleTypeFolder<Interner> for Filler<'_> {
idx: chalk_ir::PlaceholderIndex,
_outer_binder: DebruijnIndex,
) -> std::result::Result<chalk_ir::Const<Interner>, Self::Error> {
- let x = from_placeholder_idx(self.db, idx);
- let Some(idx) = self.generics.as_ref().and_then(|g| g.param_idx(x)) else {
+ let it = from_placeholder_idx(self.db, idx);
+ let Some(idx) = self.generics.as_ref().and_then(|g| g.param_idx(it)) else {
not_supported!("missing idx in generics");
};
Ok(self
.subst
.as_slice(Interner)
.get(idx)
- .and_then(|x| x.constant(Interner))
- .ok_or_else(|| MirLowerError::GenericArgNotProvided(x, self.subst.clone()))?
+ .and_then(|it| it.constant(Interner))
+ .ok_or_else(|| MirLowerError::GenericArgNotProvided(it, self.subst.clone()))?
.clone())
}
@@ -115,16 +114,16 @@ impl FallibleTypeFolder<Interner> for Filler<'_> {
idx: chalk_ir::PlaceholderIndex,
_outer_binder: DebruijnIndex,
) -> std::result::Result<Ty, Self::Error> {
- let x = from_placeholder_idx(self.db, idx);
- let Some(idx) = self.generics.as_ref().and_then(|g| g.param_idx(x)) else {
+ let it = from_placeholder_idx(self.db, idx);
+ let Some(idx) = self.generics.as_ref().and_then(|g| g.param_idx(it)) else {
not_supported!("missing idx in generics");
};
Ok(self
.subst
.as_slice(Interner)
.get(idx)
- .and_then(|x| x.ty(Interner))
- .ok_or_else(|| MirLowerError::GenericArgNotProvided(x, self.subst.clone()))?
+ .and_then(|it| it.ty(Interner))
+ .ok_or_else(|| MirLowerError::GenericArgNotProvided(it, self.subst.clone()))?
.clone())
}
@@ -180,7 +179,7 @@ impl Filler<'_> {
MirLowerError::GenericArgNotProvided(
self.generics
.as_ref()
- .and_then(|x| x.iter().nth(b.index))
+ .and_then(|it| it.iter().nth(b.index))
.unwrap()
.0,
self.subst.clone(),
@@ -193,25 +192,12 @@ impl Filler<'_> {
| chalk_ir::ConstValue::Placeholder(_) => {}
chalk_ir::ConstValue::Concrete(cc) => match &cc.interned {
crate::ConstScalar::UnevaluatedConst(const_id, subst) => {
- let mut const_id = *const_id;
let mut subst = subst.clone();
self.fill_subst(&mut subst)?;
- if let GeneralConstId::ConstId(c) = const_id {
- let (c, s) = lookup_impl_const(
- self.db,
- self.db.trait_environment_for_body(self.owner),
- c,
- subst,
- );
- const_id = GeneralConstId::ConstId(c);
- subst = s;
- }
- let result =
- self.db.const_eval(const_id.into(), subst).map_err(|e| {
- let name = const_id.name(self.db.upcast());
- MirLowerError::ConstEvalError(name, Box::new(e))
- })?;
- *c = result;
+ *c = intern_const_scalar(
+ crate::ConstScalar::UnevaluatedConst(*const_id, subst),
+ c.data(Interner).ty.clone(),
+ );
}
crate::ConstScalar::Bytes(_, _) | crate::ConstScalar::Unknown => (),
},
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/pretty.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/pretty.rs
index ac23e77bd..781ffaeca 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/pretty.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/pretty.rs
@@ -135,7 +135,7 @@ impl<'a> MirPrettyCtx<'a> {
fn for_closure(&mut self, closure: ClosureId) {
let body = match self.db.mir_body_for_closure(closure) {
- Ok(x) => x,
+ Ok(it) => it,
Err(e) => {
wln!(self, "// error in {closure:?}: {e:?}");
return;
@@ -145,7 +145,7 @@ impl<'a> MirPrettyCtx<'a> {
let indent = mem::take(&mut self.indent);
let mut ctx = MirPrettyCtx {
body: &body,
- local_to_binding: body.binding_locals.iter().map(|(x, y)| (*y, x)).collect(),
+ local_to_binding: body.binding_locals.iter().map(|(it, y)| (*y, it)).collect(),
result,
indent,
..*self
@@ -167,7 +167,7 @@ impl<'a> MirPrettyCtx<'a> {
}
fn new(body: &'a MirBody, hir_body: &'a Body, db: &'a dyn HirDatabase) -> Self {
- let local_to_binding = body.binding_locals.iter().map(|(x, y)| (*y, x)).collect();
+ let local_to_binding = body.binding_locals.iter().map(|(it, y)| (*y, it)).collect();
MirPrettyCtx {
body,
db,
@@ -315,17 +315,17 @@ impl<'a> MirPrettyCtx<'a> {
}
}
}
- ProjectionElem::TupleOrClosureField(x) => {
+ ProjectionElem::TupleOrClosureField(it) => {
f(this, local, head);
- w!(this, ".{}", x);
+ w!(this, ".{}", it);
}
ProjectionElem::Index(l) => {
f(this, local, head);
w!(this, "[{}]", this.local_name(*l).display(this.db));
}
- x => {
+ it => {
f(this, local, head);
- w!(this, ".{:?}", x);
+ w!(this, ".{:?}", it);
}
}
}
@@ -356,14 +356,14 @@ impl<'a> MirPrettyCtx<'a> {
}
self.place(p);
}
- Rvalue::Aggregate(AggregateKind::Tuple(_), x) => {
+ Rvalue::Aggregate(AggregateKind::Tuple(_), it) => {
w!(self, "(");
- self.operand_list(x);
+ self.operand_list(it);
w!(self, ")");
}
- Rvalue::Aggregate(AggregateKind::Array(_), x) => {
+ Rvalue::Aggregate(AggregateKind::Array(_), it) => {
w!(self, "[");
- self.operand_list(x);
+ self.operand_list(it);
w!(self, "]");
}
Rvalue::Repeat(op, len) => {
@@ -371,19 +371,19 @@ impl<'a> MirPrettyCtx<'a> {
self.operand(op);
w!(self, "; {}]", len.display(self.db));
}
- Rvalue::Aggregate(AggregateKind::Adt(_, _), x) => {
+ Rvalue::Aggregate(AggregateKind::Adt(_, _), it) => {
w!(self, "Adt(");
- self.operand_list(x);
+ self.operand_list(it);
w!(self, ")");
}
- Rvalue::Aggregate(AggregateKind::Closure(_), x) => {
+ Rvalue::Aggregate(AggregateKind::Closure(_), it) => {
w!(self, "Closure(");
- self.operand_list(x);
+ self.operand_list(it);
w!(self, ")");
}
- Rvalue::Aggregate(AggregateKind::Union(_, _), x) => {
+ Rvalue::Aggregate(AggregateKind::Union(_, _), it) => {
w!(self, "Union(");
- self.operand_list(x);
+ self.operand_list(it);
w!(self, ")");
}
Rvalue::Len(p) => {
@@ -428,8 +428,8 @@ impl<'a> MirPrettyCtx<'a> {
}
}
- fn operand_list(&mut self, x: &[Operand]) {
- let mut it = x.iter();
+ fn operand_list(&mut self, it: &[Operand]) {
+ let mut it = it.iter();
if let Some(first) = it.next() {
self.operand(first);
for op in it {
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests.rs
index 857141280..d22d0d85c 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/tests.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests.rs
@@ -30,7 +30,7 @@ use syntax::{
ast::{self, AstNode, HasName},
SyntaxNode,
};
-use tracing_subscriber::{layer::SubscriberExt, EnvFilter, Registry};
+use tracing_subscriber::{layer::SubscriberExt, Registry};
use tracing_tree::HierarchicalLayer;
use triomphe::Arc;
@@ -52,7 +52,8 @@ fn setup_tracing() -> Option<tracing::subscriber::DefaultGuard> {
return None;
}
- let filter = EnvFilter::from_env("CHALK_DEBUG");
+ let filter: tracing_subscriber::filter::Targets =
+ env::var("CHALK_DEBUG").ok().and_then(|it| it.parse().ok()).unwrap_or_default();
let layer = HierarchicalLayer::default()
.with_indent_lines(true)
.with_ansi(false)
@@ -205,7 +206,9 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour
let Some(node) = (match expr_or_pat {
hir_def::hir::ExprOrPatId::ExprId(expr) => expr_node(&body_source_map, expr, &db),
hir_def::hir::ExprOrPatId::PatId(pat) => pat_node(&body_source_map, pat, &db),
- }) else { continue; };
+ }) else {
+ continue;
+ };
let range = node.as_ref().original_file_range(&db);
let actual = format!(
"expected {}, got {}",
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/display_source_code.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/display_source_code.rs
index 425432479..e75b037e3 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/display_source_code.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/display_source_code.rs
@@ -227,3 +227,22 @@ fn f(a: impl Foo<i8, Assoc<i16> = i32>) {
"#,
);
}
+
+#[test]
+fn fn_def_is_shown_as_fn_ptr() {
+ check_types_source_code(
+ r#"
+fn foo(_: i32) -> i64 { 42 }
+struct S<T>(T);
+enum E { A(usize) }
+fn test() {
+ let f = foo;
+ //^ fn(i32) -> i64
+ let f = S::<i8>;
+ //^ fn(i8) -> S<i8>
+ let f = E::A;
+ //^ fn(usize) -> E
+}
+"#,
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/macros.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/macros.rs
index 111ac0b61..1e6e946a1 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/macros.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/macros.rs
@@ -202,13 +202,15 @@ fn expr_macro_def_expanded_in_various_places() {
100..119 'for _ ...!() {}': IntoIterator::IntoIter<isize>
100..119 'for _ ...!() {}': &mut IntoIterator::IntoIter<isize>
100..119 'for _ ...!() {}': fn next<IntoIterator::IntoIter<isize>>(&mut IntoIterator::IntoIter<isize>) -> Option<<IntoIterator::IntoIter<isize> as Iterator>::Item>
- 100..119 'for _ ...!() {}': Option<Iterator::Item<IntoIterator::IntoIter<isize>>>
+ 100..119 'for _ ...!() {}': Option<IntoIterator::Item<isize>>
100..119 'for _ ...!() {}': ()
100..119 'for _ ...!() {}': ()
100..119 'for _ ...!() {}': ()
- 104..105 '_': Iterator::Item<IntoIterator::IntoIter<isize>>
+ 104..105 '_': IntoIterator::Item<isize>
117..119 '{}': ()
124..134 '|| spam!()': impl Fn() -> isize
+ 140..156 'while ...!() {}': !
+ 140..156 'while ...!() {}': ()
140..156 'while ...!() {}': ()
154..156 '{}': ()
161..174 'break spam!()': !
@@ -293,13 +295,15 @@ fn expr_macro_rules_expanded_in_various_places() {
114..133 'for _ ...!() {}': IntoIterator::IntoIter<isize>
114..133 'for _ ...!() {}': &mut IntoIterator::IntoIter<isize>
114..133 'for _ ...!() {}': fn next<IntoIterator::IntoIter<isize>>(&mut IntoIterator::IntoIter<isize>) -> Option<<IntoIterator::IntoIter<isize> as Iterator>::Item>
- 114..133 'for _ ...!() {}': Option<Iterator::Item<IntoIterator::IntoIter<isize>>>
+ 114..133 'for _ ...!() {}': Option<IntoIterator::Item<isize>>
114..133 'for _ ...!() {}': ()
114..133 'for _ ...!() {}': ()
114..133 'for _ ...!() {}': ()
- 118..119 '_': Iterator::Item<IntoIterator::IntoIter<isize>>
+ 118..119 '_': IntoIterator::Item<isize>
131..133 '{}': ()
138..148 '|| spam!()': impl Fn() -> isize
+ 154..170 'while ...!() {}': !
+ 154..170 'while ...!() {}': ()
154..170 'while ...!() {}': ()
168..170 '{}': ()
175..188 'break spam!()': !
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/method_resolution.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/method_resolution.rs
index 1e57a4ae2..c837fae3f 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/method_resolution.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/method_resolution.rs
@@ -1216,6 +1216,73 @@ fn main() {
}
#[test]
+fn inherent_method_deref_raw() {
+ check_types(
+ r#"
+struct Val;
+
+impl Val {
+ pub fn method(self: *const Val) -> u32 {
+ 0
+ }
+}
+
+fn main() {
+ let foo: *const Val;
+ foo.method();
+ // ^^^^^^^^^^^^ u32
+}
+"#,
+ );
+}
+
+#[test]
+fn inherent_method_ref_self_deref_raw() {
+ check_types(
+ r#"
+struct Val;
+
+impl Val {
+ pub fn method(&self) -> u32 {
+ 0
+ }
+}
+
+fn main() {
+ let foo: *const Val;
+ foo.method();
+ // ^^^^^^^^^^^^ {unknown}
+}
+"#,
+ );
+}
+
+#[test]
+fn trait_method_deref_raw() {
+ check_types(
+ r#"
+trait Trait {
+ fn method(self: *const Self) -> u32;
+}
+
+struct Val;
+
+impl Trait for Val {
+ fn method(self: *const Self) -> u32 {
+ 0
+ }
+}
+
+fn main() {
+ let foo: *const Val;
+ foo.method();
+ // ^^^^^^^^^^^^ u32
+}
+"#,
+ );
+}
+
+#[test]
fn method_on_dyn_impl() {
check_types(
r#"
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/never_type.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/never_type.rs
index 59046c043..5d809b823 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/never_type.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/never_type.rs
@@ -412,17 +412,23 @@ fn diverging_expression_3_break() {
355..654 '{ ...; }; }': ()
398..399 'x': u32
407..433 '{ whil...; }; }': u32
+ 409..430 'while ...eak; }': !
+ 409..430 'while ...eak; }': ()
409..430 'while ...eak; }': ()
415..419 'true': bool
420..430 '{ break; }': ()
422..427 'break': !
537..538 'x': u32
546..564 '{ whil... {}; }': u32
+ 548..561 'while true {}': !
+ 548..561 'while true {}': ()
548..561 'while true {}': ()
554..558 'true': bool
559..561 '{}': ()
615..616 'x': u32
624..651 '{ whil...; }; }': u32
+ 626..648 'while ...urn; }': !
+ 626..648 'while ...urn; }': ()
626..648 'while ...urn; }': ()
632..636 'true': bool
637..648 '{ return; }': ()
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs
index 047900a32..6ea059065 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs
@@ -1240,11 +1240,11 @@ fn test() {
16..66 'for _ ... }': IntoIterator::IntoIter<()>
16..66 'for _ ... }': &mut IntoIterator::IntoIter<()>
16..66 'for _ ... }': fn next<IntoIterator::IntoIter<()>>(&mut IntoIterator::IntoIter<()>) -> Option<<IntoIterator::IntoIter<()> as Iterator>::Item>
- 16..66 'for _ ... }': Option<Iterator::Item<IntoIterator::IntoIter<()>>>
+ 16..66 'for _ ... }': Option<IntoIterator::Item<()>>
16..66 'for _ ... }': ()
16..66 'for _ ... }': ()
16..66 'for _ ... }': ()
- 20..21 '_': Iterator::Item<IntoIterator::IntoIter<()>>
+ 20..21 '_': IntoIterator::Item<()>
25..39 '{ let x = 0; }': ()
31..32 'x': i32
35..36 '0': i32
@@ -1267,6 +1267,8 @@ fn test() {
"#,
expect![[r#"
10..59 '{ ... } }': ()
+ 16..57 'while ... }': !
+ 16..57 'while ... }': ()
16..57 'while ... }': ()
22..30 '{ true }': bool
24..28 'true': bool
@@ -1978,3 +1980,23 @@ fn x(a: [i32; 4]) {
"#,
);
}
+
+#[test]
+fn dont_unify_on_casts() {
+ // #15246
+ check_types(
+ r#"
+fn unify(_: [bool; 1]) {}
+fn casted(_: *const bool) {}
+fn default<T>() -> T { loop {} }
+
+fn test() {
+ let foo = default();
+ //^^^ [bool; 1]
+
+ casted(&foo as *const _);
+ unify(foo);
+}
+"#,
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs
index a0ff62843..2ad7946c8 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs
@@ -3513,7 +3513,6 @@ fn func() {
);
}
-// FIXME
#[test]
fn castable_to() {
check_infer(
@@ -3538,10 +3537,10 @@ fn func() {
120..122 '{}': ()
138..184 '{ ...0]>; }': ()
148..149 'x': Box<[i32; 0]>
- 152..160 'Box::new': fn new<[{unknown}; 0]>([{unknown}; 0]) -> Box<[{unknown}; 0]>
- 152..164 'Box::new([])': Box<[{unknown}; 0]>
+ 152..160 'Box::new': fn new<[i32; 0]>([i32; 0]) -> Box<[i32; 0]>
+ 152..164 'Box::new([])': Box<[i32; 0]>
152..181 'Box::n...2; 0]>': Box<[i32; 0]>
- 161..163 '[]': [{unknown}; 0]
+ 161..163 '[]': [i32; 0]
"#]],
);
}
@@ -3578,6 +3577,21 @@ fn f<T>(t: Ark<T>) {
}
#[test]
+fn ref_to_array_to_ptr_cast() {
+ check_types(
+ r#"
+fn default<T>() -> T { loop {} }
+fn foo() {
+ let arr = [default()];
+ //^^^ [i32; 1]
+ let ref_to_arr = &arr;
+ let casted = ref_to_arr as *const i32;
+}
+"#,
+ );
+}
+
+#[test]
fn const_dependent_on_local() {
check_types(
r#"
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs
index 97ae732a9..542df8b34 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs
@@ -4149,6 +4149,30 @@ where
}
#[test]
+fn gats_in_bounds_for_assoc() {
+ check_types(
+ r#"
+trait Trait {
+ type Assoc: Another<Gat<i32> = usize>;
+ type Assoc2<T>: Another<Gat<T> = T>;
+}
+trait Another {
+ type Gat<T>;
+ fn foo(&self) -> Self::Gat<i32>;
+ fn bar<T>(&self) -> Self::Gat<T>;
+}
+
+fn test<T: Trait>(a: T::Assoc, b: T::Assoc2<isize>) {
+ let v = a.foo();
+ //^ usize
+ let v = b.bar::<isize>();
+ //^ isize
+}
+"#,
+ );
+}
+
+#[test]
fn bin_op_with_scalar_fallback() {
// Extra impls are significant so that chalk doesn't give us definite guidances.
check_types(
@@ -4410,3 +4434,47 @@ fn test(v: S<i32>) {
"#,
);
}
+
+#[test]
+fn associated_type_in_argument() {
+ check(
+ r#"
+ trait A {
+ fn m(&self) -> i32;
+ }
+
+ fn x<T: B>(k: &<T as B>::Ty) {
+ k.m();
+ }
+
+ struct X;
+ struct Y;
+
+ impl A for X {
+ fn m(&self) -> i32 {
+ 8
+ }
+ }
+
+ impl A for Y {
+ fn m(&self) -> i32 {
+ 32
+ }
+ }
+
+ trait B {
+ type Ty: A;
+ }
+
+ impl B for u16 {
+ type Ty = X;
+ }
+
+ fn ttt() {
+ let inp = Y;
+ x::<u16>(&inp);
+ //^^^^ expected &X, got &Y
+ }
+ "#,
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/traits.rs b/src/tools/rust-analyzer/crates/hir-ty/src/traits.rs
index f40b7db3a..3c7cfbaed 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/traits.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/traits.rs
@@ -170,7 +170,7 @@ fn solve(
struct LoggingRustIrDatabaseLoggingOnDrop<'a>(LoggingRustIrDatabase<Interner, ChalkContext<'a>>);
-impl<'a> Drop for LoggingRustIrDatabaseLoggingOnDrop<'a> {
+impl Drop for LoggingRustIrDatabaseLoggingOnDrop<'_> {
fn drop(&mut self) {
eprintln!("chalk program:\n{}", self.0);
}
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/utils.rs b/src/tools/rust-analyzer/crates/hir-ty/src/utils.rs
index 363658063..75b8b9afa 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/utils.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/utils.rs
@@ -28,14 +28,15 @@ use intern::Interned;
use rustc_hash::FxHashSet;
use smallvec::{smallvec, SmallVec};
use stdx::never;
+use triomphe::Arc;
use crate::{
consteval::unknown_const,
db::HirDatabase,
layout::{Layout, TagEncoding},
mir::pad16,
- ChalkTraitId, Const, ConstScalar, GenericArg, Interner, Substitution, TraitRef, TraitRefExt,
- Ty, WhereClause,
+ ChalkTraitId, Const, ConstScalar, GenericArg, Interner, Substitution, TraitEnvironment,
+ TraitRef, TraitRefExt, Ty, WhereClause,
};
pub(crate) fn fn_traits(
@@ -89,7 +90,7 @@ struct SuperTraits<'a> {
seen: FxHashSet<ChalkTraitId>,
}
-impl<'a> SuperTraits<'a> {
+impl SuperTraits<'_> {
fn elaborate(&mut self, trait_ref: &TraitRef) {
direct_super_trait_refs(self.db, trait_ref, |trait_ref| {
if !self.seen.contains(&trait_ref.trait_id) {
@@ -99,7 +100,7 @@ impl<'a> SuperTraits<'a> {
}
}
-impl<'a> Iterator for SuperTraits<'a> {
+impl Iterator for SuperTraits<'_> {
type Item = TraitRef;
fn next(&mut self) -> Option<Self::Item> {
@@ -417,7 +418,7 @@ impl FallibleTypeFolder<Interner> for UnevaluatedConstEvaluatorFolder<'_> {
) -> Result<Const, Self::Error> {
if let chalk_ir::ConstValue::Concrete(c) = &constant.data(Interner).value {
if let ConstScalar::UnevaluatedConst(id, subst) = &c.interned {
- if let Ok(eval) = self.db.const_eval(*id, subst.clone()) {
+ if let Ok(eval) = self.db.const_eval(*id, subst.clone(), None) {
return Ok(eval);
} else {
return Ok(unknown_const(constant.data(Interner).ty.clone()));
@@ -431,10 +432,11 @@ impl FallibleTypeFolder<Interner> for UnevaluatedConstEvaluatorFolder<'_> {
pub(crate) fn detect_variant_from_bytes<'a>(
layout: &'a Layout,
db: &dyn HirDatabase,
- krate: CrateId,
+ trait_env: Arc<TraitEnvironment>,
b: &[u8],
e: EnumId,
) -> Option<(LocalEnumVariantId, &'a Layout)> {
+ let krate = trait_env.krate;
let (var_id, var_layout) = match &layout.variants {
hir_def::layout::Variants::Single { index } => (index.0, &*layout),
hir_def::layout::Variants::Multiple { tag, tag_encoding, variants, .. } => {
diff --git a/src/tools/rust-analyzer/crates/hir/Cargo.toml b/src/tools/rust-analyzer/crates/hir/Cargo.toml
index a20aff93f..f860ee948 100644
--- a/src/tools/rust-analyzer/crates/hir/Cargo.toml
+++ b/src/tools/rust-analyzer/crates/hir/Cargo.toml
@@ -18,7 +18,7 @@ arrayvec = "0.7.2"
itertools = "0.10.5"
smallvec.workspace = true
triomphe.workspace = true
-once_cell = "1.17.0"
+once_cell = "1.17.1"
# local deps
base-db.workspace = true
diff --git a/src/tools/rust-analyzer/crates/hir/src/attrs.rs b/src/tools/rust-analyzer/crates/hir/src/attrs.rs
index b81793729..0f2fb2c81 100644
--- a/src/tools/rust-analyzer/crates/hir/src/attrs.rs
+++ b/src/tools/rust-analyzer/crates/hir/src/attrs.rs
@@ -12,9 +12,9 @@ use hir_ty::db::HirDatabase;
use syntax::{ast, AstNode};
use crate::{
- Adt, AssocItem, Const, ConstParam, Enum, Field, Function, GenericParam, Impl, LifetimeParam,
- Macro, Module, ModuleDef, Static, Struct, Trait, TraitAlias, TypeAlias, TypeParam, Union,
- Variant,
+ Adt, AssocItem, Const, ConstParam, Enum, ExternCrateDecl, Field, Function, GenericParam, Impl,
+ LifetimeParam, Macro, Module, ModuleDef, Static, Struct, Trait, TraitAlias, TypeAlias,
+ TypeParam, Union, Variant,
};
pub trait HasAttrs {
@@ -120,6 +120,39 @@ impl HasAttrs for AssocItem {
}
}
+impl HasAttrs for ExternCrateDecl {
+ fn attrs(self, db: &dyn HirDatabase) -> AttrsWithOwner {
+ let def = AttrDefId::ExternCrateId(self.into());
+ db.attrs_with_owner(def)
+ }
+ fn docs(self, db: &dyn HirDatabase) -> Option<Documentation> {
+ let crate_docs = self.resolved_crate(db)?.root_module().attrs(db).docs().map(String::from);
+ let def = AttrDefId::ExternCrateId(self.into());
+ let decl_docs = db.attrs(def).docs().map(String::from);
+ match (decl_docs, crate_docs) {
+ (None, None) => None,
+ (Some(decl_docs), None) => Some(decl_docs),
+ (None, Some(crate_docs)) => Some(crate_docs),
+ (Some(mut decl_docs), Some(crate_docs)) => {
+ decl_docs.push('\n');
+ decl_docs.push('\n');
+ decl_docs += &crate_docs;
+ Some(decl_docs)
+ }
+ }
+ .map(Documentation::new)
+ }
+ fn resolve_doc_path(
+ self,
+ db: &dyn HirDatabase,
+ link: &str,
+ ns: Option<Namespace>,
+ ) -> Option<ModuleDef> {
+ let def = AttrDefId::ExternCrateId(self.into());
+ resolve_doc_path(db, def, link, ns).map(ModuleDef::from)
+ }
+}
+
/// Resolves the item `link` points to in the scope of `def`.
fn resolve_doc_path(
db: &dyn HirDatabase,
@@ -140,7 +173,9 @@ fn resolve_doc_path(
AttrDefId::TypeAliasId(it) => it.resolver(db.upcast()),
AttrDefId::ImplId(it) => it.resolver(db.upcast()),
AttrDefId::ExternBlockId(it) => it.resolver(db.upcast()),
+ AttrDefId::UseId(it) => it.resolver(db.upcast()),
AttrDefId::MacroId(it) => it.resolver(db.upcast()),
+ AttrDefId::ExternCrateId(it) => it.resolver(db.upcast()),
AttrDefId::GenericParamId(it) => match it {
GenericParamId::TypeParamId(it) => it.parent(),
GenericParamId::ConstParamId(it) => it.parent(),
diff --git a/src/tools/rust-analyzer/crates/hir/src/db.rs b/src/tools/rust-analyzer/crates/hir/src/db.rs
index e0cde689f..936581bfe 100644
--- a/src/tools/rust-analyzer/crates/hir/src/db.rs
+++ b/src/tools/rust-analyzer/crates/hir/src/db.rs
@@ -5,13 +5,8 @@
//! But we need this for at least LRU caching at the query level.
pub use hir_def::db::*;
pub use hir_expand::db::{
- AstIdMapQuery, ExpandDatabase, ExpandDatabaseStorage, ExpandProcMacroQuery, HygieneFrameQuery,
- InternMacroCallQuery, MacroArgTextQuery, MacroDefQuery, MacroExpandQuery,
- ParseMacroExpansionErrorQuery, ParseMacroExpansionQuery,
+ AstIdMapQuery, DeclMacroExpanderQuery, ExpandDatabase, ExpandDatabaseStorage,
+ ExpandProcMacroQuery, HygieneFrameQuery, InternMacroCallQuery, MacroArgNodeQuery,
+ MacroExpandQuery, ParseMacroExpansionErrorQuery, ParseMacroExpansionQuery,
};
pub use hir_ty::db::*;
-
-#[test]
-fn hir_database_is_object_safe() {
- fn _assert_object_safe(_: &dyn HirDatabase) {}
-}
diff --git a/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs b/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs
index b64d81490..80c3bcdca 100644
--- a/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs
+++ b/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs
@@ -3,7 +3,7 @@
//!
//! This probably isn't the best way to do this -- ideally, diagnostics should
//! be expressed in terms of hir types themselves.
-pub use hir_ty::diagnostics::{IncoherentImpl, IncorrectCase};
+pub use hir_ty::diagnostics::{CaseType, IncoherentImpl, IncorrectCase};
use base_db::CrateId;
use cfg::{CfgExpr, CfgOptions};
diff --git a/src/tools/rust-analyzer/crates/hir/src/display.rs b/src/tools/rust-analyzer/crates/hir/src/display.rs
index 9a2090ab7..9dfb98e45 100644
--- a/src/tools/rust-analyzer/crates/hir/src/display.rs
+++ b/src/tools/rust-analyzer/crates/hir/src/display.rs
@@ -18,9 +18,9 @@ use hir_ty::{
};
use crate::{
- Adt, AsAssocItem, AssocItemContainer, Const, ConstParam, Enum, Field, Function, GenericParam,
- HasCrate, HasVisibility, LifetimeParam, Macro, Module, Static, Struct, Trait, TraitAlias,
- TyBuilder, Type, TypeAlias, TypeOrConstParam, TypeParam, Union, Variant,
+ Adt, AsAssocItem, AssocItemContainer, Const, ConstParam, Enum, ExternCrateDecl, Field,
+ Function, GenericParam, HasCrate, HasVisibility, LifetimeParam, Macro, Module, Static, Struct,
+ Trait, TraitAlias, TyBuilder, Type, TypeAlias, TypeOrConstParam, TypeParam, Union, Variant,
};
impl HirDisplay for Function {
@@ -238,6 +238,18 @@ impl HirDisplay for Type {
}
}
+impl HirDisplay for ExternCrateDecl {
+ fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
+ write_visibility(self.module(f.db).id, self.visibility(f.db), f)?;
+ f.write_str("extern crate ")?;
+ write!(f, "{}", self.name(f.db).display(f.db.upcast()))?;
+ if let Some(alias) = self.alias(f.db) {
+ write!(f, " as {alias}",)?;
+ }
+ Ok(())
+ }
+}
+
impl HirDisplay for GenericParam {
fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
match self {
@@ -251,8 +263,8 @@ impl HirDisplay for GenericParam {
impl HirDisplay for TypeOrConstParam {
fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
match self.split(f.db) {
- either::Either::Left(x) => x.hir_fmt(f),
- either::Either::Right(x) => x.hir_fmt(f),
+ either::Either::Left(it) => it.hir_fmt(f),
+ either::Either::Right(it) => it.hir_fmt(f),
}
}
}
@@ -303,11 +315,11 @@ fn write_generic_params(
) -> Result<(), HirDisplayError> {
let params = f.db.generic_params(def);
if params.lifetimes.is_empty()
- && params.type_or_consts.iter().all(|x| x.1.const_param().is_none())
+ && params.type_or_consts.iter().all(|it| it.1.const_param().is_none())
&& params
.type_or_consts
.iter()
- .filter_map(|x| x.1.type_param())
+ .filter_map(|it| it.1.type_param())
.all(|param| !matches!(param.provenance, TypeParamProvenance::TypeParamList))
{
return Ok(());
diff --git a/src/tools/rust-analyzer/crates/hir/src/from_id.rs b/src/tools/rust-analyzer/crates/hir/src/from_id.rs
index de2390219..fc4bbffdb 100644
--- a/src/tools/rust-analyzer/crates/hir/src/from_id.rs
+++ b/src/tools/rust-analyzer/crates/hir/src/from_id.rs
@@ -15,7 +15,7 @@ use crate::{
};
macro_rules! from_id {
- ($(($id:path, $ty:path)),*) => {$(
+ ($(($id:path, $ty:path)),* $(,)?) => {$(
impl From<$id> for $ty {
fn from(id: $id) -> $ty {
$ty { id }
@@ -47,7 +47,8 @@ from_id![
(hir_def::TypeParamId, crate::TypeParam),
(hir_def::ConstParamId, crate::ConstParam),
(hir_def::LifetimeParamId, crate::LifetimeParam),
- (hir_def::MacroId, crate::Macro)
+ (hir_def::MacroId, crate::Macro),
+ (hir_def::ExternCrateId, crate::ExternCrateDecl),
];
impl From<AdtId> for Adt {
diff --git a/src/tools/rust-analyzer/crates/hir/src/has_source.rs b/src/tools/rust-analyzer/crates/hir/src/has_source.rs
index 9f6b5c0a9..31cf8ba33 100644
--- a/src/tools/rust-analyzer/crates/hir/src/has_source.rs
+++ b/src/tools/rust-analyzer/crates/hir/src/has_source.rs
@@ -1,18 +1,19 @@
//! Provides set of implementation for hir's objects that allows get back location in file.
+use base_db::FileId;
use either::Either;
use hir_def::{
nameres::{ModuleOrigin, ModuleSource},
src::{HasChildSource, HasSource as _},
Lookup, MacroId, VariantId,
};
-use hir_expand::InFile;
+use hir_expand::{HirFileId, InFile};
use syntax::ast;
use crate::{
- db::HirDatabase, Adt, Const, Enum, Field, FieldSource, Function, Impl, LifetimeParam,
- LocalSource, Macro, Module, Static, Struct, Trait, TraitAlias, TypeAlias, TypeOrConstParam,
- Union, Variant,
+ db::HirDatabase, Adt, Const, Enum, ExternCrateDecl, Field, FieldSource, Function, Impl,
+ LifetimeParam, LocalSource, Macro, Module, Static, Struct, Trait, TraitAlias, TypeAlias,
+ TypeOrConstParam, Union, Variant,
};
pub trait HasSource {
@@ -20,6 +21,10 @@ pub trait HasSource {
/// Fetches the definition's source node.
/// Using [`crate::Semantics::source`] is preferred when working with [`crate::Semantics`],
/// as that caches the parsed file in the semantics' cache.
+ ///
+ /// The current some implementations can return `InFile` instead of `Option<InFile>`.
+ /// But we made this method `Option` to support rlib in the future
+ /// by https://github.com/rust-lang/rust-analyzer/issues/6913
fn source(self, db: &dyn HirDatabase) -> Option<InFile<Self::Ast>>;
}
@@ -32,6 +37,11 @@ impl Module {
def_map[self.id.local_id].definition_source(db.upcast())
}
+ pub fn definition_source_file_id(self, db: &dyn HirDatabase) -> HirFileId {
+ let def_map = self.id.def_map(db.upcast());
+ def_map[self.id.local_id].definition_source_file_id()
+ }
+
pub fn is_mod_rs(self, db: &dyn HirDatabase) -> bool {
let def_map = self.id.def_map(db.upcast());
match def_map[self.id.local_id].origin {
@@ -40,6 +50,16 @@ impl Module {
}
}
+ pub fn as_source_file_id(self, db: &dyn HirDatabase) -> Option<FileId> {
+ let def_map = self.id.def_map(db.upcast());
+ match def_map[self.id.local_id].origin {
+ ModuleOrigin::File { definition, .. } | ModuleOrigin::CrateRoot { definition, .. } => {
+ Some(definition)
+ }
+ _ => None,
+ }
+ }
+
pub fn is_inline(self, db: &dyn HirDatabase) -> bool {
let def_map = self.id.def_map(db.upcast());
def_map[self.id.local_id].origin.is_inline()
@@ -187,3 +207,11 @@ impl HasSource for LocalSource {
Some(self.source)
}
}
+
+impl HasSource for ExternCrateDecl {
+ type Ast = ast::ExternCrate;
+
+ fn source(self, db: &dyn HirDatabase) -> Option<InFile<Self::Ast>> {
+ Some(self.id.lookup(db.upcast()).source(db.upcast()))
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs
index 6df625380..bf041b61f 100644
--- a/src/tools/rust-analyzer/crates/hir/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs
@@ -47,22 +47,22 @@ use hir_def::{
lang_item::LangItemTarget,
layout::{self, ReprOptions, TargetDataLayout},
macro_id_to_def_id,
- nameres::{self, diagnostics::DefDiagnostic, ModuleOrigin},
+ nameres::{self, diagnostics::DefDiagnostic},
+ path::ImportAlias,
per_ns::PerNs,
resolver::{HasResolver, Resolver},
src::HasSource as _,
- AssocItemId, AssocItemLoc, AttrDefId, ConstId, ConstParamId, DefWithBodyId, EnumId,
- EnumVariantId, FunctionId, GenericDefId, HasModule, ImplId, InTypeConstId, ItemContainerId,
- LifetimeParamId, LocalEnumVariantId, LocalFieldId, Lookup, MacroExpander, MacroId, ModuleId,
- StaticId, StructId, TraitAliasId, TraitId, TypeAliasId, TypeOrConstParamId, TypeParamId,
- UnionId,
+ AssocItemId, AssocItemLoc, AttrDefId, ConstId, ConstParamId, CrateRootModuleId, DefWithBodyId,
+ EnumId, EnumVariantId, ExternCrateId, FunctionId, GenericDefId, HasModule, ImplId,
+ InTypeConstId, ItemContainerId, LifetimeParamId, LocalEnumVariantId, LocalFieldId, Lookup,
+ MacroExpander, MacroId, ModuleId, StaticId, StructId, TraitAliasId, TraitId, TypeAliasId,
+ TypeOrConstParamId, TypeParamId, UnionId,
};
use hir_expand::{name::name, MacroCallKind};
use hir_ty::{
all_super_traits, autoderef,
consteval::{try_const_usize, unknown_const_as_generic, ConstEvalError, ConstExt},
diagnostics::BodyValidationDiagnostic,
- display::HexifiedConst,
layout::{Layout as TyLayout, RustcEnumVariantIdx, TagEncoding},
method_resolution::{self, TyFingerprint},
mir::{self, interpret_mir},
@@ -89,11 +89,11 @@ use crate::db::{DefDatabase, HirDatabase};
pub use crate::{
attrs::{HasAttrs, Namespace},
diagnostics::{
- AnyDiagnostic, BreakOutsideOfLoop, ExpectedFunction, InactiveCode, IncoherentImpl,
- IncorrectCase, InvalidDeriveTarget, MacroDefError, MacroError, MacroExpansionParseError,
- MalformedDerive, MismatchedArgCount, MissingFields, MissingMatchArms, MissingUnsafe,
- MovedOutOfRef, NeedMut, NoSuchField, PrivateAssocItem, PrivateField,
- ReplaceFilterMapNextWithFindMap, TypeMismatch, TypedHole, UndeclaredLabel,
+ AnyDiagnostic, BreakOutsideOfLoop, CaseType, ExpectedFunction, InactiveCode,
+ IncoherentImpl, IncorrectCase, InvalidDeriveTarget, MacroDefError, MacroError,
+ MacroExpansionParseError, MalformedDerive, MismatchedArgCount, MissingFields,
+ MissingMatchArms, MissingUnsafe, MovedOutOfRef, NeedMut, NoSuchField, PrivateAssocItem,
+ PrivateField, ReplaceFilterMapNextWithFindMap, TypeMismatch, TypedHole, UndeclaredLabel,
UnimplementedBuiltinMacro, UnreachableLabel, UnresolvedExternCrate, UnresolvedField,
UnresolvedImport, UnresolvedMacroCall, UnresolvedMethodCall, UnresolvedModule,
UnresolvedProcMacro, UnusedMut,
@@ -201,9 +201,8 @@ impl Crate {
db.crate_graph().transitive_rev_deps(self.id).map(|id| Crate { id })
}
- pub fn root_module(self, db: &dyn HirDatabase) -> Module {
- let def_map = db.crate_def_map(self.id);
- Module { id: def_map.crate_root().into() }
+ pub fn root_module(self) -> Module {
+ Module { id: CrateRootModuleId::from(self.id).into() }
}
pub fn modules(self, db: &dyn HirDatabase) -> Vec<Module> {
@@ -248,7 +247,7 @@ impl Crate {
/// Try to get the root URL of the documentation of a crate.
pub fn get_html_root_url(self: &Crate, db: &dyn HirDatabase) -> Option<String> {
// Look for #![doc(html_root_url = "...")]
- let attrs = db.attrs(AttrDefId::ModuleId(self.root_module(db).into()));
+ let attrs = db.attrs(AttrDefId::ModuleId(self.root_module().into()));
let doc_url = attrs.by_key("doc").find_string_value_in_tt("html_root_url");
doc_url.map(|s| s.trim_matches('"').trim_end_matches('/').to_owned() + "/")
}
@@ -379,11 +378,6 @@ impl ModuleDef {
ModuleDef::BuiltinType(_) | ModuleDef::Macro(_) => return Vec::new(),
};
- let module = match self.module(db) {
- Some(it) => it,
- None => return Vec::new(),
- };
-
let mut acc = Vec::new();
match self.as_def_with_body() {
@@ -391,7 +385,7 @@ impl ModuleDef {
def.diagnostics(db, &mut acc);
}
None => {
- for diag in hir_ty::diagnostics::incorrect_case(db, module.id.krate(), id) {
+ for diag in hir_ty::diagnostics::incorrect_case(db, id) {
acc.push(diag.into())
}
}
@@ -505,15 +499,10 @@ impl Module {
/// Finds nearest non-block ancestor `Module` (`self` included).
pub fn nearest_non_block_module(self, db: &dyn HirDatabase) -> Module {
let mut id = self.id;
- loop {
- let def_map = id.def_map(db.upcast());
- let origin = def_map[id.local_id].origin;
- if matches!(origin, ModuleOrigin::BlockExpr { .. }) {
- id = id.containing_module(db.upcast()).expect("block without parent module")
- } else {
- return Module { id };
- }
+ while id.is_block_module() {
+ id = id.containing_module(db.upcast()).expect("block without parent module");
}
+ Module { id }
}
pub fn path_to_root(self, db: &dyn HirDatabase) -> Vec<Module> {
@@ -619,15 +608,21 @@ impl Module {
let inherent_impls = db.inherent_impls_in_crate(self.id.krate());
for impl_def in self.impl_defs(db) {
+ let loc = impl_def.id.lookup(db.upcast());
+ let tree = loc.id.item_tree(db.upcast());
+ let node = &tree[loc.id.value];
+ let file_id = loc.id.file_id();
+ if file_id.is_builtin_derive(db.upcast()) {
+ // these expansion come from us, diagnosing them is a waste of resources
+ // FIXME: Once we diagnose the inputs to builtin derives, we should at least extract those diagnostics somehow
+ continue;
+ }
+
for diag in db.impl_data_with_diagnostics(impl_def.id).1.iter() {
emit_def_diagnostic(db, acc, diag);
}
if inherent_impls.invalid_impls().contains(&impl_def.id) {
- let loc = impl_def.id.lookup(db.upcast());
- let tree = loc.id.item_tree(db.upcast());
- let node = &tree[loc.id.value];
- let file_id = loc.id.file_id();
let ast_id_map = db.ast_id_map(file_id);
acc.push(IncoherentImpl { impl_: ast_id_map.get(node.ast_id()), file_id }.into())
@@ -698,16 +693,18 @@ impl Module {
fn emit_macro_def_diagnostics(db: &dyn HirDatabase, acc: &mut Vec<AnyDiagnostic>, m: Macro) {
let id = macro_id_to_def_id(db.upcast(), m.id);
- if let Err(e) = db.macro_def(id) {
- let Some(ast) = id.ast_id().left() else {
- never!("MacroDefError for proc-macro: {:?}", e);
+ if let hir_expand::db::TokenExpander::DeclarativeMacro(expander) = db.macro_expander(id) {
+ if let Some(e) = expander.mac.err() {
+ let Some(ast) = id.ast_id().left() else {
+ never!("declarative expander for non decl-macro: {:?}", e);
return;
};
- emit_def_diagnostic_(
- db,
- acc,
- &DefDiagnosticKind::MacroDefError { ast, message: e.to_string() },
- );
+ emit_def_diagnostic_(
+ db,
+ acc,
+ &DefDiagnosticKind::MacroDefError { ast, message: e.to_string() },
+ );
+ }
}
}
@@ -753,7 +750,7 @@ fn emit_def_diagnostic_(
let item = ast.to_node(db.upcast());
acc.push(
InactiveCode {
- node: ast.with_value(AstPtr::new(&item).into()),
+ node: ast.with_value(SyntaxNodePtr::new(&item).into()),
cfg: cfg.clone(),
opts: opts.clone(),
}
@@ -963,8 +960,15 @@ impl Field {
}
pub fn layout(&self, db: &dyn HirDatabase) -> Result<Layout, LayoutError> {
- db.layout_of_ty(self.ty(db).ty.clone(), self.parent.module(db).krate().into())
- .map(|layout| Layout(layout, db.target_data_layout(self.krate(db).into()).unwrap()))
+ db.layout_of_ty(
+ self.ty(db).ty.clone(),
+ db.trait_environment(match hir_def::VariantId::from(self.parent) {
+ hir_def::VariantId::EnumVariantId(id) => GenericDefId::EnumVariantId(id),
+ hir_def::VariantId::StructId(id) => GenericDefId::AdtId(id.into()),
+ hir_def::VariantId::UnionId(id) => GenericDefId::AdtId(id.into()),
+ }),
+ )
+ .map(|layout| Layout(layout, db.target_data_layout(self.krate(db).into()).unwrap()))
}
pub fn parent_def(&self, _db: &dyn HirDatabase) -> VariantDef {
@@ -1234,7 +1238,7 @@ impl Adt {
pub fn has_non_default_type_params(self, db: &dyn HirDatabase) -> bool {
let subst = db.generic_defaults(self.into());
subst.iter().any(|ty| match ty.skip_binders().data(Interner) {
- GenericArgData::Ty(x) => x.is_unknown(),
+ GenericArgData::Ty(it) => it.is_unknown(),
_ => false,
})
}
@@ -1244,8 +1248,12 @@ impl Adt {
return Err(LayoutError::HasPlaceholder);
}
let krate = self.krate(db).id;
- db.layout_of_adt(self.into(), Substitution::empty(Interner), krate)
- .map(|layout| Layout(layout, db.target_data_layout(krate).unwrap()))
+ db.layout_of_adt(
+ self.into(),
+ Substitution::empty(Interner),
+ db.trait_environment(self.into()),
+ )
+ .map(|layout| Layout(layout, db.target_data_layout(krate).unwrap()))
}
/// Turns this ADT into a type. Any type parameters of the ADT will be
@@ -1635,11 +1643,11 @@ impl DefWithBody {
for moof in &borrowck_result.moved_out_of_ref {
let span: InFile<SyntaxNodePtr> = match moof.span {
mir::MirSpan::ExprId(e) => match source_map.expr_syntax(e) {
- Ok(s) => s.map(|x| x.into()),
+ Ok(s) => s.map(|it| it.into()),
Err(_) => continue,
},
mir::MirSpan::PatId(p) => match source_map.pat_syntax(p) {
- Ok(s) => s.map(|x| match x {
+ Ok(s) => s.map(|it| match it {
Either::Left(e) => e.into(),
Either::Right(e) => e.into(),
}),
@@ -1661,6 +1669,14 @@ impl DefWithBody {
let Some(&local) = mir_body.binding_locals.get(binding_id) else {
continue;
};
+ if body[binding_id]
+ .definitions
+ .iter()
+ .any(|&pat| source_map.pat_syntax(pat).is_err())
+ {
+ // Skip synthetic bindings
+ continue;
+ }
let need_mut = &mol[local];
let local = Local { parent: self.into(), binding_id };
match (need_mut, local.is_mut(db)) {
@@ -1670,11 +1686,11 @@ impl DefWithBody {
for span in spans {
let span: InFile<SyntaxNodePtr> = match span {
mir::MirSpan::ExprId(e) => match source_map.expr_syntax(*e) {
- Ok(s) => s.map(|x| x.into()),
+ Ok(s) => s.map(|it| it.into()),
Err(_) => continue,
},
mir::MirSpan::PatId(p) => match source_map.pat_syntax(*p) {
- Ok(s) => s.map(|x| match x {
+ Ok(s) => s.map(|it| match it {
Either::Left(e) => e.into(),
Either::Right(e) => e.into(),
}),
@@ -1687,7 +1703,7 @@ impl DefWithBody {
}
(mir::MutabilityReason::Not, true) => {
if !infer.mutated_bindings_in_closure.contains(&binding_id) {
- let should_ignore = matches!(body[binding_id].name.as_str(), Some(x) if x.starts_with("_"));
+ let should_ignore = matches!(body[binding_id].name.as_str(), Some(it) if it.starts_with("_"));
if !should_ignore {
acc.push(UnusedMut { local }.into())
}
@@ -1810,7 +1826,7 @@ impl DefWithBody {
// FIXME: don't ignore diagnostics for in type const
DefWithBody::InTypeConst(_) => return,
};
- for diag in hir_ty::diagnostics::incorrect_case(db, krate, def.into()) {
+ for diag in hir_ty::diagnostics::incorrect_case(db, def.into()) {
acc.push(diag.into())
}
}
@@ -1919,6 +1935,21 @@ impl Function {
db.function_data(self.id).has_async_kw()
}
+ /// Does this function have `#[test]` attribute?
+ pub fn is_test(self, db: &dyn HirDatabase) -> bool {
+ db.function_data(self.id).attrs.is_test()
+ }
+
+ /// Does this function have the ignore attribute?
+ pub fn is_ignore(self, db: &dyn HirDatabase) -> bool {
+ db.function_data(self.id).attrs.is_ignore()
+ }
+
+ /// Does this function have `#[bench]` attribute?
+ pub fn is_bench(self, db: &dyn HirDatabase) -> bool {
+ db.function_data(self.id).attrs.is_bench()
+ }
+
pub fn is_unsafe_to_call(self, db: &dyn HirDatabase) -> bool {
hir_ty::is_fn_unsafe_to_call(db, self.id)
}
@@ -1962,7 +1993,7 @@ impl Function {
return r;
}
};
- let (result, stdout, stderr) = interpret_mir(db, &body, false);
+ let (result, stdout, stderr) = interpret_mir(db, body, false, None);
let mut text = match result {
Ok(_) => "pass".to_string(),
Err(e) => {
@@ -2098,6 +2129,47 @@ impl HasVisibility for Function {
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct ExternCrateDecl {
+ pub(crate) id: ExternCrateId,
+}
+
+impl ExternCrateDecl {
+ pub fn module(self, db: &dyn HirDatabase) -> Module {
+ self.id.module(db.upcast()).into()
+ }
+
+ pub fn resolved_crate(self, db: &dyn HirDatabase) -> Option<Crate> {
+ db.extern_crate_decl_data(self.id).crate_id.map(Into::into)
+ }
+
+ pub fn name(self, db: &dyn HirDatabase) -> Name {
+ db.extern_crate_decl_data(self.id).name.clone()
+ }
+
+ pub fn alias(self, db: &dyn HirDatabase) -> Option<ImportAlias> {
+ db.extern_crate_decl_data(self.id).alias.clone()
+ }
+
+ /// Returns the name under which this crate is made accessible, taking `_` into account.
+ pub fn alias_or_name(self, db: &dyn HirDatabase) -> Option<Name> {
+ let extern_crate_decl_data = db.extern_crate_decl_data(self.id);
+ match &extern_crate_decl_data.alias {
+ Some(ImportAlias::Underscore) => None,
+ Some(ImportAlias::Alias(alias)) => Some(alias.clone()),
+ None => Some(extern_crate_decl_data.name.clone()),
+ }
+ }
+}
+
+impl HasVisibility for ExternCrateDecl {
+ fn visibility(&self, db: &dyn HirDatabase) -> Visibility {
+ db.extern_crate_decl_data(self.id)
+ .visibility
+ .resolve(db.upcast(), &self.id.resolver(db.upcast()))
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct InTypeConst {
pub(crate) id: InTypeConstId,
}
@@ -2131,8 +2203,28 @@ impl Const {
}
pub fn render_eval(self, db: &dyn HirDatabase) -> Result<String, ConstEvalError> {
- let c = db.const_eval(self.id.into(), Substitution::empty(Interner))?;
- let r = format!("{}", HexifiedConst(c).display(db));
+ let c = db.const_eval(self.id.into(), Substitution::empty(Interner), None)?;
+ let data = &c.data(Interner);
+ if let TyKind::Scalar(s) = data.ty.kind(Interner) {
+ if matches!(s, Scalar::Int(_) | Scalar::Uint(_)) {
+ if let hir_ty::ConstValue::Concrete(c) = &data.value {
+ if let hir_ty::ConstScalar::Bytes(b, _) = &c.interned {
+ let value = u128::from_le_bytes(mir::pad16(b, false));
+ let value_signed =
+ i128::from_le_bytes(mir::pad16(b, matches!(s, Scalar::Int(_))));
+ if value >= 10 {
+ return Ok(format!("{} ({:#X})", value_signed, value));
+ } else {
+ return Ok(format!("{}", value_signed));
+ }
+ }
+ }
+ }
+ }
+ if let Ok(s) = mir::render_const_using_debug_impl(db, self.id, &c) {
+ return Ok(s);
+ }
+ let r = format!("{}", c.display(db));
return Ok(r);
}
}
@@ -2270,7 +2362,7 @@ impl TypeAlias {
pub fn has_non_default_type_params(self, db: &dyn HirDatabase) -> bool {
let subst = db.generic_defaults(self.id.into());
subst.iter().any(|ty| match ty.skip_binders().data(Interner) {
- GenericArgData::Ty(x) => x.is_unknown(),
+ GenericArgData::Ty(it) => it.is_unknown(),
_ => false,
})
}
@@ -2660,8 +2752,8 @@ impl GenericDef {
let ty_params = generics.type_or_consts.iter().map(|(local_id, _)| {
let toc = TypeOrConstParam { id: TypeOrConstParamId { parent: self.into(), local_id } };
match toc.split(db) {
- Either::Left(x) => GenericParam::ConstParam(x),
- Either::Right(x) => GenericParam::TypeParam(x),
+ Either::Left(it) => GenericParam::ConstParam(it),
+ Either::Right(it) => GenericParam::TypeParam(it),
}
});
self.lifetime_params(db)
@@ -2709,14 +2801,14 @@ pub struct LocalSource {
impl LocalSource {
pub fn as_ident_pat(&self) -> Option<&ast::IdentPat> {
match &self.source.value {
- Either::Left(x) => Some(x),
+ Either::Left(it) => Some(it),
Either::Right(_) => None,
}
}
pub fn into_ident_pat(self) -> Option<ast::IdentPat> {
match self.source.value {
- Either::Left(x) => Some(x),
+ Either::Left(it) => Some(it),
Either::Right(_) => None,
}
}
@@ -2738,7 +2830,7 @@ impl LocalSource {
}
pub fn syntax_ptr(self) -> InFile<SyntaxNodePtr> {
- self.source.map(|x| SyntaxNodePtr::new(x.syntax()))
+ self.source.map(|it| SyntaxNodePtr::new(it.syntax()))
}
}
@@ -2797,13 +2889,13 @@ impl Local {
Type::new(db, def, ty)
}
- /// All definitions for this local. Example: `let (a$0, _) | (_, a$0) = x;`
+ /// All definitions for this local. Example: `let (a$0, _) | (_, a$0) = it;`
pub fn sources(self, db: &dyn HirDatabase) -> Vec<LocalSource> {
let (body, source_map) = db.body_with_source_map(self.parent);
self.sources_(db, &body, &source_map).collect()
}
- /// The leftmost definition for this local. Example: `let (a$0, _) | (_, a) = x;`
+ /// The leftmost definition for this local. Example: `let (a$0, _) | (_, a) = it;`
pub fn primary_source(self, db: &dyn HirDatabase) -> LocalSource {
let (body, source_map) = db.body_with_source_map(self.parent);
let src = self.sources_(db, &body, &source_map).next().unwrap();
@@ -3057,7 +3149,9 @@ impl TypeParam {
let subst = TyBuilder::placeholder_subst(db, self.id.parent());
let ty = ty.substitute(Interner, &subst);
match ty.data(Interner) {
- GenericArgData::Ty(x) => Some(Type::new_with_resolver_inner(db, &resolver, x.clone())),
+ GenericArgData::Ty(it) => {
+ Some(Type::new_with_resolver_inner(db, &resolver, it.clone()))
+ }
_ => None,
}
}
@@ -3096,7 +3190,7 @@ impl ConstParam {
pub fn name(self, db: &dyn HirDatabase) -> Name {
let params = db.generic_params(self.id.parent());
match params.type_or_consts[self.id.local_id()].name() {
- Some(x) => x.clone(),
+ Some(it) => it.clone(),
None => {
never!();
Name::missing()
@@ -3153,8 +3247,8 @@ impl TypeOrConstParam {
pub fn ty(self, db: &dyn HirDatabase) -> Type {
match self.split(db) {
- Either::Left(x) => x.ty(db),
- Either::Right(x) => x.ty(db),
+ Either::Left(it) => it.ty(db),
+ Either::Right(it) => it.ty(db),
}
}
}
@@ -3260,9 +3354,9 @@ impl Impl {
self.id.lookup(db.upcast()).container.into()
}
- pub fn is_builtin_derive(self, db: &dyn HirDatabase) -> Option<InFile<ast::Attr>> {
+ pub fn as_builtin_derive(self, db: &dyn HirDatabase) -> Option<InFile<ast::Attr>> {
let src = self.source(db)?;
- src.file_id.is_builtin_derive(db.upcast())
+ src.file_id.as_builtin_derive_attr_node(db.upcast())
}
}
@@ -3652,9 +3746,9 @@ impl Type {
};
let parent_subst = TyBuilder::subst_for_def(db, trait_id, None)
.push(self.ty.clone())
- .fill(|x| {
+ .fill(|it| {
// FIXME: this code is not covered in tests.
- match x {
+ match it {
ParamKind::Type => {
GenericArgData::Ty(args.next().unwrap().ty.clone()).intern(Interner)
}
@@ -3821,7 +3915,7 @@ impl Type {
pub fn as_array(&self, db: &dyn HirDatabase) -> Option<(Type, usize)> {
if let TyKind::Array(ty, len) = &self.ty.kind(Interner) {
- try_const_usize(db, len).map(|x| (self.derived(ty.clone()), x as usize))
+ try_const_usize(db, len).map(|it| (self.derived(ty.clone()), it as usize))
} else {
None
}
@@ -4275,7 +4369,7 @@ impl Type {
}
pub fn layout(&self, db: &dyn HirDatabase) -> Result<Layout, LayoutError> {
- db.layout_of_ty(self.ty.clone(), self.env.krate)
+ db.layout_of_ty(self.ty.clone(), self.env.clone())
.map(|layout| Layout(layout, db.target_data_layout(self.env.krate).unwrap()))
}
}
@@ -4662,6 +4756,12 @@ pub trait HasContainer {
fn container(&self, db: &dyn HirDatabase) -> ItemContainer;
}
+impl HasContainer for ExternCrateDecl {
+ fn container(&self, db: &dyn HirDatabase) -> ItemContainer {
+ container_id_to_hir(self.id.lookup(db.upcast()).container.into())
+ }
+}
+
impl HasContainer for Module {
fn container(&self, db: &dyn HirDatabase) -> ItemContainer {
// FIXME: handle block expressions as modules (their parent is in a different DefMap)
diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs
index 5a76a9185..e99d2984c 100644
--- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs
+++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs
@@ -15,11 +15,7 @@ use hir_def::{
type_ref::Mutability,
AsMacroCall, DefWithBodyId, FieldId, FunctionId, MacroId, TraitId, VariantId,
};
-use hir_expand::{
- db::ExpandDatabase,
- name::{known, AsName},
- ExpansionInfo, MacroCallId,
-};
+use hir_expand::{db::ExpandDatabase, name::AsName, ExpansionInfo, MacroCallId};
use itertools::Itertools;
use rustc_hash::{FxHashMap, FxHashSet};
use smallvec::{smallvec, SmallVec};
@@ -439,10 +435,6 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
self.imp.resolve_path(path)
}
- pub fn resolve_extern_crate(&self, extern_crate: &ast::ExternCrate) -> Option<Crate> {
- self.imp.resolve_extern_crate(extern_crate)
- }
-
pub fn resolve_variant(&self, record_lit: ast::RecordExpr) -> Option<VariantDef> {
self.imp.resolve_variant(record_lit).map(VariantDef::from)
}
@@ -1242,18 +1234,6 @@ impl<'db> SemanticsImpl<'db> {
self.analyze(path.syntax())?.resolve_path(self.db, path)
}
- fn resolve_extern_crate(&self, extern_crate: &ast::ExternCrate) -> Option<Crate> {
- let krate = self.scope(extern_crate.syntax())?.krate();
- let name = extern_crate.name_ref()?.as_name();
- if name == known::SELF_PARAM {
- return Some(krate);
- }
- krate
- .dependencies(self.db)
- .into_iter()
- .find_map(|dep| (dep.name == name).then_some(dep.krate))
- }
-
fn resolve_variant(&self, record_lit: ast::RecordExpr) -> Option<VariantId> {
self.analyze(record_lit.syntax())?.resolve_variant(self.db, record_lit)
}
@@ -1494,7 +1474,11 @@ impl<'db> SemanticsImpl<'db> {
}
fn is_inside_unsafe(&self, expr: &ast::Expr) -> bool {
- let Some(enclosing_item) = expr.syntax().ancestors().find_map(Either::<ast::Item, ast::Variant>::cast) else { return false };
+ let Some(enclosing_item) =
+ expr.syntax().ancestors().find_map(Either::<ast::Item, ast::Variant>::cast)
+ else {
+ return false;
+ };
let def = match &enclosing_item {
Either::Left(ast::Item::Fn(it)) if it.unsafe_token().is_some() => return true,
@@ -1599,6 +1583,7 @@ to_def_impls![
(crate::Local, ast::SelfParam, self_param_to_def),
(crate::Label, ast::Label, label_to_def),
(crate::Adt, ast::Adt, adt_to_def),
+ (crate::ExternCrateDecl, ast::ExternCrate, extern_crate_to_def),
];
fn find_root(node: &SyntaxNode) -> SyntaxNode {
@@ -1631,7 +1616,7 @@ pub struct SemanticsScope<'a> {
resolver: Resolver,
}
-impl<'a> SemanticsScope<'a> {
+impl SemanticsScope<'_> {
pub fn module(&self) -> Module {
Module { id: self.resolver.module() }
}
diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs b/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs
index c50ffa4f8..aabda3655 100644
--- a/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs
+++ b/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs
@@ -93,9 +93,9 @@ use hir_def::{
DynMap,
},
hir::{BindingId, LabelId},
- AdtId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, FieldId, FunctionId,
- GenericDefId, GenericParamId, ImplId, LifetimeParamId, MacroId, ModuleId, StaticId, StructId,
- TraitAliasId, TraitId, TypeAliasId, TypeParamId, UnionId, VariantId,
+ AdtId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, ExternCrateId, FieldId,
+ FunctionId, GenericDefId, GenericParamId, ImplId, LifetimeParamId, MacroId, ModuleId, StaticId,
+ StructId, TraitAliasId, TraitId, TypeAliasId, TypeParamId, UnionId, UseId, VariantId,
};
use hir_expand::{attrs::AttrId, name::AsName, HirFileId, MacroCallId};
use rustc_hash::FxHashMap;
@@ -203,6 +203,16 @@ impl SourceToDefCtx<'_, '_> {
) -> Option<EnumVariantId> {
self.to_def(src, keys::VARIANT)
}
+ pub(super) fn extern_crate_to_def(
+ &mut self,
+ src: InFile<ast::ExternCrate>,
+ ) -> Option<ExternCrateId> {
+ self.to_def(src, keys::EXTERN_CRATE)
+ }
+ #[allow(dead_code)]
+ pub(super) fn use_to_def(&mut self, src: InFile<ast::Use>) -> Option<UseId> {
+ self.to_def(src, keys::USE)
+ }
pub(super) fn adt_to_def(
&mut self,
InFile { file_id, value }: InFile<ast::Adt>,
@@ -298,7 +308,7 @@ impl SourceToDefCtx<'_, '_> {
pub(super) fn type_param_to_def(&mut self, src: InFile<ast::TypeParam>) -> Option<TypeParamId> {
let container: ChildContainer = self.find_generic_param_container(src.syntax())?.into();
let dyn_map = self.cache_for(container, src.file_id);
- dyn_map[keys::TYPE_PARAM].get(&src.value).copied().map(|x| TypeParamId::from_unchecked(x))
+ dyn_map[keys::TYPE_PARAM].get(&src.value).copied().map(|it| TypeParamId::from_unchecked(it))
}
pub(super) fn lifetime_param_to_def(
@@ -316,7 +326,10 @@ impl SourceToDefCtx<'_, '_> {
) -> Option<ConstParamId> {
let container: ChildContainer = self.find_generic_param_container(src.syntax())?.into();
let dyn_map = self.cache_for(container, src.file_id);
- dyn_map[keys::CONST_PARAM].get(&src.value).copied().map(|x| ConstParamId::from_unchecked(x))
+ dyn_map[keys::CONST_PARAM]
+ .get(&src.value)
+ .copied()
+ .map(|it| ConstParamId::from_unchecked(it))
}
pub(super) fn generic_param_to_def(
diff --git a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs
index ecb1b306a..3499daf11 100644
--- a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs
+++ b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs
@@ -832,7 +832,7 @@ impl SourceAnalyzer {
None => return func,
};
let env = db.trait_environment_for_body(owner);
- method_resolution::lookup_impl_method(db, env, func, substs).0
+ db.lookup_impl_method(env, func, substs).0
}
fn resolve_impl_const_or_trait_def(
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
index d07c63726..6aca716bb 100644
--- 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
@@ -3,7 +3,10 @@ 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, DefaultMethods},
+ utils::{
+ add_trait_assoc_items_to_impl, filter_assoc_items, gen_trait_fn_body, DefaultMethods,
+ IgnoreAssocItems,
+ },
AssistId, AssistKind,
};
@@ -43,6 +46,7 @@ pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext<'_
acc,
ctx,
DefaultMethods::No,
+ IgnoreAssocItems::DocHiddenAttrPresent,
"add_impl_missing_members",
"Implement missing members",
)
@@ -87,6 +91,7 @@ pub(crate) fn add_missing_default_members(
acc,
ctx,
DefaultMethods::Only,
+ IgnoreAssocItems::DocHiddenAttrPresent,
"add_impl_default_members",
"Implement default members",
)
@@ -96,6 +101,7 @@ fn add_missing_impl_members_inner(
acc: &mut Assists,
ctx: &AssistContext<'_>,
mode: DefaultMethods,
+ ignore_items: IgnoreAssocItems,
assist_id: &'static str,
label: &'static str,
) -> Option<()> {
@@ -115,10 +121,21 @@ fn add_missing_impl_members_inner(
let trait_ref = impl_.trait_ref(ctx.db())?;
let trait_ = trait_ref.trait_();
+ let mut ign_item = ignore_items;
+
+ if let IgnoreAssocItems::DocHiddenAttrPresent = ignore_items {
+ // Relax condition for local crates.
+ let db = ctx.db();
+ if trait_.module(db).krate().origin(db).is_local() {
+ ign_item = IgnoreAssocItems::No;
+ }
+ }
+
let missing_items = filter_assoc_items(
&ctx.sema,
&ide_db::traits::get_missing_assoc_items(&ctx.sema, &impl_def),
mode,
+ ign_item,
);
if missing_items.is_empty() {
@@ -1966,4 +1983,169 @@ impl AnotherTrait<i32> for () {
"#,
);
}
+
+ #[test]
+ fn doc_hidden_default_impls_ignored() {
+ // doc(hidden) attr is ignored trait and impl both belong to the local crate.
+ check_assist(
+ add_missing_default_members,
+ r#"
+struct Foo;
+trait Trait {
+ #[doc(hidden)]
+ fn func_with_default_impl() -> u32 {
+ 42
+ }
+ fn another_default_impl() -> u32 {
+ 43
+ }
+}
+impl Tra$0it for Foo {}"#,
+ r#"
+struct Foo;
+trait Trait {
+ #[doc(hidden)]
+ fn func_with_default_impl() -> u32 {
+ 42
+ }
+ fn another_default_impl() -> u32 {
+ 43
+ }
+}
+impl Trait for Foo {
+ $0fn func_with_default_impl() -> u32 {
+ 42
+ }
+
+ fn another_default_impl() -> u32 {
+ 43
+ }
+}"#,
+ )
+ }
+
+ #[test]
+ fn doc_hidden_default_impls_lang_crates() {
+ // Not applicable because Eq has a single method and this has a #[doc(hidden)] attr set.
+ check_assist_not_applicable(
+ add_missing_default_members,
+ r#"
+//- minicore: eq
+use core::cmp::Eq;
+struct Foo;
+impl E$0q for Foo { /* $0 */ }
+"#,
+ )
+ }
+
+ #[test]
+ fn doc_hidden_default_impls_lib_crates() {
+ check_assist(
+ add_missing_default_members,
+ r#"
+ //- /main.rs crate:a deps:b
+ struct B;
+ impl b::Exte$0rnTrait for B {}
+ //- /lib.rs crate:b new_source_root:library
+ pub trait ExternTrait {
+ #[doc(hidden)]
+ fn hidden_default() -> Option<()> {
+ todo!()
+ }
+
+ fn unhidden_default() -> Option<()> {
+ todo!()
+ }
+
+ fn unhidden_nondefault() -> Option<()>;
+ }
+ "#,
+ r#"
+ struct B;
+ impl b::ExternTrait for B {
+ $0fn unhidden_default() -> Option<()> {
+ todo!()
+ }
+ }
+ "#,
+ )
+ }
+
+ #[test]
+ fn doc_hidden_default_impls_local_crates() {
+ check_assist(
+ add_missing_default_members,
+ r#"
+trait LocalTrait {
+ #[doc(hidden)]
+ fn no_skip_default() -> Option<()> {
+ todo!()
+ }
+ fn no_skip_default_2() -> Option<()> {
+ todo!()
+ }
+}
+
+struct B;
+impl Loc$0alTrait for B {}
+ "#,
+ r#"
+trait LocalTrait {
+ #[doc(hidden)]
+ fn no_skip_default() -> Option<()> {
+ todo!()
+ }
+ fn no_skip_default_2() -> Option<()> {
+ todo!()
+ }
+}
+
+struct B;
+impl LocalTrait for B {
+ $0fn no_skip_default() -> Option<()> {
+ todo!()
+ }
+
+ fn no_skip_default_2() -> Option<()> {
+ todo!()
+ }
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn doc_hidden_default_impls_workspace_crates() {
+ check_assist(
+ add_missing_default_members,
+ r#"
+//- /lib.rs crate:b new_source_root:local
+trait LocalTrait {
+ #[doc(hidden)]
+ fn no_skip_default() -> Option<()> {
+ todo!()
+ }
+ fn no_skip_default_2() -> Option<()> {
+ todo!()
+ }
+}
+
+//- /main.rs crate:a deps:b
+struct B;
+impl b::Loc$0alTrait for B {}
+ "#,
+ r#"
+struct B;
+impl b::LocalTrait for B {
+ $0fn no_skip_default() -> Option<()> {
+ todo!()
+ }
+
+ fn no_skip_default_2() -> Option<()> {
+ todo!()
+ }
+}
+ "#,
+ )
+ }
}
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
index 7384390f2..3b162d7c4 100644
--- 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
@@ -8,10 +8,7 @@ use itertools::Itertools;
use syntax::ast::edit_in_place::Removable;
use syntax::ast::{self, make, AstNode, HasName, MatchArmList, MatchExpr, Pat};
-use crate::{
- utils::{self, render_snippet, Cursor},
- AssistContext, AssistId, AssistKind, Assists,
-};
+use crate::{utils, AssistContext, AssistId, AssistKind, Assists};
// Assist: add_missing_match_arms
//
@@ -40,9 +37,9 @@ use crate::{
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;
+ let arm_list_range = ctx.sema.original_range_opt(match_arm_list.syntax())?;
- if let None = cursor_at_trivial_match_arm_list(ctx, &match_expr, &match_arm_list) {
+ if cursor_at_trivial_match_arm_list(ctx, &match_expr, &match_arm_list).is_none() {
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 {
@@ -75,14 +72,18 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
.collect();
let module = ctx.sema.scope(expr.syntax())?.module();
- let (mut missing_pats, is_non_exhaustive): (
+ let (mut missing_pats, is_non_exhaustive, has_hidden_variants): (
Peekable<Box<dyn Iterator<Item = (ast::Pat, bool)>>>,
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 has_hidden_variants =
+ variants.iter().any(|variant| variant.should_be_hidden(ctx.db(), module.krate()));
+
let missing_pats = variants
.into_iter()
.filter_map(|variant| {
@@ -101,7 +102,7 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
} else {
Box::new(missing_pats)
};
- (missing_pats.peekable(), is_non_exhaustive)
+ (missing_pats.peekable(), is_non_exhaustive, has_hidden_variants)
} 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()));
@@ -124,6 +125,12 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
if n_arms > 256 {
return None;
}
+
+ let has_hidden_variants = variants_of_enums
+ .iter()
+ .flatten()
+ .any(|variant| variant.should_be_hidden(ctx.db(), module.krate()));
+
let missing_pats = variants_of_enums
.into_iter()
.multi_cartesian_product()
@@ -139,7 +146,11 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
(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)
+ (
+ (Box::new(missing_pats) as Box<dyn Iterator<Item = _>>).peekable(),
+ is_non_exhaustive,
+ has_hidden_variants,
+ )
} else if let Some((enum_def, len)) = resolve_array_of_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());
@@ -148,6 +159,9 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
return None;
}
+ let has_hidden_variants =
+ variants.iter().any(|variant| variant.should_be_hidden(ctx.db(), module.krate()));
+
let variants_of_enums = vec![variants; len];
let missing_pats = variants_of_enums
@@ -164,28 +178,42 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
(ast::Pat::from(make::slice_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)
+ (
+ (Box::new(missing_pats) as Box<dyn Iterator<Item = _>>).peekable(),
+ is_non_exhaustive,
+ has_hidden_variants,
+ )
} 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() {
+ if !needs_catch_all_arm
+ && ((has_hidden_variants && has_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| {
+ ctx.sema.original_range(match_expr.syntax()).range,
+ |edit| {
let new_match_arm_list = match_arm_list.clone_for_update();
+
+ // having any hidden variants means that we need a catch-all arm
+ needs_catch_all_arm |= has_hidden_variants;
+
let missing_arms = missing_pats
- .map(|(pat, hidden)| {
- (make::match_arm(iter::once(pat), None, make::ext::expr_todo()), hidden)
+ .filter(|(_, hidden)| {
+ // filter out hidden patterns because they're handled by the catch-all arm
+ !hidden
})
- .map(|(it, hidden)| (it.clone_for_update(), hidden));
+ .map(|(pat, _)| {
+ make::match_arm(iter::once(pat), None, make::ext::expr_todo())
+ .clone_for_update()
+ });
let catch_all_arm = new_match_arm_list
.arms()
@@ -204,15 +232,13 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
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);
- }
+ for arm in missing_arms {
+ 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(
@@ -225,24 +251,38 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
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);
+ if let (Some(first_new_arm), Some(cap)) = (first_new_arm, ctx.config.snippet_cap) {
+ match first_new_arm.syntax().descendants().find_map(ast::WildcardPat::cast) {
+ Some(it) => edit.add_placeholder_snippet(cap, it),
+ None => edit.add_tabstop_before(cap, first_new_arm),
}
- _ => builder.replace(old_range, new_match_arm_list.to_string()),
}
+
+ // FIXME: Hack for mutable syntax trees not having great support for macros
+ // Just replace the element that the original range came from
+ let old_place = {
+ // Find the original element
+ let file = ctx.sema.parse(arm_list_range.file_id);
+ let old_place = file.syntax().covering_element(arm_list_range.range);
+
+ // Make `old_place` mut
+ match old_place {
+ syntax::SyntaxElement::Node(it) => {
+ syntax::SyntaxElement::from(edit.make_syntax_mut(it))
+ }
+ syntax::SyntaxElement::Token(it) => {
+ // Don't have a way to make tokens mut, so instead make the parent mut
+ // and find the token again
+ let parent = edit.make_syntax_mut(it.parent().unwrap());
+ let mut_token =
+ parent.covering_element(it.text_range()).into_token().unwrap();
+
+ syntax::SyntaxElement::from(mut_token)
+ }
+ }
+ };
+
+ syntax::ted::replace(old_place, new_match_arm_list.syntax());
},
)
}
@@ -1621,10 +1661,9 @@ 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(
+ check_assist_not_applicable(
add_missing_match_arms,
r#"
//- /main.rs crate:main deps:e
@@ -1636,13 +1675,6 @@ fn foo(t: ::e::E) {
//- /e.rs crate:e
pub enum E { #[doc(hidden)] A, }
"#,
- r#"
-fn foo(t: ::e::E) {
- match t {
- _ => todo!(),
- }
-}
-"#,
);
}
@@ -1777,7 +1809,7 @@ fn foo(t: ::e::E, b: bool) {
#[test]
fn does_not_fill_wildcard_with_partial_wildcard_and_wildcard() {
- check_assist(
+ check_assist_not_applicable(
add_missing_match_arms,
r#"
//- /main.rs crate:main deps:e
@@ -1789,14 +1821,6 @@ fn foo(t: ::e::E, b: bool) {
}
//- /e.rs crate:e
pub enum E { #[doc(hidden)] A, }"#,
- r#"
-fn foo(t: ::e::E, b: bool) {
- match t {
- _ if b => todo!(),
- _ => todo!(),
- }
-}
-"#,
);
}
@@ -1897,4 +1921,24 @@ fn foo(t: E) {
}"#,
);
}
+
+ #[test]
+ fn not_applicable_when_match_arm_list_cannot_be_upmapped() {
+ check_assist_not_applicable(
+ add_missing_match_arms,
+ r#"
+macro_rules! foo {
+ ($($t:tt)*) => {
+ $($t)* {}
+ }
+}
+
+enum E { A }
+
+fn main() {
+ foo!(match E::A$0);
+}
+"#,
+ );
+ }
}
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
index acf82e4b2..36f68d176 100644
--- 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
@@ -42,7 +42,9 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
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,
+ NameRefClass::FieldShorthand { .. } | NameRefClass::ExternCrateShorthand { .. } => {
+ return None
+ }
};
let fun = match def {
Definition::Function(it) => it,
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
index 2b1d8f6f0..e6179ab8b 100644
--- 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
@@ -2,9 +2,10 @@ use syntax::{
ast::{self, HasName, HasVisibility},
AstNode,
SyntaxKind::{
- CONST, ENUM, FN, MACRO_DEF, MODULE, STATIC, STRUCT, TRAIT, TYPE_ALIAS, USE, VISIBILITY,
+ self, ASSOC_ITEM_LIST, CONST, ENUM, FN, MACRO_DEF, MODULE, SOURCE_FILE, STATIC, STRUCT,
+ TRAIT, TYPE_ALIAS, USE, VISIBILITY,
},
- T,
+ SyntaxNode, T,
};
use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
@@ -46,13 +47,11 @@ fn add_vis(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
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()) {
+
+ if !can_add(&parent) {
return None;
}
- // Already have visibility, do nothing
+ // Already has visibility, do nothing
if parent.children().any(|child| child.kind() == VISIBILITY) {
return None;
}
@@ -86,6 +85,29 @@ fn add_vis(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
)
}
+fn can_add(node: &SyntaxNode) -> bool {
+ const LEGAL: &[SyntaxKind] =
+ &[CONST, STATIC, TYPE_ALIAS, FN, MODULE, STRUCT, ENUM, TRAIT, USE, MACRO_DEF];
+
+ LEGAL.contains(&node.kind()) && {
+ let Some(p) = node.parent() else {
+ return false;
+ };
+
+ if p.kind() == ASSOC_ITEM_LIST {
+ p.parent()
+ .and_then(|it| ast::Impl::cast(it))
+ // inherent impls i.e 'non-trait impls' have a non-local
+ // effect, thus can have visibility even when nested.
+ // so filter them out
+ .filter(|imp| imp.for_token().is_none())
+ .is_some()
+ } else {
+ matches!(p.kind(), SOURCE_FILE | MODULE)
+ }
+ }
+}
+
fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> {
if vis.syntax().text() == "pub" {
let target = vis.syntax().text_range();
@@ -129,6 +151,16 @@ mod tests {
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;");
+ check_assist(
+ change_visibility,
+ "impl Foo { f$0n foo() {} }",
+ "impl Foo { pub(crate) fn foo() {} }",
+ );
+ check_assist(
+ change_visibility,
+ "fn bar() { impl Foo { f$0n foo() {} } }",
+ "fn bar() { impl Foo { pub(crate) fn foo() {} } }",
+ );
}
#[test]
@@ -213,4 +245,33 @@ mod tests {
check_assist_target(change_visibility, "pub(crate)$0 fn foo() {}", "pub(crate)");
check_assist_target(change_visibility, "struct S { $0field: u32 }", "field");
}
+
+ #[test]
+ fn not_applicable_for_items_within_traits() {
+ check_assist_not_applicable(change_visibility, "trait Foo { f$0n run() {} }");
+ check_assist_not_applicable(change_visibility, "trait Foo { con$0st FOO: u8 = 69; }");
+ check_assist_not_applicable(change_visibility, "impl Foo for Bar { f$0n quox() {} }");
+ }
+
+ #[test]
+ fn not_applicable_for_items_within_fns() {
+ check_assist_not_applicable(change_visibility, "fn foo() { f$0n inner() {} }");
+ check_assist_not_applicable(change_visibility, "fn foo() { unsafe f$0n inner() {} }");
+ check_assist_not_applicable(change_visibility, "fn foo() { const f$0n inner() {} }");
+ check_assist_not_applicable(change_visibility, "fn foo() { con$0st FOO: u8 = 69; }");
+ check_assist_not_applicable(change_visibility, "fn foo() { en$0um Foo {} }");
+ check_assist_not_applicable(change_visibility, "fn foo() { stru$0ct Foo {} }");
+ check_assist_not_applicable(change_visibility, "fn foo() { mo$0d foo {} }");
+ check_assist_not_applicable(change_visibility, "fn foo() { $0use foo; }");
+ check_assist_not_applicable(change_visibility, "fn foo() { $0type Foo = Bar<T>; }");
+ check_assist_not_applicable(change_visibility, "fn foo() { tr$0ait Foo {} }");
+ check_assist_not_applicable(
+ change_visibility,
+ "fn foo() { impl Trait for Bar { f$0n bar() {} } }",
+ );
+ check_assist_not_applicable(
+ change_visibility,
+ "fn foo() { impl Trait for Bar { con$0st FOO: u8 = 69; } }",
+ );
+ }
}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_two_arm_bool_match_to_matches_macro.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_two_arm_bool_match_to_matches_macro.rs
index b1b0f587c..6a5b11f54 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_two_arm_bool_match_to_matches_macro.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_two_arm_bool_match_to_matches_macro.rs
@@ -1,3 +1,6 @@
+use hir::Semantics;
+use ide_db::RootDatabase;
+use stdx::format_to;
use syntax::ast::{self, AstNode};
use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -24,6 +27,7 @@ pub(crate) fn convert_two_arm_bool_match_to_matches_macro(
acc: &mut Assists,
ctx: &AssistContext<'_>,
) -> Option<()> {
+ use ArmBodyExpression::*;
let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?;
let match_arm_list = match_expr.match_arm_list()?;
let mut arms = match_arm_list.arms();
@@ -33,21 +37,20 @@ pub(crate) fn convert_two_arm_bool_match_to_matches_macro(
cov_mark::hit!(non_two_arm_match);
return None;
}
- let first_arm_expr = first_arm.expr();
- let second_arm_expr = second_arm.expr();
+ let first_arm_expr = first_arm.expr()?;
+ let second_arm_expr = second_arm.expr()?;
+ let first_arm_body = is_bool_literal_expr(&ctx.sema, &first_arm_expr)?;
+ let second_arm_body = is_bool_literal_expr(&ctx.sema, &second_arm_expr)?;
- let invert_matches = if is_bool_literal_expr(&first_arm_expr, true)
- && is_bool_literal_expr(&second_arm_expr, false)
- {
- false
- } else if is_bool_literal_expr(&first_arm_expr, false)
- && is_bool_literal_expr(&second_arm_expr, true)
- {
- true
- } else {
+ if !matches!(
+ (&first_arm_body, &second_arm_body),
+ (Literal(true), Literal(false))
+ | (Literal(false), Literal(true))
+ | (Expression(_), Literal(false))
+ ) {
cov_mark::hit!(non_invert_bool_literal_arms);
return None;
- };
+ }
let target_range = ctx.sema.original_range(match_expr.syntax()).range;
let expr = match_expr.expr()?;
@@ -59,28 +62,55 @@ pub(crate) fn convert_two_arm_bool_match_to_matches_macro(
|builder| {
let mut arm_str = String::new();
if let Some(pat) = &first_arm.pat() {
- arm_str += &pat.to_string();
+ format_to!(arm_str, "{pat}");
}
if let Some(guard) = &first_arm.guard() {
arm_str += &format!(" {guard}");
}
- if invert_matches {
- builder.replace(target_range, format!("!matches!({expr}, {arm_str})"));
- } else {
- builder.replace(target_range, format!("matches!({expr}, {arm_str})"));
- }
+
+ let replace_with = match (first_arm_body, second_arm_body) {
+ (Literal(true), Literal(false)) => {
+ format!("matches!({expr}, {arm_str})")
+ }
+ (Literal(false), Literal(true)) => {
+ format!("!matches!({expr}, {arm_str})")
+ }
+ (Expression(body_expr), Literal(false)) => {
+ arm_str.push_str(match &first_arm.guard() {
+ Some(_) => " && ",
+ _ => " if ",
+ });
+ format!("matches!({expr}, {arm_str}{body_expr})")
+ }
+ _ => {
+ unreachable!()
+ }
+ };
+ builder.replace(target_range, replace_with);
},
)
}
-fn is_bool_literal_expr(expr: &Option<ast::Expr>, expect_bool: bool) -> bool {
- if let Some(ast::Expr::Literal(lit)) = expr {
+enum ArmBodyExpression {
+ Literal(bool),
+ Expression(ast::Expr),
+}
+
+fn is_bool_literal_expr(
+ sema: &Semantics<'_, RootDatabase>,
+ expr: &ast::Expr,
+) -> Option<ArmBodyExpression> {
+ if let ast::Expr::Literal(lit) = expr {
if let ast::LiteralKind::Bool(b) = lit.kind() {
- return b == expect_bool;
+ return Some(ArmBodyExpression::Literal(b));
}
}
- return false;
+ if !sema.type_of_expr(expr)?.original.is_bool() {
+ return None;
+ }
+
+ Some(ArmBodyExpression::Expression(expr.clone()))
}
#[cfg(test)]
@@ -122,21 +152,6 @@ fn foo(a: Option<u32>) -> bool {
}
#[test]
- fn not_applicable_non_bool_literal_arms() {
- cov_mark::check!(non_invert_bool_literal_arms);
- check_assist_not_applicable(
- convert_two_arm_bool_match_to_matches_macro,
- r#"
-fn foo(a: Option<u32>) -> bool {
- match a$0 {
- Some(val) => val == 3,
- _ => false
- }
-}
- "#,
- );
- }
- #[test]
fn not_applicable_both_false_arms() {
cov_mark::check!(non_invert_bool_literal_arms);
check_assist_not_applicable(
@@ -291,4 +306,40 @@ fn main() {
}",
);
}
+
+ #[test]
+ fn convert_non_literal_bool() {
+ check_assist(
+ convert_two_arm_bool_match_to_matches_macro,
+ r#"
+fn main() {
+ match 0$0 {
+ a @ 0..15 => a == 0,
+ _ => false,
+ }
+}
+"#,
+ r#"
+fn main() {
+ matches!(0, a @ 0..15 if a == 0)
+}
+"#,
+ );
+ check_assist(
+ convert_two_arm_bool_match_to_matches_macro,
+ r#"
+fn main() {
+ match 0$0 {
+ a @ 0..15 if thing() => a == 0,
+ _ => false,
+ }
+}
+"#,
+ r#"
+fn main() {
+ matches!(0, a @ 0..15 if thing() && a == 0)
+}
+"#,
+ );
+ }
}
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
index ea71d165e..f30ca2552 100644
--- 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
@@ -114,7 +114,7 @@ fn collect_data(ident_pat: IdentPat, ctx: &AssistContext<'_>) -> Option<TupleDat
let usages = ctx.sema.to_def(&ident_pat).map(|def| {
Definition::Local(def)
.usages(&ctx.sema)
- .in_scope(SearchScope::single_file(ctx.file_id()))
+ .in_scope(&SearchScope::single_file(ctx.file_id()))
.all()
});
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_doc_comment.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_doc_comment.rs
index 226a5dd9f..ddc8a50ed 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_doc_comment.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_doc_comment.rs
@@ -27,7 +27,9 @@ use crate::{
pub(crate) fn desugar_doc_comment(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
let comment = ctx.find_token_at_offset::<ast::Comment>()?;
// Only allow doc comments
- let Some(placement) = comment.kind().doc else { return None; };
+ let Some(placement) = comment.kind().doc else {
+ return None;
+ };
// Only allow comments which are alone on their line
if let Some(prev) = comment.syntax().prev_token() {
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
index 5c435dd9c..9beb616d9 100644
--- 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
@@ -1,5 +1,5 @@
use either::Either;
-use hir::{AssocItem, HasVisibility, Module, ModuleDef, Name, PathResolution, ScopeDef};
+use hir::{AssocItem, Enum, HasVisibility, Module, ModuleDef, Name, PathResolution, ScopeDef};
use ide_db::{
defs::{Definition, NameRefClass},
search::SearchScope,
@@ -45,7 +45,8 @@ pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext<'_>) ->
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,
+ PathResolution::Def(ModuleDef::Module(it)) => Expandable::Module(it),
+ PathResolution::Def(ModuleDef::Adt(hir::Adt::Enum(e))) => Expandable::Enum(e),
_ => return None,
};
@@ -90,6 +91,11 @@ pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext<'_>) ->
)
}
+enum Expandable {
+ Module(Module),
+ Enum(Enum),
+}
+
fn find_parent_and_path(
star: &SyntaxToken,
) -> Option<(Either<ast::UseTree, ast::UseTreeList>, ast::Path)> {
@@ -114,7 +120,7 @@ fn find_parent_and_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()
+ def.usages(&ctx.sema).in_scope(&search_scope).at_least_one()
}
#[derive(Debug, Clone)]
@@ -168,23 +174,59 @@ impl Refs {
}
}
-fn find_refs_in_mod(ctx: &AssistContext<'_>, module: Module, visible_from: Module) -> Option<Refs> {
- if !is_mod_visible_from(ctx, module, visible_from) {
+fn find_refs_in_mod(
+ ctx: &AssistContext<'_>,
+ expandable: Expandable,
+ visible_from: Module,
+) -> Option<Refs> {
+ if !is_expandable_visible_from(ctx, &expandable, 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))
+ match expandable {
+ Expandable::Module(module) => {
+ 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))
+ }
+ Expandable::Enum(enm) => Some(Refs(
+ enm.variants(ctx.db())
+ .into_iter()
+ .map(|v| Ref { visible_name: v.name(ctx.db()), def: Definition::Variant(v) })
+ .collect(),
+ )),
+ }
}
-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)
+fn is_expandable_visible_from(
+ ctx: &AssistContext<'_>,
+ expandable: &Expandable,
+ from: Module,
+) -> bool {
+ 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,
+ }
+ }
+
+ match expandable {
+ Expandable::Module(module) => 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,
+ },
+ Expandable::Enum(enm) => {
+ let module = enm.module(ctx.db());
+ enm.visibility(ctx.db()).is_visible_from(ctx.db(), from.into())
+ && is_mod_visible_from(ctx, module, from)
}
- None => true,
}
}
@@ -897,4 +939,98 @@ struct Baz {
",
);
}
+
+ #[test]
+ fn test_support_for_enums() {
+ check_assist(
+ expand_glob_import,
+ r#"
+mod foo {
+ pub enum Foo {
+ Bar,
+ Baz,
+ }
+}
+
+use foo::Foo;
+use foo::Foo::*$0;
+
+struct Strukt {
+ bar: Foo,
+}
+
+fn main() {
+ let s: Strukt = Strukt { bar: Bar };
+}"#,
+ r#"
+mod foo {
+ pub enum Foo {
+ Bar,
+ Baz,
+ }
+}
+
+use foo::Foo;
+use foo::Foo::Bar;
+
+struct Strukt {
+ bar: Foo,
+}
+
+fn main() {
+ let s: Strukt = Strukt { bar: Bar };
+}"#,
+ )
+ }
+
+ #[test]
+ fn test_expanding_multiple_variants_at_once() {
+ check_assist(
+ expand_glob_import,
+ r#"
+mod foo {
+ pub enum Foo {
+ Bar,
+ Baz,
+ }
+}
+
+mod abc {
+ use super::foo;
+ use super::foo::Foo::*$0;
+
+ struct Strukt {
+ baz: foo::Foo,
+ bar: foo::Foo,
+ }
+
+ fn trying_calling() {
+ let s: Strukt = Strukt { bar: Bar , baz : Baz };
+ }
+
+}"#,
+ r#"
+mod foo {
+ pub enum Foo {
+ Bar,
+ Baz,
+ }
+}
+
+mod abc {
+ use super::foo;
+ use super::foo::Foo::{Bar, Baz};
+
+ struct Strukt {
+ baz: foo::Foo,
+ bar: foo::Foo,
+ }
+
+ fn trying_calling() {
+ let s: Strukt = Strukt { bar: Bar , baz : Baz };
+ }
+
+}"#,
+ )
+ }
}
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
index 2a67909e6..b8b781ea4 100644
--- 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
@@ -384,7 +384,7 @@ impl LocalUsages {
Self(
Definition::Local(var)
.usages(&ctx.sema)
- .in_scope(SearchScope::single_file(ctx.file_id()))
+ .in_scope(&SearchScope::single_file(ctx.file_id()))
.all(),
)
}
@@ -1360,14 +1360,15 @@ fn make_call(ctx: &AssistContext<'_>, fun: &Function, indent: IndentLevel) -> St
}
format_to!(buf, "{expr}");
- let insert_comma = fun
- .body
- .parent()
- .and_then(ast::MatchArm::cast)
- .map_or(false, |it| it.comma_token().is_none());
+ let parent_match_arm = fun.body.parent().and_then(ast::MatchArm::cast);
+ let insert_comma = parent_match_arm.as_ref().is_some_and(|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()) {
+ } else if parent_match_arm.is_none()
+ && fun.ret_ty.is_unit()
+ && (!fun.outliving_locals.is_empty() || !expr.is_block_like())
+ {
buf.push(';');
}
buf
@@ -4611,6 +4612,29 @@ fn $0fun_name() -> i32 {
}
"#,
);
+
+ // Makes sure no semicolon is added for unit-valued match arms
+ check_assist(
+ extract_function,
+ r#"
+fn main() {
+ match () {
+ _ => $0()$0,
+ }
+}
+"#,
+ r#"
+fn main() {
+ match () {
+ _ => fun_name(),
+ }
+}
+
+fn $0fun_name() {
+ ()
+}
+"#,
+ )
}
#[test]
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
index de37f5f13..6839c5820 100644
--- 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
@@ -478,7 +478,7 @@ impl Module {
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 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;
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
index d6c59a9c8..c9f272474 100644
--- 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
@@ -1,11 +1,11 @@
use hir::{db::HirDatabase, HasSource, HasVisibility, ModuleDef, PathResolution, ScopeDef};
use ide_db::base_db::FileId;
use syntax::{
- ast::{self, HasVisibility as _},
- AstNode, TextRange, TextSize,
+ ast::{self, edit_in_place::HasVisibilityEdit, make, HasVisibility as _},
+ AstNode, TextRange,
};
-use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
+use crate::{AssistContext, AssistId, AssistKind, Assists};
// FIXME: this really should be a fix for diagnostic, rather than an assist.
@@ -40,12 +40,16 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext<'_>)
let qualifier = path.qualifier()?;
let name_ref = path.segment()?.name_ref()?;
let qualifier_res = ctx.sema.resolve_path(&qualifier)?;
- let PathResolution::Def(ModuleDef::Module(module)) = qualifier_res else { return None; };
+ let PathResolution::Def(ModuleDef::Module(module)) = qualifier_res else {
+ return None;
+ };
let (_, def) = module
.scope(ctx.db(), None)
.into_iter()
.find(|(name, _)| name.to_smol_str() == name_ref.text().as_str())?;
- let ScopeDef::ModuleDef(def) = def else { return None; };
+ let ScopeDef::ModuleDef(def) = def else {
+ return None;
+ };
let current_module = ctx.sema.scope(path.syntax())?.module();
let target_module = def.module(ctx.db())?;
@@ -54,11 +58,13 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext<'_>)
return None;
};
- let (offset, current_visibility, target, target_file, target_name) =
- target_data_for_def(ctx.db(), def)?;
+ let (vis_owner, 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 missing_visibility = if current_module.krate() == target_module.krate() {
+ make::visibility_pub_crate()
+ } else {
+ make::visibility_pub()
+ };
let assist_label = match target_name {
None => format!("Change visibility to {missing_visibility}"),
@@ -67,23 +73,14 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext<'_>)
}
};
- 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} ")),
- },
+ acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |edit| {
+ edit.edit_file(target_file);
+
+ let vis_owner = edit.make_mut(vis_owner);
+ vis_owner.set_visibility(missing_visibility.clone_for_update());
+
+ if let Some((cap, vis)) = ctx.config.snippet_cap.zip(vis_owner.visibility()) {
+ edit.add_tabstop_before(cap, vis);
}
})
}
@@ -103,19 +100,22 @@ fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext<'_>
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 {
+ let (vis_owner, target) = match in_file_source.value {
hir::FieldSource::Named(it) => {
- let s = it.syntax();
- (vis_offset(s), it.visibility(), s.text_range())
+ let range = it.syntax().text_range();
+ (ast::AnyHasVisibility::new(it), range)
}
hir::FieldSource::Pos(it) => {
- let s = it.syntax();
- (vis_offset(s), it.visibility(), s.text_range())
+ let range = it.syntax().text_range();
+ (ast::AnyHasVisibility::new(it), range)
}
};
- let missing_visibility =
- if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
+ let missing_visibility = if current_module.krate() == target_module.krate() {
+ make::visibility_pub_crate()
+ } else {
+ make::visibility_pub()
+ };
let target_file = in_file_source.file_id.original_file(ctx.db());
let target_name = record_field_def.name(ctx.db());
@@ -125,23 +125,14 @@ fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext<'_>
target_name.display(ctx.db())
);
- 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} ")),
- },
+ acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |edit| {
+ edit.edit_file(target_file);
+
+ let vis_owner = edit.make_mut(vis_owner);
+ vis_owner.set_visibility(missing_visibility.clone_for_update());
+
+ if let Some((cap, vis)) = ctx.config.snippet_cap.zip(vis_owner.visibility()) {
+ edit.add_tabstop_before(cap, vis);
}
})
}
@@ -149,11 +140,11 @@ fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext<'_>
fn target_data_for_def(
db: &dyn HirDatabase,
def: hir::ModuleDef,
-) -> Option<(TextSize, Option<ast::Visibility>, TextRange, FileId, Option<hir::Name>)> {
+) -> Option<(ast::AnyHasVisibility, 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)>
+ ) -> Option<(ast::AnyHasVisibility, TextRange, FileId)>
where
S: HasSource<Ast = Ast>,
Ast: AstNode + ast::HasVisibility,
@@ -161,18 +152,12 @@ fn target_data_for_def(
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 range = in_file_syntax.value.text_range();
+ Some((ast::AnyHasVisibility::new(source.value), range, file_id.original_file(db.upcast())))
}
let target_name;
- let (offset, current_visibility, target, target_file) = match def {
+ let (offset, target, target_file) = match def {
hir::ModuleDef::Function(f) => {
target_name = Some(f.name(db));
offset_target_and_file_id(db, f)?
@@ -209,8 +194,8 @@ fn target_data_for_def(
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)
+ let range = in_file_source.value.syntax().text_range();
+ (ast::AnyHasVisibility::new(in_file_source.value), range, file_id)
}
// FIXME
hir::ModuleDef::Macro(_) => return None,
@@ -218,7 +203,7 @@ fn target_data_for_def(
hir::ModuleDef::Variant(_) | hir::ModuleDef::BuiltinType(_) => return None,
};
- Some((offset, current_visibility, target, target_file, target_name))
+ Some((offset, target, target_file, target_name))
}
#[cfg(test)]
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
index 860372941..7e4f140a2 100644
--- 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
@@ -15,6 +15,7 @@ use crate::{
// Generates default implementation from new method.
//
// ```
+// # //- minicore: default
// struct Example { _inner: () }
//
// impl Example {
@@ -54,6 +55,7 @@ pub(crate) fn generate_default_from_new(acc: &mut Assists, ctx: &AssistContext<'
}
let impl_ = fn_node.syntax().ancestors().find_map(ast::Impl::cast)?;
+ let self_ty = impl_.self_ty()?;
if is_default_implemented(ctx, &impl_) {
cov_mark::hit!(default_block_is_already_present);
cov_mark::hit!(struct_in_module_with_default);
@@ -70,15 +72,19 @@ pub(crate) fn generate_default_from_new(acc: &mut Assists, ctx: &AssistContext<'
let default_code = " fn default() -> Self {
Self::new()
}";
- let code = generate_trait_impl_text_from_impl(&impl_, "Default", default_code);
+ let code = generate_trait_impl_text_from_impl(&impl_, self_ty, "Default", default_code);
builder.insert(insert_location.end(), code);
},
)
}
// FIXME: based on from utils::generate_impl_text_inner
-fn generate_trait_impl_text_from_impl(impl_: &ast::Impl, trait_text: &str, code: &str) -> String {
- let impl_ty = impl_.self_ty().unwrap();
+fn generate_trait_impl_text_from_impl(
+ impl_: &ast::Impl,
+ self_ty: ast::Type,
+ trait_text: &str,
+ code: &str,
+) -> String {
let generic_params = impl_.generic_param_list().map(|generic_params| {
let lifetime_params =
generic_params.lifetime_params().map(ast::GenericParam::LifetimeParam);
@@ -109,7 +115,7 @@ fn generate_trait_impl_text_from_impl(impl_: &ast::Impl, trait_text: &str, code:
if let Some(generic_params) = &generic_params {
format_to!(buf, "{generic_params}")
}
- format_to!(buf, " {trait_text} for {impl_ty}");
+ format_to!(buf, " {trait_text} for {self_ty}");
match impl_.where_clause() {
Some(where_clause) => {
@@ -136,7 +142,9 @@ fn is_default_implemented(ctx: &AssistContext<'_>, impl_: &Impl) -> bool {
let default = FamousDefs(&ctx.sema, krate).core_default_Default();
let default_trait = match default {
Some(value) => value,
- None => return false,
+ // Return `true` to avoid providing the assist because it makes no sense
+ // to impl `Default` when it's missing.
+ None => return true,
};
ty.impls_trait(db, default_trait, &[])
@@ -480,6 +488,7 @@ impl Example {
check_assist_not_applicable(
generate_default_from_new,
r#"
+//- minicore: default
struct Example { _inner: () }
impl Example {
@@ -655,4 +664,23 @@ mod test {
"#,
);
}
+
+ #[test]
+ fn not_applicable_when_default_lang_item_is_missing() {
+ check_assist_not_applicable(
+ generate_default_from_new,
+ r#"
+struct S;
+impl S {
+ fn new$0() -> Self {}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_for_missing_self_ty() {
+ // Regression test for #15398.
+ check_assist_not_applicable(generate_default_from_new, "impl { fn new$0() -> Self {} }");
+ }
}
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
index b68c766e6..31fc69562 100644
--- 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
@@ -1,13 +1,17 @@
use std::collections::HashSet;
use hir::{self, HasCrate, HasSource, HasVisibility};
-use syntax::ast::{self, make, AstNode, HasGenericParams, HasName, HasVisibility as _};
+use syntax::{
+ ast::{
+ self, edit_in_place::Indent, make, AstNode, HasGenericParams, HasName, HasVisibility as _,
+ },
+ ted,
+};
use crate::{
- utils::{convert_param_list_to_arg_list, find_struct_impl, render_snippet, Cursor},
+ utils::{convert_param_list_to_arg_list, find_struct_impl},
AssistContext, AssistId, AssistKind, Assists, GroupLabel,
};
-use syntax::ast::edit::AstNodeEdit;
// Assist: generate_delegate_methods
//
@@ -88,13 +92,15 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<'
let adt = ast::Adt::Struct(strukt.clone());
let name = name.display(ctx.db()).to_string();
// if `find_struct_impl` returns None, that means that a function named `name` already exists.
- let Some(impl_def) = find_struct_impl(ctx, &adt, std::slice::from_ref(&name)) else { continue; };
+ let Some(impl_def) = find_struct_impl(ctx, &adt, std::slice::from_ref(&name)) else {
+ continue;
+ };
acc.add_group(
&GroupLabel("Generate delegate methods…".to_owned()),
AssistId("generate_delegate_methods", AssistKind::Generate),
format!("Generate delegate for `{field_name}.{name}()`",),
target,
- |builder| {
+ |edit| {
// Create the function
let method_source = match method.source(ctx.db()) {
Some(source) => source.value,
@@ -133,36 +139,12 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<'
is_const,
is_unsafe,
)
- .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());
- }
- }
- }
+ // Get the impl to update, or create one if we need to.
+ let impl_def = match impl_def {
+ Some(impl_def) => edit.make_mut(impl_def),
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();
@@ -176,24 +158,34 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<'
None,
)
.clone_for_update();
- let assoc_items = impl_def.get_or_create_assoc_item_list();
- assoc_items.add_item(f.clone().into());
+
+ // Fixup impl_def indentation
+ let indent = strukt.indent_level();
+ impl_def.reindent_to(indent);
// 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);
- }
- }
+ let strukt = edit.make_mut(strukt.clone());
+ ted::insert_all(
+ ted::Position::after(strukt.syntax()),
+ vec![
+ make::tokens::whitespace(&format!("\n\n{indent}")).into(),
+ impl_def.syntax().clone().into(),
+ ],
+ );
+
+ impl_def
}
+ };
+
+ // Fixup function indentation.
+ // FIXME: Should really be handled by `AssocItemList::add_item`
+ f.reindent_to(impl_def.indent_level() + 1);
+
+ let assoc_items = impl_def.get_or_create_assoc_item_list();
+ assoc_items.add_item(f.clone().into());
+
+ if let Some(cap) = ctx.config.snippet_cap {
+ edit.add_tabstop_before(cap, f)
}
},
)?;
@@ -243,6 +235,45 @@ impl Person {
}
#[test]
+ fn test_generate_delegate_create_impl_block_match_indent() {
+ check_assist(
+ generate_delegate_methods,
+ r#"
+mod indent {
+ struct Age(u8);
+ impl Age {
+ fn age(&self) -> u8 {
+ self.0
+ }
+ }
+
+ struct Person {
+ ag$0e: Age,
+ }
+}"#,
+ r#"
+mod indent {
+ 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,
@@ -280,6 +311,47 @@ impl Person {
}
#[test]
+ fn test_generate_delegate_update_impl_block_match_indent() {
+ check_assist(
+ generate_delegate_methods,
+ r#"
+mod indent {
+ struct Age(u8);
+ impl Age {
+ fn age(&self) -> u8 {
+ self.0
+ }
+ }
+
+ struct Person {
+ ag$0e: Age,
+ }
+
+ impl Person {}
+}"#,
+ r#"
+mod indent {
+ 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,
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs
new file mode 100644
index 000000000..f4fa6a74c
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs
@@ -0,0 +1,1051 @@
+use std::ops::Not;
+
+use crate::{
+ assist_context::{AssistContext, Assists},
+ utils::convert_param_list_to_arg_list,
+};
+use either::Either;
+use hir::{db::HirDatabase, HasVisibility};
+use ide_db::{
+ assists::{AssistId, GroupLabel},
+ path_transform::PathTransform,
+};
+use syntax::{
+ ast::{
+ self,
+ edit::{self, AstNodeEdit},
+ make, AssocItem, HasGenericParams, HasName, HasVisibility as astHasVisibility, Path,
+ },
+ ted::{self, Position},
+ AstNode, NodeOrToken, SyntaxKind,
+};
+
+// Assist: generate_delegate_trait
+//
+// Generate delegate trait implementation for `StructField`s.
+//
+// ```
+// trait SomeTrait {
+// type T;
+// fn fn_(arg: u32) -> u32;
+// fn method_(&mut self) -> bool;
+// }
+// struct A;
+// impl SomeTrait for A {
+// type T = u32;
+//
+// fn fn_(arg: u32) -> u32 {
+// 42
+// }
+//
+// fn method_(&mut self) -> bool {
+// false
+// }
+// }
+// struct B {
+// a$0: A,
+// }
+// ```
+// ->
+// ```
+// trait SomeTrait {
+// type T;
+// fn fn_(arg: u32) -> u32;
+// fn method_(&mut self) -> bool;
+// }
+// struct A;
+// impl SomeTrait for A {
+// type T = u32;
+//
+// fn fn_(arg: u32) -> u32 {
+// 42
+// }
+//
+// fn method_(&mut self) -> bool {
+// false
+// }
+// }
+// struct B {
+// a: A,
+// }
+//
+// impl SomeTrait for B {
+// type T = <A as SomeTrait>::T;
+//
+// fn fn_(arg: u32) -> u32 {
+// <A as SomeTrait>::fn_(arg)
+// }
+//
+// fn method_(&mut self) -> bool {
+// <A as SomeTrait>::method_( &mut self.a )
+// }
+// }
+// ```
+pub(crate) fn generate_delegate_trait(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let strukt = Struct::new(ctx.find_node_at_offset::<ast::Struct>()?)?;
+
+ let field: Field = match ctx.find_node_at_offset::<ast::RecordField>() {
+ Some(field) => Field::new(&ctx, Either::Left(field))?,
+ None => {
+ let field = ctx.find_node_at_offset::<ast::TupleField>()?;
+ let field_list = ctx.find_node_at_offset::<ast::TupleFieldList>()?;
+ Field::new(&ctx, either::Right((field, field_list)))?
+ }
+ };
+
+ strukt.delegate(field, acc, ctx);
+ Some(())
+}
+
+/// A utility object that represents a struct's field.
+struct Field {
+ name: String,
+ ty: ast::Type,
+ range: syntax::TextRange,
+ impls: Vec<Delegee>,
+}
+
+impl Field {
+ pub(crate) fn new(
+ ctx: &AssistContext<'_>,
+ f: Either<ast::RecordField, (ast::TupleField, ast::TupleFieldList)>,
+ ) -> Option<Field> {
+ let db = ctx.sema.db;
+ let name: String;
+ let range: syntax::TextRange;
+ let ty: ast::Type;
+
+ let module = ctx.sema.to_module_def(ctx.file_id())?;
+
+ match f {
+ Either::Left(f) => {
+ name = f.name()?.to_string();
+ ty = f.ty()?;
+ range = f.syntax().text_range();
+ }
+ Either::Right((f, l)) => {
+ name = l.fields().position(|it| it == f)?.to_string();
+ ty = f.ty()?;
+ range = f.syntax().text_range();
+ }
+ };
+
+ let hir_ty = ctx.sema.resolve_type(&ty)?;
+ let type_impls = hir::Impl::all_for_type(db, hir_ty.clone());
+ let mut impls = Vec::with_capacity(type_impls.len());
+ let type_param = hir_ty.as_type_param(db);
+
+ if let Some(tp) = type_param {
+ for tb in tp.trait_bounds(db) {
+ impls.push(Delegee::Bound(BoundCase(tb)));
+ }
+ };
+
+ for imp in type_impls {
+ match imp.trait_(db) {
+ Some(tr) => {
+ if tr.is_visible_from(db, module) {
+ impls.push(Delegee::Impls(ImplCase(tr, imp)))
+ }
+ }
+ None => (),
+ }
+ }
+
+ Some(Field { name, ty, range, impls })
+ }
+}
+
+/// A field that we want to delegate can offer the enclosing struct
+/// trait to implement in two ways. The first way is when the field
+/// actually implements the trait and the second way is when the field
+/// has a bound type parameter. We handle these cases in different ways
+/// hence the enum.
+enum Delegee {
+ Bound(BoundCase),
+ Impls(ImplCase),
+}
+
+struct BoundCase(hir::Trait);
+struct ImplCase(hir::Trait, hir::Impl);
+
+impl Delegee {
+ fn signature(&self, db: &dyn HirDatabase) -> String {
+ let mut s = String::new();
+
+ let (Delegee::Bound(BoundCase(it)) | Delegee::Impls(ImplCase(it, _))) = self;
+
+ for m in it.module(db).path_to_root(db).iter().rev() {
+ if let Some(name) = m.name(db) {
+ s.push_str(&format!("{}::", name.to_smol_str()));
+ }
+ }
+
+ s.push_str(&it.name(db).to_smol_str());
+ s
+ }
+}
+
+/// A utility struct that is used for the enclosing struct.
+struct Struct {
+ strukt: ast::Struct,
+ name: ast::Name,
+}
+
+impl Struct {
+ pub(crate) fn new(s: ast::Struct) -> Option<Self> {
+ let name = s.name()?;
+ Some(Struct { name, strukt: s })
+ }
+
+ pub(crate) fn delegate(&self, field: Field, acc: &mut Assists, ctx: &AssistContext<'_>) {
+ let db = ctx.db();
+ for delegee in &field.impls {
+ // FIXME : We can omit already implemented impl_traits
+ // But we don't know what the &[hir::Type] argument should look like.
+
+ // let trait_ = match delegee {
+ // Delegee::Bound(b) => b.0,
+ // Delegee::Impls(i) => i.1,
+ // };
+
+ // if self.hir_ty.impls_trait(db, trait_, &[]) {
+ // continue;
+ // }
+ let signature = delegee.signature(db);
+ let Some(delegate) = generate_impl(ctx, self, &field.ty, &field.name, delegee) else {
+ continue;
+ };
+
+ acc.add_group(
+ &GroupLabel("Delegate trait impl for field...".to_owned()),
+ AssistId("generate_delegate_trait", ide_db::assists::AssistKind::Generate),
+ format!("Generate delegate impl `{}` for `{}`", signature, field.name),
+ field.range,
+ |builder| {
+ builder.insert(
+ self.strukt.syntax().text_range().end(),
+ format!("\n\n{}", delegate.syntax()),
+ );
+ },
+ );
+ }
+ }
+}
+
+fn generate_impl(
+ ctx: &AssistContext<'_>,
+ strukt: &Struct,
+ field_ty: &ast::Type,
+ field_name: &String,
+ delegee: &Delegee,
+) -> Option<ast::Impl> {
+ let delegate: ast::Impl;
+ let source: ast::Impl;
+ let genpar: Option<ast::GenericParamList>;
+ let db = ctx.db();
+ let base_path = make::path_from_text(&field_ty.to_string().as_str());
+ let s_path = make::ext::ident_path(&strukt.name.to_string());
+
+ match delegee {
+ Delegee::Bound(delegee) => {
+ let in_file = ctx.sema.source(delegee.0.to_owned())?;
+ let source: ast::Trait = in_file.value;
+
+ delegate = make::impl_trait(
+ delegee.0.is_unsafe(db),
+ None,
+ None,
+ strukt.strukt.generic_param_list(),
+ None,
+ delegee.0.is_auto(db),
+ make::ty(&delegee.0.name(db).to_smol_str()),
+ make::ty_path(s_path),
+ source.where_clause(),
+ strukt.strukt.where_clause(),
+ None,
+ )
+ .clone_for_update();
+
+ genpar = source.generic_param_list();
+ let delegate_assoc_items = delegate.get_or_create_assoc_item_list();
+ let gen_args: String =
+ genpar.map_or_else(String::new, |params| params.to_generic_args().to_string());
+
+ // Goto link : https://doc.rust-lang.org/reference/paths.html#qualified-paths
+ let qualified_path_type = make::path_from_text(&format!(
+ "<{} as {}{}>",
+ base_path.to_string(),
+ delegee.0.name(db).to_smol_str(),
+ gen_args.to_string()
+ ));
+
+ match source.assoc_item_list() {
+ Some(ai) => {
+ ai.assoc_items()
+ .filter(|item| matches!(item, AssocItem::MacroCall(_)).not())
+ .for_each(|item| {
+ let assoc =
+ process_assoc_item(item, qualified_path_type.clone(), &field_name);
+ if let Some(assoc) = assoc {
+ delegate_assoc_items.add_item(assoc);
+ }
+ });
+ }
+ None => {}
+ };
+
+ let target = ctx.sema.scope(strukt.strukt.syntax())?;
+ let source = ctx.sema.scope(source.syntax())?;
+
+ let transform =
+ PathTransform::trait_impl(&target, &source, delegee.0, delegate.clone());
+ transform.apply(&delegate.syntax());
+ }
+ Delegee::Impls(delegee) => {
+ let in_file = ctx.sema.source(delegee.1.to_owned())?;
+ source = in_file.value;
+ delegate = make::impl_trait(
+ delegee.0.is_unsafe(db),
+ source.generic_param_list(),
+ None,
+ None,
+ None,
+ delegee.0.is_auto(db),
+ make::ty(&delegee.0.name(db).to_smol_str()),
+ make::ty_path(s_path),
+ source.where_clause(),
+ strukt.strukt.where_clause(),
+ None,
+ )
+ .clone_for_update();
+ genpar = source.generic_param_list();
+ let delegate_assoc_items = delegate.get_or_create_assoc_item_list();
+ let gen_args: String =
+ genpar.map_or_else(String::new, |params| params.to_generic_args().to_string());
+
+ // Goto link : https://doc.rust-lang.org/reference/paths.html#qualified-paths
+ let qualified_path_type = make::path_from_text(&format!(
+ "<{} as {}{}>",
+ base_path.to_string().as_str(),
+ delegee.0.name(db).to_smol_str(),
+ gen_args.to_string().as_str()
+ ));
+
+ source
+ .get_or_create_assoc_item_list()
+ .assoc_items()
+ .filter(|item| matches!(item, AssocItem::MacroCall(_)).not())
+ .for_each(|item| {
+ let assoc = process_assoc_item(item, qualified_path_type.clone(), &field_name);
+ if let Some(assoc) = assoc {
+ delegate_assoc_items.add_item(assoc);
+ }
+ });
+
+ let target = ctx.sema.scope(strukt.strukt.syntax())?;
+ let source = ctx.sema.scope(source.syntax())?;
+
+ let transform =
+ PathTransform::trait_impl(&target, &source, delegee.0, delegate.clone());
+ transform.apply(&delegate.syntax());
+ }
+ }
+
+ Some(delegate)
+}
+
+fn process_assoc_item(
+ item: syntax::ast::AssocItem,
+ qual_path_ty: ast::Path,
+ base_name: &str,
+) -> Option<ast::AssocItem> {
+ match item {
+ AssocItem::Const(c) => const_assoc_item(c, qual_path_ty),
+ AssocItem::Fn(f) => func_assoc_item(f, qual_path_ty, base_name),
+ AssocItem::MacroCall(_) => {
+ // FIXME : Handle MacroCall case.
+ // macro_assoc_item(mac, qual_path_ty)
+ None
+ }
+ AssocItem::TypeAlias(ta) => ty_assoc_item(ta, qual_path_ty),
+ }
+}
+
+fn const_assoc_item(item: syntax::ast::Const, qual_path_ty: ast::Path) -> Option<AssocItem> {
+ let path_expr_segment = make::path_from_text(item.name()?.to_string().as_str());
+
+ // We want rhs of the const assignment to be a qualified path
+ // The general case for const assigment can be found [here](`https://doc.rust-lang.org/reference/items/constant-items.html`)
+ // The qualified will have the following generic syntax :
+ // <Base as Trait<GenArgs>>::ConstName;
+ // FIXME : We can't rely on `make::path_qualified` for now but it would be nice to replace the following with it.
+ // make::path_qualified(qual_path_ty, path_expr_segment.as_single_segment().unwrap());
+ let qualpath = qualpath(qual_path_ty, path_expr_segment);
+ let inner =
+ make::item_const(item.visibility(), item.name()?, item.ty()?, make::expr_path(qualpath))
+ .clone_for_update();
+
+ Some(AssocItem::Const(inner))
+}
+
+fn func_assoc_item(
+ item: syntax::ast::Fn,
+ qual_path_ty: Path,
+ base_name: &str,
+) -> Option<AssocItem> {
+ let path_expr_segment = make::path_from_text(item.name()?.to_string().as_str());
+ let qualpath = qualpath(qual_path_ty, path_expr_segment);
+
+ let call = match item.param_list() {
+ // Methods and funcs should be handled separately.
+ // We ask if the func has a `self` param.
+ Some(l) => match l.self_param() {
+ Some(slf) => {
+ let mut self_kw = make::expr_path(make::path_from_text("self"));
+ self_kw = make::expr_field(self_kw, base_name);
+
+ let tail_expr_self = match slf.kind() {
+ ast::SelfParamKind::Owned => self_kw,
+ ast::SelfParamKind::Ref => make::expr_ref(self_kw, false),
+ ast::SelfParamKind::MutRef => make::expr_ref(self_kw, true),
+ };
+
+ let param_count = l.params().count();
+ let args = convert_param_list_to_arg_list(l).clone_for_update();
+
+ if param_count > 0 {
+ // Add SelfParam and a TOKEN::COMMA
+ ted::insert_all(
+ Position::after(args.l_paren_token()?),
+ vec![
+ NodeOrToken::Node(tail_expr_self.syntax().clone_for_update()),
+ NodeOrToken::Token(make::token(SyntaxKind::WHITESPACE)),
+ NodeOrToken::Token(make::token(SyntaxKind::COMMA)),
+ ],
+ );
+ } else {
+ // Add SelfParam only
+ ted::insert(
+ Position::after(args.l_paren_token()?),
+ NodeOrToken::Node(tail_expr_self.syntax().clone_for_update()),
+ );
+ }
+
+ make::expr_call(make::expr_path(qualpath), args)
+ }
+ None => make::expr_call(make::expr_path(qualpath), convert_param_list_to_arg_list(l)),
+ },
+ None => make::expr_call(
+ make::expr_path(qualpath),
+ convert_param_list_to_arg_list(make::param_list(None, Vec::new())),
+ ),
+ }
+ .clone_for_update();
+
+ let body = make::block_expr(vec![], Some(call)).clone_for_update();
+ let func = make::fn_(
+ item.visibility(),
+ item.name()?,
+ item.generic_param_list(),
+ item.where_clause(),
+ item.param_list()?,
+ body,
+ item.ret_type(),
+ item.async_token().is_some(),
+ item.const_token().is_some(),
+ item.unsafe_token().is_some(),
+ )
+ .clone_for_update();
+
+ Some(AssocItem::Fn(func.indent(edit::IndentLevel(1)).clone_for_update()))
+}
+
+fn ty_assoc_item(item: syntax::ast::TypeAlias, qual_path_ty: Path) -> Option<AssocItem> {
+ let path_expr_segment = make::path_from_text(item.name()?.to_string().as_str());
+ let qualpath = qualpath(qual_path_ty, path_expr_segment);
+ let ty = make::ty_path(qualpath);
+ let ident = item.name()?.to_string();
+
+ let alias = make::ty_alias(
+ ident.as_str(),
+ item.generic_param_list(),
+ None,
+ item.where_clause(),
+ Some((ty, None)),
+ )
+ .clone_for_update();
+
+ Some(AssocItem::TypeAlias(alias))
+}
+
+fn qualpath(qual_path_ty: ast::Path, path_expr_seg: ast::Path) -> ast::Path {
+ make::path_from_text(&format!("{}::{}", qual_path_ty.to_string(), path_expr_seg.to_string()))
+}
+
+#[cfg(test)]
+mod test {
+
+ use super::*;
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ #[test]
+ fn test_tuple_struct_basic() {
+ check_assist(
+ generate_delegate_trait,
+ r#"
+struct Base;
+struct S(B$0ase);
+trait Trait {}
+impl Trait for Base {}
+"#,
+ r#"
+struct Base;
+struct S(Base);
+
+impl Trait for S {}
+trait Trait {}
+impl Trait for Base {}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_struct_struct_basic() {
+ check_assist(
+ generate_delegate_trait,
+ r#"
+struct Base;
+struct S {
+ ba$0se : Base
+}
+trait Trait {}
+impl Trait for Base {}
+"#,
+ r#"
+struct Base;
+struct S {
+ base : Base
+}
+
+impl Trait for S {}
+trait Trait {}
+impl Trait for Base {}
+"#,
+ )
+ }
+
+ // Structs need to be by def populated with fields
+ // However user can invoke this assist while still editing
+ // We therefore assert its non-applicability
+ #[test]
+ fn test_yet_empty_struct() {
+ check_assist_not_applicable(
+ generate_delegate_trait,
+ r#"
+struct Base;
+struct S {
+ $0
+}
+
+impl Trait for S {}
+trait Trait {}
+impl Trait for Base {}
+"#,
+ )
+ }
+
+ #[test]
+ fn test_yet_unspecified_field_type() {
+ check_assist_not_applicable(
+ generate_delegate_trait,
+ r#"
+struct Base;
+struct S {
+ ab$0c
+}
+
+impl Trait for S {}
+trait Trait {}
+impl Trait for Base {}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_unsafe_trait() {
+ check_assist(
+ generate_delegate_trait,
+ r#"
+struct Base;
+struct S {
+ ba$0se : Base
+}
+unsafe trait Trait {}
+unsafe impl Trait for Base {}
+"#,
+ r#"
+struct Base;
+struct S {
+ base : Base
+}
+
+unsafe impl Trait for S {}
+unsafe trait Trait {}
+unsafe impl Trait for Base {}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_unsafe_trait_with_unsafe_fn() {
+ check_assist(
+ generate_delegate_trait,
+ r#"
+struct Base;
+struct S {
+ ba$0se: Base,
+}
+
+unsafe trait Trait {
+ unsafe fn a_func();
+ unsafe fn a_method(&self);
+}
+unsafe impl Trait for Base {
+ unsafe fn a_func() {}
+ unsafe fn a_method(&self) {}
+}
+"#,
+ r#"
+struct Base;
+struct S {
+ base: Base,
+}
+
+unsafe impl Trait for S {
+ unsafe fn a_func() {
+ <Base as Trait>::a_func()
+ }
+
+ unsafe fn a_method(&self) {
+ <Base as Trait>::a_method( &self.base )
+ }
+}
+
+unsafe trait Trait {
+ unsafe fn a_func();
+ unsafe fn a_method(&self);
+}
+unsafe impl Trait for Base {
+ unsafe fn a_func() {}
+ unsafe fn a_method(&self) {}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_struct_with_where_clause() {
+ check_assist(
+ generate_delegate_trait,
+ r#"
+trait AnotherTrait {}
+struct S<T>
+where
+ T: AnotherTrait,
+{
+ b$0 : T,
+}"#,
+ r#"
+trait AnotherTrait {}
+struct S<T>
+where
+ T: AnotherTrait,
+{
+ b : T,
+}
+
+impl<T> AnotherTrait for S<T>
+where
+ T: AnotherTrait,
+{}"#,
+ );
+ }
+
+ #[test]
+ fn test_complex_without_where() {
+ check_assist(
+ generate_delegate_trait,
+ r#"
+trait Trait<'a, T, const C: usize> {
+ type AssocType;
+ const AssocConst: usize;
+ fn assoc_fn(p: ());
+ fn assoc_method(&self, p: ());
+}
+
+struct Base;
+struct S {
+ field$0: Base
+}
+
+impl<'a, T, const C: usize> Trait<'a, T, C> for Base {
+ type AssocType = ();
+ const AssocConst: usize = 0;
+ fn assoc_fn(p: ()) {}
+ fn assoc_method(&self, p: ()) {}
+}
+"#,
+ r#"
+trait Trait<'a, T, const C: usize> {
+ type AssocType;
+ const AssocConst: usize;
+ fn assoc_fn(p: ());
+ fn assoc_method(&self, p: ());
+}
+
+struct Base;
+struct S {
+ field: Base
+}
+
+impl<'a, T, const C: usize> Trait<'a, T, C> for S {
+ type AssocType = <Base as Trait<'a, T, C>>::AssocType;
+
+ const AssocConst: usize = <Base as Trait<'a, T, C>>::AssocConst;
+
+ fn assoc_fn(p: ()) {
+ <Base as Trait<'a, T, C>>::assoc_fn(p)
+ }
+
+ fn assoc_method(&self, p: ()) {
+ <Base as Trait<'a, T, C>>::assoc_method( &self.field , p)
+ }
+}
+
+impl<'a, T, const C: usize> Trait<'a, T, C> for Base {
+ type AssocType = ();
+ const AssocConst: usize = 0;
+ fn assoc_fn(p: ()) {}
+ fn assoc_method(&self, p: ()) {}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_complex_two() {
+ check_assist(
+ generate_delegate_trait,
+ r"
+trait AnotherTrait {}
+
+trait Trait<'a, T, const C: usize> {
+ type AssocType;
+ const AssocConst: usize;
+ fn assoc_fn(p: ());
+ fn assoc_method(&self, p: ());
+}
+
+struct Base;
+struct S {
+ fi$0eld: Base,
+}
+
+impl<'b, C, const D: usize> Trait<'b, C, D> for Base
+where
+ C: AnotherTrait,
+{
+ type AssocType = ();
+ const AssocConst: usize = 0;
+ fn assoc_fn(p: ()) {}
+ fn assoc_method(&self, p: ()) {}
+}",
+ r#"
+trait AnotherTrait {}
+
+trait Trait<'a, T, const C: usize> {
+ type AssocType;
+ const AssocConst: usize;
+ fn assoc_fn(p: ());
+ fn assoc_method(&self, p: ());
+}
+
+struct Base;
+struct S {
+ field: Base,
+}
+
+impl<'b, C, const D: usize> Trait<'b, C, D> for S
+where
+ C: AnotherTrait,
+{
+ type AssocType = <Base as Trait<'b, C, D>>::AssocType;
+
+ const AssocConst: usize = <Base as Trait<'b, C, D>>::AssocConst;
+
+ fn assoc_fn(p: ()) {
+ <Base as Trait<'b, C, D>>::assoc_fn(p)
+ }
+
+ fn assoc_method(&self, p: ()) {
+ <Base as Trait<'b, C, D>>::assoc_method( &self.field , p)
+ }
+}
+
+impl<'b, C, const D: usize> Trait<'b, C, D> for Base
+where
+ C: AnotherTrait,
+{
+ type AssocType = ();
+ const AssocConst: usize = 0;
+ fn assoc_fn(p: ()) {}
+ fn assoc_method(&self, p: ()) {}
+}"#,
+ )
+ }
+
+ #[test]
+ fn test_complex_three() {
+ check_assist(
+ generate_delegate_trait,
+ r#"
+trait AnotherTrait {}
+trait YetAnotherTrait {}
+
+struct StructImplsAll();
+impl AnotherTrait for StructImplsAll {}
+impl YetAnotherTrait for StructImplsAll {}
+
+trait Trait<'a, T, const C: usize> {
+ type A;
+ const ASSOC_CONST: usize = C;
+ fn assoc_fn(p: ());
+ fn assoc_method(&self, p: ());
+}
+
+struct Base;
+struct S {
+ fi$0eld: Base,
+}
+
+impl<'b, A: AnotherTrait + YetAnotherTrait, const B: usize> Trait<'b, A, B> for Base
+where
+ A: AnotherTrait,
+{
+ type A = i32;
+
+ const ASSOC_CONST: usize = B;
+
+ fn assoc_fn(p: ()) {}
+
+ fn assoc_method(&self, p: ()) {}
+}
+"#,
+ r#"
+trait AnotherTrait {}
+trait YetAnotherTrait {}
+
+struct StructImplsAll();
+impl AnotherTrait for StructImplsAll {}
+impl YetAnotherTrait for StructImplsAll {}
+
+trait Trait<'a, T, const C: usize> {
+ type A;
+ const ASSOC_CONST: usize = C;
+ fn assoc_fn(p: ());
+ fn assoc_method(&self, p: ());
+}
+
+struct Base;
+struct S {
+ field: Base,
+}
+
+impl<'b, A: AnotherTrait + YetAnotherTrait, const B: usize> Trait<'b, A, B> for S
+where
+ A: AnotherTrait,
+{
+ type A = <Base as Trait<'b, A, B>>::A;
+
+ const ASSOC_CONST: usize = <Base as Trait<'b, A, B>>::ASSOC_CONST;
+
+ fn assoc_fn(p: ()) {
+ <Base as Trait<'b, A, B>>::assoc_fn(p)
+ }
+
+ fn assoc_method(&self, p: ()) {
+ <Base as Trait<'b, A, B>>::assoc_method( &self.field , p)
+ }
+}
+
+impl<'b, A: AnotherTrait + YetAnotherTrait, const B: usize> Trait<'b, A, B> for Base
+where
+ A: AnotherTrait,
+{
+ type A = i32;
+
+ const ASSOC_CONST: usize = B;
+
+ fn assoc_fn(p: ()) {}
+
+ fn assoc_method(&self, p: ()) {}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn test_type_bound() {
+ check_assist(
+ generate_delegate_trait,
+ r#"
+trait AnotherTrait {}
+struct S<T>
+where
+ T: AnotherTrait,
+{
+ b$0: T,
+}"#,
+ r#"
+trait AnotherTrait {}
+struct S<T>
+where
+ T: AnotherTrait,
+{
+ b: T,
+}
+
+impl<T> AnotherTrait for S<T>
+where
+ T: AnotherTrait,
+{}"#,
+ );
+ }
+
+ #[test]
+ fn test_docstring_example() {
+ check_assist(
+ generate_delegate_trait,
+ r#"
+trait SomeTrait {
+ type T;
+ fn fn_(arg: u32) -> u32;
+ fn method_(&mut self) -> bool;
+}
+struct A;
+impl SomeTrait for A {
+ type T = u32;
+ fn fn_(arg: u32) -> u32 {
+ 42
+ }
+ fn method_(&mut self) -> bool {
+ false
+ }
+}
+struct B {
+ a$0: A,
+}
+"#,
+ r#"
+trait SomeTrait {
+ type T;
+ fn fn_(arg: u32) -> u32;
+ fn method_(&mut self) -> bool;
+}
+struct A;
+impl SomeTrait for A {
+ type T = u32;
+ fn fn_(arg: u32) -> u32 {
+ 42
+ }
+ fn method_(&mut self) -> bool {
+ false
+ }
+}
+struct B {
+ a: A,
+}
+
+impl SomeTrait for B {
+ type T = <A as SomeTrait>::T;
+
+ fn fn_(arg: u32) -> u32 {
+ <A as SomeTrait>::fn_(arg)
+ }
+
+ fn method_(&mut self) -> bool {
+ <A as SomeTrait>::method_( &mut self.a )
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn import_from_other_mod() {
+ check_assist(
+ generate_delegate_trait,
+ r#"
+mod some_module {
+ pub trait SomeTrait {
+ type T;
+ fn fn_(arg: u32) -> u32;
+ fn method_(&mut self) -> bool;
+ }
+ pub struct A;
+ impl SomeTrait for A {
+ type T = u32;
+
+ fn fn_(arg: u32) -> u32 {
+ 42
+ }
+
+ fn method_(&mut self) -> bool {
+ false
+ }
+ }
+}
+
+struct B {
+ a$0: some_module::A,
+}"#,
+ r#"
+mod some_module {
+ pub trait SomeTrait {
+ type T;
+ fn fn_(arg: u32) -> u32;
+ fn method_(&mut self) -> bool;
+ }
+ pub struct A;
+ impl SomeTrait for A {
+ type T = u32;
+
+ fn fn_(arg: u32) -> u32 {
+ 42
+ }
+
+ fn method_(&mut self) -> bool {
+ false
+ }
+ }
+}
+
+struct B {
+ a: some_module::A,
+}
+
+impl some_module::SomeTrait for B {
+ type T = <some_module::A as some_module::SomeTrait>::T;
+
+ fn fn_(arg: u32) -> u32 {
+ <some_module::A as some_module::SomeTrait>::fn_(arg)
+ }
+
+ fn method_(&mut self) -> bool {
+ <some_module::A as some_module::SomeTrait>::method_( &mut self.a )
+ }
+}"#,
+ )
+ }
+}
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
index 78ac2eb30..747f70f9f 100644
--- 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
@@ -1,7 +1,6 @@
use syntax::{
- ast::{self, edit::IndentLevel, AstNode, HasAttrs},
- SyntaxKind::{COMMENT, WHITESPACE},
- TextSize,
+ ast::{self, edit_in_place::AttrsOwnerEdit, make, AstNode, HasAttrs},
+ T,
};
use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -27,48 +26,37 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
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 => {
- let indent_level = IndentLevel::from_node(nominal.syntax());
- builder.insert_snippet(
- cap,
- node_start,
- format!("#[derive($0)]\n{indent_level}"),
- );
- }
- Some(tt) => {
- // Just move the cursor.
- builder.insert_snippet(
- cap,
- tt.syntax().text_range().end() - TextSize::of(')'),
- "$0",
- )
- }
- };
- },
- )
-}
+ acc.add(AssistId("generate_derive", AssistKind::Generate), "Add `#[derive]`", target, |edit| {
+ 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 => {
+ let derive = make::attr_outer(make::meta_token_tree(
+ make::ext::ident_path("derive"),
+ make::token_tree(T!['('], vec![]).clone_for_update(),
+ ))
+ .clone_for_update();
+
+ let nominal = edit.make_mut(nominal);
+ nominal.add_attr(derive.clone());
-// 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())
+ edit.add_tabstop_before_token(
+ cap,
+ derive.meta().unwrap().token_tree().unwrap().r_paren_token().unwrap(),
+ );
+ }
+ Some(tt) => {
+ // Just move the cursor.
+ let tt = edit.make_mut(tt);
+ edit.add_tabstop_before_token(cap, tt.right_delimiter_token().unwrap());
+ }
+ };
+ })
}
#[cfg(test)]
@@ -115,6 +103,38 @@ mod m {
}
#[test]
+ fn add_derive_existing_with_brackets() {
+ check_assist(
+ generate_derive,
+ "
+#[derive[Clone]]
+struct Foo { a: i32$0, }
+",
+ "
+#[derive[Clone$0]]
+struct Foo { a: i32, }
+",
+ );
+ }
+
+ #[test]
+ fn add_derive_existing_missing_delimiter() {
+ // since `#[derive]` isn't a simple attr call (i.e. `#[derive()]`)
+ // we don't consider it as a proper derive attr and generate a new
+ // one instead
+ check_assist(
+ generate_derive,
+ "
+#[derive]
+struct Foo { a: i32$0, }",
+ "
+#[derive]
+#[derive($0)]
+struct Foo { a: i32, }",
+ );
+ }
+
+ #[test]
fn add_derive_new_with_doc_comment() {
check_assist(
generate_derive,
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
index c579f6780..5b13e01b1 100644
--- 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
@@ -623,7 +623,9 @@ fn fn_generic_params(
fn params_and_where_preds_in_scope(
ctx: &AssistContext<'_>,
) -> (Vec<ast::GenericParam>, Vec<ast::WherePred>) {
- let Some(body) = containing_body(ctx) else { return Default::default(); };
+ let Some(body) = containing_body(ctx) else {
+ return Default::default();
+ };
let mut generic_params = Vec::new();
let mut where_clauses = Vec::new();
@@ -1876,7 +1878,6 @@ where
#[test]
fn add_function_with_fn_arg() {
- // FIXME: The argument in `bar` is wrong.
check_assist(
generate_function,
r"
@@ -1897,7 +1898,7 @@ fn foo() {
bar(Baz::new);
}
-fn bar(new: fn) ${0:-> _} {
+fn bar(new: fn() -> Baz) ${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_or_setter.rs
index dd6bbd84a..9c9478b04 100644
--- 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_or_setter.rs
@@ -1,4 +1,4 @@
-use ide_db::famous_defs::FamousDefs;
+use ide_db::{famous_defs::FamousDefs, source_change::SourceChangeBuilder};
use stdx::{format_to, to_lower_snake_case};
use syntax::{
ast::{self, AstNode, HasName, HasVisibility},
@@ -10,6 +10,66 @@ use crate::{
AssistContext, AssistId, AssistKind, Assists, GroupLabel,
};
+// Assist: generate_setter
+//
+// Generate a setter method.
+//
+// ```
+// struct Person {
+// nam$0e: String,
+// }
+// ```
+// ->
+// ```
+// struct Person {
+// name: String,
+// }
+//
+// impl Person {
+// fn $0set_name(&mut self, name: String) {
+// self.name = name;
+// }
+// }
+// ```
+pub(crate) fn generate_setter(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ // This if condition denotes two modes this assist can work in:
+ // - First is acting upon selection of record fields
+ // - Next is acting upon a single record field
+ //
+ // This is the only part where implementation diverges a bit,
+ // subsequent code is generic for both of these modes
+
+ let (strukt, info_of_record_fields, mut fn_names) = extract_and_parse(ctx, AssistType::Set)?;
+
+ // No record fields to do work on :(
+ if info_of_record_fields.len() == 0 {
+ return None;
+ }
+
+ // Prepend set_ to fn names.
+ fn_names.iter_mut().for_each(|name| *name = format!("set_{}", name));
+
+ // Return early if we've found an existing fn
+ let impl_def = find_struct_impl(ctx, &ast::Adt::Struct(strukt.clone()), &fn_names)?;
+
+ // Computing collective text range of all record fields in selected region
+ let target: TextRange = info_of_record_fields
+ .iter()
+ .map(|record_field_info| record_field_info.target)
+ .reduce(|acc, target| acc.cover(target))?;
+
+ let setter_info = AssistInfo { impl_def, strukt, assist_type: AssistType::Set };
+
+ acc.add_group(
+ &GroupLabel("Generate getter/setter".to_owned()),
+ AssistId("generate_setter", AssistKind::Generate),
+ "Generate a setter method",
+ target,
+ |builder| build_source_change(builder, ctx, info_of_record_fields, setter_info),
+ );
+ Some(())
+}
+
// Assist: generate_getter
//
// Generate a getter method.
@@ -83,10 +143,16 @@ struct RecordFieldInfo {
target: TextRange,
}
-struct GetterInfo {
+struct AssistInfo {
impl_def: Option<ast::Impl>,
strukt: ast::Struct,
- mutable: bool,
+ assist_type: AssistType,
+}
+
+enum AssistType {
+ Get,
+ MutGet,
+ Set,
}
pub(crate) fn generate_getter_impl(
@@ -94,40 +160,8 @@ pub(crate) fn generate_getter_impl(
ctx: &AssistContext<'_>,
mutable: bool,
) -> Option<()> {
- // This if condition denotes two modes this assist can work in:
- // - First is acting upon selection of record fields
- // - Next is acting upon a single record field
- //
- // This is the only part where implementation diverges a bit,
- // subsequent code is generic for both of these modes
-
- let (strukt, info_of_record_fields, fn_names) = if !ctx.has_empty_selection() {
- // Selection Mode
- let node = ctx.covering_element();
-
- let node = match node {
- syntax::NodeOrToken::Node(n) => n,
- syntax::NodeOrToken::Token(t) => t.parent()?,
- };
-
- let parent_struct = node.ancestors().find_map(ast::Struct::cast)?;
-
- let (info_of_record_fields, field_names) =
- extract_and_parse_record_fields(&parent_struct, ctx.selection_trimmed(), mutable)?;
-
- (parent_struct, info_of_record_fields, field_names)
- } else {
- // Single Record Field mode
- let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
- let field = ctx.find_node_at_offset::<ast::RecordField>()?;
-
- let record_field_info = parse_record_field(field, mutable)?;
-
- let fn_name = record_field_info.fn_name.clone();
-
- (strukt, vec![record_field_info], vec![fn_name])
- };
-
+ let (strukt, info_of_record_fields, fn_names) =
+ extract_and_parse(ctx, if mutable { AssistType::MutGet } else { AssistType::Get })?;
// No record fields to do work on :(
if info_of_record_fields.len() == 0 {
return None;
@@ -147,98 +181,30 @@ pub(crate) fn generate_getter_impl(
.map(|record_field_info| record_field_info.target)
.reduce(|acc, target| acc.cover(target))?;
- let getter_info = GetterInfo { impl_def, strukt, mutable };
+ let getter_info = AssistInfo {
+ impl_def,
+ strukt,
+ assist_type: if mutable { AssistType::MutGet } else { AssistType::Get },
+ };
acc.add_group(
&GroupLabel("Generate getter/setter".to_owned()),
AssistId(id, AssistKind::Generate),
label,
target,
- |builder| {
- let record_fields_count = info_of_record_fields.len();
-
- let mut buf = String::with_capacity(512);
-
- // Check if an impl exists
- if let Some(impl_def) = &getter_info.impl_def {
- // Check if impl is empty
- if let Some(assoc_item_list) = impl_def.assoc_item_list() {
- if assoc_item_list.assoc_items().next().is_some() {
- // If not empty then only insert a new line
- buf.push('\n');
- }
- }
- }
-
- for (i, record_field_info) in info_of_record_fields.iter().enumerate() {
- // this buf inserts a newline at the end of a getter
- // automatically, if one wants to add one more newline
- // for separating it from other assoc items, that needs
- // to be handled separately
- let mut getter_buf =
- generate_getter_from_info(ctx, &getter_info, record_field_info);
-
- // Insert `$0` only for last getter we generate
- if i == record_fields_count - 1 {
- if ctx.config.snippet_cap.is_some() {
- getter_buf = getter_buf.replacen("fn ", "fn $0", 1);
- }
- }
-
- // For first element we do not merge with '\n', as
- // that can be inserted by impl_def check defined
- // above, for other cases which are:
- //
- // - impl exists but it empty, here we would ideally
- // not want to keep newline between impl <struct> {
- // and fn <fn-name>() { line
- //
- // - next if impl itself does not exist, in this
- // case we ourselves generate a new impl and that
- // again ends up with the same reasoning as above
- // for not keeping newline
- if i == 0 {
- buf = buf + &getter_buf;
- } else {
- buf = buf + "\n" + &getter_buf;
- }
-
- // We don't insert a new line at the end of
- // last getter as it will end up in the end
- // of an impl where we would not like to keep
- // getter and end of impl ( i.e. `}` ) with an
- // extra line for no reason
- if i < record_fields_count - 1 {
- buf = buf + "\n";
- }
- }
-
- let start_offset = getter_info
- .impl_def
- .as_ref()
- .and_then(|impl_def| find_impl_block_end(impl_def.to_owned(), &mut buf))
- .unwrap_or_else(|| {
- buf = generate_impl_text(&ast::Adt::Struct(getter_info.strukt.clone()), &buf);
- getter_info.strukt.syntax().text_range().end()
- });
-
- match ctx.config.snippet_cap {
- Some(cap) => builder.insert_snippet(cap, start_offset, buf),
- None => builder.insert(start_offset, buf),
- }
- },
+ |builder| build_source_change(builder, ctx, info_of_record_fields, getter_info),
)
}
fn generate_getter_from_info(
ctx: &AssistContext<'_>,
- info: &GetterInfo,
+ info: &AssistInfo,
record_field_info: &RecordFieldInfo,
) -> String {
let mut buf = String::with_capacity(512);
let vis = info.strukt.visibility().map_or(String::new(), |v| format!("{v} "));
- let (ty, body) = if info.mutable {
+ let (ty, body) = if matches!(info.assist_type, AssistType::MutGet) {
(
format!("&mut {}", record_field_info.field_ty),
format!("&mut self.{}", record_field_info.field_name),
@@ -273,7 +239,7 @@ fn generate_getter_from_info(
}}",
vis,
record_field_info.fn_name,
- info.mutable.then_some("mut ").unwrap_or_default(),
+ matches!(info.assist_type, AssistType::MutGet).then_some("mut ").unwrap_or_default(),
ty,
body,
);
@@ -281,10 +247,58 @@ fn generate_getter_from_info(
buf
}
+fn generate_setter_from_info(info: &AssistInfo, record_field_info: &RecordFieldInfo) -> String {
+ let mut buf = String::with_capacity(512);
+ let strukt = &info.strukt;
+ let fn_name = &record_field_info.fn_name;
+ let field_ty = &record_field_info.field_ty;
+ let vis = strukt.visibility().map_or(String::new(), |v| format!("{v} "));
+ format_to!(
+ buf,
+ " {vis}fn set_{fn_name}(&mut self, {fn_name}: {field_ty}) {{
+ self.{fn_name} = {fn_name};
+ }}"
+ );
+
+ buf
+}
+
+fn extract_and_parse(
+ ctx: &AssistContext<'_>,
+ assist_type: AssistType,
+) -> Option<(ast::Struct, Vec<RecordFieldInfo>, Vec<String>)> {
+ // This if condition denotes two modes assists can work in:
+ // - First is acting upon selection of record fields
+ // - Next is acting upon a single record field
+ if !ctx.has_empty_selection() {
+ // Selection Mode
+ let node = ctx.covering_element();
+
+ let node = match node {
+ syntax::NodeOrToken::Node(n) => n,
+ syntax::NodeOrToken::Token(t) => t.parent()?,
+ };
+
+ let parent_struct = node.ancestors().find_map(ast::Struct::cast)?;
+
+ let (info_of_record_fields, field_names) =
+ extract_and_parse_record_fields(&parent_struct, ctx.selection_trimmed(), &assist_type)?;
+
+ return Some((parent_struct, info_of_record_fields, field_names));
+ }
+
+ // Single Record Field mode
+ let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
+ let field = ctx.find_node_at_offset::<ast::RecordField>()?;
+ let record_field_info = parse_record_field(field, &assist_type)?;
+ let fn_name = record_field_info.fn_name.clone();
+ Some((strukt, vec![record_field_info], vec![fn_name]))
+}
+
fn extract_and_parse_record_fields(
node: &ast::Struct,
selection_range: TextRange,
- mutable: bool,
+ assist_type: &AssistType,
) -> Option<(Vec<RecordFieldInfo>, Vec<String>)> {
let mut field_names: Vec<String> = vec![];
let field_list = node.field_list()?;
@@ -295,7 +309,7 @@ fn extract_and_parse_record_fields(
.fields()
.filter_map(|record_field| {
if selection_range.contains_range(record_field.syntax().text_range()) {
- let record_field_info = parse_record_field(record_field, mutable)?;
+ let record_field_info = parse_record_field(record_field, assist_type)?;
field_names.push(record_field_info.fn_name.clone());
return Some(record_field_info);
}
@@ -316,12 +330,15 @@ fn extract_and_parse_record_fields(
}
}
-fn parse_record_field(record_field: ast::RecordField, mutable: bool) -> Option<RecordFieldInfo> {
+fn parse_record_field(
+ record_field: ast::RecordField,
+ assist_type: &AssistType,
+) -> Option<RecordFieldInfo> {
let field_name = record_field.name()?;
let field_ty = record_field.ty()?;
let mut fn_name = to_lower_snake_case(&field_name.to_string());
- if mutable {
+ if matches!(assist_type, AssistType::MutGet) {
format_to!(fn_name, "_mut");
}
@@ -330,8 +347,89 @@ fn parse_record_field(record_field: ast::RecordField, mutable: bool) -> Option<R
Some(RecordFieldInfo { field_name, field_ty, fn_name, target })
}
+fn build_source_change(
+ builder: &mut SourceChangeBuilder,
+ ctx: &AssistContext<'_>,
+ info_of_record_fields: Vec<RecordFieldInfo>,
+ assist_info: AssistInfo,
+) {
+ let record_fields_count = info_of_record_fields.len();
+
+ let mut buf = String::with_capacity(512);
+
+ // Check if an impl exists
+ if let Some(impl_def) = &assist_info.impl_def {
+ // Check if impl is empty
+ if let Some(assoc_item_list) = impl_def.assoc_item_list() {
+ if assoc_item_list.assoc_items().next().is_some() {
+ // If not empty then only insert a new line
+ buf.push('\n');
+ }
+ }
+ }
+
+ for (i, record_field_info) in info_of_record_fields.iter().enumerate() {
+ // this buf inserts a newline at the end of a getter
+ // automatically, if one wants to add one more newline
+ // for separating it from other assoc items, that needs
+ // to be handled separately
+ let mut getter_buf = match assist_info.assist_type {
+ AssistType::Set => generate_setter_from_info(&assist_info, record_field_info),
+ _ => generate_getter_from_info(ctx, &assist_info, record_field_info),
+ };
+
+ // Insert `$0` only for last getter we generate
+ if i == record_fields_count - 1 {
+ if ctx.config.snippet_cap.is_some() {
+ getter_buf = getter_buf.replacen("fn ", "fn $0", 1);
+ }
+ }
+
+ // For first element we do not merge with '\n', as
+ // that can be inserted by impl_def check defined
+ // above, for other cases which are:
+ //
+ // - impl exists but it empty, here we would ideally
+ // not want to keep newline between impl <struct> {
+ // and fn <fn-name>() { line
+ //
+ // - next if impl itself does not exist, in this
+ // case we ourselves generate a new impl and that
+ // again ends up with the same reasoning as above
+ // for not keeping newline
+ if i == 0 {
+ buf = buf + &getter_buf;
+ } else {
+ buf = buf + "\n" + &getter_buf;
+ }
+
+ // We don't insert a new line at the end of
+ // last getter as it will end up in the end
+ // of an impl where we would not like to keep
+ // getter and end of impl ( i.e. `}` ) with an
+ // extra line for no reason
+ if i < record_fields_count - 1 {
+ buf = buf + "\n";
+ }
+ }
+
+ let start_offset = assist_info
+ .impl_def
+ .as_ref()
+ .and_then(|impl_def| find_impl_block_end(impl_def.to_owned(), &mut buf))
+ .unwrap_or_else(|| {
+ buf = generate_impl_text(&ast::Adt::Struct(assist_info.strukt.clone()), &buf);
+ assist_info.strukt.syntax().text_range().end()
+ });
+
+ match ctx.config.snippet_cap {
+ Some(cap) => builder.insert_snippet(cap, start_offset, buf),
+ None => builder.insert(start_offset, buf),
+ }
+}
+
#[cfg(test)]
-mod tests {
+mod tests_getter {
use crate::tests::{check_assist, check_assist_no_snippet_cap, check_assist_not_applicable};
use super::*;
@@ -812,3 +910,105 @@ impl Context {
);
}
}
+
+#[cfg(test)]
+mod tests_setter {
+ 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 $0set_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 $0set_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 $0set_count(&mut self, count: usize) {
+ self.count = count;
+ }
+}"#,
+ );
+ }
+}
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
deleted file mode 100644
index 62f72df1c..000000000
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_setter.rs
+++ /dev/null
@@ -1,175 +0,0 @@
-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}")])?;
-
- 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,
- " {vis}fn set_{fn_name}(&mut self, {fn_name}: {field_ty}) {{
- self.{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/generate_trait_from_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs
new file mode 100644
index 000000000..0f67380d1
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs
@@ -0,0 +1,429 @@
+use crate::assist_context::{AssistContext, Assists};
+use ide_db::assists::AssistId;
+use syntax::{
+ ast::{self, edit::IndentLevel, make, HasGenericParams, HasVisibility},
+ ted, AstNode, SyntaxKind,
+};
+
+// NOTES :
+// We generate erroneous code if a function is declared const (E0379)
+// This is left to the user to correct as our only option is to remove the
+// function completely which we should not be doing.
+
+// Assist: generate_trait_from_impl
+//
+// Generate trait for an already defined inherent impl and convert impl to a trait impl.
+//
+// ```
+// struct Foo<const N: usize>([i32; N]);
+//
+// macro_rules! const_maker {
+// ($t:ty, $v:tt) => {
+// const CONST: $t = $v;
+// };
+// }
+//
+// impl<const N: usize> Fo$0o<N> {
+// // Used as an associated constant.
+// const CONST_ASSOC: usize = N * 4;
+//
+// fn create() -> Option<()> {
+// Some(())
+// }
+//
+// const_maker! {i32, 7}
+// }
+// ```
+// ->
+// ```
+// struct Foo<const N: usize>([i32; N]);
+//
+// macro_rules! const_maker {
+// ($t:ty, $v:tt) => {
+// const CONST: $t = $v;
+// };
+// }
+//
+// trait ${0:TraitName}<const N: usize> {
+// // Used as an associated constant.
+// const CONST_ASSOC: usize = N * 4;
+//
+// fn create() -> Option<()>;
+//
+// const_maker! {i32, 7}
+// }
+//
+// impl<const N: usize> ${0:TraitName}<N> for Foo<N> {
+// // Used as an associated constant.
+// const CONST_ASSOC: usize = N * 4;
+//
+// fn create() -> Option<()> {
+// Some(())
+// }
+//
+// const_maker! {i32, 7}
+// }
+// ```
+pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ // Get AST Node
+ let impl_ast = ctx.find_node_at_offset::<ast::Impl>()?;
+
+ // Check if cursor is to the left of assoc item list's L_CURLY.
+ // if no L_CURLY then return.
+ let l_curly = impl_ast.assoc_item_list()?.l_curly_token()?;
+
+ let cursor_offset = ctx.offset();
+ let l_curly_offset = l_curly.text_range();
+ if cursor_offset >= l_curly_offset.start() {
+ return None;
+ }
+
+ // If impl is not inherent then we don't really need to go any further.
+ if impl_ast.for_token().is_some() {
+ return None;
+ }
+
+ let assoc_items = impl_ast.assoc_item_list()?;
+ let first_element = assoc_items.assoc_items().next();
+ if first_element.is_none() {
+ // No reason for an assist.
+ return None;
+ }
+
+ let impl_name = impl_ast.self_ty()?;
+
+ acc.add(
+ AssistId("generate_trait_from_impl", ide_db::assists::AssistKind::Generate),
+ "Generate trait from impl",
+ impl_ast.syntax().text_range(),
+ |builder| {
+ let trait_items = assoc_items.clone_for_update();
+ let impl_items = assoc_items.clone_for_update();
+
+ trait_items.assoc_items().for_each(|item| {
+ strip_body(&item);
+ remove_items_visibility(&item);
+ });
+
+ impl_items.assoc_items().for_each(|item| {
+ remove_items_visibility(&item);
+ });
+
+ let trait_ast = make::trait_(
+ false,
+ "NewTrait",
+ impl_ast.generic_param_list(),
+ impl_ast.where_clause(),
+ trait_items,
+ );
+
+ // Change `impl Foo` to `impl NewTrait for Foo`
+ let arg_list = if let Some(genpars) = impl_ast.generic_param_list() {
+ genpars.to_generic_args().to_string()
+ } else {
+ "".to_string()
+ };
+
+ if let Some(snippet_cap) = ctx.config.snippet_cap {
+ builder.replace_snippet(
+ snippet_cap,
+ impl_name.syntax().text_range(),
+ format!("${{0:TraitName}}{} for {}", arg_list, impl_name.to_string()),
+ );
+
+ // Insert trait before TraitImpl
+ builder.insert_snippet(
+ snippet_cap,
+ impl_ast.syntax().text_range().start(),
+ format!(
+ "{}\n\n{}",
+ trait_ast.to_string().replace("NewTrait", "${0:TraitName}"),
+ IndentLevel::from_node(impl_ast.syntax())
+ ),
+ );
+ } else {
+ builder.replace(
+ impl_name.syntax().text_range(),
+ format!("NewTrait{} for {}", arg_list, impl_name.to_string()),
+ );
+
+ // Insert trait before TraitImpl
+ builder.insert(
+ impl_ast.syntax().text_range().start(),
+ format!(
+ "{}\n\n{}",
+ trait_ast.to_string(),
+ IndentLevel::from_node(impl_ast.syntax())
+ ),
+ );
+ }
+
+ builder.replace(assoc_items.syntax().text_range(), impl_items.to_string());
+ },
+ );
+
+ Some(())
+}
+
+/// `E0449` Trait items always share the visibility of their trait
+fn remove_items_visibility(item: &ast::AssocItem) {
+ match item {
+ ast::AssocItem::Const(c) => {
+ if let Some(vis) = c.visibility() {
+ ted::remove(vis.syntax());
+ }
+ }
+ ast::AssocItem::Fn(f) => {
+ if let Some(vis) = f.visibility() {
+ ted::remove(vis.syntax());
+ }
+ }
+ ast::AssocItem::TypeAlias(t) => {
+ if let Some(vis) = t.visibility() {
+ ted::remove(vis.syntax());
+ }
+ }
+ _ => (),
+ }
+}
+
+fn strip_body(item: &ast::AssocItem) {
+ match item {
+ ast::AssocItem::Fn(f) => {
+ if let Some(body) = f.body() {
+ // In constrast to function bodies, we want to see no ws before a semicolon.
+ // So let's remove them if we see any.
+ if let Some(prev) = body.syntax().prev_sibling_or_token() {
+ if prev.kind() == SyntaxKind::WHITESPACE {
+ ted::remove(prev);
+ }
+ }
+
+ ted::replace(body.syntax(), make::tokens::semicolon());
+ }
+ }
+ _ => (),
+ };
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::tests::{check_assist, check_assist_no_snippet_cap, check_assist_not_applicable};
+
+ #[test]
+ fn test_trigger_when_cursor_on_header() {
+ check_assist_not_applicable(
+ generate_trait_from_impl,
+ r#"
+struct Foo(f64);
+
+impl Foo { $0
+ fn add(&mut self, x: f64) {
+ self.0 += x;
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn test_assoc_item_fn() {
+ check_assist_no_snippet_cap(
+ generate_trait_from_impl,
+ r#"
+struct Foo(f64);
+
+impl F$0oo {
+ fn add(&mut self, x: f64) {
+ self.0 += x;
+ }
+}"#,
+ r#"
+struct Foo(f64);
+
+trait NewTrait {
+ fn add(&mut self, x: f64);
+}
+
+impl NewTrait for Foo {
+ fn add(&mut self, x: f64) {
+ self.0 += x;
+ }
+}"#,
+ )
+ }
+
+ #[test]
+ fn test_assoc_item_macro() {
+ check_assist_no_snippet_cap(
+ generate_trait_from_impl,
+ r#"
+struct Foo;
+
+macro_rules! const_maker {
+ ($t:ty, $v:tt) => {
+ const CONST: $t = $v;
+ };
+}
+
+impl F$0oo {
+ const_maker! {i32, 7}
+}"#,
+ r#"
+struct Foo;
+
+macro_rules! const_maker {
+ ($t:ty, $v:tt) => {
+ const CONST: $t = $v;
+ };
+}
+
+trait NewTrait {
+ const_maker! {i32, 7}
+}
+
+impl NewTrait for Foo {
+ const_maker! {i32, 7}
+}"#,
+ )
+ }
+
+ #[test]
+ fn test_assoc_item_const() {
+ check_assist_no_snippet_cap(
+ generate_trait_from_impl,
+ r#"
+struct Foo;
+
+impl F$0oo {
+ const ABC: i32 = 3;
+}"#,
+ r#"
+struct Foo;
+
+trait NewTrait {
+ const ABC: i32 = 3;
+}
+
+impl NewTrait for Foo {
+ const ABC: i32 = 3;
+}"#,
+ )
+ }
+
+ #[test]
+ fn test_impl_with_generics() {
+ check_assist_no_snippet_cap(
+ generate_trait_from_impl,
+ r#"
+struct Foo<const N: usize>([i32; N]);
+
+impl<const N: usize> F$0oo<N> {
+ // Used as an associated constant.
+ const CONST: usize = N * 4;
+}
+ "#,
+ r#"
+struct Foo<const N: usize>([i32; N]);
+
+trait NewTrait<const N: usize> {
+ // Used as an associated constant.
+ const CONST: usize = N * 4;
+}
+
+impl<const N: usize> NewTrait<N> for Foo<N> {
+ // Used as an associated constant.
+ const CONST: usize = N * 4;
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn test_trait_items_should_not_have_vis() {
+ check_assist_no_snippet_cap(
+ generate_trait_from_impl,
+ r#"
+struct Foo;
+
+impl F$0oo {
+ pub fn a_func() -> Option<()> {
+ Some(())
+ }
+}"#,
+ r#"
+struct Foo;
+
+trait NewTrait {
+ fn a_func() -> Option<()>;
+}
+
+impl NewTrait for Foo {
+ fn a_func() -> Option<()> {
+ Some(())
+ }
+}"#,
+ )
+ }
+
+ #[test]
+ fn test_empty_inherent_impl() {
+ check_assist_not_applicable(
+ generate_trait_from_impl,
+ r#"
+impl Emp$0tyImpl{}
+"#,
+ )
+ }
+
+ #[test]
+ fn test_not_top_level_impl() {
+ check_assist_no_snippet_cap(
+ generate_trait_from_impl,
+ r#"
+mod a {
+ impl S$0 {
+ fn foo() {}
+ }
+}"#,
+ r#"
+mod a {
+ trait NewTrait {
+ fn foo();
+ }
+
+ impl NewTrait for S {
+ fn foo() {}
+ }
+}"#,
+ )
+ }
+
+ #[test]
+ fn test_snippet_cap_is_some() {
+ check_assist(
+ generate_trait_from_impl,
+ r#"
+struct Foo<const N: usize>([i32; N]);
+
+impl<const N: usize> F$0oo<N> {
+ // Used as an associated constant.
+ const CONST: usize = N * 4;
+}
+ "#,
+ r#"
+struct Foo<const N: usize>([i32; N]);
+
+trait ${0:TraitName}<const N: usize> {
+ // Used as an associated constant.
+ const CONST: usize = N * 4;
+}
+
+impl<const N: usize> ${0:TraitName}<N> for Foo<N> {
+ // Used as an associated constant.
+ const CONST: usize = N * 4;
+}
+ "#,
+ )
+ }
+}
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
index 797180fa1..ffab58509 100644
--- 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
@@ -15,7 +15,7 @@ use ide_db::{
};
use itertools::{izip, Itertools};
use syntax::{
- ast::{self, edit_in_place::Indent, HasArgList, PathExpr},
+ ast::{self, edit::IndentLevel, edit_in_place::Indent, HasArgList, PathExpr},
ted, AstNode, NodeOrToken, SyntaxKind,
};
@@ -80,7 +80,7 @@ pub(crate) fn inline_into_callers(acc: &mut Assists, ctx: &AssistContext<'_>) ->
let is_recursive_fn = usages
.clone()
- .in_scope(SearchScope::file_range(FileRange {
+ .in_scope(&SearchScope::file_range(FileRange {
file_id: def_file,
range: func_body.syntax().text_range(),
}))
@@ -306,7 +306,7 @@ fn inline(
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() {
+ let mut 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
@@ -391,19 +391,19 @@ fn inline(
}
}
+ let mut let_stmts = Vec::new();
+
// 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() {
+ for ((pat, param_ty, _), usages, expr) in izip!(params, param_use_nodes, arguments) {
// 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;
- let insert_let_stmt = || {
+ let mut insert_let_stmt = || {
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(),
- )
- }
+ let_stmts.push(
+ make::let_stmt(pat.clone(), ty, Some(expr.clone())).clone_for_update().into(),
+ );
};
// check if there is a local var in the function that conflicts with parameter
@@ -457,6 +457,24 @@ fn inline(
}
}
+ let is_async_fn = function.is_async(sema.db);
+ if is_async_fn {
+ cov_mark::hit!(inline_call_async_fn);
+ body = make::async_move_block_expr(body.statements(), body.tail_expr()).clone_for_update();
+
+ // Arguments should be evaluated outside the async block, and then moved into it.
+ if !let_stmts.is_empty() {
+ cov_mark::hit!(inline_call_async_fn_with_let_stmts);
+ body.indent(IndentLevel(1));
+ body = make::block_expr(let_stmts, Some(body.into())).clone_for_update();
+ }
+ } else if let Some(stmt_list) = body.stmt_list() {
+ ted::insert_all(
+ ted::Position::after(stmt_list.l_curly_token().unwrap()),
+ let_stmts.into_iter().map(|stmt| stmt.syntax().clone().into()).collect(),
+ );
+ }
+
let original_indentation = match node {
ast::CallableExpr::Call(it) => it.indent_level(),
ast::CallableExpr::MethodCall(it) => it.indent_level(),
@@ -464,7 +482,7 @@ fn inline(
body.reindent_to(original_indentation);
match body.tail_expr() {
- Some(expr) if body.statements().next().is_none() => expr,
+ Some(expr) if !is_async_fn && body.statements().next().is_none() => expr,
_ => match node
.syntax()
.parent()
@@ -1353,4 +1371,107 @@ fn main() {
"#,
);
}
+
+ #[test]
+ fn async_fn_single_expression() {
+ cov_mark::check!(inline_call_async_fn);
+ check_assist(
+ inline_call,
+ r#"
+async fn bar(x: u32) -> u32 { x + 1 }
+async fn foo(arg: u32) -> u32 {
+ bar(arg).await * 2
+}
+fn spawn<T>(_: T) {}
+fn main() {
+ spawn(foo$0(42));
+}
+"#,
+ r#"
+async fn bar(x: u32) -> u32 { x + 1 }
+async fn foo(arg: u32) -> u32 {
+ bar(arg).await * 2
+}
+fn spawn<T>(_: T) {}
+fn main() {
+ spawn(async move {
+ bar(42).await * 2
+ });
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn async_fn_multiple_statements() {
+ cov_mark::check!(inline_call_async_fn);
+ check_assist(
+ inline_call,
+ r#"
+async fn bar(x: u32) -> u32 { x + 1 }
+async fn foo(arg: u32) -> u32 {
+ bar(arg).await;
+ 42
+}
+fn spawn<T>(_: T) {}
+fn main() {
+ spawn(foo$0(42));
+}
+"#,
+ r#"
+async fn bar(x: u32) -> u32 { x + 1 }
+async fn foo(arg: u32) -> u32 {
+ bar(arg).await;
+ 42
+}
+fn spawn<T>(_: T) {}
+fn main() {
+ spawn(async move {
+ bar(42).await;
+ 42
+ });
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn async_fn_with_let_statements() {
+ cov_mark::check!(inline_call_async_fn);
+ cov_mark::check!(inline_call_async_fn_with_let_stmts);
+ check_assist(
+ inline_call,
+ r#"
+async fn bar(x: u32) -> u32 { x + 1 }
+async fn foo(x: u32, y: u32, z: &u32) -> u32 {
+ bar(x).await;
+ y + y + *z
+}
+fn spawn<T>(_: T) {}
+fn main() {
+ let var = 42;
+ spawn(foo$0(var, var + 1, &var));
+}
+"#,
+ r#"
+async fn bar(x: u32) -> u32 { x + 1 }
+async fn foo(x: u32, y: u32, z: &u32) -> u32 {
+ bar(x).await;
+ y + y + *z
+}
+fn spawn<T>(_: T) {}
+fn main() {
+ let var = 42;
+ spawn({
+ let y = var + 1;
+ let z: &u32 = &var;
+ async move {
+ bar(var).await;
+ y + y + *z
+ }
+ });
+}
+"#,
+ );
+ }
}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_macro.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_macro.rs
index 5aa8e56f5..5d956b1a5 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_macro.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_macro.rs
@@ -37,11 +37,10 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
pub(crate) fn inline_macro(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
let unexpanded = ctx.find_node_at_offset::<ast::MacroCall>()?;
let expanded = insert_ws_into(ctx.sema.expand(&unexpanded)?.clone_for_update());
-
let text_range = unexpanded.syntax().text_range();
acc.add(
- AssistId("inline_macro", AssistKind::RefactorRewrite),
+ AssistId("inline_macro", AssistKind::RefactorInline),
format!("Inline macro"),
text_range,
|builder| builder.replace(text_range, expanded.to_string()),
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_const_to_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_const_to_impl.rs
index b6027eac5..22d536b5a 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_const_to_impl.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_const_to_impl.rs
@@ -54,7 +54,11 @@ pub(crate) fn move_const_to_impl(acc: &mut Assists, ctx: &AssistContext<'_>) ->
// NOTE: We can technically provide this assist for default methods in trait definitions, but
// it's somewhat complex to handle it correctly when the const's name conflicts with
// supertrait's item. We may want to consider implementing it in the future.
- let AssocItemContainer::Impl(impl_) = ctx.sema.to_def(&parent_fn)?.as_assoc_item(db)?.container(db) else { return None; };
+ let AssocItemContainer::Impl(impl_) =
+ ctx.sema.to_def(&parent_fn)?.as_assoc_item(db)?.container(db)
+ else {
+ return None;
+ };
if impl_.trait_(db).is_some() {
return None;
}
@@ -78,17 +82,19 @@ pub(crate) fn move_const_to_impl(acc: &mut Assists, ctx: &AssistContext<'_>) ->
return None;
}
- let usages =
- Definition::Const(def).usages(&ctx.sema).in_scope(SearchScope::file_range(FileRange {
- file_id: ctx.file_id(),
- range: parent_fn.syntax().text_range(),
- }));
-
acc.add(
AssistId("move_const_to_impl", crate::AssistKind::RefactorRewrite),
"Move const to impl block",
const_.syntax().text_range(),
|builder| {
+ let usages = Definition::Const(def)
+ .usages(&ctx.sema)
+ .in_scope(&SearchScope::file_range(FileRange {
+ file_id: ctx.file_id(),
+ range: parent_fn.syntax().text_range(),
+ }))
+ .all();
+
let range_to_delete = match const_.syntax().next_sibling_or_token() {
Some(s) if matches!(s.kind(), SyntaxKind::WHITESPACE) => {
// Remove following whitespaces too.
@@ -99,7 +105,7 @@ pub(crate) fn move_const_to_impl(acc: &mut Assists, ctx: &AssistContext<'_>) ->
builder.delete(range_to_delete);
let const_ref = format!("Self::{}", name.display(ctx.db()));
- for range in usages.all().file_ranges().map(|it| it.range) {
+ for range in usages.file_ranges().map(|it| it.range) {
builder.replace(range, const_ref.clone());
}
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
index 23153b4c5..5cc110cf1 100644
--- 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
@@ -8,13 +8,10 @@ use ide_db::{
use stdx::to_upper_snake_case;
use syntax::{
ast::{self, make, HasName},
- AstNode, WalkEvent,
+ ted, AstNode, WalkEvent,
};
-use crate::{
- assist_context::{AssistContext, Assists},
- utils::{render_snippet, Cursor},
-};
+use crate::assist_context::{AssistContext, Assists};
// Assist: promote_local_to_const
//
@@ -70,29 +67,33 @@ pub(crate) fn promote_local_to_const(acc: &mut Assists, ctx: &AssistContext<'_>)
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_stmt.syntax().text_range(),
+ |edit| {
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()) {
+ let name = make::name_ref(&name);
+
for usage in usages {
- builder.replace(usage.range, &name);
+ let Some(usage) = usage.name.as_name_ref().cloned() else { continue };
+ let usage = edit.make_mut(usage);
+ ted::replace(usage.syntax(), name.clone_for_update().syntax());
}
}
- 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()),
+ let item = make::item_const(None, make::name(&name), make::ty(&ty), initializer)
+ .clone_for_update();
+ let let_stmt = edit.make_mut(let_stmt);
+
+ if let Some((cap, name)) = ctx.config.snippet_cap.zip(item.name()) {
+ edit.add_tabstop_before(cap, name);
}
+
+ ted::replace(let_stmt.syntax(), item.syntax());
},
)
}
@@ -158,6 +159,27 @@ fn foo() {
}
#[test]
+ fn multiple_uses() {
+ check_assist(
+ promote_local_to_const,
+ r"
+fn foo() {
+ let x$0 = 0;
+ let y = x;
+ let z = (x, x, x, x);
+}
+",
+ r"
+fn foo() {
+ const $0X: i32 = 0;
+ let y = X;
+ let z = (X, X, X, X);
+}
+",
+ );
+ }
+
+ #[test]
fn not_applicable_non_const_meth_call() {
cov_mark::check!(promote_local_non_const);
check_assist_not_applicable(
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
index a5c7fea40..f222b3eb9 100644
--- 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
@@ -102,7 +102,7 @@ struct AssignmentsCollector<'a> {
assignments: Vec<(ast::BinExpr, ast::Expr)>,
}
-impl<'a> AssignmentsCollector<'a> {
+impl AssignmentsCollector<'_> {
fn collect_match(&mut self, match_expr: &ast::MatchExpr) -> Option<()> {
for arm in match_expr.match_arm_list()?.arms() {
match arm.expr()? {
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_unused_imports.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_unused_imports.rs
new file mode 100644
index 000000000..dd4839351
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_unused_imports.rs
@@ -0,0 +1,739 @@
+use std::collections::{hash_map::Entry, HashMap};
+
+use hir::{InFile, Module, ModuleSource};
+use ide_db::{
+ base_db::FileRange,
+ defs::Definition,
+ search::{FileReference, ReferenceCategory, SearchScope},
+ RootDatabase,
+};
+use syntax::{ast, AstNode};
+use text_edit::TextRange;
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: remove_unused_imports
+//
+// Removes any use statements in the current selection that are unused.
+//
+// ```
+// struct X();
+// mod foo {
+// use super::X$0;
+// }
+// ```
+// ->
+// ```
+// struct X();
+// mod foo {
+// }
+// ```
+pub(crate) fn remove_unused_imports(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ // First, grab the uses that intersect with the current selection.
+ let selected_el = match ctx.covering_element() {
+ syntax::NodeOrToken::Node(n) => n,
+ syntax::NodeOrToken::Token(t) => t.parent()?,
+ };
+
+ // This applies to all uses that are selected, or are ancestors of our selection.
+ let uses_up = selected_el.ancestors().skip(1).filter_map(ast::Use::cast);
+ let uses_down = selected_el
+ .descendants()
+ .filter(|x| x.text_range().intersect(ctx.selection_trimmed()).is_some())
+ .filter_map(ast::Use::cast);
+ let uses = uses_up.chain(uses_down).collect::<Vec<_>>();
+
+ // Maps use nodes to the scope that we should search through to find
+ let mut search_scopes = HashMap::<Module, Vec<SearchScope>>::new();
+
+ // iterator over all unused use trees
+ let mut unused = uses
+ .into_iter()
+ .flat_map(|u| u.syntax().descendants().filter_map(ast::UseTree::cast))
+ .filter(|u| u.use_tree_list().is_none())
+ .filter_map(|u| {
+ // Find any uses trees that are unused
+
+ let use_module = ctx.sema.scope(&u.syntax()).map(|s| s.module())?;
+ let scope = match search_scopes.entry(use_module) {
+ Entry::Occupied(o) => o.into_mut(),
+ Entry::Vacant(v) => v.insert(module_search_scope(ctx.db(), use_module)),
+ };
+
+ // Gets the path associated with this use tree. If there isn't one, then ignore this use tree.
+ let path = if let Some(path) = u.path() {
+ path
+ } else if u.star_token().is_some() {
+ // This case maps to the situation where the * token is braced.
+ // In this case, the parent use tree's path is the one we should use to resolve the glob.
+ match u.syntax().ancestors().skip(1).find_map(ast::UseTree::cast) {
+ Some(parent_u) if parent_u.path().is_some() => parent_u.path().unwrap(),
+ _ => return None,
+ }
+ } else {
+ return None;
+ };
+
+ // Get the actual definition associated with this use item.
+ let res = match ctx.sema.resolve_path(&path) {
+ Some(x) => x,
+ None => {
+ return None;
+ }
+ };
+
+ let def = match res {
+ hir::PathResolution::Def(d) => Definition::from(d),
+ _ => return None,
+ };
+
+ if u.star_token().is_some() {
+ // Check if any of the children of this module are used
+ let def_mod = match def {
+ Definition::Module(module) => module,
+ _ => return None,
+ };
+
+ if !def_mod
+ .scope(ctx.db(), Some(use_module))
+ .iter()
+ .filter_map(|(_, x)| match x {
+ hir::ScopeDef::ModuleDef(d) => Some(Definition::from(*d)),
+ _ => None,
+ })
+ .any(|d| used_once_in_scope(ctx, d, scope))
+ {
+ return Some(u);
+ }
+ } else if let Definition::Trait(ref t) = def {
+ // If the trait or any item is used.
+ if !std::iter::once(def)
+ .chain(t.items(ctx.db()).into_iter().map(Definition::from))
+ .any(|d| used_once_in_scope(ctx, d, scope))
+ {
+ return Some(u);
+ }
+ } else {
+ if !used_once_in_scope(ctx, def, &scope) {
+ return Some(u);
+ }
+ }
+
+ None
+ })
+ .peekable();
+
+ // Peek so we terminate early if an unused use is found. Only do the rest of the work if the user selects the assist.
+ if unused.peek().is_some() {
+ acc.add(
+ AssistId("remove_unused_imports", AssistKind::QuickFix),
+ "Remove all the unused imports",
+ selected_el.text_range(),
+ |builder| {
+ let unused: Vec<ast::UseTree> = unused.map(|x| builder.make_mut(x)).collect();
+ for node in unused {
+ node.remove_recursive();
+ }
+ },
+ )
+ } else {
+ None
+ }
+}
+
+fn used_once_in_scope(ctx: &AssistContext<'_>, def: Definition, scopes: &Vec<SearchScope>) -> bool {
+ let mut found = false;
+
+ for scope in scopes {
+ let mut search_non_import = |_, r: FileReference| {
+ // The import itself is a use; we must skip that.
+ if r.category != Some(ReferenceCategory::Import) {
+ found = true;
+ true
+ } else {
+ false
+ }
+ };
+ def.usages(&ctx.sema).in_scope(scope).search(&mut search_non_import);
+ if found {
+ break;
+ }
+ }
+
+ found
+}
+
+/// Build a search scope spanning the given module but none of its submodules.
+fn module_search_scope(db: &RootDatabase, module: hir::Module) -> Vec<SearchScope> {
+ 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()),
+ },
+ )
+ }
+ };
+
+ fn split_at_subrange(first: TextRange, second: TextRange) -> (TextRange, Option<TextRange>) {
+ let intersect = first.intersect(second);
+ if let Some(intersect) = intersect {
+ let start_range = TextRange::new(first.start(), intersect.start());
+
+ if intersect.end() < first.end() {
+ (start_range, Some(TextRange::new(intersect.end(), first.end())))
+ } else {
+ (start_range, None)
+ }
+ } else {
+ (first, None)
+ }
+ }
+
+ let mut scopes = Vec::new();
+ if let Some(range) = range {
+ let mut ranges = vec![range];
+
+ for child in module.children(db) {
+ let rng = match child.definition_source(db).value {
+ ModuleSource::SourceFile(_) => continue,
+ ModuleSource::Module(it) => it.syntax().text_range(),
+ ModuleSource::BlockExpr(_) => continue,
+ };
+ let mut new_ranges = Vec::new();
+ for old_range in ranges.iter_mut() {
+ let split = split_at_subrange(old_range.clone(), rng);
+ *old_range = split.0;
+ new_ranges.extend(split.1);
+ }
+
+ ranges.append(&mut new_ranges);
+ }
+
+ for range in ranges {
+ scopes.push(SearchScope::file_range(FileRange { file_id, range }));
+ }
+ } else {
+ scopes.push(SearchScope::single_file(file_id));
+ }
+
+ scopes
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn remove_unused() {
+ check_assist(
+ remove_unused_imports,
+ r#"
+struct X();
+struct Y();
+mod z {
+ $0use super::X;
+ use super::Y;$0
+}
+"#,
+ r#"
+struct X();
+struct Y();
+mod z {
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn remove_unused_is_precise() {
+ check_assist(
+ remove_unused_imports,
+ r#"
+struct X();
+mod z {
+$0use super::X;$0
+
+fn w() {
+ struct X();
+ let x = X();
+}
+}
+"#,
+ r#"
+struct X();
+mod z {
+
+fn w() {
+ struct X();
+ let x = X();
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn trait_name_use_is_use() {
+ check_assist_not_applicable(
+ remove_unused_imports,
+ r#"
+struct X();
+trait Y {
+ fn f();
+}
+
+impl Y for X {
+ fn f() {}
+}
+mod z {
+$0use super::X;
+use super::Y;$0
+
+fn w() {
+ X::f();
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn trait_item_use_is_use() {
+ check_assist_not_applicable(
+ remove_unused_imports,
+ r#"
+struct X();
+trait Y {
+ fn f(self);
+}
+
+impl Y for X {
+ fn f(self) {}
+}
+mod z {
+$0use super::X;
+use super::Y;$0
+
+fn w() {
+ let x = X();
+ x.f();
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn ranamed_trait_item_use_is_use() {
+ check_assist_not_applicable(
+ remove_unused_imports,
+ r#"
+struct X();
+trait Y {
+ fn f(self);
+}
+
+impl Y for X {
+ fn f(self) {}
+}
+mod z {
+$0use super::X;
+use super::Y as Z;$0
+
+fn w() {
+ let x = X();
+ x.f();
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn ranamed_underscore_trait_item_use_is_use() {
+ check_assist_not_applicable(
+ remove_unused_imports,
+ r#"
+struct X();
+trait Y {
+ fn f(self);
+}
+
+impl Y for X {
+ fn f(self) {}
+}
+mod z {
+$0use super::X;
+use super::Y as _;$0
+
+fn w() {
+ let x = X();
+ x.f();
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn dont_remove_used() {
+ check_assist_not_applicable(
+ remove_unused_imports,
+ r#"
+struct X();
+struct Y();
+mod z {
+$0use super::X;
+use super::Y;$0
+
+fn w() {
+ let x = X();
+ let y = Y();
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn remove_unused_in_braces() {
+ check_assist(
+ remove_unused_imports,
+ r#"
+struct X();
+struct Y();
+mod z {
+ $0use super::{X, Y};$0
+
+ fn w() {
+ let x = X();
+ }
+}
+"#,
+ r#"
+struct X();
+struct Y();
+mod z {
+ use super::{X};
+
+ fn w() {
+ let x = X();
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn remove_unused_under_cursor() {
+ check_assist(
+ remove_unused_imports,
+ r#"
+struct X();
+mod z {
+ use super::X$0;
+}
+"#,
+ r#"
+struct X();
+mod z {
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn remove_multi_use_block() {
+ check_assist(
+ remove_unused_imports,
+ r#"
+struct X();
+$0mod y {
+ use super::X;
+}
+mod z {
+ use super::X;
+}$0
+"#,
+ r#"
+struct X();
+mod y {
+}
+mod z {
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn remove_nested() {
+ check_assist(
+ remove_unused_imports,
+ r#"
+struct X();
+mod y {
+ struct Y();
+ mod z {
+ use crate::{X, y::Y}$0;
+ fn f() {
+ let x = X();
+ }
+ }
+}
+"#,
+ r#"
+struct X();
+mod y {
+ struct Y();
+ mod z {
+ use crate::{X};
+ fn f() {
+ let x = X();
+ }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn remove_nested_first_item() {
+ check_assist(
+ remove_unused_imports,
+ r#"
+struct X();
+mod y {
+ struct Y();
+ mod z {
+ use crate::{X, y::Y}$0;
+ fn f() {
+ let y = Y();
+ }
+ }
+}
+"#,
+ r#"
+struct X();
+mod y {
+ struct Y();
+ mod z {
+ use crate::{y::Y};
+ fn f() {
+ let y = Y();
+ }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn remove_nested_all_unused() {
+ check_assist(
+ remove_unused_imports,
+ r#"
+struct X();
+mod y {
+ struct Y();
+ mod z {
+ use crate::{X, y::Y}$0;
+ }
+}
+"#,
+ r#"
+struct X();
+mod y {
+ struct Y();
+ mod z {
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn remove_unused_glob() {
+ check_assist(
+ remove_unused_imports,
+ r#"
+struct X();
+struct Y();
+mod z {
+ use super::*$0;
+}
+"#,
+ r#"
+struct X();
+struct Y();
+mod z {
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn remove_unused_braced_glob() {
+ check_assist(
+ remove_unused_imports,
+ r#"
+struct X();
+struct Y();
+mod z {
+ use super::{*}$0;
+}
+"#,
+ r#"
+struct X();
+struct Y();
+mod z {
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn dont_remove_used_glob() {
+ check_assist_not_applicable(
+ remove_unused_imports,
+ r#"
+struct X();
+struct Y();
+mod z {
+ use super::*$0;
+
+ fn f() {
+ let x = X();
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn only_remove_from_selection() {
+ check_assist(
+ remove_unused_imports,
+ r#"
+struct X();
+struct Y();
+mod z {
+ $0use super::X;$0
+ use super::Y;
+}
+mod w {
+ use super::Y;
+}
+"#,
+ r#"
+struct X();
+struct Y();
+mod z {
+ use super::Y;
+}
+mod w {
+ use super::Y;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_several_files() {
+ check_assist(
+ remove_unused_imports,
+ r#"
+//- /foo.rs
+pub struct X();
+pub struct Y();
+
+//- /main.rs
+$0use foo::X;
+use foo::Y;
+$0
+mod foo;
+mod z {
+ use crate::foo::X;
+}
+"#,
+ r#"
+
+mod foo;
+mod z {
+ use crate::foo::X;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn use_in_submodule_doesnt_count() {
+ check_assist(
+ remove_unused_imports,
+ r#"
+struct X();
+mod z {
+ use super::X$0;
+
+ mod w {
+ use crate::X;
+
+ fn f() {
+ let x = X();
+ }
+ }
+}
+"#,
+ r#"
+struct X();
+mod z {
+
+ mod w {
+ use crate::X;
+
+ fn f() {
+ let x = X();
+ }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn use_in_submodule_file_doesnt_count() {
+ check_assist(
+ remove_unused_imports,
+ r#"
+//- /z/foo.rs
+use crate::X;
+fn f() {
+ let x = X();
+}
+
+//- /main.rs
+pub struct X();
+
+mod z {
+ use crate::X$0;
+ mod foo;
+}
+"#,
+ r#"
+pub struct X();
+
+mod z {
+ mod foo;
+}
+"#,
+ );
+ }
+}
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
index 3bdd795be..ac45581b7 100644
--- 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
@@ -10,7 +10,7 @@ use crate::{
assist_context::{AssistContext, Assists, SourceChangeBuilder},
utils::{
add_trait_assoc_items_to_impl, filter_assoc_items, gen_trait_fn_body,
- generate_trait_impl_text, render_snippet, Cursor, DefaultMethods,
+ generate_trait_impl_text, render_snippet, Cursor, DefaultMethods, IgnoreAssocItems,
},
AssistId, AssistKind,
};
@@ -73,7 +73,7 @@ pub(crate) fn replace_derive_with_manual_impl(
&ctx.sema,
current_crate,
NameToImport::exact_case_sensitive(path.segments().last()?.to_string()),
- items_locator::AssocItemSearch::Exclude,
+ items_locator::AssocSearchMode::Exclude,
Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT.inner()),
)
.filter_map(|item| match item.as_module_def()? {
@@ -172,7 +172,17 @@ fn impl_def_from_trait(
) -> 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);
+
+ // Keep assoc items of local crates even if they have #[doc(hidden)] attr.
+ let ignore_items = if trait_.module(sema.db).krate().origin(sema.db).is_local() {
+ IgnoreAssocItems::No
+ } else {
+ IgnoreAssocItems::DocHiddenAttrPresent
+ };
+
+ let trait_items =
+ filter_assoc_items(sema, &trait_.items(sema.db), DefaultMethods::No, ignore_items);
+
if trait_items.is_empty() {
return None;
}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs
index e7b62d49b..c7c0be4c7 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs
@@ -157,7 +157,7 @@ fn find_usages(
file_id: FileId,
) -> UsageSearchResult {
let file_range = FileRange { file_id, range: fn_.syntax().text_range() };
- type_param_def.usages(sema).in_scope(SearchScope::file_range(file_range)).all()
+ type_param_def.usages(sema).in_scope(&SearchScope::file_range(file_range)).all()
}
fn check_valid_usages(usages: &UsageSearchResult, param_list_range: TextRange) -> bool {
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
index 26f3c1926..f235b554e 100644
--- 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
@@ -38,14 +38,18 @@ pub(crate) fn unwrap_result_return_type(acc: &mut Assists, ctx: &AssistContext<'
};
let type_ref = &ret_type.ty()?;
- let Some(hir::Adt::Enum(ret_enum)) = ctx.sema.resolve_type(type_ref)?.as_adt() else { return None; };
+ let Some(hir::Adt::Enum(ret_enum)) = ctx.sema.resolve_type(type_ref)?.as_adt() else {
+ return None;
+ };
let result_enum =
FamousDefs(&ctx.sema, ctx.sema.scope(type_ref.syntax())?.krate()).core_result_Result()?;
if ret_enum != result_enum {
return None;
}
- let Some(ok_type) = unwrap_result_type(type_ref) else { return None; };
+ let Some(ok_type) = unwrap_result_type(type_ref) else {
+ return None;
+ };
acc.add(
AssistId("unwrap_result_return_type", AssistKind::RefactorRewrite),
@@ -130,12 +134,16 @@ fn tail_cb_impl(acc: &mut Vec<ast::Expr>, e: &ast::Expr) {
// Tries to extract `T` from `Result<T, E>`.
fn unwrap_result_type(ty: &ast::Type) -> Option<ast::Type> {
- let ast::Type::PathType(path_ty) = ty else { return None; };
+ let ast::Type::PathType(path_ty) = ty else {
+ return None;
+ };
let path = path_ty.path()?;
let segment = path.first_segment()?;
let generic_arg_list = segment.generic_arg_list()?;
let generic_args: Vec<_> = generic_arg_list.generic_args().collect();
- let ast::GenericArg::TypeArg(ok_type) = generic_args.first()? else { return None; };
+ let ast::GenericArg::TypeArg(ok_type) = generic_args.first()? else {
+ return None;
+ };
ok_type.ty()
}
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
index b6c489eb6..24c338745 100644
--- 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
@@ -6,7 +6,7 @@ use ide_db::{
};
use syntax::{
ast::{self, make, Expr},
- match_ast, AstNode,
+ match_ast, ted, AstNode,
};
use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -52,8 +52,8 @@ pub(crate) fn wrap_return_type_in_result(acc: &mut Assists, ctx: &AssistContext<
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);
+ |edit| {
+ let body = edit.make_mut(ast::Expr::BlockExpr(body));
let mut exprs_to_wrap = Vec::new();
let tail_cb = &mut |e: &_| tail_cb_impl(&mut exprs_to_wrap, e);
@@ -70,17 +70,24 @@ pub(crate) fn wrap_return_type_in_result(acc: &mut Assists, ctx: &AssistContext<
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);
+ )
+ .clone_for_update();
+ ted::replace(ret_expr_arg.syntax(), ok_wrapped.syntax());
}
- match ctx.config.snippet_cap {
- Some(cap) => {
- let snippet = format!("Result<{type_ref}, ${{0:_}}>");
- builder.replace_snippet(cap, type_ref.syntax().text_range(), snippet)
- }
- None => builder
- .replace(type_ref.syntax().text_range(), format!("Result<{type_ref}, _>")),
+ let new_result_ty =
+ make::ext::ty_result(type_ref.clone(), make::ty_placeholder()).clone_for_update();
+ let old_result_ty = edit.make_mut(type_ref.clone());
+
+ ted::replace(old_result_ty.syntax(), new_result_ty.syntax());
+
+ if let Some(cap) = ctx.config.snippet_cap {
+ let generic_args = new_result_ty
+ .syntax()
+ .descendants()
+ .find_map(ast::GenericArgList::cast)
+ .unwrap();
+ edit.add_placeholder_snippet(cap, generic_args.generic_args().last().unwrap());
}
},
)
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs
index 111753bf3..2ebb5ef9b 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs
@@ -145,6 +145,7 @@ mod handlers {
mod generate_constant;
mod generate_default_from_enum_variant;
mod generate_default_from_new;
+ mod generate_delegate_trait;
mod generate_deref;
mod generate_derive;
mod generate_documentation_template;
@@ -153,12 +154,12 @@ mod handlers {
mod generate_enum_variant;
mod generate_from_impl_for_enum;
mod generate_function;
- mod generate_getter;
+ mod generate_getter_or_setter;
mod generate_impl;
mod generate_is_empty_from_len;
mod generate_new;
- mod generate_setter;
mod generate_delegate_methods;
+ mod generate_trait_from_impl;
mod add_return_type;
mod inline_call;
mod inline_const_as_literal;
@@ -183,6 +184,7 @@ mod handlers {
mod raw_string;
mod remove_dbg;
mod remove_mut;
+ mod remove_unused_imports;
mod remove_unused_param;
mod remove_parentheses;
mod reorder_fields;
@@ -251,6 +253,7 @@ mod handlers {
generate_constant::generate_constant,
generate_default_from_enum_variant::generate_default_from_enum_variant,
generate_default_from_new::generate_default_from_new,
+ generate_delegate_trait::generate_delegate_trait,
generate_derive::generate_derive,
generate_documentation_template::generate_documentation_template,
generate_documentation_template::generate_doc_example,
@@ -264,6 +267,7 @@ mod handlers {
generate_impl::generate_trait_impl,
generate_is_empty_from_len::generate_is_empty_from_len,
generate_new::generate_new,
+ generate_trait_from_impl::generate_trait_from_impl,
inline_call::inline_call,
inline_call::inline_into_callers,
inline_const_as_literal::inline_const_as_literal,
@@ -291,6 +295,7 @@ mod handlers {
raw_string::make_usual_string,
raw_string::remove_hash,
remove_mut::remove_mut,
+ remove_unused_imports::remove_unused_imports,
remove_unused_param::remove_unused_param,
remove_parentheses::remove_parentheses,
reorder_fields::reorder_fields,
@@ -334,9 +339,9 @@ mod handlers {
extract_function::extract_function,
extract_module::extract_module,
//
- generate_getter::generate_getter,
- generate_getter::generate_getter_mut,
- generate_setter::generate_setter,
+ generate_getter_or_setter::generate_getter,
+ generate_getter_or_setter::generate_getter_mut,
+ generate_getter_or_setter::generate_setter,
generate_delegate_methods::generate_delegate_methods,
generate_deref::generate_deref,
//
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs b/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs
index 344f2bfcc..cc3e251a8 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs
@@ -132,8 +132,13 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) {
.filter(|it| !it.source_file_edits.is_empty() || !it.file_system_edits.is_empty())
.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) {
+ if let Some((source_file_edit, snippet_edit)) =
+ source_change.get_source_and_snippet_edit(file_id)
+ {
source_file_edit.apply(&mut actual);
+ if let Some(snippet_edit) = snippet_edit {
+ snippet_edit.apply(&mut actual);
+ }
}
actual
};
@@ -191,9 +196,12 @@ fn check_with_config(
&& source_change.file_system_edits.len() == 0;
let mut buf = String::new();
- for (file_id, edit) in source_change.source_file_edits {
+ for (file_id, (edit, snippet_edit)) in source_change.source_file_edits {
let mut text = db.file_text(file_id).as_ref().to_owned();
edit.apply(&mut text);
+ if let Some(snippet_edit) = snippet_edit {
+ snippet_edit.apply(&mut text);
+ }
if !skip_header {
let sr = db.file_source_root(file_id);
let sr = db.source_root(sr);
@@ -485,18 +493,21 @@ pub fn test_some_range(a: int) -> bool {
source_file_edits: {
FileId(
0,
- ): TextEdit {
- indels: [
- Indel {
- insert: "let $0var_name = 5;\n ",
- delete: 45..45,
- },
- Indel {
- insert: "var_name",
- delete: 59..60,
- },
- ],
- },
+ ): (
+ TextEdit {
+ indels: [
+ Indel {
+ insert: "let $0var_name = 5;\n ",
+ delete: 45..45,
+ },
+ Indel {
+ insert: "var_name",
+ delete: 59..60,
+ },
+ ],
+ },
+ None,
+ ),
},
file_system_edits: [],
is_snippet: true,
@@ -544,18 +555,21 @@ pub fn test_some_range(a: int) -> bool {
source_file_edits: {
FileId(
0,
- ): TextEdit {
- indels: [
- Indel {
- insert: "let $0var_name = 5;\n ",
- delete: 45..45,
- },
- Indel {
- insert: "var_name",
- delete: 59..60,
- },
- ],
- },
+ ): (
+ TextEdit {
+ indels: [
+ Indel {
+ insert: "let $0var_name = 5;\n ",
+ delete: 45..45,
+ },
+ Indel {
+ insert: "var_name",
+ delete: 59..60,
+ },
+ ],
+ },
+ None,
+ ),
},
file_system_edits: [],
is_snippet: true,
@@ -581,18 +595,21 @@ pub fn test_some_range(a: int) -> bool {
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,
- },
- ],
- },
+ ): (
+ TextEdit {
+ indels: [
+ Indel {
+ insert: "fun_name()",
+ delete: 59..60,
+ },
+ Indel {
+ insert: "\n\nfn $0fun_name() -> i32 {\n 5\n}",
+ delete: 110..110,
+ },
+ ],
+ },
+ None,
+ ),
},
file_system_edits: [],
is_snippet: true,
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
index c097e0739..6eadc3dbc 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs
@@ -952,6 +952,7 @@ fn doctest_generate_default_from_new() {
check_doc_test(
"generate_default_from_new",
r#####"
+//- minicore: default
struct Example { _inner: () }
impl Example {
@@ -1016,6 +1017,69 @@ impl Person {
}
#[test]
+fn doctest_generate_delegate_trait() {
+ check_doc_test(
+ "generate_delegate_trait",
+ r#####"
+trait SomeTrait {
+ type T;
+ fn fn_(arg: u32) -> u32;
+ fn method_(&mut self) -> bool;
+}
+struct A;
+impl SomeTrait for A {
+ type T = u32;
+
+ fn fn_(arg: u32) -> u32 {
+ 42
+ }
+
+ fn method_(&mut self) -> bool {
+ false
+ }
+}
+struct B {
+ a$0: A,
+}
+"#####,
+ r#####"
+trait SomeTrait {
+ type T;
+ fn fn_(arg: u32) -> u32;
+ fn method_(&mut self) -> bool;
+}
+struct A;
+impl SomeTrait for A {
+ type T = u32;
+
+ fn fn_(arg: u32) -> u32 {
+ 42
+ }
+
+ fn method_(&mut self) -> bool {
+ false
+ }
+}
+struct B {
+ a: A,
+}
+
+impl SomeTrait for B {
+ type T = <A as SomeTrait>::T;
+
+ fn fn_(arg: u32) -> u32 {
+ <A as SomeTrait>::fn_(arg)
+ }
+
+ fn method_(&mut self) -> bool {
+ <A as SomeTrait>::method_( &mut self.a )
+ }
+}
+"#####,
+ )
+}
+
+#[test]
fn doctest_generate_deref() {
check_doc_test(
"generate_deref",
@@ -1429,7 +1493,7 @@ struct Person {
}
impl Person {
- fn set_name(&mut self, name: String) {
+ fn $0set_name(&mut self, name: String) {
self.name = name;
}
}
@@ -1438,6 +1502,62 @@ impl Person {
}
#[test]
+fn doctest_generate_trait_from_impl() {
+ check_doc_test(
+ "generate_trait_from_impl",
+ r#####"
+struct Foo<const N: usize>([i32; N]);
+
+macro_rules! const_maker {
+ ($t:ty, $v:tt) => {
+ const CONST: $t = $v;
+ };
+}
+
+impl<const N: usize> Fo$0o<N> {
+ // Used as an associated constant.
+ const CONST_ASSOC: usize = N * 4;
+
+ fn create() -> Option<()> {
+ Some(())
+ }
+
+ const_maker! {i32, 7}
+}
+"#####,
+ r#####"
+struct Foo<const N: usize>([i32; N]);
+
+macro_rules! const_maker {
+ ($t:ty, $v:tt) => {
+ const CONST: $t = $v;
+ };
+}
+
+trait ${0:TraitName}<const N: usize> {
+ // Used as an associated constant.
+ const CONST_ASSOC: usize = N * 4;
+
+ fn create() -> Option<()>;
+
+ const_maker! {i32, 7}
+}
+
+impl<const N: usize> ${0:TraitName}<N> for Foo<N> {
+ // Used as an associated constant.
+ const CONST_ASSOC: usize = N * 4;
+
+ fn create() -> Option<()> {
+ Some(())
+ }
+
+ const_maker! {i32, 7}
+}
+"#####,
+ )
+}
+
+#[test]
fn doctest_generate_trait_impl() {
check_doc_test(
"generate_trait_impl",
@@ -2115,6 +2235,24 @@ fn main() {
}
#[test]
+fn doctest_remove_unused_imports() {
+ check_doc_test(
+ "remove_unused_imports",
+ r#####"
+struct X();
+mod foo {
+ use super::X$0;
+}
+"#####,
+ r#####"
+struct X();
+mod foo {
+}
+"#####,
+ )
+}
+
+#[test]
fn doctest_remove_unused_param() {
check_doc_test(
"remove_unused_param",
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs
index 03d855350..a262570d9 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs
@@ -3,7 +3,7 @@
use std::ops;
pub(crate) use gen_trait_fn_body::gen_trait_fn_body;
-use hir::{db::HirDatabase, HirDisplay, InFile, Semantics};
+use hir::{db::HirDatabase, HasAttrs as HirHasAttrs, HirDisplay, InFile, Semantics};
use ide_db::{
famous_defs::FamousDefs, path_transform::PathTransform,
syntax_helpers::insert_whitespace_into_node::insert_ws_into, RootDatabase, SnippetCap,
@@ -84,6 +84,12 @@ pub fn test_related_attribute(fn_def: &ast::Fn) -> Option<ast::Attr> {
})
}
+#[derive(Clone, Copy, PartialEq)]
+pub enum IgnoreAssocItems {
+ DocHiddenAttrPresent,
+ No,
+}
+
#[derive(Copy, Clone, PartialEq)]
pub enum DefaultMethods {
Only,
@@ -94,11 +100,16 @@ pub fn filter_assoc_items(
sema: &Semantics<'_, RootDatabase>,
items: &[hir::AssocItem],
default_methods: DefaultMethods,
+ ignore_items: IgnoreAssocItems,
) -> Vec<InFile<ast::AssocItem>> {
return items
.iter()
- // Note: This throws away items with no source.
.copied()
+ .filter(|assoc_item| {
+ !(ignore_items == IgnoreAssocItems::DocHiddenAttrPresent
+ && assoc_item.attrs(sema.db).has_doc_hidden())
+ })
+ // Note: This throws away items with no source.
.filter_map(|assoc_item| {
let item = match assoc_item {
hir::AssocItem::Function(it) => sema.source(it)?.map(ast::AssocItem::Fn),
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
index 19bfd294b..62bdb6ee6 100644
--- 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
@@ -42,10 +42,11 @@ pub(crate) fn complete_cfg(acc: &mut Completions, ctx: &CompletionContext<'_>) {
};
}
-const KNOWN_ARCH: [&str; 19] = [
+const KNOWN_ARCH: [&str; 20] = [
"aarch64",
"arm",
"avr",
+ "csky",
"hexagon",
"mips",
"mips64",
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
index c717a9cb5..e411c1c86 100644
--- 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
@@ -30,6 +30,8 @@ const SUPPORTED_CALLING_CONVENTIONS: &[&str] = &[
"efiapi",
"avr-interrupt",
"avr-non-blocking-interrupt",
+ "riscv-interrupt-m",
+ "riscv-interrupt-s",
"C-cmse-nonsecure-call",
"wasm",
"system",
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
index d3e75c6da..1e0989405 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/mod_.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/mod_.rs
@@ -42,7 +42,7 @@ pub(crate) fn complete_mod(
}
let module_definition_file =
- current_module.definition_source(ctx.db).file_id.original_file(ctx.db);
+ current_module.definition_source_file_id(ctx.db).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,
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs
index 7b145f3c1..3cb65b272 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs
@@ -463,7 +463,9 @@ impl CompletionContext<'_> {
/// Checks whether this item should be listed in regards to stability. Returns `true` if we should.
pub(crate) fn check_stability(&self, attrs: Option<&hir::Attrs>) -> bool {
- let Some(attrs) = attrs else { return true; };
+ let Some(attrs) = attrs else {
+ return true;
+ };
!attrs.is_unstable() || self.is_nightly
}
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
index cc5221cfc..3ea506590 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs
@@ -243,10 +243,7 @@ fn analyze(
let Some(name_like) = find_node_at_offset(&speculative_file, offset) else {
let analysis = if let Some(original) = ast::String::cast(original_token.clone()) {
- CompletionAnalysis::String {
- original,
- expanded: ast::String::cast(self_token.clone()),
- }
+ CompletionAnalysis::String { original, expanded: ast::String::cast(self_token.clone()) }
} else {
// Fix up trailing whitespace problem
// #[attr(foo = $0
@@ -736,7 +733,7 @@ fn classify_name_ref(
return None;
}
let parent = match ast::Fn::cast(parent.parent()?) {
- Some(x) => x.param_list(),
+ Some(it) => it.param_list(),
None => ast::ClosureExpr::cast(parent.parent()?)?.param_list(),
};
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/item.rs b/src/tools/rust-analyzer/crates/ide-completion/src/item.rs
index e850f7bfd..0309952c2 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/item.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/item.rs
@@ -427,9 +427,26 @@ impl Builder {
let insert_text = self.insert_text.unwrap_or_else(|| label.to_string());
if !self.doc_aliases.is_empty() {
- let doc_aliases = self.doc_aliases.into_iter().join(", ");
+ let doc_aliases = self.doc_aliases.iter().join(", ");
label = SmolStr::from(format!("{label} (alias {doc_aliases})"));
- lookup = SmolStr::from(format!("{lookup} {doc_aliases}"));
+ let lookup_doc_aliases = self
+ .doc_aliases
+ .iter()
+ // Don't include aliases in `lookup` that aren't valid identifiers as including
+ // them results in weird completion filtering behavior e.g. `Partial>` matching
+ // `PartialOrd` because it has an alias of ">".
+ .filter(|alias| {
+ let mut chars = alias.chars();
+ chars.next().is_some_and(char::is_alphabetic)
+ && chars.all(|c| c.is_alphanumeric() || c == '_')
+ })
+ // Deliberately concatenated without separators as adding separators e.g.
+ // `alias1, alias2` results in LSP clients continuing to display the completion even
+ // after typing a comma or space.
+ .join("");
+ if !lookup_doc_aliases.is_empty() {
+ lookup = SmolStr::from(format!("{lookup}{lookup_doc_aliases}"));
+ }
}
if let [import_edit] = &*self.imports_to_add {
// snippets can have multiple imports, but normal completions only have up to one
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs b/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs
index 106d4e1e5..2eaa42040 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs
@@ -231,7 +231,7 @@ pub fn resolve_completion_edits(
&sema,
current_crate,
NameToImport::exact_case_sensitive(imported_name),
- items_locator::AssocItemSearch::Include,
+ items_locator::AssocSearchMode::Include,
Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT.inner()),
);
let import = items_with_name
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
index c97144b61..1aaf39587 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs
@@ -300,6 +300,7 @@ struct Foo;
at deprecated
at derive macro derive
at derive(…)
+ at derive_const macro derive_const
at doc = "…"
at doc(alias = "…")
at doc(hidden)
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
index 382472083..e80a28904 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs
@@ -1280,3 +1280,26 @@ fn here_we_go() {
"#]],
);
}
+
+#[test]
+fn completion_filtering_excludes_non_identifier_doc_aliases() {
+ check_edit(
+ "PartialOrdcmporder",
+ r#"
+#[doc(alias = ">")]
+#[doc(alias = "cmp")]
+#[doc(alias = "order")]
+trait PartialOrd {}
+
+struct Foo<T: Partial$0
+"#,
+ r#"
+#[doc(alias = ">")]
+#[doc(alias = "cmp")]
+#[doc(alias = "order")]
+trait PartialOrd {}
+
+struct Foo<T: PartialOrd
+"#,
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/ide-db/Cargo.toml b/src/tools/rust-analyzer/crates/ide-db/Cargo.toml
index 4e75dc4db..faec74206 100644
--- a/src/tools/rust-analyzer/crates/ide-db/Cargo.toml
+++ b/src/tools/rust-analyzer/crates/ide-db/Cargo.toml
@@ -21,7 +21,7 @@ once_cell = "1.17.0"
either = "1.7.0"
itertools = "0.10.5"
arrayvec = "0.7.2"
-indexmap = "1.9.1"
+indexmap = "2.0.0"
memchr = "2.5.0"
triomphe.workspace = true
nohash-hasher.workspace = true
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
index 0dd544d0a..a0b05c87a 100644
--- a/src/tools/rust-analyzer/crates/ide-db/src/apply_change.rs
+++ b/src/tools/rust-analyzer/crates/ide-db/src/apply_change.rs
@@ -99,8 +99,8 @@ impl RootDatabase {
hir::db::AstIdMapQuery
hir::db::ParseMacroExpansionQuery
hir::db::InternMacroCallQuery
- hir::db::MacroArgTextQuery
- hir::db::MacroDefQuery
+ hir::db::MacroArgNodeQuery
+ hir::db::DeclMacroExpanderQuery
hir::db::MacroExpandQuery
hir::db::ExpandProcMacroQuery
hir::db::HygieneFrameQuery
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/defs.rs b/src/tools/rust-analyzer/crates/ide-db/src/defs.rs
index 760834bfa..5e4562d9c 100644
--- a/src/tools/rust-analyzer/crates/ide-db/src/defs.rs
+++ b/src/tools/rust-analyzer/crates/ide-db/src/defs.rs
@@ -7,10 +7,10 @@
use arrayvec::ArrayVec;
use hir::{
- Adt, AsAssocItem, AssocItem, BuiltinAttr, BuiltinType, Const, Crate, DeriveHelper, Field,
- Function, GenericParam, HasVisibility, Impl, Label, Local, Macro, Module, ModuleDef, Name,
- PathResolution, Semantics, Static, ToolModule, Trait, TraitAlias, TypeAlias, Variant,
- Visibility,
+ Adt, AsAssocItem, AssocItem, BuiltinAttr, BuiltinType, Const, Crate, DeriveHelper,
+ ExternCrateDecl, Field, Function, GenericParam, HasVisibility, Impl, Label, Local, Macro,
+ Module, ModuleDef, Name, PathResolution, Semantics, Static, ToolModule, Trait, TraitAlias,
+ TypeAlias, Variant, Visibility,
};
use stdx::impl_from;
use syntax::{
@@ -42,6 +42,7 @@ pub enum Definition {
DeriveHelper(DeriveHelper),
BuiltinAttr(BuiltinAttr),
ToolModule(ToolModule),
+ ExternCrateDecl(ExternCrateDecl),
}
impl Definition {
@@ -73,6 +74,7 @@ impl Definition {
Definition::Local(it) => it.module(db),
Definition::GenericParam(it) => it.module(db),
Definition::Label(it) => it.module(db),
+ Definition::ExternCrateDecl(it) => it.module(db),
Definition::DeriveHelper(it) => it.derive().module(db),
Definition::BuiltinAttr(_) | Definition::BuiltinType(_) | Definition::ToolModule(_) => {
return None
@@ -93,6 +95,7 @@ impl Definition {
Definition::TraitAlias(it) => it.visibility(db),
Definition::TypeAlias(it) => it.visibility(db),
Definition::Variant(it) => it.visibility(db),
+ Definition::ExternCrateDecl(it) => it.visibility(db),
Definition::BuiltinType(_) => Visibility::Public,
Definition::Macro(_) => return None,
Definition::BuiltinAttr(_)
@@ -127,6 +130,7 @@ impl Definition {
Definition::BuiltinAttr(_) => return None, // FIXME
Definition::ToolModule(_) => return None, // FIXME
Definition::DeriveHelper(it) => it.name(db),
+ Definition::ExternCrateDecl(it) => return it.alias_or_name(db),
};
Some(name)
}
@@ -196,6 +200,10 @@ impl IdentClass {
res.push(Definition::Local(local_ref));
res.push(Definition::Field(field_ref));
}
+ IdentClass::NameRefClass(NameRefClass::ExternCrateShorthand { decl, krate }) => {
+ res.push(Definition::ExternCrateDecl(decl));
+ res.push(Definition::Module(krate.root_module()));
+ }
IdentClass::Operator(
OperatorClass::Await(func)
| OperatorClass::Prefix(func)
@@ -222,6 +230,10 @@ impl IdentClass {
res.push(Definition::Local(local_ref));
res.push(Definition::Field(field_ref));
}
+ IdentClass::NameRefClass(NameRefClass::ExternCrateShorthand { decl, krate }) => {
+ res.push(Definition::ExternCrateDecl(decl));
+ res.push(Definition::Module(krate.root_module()));
+ }
IdentClass::Operator(_) => (),
}
res
@@ -310,6 +322,7 @@ impl NameClass {
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)?)),
+ ast::Item::ExternCrate(it) => Definition::ExternCrateDecl(sema.to_def(&it)?),
_ => return None,
};
Some(definition)
@@ -346,10 +359,8 @@ impl NameClass {
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))
+ sema.to_def(&rename.syntax().parent().and_then(ast::ExternCrate::cast)?)
+ .map(Definition::ExternCrateDecl)
}
}
}
@@ -427,7 +438,19 @@ impl OperatorClass {
#[derive(Debug)]
pub enum NameRefClass {
Definition(Definition),
- FieldShorthand { local_ref: Local, field_ref: Field },
+ FieldShorthand {
+ local_ref: Local,
+ field_ref: Field,
+ },
+ /// The specific situation where we have an extern crate decl without a rename
+ /// Here we have both a declaration and a reference.
+ /// ```rs
+ /// extern crate foo;
+ /// ```
+ ExternCrateShorthand {
+ decl: ExternCrateDecl,
+ krate: Crate,
+ },
}
impl NameRefClass {
@@ -513,10 +536,14 @@ impl NameRefClass {
}
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)))
+ ast::ExternCrate(extern_crate_ast) => {
+ let extern_crate = sema.to_def(&extern_crate_ast)?;
+ let krate = extern_crate.resolved_crate(sema.db)?;
+ Some(if extern_crate_ast.rename().is_some() {
+ NameRefClass::Definition(Definition::Module(krate.root_module()))
+ } else {
+ NameRefClass::ExternCrateShorthand { krate, decl: extern_crate }
+ })
},
_ => None
}
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
index c8341fed1..b63dde2c2 100644
--- a/src/tools/rust-analyzer/crates/ide-db/src/famous_defs.rs
+++ b/src/tools/rust-analyzer/crates/ide-db/src/famous_defs.rs
@@ -167,7 +167,7 @@ impl FamousDefs<'_, '_> {
lang_crate => lang_crate,
};
let std_crate = self.find_lang_crate(lang_crate)?;
- let mut module = std_crate.root_module(db);
+ let mut module = std_crate.root_module();
for segment in path {
module = module.children(db).find_map(|child| {
let name = child.name(db)?;
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
index e488300b4..49b37024a 100644
--- a/src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs
+++ b/src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs
@@ -931,22 +931,6 @@ $ cat $(find -name '*.s')
"##,
},
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`
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/helpers.rs b/src/tools/rust-analyzer/crates/ide-db/src/helpers.rs
index eba9d8afc..1eb8f0002 100644
--- a/src/tools/rust-analyzer/crates/ide-db/src/helpers.rs
+++ b/src/tools/rust-analyzer/crates/ide-db/src/helpers.rs
@@ -9,7 +9,10 @@ use syntax::{
AstToken, SyntaxKind, SyntaxToken, TokenAtOffset,
};
-use crate::{defs::Definition, generated, RootDatabase};
+use crate::{
+ defs::{Definition, IdentClass},
+ generated, RootDatabase,
+};
pub fn item_name(db: &RootDatabase, item: ItemInNs) -> Option<Name> {
match item {
@@ -109,3 +112,16 @@ pub fn is_editable_crate(krate: Crate, db: &RootDatabase) -> bool {
let source_root_id = db.file_source_root(root_file);
!db.source_root(source_root_id).is_library
}
+
+pub 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_no_ops);
+ if let Some(&[x]) = def.as_deref() {
+ return Some(x);
+ }
+ }
+ None
+}
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
index 901d592c6..e52dc3567 100644
--- 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
@@ -13,7 +13,7 @@ use syntax::{
use crate::{
helpers::item_name,
- items_locator::{self, AssocItemSearch, DEFAULT_QUERY_SEARCH_LIMIT},
+ items_locator::{self, AssocSearchMode, DEFAULT_QUERY_SEARCH_LIMIT},
RootDatabase,
};
@@ -317,7 +317,7 @@ fn path_applicable_imports(
// * 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,
+ AssocSearchMode::Exclude,
Some(DEFAULT_QUERY_SEARCH_LIMIT.inner()),
)
.filter_map(|item| {
@@ -334,7 +334,7 @@ fn path_applicable_imports(
sema,
current_crate,
path_candidate.name.clone(),
- AssocItemSearch::Include,
+ AssocSearchMode::Include,
Some(DEFAULT_QUERY_SEARCH_LIMIT.inner()),
)
.filter_map(|item| {
@@ -483,7 +483,7 @@ fn trait_applicable_items(
sema,
current_crate,
trait_candidate.assoc_item_name.clone(),
- AssocItemSearch::AssocItemsOnly,
+ AssocSearchMode::AssocItemsOnly,
Some(DEFAULT_QUERY_SEARCH_LIMIT.inner()),
)
.filter_map(|input| item_as_assoc(db, input))
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
index 46f1353e2..3f7a3ec2d 100644
--- a/src/tools/rust-analyzer/crates/ide-db/src/items_locator.rs
+++ b/src/tools/rust-analyzer/crates/ide-db/src/items_locator.rs
@@ -3,10 +3,7 @@
//! 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},
- AsAssocItem, Crate, ItemInNs, Semantics,
-};
+use hir::{import_map, AsAssocItem, Crate, ItemInNs, Semantics};
use limit::Limit;
use crate::{imports::import_assets::NameToImport, symbol_index, RootDatabase};
@@ -14,23 +11,14 @@ use crate::{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,
-}
+pub use import_map::AssocSearchMode;
/// 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,
+ assoc_item_search: AssocSearchMode,
limit: Option<usize>,
) -> impl Iterator<Item = ItemInNs> + 'a {
let _p = profile::span("items_with_name").detail(|| {
@@ -48,9 +36,7 @@ pub fn items_with_name<'a>(
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);
+ let external_query = import_map::Query::new(exact_name);
(
local_query,
@@ -61,17 +47,8 @@ pub fn items_with_name<'a>(
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();
- }
- }
+ .fuzzy()
+ .assoc_search_mode(assoc_item_search);
if fuzzy_search_string.to_lowercase() != fuzzy_search_string {
local_query.case_sensitive();
@@ -93,13 +70,15 @@ pub fn items_with_name<'a>(
fn find_items<'a>(
sema: &'a Semantics<'_, RootDatabase>,
krate: Crate,
- assoc_item_search: AssocItemSearch,
+ assoc_item_search: AssocSearchMode,
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;
+ // NOTE: `external_query` includes `assoc_item_search`, so we don't need to
+ // filter on our own.
let external_importables =
krate.query_external_importables(db, external_query).map(|external_importable| {
match external_importable {
@@ -112,18 +91,15 @@ fn find_items<'a>(
let local_results = local_query
.search(&symbol_index::crate_symbols(db, krate))
.into_iter()
- .filter_map(|local_candidate| match local_candidate.def {
- hir::ModuleDef::Macro(macro_def) => Some(ItemInNs::Macros(macro_def)),
- def => Some(ItemInNs::from(def)),
+ .filter(move |candidate| match assoc_item_search {
+ AssocSearchMode::Include => true,
+ AssocSearchMode::Exclude => candidate.def.as_assoc_item(db).is_none(),
+ AssocSearchMode::AssocItemsOnly => candidate.def.as_assoc_item(db).is_some(),
+ })
+ .map(|local_candidate| match local_candidate.def {
+ hir::ModuleDef::Macro(macro_def) => ItemInNs::Macros(macro_def),
+ def => ItemInNs::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 is_assoc_item(item: ItemInNs, db: &RootDatabase) -> bool {
- item.as_module_def().and_then(|module_def| module_def.as_assoc_item(db)).is_some()
+ external_importables.chain(local_results)
}
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/lib.rs b/src/tools/rust-analyzer/crates/ide-db/src/lib.rs
index ff1a20f03..f27ed485d 100644
--- a/src/tools/rust-analyzer/crates/ide-db/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/ide-db/src/lib.rs
@@ -200,8 +200,8 @@ impl RootDatabase {
hir_db::AstIdMapQuery
// hir_db::ParseMacroExpansionQuery
// hir_db::InternMacroCallQuery
- hir_db::MacroArgTextQuery
- hir_db::MacroDefQuery
+ hir_db::MacroArgNodeQuery
+ hir_db::DeclMacroExpanderQuery
// hir_db::MacroExpandQuery
hir_db::ExpandProcMacroQuery
hir_db::HygieneFrameQuery
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
index 73e6a920e..1d0cb426a 100644
--- a/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs
+++ b/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs
@@ -195,7 +195,7 @@ fn postorder(item: &SyntaxNode) -> impl Iterator<Item = SyntaxNode> {
})
}
-impl<'a> Ctx<'a> {
+impl Ctx<'_> {
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
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/rename.rs b/src/tools/rust-analyzer/crates/ide-db/src/rename.rs
index 52a23b4b8..aa0bb7cce 100644
--- a/src/tools/rust-analyzer/crates/ide-db/src/rename.rs
+++ b/src/tools/rust-analyzer/crates/ide-db/src/rename.rs
@@ -82,8 +82,9 @@ impl Definition {
}
/// Textual range of the identifier which will change when renaming this
- /// `Definition`. Note that some definitions, like builtin types, can't be
- /// renamed.
+ /// `Definition`. Note that builtin types can't be
+ /// renamed and extern crate names will report its range, though a rename will introduce
+ /// an alias instead.
pub fn range_for_rename(self, sema: &Semantics<'_, RootDatabase>) -> Option<FileRange> {
let res = match self {
Definition::Macro(mac) => {
@@ -146,6 +147,16 @@ impl Definition {
let lifetime = src.value.lifetime()?;
src.with_value(lifetime.syntax()).original_file_range_opt(sema.db)
}
+ Definition::ExternCrateDecl(it) => {
+ let src = it.source(sema.db)?;
+ if let Some(rename) = src.value.rename() {
+ let name = rename.name()?;
+ src.with_value(name.syntax()).original_file_range_opt(sema.db)
+ } else {
+ let name = src.value.name_ref()?;
+ src.with_value(name.syntax()).original_file_range_opt(sema.db)
+ }
+ }
Definition::BuiltinType(_) => return None,
Definition::SelfType(_) => return None,
Definition::BuiltinAttr(_) => return None,
@@ -526,6 +537,9 @@ fn source_edit_from_def(
TextRange::new(range.start() + syntax::TextSize::from(1), range.end()),
new_name.strip_prefix('\'').unwrap_or(new_name).to_owned(),
),
+ Definition::ExternCrateDecl(decl) if decl.alias(sema.db).is_none() => {
+ (TextRange::empty(range.end()), format!(" as {new_name}"))
+ }
_ => (range, new_name.to_owned()),
};
edit.replace(range, new_name);
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/search.rs b/src/tools/rust-analyzer/crates/ide-db/src/search.rs
index e8ff107bd..d5abd0991 100644
--- a/src/tools/rust-analyzer/crates/ide-db/src/search.rs
+++ b/src/tools/rust-analyzer/crates/ide-db/src/search.rs
@@ -127,7 +127,7 @@ impl SearchScope {
}
/// Build a search scope spanning the given module and all its submodules.
- fn module_and_children(db: &RootDatabase, module: hir::Module) -> SearchScope {
+ pub fn module_and_children(db: &RootDatabase, module: hir::Module) -> SearchScope {
let mut entries = IntMap::default();
let (file_id, range) = {
@@ -149,10 +149,8 @@ impl SearchScope {
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);
+ if let Some(file_id) = module.as_source_file_id(db) {
+ entries.insert(file_id, None);
}
to_visit.extend(module.children(db));
}
@@ -331,7 +329,7 @@ impl Definition {
pub struct FindUsages<'a> {
def: Definition,
sema: &'a Semantics<'a, RootDatabase>,
- scope: Option<SearchScope>,
+ scope: Option<&'a SearchScope>,
/// The container of our definition should it be an assoc item
assoc_item_container: Option<hir::AssocItemContainer>,
/// whether to search for the `Self` type of the definition
@@ -342,19 +340,19 @@ pub struct FindUsages<'a> {
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> {
+ pub fn include_self_refs(mut self) -> Self {
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> {
+ pub fn in_scope(self, scope: &'a SearchScope) -> Self {
self.set_scope(Some(scope))
}
/// Limit the search to a given [`SearchScope`].
- pub fn set_scope(mut self, scope: Option<SearchScope>) -> FindUsages<'a> {
+ pub fn set_scope(mut self, scope: Option<&'a SearchScope>) -> Self {
assert!(self.scope.is_none());
self.scope = scope;
self
@@ -378,7 +376,7 @@ impl<'a> FindUsages<'a> {
res
}
- fn search(&self, sink: &mut dyn FnMut(FileId, FileReference) -> bool) {
+ pub fn search(&self, sink: &mut dyn FnMut(FileId, FileReference) -> bool) {
let _p = profile::span("FindUsages:search");
let sema = self.sema;
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
index 061fb0f05..39763479c 100644
--- a/src/tools/rust-analyzer/crates/ide-db/src/source_change.rs
+++ b/src/tools/rust-analyzer/crates/ide-db/src/source_change.rs
@@ -7,14 +7,17 @@ use std::{collections::hash_map::Entry, iter, mem};
use crate::SnippetCap;
use base_db::{AnchoredPathBuf, FileId};
+use itertools::Itertools;
use nohash_hasher::IntMap;
use stdx::never;
-use syntax::{algo, ast, ted, AstNode, SyntaxNode, SyntaxNodePtr, TextRange, TextSize};
+use syntax::{
+ algo, AstNode, SyntaxElement, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange, TextSize,
+};
use text_edit::{TextEdit, TextEditBuilder};
#[derive(Default, Debug, Clone)]
pub struct SourceChange {
- pub source_file_edits: IntMap<FileId, TextEdit>,
+ pub source_file_edits: IntMap<FileId, (TextEdit, Option<SnippetEdit>)>,
pub file_system_edits: Vec<FileSystemEdit>,
pub is_snippet: bool,
}
@@ -23,7 +26,7 @@ impl SourceChange {
/// Creates a new SourceChange with the given label
/// from the edits.
pub fn from_edits(
- source_file_edits: IntMap<FileId, TextEdit>,
+ source_file_edits: IntMap<FileId, (TextEdit, Option<SnippetEdit>)>,
file_system_edits: Vec<FileSystemEdit>,
) -> Self {
SourceChange { source_file_edits, file_system_edits, is_snippet: false }
@@ -31,7 +34,7 @@ impl SourceChange {
pub fn from_text_edit(file_id: FileId, edit: TextEdit) -> Self {
SourceChange {
- source_file_edits: iter::once((file_id, edit)).collect(),
+ source_file_edits: iter::once((file_id, (edit, None))).collect(),
..Default::default()
}
}
@@ -39,12 +42,31 @@ impl SourceChange {
/// 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) {
+ self.insert_source_and_snippet_edit(file_id, edit, None)
+ }
+
+ /// Inserts a [`TextEdit`] and potentially a [`SnippetEdit`] for the given [`FileId`].
+ /// This properly handles merging existing edits for a file if some already exist.
+ pub fn insert_source_and_snippet_edit(
+ &mut self,
+ file_id: FileId,
+ edit: TextEdit,
+ snippet_edit: Option<SnippetEdit>,
+ ) {
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");
+ let value = entry.get_mut();
+ never!(value.0.union(edit).is_err(), "overlapping edits for same file");
+ never!(
+ value.1.is_some() && snippet_edit.is_some(),
+ "overlapping snippet edits for same file"
+ );
+ if value.1.is_none() {
+ value.1 = snippet_edit;
+ }
}
Entry::Vacant(entry) => {
- entry.insert(edit);
+ entry.insert((edit, snippet_edit));
}
}
}
@@ -53,7 +75,10 @@ impl SourceChange {
self.file_system_edits.push(edit);
}
- pub fn get_source_edit(&self, file_id: FileId) -> Option<&TextEdit> {
+ pub fn get_source_and_snippet_edit(
+ &self,
+ file_id: FileId,
+ ) -> Option<&(TextEdit, Option<SnippetEdit>)> {
self.source_file_edits.get(&file_id)
}
@@ -67,7 +92,18 @@ impl SourceChange {
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));
+ self.extend(iter.into_iter().map(|(file_id, edit)| (file_id, (edit, None))))
+ }
+}
+
+impl Extend<(FileId, (TextEdit, Option<SnippetEdit>))> for SourceChange {
+ fn extend<T: IntoIterator<Item = (FileId, (TextEdit, Option<SnippetEdit>))>>(
+ &mut self,
+ iter: T,
+ ) {
+ iter.into_iter().for_each(|(file_id, (edit, snippet_edit))| {
+ self.insert_source_and_snippet_edit(file_id, edit, snippet_edit)
+ });
}
}
@@ -79,6 +115,8 @@ impl Extend<FileSystemEdit> for SourceChange {
impl From<IntMap<FileId, TextEdit>> for SourceChange {
fn from(source_file_edits: IntMap<FileId, TextEdit>) -> SourceChange {
+ let source_file_edits =
+ source_file_edits.into_iter().map(|(file_id, edit)| (file_id, (edit, None))).collect();
SourceChange { source_file_edits, file_system_edits: Vec::new(), is_snippet: false }
}
}
@@ -91,6 +129,65 @@ impl FromIterator<(FileId, TextEdit)> for SourceChange {
}
}
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct SnippetEdit(Vec<(u32, TextRange)>);
+
+impl SnippetEdit {
+ pub fn new(snippets: Vec<Snippet>) -> Self {
+ let mut snippet_ranges = snippets
+ .into_iter()
+ .zip(1..)
+ .with_position()
+ .map(|pos| {
+ let (snippet, index) = match pos {
+ itertools::Position::First(it) | itertools::Position::Middle(it) => it,
+ // last/only snippet gets index 0
+ itertools::Position::Last((snippet, _))
+ | itertools::Position::Only((snippet, _)) => (snippet, 0),
+ };
+
+ let range = match snippet {
+ Snippet::Tabstop(pos) => TextRange::empty(pos),
+ Snippet::Placeholder(range) => range,
+ };
+ (index, range)
+ })
+ .collect_vec();
+
+ snippet_ranges.sort_by_key(|(_, range)| range.start());
+
+ // Ensure that none of the ranges overlap
+ let disjoint_ranges = snippet_ranges
+ .iter()
+ .zip(snippet_ranges.iter().skip(1))
+ .all(|((_, left), (_, right))| left.end() <= right.start() || left == right);
+ stdx::always!(disjoint_ranges);
+
+ SnippetEdit(snippet_ranges)
+ }
+
+ /// Inserts all of the snippets into the given text.
+ pub fn apply(&self, text: &mut String) {
+ // Start from the back so that we don't have to adjust ranges
+ for (index, range) in self.0.iter().rev() {
+ if range.is_empty() {
+ // is a tabstop
+ text.insert_str(range.start().into(), &format!("${index}"));
+ } else {
+ // is a placeholder
+ text.insert(range.end().into(), '}');
+ text.insert_str(range.start().into(), &format!("${{{index}:"));
+ }
+ }
+ }
+
+ /// Gets the underlying snippet index + text range
+ /// Tabstops are represented by an empty range, and placeholders use the range that they were given
+ pub fn into_edit_ranges(self) -> Vec<(u32, TextRange)> {
+ self.0
+ }
+}
+
pub struct SourceChangeBuilder {
pub edit: TextEditBuilder,
pub file_id: FileId,
@@ -149,24 +246,19 @@ impl SourceChangeBuilder {
}
fn commit(&mut self) {
- // Render snippets first so that they get bundled into the tree diff
- if let Some(mut snippets) = self.snippet_builder.take() {
- // Last snippet always has stop index 0
- let last_stop = snippets.places.pop().unwrap();
- last_stop.place(0);
-
- for (index, stop) in snippets.places.into_iter().enumerate() {
- stop.place(index + 1)
- }
- }
+ let snippet_edit = self.snippet_builder.take().map(|builder| {
+ SnippetEdit::new(
+ builder.places.into_iter().map(PlaceSnippet::finalize_position).collect_vec(),
+ )
+ });
if let Some(tm) = self.mutated_tree.take() {
- algo::diff(&tm.immutable, &tm.mutable_clone).into_text_edit(&mut self.edit)
+ 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);
+ if !edit.is_empty() || snippet_edit.is_some() {
+ self.source_change.insert_source_and_snippet_edit(self.file_id, edit, snippet_edit);
}
}
@@ -237,19 +329,31 @@ impl SourceChangeBuilder {
/// Adds a tabstop snippet to place the cursor before `node`
pub fn add_tabstop_before(&mut self, _cap: SnippetCap, node: impl AstNode) {
assert!(node.syntax().parent().is_some());
- self.add_snippet(PlaceSnippet::Before(node.syntax().clone()));
+ self.add_snippet(PlaceSnippet::Before(node.syntax().clone().into()));
}
/// Adds a tabstop snippet to place the cursor after `node`
pub fn add_tabstop_after(&mut self, _cap: SnippetCap, node: impl AstNode) {
assert!(node.syntax().parent().is_some());
- self.add_snippet(PlaceSnippet::After(node.syntax().clone()));
+ self.add_snippet(PlaceSnippet::After(node.syntax().clone().into()));
+ }
+
+ /// Adds a tabstop snippet to place the cursor before `token`
+ pub fn add_tabstop_before_token(&mut self, _cap: SnippetCap, token: SyntaxToken) {
+ assert!(token.parent().is_some());
+ self.add_snippet(PlaceSnippet::Before(token.clone().into()));
+ }
+
+ /// Adds a tabstop snippet to place the cursor after `token`
+ pub fn add_tabstop_after_token(&mut self, _cap: SnippetCap, token: SyntaxToken) {
+ assert!(token.parent().is_some());
+ self.add_snippet(PlaceSnippet::After(token.clone().into()));
}
/// Adds a snippet to move the cursor selected over `node`
pub fn add_placeholder_snippet(&mut self, _cap: SnippetCap, node: impl AstNode) {
assert!(node.syntax().parent().is_some());
- self.add_snippet(PlaceSnippet::Over(node.syntax().clone()))
+ self.add_snippet(PlaceSnippet::Over(node.syntax().clone().into()))
}
fn add_snippet(&mut self, snippet: PlaceSnippet) {
@@ -260,6 +364,16 @@ impl SourceChangeBuilder {
pub fn finish(mut self) -> SourceChange {
self.commit();
+
+ // Only one file can have snippet edits
+ stdx::never!(self
+ .source_change
+ .source_file_edits
+ .iter()
+ .filter(|(_, (_, snippet_edit))| snippet_edit.is_some())
+ .at_most_one()
+ .is_err());
+
mem::take(&mut self.source_change)
}
}
@@ -281,65 +395,28 @@ impl From<FileSystemEdit> for SourceChange {
}
}
+pub enum Snippet {
+ /// A tabstop snippet (e.g. `$0`).
+ Tabstop(TextSize),
+ /// A placeholder snippet (e.g. `${0:placeholder}`).
+ Placeholder(TextRange),
+}
+
enum PlaceSnippet {
- /// Place a tabstop before a node
- Before(SyntaxNode),
- /// Place a tabstop before a node
- After(SyntaxNode),
- /// Place a placeholder snippet in place of the node
- Over(SyntaxNode),
+ /// Place a tabstop before an element
+ Before(SyntaxElement),
+ /// Place a tabstop before an element
+ After(SyntaxElement),
+ /// Place a placeholder snippet in place of the element
+ Over(SyntaxElement),
}
impl PlaceSnippet {
- /// Places the snippet before or over a node with the given tab stop index
- fn place(self, order: usize) {
- // ensure the target node is still attached
- match &self {
- PlaceSnippet::Before(node) | PlaceSnippet::After(node) | PlaceSnippet::Over(node) => {
- // node should still be in the tree, but if it isn't
- // then it's okay to just ignore this place
- if stdx::never!(node.parent().is_none()) {
- return;
- }
- }
- }
-
+ fn finalize_position(self) -> Snippet {
match self {
- PlaceSnippet::Before(node) => {
- ted::insert_raw(ted::Position::before(&node), Self::make_tab_stop(order));
- }
- PlaceSnippet::After(node) => {
- ted::insert_raw(ted::Position::after(&node), Self::make_tab_stop(order));
- }
- PlaceSnippet::Over(node) => {
- let position = ted::Position::before(&node);
- node.detach();
-
- let snippet = ast::SourceFile::parse(&format!("${{{order}:_}}"))
- .syntax_node()
- .clone_for_update();
-
- let placeholder =
- snippet.descendants().find_map(ast::UnderscoreExpr::cast).unwrap();
- ted::replace(placeholder.syntax(), node);
-
- ted::insert_raw(position, snippet);
- }
+ PlaceSnippet::Before(it) => Snippet::Tabstop(it.text_range().start()),
+ PlaceSnippet::After(it) => Snippet::Tabstop(it.text_range().end()),
+ PlaceSnippet::Over(it) => Snippet::Placeholder(it.text_range()),
}
}
-
- fn make_tab_stop(order: usize) -> SyntaxNode {
- let stop = ast::SourceFile::parse(&format!("stop!(${order})"))
- .syntax_node()
- .descendants()
- .find_map(ast::TokenTree::cast)
- .unwrap()
- .syntax()
- .clone_for_update();
-
- stop.first_token().unwrap().detach();
- stop.last_token().unwrap().detach();
-
- stop
- }
}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/Cargo.toml b/src/tools/rust-analyzer/crates/ide-diagnostics/Cargo.toml
index e18624fcc..14aa39401 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/Cargo.toml
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/Cargo.toml
@@ -16,6 +16,7 @@ cov-mark = "2.0.0-pre.1"
either = "1.7.0"
itertools = "0.10.5"
serde_json = "1.0.86"
+once_cell = "1.17.0"
# local deps
profile.workspace = true
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
index 30576c71f..491005403 100644
--- 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
@@ -1,4 +1,4 @@
-use crate::{Diagnostic, DiagnosticsContext};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: break-outside-of-loop
//
@@ -13,10 +13,11 @@ pub(crate) fn break_outside_of_loop(
let construct = if d.is_break { "break" } else { "continue" };
format!("{construct} outside of loop")
};
- Diagnostic::new(
- "break-outside-of-loop",
+ Diagnostic::new_with_syntax_node_ptr(
+ ctx,
+ DiagnosticCode::RustcHardError("E0268"),
message,
- ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
+ d.expr.clone().map(|it| it.into()),
)
}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/expected_function.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/expected_function.rs
index d2f27664d..e1e5db91c 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/expected_function.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/expected_function.rs
@@ -1,6 +1,6 @@
use hir::HirDisplay;
-use crate::{Diagnostic, DiagnosticsContext};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: expected-function
//
@@ -9,10 +9,11 @@ pub(crate) fn expected_function(
ctx: &DiagnosticsContext<'_>,
d: &hir::ExpectedFunction,
) -> Diagnostic {
- Diagnostic::new(
- "expected-function",
+ Diagnostic::new_with_syntax_node_ptr(
+ ctx,
+ DiagnosticCode::RustcHardError("E0618"),
format!("expected function, found {}", d.found.display(ctx.sema.db)),
- ctx.sema.diagnostics_display_range(d.call.clone().map(|it| it.into())).range,
+ d.call.clone().map(|it| it.into()),
)
.experimental()
}
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
index 2b7105362..3b69640af 100644
--- 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
@@ -5,7 +5,7 @@ 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};
+use crate::{fix, Diagnostic, DiagnosticCode};
pub(crate) fn field_shorthand(acc: &mut Vec<Diagnostic>, file_id: FileId, node: &SyntaxNode) {
match_ast! {
@@ -46,14 +46,17 @@ fn check_expr_field_shorthand(
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,
- )])),
+ Diagnostic::new(
+ DiagnosticCode::Clippy("redundant_field_names"),
+ "Shorthand struct initialization",
+ field_range,
+ )
+ .with_fixes(Some(vec![fix(
+ "use_expr_field_shorthand",
+ "Use struct shorthand initialization",
+ SourceChange::from_text_edit(file_id, edit),
+ field_range,
+ )])),
);
}
}
@@ -87,14 +90,17 @@ fn check_pat_field_shorthand(
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,
- )])),
+ Diagnostic::new(
+ DiagnosticCode::Clippy("redundant_field_names"),
+ "Shorthand struct pattern",
+ field_range,
+ )
+ .with_fixes(Some(vec![fix(
+ "use_pat_field_shorthand",
+ "Use struct field shorthand",
+ SourceChange::from_text_edit(file_id, edit),
+ field_range,
+ )])),
);
}
}
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
index f558b7256..9eb763d3e 100644
--- 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
@@ -1,7 +1,7 @@
use cfg::DnfExpr;
use stdx::format_to;
-use crate::{Diagnostic, DiagnosticsContext, Severity};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, Severity};
// Diagnostic: inactive-code
//
@@ -27,13 +27,12 @@ pub(crate) fn inactive_code(
format_to!(message, ": {}", inactive);
}
}
-
+ // FIXME: This shouldn't be a diagnostic
let res = Diagnostic::new(
- "inactive-code",
+ DiagnosticCode::Ra("inactive-code", Severity::WeakWarning),
message,
ctx.sema.diagnostics_display_range(d.node.clone()).range,
)
- .severity(Severity::WeakWarning)
.with_unused(true);
Some(res)
}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incoherent_impl.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incoherent_impl.rs
index 72af9ebfc..4afb4db03 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incoherent_impl.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incoherent_impl.rs
@@ -1,17 +1,17 @@
use hir::InFile;
-use crate::{Diagnostic, DiagnosticsContext, Severity};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: incoherent-impl
//
// This diagnostic is triggered if the targe type of an impl is from a foreign crate.
pub(crate) fn incoherent_impl(ctx: &DiagnosticsContext<'_>, d: &hir::IncoherentImpl) -> Diagnostic {
- Diagnostic::new(
- "incoherent-impl",
+ Diagnostic::new_with_syntax_node_ptr(
+ ctx,
+ DiagnosticCode::RustcHardError("E0210"),
format!("cannot define inherent `impl` for foreign type"),
- ctx.sema.diagnostics_display_range(InFile::new(d.file_id, d.impl_.clone().into())).range,
+ InFile::new(d.file_id, d.impl_.clone().into()),
)
- .severity(Severity::Error)
}
#[cfg(test)]
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
index 90279e145..235062bf5 100644
--- 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
@@ -1,4 +1,4 @@
-use hir::{db::ExpandDatabase, InFile};
+use hir::{db::ExpandDatabase, CaseType, InFile};
use ide_db::{assists::Assist, defs::NameClass};
use syntax::AstNode;
@@ -6,23 +6,29 @@ use crate::{
// references::rename::rename_with_semantics,
unresolved_fix,
Diagnostic,
+ DiagnosticCode,
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",
+ let code = match d.expected_case {
+ CaseType::LowerSnakeCase => DiagnosticCode::RustcLint("non_snake_case"),
+ CaseType::UpperSnakeCase => DiagnosticCode::RustcLint("non_upper_case_globals"),
+ // The name is lying. It also covers variants, traits, ...
+ CaseType::UpperCamelCase => DiagnosticCode::RustcLint("non_camel_case_types"),
+ };
+ Diagnostic::new_with_syntax_node_ptr(
+ ctx,
+ code,
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,
+ InFile::new(d.file, d.ident.clone().into()),
)
- .severity(Severity::WeakWarning)
.with_fixes(fixes(ctx, d))
}
@@ -149,7 +155,7 @@ impl TestStruct {
check_diagnostics(
r#"
fn FOO() {}
-// ^^^ 💡 weak: Function `FOO` should have snake_case name, e.g. `foo`
+// ^^^ 💡 warn: Function `FOO` should have snake_case name, e.g. `foo`
"#,
);
check_fix(r#"fn FOO$0() {}"#, r#"fn foo() {}"#);
@@ -160,7 +166,7 @@ fn FOO() {}
check_diagnostics(
r#"
fn NonSnakeCaseName() {}
-// ^^^^^^^^^^^^^^^^ 💡 weak: Function `NonSnakeCaseName` should have snake_case name, e.g. `non_snake_case_name`
+// ^^^^^^^^^^^^^^^^ 💡 warn: Function `NonSnakeCaseName` should have snake_case name, e.g. `non_snake_case_name`
"#,
);
}
@@ -170,10 +176,10 @@ fn NonSnakeCaseName() {}
check_diagnostics(
r#"
fn foo(SomeParam: u8) {}
- // ^^^^^^^^^ 💡 weak: Parameter `SomeParam` should have snake_case name, e.g. `some_param`
+ // ^^^^^^^^^ 💡 warn: 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`
+ // ^^^^^^^^^^ 💡 warn: Parameter `CAPS_PARAM` should have snake_case name, e.g. `caps_param`
"#,
);
}
@@ -184,9 +190,9 @@ fn foo2(ok_param: &str, CAPS_PARAM: u8) {}
r#"
fn foo() {
let SOME_VALUE = 10;
- // ^^^^^^^^^^ 💡 weak: Variable `SOME_VALUE` should have snake_case name, e.g. `some_value`
+ // ^^^^^^^^^^ 💡 warn: 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`
+ // ^^^^^^^^^^^^ 💡 warn: Variable `AnotherValue` should have snake_case name, e.g. `another_value`
}
"#,
);
@@ -197,10 +203,10 @@ fn foo() {
check_diagnostics(
r#"
struct non_camel_case_name {}
- // ^^^^^^^^^^^^^^^^^^^ 💡 weak: Structure `non_camel_case_name` should have CamelCase name, e.g. `NonCamelCaseName`
+ // ^^^^^^^^^^^^^^^^^^^ 💡 warn: 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`
+ // ^^^^^^^^^^^^^^ 💡 warn: Structure `SCREAMING_CASE` should have CamelCase name, e.g. `ScreamingCase`
"#,
);
}
@@ -219,7 +225,7 @@ struct AABB {}
check_diagnostics(
r#"
struct SomeStruct { SomeField: u8 }
- // ^^^^^^^^^ 💡 weak: Field `SomeField` should have snake_case name, e.g. `some_field`
+ // ^^^^^^^^^ 💡 warn: Field `SomeField` should have snake_case name, e.g. `some_field`
"#,
);
}
@@ -229,10 +235,10 @@ struct SomeStruct { SomeField: u8 }
check_diagnostics(
r#"
enum some_enum { Val(u8) }
- // ^^^^^^^^^ 💡 weak: Enum `some_enum` should have CamelCase name, e.g. `SomeEnum`
+ // ^^^^^^^^^ 💡 warn: Enum `some_enum` should have CamelCase name, e.g. `SomeEnum`
enum SOME_ENUM {}
- // ^^^^^^^^^ 💡 weak: Enum `SOME_ENUM` should have CamelCase name, e.g. `SomeEnum`
+ // ^^^^^^^^^ 💡 warn: Enum `SOME_ENUM` should have CamelCase name, e.g. `SomeEnum`
"#,
);
}
@@ -251,7 +257,7 @@ enum AABB {}
check_diagnostics(
r#"
enum SomeEnum { SOME_VARIANT(u8) }
- // ^^^^^^^^^^^^ 💡 weak: Variant `SOME_VARIANT` should have CamelCase name, e.g. `SomeVariant`
+ // ^^^^^^^^^^^^ 💡 warn: Variant `SOME_VARIANT` should have CamelCase name, e.g. `SomeVariant`
"#,
);
}
@@ -261,7 +267,7 @@ enum SomeEnum { SOME_VARIANT(u8) }
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`
+ // ^^^^^^^^^^^^^^^^ 💡 warn: Constant `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST`
"#,
);
}
@@ -271,7 +277,7 @@ const some_weird_const: u8 = 10;
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`
+ // ^^^^^^^^^^^^^^^^ 💡 warn: Static variable `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST`
"#,
);
}
@@ -281,13 +287,13 @@ static some_weird_const: u8 = 10;
check_diagnostics(
r#"
struct someStruct;
- // ^^^^^^^^^^ 💡 weak: Structure `someStruct` should have CamelCase name, e.g. `SomeStruct`
+ // ^^^^^^^^^^ 💡 warn: 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`
+ // ^^^^^^^^ 💡 warn: 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`
+ // ^^^^^^^^^^^^^^^ 💡 warn: Variable `WHY_VAR_IS_CAPS` should have snake_case name, e.g. `why_var_is_caps`
}
}
"#,
@@ -319,7 +325,7 @@ 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`
+ // ^^^^^^^^ 💡 warn: Variable `SOME_VAR` should have snake_case name, e.g. `some_var`
Some => (),
}
}
@@ -461,7 +467,7 @@ mod CheckNonstandardStyle {
#[allow(bad_style)]
mod CheckBadStyle {
- fn HiImABadFnName() {}
+ struct fooo;
}
mod F {
@@ -483,4 +489,156 @@ pub static SomeStatic: u8 = 10;
"#,
);
}
+
+ #[test]
+ fn deny_attributes() {
+ check_diagnostics(
+ r#"
+#[deny(non_snake_case)]
+fn NonSnakeCaseName(some_var: u8) -> u8 {
+ //^^^^^^^^^^^^^^^^ 💡 error: Function `NonSnakeCaseName` should have snake_case name, e.g. `non_snake_case_name`
+ // cov_flags generated output from elsewhere in this file
+ extern "C" {
+ #[no_mangle]
+ static lower_case: u8;
+ }
+
+ let OtherVar = some_var + 1;
+ //^^^^^^^^ 💡 error: Variable `OtherVar` should have snake_case name, e.g. `other_var`
+ OtherVar
+}
+
+#[deny(nonstandard_style)]
+mod CheckNonstandardStyle {
+ fn HiImABadFnName() {}
+ //^^^^^^^^^^^^^^ 💡 error: Function `HiImABadFnName` should have snake_case name, e.g. `hi_im_abad_fn_name`
+}
+
+#[deny(warnings)]
+mod CheckBadStyle {
+ struct fooo;
+ //^^^^ 💡 error: Structure `fooo` should have CamelCase name, e.g. `Fooo`
+}
+
+mod F {
+ #![deny(non_snake_case)]
+ fn CheckItWorksWithModAttr() {}
+ //^^^^^^^^^^^^^^^^^^^^^^^ 💡 error: Function `CheckItWorksWithModAttr` should have snake_case name, e.g. `check_it_works_with_mod_attr`
+}
+
+#[deny(non_snake_case, non_camel_case_types)]
+pub struct some_type {
+ //^^^^^^^^^ 💡 error: Structure `some_type` should have CamelCase name, e.g. `SomeType`
+ SOME_FIELD: u8,
+ //^^^^^^^^^^ 💡 error: Field `SOME_FIELD` should have snake_case name, e.g. `some_field`
+ SomeField: u16,
+ //^^^^^^^^^ 💡 error: Field `SomeField` should have snake_case name, e.g. `some_field`
+}
+
+#[deny(non_upper_case_globals)]
+pub const some_const: u8 = 10;
+ //^^^^^^^^^^ 💡 error: Constant `some_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST`
+
+#[deny(non_upper_case_globals)]
+pub static SomeStatic: u8 = 10;
+ //^^^^^^^^^^ 💡 error: Static variable `SomeStatic` should have UPPER_SNAKE_CASE name, e.g. `SOME_STATIC`
+ "#,
+ );
+ }
+
+ #[test]
+ fn fn_inner_items() {
+ check_diagnostics(
+ r#"
+fn main() {
+ const foo: bool = true;
+ //^^^ 💡 warn: Constant `foo` should have UPPER_SNAKE_CASE name, e.g. `FOO`
+ static bar: bool = true;
+ //^^^ 💡 warn: Static variable `bar` should have UPPER_SNAKE_CASE name, e.g. `BAR`
+ fn BAZ() {
+ //^^^ 💡 warn: Function `BAZ` should have snake_case name, e.g. `baz`
+ const foo: bool = true;
+ //^^^ 💡 warn: Constant `foo` should have UPPER_SNAKE_CASE name, e.g. `FOO`
+ static bar: bool = true;
+ //^^^ 💡 warn: Static variable `bar` should have UPPER_SNAKE_CASE name, e.g. `BAR`
+ fn BAZ() {
+ //^^^ 💡 warn: Function `BAZ` should have snake_case name, e.g. `baz`
+ let INNER_INNER = 42;
+ //^^^^^^^^^^^ 💡 warn: Variable `INNER_INNER` should have snake_case name, e.g. `inner_inner`
+ }
+
+ let INNER_LOCAL = 42;
+ //^^^^^^^^^^^ 💡 warn: Variable `INNER_LOCAL` should have snake_case name, e.g. `inner_local`
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn const_body_inner_items() {
+ check_diagnostics(
+ r#"
+const _: () = {
+ static bar: bool = true;
+ //^^^ 💡 warn: Static variable `bar` should have UPPER_SNAKE_CASE name, e.g. `BAR`
+ fn BAZ() {}
+ //^^^ 💡 warn: Function `BAZ` should have snake_case name, e.g. `baz`
+
+ const foo: () = {
+ //^^^ 💡 warn: Constant `foo` should have UPPER_SNAKE_CASE name, e.g. `FOO`
+ const foo: bool = true;
+ //^^^ 💡 warn: Constant `foo` should have UPPER_SNAKE_CASE name, e.g. `FOO`
+ static bar: bool = true;
+ //^^^ 💡 warn: Static variable `bar` should have UPPER_SNAKE_CASE name, e.g. `BAR`
+ fn BAZ() {}
+ //^^^ 💡 warn: Function `BAZ` should have snake_case name, e.g. `baz`
+ };
+};
+"#,
+ );
+ }
+
+ #[test]
+ fn static_body_inner_items() {
+ check_diagnostics(
+ r#"
+static FOO: () = {
+ const foo: bool = true;
+ //^^^ 💡 warn: Constant `foo` should have UPPER_SNAKE_CASE name, e.g. `FOO`
+ fn BAZ() {}
+ //^^^ 💡 warn: Function `BAZ` should have snake_case name, e.g. `baz`
+
+ static bar: () = {
+ //^^^ 💡 warn: Static variable `bar` should have UPPER_SNAKE_CASE name, e.g. `BAR`
+ const foo: bool = true;
+ //^^^ 💡 warn: Constant `foo` should have UPPER_SNAKE_CASE name, e.g. `FOO`
+ static bar: bool = true;
+ //^^^ 💡 warn: Static variable `bar` should have UPPER_SNAKE_CASE name, e.g. `BAR`
+ fn BAZ() {}
+ //^^^ 💡 warn: Function `BAZ` should have snake_case name, e.g. `baz`
+ };
+};
+"#,
+ );
+ }
+
+ #[test]
+ fn enum_variant_body_inner_item() {
+ check_diagnostics(
+ r#"
+enum E {
+ A = {
+ const foo: bool = true;
+ //^^^ 💡 warn: Constant `foo` should have UPPER_SNAKE_CASE name, e.g. `FOO`
+ static bar: bool = true;
+ //^^^ 💡 warn: Static variable `bar` should have UPPER_SNAKE_CASE name, e.g. `BAR`
+ fn BAZ() {}
+ //^^^ 💡 warn: Function `BAZ` should have snake_case name, e.g. `baz`
+ 42
+ },
+}
+"#,
+ );
+ }
}
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
index c779266bc..1ec17952b 100644
--- 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
@@ -1,4 +1,4 @@
-use crate::{Diagnostic, DiagnosticsContext, Severity};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: invalid-derive-target
//
@@ -11,11 +11,10 @@ pub(crate) fn invalid_derive_target(
let display_range = ctx.sema.diagnostics_display_range(d.node.clone()).range;
Diagnostic::new(
- "invalid-derive-target",
+ DiagnosticCode::RustcHardError("E0774"),
"`derive` may only be applied to `struct`s, `enum`s and `union`s",
display_range,
)
- .severity(Severity::Error)
}
#[cfg(test)]
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs
index 04ce1e0fe..a337e2660 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs
@@ -17,7 +17,7 @@ use syntax::{
};
use text_edit::TextEdit;
-use crate::{fix, Diagnostic, DiagnosticsConfig, Severity};
+use crate::{fix, Diagnostic, DiagnosticCode, DiagnosticsConfig, Severity};
#[derive(Default)]
struct State {
@@ -117,11 +117,10 @@ pub(crate) fn json_in_items(
edit.insert(range.start(), state.result);
acc.push(
Diagnostic::new(
- "json-is-not-rust",
+ DiagnosticCode::Ra("json-is-not-rust", Severity::WeakWarning),
"JSON syntax is not valid as a Rust item",
range,
)
- .severity(Severity::WeakWarning)
.with_fixes(Some(vec![{
let mut scb = SourceChangeBuilder::new(file_id);
let scope = match import_scope {
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
index 7547779a9..f54cdd63b 100644
--- 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
@@ -1,4 +1,4 @@
-use crate::{Diagnostic, DiagnosticsContext};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, Severity};
// Diagnostic: macro-error
//
@@ -6,7 +6,12 @@ use crate::{Diagnostic, DiagnosticsContext};
pub(crate) fn macro_error(ctx: &DiagnosticsContext<'_>, d: &hir::MacroError) -> Diagnostic {
// Use more accurate position if available.
let display_range = ctx.resolve_precise_location(&d.node, d.precise_location);
- Diagnostic::new("macro-error", d.message.clone(), display_range).experimental()
+ Diagnostic::new(
+ DiagnosticCode::Ra("macro-error", Severity::Error),
+ d.message.clone(),
+ display_range,
+ )
+ .experimental()
}
// Diagnostic: macro-error
@@ -16,7 +21,12 @@ pub(crate) fn macro_def_error(ctx: &DiagnosticsContext<'_>, d: &hir::MacroDefErr
// Use more accurate position if available.
let display_range =
ctx.resolve_precise_location(&d.node.clone().map(|it| it.syntax_node_ptr()), d.name);
- Diagnostic::new("macro-def-error", d.message.clone(), display_range).experimental()
+ Diagnostic::new(
+ DiagnosticCode::Ra("macro-def-error", Severity::Error),
+ d.message.clone(),
+ display_range,
+ )
+ .experimental()
}
#[cfg(test)]
@@ -41,6 +51,9 @@ macro_rules! compile_error { () => {} }
compile_error!("compile_error macro works");
//^^^^^^^^^^^^^ error: compile_error macro works
+
+ compile_error! { "compile_error macro braced works" }
+//^^^^^^^^^^^^^ error: compile_error macro braced works
"#,
);
}
@@ -67,7 +80,7 @@ macro_rules! m {
fn f() {
m!();
- //^^^^ error: unresolved macro `$crate::private::concat!`
+ //^^^^ error: unresolved macro $crate::private::concat
}
//- /core.rs crate:core
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
index cd48bdba0..fc57dde69 100644
--- 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
@@ -1,4 +1,4 @@
-use crate::{Diagnostic, DiagnosticsContext, Severity};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: malformed-derive
//
@@ -10,11 +10,10 @@ pub(crate) fn malformed_derive(
let display_range = ctx.sema.diagnostics_display_range(d.node.clone()).range;
Diagnostic::new(
- "malformed-derive",
+ DiagnosticCode::RustcHardError("E0777"),
"malformed derive input, derive attributes are of the form `#[derive(Derive1, Derive2, ...)]`",
display_range,
)
- .severity(Severity::Error)
}
#[cfg(test)]
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
index c5db8c374..6238c7e09 100644
--- 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
@@ -3,7 +3,7 @@ use syntax::{
AstNode, TextRange,
};
-use crate::{adjusted_display_range, Diagnostic, DiagnosticsContext};
+use crate::{adjusted_display_range, Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: mismatched-arg-count
//
@@ -14,7 +14,7 @@ pub(crate) fn mismatched_arg_count(
) -> Diagnostic {
let s = if d.expected == 1 { "" } else { "s" };
let message = format!("expected {} argument{s}, found {}", d.expected, d.found);
- Diagnostic::new("mismatched-arg-count", message, invalid_args_range(ctx, d))
+ Diagnostic::new(DiagnosticCode::RustcHardError("E0107"), message, invalid_args_range(ctx, d))
}
fn invalid_args_range(ctx: &DiagnosticsContext<'_>, d: &hir::MismatchedArgCount) -> TextRange {
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
index 60ccc41df..acc31cd11 100644
--- 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
@@ -15,7 +15,7 @@ use syntax::{
};
use text_edit::TextEdit;
-use crate::{fix, Diagnostic, DiagnosticsContext};
+use crate::{fix, Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: missing-fields
//
@@ -42,7 +42,7 @@ pub(crate) fn missing_fields(ctx: &DiagnosticsContext<'_>, d: &hir::MissingField
.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)
+ Diagnostic::new_with_syntax_node_ptr(ctx, DiagnosticCode::RustcHardError("E0063"), message, ptr)
.with_fixes(fixes(ctx, d))
}
@@ -208,7 +208,7 @@ fn get_default_constructor(
}
let krate = ctx.sema.to_module_def(d.file.original_file(ctx.sema.db))?.krate();
- let module = krate.root_module(ctx.sema.db);
+ let module = krate.root_module();
// Look for a ::new() associated function
let has_new_func = ty
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
index 3f13b97a4..82a9a3bd5 100644
--- 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
@@ -1,4 +1,4 @@
-use crate::{Diagnostic, DiagnosticsContext};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: missing-match-arm
//
@@ -7,10 +7,11 @@ pub(crate) fn missing_match_arms(
ctx: &DiagnosticsContext<'_>,
d: &hir::MissingMatchArms,
) -> Diagnostic {
- Diagnostic::new(
- "missing-match-arm",
+ Diagnostic::new_with_syntax_node_ptr(
+ ctx,
+ DiagnosticCode::RustcHardError("E0004"),
format!("missing match arm: {}", d.uncovered_patterns),
- ctx.sema.diagnostics_display_range(d.scrutinee_expr.clone().map(Into::into)).range,
+ d.scrutinee_expr.clone().map(Into::into),
)
}
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
index 2026b6fce..70b26009b 100644
--- 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
@@ -4,16 +4,17 @@ use syntax::{ast, SyntaxNode};
use syntax::{match_ast, AstNode};
use text_edit::TextEdit;
-use crate::{fix, Diagnostic, DiagnosticsContext};
+use crate::{fix, Diagnostic, DiagnosticCode, 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",
+ Diagnostic::new_with_syntax_node_ptr(
+ ctx,
+ DiagnosticCode::RustcHardError("E0133"),
"this operation is unsafe and requires an unsafe function or block",
- ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
+ d.expr.clone().map(|it| it.into()),
)
.with_fixes(fixes(ctx, d))
}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/moved_out_of_ref.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/moved_out_of_ref.rs
index 32e321107..3aa4aa970 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/moved_out_of_ref.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/moved_out_of_ref.rs
@@ -1,14 +1,15 @@
-use crate::{Diagnostic, DiagnosticsContext};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
use hir::HirDisplay;
// Diagnostic: moved-out-of-ref
//
// This diagnostic is triggered on moving non copy things out of references.
pub(crate) fn moved_out_of_ref(ctx: &DiagnosticsContext<'_>, d: &hir::MovedOutOfRef) -> Diagnostic {
- Diagnostic::new(
- "moved-out-of-ref",
+ Diagnostic::new_with_syntax_node_ptr(
+ ctx,
+ DiagnosticCode::RustcHardError("E0507"),
format!("cannot move `{}` out of reference", d.ty.display(ctx.sema.db)),
- ctx.sema.diagnostics_display_range(d.span.clone()).range,
+ d.span.clone(),
)
.experimental() // spans are broken, and I'm not sure how precise we can detect copy types
}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mutability_errors.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mutability_errors.rs
index f61460e31..e0c3bedce 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mutability_errors.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mutability_errors.rs
@@ -2,7 +2,7 @@ use ide_db::source_change::SourceChange;
use syntax::{AstNode, SyntaxKind, SyntaxNode, SyntaxToken, T};
use text_edit::TextEdit;
-use crate::{fix, Diagnostic, DiagnosticsContext, Severity};
+use crate::{fix, Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: need-mut
//
@@ -29,13 +29,15 @@ pub(crate) fn need_mut(ctx: &DiagnosticsContext<'_>, d: &hir::NeedMut) -> Diagno
use_range,
)])
})();
- Diagnostic::new(
- "need-mut",
+ Diagnostic::new_with_syntax_node_ptr(
+ ctx,
+ // FIXME: `E0384` is not the only error that this diagnostic handles
+ DiagnosticCode::RustcHardError("E0384"),
format!(
"cannot mutate immutable variable `{}`",
d.local.name(ctx.sema.db).display(ctx.sema.db)
),
- ctx.sema.diagnostics_display_range(d.span.clone()).range,
+ d.span.clone(),
)
.with_fixes(fixes)
}
@@ -68,12 +70,12 @@ pub(crate) fn unused_mut(ctx: &DiagnosticsContext<'_>, d: &hir::UnusedMut) -> Di
)])
})();
let ast = d.local.primary_source(ctx.sema.db).syntax_ptr();
- Diagnostic::new(
- "unused-mut",
+ Diagnostic::new_with_syntax_node_ptr(
+ ctx,
+ DiagnosticCode::RustcLint("unused_mut"),
"variable does not need to be mutable",
- ctx.sema.diagnostics_display_range(ast).range,
+ ast,
)
- .severity(Severity::WeakWarning)
.experimental() // Not supporting `#[allow(unused_mut)]` leads to false positive.
.with_fixes(fixes)
}
@@ -93,7 +95,7 @@ mod tests {
fn f(_: i32) {}
fn main() {
let mut x = 2;
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
f(x);
}
"#,
@@ -268,7 +270,7 @@ fn main() {
fn f(_: i32) {}
fn main() {
let mut x = (2, 7);
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
f(x.1);
}
"#,
@@ -302,7 +304,7 @@ fn main() {
r#"
fn main() {
let mut x = &mut 2;
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
*x = 5;
}
"#,
@@ -346,7 +348,7 @@ fn main() {
r#"
//- minicore: copy, builtin_impls
fn clone(mut i: &!) -> ! {
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
*i
}
"#,
@@ -360,7 +362,7 @@ fn main() {
//- minicore: option
fn main() {
let mut v = &mut Some(2);
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
let _ = || match v {
Some(k) => {
*k = 5;
@@ -386,7 +388,7 @@ fn main() {
fn main() {
match (2, 3) {
(x, mut y) => {
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
x = 7;
//^^^^^ 💡 error: cannot mutate immutable variable `x`
}
@@ -407,7 +409,7 @@ fn main() {
fn main() {
return;
let mut x = 2;
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
&mut x;
}
"#,
@@ -417,7 +419,7 @@ fn main() {
fn main() {
loop {}
let mut x = 2;
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
&mut x;
}
"#,
@@ -438,7 +440,7 @@ fn main(b: bool) {
g();
}
let mut x = 2;
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
&mut x;
}
"#,
@@ -452,7 +454,7 @@ fn main(b: bool) {
return;
}
let mut x = 2;
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
&mut x;
}
"#,
@@ -466,7 +468,7 @@ fn main(b: bool) {
fn f(_: i32) {}
fn main() {
let mut x;
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
x = 5;
f(x);
}
@@ -477,7 +479,7 @@ fn main() {
fn f(_: i32) {}
fn main(b: bool) {
let mut x;
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
if b {
x = 1;
} else {
@@ -552,15 +554,15 @@ fn f(_: i32) {}
fn main() {
loop {
let mut x = 1;
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
f(x);
if let mut y = 2 {
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
f(y);
}
match 3 {
mut z => f(z),
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
}
}
}
@@ -577,9 +579,9 @@ fn main() {
loop {
let c @ (
mut b,
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
mut d
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
);
a = 1;
//^^^^^ 💡 error: cannot mutate immutable variable `a`
@@ -597,7 +599,7 @@ fn main() {
check_diagnostics(
r#"
fn f(mut x: i32) {
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
}
"#,
);
@@ -640,7 +642,7 @@ fn f() {
//- minicore: iterators, copy
fn f(x: [(i32, u8); 10]) {
for (a, mut b) in x {
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
a = 2;
//^^^^^ 💡 error: cannot mutate immutable variable `a`
}
@@ -657,9 +659,9 @@ fn f(x: [(i32, u8); 10]) {
fn f(x: [(i32, u8); 10]) {
let mut it = x.into_iter();
while let Some((a, mut b)) = it.next() {
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
while let Some((c, mut d)) = it.next() {
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
a = 2;
//^^^^^ 💡 error: cannot mutate immutable variable `a`
c = 2;
@@ -683,7 +685,7 @@ fn f() {
let x = &mut x;
//^^^^^^ 💡 error: cannot mutate immutable variable `x`
let mut x = x;
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
x[2] = 5;
}
"#,
@@ -711,13 +713,13 @@ impl IndexMut<usize> for Foo {
}
fn f() {
let mut x = Foo;
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
let y = &x[2];
let x = Foo;
let y = &mut x[2];
//^💡 error: cannot mutate immutable variable `x`
let mut x = &mut Foo;
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
let y: &mut (i32, u8) = &mut x[2];
let x = Foo;
let ref mut y = x[7];
@@ -731,7 +733,7 @@ fn f() {
}
let mut x = Foo;
let mut i = 5;
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
let y = &mut x[i];
}
"#,
@@ -759,7 +761,7 @@ impl DerefMut for Foo {
}
fn f() {
let mut x = Foo;
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
let y = &*x;
let x = Foo;
let y = &mut *x;
@@ -790,11 +792,27 @@ fn f() {
fn f(_: i32) {}
fn main() {
let ((Some(mut x), None) | (_, Some(mut x))) = (None, Some(7));
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
f(x);
}
"#,
);
+ check_diagnostics(
+ r#"
+struct Foo(i32);
+
+const X: Foo = Foo(5);
+const Y: Foo = Foo(12);
+
+const fn f(mut a: Foo) -> bool {
+ //^^^^^ 💡 warn: variable does not need to be mutable
+ match a {
+ X | Y => true,
+ _ => false,
+ }
+}
+"#,
+ );
}
#[test]
@@ -842,7 +860,7 @@ pub struct TreeLeaf {
pub fn test() {
let mut tree = Tree::Leaf(
- //^^^^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^^^^ 💡 warn: variable does not need to be mutable
TreeLeaf {
depth: 0,
data: 0
@@ -859,7 +877,7 @@ pub fn test() {
r#"
//- minicore: fn
fn fn_ref(mut x: impl Fn(u8) -> u8) -> u8 {
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
x(2)
}
fn fn_mut(x: impl FnMut(u8) -> u8) -> u8 {
@@ -867,11 +885,11 @@ fn fn_mut(x: impl FnMut(u8) -> u8) -> u8 {
//^ 💡 error: cannot mutate immutable variable `x`
}
fn fn_borrow_mut(mut x: &mut impl FnMut(u8) -> u8) -> u8 {
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
x(2)
}
fn fn_once(mut x: impl FnOnce(u8) -> u8) -> u8 {
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
x(2)
}
"#,
@@ -915,14 +933,14 @@ fn fn_once(mut x: impl FnOnce(u8) -> u8) -> u8 {
//- minicore: copy, fn
fn f() {
let mut x = 5;
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
let mut y = 2;
y = 7;
let closure = || {
let mut z = 8;
z = 3;
let mut k = z;
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
};
}
"#,
@@ -949,7 +967,7 @@ fn f() {
fn f() {
struct X;
let mut x = X;
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
let c1 = || x;
let mut x = X;
let c2 = || { x = X; x };
@@ -965,12 +983,12 @@ fn f() {
fn f() {
let mut x = &mut 5;
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
let closure1 = || { *x = 2; };
let _ = closure1();
//^^^^^^^^ 💡 error: cannot mutate immutable variable `closure1`
let mut x = &mut 5;
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
let closure1 = || { *x = 2; &x; };
let _ = closure1();
//^^^^^^^^ 💡 error: cannot mutate immutable variable `closure1`
@@ -979,12 +997,12 @@ fn f() {
let _ = closure1();
//^^^^^^^^ 💡 error: cannot mutate immutable variable `closure1`
let mut x = &mut 5;
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
let closure1 = move || { *x = 2; };
let _ = closure1();
//^^^^^^^^ 💡 error: cannot mutate immutable variable `closure1`
let mut x = &mut X(1, 2);
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
let closure1 = || { x.0 = 2; };
let _ = closure1();
//^^^^^^^^ 💡 error: cannot mutate immutable variable `closure1`
@@ -1001,7 +1019,7 @@ fn f() {
fn x(t: &[u8]) {
match t {
&[a, mut b] | &[a, _, mut b] => {
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
a = 2;
//^^^^^ 💡 error: cannot mutate immutable variable `a`
@@ -1055,7 +1073,7 @@ fn f() {
*x = 7;
//^^^^^^ 💡 error: cannot mutate immutable variable `x`
let mut y = Box::new(5);
- //^^^^^ 💡 weak: variable does not need to be mutable
+ //^^^^^ 💡 warn: variable does not need to be mutable
*x = *y;
//^^^^^^^ 💡 error: cannot mutate immutable variable `x`
let x = Box::new(5);
@@ -1067,6 +1085,33 @@ fn f() {
}
#[test]
+ fn regression_15143() {
+ check_diagnostics(
+ r#"
+ trait Tr {
+ type Ty;
+ }
+
+ struct A;
+
+ impl Tr for A {
+ type Ty = (u32, i64);
+ }
+
+ struct B<T: Tr> {
+ f: <T as Tr>::Ty,
+ }
+
+ fn main(b: B<A>) {
+ let f = b.f.0;
+ f = 5;
+ //^^^^^ 💡 error: cannot mutate immutable variable `f`
+ }
+ "#,
+ );
+ }
+
+ #[test]
fn allow_unused_mut_for_identifiers_starting_with_underline() {
check_diagnostics(
r#"
@@ -1080,17 +1125,51 @@ fn main() {
}
#[test]
- fn respect_allow_unused_mut() {
- // FIXME: respect
+ fn respect_lint_attributes_for_unused_mut() {
check_diagnostics(
r#"
fn f(_: i32) {}
fn main() {
#[allow(unused_mut)]
let mut x = 2;
- //^^^^^ 💡 weak: variable does not need to be mutable
f(x);
}
+
+fn main2() {
+ #[deny(unused_mut)]
+ let mut x = 2;
+ //^^^^^ 💡 error: variable does not need to be mutable
+ f(x);
+}
+"#,
+ );
+ check_diagnostics(
+ r#"
+macro_rules! mac {
+ ($($x:expr),*$(,)*) => ({
+ #[allow(unused_mut)]
+ let mut vec = 2;
+ vec
+ });
+}
+
+fn main2() {
+ let mut x = mac![];
+ //^^^^^ 💡 warn: variable does not need to be mutable
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn regression_15099() {
+ check_diagnostics(
+ r#"
+//- minicore: iterator, range
+fn f() {
+ loop {}
+ for _ in 0..2 {}
+}
"#,
);
}
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
index a39eceab2..a34a5824f 100644
--- 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
@@ -6,16 +6,17 @@ use syntax::{
};
use text_edit::TextEdit;
-use crate::{fix, Assist, Diagnostic, DiagnosticsContext};
+use crate::{fix, Assist, Diagnostic, DiagnosticCode, 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",
+ Diagnostic::new_with_syntax_node_ptr(
+ ctx,
+ DiagnosticCode::RustcHardError("E0559"),
"no such field",
- ctx.sema.diagnostics_display_range(d.field.clone().map(|it| it.into())).range,
+ d.field.clone().map(|it| it.into()),
)
.with_fixes(fixes(ctx, d))
}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_assoc_item.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_assoc_item.rs
index 4cd85a479..c44d28e77 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_assoc_item.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_assoc_item.rs
@@ -1,6 +1,6 @@
use either::Either;
-use crate::{Diagnostic, DiagnosticsContext};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: private-assoc-item
//
@@ -16,8 +16,9 @@ pub(crate) fn private_assoc_item(
.name(ctx.sema.db)
.map(|name| format!("`{}` ", name.display(ctx.sema.db)))
.unwrap_or_default();
- Diagnostic::new(
- "private-assoc-item",
+ Diagnostic::new_with_syntax_node_ptr(
+ ctx,
+ DiagnosticCode::RustcHardError("E0624"),
format!(
"{} {}is private",
match d.item {
@@ -27,15 +28,13 @@ pub(crate) fn private_assoc_item(
},
name,
),
- ctx.sema
- .diagnostics_display_range(d.expr_or_pat.clone().map(|it| match it {
+ d.expr_or_pat.clone().map(|it| match it {
+ Either::Left(it) => it.into(),
+ Either::Right(it) => match it {
Either::Left(it) => it.into(),
- Either::Right(it) => match it {
- Either::Left(it) => it.into(),
- Either::Right(it) => it.into(),
- },
- }))
- .range,
+ Either::Right(it) => it.into(),
+ },
+ }),
)
}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_field.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_field.rs
index de7f51f69..553defcf9 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_field.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_field.rs
@@ -1,18 +1,19 @@
-use crate::{Diagnostic, DiagnosticsContext};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: private-field
//
// This diagnostic is triggered if the accessed field is not visible from the current module.
pub(crate) fn private_field(ctx: &DiagnosticsContext<'_>, d: &hir::PrivateField) -> Diagnostic {
// FIXME: add quickfix
- Diagnostic::new(
- "private-field",
+ Diagnostic::new_with_syntax_node_ptr(
+ ctx,
+ DiagnosticCode::RustcHardError("E0616"),
format!(
"field `{}` of `{}` is private",
d.field.name(ctx.sema.db).display(ctx.sema.db),
d.field.parent_def(ctx.sema.db).name(ctx.sema.db).display(ctx.sema.db)
),
- ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
+ d.expr.clone().map(|it| it.into()),
)
}
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
index d3eda3c5e..083ef3e8d 100644
--- 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
@@ -6,7 +6,7 @@ use syntax::{
};
use text_edit::TextEdit;
-use crate::{fix, Assist, Diagnostic, DiagnosticsContext, Severity};
+use crate::{fix, Assist, Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: replace-filter-map-next-with-find-map
//
@@ -15,12 +15,12 @@ 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",
+ Diagnostic::new_with_syntax_node_ptr(
+ ctx,
+ DiagnosticCode::Clippy("filter_map_next"),
"replace filter_map(..).next() with find_map(..)",
- ctx.sema.diagnostics_display_range(InFile::new(d.file, d.next_expr.clone().into())).range,
+ InFile::new(d.file, d.next_expr.clone().into()),
)
- .severity(Severity::WeakWarning)
.with_fixes(fixes(ctx, d))
}
@@ -64,7 +64,7 @@ mod tests {
pub(crate) fn check_diagnostics(ra_fixture: &str) {
let mut config = DiagnosticsConfig::test_sample();
config.disabled.insert("inactive-code".to_string());
- config.disabled.insert("unresolved-method".to_string());
+ config.disabled.insert("E0599".to_string());
check_diagnostics_with_config(config, ra_fixture)
}
@@ -139,4 +139,33 @@ fn foo() {
"#,
)
}
+
+ #[test]
+ fn respect_lint_attributes_for_clippy_equivalent() {
+ check_diagnostics(
+ r#"
+//- minicore: iterators
+
+fn foo() {
+ #[allow(clippy::filter_map_next)]
+ let m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
+}
+
+#[deny(clippy::filter_map_next)]
+fn foo() {
+ let m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
+} //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 error: replace filter_map(..).next() with find_map(..)
+
+fn foo() {
+ let m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
+} //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 weak: replace filter_map(..).next() with find_map(..)
+
+#[warn(clippy::filter_map_next)]
+fn foo() {
+ let m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
+} //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 warn: replace filter_map(..).next() with find_map(..)
+
+"#,
+ );
+ }
}
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
index c28f98d83..15bd28c00 100644
--- 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
@@ -7,7 +7,7 @@ use syntax::{
};
use text_edit::TextEdit;
-use crate::{adjusted_display_range, fix, Assist, Diagnostic, DiagnosticsContext};
+use crate::{adjusted_display_range, fix, Assist, Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: type-mismatch
//
@@ -39,7 +39,7 @@ pub(crate) fn type_mismatch(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch)
}
};
let mut diag = Diagnostic::new(
- "type-mismatch",
+ DiagnosticCode::RustcHardError("E0308"),
format!(
"expected {}, found {}",
d.expected.display(ctx.sema.db).with_closure_style(ClosureStyle::ClosureWithId),
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/typed_hole.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/typed_hole.rs
index e12bbcf68..4af672271 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/typed_hole.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/typed_hole.rs
@@ -7,7 +7,7 @@ use ide_db::{
use syntax::AstNode;
use text_edit::TextEdit;
-use crate::{Diagnostic, DiagnosticsContext};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: typed-hole
//
@@ -26,7 +26,8 @@ pub(crate) fn typed_hole(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Di
)
};
- Diagnostic::new("typed-hole", message, display_range.range).with_fixes(fixes)
+ Diagnostic::new(DiagnosticCode::RustcHardError("typed-hole"), message, display_range.range)
+ .with_fixes(fixes)
}
fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Option<Vec<Assist>> {
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/undeclared_label.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/undeclared_label.rs
index 034e4fcfb..7de9a9a32 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/undeclared_label.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/undeclared_label.rs
@@ -1,4 +1,4 @@
-use crate::{Diagnostic, DiagnosticsContext};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: undeclared-label
pub(crate) fn undeclared_label(
@@ -6,10 +6,11 @@ pub(crate) fn undeclared_label(
d: &hir::UndeclaredLabel,
) -> Diagnostic {
let name = &d.name;
- Diagnostic::new(
- "undeclared-label",
+ Diagnostic::new_with_syntax_node_ptr(
+ ctx,
+ DiagnosticCode::RustcHardError("undeclared-label"),
format!("use of undeclared label `{}`", name.display(ctx.sema.db)),
- ctx.sema.diagnostics_display_range(d.node.clone().map(|it| it.into())).range,
+ d.node.clone().map(|it| it.into()),
)
}
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
index e879de75c..bcce72a7d 100644
--- 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
@@ -1,4 +1,4 @@
-use crate::{Diagnostic, DiagnosticsContext, Severity};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, Severity};
// Diagnostic: unimplemented-builtin-macro
//
@@ -7,10 +7,10 @@ pub(crate) fn unimplemented_builtin_macro(
ctx: &DiagnosticsContext<'_>,
d: &hir::UnimplementedBuiltinMacro,
) -> Diagnostic {
- Diagnostic::new(
- "unimplemented-builtin-macro",
+ Diagnostic::new_with_syntax_node_ptr(
+ ctx,
+ DiagnosticCode::Ra("unimplemented-builtin-macro", Severity::WeakWarning),
"unimplemented built-in macro".to_string(),
- ctx.sema.diagnostics_display_range(d.node.clone()).range,
+ d.node.clone(),
)
- .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
index 271e7ce73..e04f27c27 100644
--- 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
@@ -14,7 +14,7 @@ use syntax::{
};
use text_edit::TextEdit;
-use crate::{fix, Assist, Diagnostic, DiagnosticsContext, Severity};
+use crate::{fix, Assist, Diagnostic, DiagnosticCode, DiagnosticsContext, Severity};
// Diagnostic: unlinked-file
//
@@ -46,8 +46,7 @@ pub(crate) fn unlinked_file(
.unwrap_or(range);
acc.push(
- Diagnostic::new("unlinked-file", message, range)
- .severity(Severity::WeakWarning)
+ Diagnostic::new(DiagnosticCode::Ra("unlinked-file", Severity::WeakWarning), message, range)
.with_fixes(fixes),
);
}
@@ -119,10 +118,11 @@ fn fixes(ctx: &DiagnosticsContext<'_>, file_id: FileId) -> Option<Vec<Assist>> {
stack.pop();
'crates: for &krate in ctx.sema.db.relevant_crates(parent_id).iter() {
let crate_def_map = ctx.sema.db.crate_def_map(krate);
- let Some((_, module)) =
- crate_def_map.modules()
- .find(|(_, module)| module.origin.file_id() == Some(parent_id) && !module.origin.is_inline())
- else { continue };
+ let Some((_, module)) = crate_def_map.modules().find(|(_, module)| {
+ module.origin.file_id() == Some(parent_id) && !module.origin.is_inline()
+ }) else {
+ continue;
+ };
if stack.is_empty() {
return make_fixes(
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unreachable_label.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unreachable_label.rs
index 9fedadeae..1c5d6cd09 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unreachable_label.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unreachable_label.rs
@@ -1,4 +1,4 @@
-use crate::{Diagnostic, DiagnosticsContext};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: unreachable-label
pub(crate) fn unreachable_label(
@@ -6,10 +6,11 @@ pub(crate) fn unreachable_label(
d: &hir::UnreachableLabel,
) -> Diagnostic {
let name = &d.name;
- Diagnostic::new(
- "unreachable-label",
+ Diagnostic::new_with_syntax_node_ptr(
+ ctx,
+ DiagnosticCode::RustcHardError("E0767"),
format!("use of unreachable label `{}`", name.display(ctx.sema.db)),
- ctx.sema.diagnostics_display_range(d.node.clone().map(|it| it.into())).range,
+ d.node.clone().map(|it| it.into()),
)
}
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
index 74e4a69c6..f8265b632 100644
--- 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
@@ -1,4 +1,4 @@
-use crate::{Diagnostic, DiagnosticsContext};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: unresolved-extern-crate
//
@@ -7,10 +7,11 @@ pub(crate) fn unresolved_extern_crate(
ctx: &DiagnosticsContext<'_>,
d: &hir::UnresolvedExternCrate,
) -> Diagnostic {
- Diagnostic::new(
- "unresolved-extern-crate",
+ Diagnostic::new_with_syntax_node_ptr(
+ ctx,
+ DiagnosticCode::RustcHardError("unresolved-extern-crate"),
"unresolved extern crate",
- ctx.sema.diagnostics_display_range(d.decl.clone().map(|it| it.into())).range,
+ d.decl.clone().map(|it| it.into()),
)
}
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_field.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_field.rs
index 5e4efa41f..0758706e4 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_field.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_field.rs
@@ -8,7 +8,7 @@ use ide_db::{
use syntax::{ast, AstNode, AstPtr};
use text_edit::TextEdit;
-use crate::{Diagnostic, DiagnosticsContext};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: unresolved-field
//
@@ -22,14 +22,15 @@ pub(crate) fn unresolved_field(
} else {
""
};
- Diagnostic::new(
- "unresolved-field",
+ Diagnostic::new_with_syntax_node_ptr(
+ ctx,
+ DiagnosticCode::RustcHardError("E0559"),
format!(
"no field `{}` on type `{}`{method_suffix}",
d.name.display(ctx.sema.db),
d.receiver.display(ctx.sema.db)
),
- ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
+ d.expr.clone().map(|it| it.into()),
)
.with_fixes(fixes(ctx, d))
.experimental()
@@ -67,7 +68,10 @@ fn method_fix(
}
#[cfg(test)]
mod tests {
- use crate::tests::check_diagnostics;
+ use crate::{
+ tests::{check_diagnostics, check_diagnostics_with_config},
+ DiagnosticsConfig,
+ };
#[test]
fn smoke_test() {
@@ -145,4 +149,11 @@ fn foo() {
"#,
);
}
+
+ #[test]
+ fn no_diagnostic_for_missing_name() {
+ let mut config = DiagnosticsConfig::test_sample();
+ config.disabled.insert("syntax-error".to_owned());
+ check_diagnostics_with_config(config, "fn foo() { (). }");
+ }
}
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
index e52a88459..6b8026c03 100644
--- 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
@@ -1,4 +1,4 @@
-use crate::{Diagnostic, DiagnosticsContext};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: unresolved-import
//
@@ -8,10 +8,11 @@ pub(crate) fn unresolved_import(
ctx: &DiagnosticsContext<'_>,
d: &hir::UnresolvedImport,
) -> Diagnostic {
- Diagnostic::new(
- "unresolved-import",
+ Diagnostic::new_with_syntax_node_ptr(
+ ctx,
+ DiagnosticCode::RustcHardError("E0432"),
"unresolved import",
- ctx.sema.diagnostics_display_range(d.decl.clone().map(|it| it.into())).range,
+ d.decl.clone().map(|it| it.into()),
)
// This currently results in false positives in the following cases:
// - `cfg_if!`-generated code in libstd (we don't load the sysroot correctly)
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
index 3943b51ab..33e7c2e37 100644
--- 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
@@ -1,4 +1,4 @@
-use crate::{Diagnostic, DiagnosticsContext};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: unresolved-macro-call
//
@@ -12,7 +12,7 @@ pub(crate) fn unresolved_macro_call(
let display_range = ctx.resolve_precise_location(&d.macro_call, d.precise_location);
let bang = if d.is_bang { "!" } else { "" };
Diagnostic::new(
- "unresolved-macro-call",
+ DiagnosticCode::RustcHardError("unresolved-macro-call"),
format!("unresolved macro `{}{bang}`", d.path.display(ctx.sema.db)),
display_range,
)
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_method.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_method.rs
index 8bbb837e6..ae9f6744c 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_method.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_method.rs
@@ -8,7 +8,7 @@ use ide_db::{
use syntax::{ast, AstNode, TextRange};
use text_edit::TextEdit;
-use crate::{Diagnostic, DiagnosticsContext};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: unresolved-method
//
@@ -22,14 +22,15 @@ pub(crate) fn unresolved_method(
} else {
""
};
- Diagnostic::new(
- "unresolved-method",
+ Diagnostic::new_with_syntax_node_ptr(
+ ctx,
+ DiagnosticCode::RustcHardError("E0599"),
format!(
"no method `{}` on type `{}`{field_suffix}",
d.name.display(ctx.sema.db),
d.receiver.display(ctx.sema.db)
),
- ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
+ d.expr.clone().map(|it| it.into()),
)
.with_fixes(fixes(ctx, d))
.experimental()
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
index 6e3fd3b42..be24e50c9 100644
--- 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
@@ -3,7 +3,7 @@ use ide_db::{assists::Assist, base_db::AnchoredPathBuf, source_change::FileSyste
use itertools::Itertools;
use syntax::AstNode;
-use crate::{fix, Diagnostic, DiagnosticsContext};
+use crate::{fix, Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: unresolved-module
//
@@ -12,8 +12,9 @@ pub(crate) fn unresolved_module(
ctx: &DiagnosticsContext<'_>,
d: &hir::UnresolvedModule,
) -> Diagnostic {
- Diagnostic::new(
- "unresolved-module",
+ Diagnostic::new_with_syntax_node_ptr(
+ ctx,
+ DiagnosticCode::RustcHardError("E0583"),
match &*d.candidates {
[] => "unresolved module".to_string(),
[candidate] => format!("unresolved module, can't find module file: {candidate}"),
@@ -25,7 +26,7 @@ pub(crate) fn unresolved_module(
)
}
},
- ctx.sema.diagnostics_display_range(d.decl.clone().map(|it| it.into())).range,
+ d.decl.clone().map(|it| it.into()),
)
.with_fixes(fixes(ctx, d))
}
@@ -82,8 +83,8 @@ mod baz {}
expect![[r#"
[
Diagnostic {
- code: DiagnosticCode(
- "unresolved-module",
+ code: RustcHardError(
+ "E0583",
),
message: "unresolved module, can't find module file: foo.rs, or foo/mod.rs",
range: 0..8,
@@ -148,6 +149,22 @@ mod baz {}
},
],
),
+ main_node: Some(
+ InFile {
+ file_id: FileId(
+ FileId(
+ 0,
+ ),
+ ),
+ value: MODULE@0..8
+ MOD_KW@0..3 "mod"
+ WHITESPACE@3..4 " "
+ NAME@4..7
+ IDENT@4..7 "foo"
+ SEMICOLON@7..8 ";"
+ ,
+ },
+ ),
},
]
"#]],
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
index ae5cf1358..015a3d6b2 100644
--- 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
@@ -1,6 +1,6 @@
use hir::db::DefDatabase;
-use crate::{Diagnostic, DiagnosticsContext, Severity};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, Severity};
// Diagnostic: unresolved-proc-macro
//
@@ -41,5 +41,5 @@ pub(crate) fn unresolved_proc_macro(
};
let message = format!("{not_expanded_message}: {message}");
- Diagnostic::new("unresolved-proc-macro", message, display_range).severity(severity)
+ Diagnostic::new(DiagnosticCode::Ra("unresolved-proc-macro", severity), message, display_range)
}
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
index 289ed0458..0aa439f79 100644
--- 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
@@ -1,9 +1,9 @@
use ide_db::{base_db::FileId, source_change::SourceChange};
use itertools::Itertools;
-use syntax::{ast, AstNode, SyntaxNode, TextRange};
+use syntax::{ast, AstNode, SyntaxNode};
use text_edit::TextEdit;
-use crate::{fix, Diagnostic, Severity};
+use crate::{fix, Diagnostic, DiagnosticCode};
// Diagnostic: unnecessary-braces
//
@@ -15,6 +15,11 @@ pub(crate) fn useless_braces(
) -> 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 `self` inside the bracketed `use`, don't show diagnostic.
+ if single_use_tree.path()?.segment()?.self_token().is_some() {
+ return Some(());
+ }
+
// 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() {
@@ -22,21 +27,18 @@ pub(crate) fn useless_braces(
}
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()
- });
+ 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);
+ let edit = edit_builder.finish();
acc.push(
Diagnostic::new(
- "unnecessary-braces",
+ DiagnosticCode::RustcLint("unused_braces"),
"Unnecessary braces in use statement".to_string(),
use_range,
)
- .severity(Severity::WeakWarning)
.with_fixes(Some(vec![fix(
"remove_braces",
"Remove unnecessary braces",
@@ -49,19 +51,12 @@ pub(crate) fn useless_braces(
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};
+ use crate::{
+ tests::{check_diagnostics, check_diagnostics_with_config, check_fix},
+ DiagnosticsConfig,
+ };
#[test]
fn test_check_unnecessary_braces_in_use_statement() {
@@ -94,6 +89,32 @@ mod a {
}
"#,
);
+ check_diagnostics(
+ r#"
+use a::{self};
+
+mod a {
+}
+"#,
+ );
+ check_diagnostics(
+ r#"
+use a::{self as cool_name};
+
+mod a {
+}
+"#,
+ );
+
+ let mut config = DiagnosticsConfig::test_sample();
+ config.disabled.insert("syntax-error".to_string());
+ check_diagnostics_with_config(
+ config,
+ r#"
+mod a { pub mod b {} }
+use a::{b::self};
+"#,
+ );
check_fix(
r#"
mod b {}
@@ -126,16 +147,6 @@ use a::c;
);
check_fix(
r#"
-mod a {}
-use a::{self$0};
-"#,
- r#"
-mod a {}
-use a;
-"#,
- );
- check_fix(
- r#"
mod a { pub mod c {} pub mod d { pub mod e {} } }
use a::{c, d::{e$0}};
"#,
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs
index 55a4a482d..b1b9b4b8e 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs
@@ -67,24 +67,61 @@ mod handlers {
#[cfg(test)]
mod tests;
+use std::collections::HashMap;
+
use hir::{diagnostics::AnyDiagnostic, InFile, Semantics};
use ide_db::{
assists::{Assist, AssistId, AssistKind, AssistResolveStrategy},
base_db::{FileId, FileRange, SourceDatabase},
+ generated::lints::{LintGroup, CLIPPY_LINT_GROUPS, DEFAULT_LINT_GROUPS},
imports::insert_use::InsertUseConfig,
label::Label,
source_change::SourceChange,
- FxHashSet, RootDatabase,
+ syntax_helpers::node_ext::parse_tt_as_comma_sep_paths,
+ FxHashMap, FxHashSet, RootDatabase,
+};
+use once_cell::sync::Lazy;
+use stdx::never;
+use syntax::{
+ algo::find_node_at_range,
+ ast::{self, AstNode},
+ SyntaxNode, SyntaxNodePtr, TextRange,
};
-use syntax::{algo::find_node_at_range, ast::AstNode, SyntaxNodePtr, TextRange};
// FIXME: Make this an enum
-#[derive(Copy, Clone, Debug, PartialEq)]
-pub struct DiagnosticCode(pub &'static str);
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum DiagnosticCode {
+ RustcHardError(&'static str),
+ RustcLint(&'static str),
+ Clippy(&'static str),
+ Ra(&'static str, Severity),
+}
impl DiagnosticCode {
- pub fn as_str(&self) -> &str {
- self.0
+ pub fn url(&self) -> String {
+ match self {
+ DiagnosticCode::RustcHardError(e) => {
+ format!("https://doc.rust-lang.org/stable/error_codes/{e}.html")
+ }
+ DiagnosticCode::RustcLint(e) => {
+ format!("https://doc.rust-lang.org/rustc/?search={e}")
+ }
+ DiagnosticCode::Clippy(e) => {
+ format!("https://rust-lang.github.io/rust-clippy/master/#/{e}")
+ }
+ DiagnosticCode::Ra(e, _) => {
+ format!("https://rust-analyzer.github.io/manual.html#{e}")
+ }
+ }
+ }
+
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ DiagnosticCode::RustcHardError(r)
+ | DiagnosticCode::RustcLint(r)
+ | DiagnosticCode::Clippy(r)
+ | DiagnosticCode::Ra(r, _) => r,
+ }
}
}
@@ -97,29 +134,51 @@ pub struct Diagnostic {
pub unused: bool,
pub experimental: bool,
pub fixes: Option<Vec<Assist>>,
+ // The node that will be affected by `#[allow]` and similar attributes.
+ pub main_node: Option<InFile<SyntaxNode>>,
}
impl Diagnostic {
- fn new(code: &'static str, message: impl Into<String>, range: TextRange) -> Diagnostic {
+ fn new(code: DiagnosticCode, message: impl Into<String>, range: TextRange) -> Diagnostic {
let message = message.into();
Diagnostic {
- code: DiagnosticCode(code),
+ code,
message,
range,
- severity: Severity::Error,
+ severity: match code {
+ DiagnosticCode::RustcHardError(_) => Severity::Error,
+ // FIXME: Rustc lints are not always warning, but the ones that are currently implemented are all warnings.
+ DiagnosticCode::RustcLint(_) => Severity::Warning,
+ // FIXME: We can make this configurable, and if the user uses `cargo clippy` on flycheck, we can
+ // make it normal warning.
+ DiagnosticCode::Clippy(_) => Severity::WeakWarning,
+ DiagnosticCode::Ra(_, s) => s,
+ },
unused: false,
experimental: false,
fixes: None,
+ main_node: None,
}
}
+ fn new_with_syntax_node_ptr(
+ ctx: &DiagnosticsContext<'_>,
+ code: DiagnosticCode,
+ message: impl Into<String>,
+ node: InFile<SyntaxNodePtr>,
+ ) -> Diagnostic {
+ let file_id = node.file_id;
+ Diagnostic::new(code, message, ctx.sema.diagnostics_display_range(node.clone()).range)
+ .with_main_node(node.map(|x| x.to_node(&ctx.sema.parse_or_expand(file_id))))
+ }
+
fn experimental(mut self) -> Diagnostic {
self.experimental = true;
self
}
- fn severity(mut self, severity: Severity) -> Diagnostic {
- self.severity = severity;
+ fn with_main_node(mut self, main_node: InFile<SyntaxNode>) -> Diagnostic {
+ self.main_node = Some(main_node);
self
}
@@ -134,12 +193,12 @@ impl Diagnostic {
}
}
-#[derive(Debug, Copy, Clone)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Severity {
Error,
- // We don't actually emit this one yet, but we should at some point.
- // Warning,
+ Warning,
WeakWarning,
+ Allow,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -155,6 +214,8 @@ impl Default for ExprFillDefaultMode {
#[derive(Debug, Clone)]
pub struct DiagnosticsConfig {
+ /// Whether native diagnostics are enabled.
+ pub enabled: bool,
pub proc_macros_enabled: bool,
pub proc_attr_macros_enabled: bool,
pub disable_experimental: bool,
@@ -171,6 +232,7 @@ impl DiagnosticsConfig {
use ide_db::imports::insert_use::ImportGranularity;
Self {
+ enabled: true,
proc_macros_enabled: Default::default(),
proc_attr_macros_enabled: Default::default(),
disable_experimental: Default::default(),
@@ -194,7 +256,7 @@ struct DiagnosticsContext<'a> {
resolve: &'a AssistResolveStrategy,
}
-impl<'a> DiagnosticsContext<'a> {
+impl DiagnosticsContext<'_> {
fn resolve_precise_location(
&self,
node: &InFile<SyntaxNodePtr>,
@@ -228,11 +290,13 @@ pub fn diagnostics(
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())
- }),
- );
+ res.extend(parse.errors().iter().take(128).map(|err| {
+ Diagnostic::new(
+ DiagnosticCode::RustcHardError("syntax-error"),
+ format!("Syntax Error: {err}"),
+ err.range(),
+ )
+ }));
let parse = sema.parse(file_id);
@@ -271,7 +335,7 @@ pub fn diagnostics(
res.extend(d.errors.iter().take(32).map(|err| {
{
Diagnostic::new(
- "syntax-error",
+ DiagnosticCode::RustcHardError("syntax-error"),
format!("Syntax Error in Expansion: {err}"),
ctx.resolve_precise_location(&d.node.clone(), d.precise_location),
)
@@ -309,14 +373,168 @@ pub fn diagnostics(
res.push(d)
}
+ let mut diagnostics_of_range =
+ res.iter_mut().filter_map(|x| Some((x.main_node.clone()?, x))).collect::<FxHashMap<_, _>>();
+
+ let mut rustc_stack: FxHashMap<String, Vec<Severity>> = FxHashMap::default();
+ let mut clippy_stack: FxHashMap<String, Vec<Severity>> = FxHashMap::default();
+
+ handle_lint_attributes(
+ &ctx.sema,
+ parse.syntax(),
+ &mut rustc_stack,
+ &mut clippy_stack,
+ &mut diagnostics_of_range,
+ );
+
res.retain(|d| {
- !ctx.config.disabled.contains(d.code.as_str())
+ d.severity != Severity::Allow
+ && !ctx.config.disabled.contains(d.code.as_str())
&& !(ctx.config.disable_experimental && d.experimental)
});
res
}
+// `__RA_EVERY_LINT` is a fake lint group to allow every lint in proc macros
+
+static RUSTC_LINT_GROUPS_DICT: Lazy<HashMap<&str, Vec<&str>>> =
+ Lazy::new(|| build_group_dict(DEFAULT_LINT_GROUPS, &["warnings", "__RA_EVERY_LINT"], ""));
+
+static CLIPPY_LINT_GROUPS_DICT: Lazy<HashMap<&str, Vec<&str>>> =
+ Lazy::new(|| build_group_dict(CLIPPY_LINT_GROUPS, &["__RA_EVERY_LINT"], "clippy::"));
+
+fn build_group_dict(
+ lint_group: &'static [LintGroup],
+ all_groups: &'static [&'static str],
+ prefix: &'static str,
+) -> HashMap<&'static str, Vec<&'static str>> {
+ let mut r: HashMap<&str, Vec<&str>> = HashMap::new();
+ for g in lint_group {
+ for child in g.children {
+ r.entry(child.strip_prefix(prefix).unwrap())
+ .or_default()
+ .push(g.lint.label.strip_prefix(prefix).unwrap());
+ }
+ }
+ for (lint, groups) in r.iter_mut() {
+ groups.push(lint);
+ groups.extend_from_slice(all_groups);
+ }
+ r
+}
+
+fn handle_lint_attributes(
+ sema: &Semantics<'_, RootDatabase>,
+ root: &SyntaxNode,
+ rustc_stack: &mut FxHashMap<String, Vec<Severity>>,
+ clippy_stack: &mut FxHashMap<String, Vec<Severity>>,
+ diagnostics_of_range: &mut FxHashMap<InFile<SyntaxNode>, &mut Diagnostic>,
+) {
+ let file_id = sema.hir_file_for(root);
+ for ev in root.preorder() {
+ match ev {
+ syntax::WalkEvent::Enter(node) => {
+ for attr in node.children().filter_map(ast::Attr::cast) {
+ parse_lint_attribute(attr, rustc_stack, clippy_stack, |stack, severity| {
+ stack.push(severity);
+ });
+ }
+ if let Some(x) =
+ diagnostics_of_range.get_mut(&InFile { file_id, value: node.clone() })
+ {
+ const EMPTY_LINTS: &[&str] = &[];
+ let (names, stack) = match x.code {
+ DiagnosticCode::RustcLint(name) => (
+ RUSTC_LINT_GROUPS_DICT.get(name).map_or(EMPTY_LINTS, |x| &**x),
+ &mut *rustc_stack,
+ ),
+ DiagnosticCode::Clippy(name) => (
+ CLIPPY_LINT_GROUPS_DICT.get(name).map_or(EMPTY_LINTS, |x| &**x),
+ &mut *clippy_stack,
+ ),
+ _ => continue,
+ };
+ for &name in names {
+ if let Some(s) = stack.get(name).and_then(|x| x.last()) {
+ x.severity = *s;
+ }
+ }
+ }
+ if let Some(item) = ast::Item::cast(node.clone()) {
+ if let Some(me) = sema.expand_attr_macro(&item) {
+ for stack in [&mut *rustc_stack, &mut *clippy_stack] {
+ stack
+ .entry("__RA_EVERY_LINT".to_owned())
+ .or_default()
+ .push(Severity::Allow);
+ }
+ handle_lint_attributes(
+ sema,
+ &me,
+ rustc_stack,
+ clippy_stack,
+ diagnostics_of_range,
+ );
+ for stack in [&mut *rustc_stack, &mut *clippy_stack] {
+ stack.entry("__RA_EVERY_LINT".to_owned()).or_default().pop();
+ }
+ }
+ }
+ if let Some(mc) = ast::MacroCall::cast(node) {
+ if let Some(me) = sema.expand(&mc) {
+ handle_lint_attributes(
+ sema,
+ &me,
+ rustc_stack,
+ clippy_stack,
+ diagnostics_of_range,
+ );
+ }
+ }
+ }
+ syntax::WalkEvent::Leave(node) => {
+ for attr in node.children().filter_map(ast::Attr::cast) {
+ parse_lint_attribute(attr, rustc_stack, clippy_stack, |stack, severity| {
+ if stack.pop() != Some(severity) {
+ never!("Mismatched serevity in walking lint attributes");
+ }
+ });
+ }
+ }
+ }
+ }
+}
+
+fn parse_lint_attribute(
+ attr: ast::Attr,
+ rustc_stack: &mut FxHashMap<String, Vec<Severity>>,
+ clippy_stack: &mut FxHashMap<String, Vec<Severity>>,
+ job: impl Fn(&mut Vec<Severity>, Severity),
+) {
+ let Some((tag, args_tt)) = attr.as_simple_call() else {
+ return;
+ };
+ let serevity = match tag.as_str() {
+ "allow" => Severity::Allow,
+ "warn" => Severity::Warning,
+ "forbid" | "deny" => Severity::Error,
+ _ => return,
+ };
+ for lint in parse_tt_as_comma_sep_paths(args_tt).into_iter().flatten() {
+ if let Some(lint) = lint.as_single_name_ref() {
+ job(rustc_stack.entry(lint.to_string()).or_default(), serevity);
+ }
+ if let Some(tool) = lint.qualifier().and_then(|x| x.as_single_name_ref()) {
+ if let Some(name_ref) = &lint.segment().and_then(|x| x.name_ref()) {
+ if tool.to_string() == "clippy" {
+ job(clippy_stack.entry(name_ref.to_string()).or_default(), serevity);
+ }
+ }
+ }
+ }
+}
+
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);
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/tests.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/tests.rs
index b5cd4e0d6..ee0e03549 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/tests.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/tests.rs
@@ -49,8 +49,11 @@ fn check_nth_fix(nth: usize, ra_fixture_before: &str, ra_fixture_after: &str) {
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() {
+ for (edit, snippet_edit) in source_change.source_file_edits.values() {
edit.apply(&mut actual);
+ if let Some(snippet_edit) = snippet_edit {
+ snippet_edit.apply(&mut actual);
+ }
}
actual
};
@@ -114,6 +117,8 @@ pub(crate) fn check_diagnostics_with_config(config: DiagnosticsConfig, ra_fixtur
annotation.push_str(match d.severity {
Severity::Error => "error",
Severity::WeakWarning => "weak",
+ Severity::Warning => "warn",
+ Severity::Allow => "allow",
});
annotation.push_str(": ");
annotation.push_str(&d.message);
@@ -130,14 +135,19 @@ pub(crate) fn check_diagnostics_with_config(config: DiagnosticsConfig, ra_fixtur
)
}
}
- assert_eq!(expected, actual);
+ if expected != actual {
+ let fneg = expected.iter().filter(|x| !actual.contains(x)).collect::<Vec<_>>();
+ let fpos = actual.iter().filter(|x| !expected.contains(x)).collect::<Vec<_>>();
+
+ panic!("Diagnostic test failed.\nFalse negatives: {fneg:?}\nFalse positives: {fpos:?}");
+ }
}
}
#[test]
fn test_disabled_diagnostics() {
let mut config = DiagnosticsConfig::test_sample();
- config.disabled.insert("unresolved-module".into());
+ config.disabled.insert("E0583".into());
let (db, file_id) = RootDatabase::with_single_file(r#"mod foo;"#);
@@ -159,7 +169,7 @@ fn minicore_smoke_test() {
let source = minicore.source_code();
let mut config = DiagnosticsConfig::test_sample();
// This should be ignored since we conditionaly remove code which creates single item use with braces
- config.disabled.insert("unnecessary-braces".to_string());
+ config.disabled.insert("unused_braces".to_string());
check_diagnostics_with_config(config, &source);
}
diff --git a/src/tools/rust-analyzer/crates/ide-ssr/src/search.rs b/src/tools/rust-analyzer/crates/ide-ssr/src/search.rs
index 0a85569b6..ca76d0a87 100644
--- a/src/tools/rust-analyzer/crates/ide-ssr/src/search.rs
+++ b/src/tools/rust-analyzer/crates/ide-ssr/src/search.rs
@@ -22,7 +22,7 @@ pub(crate) struct UsageCache {
usages: Vec<(Definition, UsageSearchResult)>,
}
-impl<'db> MatchFinder<'db> {
+impl MatchFinder<'_> {
/// 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.
@@ -121,7 +121,7 @@ impl<'db> MatchFinder<'db> {
// 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();
+ 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;
}
diff --git a/src/tools/rust-analyzer/crates/ide/src/doc_links.rs b/src/tools/rust-analyzer/crates/ide/src/doc_links.rs
index 8112c4f72..d240127f3 100644
--- a/src/tools/rust-analyzer/crates/ide/src/doc_links.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/doc_links.rs
@@ -153,6 +153,9 @@ pub(crate) fn external_docs(
NameRefClass::FieldShorthand { local_ref: _, field_ref } => {
Definition::Field(field_ref)
}
+ NameRefClass::ExternCrateShorthand { decl, .. } => {
+ Definition::ExternCrateDecl(decl)
+ }
},
ast::Name(name) => match NameClass::classify(sema, &name)? {
NameClass::Definition(it) | NameClass::ConstReference(it) => it,
@@ -209,6 +212,7 @@ pub(crate) fn resolve_doc_path_for_def(
Definition::Macro(it) => it.resolve_doc_path(db, link, ns),
Definition::Field(it) => it.resolve_doc_path(db, link, ns),
Definition::SelfType(it) => it.resolve_doc_path(db, link, ns),
+ Definition::ExternCrateDecl(it) => it.resolve_doc_path(db, link, ns),
Definition::BuiltinAttr(_)
| Definition::ToolModule(_)
| Definition::BuiltinType(_)
@@ -330,7 +334,9 @@ fn get_doc_links(
base_url.and_then(|url| url.join(path).ok())
};
- let Some((target, file, frag)) = filename_and_frag_for_def(db, def) else { return Default::default(); };
+ let Some((target, file, frag)) = filename_and_frag_for_def(db, def) else {
+ return Default::default();
+ };
let (mut web_url, mut local_url) = get_doc_base_urls(db, target, target_dir, sysroot);
@@ -615,6 +621,9 @@ fn filename_and_frag_for_def(
// FIXME fragment numbering
return Some((adt, file, Some(String::from("impl"))));
}
+ Definition::ExternCrateDecl(it) => {
+ format!("{}/index.html", it.name(db).display(db.upcast()))
+ }
Definition::Local(_)
| Definition::GenericParam(_)
| Definition::Label(_)
diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs b/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs
index e70bc2ec5..c39c696cf 100644
--- a/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs
@@ -37,11 +37,15 @@ pub(crate) fn goto_declaration(
match parent {
ast::NameRef(name_ref) => match NameRefClass::classify(&sema, &name_ref)? {
NameRefClass::Definition(it) => Some(it),
- NameRefClass::FieldShorthand { field_ref, .. } => return field_ref.try_to_nav(db),
+ NameRefClass::FieldShorthand { field_ref, .. } =>
+ return field_ref.try_to_nav(db),
+ NameRefClass::ExternCrateShorthand { decl, .. } =>
+ return decl.try_to_nav(db),
},
ast::Name(name) => match NameClass::classify(&sema, &name)? {
NameClass::Definition(it) | NameClass::ConstReference(it) => Some(it),
- NameClass::PatFieldShorthand { field_ref, .. } => return field_ref.try_to_nav(db),
+ NameClass::PatFieldShorthand { field_ref, .. } =>
+ return field_ref.try_to_nav(db),
},
_ => None
}
@@ -53,6 +57,7 @@ pub(crate) fn goto_declaration(
Definition::Const(c) => c.as_assoc_item(db),
Definition::TypeAlias(ta) => ta.as_assoc_item(db),
Definition::Function(f) => f.as_assoc_item(db),
+ Definition::ExternCrateDecl(it) => return it.try_to_nav(db),
_ => None,
}?;
@@ -211,4 +216,30 @@ fn main() {
"#,
);
}
+
+ #[test]
+ fn goto_decl_for_extern_crate() {
+ check(
+ r#"
+//- /main.rs crate:main deps:std
+extern crate std$0;
+ /// ^^^
+//- /std/lib.rs crate:std
+// empty
+"#,
+ )
+ }
+
+ #[test]
+ fn goto_decl_for_renamed_extern_crate() {
+ check(
+ r#"
+//- /main.rs crate:main deps:std
+extern crate std as abc$0;
+ /// ^^^
+//- /std/lib.rs crate:std
+// empty
+"#,
+ )
+ }
}
diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs
index 4e641357e..21471ab2a 100644
--- a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs
@@ -1,6 +1,9 @@
use std::mem::discriminant;
-use crate::{doc_links::token_as_doc_comment, FilePosition, NavigationTarget, RangeInfo, TryToNav};
+use crate::{
+ doc_links::token_as_doc_comment, navigation_target::ToNav, FilePosition, NavigationTarget,
+ RangeInfo, TryToNav,
+};
use hir::{AsAssocItem, AssocItem, Semantics};
use ide_db::{
base_db::{AnchoredPath, FileId, FileLoader},
@@ -73,6 +76,13 @@ pub(crate) fn goto_definition(
.definitions()
.into_iter()
.flat_map(|def| {
+ if let Definition::ExternCrateDecl(crate_def) = def {
+ return crate_def
+ .resolved_crate(db)
+ .map(|it| it.root_module().to_nav(sema.db))
+ .into_iter()
+ .collect();
+ }
try_filter_trait_item_definition(sema, &def)
.unwrap_or_else(|| def_to_nav(sema.db, def))
})
diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs b/src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs
index a1a119629..37166bdbd 100644
--- a/src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs
@@ -34,54 +34,50 @@ pub(crate) fn goto_implementation(
_ => 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)
+ 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).and_then(|class| match class {
+ NameClass::Definition(it) | NameClass::ConstReference(it) => Some(it),
+ NameClass::PatFieldShorthand { .. } => None,
+ })
+ }
+ ast::NameLike::NameRef(name_ref) => NameRefClass::classify(&sema, name_ref)
+ .and_then(|class| match class {
+ NameRefClass::Definition(def) => Some(def),
+ NameRefClass::FieldShorthand { .. }
+ | NameRefClass::ExternCrateShorthand { .. } => None,
+ }),
+ 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)
}
- })
- }
- 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)
+ 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)
}
- })
- }
- 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();
+ _ => return None,
+ };
+ Some(navs)
+ })
+ .flatten()
+ .collect();
Some(RangeInfo { range, info: navs })
}
diff --git a/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs b/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs
index 7e545491f..43e89a334 100644
--- a/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs
@@ -100,10 +100,7 @@ fn highlight_closure_captures(
.flat_map(|local| {
let usages = Definition::Local(local)
.usages(sema)
- .set_scope(Some(SearchScope::file_range(FileRange {
- file_id,
- range: search_range,
- })))
+ .in_scope(&SearchScope::file_range(FileRange { file_id, range: search_range }))
.include_self_refs()
.all()
.references
@@ -139,7 +136,7 @@ fn highlight_references(
.iter()
.filter_map(|&d| {
d.usages(sema)
- .set_scope(Some(SearchScope::single_file(file_id)))
+ .in_scope(&SearchScope::single_file(file_id))
.include_self_refs()
.all()
.references
@@ -183,7 +180,7 @@ fn highlight_references(
.filter_map(|item| {
Definition::from(item)
.usages(sema)
- .set_scope(Some(SearchScope::file_range(FileRange {
+ .set_scope(Some(&SearchScope::file_range(FileRange {
file_id,
range: trait_item_use_scope.text_range(),
})))
diff --git a/src/tools/rust-analyzer/crates/ide/src/hover.rs b/src/tools/rust-analyzer/crates/ide/src/hover.rs
index 5ef6ac948..40659e6c2 100644
--- a/src/tools/rust-analyzer/crates/ide/src/hover.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/hover.rs
@@ -9,7 +9,7 @@ use either::Either;
use hir::{db::DefDatabase, HasSource, LangItem, Semantics};
use ide_db::{
base_db::FileRange,
- defs::{Definition, IdentClass, OperatorClass},
+ defs::{Definition, IdentClass, NameRefClass, OperatorClass},
famous_defs::FamousDefs,
helpers::pick_best_token,
FxIndexSet, RootDatabase,
@@ -186,7 +186,20 @@ fn hover_simple(
// rendering poll is very confusing
return None;
}
- Some(class.definitions().into_iter().zip(iter::once(node).cycle()))
+ if let IdentClass::NameRefClass(NameRefClass::ExternCrateShorthand {
+ decl,
+ ..
+ }) = class
+ {
+ return Some(vec![(Definition::ExternCrateDecl(decl), node)]);
+ }
+ Some(
+ class
+ .definitions()
+ .into_iter()
+ .zip(iter::once(node).cycle())
+ .collect::<Vec<_>>(),
+ )
})
.flatten()
.unique_by(|&(def, _)| def)
diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs
index 136214641..a33a6ee18 100644
--- a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs
@@ -257,7 +257,7 @@ pub(super) fn keyword(
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 docs = doc_owner.docs(sema.db)?;
let markup = process_markup(
sema.db,
Definition::Module(doc_owner),
@@ -422,10 +422,10 @@ pub(super) fn definition(
|&it| {
if !it.parent_enum(db).is_data_carrying(db) {
match it.eval(db) {
- Ok(x) => {
- Some(if x >= 10 { format!("{x} ({x:#X})") } else { format!("{x}") })
+ Ok(it) => {
+ Some(if it >= 10 { format!("{it} ({it:#X})") } else { format!("{it}") })
}
- Err(_) => it.value(db).map(|x| format!("{x:?}")),
+ Err(_) => it.value(db).map(|it| format!("{it:?}")),
}
} else {
None
@@ -437,7 +437,7 @@ pub(super) fn definition(
Definition::Const(it) => label_value_and_docs(db, it, |it| {
let body = it.render_eval(db);
match body {
- Ok(x) => Some(x),
+ Ok(it) => Some(it),
Err(_) => {
let source = it.source(db)?;
let mut body = source.value.body()?.syntax().clone();
@@ -472,6 +472,7 @@ pub(super) fn definition(
}
Definition::GenericParam(it) => label_and_docs(db, it),
Definition::Label(it) => return Some(Markup::fenced_block(&it.name(db).display(db))),
+ Definition::ExternCrateDecl(it) => label_and_docs(db, it),
// 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))),
@@ -620,7 +621,7 @@ where
D: HasAttrs + HirDisplay,
{
let label = def.display(db).to_string();
- let docs = def.attrs(db).docs();
+ let docs = def.docs(db);
(label, docs)
}
@@ -645,7 +646,7 @@ where
) {
format_to!(label, "{layout}");
}
- let docs = def.attrs(db).docs();
+ let docs = def.docs(db);
(label, docs)
}
@@ -677,7 +678,7 @@ where
) {
format_to!(label, "{layout}");
}
- let docs = def.attrs(db).docs();
+ let docs = def.docs(db);
(label, docs)
}
@@ -696,7 +697,7 @@ where
} else {
def.display(db).to_string()
};
- let docs = def.attrs(db).docs();
+ let docs = def.docs(db);
(label, docs)
}
@@ -727,14 +728,14 @@ fn builtin(famous_defs: &FamousDefs<'_, '_>, builtin: hir::BuiltinType) -> Optio
// std exposes prim_{} modules with docstrings on the root to document the builtins
let primitive_mod = format!("prim_{}", builtin.name().display(famous_defs.0.db));
let doc_owner = find_std_module(famous_defs, &primitive_mod)?;
- let docs = doc_owner.attrs(famous_defs.0.db).docs()?;
+ let docs = doc_owner.docs(famous_defs.0.db)?;
markup(Some(docs.into()), builtin.name().display(famous_defs.0.db).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);
+ let std_root_module = std_crate.root_module();
std_root_module.children(db).find(|module| {
module.name(db).map_or(false, |module| module.display(db).to_string() == name)
})
diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs
index f75ebfa12..ddc71dffa 100644
--- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs
@@ -674,7 +674,7 @@ struct Foo { fiel$0d_a: u8, field_b: i32, field_c: i16 }
```
```rust
- field_a: u8 // size = 1, align = 1, offset = 4
+ field_a: u8 // size = 1, align = 1, offset = 6
```
"#]],
);
@@ -779,6 +779,39 @@ const foo$0: u32 = {
```
"#]],
);
+
+ check(
+ r#"const FOO$0: i32 = -2147483648;"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: i32 = -2147483648 (0x80000000)
+ ```
+ "#]],
+ );
+
+ check(
+ r#"
+ const FOO: i32 = -2147483648;
+ const BAR$0: bool = FOO > 0;
+ "#,
+ expect![[r#"
+ *BAR*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const BAR: bool = false
+ ```
+ "#]],
+ );
}
#[test]
@@ -1583,6 +1616,9 @@ fn test_hover_extern_crate() {
check(
r#"
//- /main.rs crate:main deps:std
+//! Crate docs
+
+/// Decl docs!
extern crate st$0d;
//- /std/lib.rs crate:std
//! Standard library for this test
@@ -1591,23 +1627,32 @@ extern crate st$0d;
//! abc123
"#,
expect![[r#"
- *std*
+ *std*
- ```rust
- extern crate std
- ```
+ ```rust
+ main
+ ```
- ---
+ ```rust
+ extern crate std
+ ```
+
+ ---
- Standard library for this test
+ Decl docs!
- Printed?
- abc123
- "#]],
+ Standard library for this test
+
+ Printed?
+ abc123
+ "#]],
);
check(
r#"
//- /main.rs crate:main deps:std
+//! Crate docs
+
+/// Decl docs!
extern crate std as ab$0c;
//- /std/lib.rs crate:std
//! Standard library for this test
@@ -1616,19 +1661,25 @@ extern crate std as ab$0c;
//! abc123
"#,
expect![[r#"
- *abc*
+ *abc*
- ```rust
- extern crate std
- ```
+ ```rust
+ main
+ ```
- ---
+ ```rust
+ extern crate std as abc
+ ```
- Standard library for this test
+ ---
- Printed?
- abc123
- "#]],
+ Decl docs!
+
+ Standard library for this test
+
+ Printed?
+ abc123
+ "#]],
);
}
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs
index 10bee2a6a..6d6bd315e 100644
--- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs
@@ -259,7 +259,7 @@ fn needs_parens_for_adjustment_hints(expr: &ast::Expr, postfix: bool) -> (bool,
}
})() else {
never!("broken syntax tree?\n{:?}\n{:?}", expr, dummy_expr);
- return (true, true)
+ return (true, true);
};
// At this point
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs
index 84eac16b9..b621a8dda 100644
--- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs
@@ -474,7 +474,7 @@ fn main() {
file_id: FileId(
1,
),
- range: 9287..9295,
+ range: 9289..9297,
},
),
tooltip: "",
@@ -487,7 +487,7 @@ fn main() {
file_id: FileId(
1,
),
- range: 9319..9323,
+ range: 9321..9325,
},
),
tooltip: "",
@@ -511,7 +511,7 @@ fn main() {
file_id: FileId(
1,
),
- range: 9287..9295,
+ range: 9289..9297,
},
),
tooltip: "",
@@ -524,7 +524,7 @@ fn main() {
file_id: FileId(
1,
),
- range: 9319..9323,
+ range: 9321..9325,
},
),
tooltip: "",
@@ -548,7 +548,7 @@ fn main() {
file_id: FileId(
1,
),
- range: 9287..9295,
+ range: 9289..9297,
},
),
tooltip: "",
@@ -561,7 +561,7 @@ fn main() {
file_id: FileId(
1,
),
- range: 9319..9323,
+ range: 9321..9325,
},
),
tooltip: "",
diff --git a/src/tools/rust-analyzer/crates/ide/src/interpret_function.rs b/src/tools/rust-analyzer/crates/ide/src/interpret_function.rs
index cbcbb4b09..d06ffd535 100644
--- a/src/tools/rust-analyzer/crates/ide/src/interpret_function.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/interpret_function.rs
@@ -34,13 +34,15 @@ fn find_and_interpret(db: &RootDatabase, position: FilePosition) -> Option<Strin
_ => return None,
};
let span_formatter = |file_id, text_range: TextRange| {
- let line_col = db.line_index(file_id).line_col(text_range.start());
let path = &db
.source_root(db.file_source_root(file_id))
.path_for_file(&file_id)
.map(|x| x.to_string());
let path = path.as_deref().unwrap_or("<unknown file>");
- format!("file://{path}#{}:{}", line_col.line + 1, line_col.col)
+ match db.line_index(file_id).try_line_col(text_range.start()) {
+ Some(line_col) => format!("file://{path}#{}:{}", line_col.line + 1, line_col.col),
+ None => format!("file://{path} range {:?}", text_range),
+ }
};
Some(def.eval(db, span_formatter))
}
diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs
index f195f78b3..bf77d55d5 100644
--- a/src/tools/rust-analyzer/crates/ide/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/lib.rs
@@ -60,6 +60,7 @@ mod interpret_function;
mod view_item_tree;
mod shuffle_crate_graph;
mod fetch_crates;
+mod view_memory_layout;
use std::ffi::OsStr;
@@ -74,6 +75,7 @@ use ide_db::{
};
use syntax::SourceFile;
use triomphe::Arc;
+use view_memory_layout::{view_memory_layout, RecursiveMemoryLayout};
use crate::navigation_target::{ToNav, TryToNav};
@@ -125,7 +127,7 @@ pub use ide_db::{
label::Label,
line_index::{LineCol, LineIndex},
search::{ReferenceCategory, SearchScope},
- source_change::{FileSystemEdit, SourceChange},
+ source_change::{FileSystemEdit, SnippetEdit, SourceChange},
symbol_index::Query,
RootDatabase, SymbolKind,
};
@@ -642,7 +644,7 @@ impl Analysis {
};
self.with_db(|db| {
- let diagnostic_assists = if include_fixes {
+ let diagnostic_assists = if diagnostics_config.enabled && include_fixes {
ide_diagnostics::diagnostics(db, diagnostics_config, &resolve, frange.file_id)
.into_iter()
.flat_map(|it| it.fixes.unwrap_or_default())
@@ -724,6 +726,13 @@ impl Analysis {
self.with_db(|db| move_item::move_item(db, range, direction))
}
+ pub fn get_recursive_memory_layout(
+ &self,
+ position: FilePosition,
+ ) -> Cancellable<Option<RecursiveMemoryLayout>> {
+ self.with_db(|db| view_memory_layout(db, position))
+ }
+
/// Performs an operation on the database that may be canceled.
///
/// rust-analyzer needs to be able to answer semantic questions about the
diff --git a/src/tools/rust-analyzer/crates/ide/src/moniker.rs b/src/tools/rust-analyzer/crates/ide/src/moniker.rs
index 0d57e63d2..17f3771b1 100644
--- a/src/tools/rust-analyzer/crates/ide/src/moniker.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/moniker.rs
@@ -177,6 +177,17 @@ pub(crate) fn def_to_moniker(
});
}
+ // Qualify locals/parameters by their parent definition name.
+ if let Definition::Local(it) = def {
+ let parent_name = it.parent(db).name(db);
+ if let Some(name) = parent_name {
+ description.push(MonikerDescriptor {
+ name: name.display(db).to_string(),
+ desc: MonikerDescriptorKind::Method,
+ });
+ }
+ }
+
let name_desc = match def {
// These are handled by top-level guard (for performance).
Definition::GenericParam(_)
@@ -247,6 +258,10 @@ pub(crate) fn def_to_moniker(
name: s.name(db).display(db).to_string(),
desc: MonikerDescriptorKind::Meta,
},
+ Definition::ExternCrateDecl(m) => MonikerDescriptor {
+ name: m.name(db).display(db).to_string(),
+ desc: MonikerDescriptorKind::Namespace,
+ },
};
description.push(name_desc);
@@ -320,7 +335,7 @@ use foo::module::func;
fn main() {
func$0();
}
-//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
pub mod module {
pub fn func() {}
}
@@ -336,7 +351,7 @@ use foo::module::func;
fn main() {
func();
}
-//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
pub mod module {
pub fn func$0() {}
}
@@ -351,7 +366,7 @@ pub mod module {
fn moniker_for_trait() {
check_moniker(
r#"
-//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
pub mod module {
pub trait MyTrait {
pub fn func$0() {}
@@ -368,7 +383,7 @@ pub mod module {
fn moniker_for_trait_constant() {
check_moniker(
r#"
-//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
pub mod module {
pub trait MyTrait {
const MY_CONST$0: u8;
@@ -385,7 +400,7 @@ pub mod module {
fn moniker_for_trait_type() {
check_moniker(
r#"
-//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
pub mod module {
pub trait MyTrait {
type MyType$0;
@@ -402,7 +417,7 @@ pub mod module {
fn moniker_for_trait_impl_function() {
check_moniker(
r#"
-//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
pub mod module {
pub trait MyTrait {
pub fn func() {}
@@ -430,7 +445,7 @@ 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
+//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
pub struct St {
pub a: i32,
}
@@ -450,7 +465,7 @@ use foo::module::func;
fn main() {
func();
}
-//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
pub mod module {
pub fn func() {
let x$0 = 2;
diff --git a/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs b/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs
index 385c1b0c0..d1479dd1e 100644
--- a/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs
@@ -102,7 +102,7 @@ impl NavigationTarget {
full_range,
SymbolKind::Module,
);
- res.docs = module.attrs(db).docs();
+ res.docs = module.docs(db);
res.description = Some(module.display(db).to_string());
return res;
}
@@ -217,6 +217,7 @@ impl TryToNav for Definition {
Definition::Trait(it) => it.try_to_nav(db),
Definition::TraitAlias(it) => it.try_to_nav(db),
Definition::TypeAlias(it) => it.try_to_nav(db),
+ Definition::ExternCrateDecl(it) => Some(it.try_to_nav(db)?),
Definition::BuiltinType(_) => None,
Definition::ToolModule(_) => None,
Definition::BuiltinAttr(_) => None,
@@ -357,13 +358,11 @@ impl ToNav for hir::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 derive_attr = self.as_builtin_derive(db);
- let focus = if derive_attr.is_some() { None } else { value.self_ty() };
-
- let syntax = match &derive_attr {
- Some(attr) => attr.value.syntax(),
- None => value.syntax(),
+ let (focus, syntax) = match &derive_attr {
+ Some(attr) => (None, attr.value.syntax()),
+ None => (value.self_ty(), value.syntax()),
};
let (file_id, full_range, focus_range) = orig_range_with_focus(db, file_id, syntax, focus);
@@ -377,6 +376,30 @@ impl TryToNav for hir::Impl {
}
}
+impl TryToNav for hir::ExternCrateDecl {
+ fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
+ let src = self.source(db)?;
+ let InFile { file_id, value } = src;
+ let focus = value
+ .rename()
+ .map_or_else(|| value.name_ref().map(Either::Left), |it| it.name().map(Either::Right));
+ let (file_id, full_range, focus_range) =
+ orig_range_with_focus(db, file_id, value.syntax(), focus);
+ let mut res = NavigationTarget::from_syntax(
+ file_id,
+ self.alias_or_name(db).unwrap_or_else(|| self.name(db)).to_smol_str(),
+ focus_range,
+ full_range,
+ SymbolKind::Module,
+ );
+
+ res.docs = self.docs(db);
+ res.description = Some(self.display(db).to_string());
+ res.container_name = container_name(db, *self);
+ Some(res)
+ }
+}
+
impl TryToNav for hir::Field {
fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
let src = self.source(db)?;
diff --git a/src/tools/rust-analyzer/crates/ide/src/references.rs b/src/tools/rust-analyzer/crates/ide/src/references.rs
index fdc5261ac..813f9ed94 100644
--- a/src/tools/rust-analyzer/crates/ide/src/references.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/references.rs
@@ -74,7 +74,7 @@ pub(crate) fn find_all_refs(
}
});
let mut usages =
- def.usages(sema).set_scope(search_scope.clone()).include_self_refs().all();
+ def.usages(sema).set_scope(search_scope.as_ref()).include_self_refs().all();
if literal_search {
retain_adt_literal_usages(&mut usages, def, sema);
@@ -137,6 +137,9 @@ pub(crate) fn find_defs<'a>(
NameRefClass::FieldShorthand { local_ref, field_ref: _ } => {
Definition::Local(local_ref)
}
+ NameRefClass::ExternCrateShorthand { decl, .. } => {
+ Definition::ExternCrateDecl(decl)
+ }
}
}
ast::NameLike::Name(name) => match NameClass::classify(sema, &name)? {
diff --git a/src/tools/rust-analyzer/crates/ide/src/rename.rs b/src/tools/rust-analyzer/crates/ide/src/rename.rs
index e10c46381..dae8e71e8 100644
--- a/src/tools/rust-analyzer/crates/ide/src/rename.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/rename.rs
@@ -145,7 +145,14 @@ fn find_definitions(
if name
.syntax()
.parent()
- .map_or(false, |it| ast::Rename::can_cast(it.kind())) =>
+ .map_or(false, |it| ast::Rename::can_cast(it.kind()))
+ // FIXME: uncomment this once we resolve to usages to extern crate declarations
+ // && name
+ // .syntax()
+ // .ancestors()
+ // .nth(2)
+ // .map_or(true, |it| !ast::ExternCrate::can_cast(it.kind()))
+ =>
{
bail!("Renaming aliases is currently unsupported")
}
@@ -165,7 +172,12 @@ fn find_definitions(
NameRefClass::FieldShorthand { local_ref, field_ref: _ } => {
Definition::Local(local_ref)
}
+ NameRefClass::ExternCrateShorthand { decl, .. } => {
+ Definition::ExternCrateDecl(decl)
+ }
})
+ // FIXME: uncomment this once we resolve to usages to extern crate declarations
+ .filter(|def| !matches!(def, Definition::ExternCrateDecl(..)))
.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
@@ -367,7 +379,7 @@ mod tests {
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() {
+ for indel in edit.1 .0.into_iter() {
text_edit_builder.replace(indel.delete, indel.insert);
}
}
@@ -895,14 +907,17 @@ mod foo$0;
source_file_edits: {
FileId(
1,
- ): TextEdit {
- indels: [
- Indel {
- insert: "foo2",
- delete: 4..7,
- },
- ],
- },
+ ): (
+ TextEdit {
+ indels: [
+ Indel {
+ insert: "foo2",
+ delete: 4..7,
+ },
+ ],
+ },
+ None,
+ ),
},
file_system_edits: [
MoveFile {
@@ -944,24 +959,30 @@ use crate::foo$0::FooContent;
source_file_edits: {
FileId(
0,
- ): TextEdit {
- indels: [
- Indel {
- insert: "quux",
- delete: 8..11,
- },
- ],
- },
+ ): (
+ TextEdit {
+ indels: [
+ Indel {
+ insert: "quux",
+ delete: 8..11,
+ },
+ ],
+ },
+ None,
+ ),
FileId(
2,
- ): TextEdit {
- indels: [
- Indel {
- insert: "quux",
- delete: 11..14,
- },
- ],
- },
+ ): (
+ TextEdit {
+ indels: [
+ Indel {
+ insert: "quux",
+ delete: 11..14,
+ },
+ ],
+ },
+ None,
+ ),
},
file_system_edits: [
MoveFile {
@@ -997,14 +1018,17 @@ mod fo$0o;
source_file_edits: {
FileId(
0,
- ): TextEdit {
- indels: [
- Indel {
- insert: "foo2",
- delete: 4..7,
- },
- ],
- },
+ ): (
+ TextEdit {
+ indels: [
+ Indel {
+ insert: "foo2",
+ delete: 4..7,
+ },
+ ],
+ },
+ None,
+ ),
},
file_system_edits: [
MoveDir {
@@ -1047,14 +1071,17 @@ mod outer { mod fo$0o; }
source_file_edits: {
FileId(
0,
- ): TextEdit {
- indels: [
- Indel {
- insert: "bar",
- delete: 16..19,
- },
- ],
- },
+ ): (
+ TextEdit {
+ indels: [
+ Indel {
+ insert: "bar",
+ delete: 16..19,
+ },
+ ],
+ },
+ None,
+ ),
},
file_system_edits: [
MoveFile {
@@ -1120,24 +1147,30 @@ pub mod foo$0;
source_file_edits: {
FileId(
0,
- ): TextEdit {
- indels: [
- Indel {
- insert: "foo2",
- delete: 27..30,
- },
- ],
- },
+ ): (
+ TextEdit {
+ indels: [
+ Indel {
+ insert: "foo2",
+ delete: 27..30,
+ },
+ ],
+ },
+ None,
+ ),
FileId(
1,
- ): TextEdit {
- indels: [
- Indel {
- insert: "foo2",
- delete: 8..11,
- },
- ],
- },
+ ): (
+ TextEdit {
+ indels: [
+ Indel {
+ insert: "foo2",
+ delete: 8..11,
+ },
+ ],
+ },
+ None,
+ ),
},
file_system_edits: [
MoveFile {
@@ -1187,14 +1220,17 @@ mod quux;
source_file_edits: {
FileId(
0,
- ): TextEdit {
- indels: [
- Indel {
- insert: "foo2",
- delete: 4..7,
- },
- ],
- },
+ ): (
+ TextEdit {
+ indels: [
+ Indel {
+ insert: "foo2",
+ delete: 4..7,
+ },
+ ],
+ },
+ None,
+ ),
},
file_system_edits: [
MoveFile {
@@ -1325,18 +1361,21 @@ pub fn baz() {}
source_file_edits: {
FileId(
0,
- ): TextEdit {
- indels: [
- Indel {
- insert: "r#fn",
- delete: 4..7,
- },
- Indel {
- insert: "r#fn",
- delete: 22..25,
- },
- ],
- },
+ ): (
+ TextEdit {
+ indels: [
+ Indel {
+ insert: "r#fn",
+ delete: 4..7,
+ },
+ Indel {
+ insert: "r#fn",
+ delete: 22..25,
+ },
+ ],
+ },
+ None,
+ ),
},
file_system_edits: [
MoveFile {
@@ -1395,18 +1434,21 @@ pub fn baz() {}
source_file_edits: {
FileId(
0,
- ): TextEdit {
- indels: [
- Indel {
- insert: "foo",
- delete: 4..8,
- },
- Indel {
- insert: "foo",
- delete: 23..27,
- },
- ],
- },
+ ): (
+ TextEdit {
+ indels: [
+ Indel {
+ insert: "foo",
+ delete: 4..8,
+ },
+ Indel {
+ insert: "foo",
+ delete: 23..27,
+ },
+ ],
+ },
+ None,
+ ),
},
file_system_edits: [
MoveFile {
@@ -2487,4 +2529,109 @@ fn main() {
",
)
}
+
+ #[test]
+ fn extern_crate() {
+ check_prepare(
+ r"
+//- /lib.rs crate:main deps:foo
+extern crate foo$0;
+use foo as qux;
+//- /foo.rs crate:foo
+",
+ expect![[r#"No references found at position"#]],
+ );
+ // FIXME: replace above check_prepare with this once we resolve to usages to extern crate declarations
+ // check(
+ // "bar",
+ // r"
+ // //- /lib.rs crate:main deps:foo
+ // extern crate foo$0;
+ // use foo as qux;
+ // //- /foo.rs crate:foo
+ // ",
+ // r"
+ // extern crate foo as bar;
+ // use bar as qux;
+ // ",
+ // );
+ }
+
+ #[test]
+ fn extern_crate_rename() {
+ check_prepare(
+ r"
+//- /lib.rs crate:main deps:foo
+extern crate foo as qux$0;
+use qux as frob;
+//- /foo.rs crate:foo
+",
+ expect!["Renaming aliases is currently unsupported"],
+ );
+ // FIXME: replace above check_prepare with this once we resolve to usages to extern crate
+ // declarations
+ // check(
+ // "bar",
+ // r"
+ // //- /lib.rs crate:main deps:foo
+ // extern crate foo as qux$0;
+ // use qux as frob;
+ // //- /foo.rs crate:foo
+ // ",
+ // r"
+ // extern crate foo as bar;
+ // use bar as frob;
+ // ",
+ // );
+ }
+
+ #[test]
+ fn extern_crate_self() {
+ check_prepare(
+ r"
+extern crate self$0;
+use self as qux;
+",
+ expect!["No references found at position"],
+ );
+ // FIXME: replace above check_prepare with this once we resolve to usages to extern crate declarations
+ // check(
+ // "bar",
+ // r"
+ // extern crate self$0;
+ // use self as qux;
+ // ",
+ // r"
+ // extern crate self as bar;
+ // use self as qux;
+ // ",
+ // );
+ }
+
+ #[test]
+ fn extern_crate_self_rename() {
+ check_prepare(
+ r"
+//- /lib.rs crate:main deps:foo
+extern crate self as qux$0;
+use qux as frob;
+//- /foo.rs crate:foo
+",
+ expect!["Renaming aliases is currently unsupported"],
+ );
+ // FIXME: replace above check_prepare with this once we resolve to usages to extern crate declarations
+ // check(
+ // "bar",
+ // r"
+ // //- /lib.rs crate:main deps:foo
+ // extern crate self as qux$0;
+ // use qux as frob;
+ // //- /foo.rs crate:foo
+ // ",
+ // r"
+ // extern crate self as bar;
+ // use bar as frob;
+ // ",
+ // );
+ }
}
diff --git a/src/tools/rust-analyzer/crates/ide/src/runnables.rs b/src/tools/rust-analyzer/crates/ide/src/runnables.rs
index 27ad63d82..5f87a7855 100644
--- a/src/tools/rust-analyzer/crates/ide/src/runnables.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/runnables.rs
@@ -2,7 +2,7 @@ use std::fmt;
use ast::HasName;
use cfg::CfgExpr;
-use hir::{AsAssocItem, HasAttrs, HasSource, Semantics};
+use hir::{db::HirDatabase, AsAssocItem, HasAttrs, HasSource, Semantics};
use ide_assists::utils::test_related_attribute;
use ide_db::{
base_db::{FilePosition, FileRange},
@@ -14,7 +14,7 @@ use ide_db::{
use itertools::Itertools;
use stdx::{always, format_to};
use syntax::{
- ast::{self, AstNode, HasAttrs as _},
+ ast::{self, AstNode},
SmolStr, SyntaxNode,
};
@@ -232,7 +232,7 @@ fn find_related_tests(
for def in defs {
let defs = def
.usages(sema)
- .set_scope(search_scope.clone())
+ .set_scope(search_scope.as_ref())
.all()
.references
.into_values()
@@ -307,10 +307,9 @@ 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 root = def.module(sema.db).krate().root_module();
let kind = if name == "main" && def.module(sema.db) == root {
RunnableKind::Bin
@@ -323,10 +322,10 @@ pub(crate) fn runnable_fn(
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);
+ if def.is_test(sema.db) {
+ let attr = TestAttr::from_fn(sema.db, def);
RunnableKind::Test { test_id: test_id(), attr }
- } else if func.value.has_atom_attr("bench") {
+ } else if def.is_bench(sema.db) {
RunnableKind::Bench { test_id: test_id() }
} else {
return None;
@@ -335,7 +334,7 @@ pub(crate) fn runnable_fn(
let nav = NavigationTarget::from_named(
sema.db,
- func.as_ref().map(|it| it as &dyn ast::HasName),
+ def.source(sema.db)?.as_ref().map(|it| it as &dyn ast::HasName),
SymbolKind::Function,
);
let cfg = def.attrs(sema.db).cfg();
@@ -487,12 +486,8 @@ pub struct TestAttr {
}
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 }
+ fn from_fn(db: &dyn HirDatabase, fn_def: hir::Function) -> TestAttr {
+ TestAttr { ignore: fn_def.is_ignore(db) }
}
}
@@ -594,6 +589,9 @@ fn main() {}
#[test]
fn test_foo() {}
+#[::core::prelude::v1::test]
+fn test_full_path() {}
+
#[test]
#[ignore]
fn test_foo() {}
@@ -605,7 +603,7 @@ mod not_a_root {
fn main() {}
}
"#,
- &[TestMod, Bin, Test, Test, Bench],
+ &[TestMod, Bin, Test, Test, Test, Bench],
expect![[r#"
[
Runnable {
@@ -614,7 +612,7 @@ mod not_a_root {
file_id: FileId(
0,
),
- full_range: 0..137,
+ full_range: 0..190,
name: "",
kind: Module,
},
@@ -664,8 +662,29 @@ mod not_a_root {
file_id: FileId(
0,
),
- full_range: 41..75,
- focus_range: 62..70,
+ full_range: 41..92,
+ focus_range: 73..87,
+ name: "test_full_path",
+ kind: Function,
+ },
+ kind: Test {
+ test_id: Path(
+ "test_full_path",
+ ),
+ attr: TestAttr {
+ ignore: false,
+ },
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 94..128,
+ focus_range: 115..123,
name: "test_foo",
kind: Function,
},
@@ -685,8 +704,8 @@ mod not_a_root {
file_id: FileId(
0,
),
- full_range: 77..99,
- focus_range: 89..94,
+ full_range: 130..152,
+ focus_range: 142..147,
name: "bench",
kind: Function,
},
diff --git a/src/tools/rust-analyzer/crates/ide/src/ssr.rs b/src/tools/rust-analyzer/crates/ide/src/ssr.rs
index deaf3c9c4..d8d81869a 100644
--- a/src/tools/rust-analyzer/crates/ide/src/ssr.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/ssr.rs
@@ -126,14 +126,17 @@ mod tests {
source_file_edits: {
FileId(
0,
- ): TextEdit {
- indels: [
- Indel {
- insert: "3",
- delete: 33..34,
- },
- ],
- },
+ ): (
+ TextEdit {
+ indels: [
+ Indel {
+ insert: "3",
+ delete: 33..34,
+ },
+ ],
+ },
+ None,
+ ),
},
file_system_edits: [],
is_snippet: false,
@@ -163,24 +166,30 @@ mod tests {
source_file_edits: {
FileId(
0,
- ): TextEdit {
- indels: [
- Indel {
- insert: "3",
- delete: 33..34,
- },
- ],
- },
+ ): (
+ TextEdit {
+ indels: [
+ Indel {
+ insert: "3",
+ delete: 33..34,
+ },
+ ],
+ },
+ None,
+ ),
FileId(
1,
- ): TextEdit {
- indels: [
- Indel {
- insert: "3",
- delete: 11..12,
- },
- ],
- },
+ ): (
+ TextEdit {
+ indels: [
+ Indel {
+ insert: "3",
+ delete: 11..12,
+ },
+ ],
+ },
+ None,
+ ),
},
file_system_edits: [],
is_snippet: false,
diff --git a/src/tools/rust-analyzer/crates/ide/src/static_index.rs b/src/tools/rust-analyzer/crates/ide/src/static_index.rs
index 3e3d9f8f8..d8696198d 100644
--- a/src/tools/rust-analyzer/crates/ide/src/static_index.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/static_index.rs
@@ -3,13 +3,14 @@
use std::collections::HashMap;
-use hir::{db::HirDatabase, Crate, Module, Semantics};
+use hir::{db::HirDatabase, Crate, Module};
+use ide_db::helpers::get_definition;
use ide_db::{
base_db::{FileId, FileRange, SourceDatabaseExt},
- defs::{Definition, IdentClass},
+ defs::Definition,
FxHashSet, RootDatabase,
};
-use syntax::{AstNode, SyntaxKind::*, SyntaxToken, TextRange, T};
+use syntax::{AstNode, SyntaxKind::*, TextRange, T};
use crate::{
hover::hover_for_definition,
@@ -73,7 +74,7 @@ impl TokenStore {
}
pub fn iter(self) -> impl Iterator<Item = (TokenId, TokenStaticData)> {
- self.0.into_iter().enumerate().map(|(i, x)| (TokenId(i), x))
+ self.0.into_iter().enumerate().map(|(id, data)| (TokenId(id), data))
}
}
@@ -87,7 +88,7 @@ pub struct StaticIndexedFile {
fn all_modules(db: &dyn HirDatabase) -> Vec<Module> {
let mut worklist: Vec<_> =
- Crate::all(db).into_iter().map(|krate| krate.root_module(db)).collect();
+ Crate::all(db).into_iter().map(|krate| krate.root_module()).collect();
let mut modules = Vec::new();
while let Some(module) = worklist.pop() {
@@ -132,9 +133,9 @@ impl StaticIndex<'_> {
// 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 {
+ let tokens = tokens_or_nodes.descendants_with_tokens().filter_map(|it| match it {
syntax::NodeOrToken::Node(_) => None,
- syntax::NodeOrToken::Token(x) => Some(x),
+ syntax::NodeOrToken::Token(it) => Some(it),
});
let hover_config = HoverConfig {
links_in_hover: true,
@@ -154,28 +155,29 @@ impl StaticIndex<'_> {
let range = token.text_range();
let node = token.parent().unwrap();
let def = match get_definition(&sema, token.clone()) {
- Some(x) => x,
+ Some(it) => it,
None => continue,
};
- let id = if let Some(x) = self.def_map.get(&def) {
- *x
+ let id = if let Some(it) = self.def_map.get(&def) {
+ *it
} else {
- let x = self.tokens.insert(TokenStaticData {
+ let it = 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() }),
+ definition: def.try_to_nav(self.db).map(|it| FileRange {
+ file_id: it.file_id,
+ range: it.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
+ self.def_map.insert(def, it);
+ it
};
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,
+ Some(it) => it.file_id == file_id && it.focus_or_full_range() == range,
None => false,
},
});
@@ -187,7 +189,7 @@ impl StaticIndex<'_> {
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 file_id = module.definition_source_file_id(db).original_file(db);
let source_root = db.file_source_root(file_id);
let source_root = db.source_root(source_root);
!source_root.is_library
@@ -201,7 +203,7 @@ impl StaticIndex<'_> {
};
let mut visited_files = FxHashSet::default();
for module in work {
- let file_id = module.definition_source(db).file_id.original_file(db);
+ let file_id = module.definition_source_file_id(db).original_file(db);
if visited_files.contains(&file_id) {
continue;
}
@@ -213,16 +215,6 @@ impl StaticIndex<'_> {
}
}
-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_no_ops);
- if let Some(&[x]) = def.as_deref() {
- return Some(x);
- }
- }
- None
-}
-
#[cfg(test)]
mod tests {
use crate::{fixture, StaticIndex};
@@ -233,14 +225,14 @@ mod tests {
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();
+ let mut range_set: HashSet<_> = ranges.iter().map(|it| it.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:?}");
+ let it = FileRange { file_id: f.file_id, range };
+ if !range_set.contains(&it) {
+ panic!("additional range {it:?}");
}
- range_set.remove(&x);
+ range_set.remove(&it);
}
}
if !range_set.is_empty() {
@@ -251,17 +243,17 @@ mod tests {
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();
+ let mut range_set: HashSet<_> = ranges.iter().map(|it| it.0).collect();
for (_, t) in s.tokens.iter() {
- if let Some(x) = t.definition {
- if x.range.start() == TextSize::from(0) {
+ if let Some(t) = t.definition {
+ if t.range.start() == TextSize::from(0) {
// ignore definitions that are whole of file
continue;
}
- if !range_set.contains(&x) {
- panic!("additional definition {x:?}");
+ if !range_set.contains(&t) {
+ panic!("additional definition {t:?}");
}
- range_set.remove(&x);
+ range_set.remove(&t);
}
}
if !range_set.is_empty() {
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs
index dc06591ff..ae9723640 100644
--- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs
@@ -24,7 +24,7 @@ use syntax::{
use crate::{
syntax_highlighting::{
- escape::{highlight_escape_char, highlight_escape_string},
+ escape::{highlight_escape_byte, highlight_escape_char, highlight_escape_string},
format::highlight_format_string,
highlights::Highlights,
macro_::MacroHighlighter,
@@ -265,10 +265,14 @@ fn traverse(
// set macro and attribute highlighting states
match event.clone() {
- Enter(NodeOrToken::Node(node)) if ast::TokenTree::can_cast(node.kind()) => {
+ Enter(NodeOrToken::Node(node))
+ if current_macro.is_none() && ast::TokenTree::can_cast(node.kind()) =>
+ {
tt_level += 1;
}
- Leave(NodeOrToken::Node(node)) if ast::TokenTree::can_cast(node.kind()) => {
+ Leave(NodeOrToken::Node(node))
+ if current_macro.is_none() && ast::TokenTree::can_cast(node.kind()) =>
+ {
tt_level -= 1;
}
Enter(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => {
@@ -387,7 +391,7 @@ fn traverse(
};
let descended_element = if in_macro {
// Attempt to descend tokens into macro-calls.
- match element {
+ let res = match element {
NodeOrToken::Token(token) if token.kind() != COMMENT => {
let token = match attr_or_derive_item {
Some(AttrOrDerive::Attr(_)) => {
@@ -412,7 +416,8 @@ fn traverse(
}
}
e => e,
- }
+ };
+ res
} else {
element
};
@@ -466,6 +471,14 @@ fn traverse(
};
highlight_escape_char(hl, &char, range.start())
+ } else if ast::Byte::can_cast(token.kind())
+ && ast::Byte::can_cast(descended_token.kind())
+ {
+ let Some(byte) = ast::Byte::cast(token) else {
+ continue;
+ };
+
+ highlight_escape_byte(hl, &byte, range.start())
}
}
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
index 211e35880..5913ca5e4 100644
--- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/escape.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/escape.rs
@@ -1,7 +1,7 @@
//! Syntax highlighting for escape sequences
use crate::syntax_highlighting::highlights::Highlights;
use crate::{HlRange, HlTag};
-use syntax::ast::{Char, IsString};
+use syntax::ast::{Byte, Char, IsString};
use syntax::{AstToken, TextRange, TextSize};
pub(super) fn highlight_escape_string<T: IsString>(
@@ -10,14 +10,14 @@ pub(super) fn highlight_escape_string<T: IsString>(
start: TextSize,
) {
string.escaped_char_ranges(&mut |piece_range, char| {
- if char.is_err() {
- return;
- }
-
if string.text()[piece_range.start().into()..].starts_with('\\') {
+ let highlight = match char {
+ Ok(_) => HlTag::EscapeSequence,
+ Err(_) => HlTag::InvalidEscapeSequence,
+ };
stack.add(HlRange {
range: piece_range + start,
- highlight: HlTag::EscapeSequence.into(),
+ highlight: highlight.into(),
binding_hash: None,
});
}
@@ -26,6 +26,9 @@ pub(super) fn highlight_escape_string<T: IsString>(
pub(super) fn highlight_escape_char(stack: &mut Highlights, char: &Char, start: TextSize) {
if char.value().is_none() {
+ // We do not emit invalid escapes highlighting here. The lexer would likely be in a bad
+ // state and this token contains junks, since `'` is not a reliable delimiter (consider
+ // lifetimes). Nonetheless, parser errors should already be emitted.
return;
}
@@ -43,3 +46,24 @@ pub(super) fn highlight_escape_char(stack: &mut Highlights, char: &Char, start:
TextRange::new(start + TextSize::from(1), start + TextSize::from(text.len() as u32 + 1));
stack.add(HlRange { range, highlight: HlTag::EscapeSequence.into(), binding_hash: None })
}
+
+pub(super) fn highlight_escape_byte(stack: &mut Highlights, byte: &Byte, start: TextSize) {
+ if byte.value().is_none() {
+ // See `highlight_escape_char` for why no error highlighting here.
+ return;
+ }
+
+ let text = byte.text();
+ if !text.starts_with("b'") || !text.ends_with('\'') {
+ return;
+ }
+
+ let text = &text[2..text.len() - 1];
+ if !text.starts_with('\\') {
+ return;
+ }
+
+ let range =
+ TextRange::new(start + TextSize::from(2), start + TextSize::from(text.len() as u32 + 2));
+ stack.add(HlRange { range, highlight: HlTag::EscapeSequence.into(), binding_hash: None })
+}
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
index 3c40246a6..8e96bfa01 100644
--- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs
@@ -269,7 +269,26 @@ fn highlight_name_ref(
h
}
- NameRefClass::FieldShorthand { .. } => SymbolKind::Field.into(),
+ NameRefClass::FieldShorthand { field_ref, .. } => {
+ highlight_def(sema, krate, field_ref.into())
+ }
+ NameRefClass::ExternCrateShorthand { decl, krate: resolved_krate } => {
+ let mut h = HlTag::Symbol(SymbolKind::Module).into();
+
+ if resolved_krate != krate {
+ h |= HlMod::Library
+ }
+ let is_public = decl.visibility(db) == hir::Visibility::Public;
+ if is_public {
+ h |= HlMod::Public
+ }
+ let is_from_builtin_crate = resolved_krate.is_builtin(db);
+ if is_from_builtin_crate {
+ h |= HlMod::DefaultLibrary;
+ }
+ h |= HlMod::CrateRoot;
+ h
+ }
};
h.tag = match name_ref.token_kind() {
@@ -474,6 +493,14 @@ fn highlight_def(
}
h
}
+ Definition::ExternCrateDecl(extern_crate) => {
+ let mut highlight =
+ Highlight::new(HlTag::Symbol(SymbolKind::Module)) | HlMod::CrateRoot;
+ if extern_crate.alias(db).is_none() {
+ highlight |= HlMod::Library;
+ }
+ highlight
+ }
Definition::Label(_) => Highlight::new(HlTag::Symbol(SymbolKind::Label)),
Definition::BuiltinAttr(_) => Highlight::new(HlTag::Symbol(SymbolKind::BuiltinAttr)),
Definition::ToolModule(_) => Highlight::new(HlTag::Symbol(SymbolKind::ToolModule)),
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
index 2c7823069..bbc6b55a6 100644
--- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/html.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/html.rs
@@ -109,6 +109,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
.control { font-style: italic; }
.reference { font-style: italic; font-weight: bold; }
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.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
index 901df147d..2657a6414 100644
--- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs
@@ -288,7 +288,7 @@ fn find_doc_string_in_attr(attr: &hir::Attr, it: &ast::Attr) -> Option<ast::Stri
fn module_def_to_hl_tag(def: Definition) -> HlTag {
let symbol = match def {
- Definition::Module(_) => SymbolKind::Module,
+ Definition::Module(_) | Definition::ExternCrateDecl(_) => SymbolKind::Module,
Definition::Function(_) => SymbolKind::Function,
Definition::Adt(hir::Adt::Struct(_)) => SymbolKind::Struct,
Definition::Adt(hir::Adt::Enum(_)) => SymbolKind::Enum,
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
index f98310911..6d4cdd0ef 100644
--- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tags.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tags.rs
@@ -29,6 +29,7 @@ pub enum HlTag {
Comment,
EscapeSequence,
FormatSpecifier,
+ InvalidEscapeSequence,
Keyword,
NumericLiteral,
Operator(HlOperator),
@@ -166,6 +167,7 @@ impl HlTag {
HlTag::CharLiteral => "char_literal",
HlTag::Comment => "comment",
HlTag::EscapeSequence => "escape_sequence",
+ HlTag::InvalidEscapeSequence => "invalid_escape_sequence",
HlTag::FormatSpecifier => "format_specifier",
HlTag::Keyword => "keyword",
HlTag::Punctuation(punct) => match punct {
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
index 9ed65fbc8..4dcbfe4eb 100644
--- 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
@@ -40,7 +40,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
.control { font-style: italic; }
.reference { font-style: italic; font-weight: bold; }
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.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>
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
index 567ab8ccc..bf5505caf 100644
--- 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
@@ -40,7 +40,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
.control { font-style: italic; }
.reference { font-style: italic; font-weight: bold; }
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.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>
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
index 1e4c06df7..0d1b3c1f1 100644
--- 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
@@ -40,7 +40,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
.control { font-style: italic; }
.reference { font-style: italic; font-weight: bold; }
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.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>
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
index 5d66f832d..dd1528ed0 100644
--- 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
@@ -40,7 +40,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
.control { font-style: italic; }
.reference { font-style: italic; font-weight: bold; }
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.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>
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
index 35f240d42..d5f92aa5d 100644
--- 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
@@ -40,7 +40,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
.control { font-style: italic; }
.reference { font-style: italic; font-weight: bold; }
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.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>
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
index 87b9da46e..88a008796 100644
--- 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
@@ -40,8 +40,10 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
.control { font-style: italic; }
.reference { font-style: italic; font-weight: bold; }
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.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 default_library library">alloc</span> <span class="keyword">as</span> <span class="module crate_root default_library declaration library">abc</span><span class="semicolon">;</span>
+<span class="keyword">extern</span> <span class="keyword">crate</span> <span class="module crate_root default_library library">alloc</span> <span class="keyword">as</span> <span class="module crate_root declaration">abc</span><span class="semicolon">;</span>
+<span class="keyword">extern</span> <span class="keyword">crate</span> <span class="unresolved_reference">unresolved</span> <span class="keyword">as</span> <span class="module crate_root declaration">definitely_unresolved</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
index 6b049f379..bdeb09d2f 100644
--- 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
@@ -40,7 +40,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
.control { font-style: italic; }
.reference { font-style: italic; font-weight: bold; }
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.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>
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
index d9c3db6fb..f9c33b8a6 100644
--- 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
@@ -40,7 +40,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
.control { font-style: italic; }
.reference { font-style: italic; font-weight: bold; }
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.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="punctuation">&</span><span class="builtin_type">str</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
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
index 3900959be..2043752bc 100644
--- 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
@@ -40,9 +40,10 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
.control { font-style: italic; }
.reference { font-style: italic; font-weight: bold; }
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.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>
+<pre><code><span class="keyword">extern</span> <span class="keyword">crate</span> <span class="self_keyword crate_root">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>
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
index f98e0b1cd..ec39998de 100644
--- 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
@@ -40,7 +40,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
.control { font-style: italic; }
.reference { font-style: italic; font-weight: bold; }
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.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>
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
index 2cbbf6964..06b66b302 100644
--- 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
@@ -40,7 +40,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
.control { font-style: italic; }
.reference { font-style: italic; font-weight: bold; }
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.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 macro">{</span>
<span class="brace macro">{</span>
@@ -89,8 +90,18 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
<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">concat</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">include</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">format_args</span> <span class="brace">{</span><span class="brace">}</span>
+
+<span class="macro">include</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="none macro">concat</span><span class="punctuation macro">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"foo/"</span><span class="comma macro">,</span> <span class="string_literal macro">"foo.rs"</span><span class="parenthesis macro">)</span><span class="parenthesis macro">)</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="unresolved_reference">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"Hello, {}!"</span><span class="comma macro">,</span> <span class="numeric_literal macro">92</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
+ <span class="macro">format_args</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"Hello, </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">!"</span><span class="comma macro">,</span> <span class="numeric_literal macro">92</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
<span class="macro">dont_color_me_braces</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
<span class="macro">noop</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="macro macro">noop</span><span class="macro_bang macro">!</span><span class="parenthesis macro">(</span><span class="numeric_literal macro">1</span><span class="parenthesis macro">)</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
-<span class="brace">}</span></code></pre> \ No newline at end of file
+<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
index 8a1d69816..4dcf8e5f0 100644
--- 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
@@ -40,7 +40,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
.control { font-style: italic; }
.reference { font-style: italic; font-weight: bold; }
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.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>
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
index c4c3e3dc2..084bbf2f7 100644
--- 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
@@ -40,7 +40,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
.control { font-style: italic; }
.reference { font-style: italic; font-weight: bold; }
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.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>
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
index 2369071ae..1af4bcfbd 100644
--- 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
@@ -40,7 +40,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
.control { font-style: italic; }
.reference { font-style: italic; font-weight: bold; }
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.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>
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
index bff35c897..ec18c3ea1 100644
--- 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
@@ -40,7 +40,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
.control { font-style: italic; }
.reference { font-style: italic; font-weight: bold; }
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.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>
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
index fa374b04f..3ac8aa9cc 100644
--- 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
@@ -40,7 +40,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
.control { font-style: italic; }
.reference { font-style: italic; font-weight: bold; }
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.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>
@@ -105,6 +106,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
<span class="keyword">let</span> <span class="variable declaration">a</span> <span class="operator">=</span> <span class="char_literal">'</span><span class="escape_sequence">\x65</span><span class="char_literal">'</span><span class="semicolon">;</span>
<span class="keyword">let</span> <span class="variable declaration">a</span> <span class="operator">=</span> <span class="char_literal">'</span><span class="escape_sequence">\x00</span><span class="char_literal">'</span><span class="semicolon">;</span>
+ <span class="keyword">let</span> <span class="variable declaration">a</span> <span class="operator">=</span> <span class="byte_literal">b'</span><span class="escape_sequence">\xFF</span><span class="byte_literal">'</span><span class="semicolon">;</span>
+
<span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"Hello </span><span class="escape_sequence">{{</span><span class="string_literal macro">Hello</span><span class="escape_sequence">}}</span><span class="string_literal macro">"</span><span class="parenthesis macro">)</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 macro">(</span><span class="string_literal macro">"Hello"</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="comment">// =&gt; "Hello"</span>
@@ -159,9 +162,10 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
<span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"Hello</span><span class="escape_sequence">\n</span><span class="string_literal macro">World"</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
<span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</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 macro"> World"</span><span class="parenthesis macro">)</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="keyword">let</span> <span class="punctuation">_</span> <span class="operator">=</span> <span class="string_literal">r"\\"</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="invalid_escape_sequence">\xFF</span><span class="escape_sequence">\u{FF}</span><span class="escape_sequence">\n</span><span class="string_literal">"</span><span class="semicolon">;</span> <span class="comment">// invalid non-UTF8 escape sequences</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">\xFF</span><span class="invalid_escape_sequence">\u{FF}</span><span class="escape_sequence">\n</span><span class="string_literal">"</span><span class="semicolon">;</span> <span class="comment">// valid bytes, invalid unicodes</span>
+ <span class="keyword">let</span> <span class="punctuation">_</span> <span class="operator">=</span> <span class="string_literal">c"</span><span class="escape_sequence">\u{FF}</span><span class="escape_sequence">\xFF</span><span class="string_literal">"</span><span class="semicolon">;</span> <span class="comment">// valid bytes, valid unicodes</span>
+ <span class="keyword">let</span> <span class="variable declaration reference">backslash</span> <span class="operator">=</span> <span class="string_literal">r"\\"</span><span class="semicolon">;</span>
<span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="escape_sequence">\x41</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="none macro">A</span> <span class="operator macro">=</span> <span class="numeric_literal macro">92</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
<span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="variable">ничоси</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="none macro">ничоси</span> <span class="operator macro">=</span> <span class="numeric_literal macro">92</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
@@ -173,6 +177,6 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
<span class="macro">assert</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="bool_literal macro">true</span><span class="comma macro">,</span> <span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> asdasd"</span><span class="comma macro">,</span> <span class="numeric_literal macro">1</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
<span class="macro">toho</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">fmt"</span><span class="comma macro">,</span> <span class="numeric_literal macro">0</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
<span class="macro unsafe">asm</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"mov eax, </span><span class="format_specifier">{</span><span class="numeric_literal">0</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
- <span class="macro">format_args</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="none macro">concat</span><span class="punctuation macro">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="parenthesis macro">)</span><span class="comma macro">,</span> <span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
- <span class="macro">format_args</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="none macro">format_args</span><span class="operator macro">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="numeric_literal macro">0</span><span class="parenthesis macro">)</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
+ <span class="macro">format_args</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="none macro">concat</span><span class="punctuation macro">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="parenthesis macro">)</span><span class="comma macro">,</span> <span class="string_literal macro">"{}"</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
+ <span class="macro">format_args</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="variable macro reference">backslash</span><span class="comma macro">,</span> <span class="none macro">format_args</span><span class="punctuation macro">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="numeric_literal macro">0</span><span class="parenthesis macro">)</span><span class="comma macro">,</span> <span class="unresolved_reference macro">foo</span><span class="comma macro">,</span> <span class="string_literal macro">"bar"</span><span class="comma macro">,</span> <span class="none macro">toho</span><span class="punctuation macro">!</span><span class="parenthesis macro">(</span><span class="parenthesis macro">)</span><span class="comma macro">,</span> <span class="variable macro reference">backslash</span><span class="parenthesis macro">)</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
index 654d51b8a..c72ea54e9 100644
--- 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
@@ -40,7 +40,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
.control { font-style: italic; }
.reference { font-style: italic; font-weight: bold; }
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.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>
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
index 497992f68..8749d355c 100644
--- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs
@@ -48,6 +48,7 @@ fn macros() {
check_highlighting(
r#"
//- proc_macros: mirror
+//- /lib.rs crate:lib
proc_macros::mirror! {
{
,i32 :x pub
@@ -95,11 +96,23 @@ macro without_args {
}
}
+#[rustc_builtin_macro]
+macro_rules! concat {}
+#[rustc_builtin_macro]
+macro_rules! include {}
+#[rustc_builtin_macro]
+macro_rules! format_args {}
+
+include!(concat!("foo/", "foo.rs"));
+
fn main() {
- println!("Hello, {}!", 92);
+ format_args!("Hello, {}!", 92);
dont_color_me_braces!();
noop!(noop!(1));
}
+//- /foo/foo.rs crate:foo
+mod foo {}
+use self::foo as bar;
"#,
expect_file!["./test_data/highlight_macros.html"],
false,
@@ -451,6 +464,8 @@ fn main() {
let a = '\x65';
let a = '\x00';
+ let a = b'\xFF';
+
println!("Hello {{Hello}}");
// from https://doc.rust-lang.org/std/fmt/index.html
println!("Hello"); // => "Hello"
@@ -505,9 +520,10 @@ fn main() {
println!("Hello\nWorld");
println!("\u{48}\x65\x6C\x6C\x6F World");
- let _ = "\x28\x28\x00\x63\n";
- let _ = b"\x28\x28\x00\x63\n";
- let _ = r"\\";
+ let _ = "\x28\x28\x00\x63\xFF\u{FF}\n"; // invalid non-UTF8 escape sequences
+ let _ = b"\x28\x28\x00\x63\xFF\u{FF}\n"; // valid bytes, invalid unicodes
+ let _ = c"\u{FF}\xFF"; // valid bytes, valid unicodes
+ let backslash = r"\\";
println!("{\x41}", A = 92);
println!("{ничоси}", ничоси = 92);
@@ -520,7 +536,7 @@ fn main() {
toho!("{}fmt", 0);
asm!("mov eax, {0}");
format_args!(concat!("{}"), "{}");
- format_args!("{}", format_args!("{}", 0));
+ format_args!("{} {} {} {} {} {}", backslash, format_args!("{}", 0), foo, "bar", toho!(), backslash);
}"#,
expect_file!["./test_data/highlight_strings.html"],
false,
@@ -788,6 +804,7 @@ fn test_extern_crate() {
//- /main.rs crate:main deps:std,alloc
extern crate std;
extern crate alloc as abc;
+extern crate unresolved as definitely_unresolved;
//- /std/lib.rs crate:std
pub struct S;
//- /alloc/lib.rs crate:alloc
diff --git a/src/tools/rust-analyzer/crates/ide/src/view_memory_layout.rs b/src/tools/rust-analyzer/crates/ide/src/view_memory_layout.rs
new file mode 100644
index 000000000..2f6332abd
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/view_memory_layout.rs
@@ -0,0 +1,409 @@
+use std::fmt;
+
+use hir::{Field, HirDisplay, Layout, Semantics, Type};
+use ide_db::{
+ defs::Definition,
+ helpers::{get_definition, pick_best_token},
+ RootDatabase,
+};
+use syntax::{AstNode, SyntaxKind};
+
+use crate::FilePosition;
+
+pub struct MemoryLayoutNode {
+ pub item_name: String,
+ pub typename: String,
+ pub size: u64,
+ pub alignment: u64,
+ pub offset: u64,
+ pub parent_idx: i64,
+ pub children_start: i64,
+ pub children_len: u64,
+}
+
+pub struct RecursiveMemoryLayout {
+ pub nodes: Vec<MemoryLayoutNode>,
+}
+
+// NOTE: this is currently strictly for testing and so isn't super useful as a visualization tool, however it could be adapted to become one?
+impl fmt::Display for RecursiveMemoryLayout {
+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fn process(
+ fmt: &mut fmt::Formatter<'_>,
+ nodes: &Vec<MemoryLayoutNode>,
+ idx: usize,
+ depth: usize,
+ ) -> fmt::Result {
+ let mut out = "\t".repeat(depth);
+ let node = &nodes[idx];
+ out += &format!(
+ "{}: {} (size: {}, align: {}, field offset: {})\n",
+ node.item_name, node.typename, node.size, node.alignment, node.offset
+ );
+ write!(fmt, "{}", out)?;
+ if node.children_start != -1 {
+ for j in nodes[idx].children_start
+ ..(nodes[idx].children_start + nodes[idx].children_len as i64)
+ {
+ process(fmt, nodes, j as usize, depth + 1)?;
+ }
+ }
+ Ok(())
+ }
+
+ process(fmt, &self.nodes, 0, 0)
+ }
+}
+
+enum FieldOrTupleIdx {
+ Field(Field),
+ TupleIdx(usize),
+}
+
+impl FieldOrTupleIdx {
+ fn name(&self, db: &RootDatabase) -> String {
+ match *self {
+ FieldOrTupleIdx::Field(f) => f
+ .name(db)
+ .as_str()
+ .map(|s| s.to_owned())
+ .unwrap_or_else(|| format!(".{}", f.name(db).as_tuple_index().unwrap())),
+ FieldOrTupleIdx::TupleIdx(i) => format!(".{i}").to_owned(),
+ }
+ }
+
+ fn index(&self) -> usize {
+ match *self {
+ FieldOrTupleIdx::Field(f) => f.index(),
+ FieldOrTupleIdx::TupleIdx(i) => i,
+ }
+ }
+}
+
+// Feature: View Memory Layout
+//
+// Displays the recursive memory layout of a datatype.
+//
+// |===
+// | Editor | Action Name
+//
+// | VS Code | **rust-analyzer: View Memory Layout**
+// |===
+pub(crate) fn view_memory_layout(
+ db: &RootDatabase,
+ position: FilePosition,
+) -> Option<RecursiveMemoryLayout> {
+ let sema = Semantics::new(db);
+ let file = sema.parse(position.file_id);
+ let token =
+ pick_best_token(file.syntax().token_at_offset(position.offset), |kind| match kind {
+ SyntaxKind::IDENT => 3,
+ _ => 0,
+ })?;
+
+ let def = get_definition(&sema, token)?;
+
+ let ty = match def {
+ Definition::Adt(it) => it.ty(db),
+ Definition::TypeAlias(it) => it.ty(db),
+ Definition::BuiltinType(it) => it.ty(db),
+ Definition::SelfType(it) => it.self_ty(db),
+ Definition::Local(it) => it.ty(db),
+ Definition::Field(it) => it.ty(db),
+ Definition::Const(it) => it.ty(db),
+ Definition::Static(it) => it.ty(db),
+ _ => return None,
+ };
+
+ fn read_layout(
+ nodes: &mut Vec<MemoryLayoutNode>,
+ db: &RootDatabase,
+ ty: &Type,
+ layout: &Layout,
+ parent_idx: usize,
+ ) {
+ let mut fields = ty
+ .fields(db)
+ .into_iter()
+ .map(|(f, ty)| (FieldOrTupleIdx::Field(f), ty))
+ .chain(
+ ty.tuple_fields(db)
+ .into_iter()
+ .enumerate()
+ .map(|(i, ty)| (FieldOrTupleIdx::TupleIdx(i), ty)),
+ )
+ .collect::<Vec<_>>();
+
+ if fields.len() == 0 {
+ return;
+ }
+
+ fields.sort_by_key(|(f, _)| layout.field_offset(f.index()).unwrap());
+
+ let children_start = nodes.len();
+ nodes[parent_idx].children_start = children_start as i64;
+ nodes[parent_idx].children_len = fields.len() as u64;
+
+ for (field, child_ty) in fields.iter() {
+ if let Ok(child_layout) = child_ty.layout(db) {
+ nodes.push(MemoryLayoutNode {
+ item_name: field.name(db),
+ typename: child_ty.display(db).to_string(),
+ size: child_layout.size(),
+ alignment: child_layout.align(),
+ offset: layout.field_offset(field.index()).unwrap_or(0),
+ parent_idx: parent_idx as i64,
+ children_start: -1,
+ children_len: 0,
+ });
+ } else {
+ nodes.push(MemoryLayoutNode {
+ item_name: field.name(db)
+ + format!("(no layout data: {:?})", child_ty.layout(db).unwrap_err())
+ .as_ref(),
+ typename: child_ty.display(db).to_string(),
+ size: 0,
+ offset: 0,
+ alignment: 0,
+ parent_idx: parent_idx as i64,
+ children_start: -1,
+ children_len: 0,
+ });
+ }
+ }
+
+ for (i, (_, child_ty)) in fields.iter().enumerate() {
+ if let Ok(child_layout) = child_ty.layout(db) {
+ read_layout(nodes, db, &child_ty, &child_layout, children_start + i);
+ }
+ }
+ }
+
+ ty.layout(db)
+ .map(|layout| {
+ let item_name = match def {
+ // def is a datatype
+ Definition::Adt(_)
+ | Definition::TypeAlias(_)
+ | Definition::BuiltinType(_)
+ | Definition::SelfType(_) => "[ROOT]".to_owned(),
+
+ // def is an item
+ def => def
+ .name(db)
+ .map(|n| {
+ n.as_str()
+ .map(|s| s.to_owned())
+ .unwrap_or_else(|| format!(".{}", n.as_tuple_index().unwrap()))
+ })
+ .unwrap_or("[ROOT]".to_owned()),
+ };
+
+ let typename = ty.display(db).to_string();
+
+ let mut nodes = vec![MemoryLayoutNode {
+ item_name,
+ typename: typename.clone(),
+ size: layout.size(),
+ offset: 0,
+ alignment: layout.align(),
+ parent_idx: -1,
+ children_start: -1,
+ children_len: 0,
+ }];
+ read_layout(&mut nodes, db, &ty, &layout, 0);
+
+ RecursiveMemoryLayout { nodes }
+ })
+ .ok()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::fixture;
+ use expect_test::expect;
+
+ fn make_memory_layout(ra_fixture: &str) -> Option<RecursiveMemoryLayout> {
+ let (analysis, position, _) = fixture::annotations(ra_fixture);
+
+ view_memory_layout(&analysis.db, position)
+ }
+
+ #[test]
+ fn view_memory_layout_none() {
+ assert!(make_memory_layout(r#"$0"#).is_none());
+ assert!(make_memory_layout(r#"stru$0ct Blah {}"#).is_none());
+ }
+
+ #[test]
+ fn view_memory_layout_primitive() {
+ expect![[r#"
+ foo: i32 (size: 4, align: 4, field offset: 0)
+ "#]]
+ .assert_eq(
+ &make_memory_layout(
+ r#"
+fn main() {
+ let foo$0 = 109; // default i32
+}
+"#,
+ )
+ .unwrap()
+ .to_string(),
+ );
+ }
+
+ #[test]
+ fn view_memory_layout_constant() {
+ expect![[r#"
+ BLAH: bool (size: 1, align: 1, field offset: 0)
+ "#]]
+ .assert_eq(
+ &make_memory_layout(
+ r#"
+const BLAH$0: bool = 0;
+"#,
+ )
+ .unwrap()
+ .to_string(),
+ );
+ }
+
+ #[test]
+ fn view_memory_layout_static() {
+ expect![[r#"
+ BLAH: bool (size: 1, align: 1, field offset: 0)
+ "#]]
+ .assert_eq(
+ &make_memory_layout(
+ r#"
+static BLAH$0: bool = 0;
+"#,
+ )
+ .unwrap()
+ .to_string(),
+ );
+ }
+
+ #[test]
+ fn view_memory_layout_tuple() {
+ expect![[r#"
+ x: (f64, u8, i64) (size: 24, align: 8, field offset: 0)
+ .0: f64 (size: 8, align: 8, field offset: 0)
+ .1: u8 (size: 1, align: 1, field offset: 8)
+ .2: i64 (size: 8, align: 8, field offset: 16)
+ "#]]
+ .assert_eq(
+ &make_memory_layout(
+ r#"
+fn main() {
+ let x$0 = (101.0, 111u8, 119i64);
+}
+"#,
+ )
+ .unwrap()
+ .to_string(),
+ );
+ }
+
+ #[test]
+ fn view_memory_layout_c_struct() {
+ expect![[r#"
+ [ROOT]: Blah (size: 16, align: 4, field offset: 0)
+ a: u32 (size: 4, align: 4, field offset: 0)
+ b: (i32, u8) (size: 8, align: 4, field offset: 4)
+ .0: i32 (size: 4, align: 4, field offset: 0)
+ .1: u8 (size: 1, align: 1, field offset: 4)
+ c: i8 (size: 1, align: 1, field offset: 12)
+ "#]]
+ .assert_eq(
+ &make_memory_layout(
+ r#"
+#[repr(C)]
+struct Blah$0 {
+ a: u32,
+ b: (i32, u8),
+ c: i8,
+}
+"#,
+ )
+ .unwrap()
+ .to_string(),
+ );
+ }
+
+ #[test]
+ fn view_memory_layout_struct() {
+ expect![[r#"
+ [ROOT]: Blah (size: 16, align: 4, field offset: 0)
+ b: (i32, u8) (size: 8, align: 4, field offset: 0)
+ .0: i32 (size: 4, align: 4, field offset: 0)
+ .1: u8 (size: 1, align: 1, field offset: 4)
+ a: u32 (size: 4, align: 4, field offset: 8)
+ c: i8 (size: 1, align: 1, field offset: 12)
+ "#]]
+ .assert_eq(
+ &make_memory_layout(
+ r#"
+struct Blah$0 {
+ a: u32,
+ b: (i32, u8),
+ c: i8,
+}
+"#,
+ )
+ .unwrap()
+ .to_string(),
+ );
+ }
+
+ #[test]
+ fn view_memory_layout_member() {
+ expect![[r#"
+ a: bool (size: 1, align: 1, field offset: 0)
+ "#]]
+ .assert_eq(
+ &make_memory_layout(
+ r#"
+#[repr(C)]
+struct Oof {
+ a$0: bool,
+}
+"#,
+ )
+ .unwrap()
+ .to_string(),
+ );
+ }
+
+ #[test]
+ fn view_memory_layout_alias() {
+ let ml_a = make_memory_layout(
+ r#"
+struct X {
+ a: u32,
+ b: i8,
+ c: (f32, f32),
+}
+
+type Foo$0 = X;
+"#,
+ )
+ .unwrap();
+
+ let ml_b = make_memory_layout(
+ r#"
+struct X$0 {
+ a: u32,
+ b: i8,
+ c: (f32, f32),
+}
+"#,
+ )
+ .unwrap();
+
+ assert_eq!(ml_a.to_string(), ml_b.to_string());
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/intern/Cargo.toml b/src/tools/rust-analyzer/crates/intern/Cargo.toml
index dcd0d7881..4d56c7719 100644
--- a/src/tools/rust-analyzer/crates/intern/Cargo.toml
+++ b/src/tools/rust-analyzer/crates/intern/Cargo.toml
@@ -15,7 +15,7 @@ doctest = false
[dependencies]
# We need to freeze the version of the crate, as the raw-api feature is considered unstable
dashmap = { version = "=5.4.0", features = ["raw-api"] }
-hashbrown = { version = "0.12.1", default-features = false }
+hashbrown.workspace = true
once_cell = "1.17.0"
rustc-hash = "1.1.0"
triomphe.workspace = true
diff --git a/src/tools/rust-analyzer/crates/load-cargo/Cargo.toml b/src/tools/rust-analyzer/crates/load-cargo/Cargo.toml
new file mode 100644
index 000000000..f041ca88a
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/load-cargo/Cargo.toml
@@ -0,0 +1,25 @@
+[package]
+name = "load-cargo"
+version = "0.0.0"
+description = "TBD"
+
+rust-version.workspace = true
+edition.workspace = true
+license.workspace = true
+authors.workspace = true
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+anyhow = "1.0.62"
+crossbeam-channel = "0.5.5"
+itertools = "0.10.5"
+tracing = "0.1.35"
+
+ide.workspace = true
+ide-db.workspace =true
+proc-macro-api.workspace = true
+project-model.workspace = true
+tt.workspace = true
+vfs.workspace = true
+vfs-notify.workspace = true
diff --git a/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs b/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs
new file mode 100644
index 000000000..7a795dd62
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs
@@ -0,0 +1,441 @@
+//! Loads a Cargo project into a static instance of analysis, without support
+//! for incorporating changes.
+// Note, don't remove any public api from this. This API is consumed by external tools
+// to run rust-analyzer as a library.
+use std::{collections::hash_map::Entry, mem, path::Path, sync};
+
+use ::tt::token_id as tt;
+use crossbeam_channel::{unbounded, Receiver};
+use ide::{AnalysisHost, Change, SourceRoot};
+use ide_db::{
+ base_db::{
+ CrateGraph, Env, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacroKind,
+ ProcMacroLoadResult, ProcMacros,
+ },
+ FxHashMap,
+};
+use itertools::Itertools;
+use proc_macro_api::{MacroDylib, ProcMacroServer};
+use project_model::{CargoConfig, PackageRoot, ProjectManifest, ProjectWorkspace};
+use vfs::{file_set::FileSetConfig, loader::Handle, AbsPath, AbsPathBuf, VfsPath};
+
+pub struct LoadCargoConfig {
+ pub load_out_dirs_from_check: bool,
+ pub with_proc_macro_server: ProcMacroServerChoice,
+ pub prefill_caches: bool,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum ProcMacroServerChoice {
+ Sysroot,
+ Explicit(AbsPathBuf),
+ None,
+}
+
+pub fn load_workspace_at(
+ root: &Path,
+ cargo_config: &CargoConfig,
+ load_config: &LoadCargoConfig,
+ progress: &dyn Fn(String),
+) -> anyhow::Result<(AnalysisHost, vfs::Vfs, Option<ProcMacroServer>)> {
+ let root = AbsPathBuf::assert(std::env::current_dir()?.join(root));
+ let root = ProjectManifest::discover_single(&root)?;
+ let mut workspace = ProjectWorkspace::load(root, cargo_config, progress)?;
+
+ if load_config.load_out_dirs_from_check {
+ let build_scripts = workspace.run_build_scripts(cargo_config, progress)?;
+ workspace.set_build_scripts(build_scripts)
+ }
+
+ load_workspace(workspace, &cargo_config.extra_env, load_config)
+}
+
+pub fn load_workspace(
+ ws: ProjectWorkspace,
+ extra_env: &FxHashMap<String, String>,
+ load_config: &LoadCargoConfig,
+) -> anyhow::Result<(AnalysisHost, vfs::Vfs, Option<ProcMacroServer>)> {
+ let (sender, receiver) = unbounded();
+ let mut vfs = vfs::Vfs::default();
+ let mut loader = {
+ let loader =
+ vfs_notify::NotifyHandle::spawn(Box::new(move |msg| sender.send(msg).unwrap()));
+ Box::new(loader)
+ };
+
+ let proc_macro_server = match &load_config.with_proc_macro_server {
+ ProcMacroServerChoice::Sysroot => ws
+ .find_sysroot_proc_macro_srv()
+ .and_then(|it| ProcMacroServer::spawn(it).map_err(Into::into)),
+ ProcMacroServerChoice::Explicit(path) => {
+ ProcMacroServer::spawn(path.clone()).map_err(Into::into)
+ }
+ ProcMacroServerChoice::None => Err(anyhow::format_err!("proc macro server disabled")),
+ };
+
+ let (crate_graph, proc_macros) = ws.to_crate_graph(
+ &mut |path: &AbsPath| {
+ let contents = loader.load_sync(path);
+ let path = vfs::VfsPath::from(path.to_path_buf());
+ vfs.set_file_contents(path.clone(), contents);
+ vfs.file_id(&path)
+ },
+ extra_env,
+ );
+ let proc_macros = {
+ let proc_macro_server = match &proc_macro_server {
+ Ok(it) => Ok(it),
+ Err(e) => Err(e.to_string()),
+ };
+ proc_macros
+ .into_iter()
+ .map(|(crate_id, path)| {
+ (
+ crate_id,
+ path.map_or_else(
+ |_| Err("proc macro crate is missing dylib".to_owned()),
+ |(_, path)| {
+ proc_macro_server.as_ref().map_err(Clone::clone).and_then(
+ |proc_macro_server| load_proc_macro(proc_macro_server, &path, &[]),
+ )
+ },
+ ),
+ )
+ })
+ .collect()
+ };
+
+ let project_folders = ProjectFolders::new(&[ws], &[]);
+ loader.set_config(vfs::loader::Config {
+ load: project_folders.load,
+ watch: vec![],
+ version: 0,
+ });
+
+ let host = load_crate_graph(
+ crate_graph,
+ proc_macros,
+ project_folders.source_root_config,
+ &mut vfs,
+ &receiver,
+ );
+
+ if load_config.prefill_caches {
+ host.analysis().parallel_prime_caches(1, |_| {})?;
+ }
+ Ok((host, vfs, proc_macro_server.ok()))
+}
+
+#[derive(Default)]
+pub struct ProjectFolders {
+ pub load: Vec<vfs::loader::Entry>,
+ pub watch: Vec<usize>,
+ pub source_root_config: SourceRootConfig,
+}
+
+impl ProjectFolders {
+ pub fn new(workspaces: &[ProjectWorkspace], global_excludes: &[AbsPathBuf]) -> ProjectFolders {
+ let mut res = ProjectFolders::default();
+ let mut fsc = FileSetConfig::builder();
+ let mut local_filesets = vec![];
+
+ // Dedup source roots
+ // Depending on the project setup, we can have duplicated source roots, or for example in
+ // the case of the rustc workspace, we can end up with two source roots that are almost the
+ // same but not quite, like:
+ // PackageRoot { is_local: false, include: [AbsPathBuf(".../rust/src/tools/miri/cargo-miri")], exclude: [] }
+ // PackageRoot {
+ // is_local: true,
+ // include: [AbsPathBuf(".../rust/src/tools/miri/cargo-miri"), AbsPathBuf(".../rust/build/x86_64-pc-windows-msvc/stage0-tools/x86_64-pc-windows-msvc/release/build/cargo-miri-85801cd3d2d1dae4/out")],
+ // exclude: [AbsPathBuf(".../rust/src/tools/miri/cargo-miri/.git"), AbsPathBuf(".../rust/src/tools/miri/cargo-miri/target")]
+ // }
+ //
+ // The first one comes from the explicit rustc workspace which points to the rustc workspace itself
+ // The second comes from the rustc workspace that we load as the actual project workspace
+ // These `is_local` differing in this kind of way gives us problems, especially when trying to filter diagnostics as we don't report diagnostics for external libraries.
+ // So we need to deduplicate these, usually it would be enough to deduplicate by `include`, but as the rustc example shows here that doesn't work,
+ // so we need to also coalesce the includes if they overlap.
+
+ let mut roots: Vec<_> = workspaces
+ .iter()
+ .flat_map(|ws| ws.to_roots())
+ .update(|root| root.include.sort())
+ .sorted_by(|a, b| a.include.cmp(&b.include))
+ .collect();
+
+ // map that tracks indices of overlapping roots
+ let mut overlap_map = FxHashMap::<_, Vec<_>>::default();
+ let mut done = false;
+
+ while !mem::replace(&mut done, true) {
+ // maps include paths to indices of the corresponding root
+ let mut include_to_idx = FxHashMap::default();
+ // Find and note down the indices of overlapping roots
+ for (idx, root) in roots.iter().enumerate().filter(|(_, it)| !it.include.is_empty()) {
+ for include in &root.include {
+ match include_to_idx.entry(include) {
+ Entry::Occupied(e) => {
+ overlap_map.entry(*e.get()).or_default().push(idx);
+ }
+ Entry::Vacant(e) => {
+ e.insert(idx);
+ }
+ }
+ }
+ }
+ for (k, v) in overlap_map.drain() {
+ done = false;
+ for v in v {
+ let r = mem::replace(
+ &mut roots[v],
+ PackageRoot { is_local: false, include: vec![], exclude: vec![] },
+ );
+ roots[k].is_local |= r.is_local;
+ roots[k].include.extend(r.include);
+ roots[k].exclude.extend(r.exclude);
+ }
+ roots[k].include.sort();
+ roots[k].exclude.sort();
+ roots[k].include.dedup();
+ roots[k].exclude.dedup();
+ }
+ }
+
+ for root in roots.into_iter().filter(|it| !it.include.is_empty()) {
+ let file_set_roots: Vec<VfsPath> =
+ root.include.iter().cloned().map(VfsPath::from).collect();
+
+ let entry = {
+ let mut dirs = vfs::loader::Directories::default();
+ dirs.extensions.push("rs".into());
+ dirs.include.extend(root.include);
+ dirs.exclude.extend(root.exclude);
+ for excl in global_excludes {
+ if dirs
+ .include
+ .iter()
+ .any(|incl| incl.starts_with(excl) || excl.starts_with(incl))
+ {
+ dirs.exclude.push(excl.clone());
+ }
+ }
+
+ vfs::loader::Entry::Directories(dirs)
+ };
+
+ if root.is_local {
+ res.watch.push(res.load.len());
+ }
+ res.load.push(entry);
+
+ if root.is_local {
+ local_filesets.push(fsc.len());
+ }
+ fsc.add_file_set(file_set_roots)
+ }
+
+ let fsc = fsc.build();
+ res.source_root_config = SourceRootConfig { fsc, local_filesets };
+
+ res
+ }
+}
+
+#[derive(Default, Debug)]
+pub struct SourceRootConfig {
+ pub fsc: FileSetConfig,
+ pub local_filesets: Vec<usize>,
+}
+
+impl SourceRootConfig {
+ pub fn partition(&self, vfs: &vfs::Vfs) -> Vec<SourceRoot> {
+ self.fsc
+ .partition(vfs)
+ .into_iter()
+ .enumerate()
+ .map(|(idx, file_set)| {
+ let is_local = self.local_filesets.contains(&idx);
+ if is_local {
+ SourceRoot::new_local(file_set)
+ } else {
+ SourceRoot::new_library(file_set)
+ }
+ })
+ .collect()
+ }
+}
+
+/// Load the proc-macros for the given lib path, replacing all expanders whose names are in `dummy_replace`
+/// with an identity dummy expander.
+pub fn load_proc_macro(
+ server: &ProcMacroServer,
+ path: &AbsPath,
+ dummy_replace: &[Box<str>],
+) -> ProcMacroLoadResult {
+ let res: Result<Vec<_>, String> = (|| {
+ let dylib = MacroDylib::new(path.to_path_buf());
+ let vec = server.load_dylib(dylib).map_err(|e| format!("{e}"))?;
+ if vec.is_empty() {
+ return Err("proc macro library returned no proc macros".to_string());
+ }
+ Ok(vec
+ .into_iter()
+ .map(|expander| expander_to_proc_macro(expander, dummy_replace))
+ .collect())
+ })();
+ match res {
+ Ok(proc_macros) => {
+ tracing::info!(
+ "Loaded proc-macros for {path}: {:?}",
+ proc_macros.iter().map(|it| it.name.clone()).collect::<Vec<_>>()
+ );
+ Ok(proc_macros)
+ }
+ Err(e) => {
+ tracing::warn!("proc-macro loading for {path} failed: {e}");
+ Err(e)
+ }
+ }
+}
+
+fn load_crate_graph(
+ crate_graph: CrateGraph,
+ proc_macros: ProcMacros,
+ source_root_config: SourceRootConfig,
+ vfs: &mut vfs::Vfs,
+ receiver: &Receiver<vfs::loader::Message>,
+) -> AnalysisHost {
+ let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<usize>().ok());
+ let mut host = AnalysisHost::new(lru_cap);
+ let mut analysis_change = Change::new();
+
+ host.raw_database_mut().enable_proc_attr_macros();
+
+ // wait until Vfs has loaded all roots
+ for task in receiver {
+ match task {
+ vfs::loader::Message::Progress { n_done, n_total, config_version: _ } => {
+ if n_done == n_total {
+ break;
+ }
+ }
+ vfs::loader::Message::Loaded { files } => {
+ for (path, contents) in files {
+ vfs.set_file_contents(path.into(), contents);
+ }
+ }
+ }
+ }
+ let changes = vfs.take_changes();
+ for file in changes {
+ if file.exists() {
+ let contents = vfs.file_contents(file.file_id);
+ if let Ok(text) = std::str::from_utf8(contents) {
+ analysis_change.change_file(file.file_id, Some(text.into()))
+ }
+ }
+ }
+ let source_roots = source_root_config.partition(vfs);
+ analysis_change.set_roots(source_roots);
+
+ analysis_change.set_crate_graph(crate_graph);
+ analysis_change.set_proc_macros(proc_macros);
+
+ host.apply_change(analysis_change);
+ host
+}
+
+fn expander_to_proc_macro(
+ expander: proc_macro_api::ProcMacro,
+ dummy_replace: &[Box<str>],
+) -> ProcMacro {
+ let name = From::from(expander.name());
+ let kind = match expander.kind() {
+ proc_macro_api::ProcMacroKind::CustomDerive => ProcMacroKind::CustomDerive,
+ proc_macro_api::ProcMacroKind::FuncLike => ProcMacroKind::FuncLike,
+ proc_macro_api::ProcMacroKind::Attr => ProcMacroKind::Attr,
+ };
+ let expander: sync::Arc<dyn ProcMacroExpander> =
+ if dummy_replace.iter().any(|replace| &**replace == name) {
+ match kind {
+ ProcMacroKind::Attr => sync::Arc::new(IdentityExpander),
+ _ => sync::Arc::new(EmptyExpander),
+ }
+ } else {
+ sync::Arc::new(Expander(expander))
+ };
+ ProcMacro { name, kind, expander }
+}
+
+#[derive(Debug)]
+struct Expander(proc_macro_api::ProcMacro);
+
+impl ProcMacroExpander for Expander {
+ fn expand(
+ &self,
+ subtree: &tt::Subtree,
+ attrs: Option<&tt::Subtree>,
+ env: &Env,
+ ) -> Result<tt::Subtree, ProcMacroExpansionError> {
+ let env = env.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect();
+ match self.0.expand(subtree, attrs, env) {
+ Ok(Ok(subtree)) => Ok(subtree),
+ Ok(Err(err)) => Err(ProcMacroExpansionError::Panic(err.0)),
+ Err(err) => Err(ProcMacroExpansionError::System(err.to_string())),
+ }
+ }
+}
+
+/// Dummy identity expander, used for attribute proc-macros that are deliberately ignored by the user.
+#[derive(Debug)]
+struct IdentityExpander;
+
+impl ProcMacroExpander for IdentityExpander {
+ fn expand(
+ &self,
+ subtree: &tt::Subtree,
+ _: Option<&tt::Subtree>,
+ _: &Env,
+ ) -> Result<tt::Subtree, ProcMacroExpansionError> {
+ Ok(subtree.clone())
+ }
+}
+
+/// Empty expander, used for proc-macros that are deliberately ignored by the user.
+#[derive(Debug)]
+struct EmptyExpander;
+
+impl ProcMacroExpander for EmptyExpander {
+ fn expand(
+ &self,
+ _: &tt::Subtree,
+ _: Option<&tt::Subtree>,
+ _: &Env,
+ ) -> Result<tt::Subtree, ProcMacroExpansionError> {
+ Ok(tt::Subtree::empty())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use ide_db::base_db::SourceDatabase;
+
+ use super::*;
+
+ #[test]
+ fn test_loading_rust_analyzer() {
+ let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap();
+ let cargo_config = CargoConfig::default();
+ let load_cargo_config = LoadCargoConfig {
+ load_out_dirs_from_check: false,
+ with_proc_macro_server: ProcMacroServerChoice::None,
+ prefill_caches: false,
+ };
+ let (host, _vfs, _proc_macro) =
+ load_workspace_at(path, &cargo_config, &load_cargo_config, &|_| {}).unwrap();
+
+ let n_crates = host.raw_database().crate_graph().iter().count();
+ // RA has quite a few crates, but the exact count doesn't matter
+ assert!(n_crates > 20);
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs b/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs
index d28dd17de..9d43e1304 100644
--- a/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs
+++ b/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs
@@ -20,10 +20,7 @@ fn benchmark_parse_macro_rules() {
let rules = macro_rules_fixtures_tt();
let hash: usize = {
let _pt = bench("mbe parse macro rules");
- rules
- .values()
- .map(|it| DeclarativeMacro::parse_macro_rules(it, true).unwrap().rules.len())
- .sum()
+ rules.values().map(|it| DeclarativeMacro::parse_macro_rules(it, true).rules.len()).sum()
};
assert_eq!(hash, 1144);
}
@@ -41,7 +38,7 @@ fn benchmark_expand_macro_rules() {
invocations
.into_iter()
.map(|(id, tt)| {
- let res = rules[&id].expand(&tt);
+ let res = rules[&id].expand(tt);
assert!(res.err.is_none());
res.value.token_trees.len()
})
@@ -53,7 +50,7 @@ fn benchmark_expand_macro_rules() {
fn macro_rules_fixtures() -> FxHashMap<String, DeclarativeMacro> {
macro_rules_fixtures_tt()
.into_iter()
- .map(|(id, tt)| (id, DeclarativeMacro::parse_macro_rules(&tt, true).unwrap()))
+ .map(|(id, tt)| (id, DeclarativeMacro::parse_macro_rules(&tt, true)))
.collect()
}
@@ -105,7 +102,7 @@ fn invocation_fixtures(rules: &FxHashMap<String, DeclarativeMacro>) -> Vec<(Stri
for op in rule.lhs.iter() {
collect_from_op(op, &mut subtree, &mut seed);
}
- if it.expand(&subtree).err.is_none() {
+ if it.expand(subtree.clone()).err.is_none() {
res.push((name.clone(), subtree));
break;
}
diff --git a/src/tools/rust-analyzer/crates/mbe/src/expander.rs b/src/tools/rust-analyzer/crates/mbe/src/expander.rs
index 8e2181e97..f2d89d3ef 100644
--- a/src/tools/rust-analyzer/crates/mbe/src/expander.rs
+++ b/src/tools/rust-analyzer/crates/mbe/src/expander.rs
@@ -123,4 +123,14 @@ enum Fragment {
/// proc-macro delimiter=none. As we later discovered, "none" delimiters are
/// tricky to handle in the parser, and rustc doesn't handle those either.
Expr(tt::TokenTree),
+ /// There are roughly two types of paths: paths in expression context, where a
+ /// separator `::` between an identifier and its following generic argument list
+ /// is mandatory, and paths in type context, where `::` can be omitted.
+ ///
+ /// Unlike rustc, we need to transform the parsed fragments back into tokens
+ /// during transcription. When the matched path fragment is a type-context path
+ /// and is trasncribed as an expression-context path, verbatim transcription
+ /// would cause a syntax error. We need to fix it up just before transcribing;
+ /// see `transcriber::fix_up_and_push_path_tt()`.
+ Path(tt::TokenTree),
}
diff --git a/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs b/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs
index 474826079..1471af98b 100644
--- a/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs
+++ b/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs
@@ -742,7 +742,11 @@ fn match_meta_var(
is_2021: bool,
) -> ExpandResult<Option<Fragment>> {
let fragment = match kind {
- MetaVarKind::Path => parser::PrefixEntryPoint::Path,
+ MetaVarKind::Path => {
+ return input
+ .expect_fragment(parser::PrefixEntryPoint::Path)
+ .map(|it| it.map(Fragment::Path));
+ }
MetaVarKind::Ty => parser::PrefixEntryPoint::Ty,
MetaVarKind::Pat if is_2021 => parser::PrefixEntryPoint::PatTop,
MetaVarKind::Pat => parser::PrefixEntryPoint::Pat,
@@ -771,7 +775,7 @@ fn match_meta_var(
.expect_fragment(parser::PrefixEntryPoint::Expr)
.map(|tt| tt.map(Fragment::Expr));
}
- _ => {
+ MetaVarKind::Ident | MetaVarKind::Tt | MetaVarKind::Lifetime | MetaVarKind::Literal => {
let tt_result = match kind {
MetaVarKind::Ident => input
.expect_ident()
@@ -799,7 +803,7 @@ fn match_meta_var(
})
.map_err(|()| ExpandError::binding_error("expected literal"))
}
- _ => Err(ExpandError::UnexpectedToken),
+ _ => unreachable!(),
};
return tt_result.map(|it| Some(Fragment::Tokens(it))).into();
}
@@ -884,7 +888,7 @@ impl<'a> Iterator for OpDelimitedIter<'a> {
}
}
-impl<'a> TtIter<'a> {
+impl TtIter<'_> {
fn expect_separator(&mut self, separator: &Separator) -> bool {
let mut fork = self.clone();
let ok = match separator {
diff --git a/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs b/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs
index 6161af185..cdac2f1e3 100644
--- a/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs
+++ b/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs
@@ -400,7 +400,8 @@ fn push_fragment(buf: &mut Vec<tt::TokenTree>, fragment: Fragment) {
}
buf.push(tt.into())
}
- Fragment::Tokens(tt) | Fragment::Expr(tt) => buf.push(tt),
+ Fragment::Path(tt::TokenTree::Subtree(tt)) => fix_up_and_push_path_tt(buf, tt),
+ Fragment::Tokens(tt) | Fragment::Expr(tt) | Fragment::Path(tt) => buf.push(tt),
}
}
@@ -411,6 +412,45 @@ fn push_subtree(buf: &mut Vec<tt::TokenTree>, tt: tt::Subtree) {
}
}
+/// Inserts the path separator `::` between an identifier and its following generic
+/// argument list, and then pushes into the buffer. See [`Fragment::Path`] for why
+/// we need this fixup.
+fn fix_up_and_push_path_tt(buf: &mut Vec<tt::TokenTree>, subtree: tt::Subtree) {
+ stdx::always!(matches!(subtree.delimiter.kind, tt::DelimiterKind::Invisible));
+ let mut prev_was_ident = false;
+ // Note that we only need to fix up the top-level `TokenTree`s because the
+ // context of the paths in the descendant `Subtree`s won't be changed by the
+ // mbe transcription.
+ for tt in subtree.token_trees {
+ if prev_was_ident {
+ // Pedantically, `(T) -> U` in `FnOnce(T) -> U` is treated as a generic
+ // argument list and thus needs `::` between it and `FnOnce`. However in
+ // today's Rust this type of path *semantically* cannot appear as a
+ // top-level expression-context path, so we can safely ignore it.
+ if let tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char: '<', .. })) = tt {
+ buf.push(
+ tt::Leaf::Punct(tt::Punct {
+ char: ':',
+ spacing: tt::Spacing::Joint,
+ span: tt::Span::unspecified(),
+ })
+ .into(),
+ );
+ buf.push(
+ tt::Leaf::Punct(tt::Punct {
+ char: ':',
+ spacing: tt::Spacing::Alone,
+ span: tt::Span::unspecified(),
+ })
+ .into(),
+ );
+ }
+ }
+ prev_was_ident = matches!(tt, tt::TokenTree::Leaf(tt::Leaf::Ident(_)));
+ buf.push(tt);
+ }
+}
+
/// Handles `${count(t, depth)}`. `our_depth` is the recursion depth and `count_depth` is the depth
/// defined by the metavar expression.
fn count(
diff --git a/src/tools/rust-analyzer/crates/mbe/src/lib.rs b/src/tools/rust-analyzer/crates/mbe/src/lib.rs
index 5ef20ff8a..9d886a1c9 100644
--- a/src/tools/rust-analyzer/crates/mbe/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/mbe/src/lib.rs
@@ -28,13 +28,13 @@ use crate::{
tt_iter::TtIter,
};
-// FIXME: we probably should re-think `token_tree_to_syntax_node` interfaces
pub use self::tt::{Delimiter, DelimiterKind, Punct};
pub use ::parser::TopEntryPoint;
pub use crate::{
syntax_bridge::{
- parse_exprs_with_sep, parse_to_token_tree, syntax_node_to_token_tree,
+ parse_exprs_with_sep, parse_to_token_tree, syntax_node_to_token_map,
+ syntax_node_to_token_map_with_modifications, syntax_node_to_token_tree,
syntax_node_to_token_tree_with_modifications, token_tree_to_syntax_node, SyntheticToken,
SyntheticTokenId,
},
@@ -131,6 +131,7 @@ pub struct DeclarativeMacro {
// This is used for correctly determining the behavior of the pat fragment
// FIXME: This should be tracked by hygiene of the fragment identifier!
is_2021: bool,
+ err: Option<Box<ParseError>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -206,81 +207,118 @@ impl Shift {
}
}
-#[derive(Debug, Eq, PartialEq)]
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Origin {
Def,
Call,
}
impl DeclarativeMacro {
+ pub fn from_err(err: ParseError, is_2021: bool) -> DeclarativeMacro {
+ DeclarativeMacro {
+ rules: Box::default(),
+ shift: Shift(0),
+ is_2021,
+ err: Some(Box::new(err)),
+ }
+ }
+
/// The old, `macro_rules! m {}` flavor.
- pub fn parse_macro_rules(
- tt: &tt::Subtree,
- is_2021: bool,
- ) -> Result<DeclarativeMacro, ParseError> {
+ pub fn parse_macro_rules(tt: &tt::Subtree, is_2021: bool) -> DeclarativeMacro {
// Note: this parsing can be implemented using mbe machinery itself, by
// matching against `$($lhs:tt => $rhs:tt);*` pattern, but implementing
// manually seems easier.
let mut src = TtIter::new(tt);
let mut rules = Vec::new();
+ let mut err = None;
+
while src.len() > 0 {
- let rule = Rule::parse(&mut src, true)?;
+ let rule = match Rule::parse(&mut src, true) {
+ Ok(it) => it,
+ Err(e) => {
+ err = Some(Box::new(e));
+ break;
+ }
+ };
rules.push(rule);
if let Err(()) = src.expect_char(';') {
if src.len() > 0 {
- return Err(ParseError::expected("expected `;`"));
+ err = Some(Box::new(ParseError::expected("expected `;`")));
}
break;
}
}
for Rule { lhs, .. } in &rules {
- validate(lhs)?;
+ if let Err(e) = validate(lhs) {
+ err = Some(Box::new(e));
+ break;
+ }
}
- Ok(DeclarativeMacro { rules: rules.into_boxed_slice(), shift: Shift::new(tt), is_2021 })
+ DeclarativeMacro { rules: rules.into_boxed_slice(), shift: Shift::new(tt), is_2021, err }
}
/// The new, unstable `macro m {}` flavor.
- pub fn parse_macro2(tt: &tt::Subtree, is_2021: bool) -> Result<DeclarativeMacro, ParseError> {
+ pub fn parse_macro2(tt: &tt::Subtree, is_2021: bool) -> DeclarativeMacro {
let mut src = TtIter::new(tt);
let mut rules = Vec::new();
+ let mut err = None;
if tt::DelimiterKind::Brace == tt.delimiter.kind {
cov_mark::hit!(parse_macro_def_rules);
while src.len() > 0 {
- let rule = Rule::parse(&mut src, true)?;
+ let rule = match Rule::parse(&mut src, true) {
+ Ok(it) => it,
+ Err(e) => {
+ err = Some(Box::new(e));
+ break;
+ }
+ };
rules.push(rule);
if let Err(()) = src.expect_any_char(&[';', ',']) {
if src.len() > 0 {
- return Err(ParseError::expected("expected `;` or `,` to delimit rules"));
+ err = Some(Box::new(ParseError::expected(
+ "expected `;` or `,` to delimit rules",
+ )));
}
break;
}
}
} else {
cov_mark::hit!(parse_macro_def_simple);
- let rule = Rule::parse(&mut src, false)?;
- if src.len() != 0 {
- return Err(ParseError::expected("remaining tokens in macro def"));
+ match Rule::parse(&mut src, false) {
+ Ok(rule) => {
+ if src.len() != 0 {
+ err = Some(Box::new(ParseError::expected("remaining tokens in macro def")));
+ }
+ rules.push(rule);
+ }
+ Err(e) => {
+ err = Some(Box::new(e));
+ }
}
- rules.push(rule);
}
for Rule { lhs, .. } in &rules {
- validate(lhs)?;
+ if let Err(e) = validate(lhs) {
+ err = Some(Box::new(e));
+ break;
+ }
}
- Ok(DeclarativeMacro { rules: rules.into_boxed_slice(), shift: Shift::new(tt), is_2021 })
+ DeclarativeMacro { rules: rules.into_boxed_slice(), shift: Shift::new(tt), is_2021, err }
}
- pub fn expand(&self, tt: &tt::Subtree) -> ExpandResult<tt::Subtree> {
- // apply shift
- let mut tt = tt.clone();
+ pub fn expand(&self, mut tt: tt::Subtree) -> ExpandResult<tt::Subtree> {
self.shift.shift_all(&mut tt);
expander::expand_rules(&self.rules, &tt, self.is_2021)
}
+ pub fn err(&self) -> Option<&ParseError> {
+ self.err.as_deref()
+ }
+
pub fn map_id_down(&self, id: tt::TokenId) -> tt::TokenId {
self.shift.shift(id)
}
diff --git a/src/tools/rust-analyzer/crates/mbe/src/syntax_bridge.rs b/src/tools/rust-analyzer/crates/mbe/src/syntax_bridge.rs
index 8cbf0f8fc..62b2accf5 100644
--- a/src/tools/rust-analyzer/crates/mbe/src/syntax_bridge.rs
+++ b/src/tools/rust-analyzer/crates/mbe/src/syntax_bridge.rs
@@ -53,6 +53,37 @@ pub fn syntax_node_to_token_tree_with_modifications(
(subtree, c.id_alloc.map, c.id_alloc.next_id)
}
+/// Convert the syntax node to a `TokenTree` (what macro
+/// will consume).
+pub fn syntax_node_to_token_map(node: &SyntaxNode) -> TokenMap {
+ syntax_node_to_token_map_with_modifications(
+ node,
+ Default::default(),
+ 0,
+ Default::default(),
+ Default::default(),
+ )
+ .0
+}
+
+/// Convert the syntax node to a `TokenTree` (what macro will consume)
+/// with the censored range excluded.
+pub fn syntax_node_to_token_map_with_modifications(
+ node: &SyntaxNode,
+ existing_token_map: TokenMap,
+ next_id: u32,
+ replace: FxHashMap<SyntaxElement, Vec<SyntheticToken>>,
+ append: FxHashMap<SyntaxElement, Vec<SyntheticToken>>,
+) -> (TokenMap, u32) {
+ let global_offset = node.text_range().start();
+ let mut c = Converter::new(node, global_offset, existing_token_map, next_id, replace, append);
+ collect_tokens(&mut c);
+ c.id_alloc.map.shrink_to_fit();
+ always!(c.replace.is_empty(), "replace: {:?}", c.replace);
+ always!(c.append.is_empty(), "append: {:?}", c.append);
+ (c.id_alloc.map, c.id_alloc.next_id)
+}
+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct SyntheticTokenId(pub u32);
@@ -327,6 +358,111 @@ fn convert_tokens<C: TokenConverter>(conv: &mut C) -> tt::Subtree {
}
}
+fn collect_tokens<C: TokenConverter>(conv: &mut C) {
+ struct StackEntry {
+ idx: usize,
+ open_range: TextRange,
+ delimiter: tt::DelimiterKind,
+ }
+
+ let entry = StackEntry {
+ delimiter: tt::DelimiterKind::Invisible,
+ // never used (delimiter is `None`)
+ idx: !0,
+ open_range: TextRange::empty(TextSize::of('.')),
+ };
+ let mut stack = NonEmptyVec::new(entry);
+
+ loop {
+ let StackEntry { delimiter, .. } = stack.last_mut();
+ let (token, range) = match conv.bump() {
+ Some(it) => it,
+ None => break,
+ };
+ let synth_id = token.synthetic_id(conv);
+
+ let kind = token.kind(conv);
+ if kind == COMMENT {
+ // Since `convert_doc_comment` can fail, we need to peek the next id, so that we can
+ // figure out which token id to use for the doc comment, if it is converted successfully.
+ let next_id = conv.id_alloc().peek_next_id();
+ if let Some(_tokens) = conv.convert_doc_comment(&token, next_id) {
+ let id = conv.id_alloc().alloc(range, synth_id);
+ debug_assert_eq!(id, next_id);
+ }
+ continue;
+ }
+ if kind.is_punct() && kind != UNDERSCORE {
+ if synth_id.is_none() {
+ assert_eq!(range.len(), TextSize::of('.'));
+ }
+
+ let expected = match delimiter {
+ tt::DelimiterKind::Parenthesis => Some(T![')']),
+ tt::DelimiterKind::Brace => Some(T!['}']),
+ tt::DelimiterKind::Bracket => Some(T![']']),
+ tt::DelimiterKind::Invisible => None,
+ };
+
+ if let Some(expected) = expected {
+ if kind == expected {
+ if let Some(entry) = stack.pop() {
+ conv.id_alloc().close_delim(entry.idx, Some(range));
+ }
+ continue;
+ }
+ }
+
+ let delim = match kind {
+ T!['('] => Some(tt::DelimiterKind::Parenthesis),
+ T!['{'] => Some(tt::DelimiterKind::Brace),
+ T!['['] => Some(tt::DelimiterKind::Bracket),
+ _ => None,
+ };
+
+ if let Some(kind) = delim {
+ let (_id, idx) = conv.id_alloc().open_delim(range, synth_id);
+
+ stack.push(StackEntry { idx, open_range: range, delimiter: kind });
+ continue;
+ }
+
+ conv.id_alloc().alloc(range, synth_id);
+ } else {
+ macro_rules! make_leaf {
+ ($i:ident) => {{
+ conv.id_alloc().alloc(range, synth_id);
+ }};
+ }
+ match kind {
+ T![true] | T![false] => make_leaf!(Ident),
+ IDENT => make_leaf!(Ident),
+ UNDERSCORE => make_leaf!(Ident),
+ k if k.is_keyword() => make_leaf!(Ident),
+ k if k.is_literal() => make_leaf!(Literal),
+ LIFETIME_IDENT => {
+ let char_unit = TextSize::of('\'');
+ let r = TextRange::at(range.start(), char_unit);
+ conv.id_alloc().alloc(r, synth_id);
+
+ let r = TextRange::at(range.start() + char_unit, range.len() - char_unit);
+ conv.id_alloc().alloc(r, synth_id);
+ continue;
+ }
+ _ => continue,
+ };
+ };
+
+ // If we get here, we've consumed all input tokens.
+ // We might have more than one subtree in the stack, if the delimiters are improperly balanced.
+ // Merge them so we're left with one.
+ while let Some(entry) = stack.pop() {
+ conv.id_alloc().close_delim(entry.idx, None);
+ conv.id_alloc().alloc(entry.open_range, None);
+ }
+ }
+}
+
fn is_single_token_op(kind: SyntaxKind) -> bool {
matches!(
kind,
@@ -509,12 +645,12 @@ trait TokenConverter: Sized {
fn id_alloc(&mut self) -> &mut TokenIdAlloc;
}
-impl<'a> SrcToken<RawConverter<'a>> for usize {
- fn kind(&self, ctx: &RawConverter<'a>) -> SyntaxKind {
+impl SrcToken<RawConverter<'_>> for usize {
+ fn kind(&self, ctx: &RawConverter<'_>) -> SyntaxKind {
ctx.lexed.kind(*self)
}
- fn to_char(&self, ctx: &RawConverter<'a>) -> Option<char> {
+ fn to_char(&self, ctx: &RawConverter<'_>) -> Option<char> {
ctx.lexed.text(*self).chars().next()
}
@@ -522,12 +658,12 @@ impl<'a> SrcToken<RawConverter<'a>> for usize {
ctx.lexed.text(*self).into()
}
- fn synthetic_id(&self, _ctx: &RawConverter<'a>) -> Option<SyntheticTokenId> {
+ fn synthetic_id(&self, _ctx: &RawConverter<'_>) -> Option<SyntheticTokenId> {
None
}
}
-impl<'a> TokenConverter for RawConverter<'a> {
+impl TokenConverter for RawConverter<'_> {
type Token = usize;
fn convert_doc_comment(&self, &token: &usize, span: tt::TokenId) -> Option<Vec<tt::TokenTree>> {
@@ -800,7 +936,7 @@ fn delim_to_str(d: tt::DelimiterKind, closing: bool) -> Option<&'static str> {
Some(&texts[idx..texts.len() - (1 - idx)])
}
-impl<'a> TtTreeSink<'a> {
+impl TtTreeSink<'_> {
/// Parses a float literal as if it was a one to two name ref nodes with a dot inbetween.
/// This occurs when a float literal is used as a field access.
fn float_split(&mut self, has_pseudo_dot: bool) {
diff --git a/src/tools/rust-analyzer/crates/mbe/src/token_map.rs b/src/tools/rust-analyzer/crates/mbe/src/token_map.rs
index c923e7a69..73a27df5d 100644
--- a/src/tools/rust-analyzer/crates/mbe/src/token_map.rs
+++ b/src/tools/rust-analyzer/crates/mbe/src/token_map.rs
@@ -110,4 +110,15 @@ impl TokenMap {
// FIXME: This could be accidentally quadratic
self.entries.remove(idx);
}
+
+ pub fn entries(&self) -> impl Iterator<Item = (tt::TokenId, TextRange)> + '_ {
+ self.entries.iter().filter_map(|&(tid, tr)| match tr {
+ TokenTextRange::Token(range) => Some((tid, range)),
+ TokenTextRange::Delimiter(_) => None,
+ })
+ }
+
+ pub fn filter(&mut self, id: impl Fn(tt::TokenId) -> bool) {
+ self.entries.retain(|&(tid, _)| id(tid));
+ }
}
diff --git a/src/tools/rust-analyzer/crates/mbe/src/tt_iter.rs b/src/tools/rust-analyzer/crates/mbe/src/tt_iter.rs
index 59dbf1568..79ff8ca28 100644
--- a/src/tools/rust-analyzer/crates/mbe/src/tt_iter.rs
+++ b/src/tools/rust-analyzer/crates/mbe/src/tt_iter.rs
@@ -197,4 +197,4 @@ impl<'a> Iterator for TtIter<'a> {
}
}
-impl<'a> std::iter::ExactSizeIterator for TtIter<'a> {}
+impl std::iter::ExactSizeIterator for TtIter<'_> {}
diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar.rs b/src/tools/rust-analyzer/crates/parser/src/grammar.rs
index 1814e0e54..333318f53 100644
--- a/src/tools/rust-analyzer/crates/parser/src/grammar.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/grammar.rs
@@ -165,6 +165,40 @@ pub(crate) mod entry {
}
m.complete(p, ERROR);
}
+
+ pub(crate) fn eager_macro_input(p: &mut Parser<'_>) {
+ let m = p.start();
+
+ let closing_paren_kind = match p.current() {
+ T!['{'] => T!['}'],
+ T!['('] => T![')'],
+ T!['['] => T![']'],
+ _ => {
+ p.error("expected `{`, `[`, `(`");
+ while !p.at(EOF) {
+ p.bump_any();
+ }
+ m.complete(p, ERROR);
+ return;
+ }
+ };
+ p.bump_any();
+ while !p.at(EOF) && !p.at(closing_paren_kind) {
+ expressions::expr(p);
+ if !p.at(EOF) && !p.at(closing_paren_kind) {
+ p.expect(T![,]);
+ }
+ }
+ p.expect(closing_paren_kind);
+ if p.at(EOF) {
+ m.complete(p, MACRO_EAGER_INPUT);
+ return;
+ }
+ while !p.at(EOF) {
+ p.bump_any();
+ }
+ m.complete(p, ERROR);
+ }
}
}
@@ -211,70 +245,54 @@ impl BlockLike {
const VISIBILITY_FIRST: TokenSet = TokenSet::new(&[T![pub], T![crate]]);
fn opt_visibility(p: &mut Parser<'_>, in_tuple_field: bool) -> bool {
- match p.current() {
- T![pub] => {
- let m = p.start();
- p.bump(T![pub]);
- if p.at(T!['(']) {
- match p.nth(1) {
- // test crate_visibility
- // pub(crate) struct S;
- // pub(self) struct S;
- // pub(super) struct S;
-
- // test_err crate_visibility_empty_recover
- // pub() struct S;
-
- // test pub_parens_typepath
- // struct B(pub (super::A));
- // struct B(pub (crate::A,));
- T![crate] | T![self] | T![super] | T![ident] | T![')'] if p.nth(2) != T![:] => {
- // If we are in a tuple struct, then the parens following `pub`
- // might be an tuple field, not part of the visibility. So in that
- // case we don't want to consume an identifier.
-
- // test pub_tuple_field
- // struct MyStruct(pub (u32, u32));
- // struct MyStruct(pub (u32));
- // struct MyStruct(pub ());
- if !(in_tuple_field && matches!(p.nth(1), T![ident] | T![')'])) {
- p.bump(T!['(']);
- paths::use_path(p);
- p.expect(T![')']);
- }
- }
- // test crate_visibility_in
- // pub(in super::A) struct S;
- // pub(in crate) struct S;
- T![in] => {
- p.bump(T!['(']);
- p.bump(T![in]);
- paths::use_path(p);
- p.expect(T![')']);
- }
- _ => {}
+ if !p.at(T![pub]) {
+ return false;
+ }
+
+ let m = p.start();
+ p.bump(T![pub]);
+ if p.at(T!['(']) {
+ match p.nth(1) {
+ // test crate_visibility
+ // pub(crate) struct S;
+ // pub(self) struct S;
+ // pub(super) struct S;
+
+ // test_err crate_visibility_empty_recover
+ // pub() struct S;
+
+ // test pub_parens_typepath
+ // struct B(pub (super::A));
+ // struct B(pub (crate::A,));
+ T![crate] | T![self] | T![super] | T![ident] | T![')'] if p.nth(2) != T![:] => {
+ // If we are in a tuple struct, then the parens following `pub`
+ // might be an tuple field, not part of the visibility. So in that
+ // case we don't want to consume an identifier.
+
+ // test pub_tuple_field
+ // struct MyStruct(pub (u32, u32));
+ // struct MyStruct(pub (u32));
+ // struct MyStruct(pub ());
+ if !(in_tuple_field && matches!(p.nth(1), T![ident] | T![')'])) {
+ p.bump(T!['(']);
+ paths::use_path(p);
+ p.expect(T![')']);
}
}
- m.complete(p, VISIBILITY);
- true
- }
- // test crate_keyword_vis
- // crate fn main() { }
- // struct S { crate field: u32 }
- // struct T(crate u32);
- T![crate] => {
- if p.nth_at(1, T![::]) {
- // test crate_keyword_path
- // fn foo() { crate::foo(); }
- return false;
+ // test crate_visibility_in
+ // pub(in super::A) struct S;
+ // pub(in crate) struct S;
+ T![in] => {
+ p.bump(T!['(']);
+ p.bump(T![in]);
+ paths::use_path(p);
+ p.expect(T![')']);
}
- let m = p.start();
- p.bump(T![crate]);
- m.complete(p, VISIBILITY);
- true
+ _ => {}
}
- _ => false,
}
+ m.complete(p, VISIBILITY);
+ true
}
fn opt_rename(p: &mut Parser<'_>) {
diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/generic_args.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/generic_args.rs
index e589b6993..211af98e6 100644
--- a/src/tools/rust-analyzer/crates/parser/src/grammar/generic_args.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/grammar/generic_args.rs
@@ -32,6 +32,9 @@ const GENERIC_ARG_FIRST: TokenSet = TokenSet::new(&[
])
.union(types::TYPE_FIRST);
+// Despite its name, it can also be used for generic param list.
+const GENERIC_ARG_RECOVERY_SET: TokenSet = TokenSet::new(&[T![>], T![,]]);
+
// test generic_arg
// type T = S<i32>;
fn generic_arg(p: &mut Parser<'_>) -> bool {
@@ -55,6 +58,15 @@ fn generic_arg(p: &mut Parser<'_>) -> bool {
// test assoc_type_eq
// type T = StreamingIterator<Item<'a> = &'a T>;
types::type_(p);
+ } else if p.at_ts(GENERIC_ARG_RECOVERY_SET) {
+ // Although `const_arg()` recovers as expected, we want to
+ // handle those here to give the following message because
+ // we don't know whether this associated item is a type or
+ // const at this point.
+
+ // test_err recover_from_missing_assoc_item_binding
+ // fn f() -> impl Iterator<Item = , Item = > {}
+ p.error("missing associated item binding");
} else {
// test assoc_const_eq
// fn foo<F: Foo<N=3>>() {}
@@ -141,12 +153,17 @@ pub(super) fn const_arg_expr(p: &mut Parser<'_>) {
expressions::literal(p);
lm.complete(p, PREFIX_EXPR);
}
- _ => {
+ _ if paths::is_use_path_start(p) => {
// This shouldn't be hit by `const_arg`
let lm = p.start();
paths::use_path(p);
lm.complete(p, PATH_EXPR);
}
+ _ => {
+ // test_err recover_from_missing_const_default
+ // struct A<const N: i32 = , const M: i32 =>;
+ p.err_recover("expected a generic const argument", GENERIC_ARG_RECOVERY_SET);
+ }
}
}
diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs
index 7fcf938ba..8ed1c84c4 100644
--- a/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs
@@ -79,10 +79,9 @@ fn const_param(p: &mut Parser<'_>, m: Marker) {
p.error("missing type for const parameter");
}
- if p.at(T![=]) {
+ if p.eat(T![=]) {
// test const_param_default_literal
// struct A<const N: i32 = -1>;
- p.bump(T![=]);
// test const_param_default_expression
// struct A<const N: i32 = { 1 }>;
diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/items.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/items.rs
index 1c056819f..4e850b1f7 100644
--- a/src/tools/rust-analyzer/crates/parser/src/grammar/items.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/grammar/items.rs
@@ -328,9 +328,6 @@ fn macro_rules(p: &mut Parser<'_>, m: Marker) {
p.bump_remap(T![macro_rules]);
p.expect(T![!]);
- if p.at(IDENT) {
- name(p);
- }
// Special-case `macro_rules! try`.
// This is a hack until we do proper edition support
@@ -340,6 +337,8 @@ fn macro_rules(p: &mut Parser<'_>, m: Marker) {
let m = p.start();
p.bump_remap(IDENT);
m.complete(p, NAME);
+ } else {
+ name(p);
}
match p.current() {
diff --git a/src/tools/rust-analyzer/crates/parser/src/lib.rs b/src/tools/rust-analyzer/crates/parser/src/lib.rs
index 1aba1f767..c155e8aaf 100644
--- a/src/tools/rust-analyzer/crates/parser/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/lib.rs
@@ -75,6 +75,8 @@ pub enum TopEntryPoint {
/// Edge case -- macros generally don't expand to attributes, with the
/// exception of `cfg_attr` which does!
MetaItem,
+ /// Edge case 2 -- eager macros expand their input to a delimited list of comma separated expressions
+ MacroEagerInput,
}
impl TopEntryPoint {
@@ -87,6 +89,7 @@ impl TopEntryPoint {
TopEntryPoint::Type => grammar::entry::top::type_,
TopEntryPoint::Expr => grammar::entry::top::expr,
TopEntryPoint::MetaItem => grammar::entry::top::meta_item,
+ TopEntryPoint::MacroEagerInput => grammar::entry::top::eager_macro_input,
};
let mut p = parser::Parser::new(input);
entry_point(&mut p);
diff --git a/src/tools/rust-analyzer/crates/parser/src/shortcuts.rs b/src/tools/rust-analyzer/crates/parser/src/shortcuts.rs
index 5cdb39700..53cdad649 100644
--- a/src/tools/rust-analyzer/crates/parser/src/shortcuts.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/shortcuts.rs
@@ -24,7 +24,7 @@ pub enum StrStep<'a> {
Error { msg: &'a str, pos: usize },
}
-impl<'a> LexedStr<'a> {
+impl LexedStr<'_> {
pub fn to_input(&self) -> crate::Input {
let mut res = crate::Input::default();
let mut was_joint = false;
@@ -223,7 +223,8 @@ fn n_attached_trivias<'a>(
) -> usize {
match kind {
CONST | ENUM | FN | IMPL | MACRO_CALL | MACRO_DEF | MACRO_RULES | MODULE | RECORD_FIELD
- | STATIC | STRUCT | TRAIT | TUPLE_FIELD | TYPE_ALIAS | UNION | USE | VARIANT => {
+ | STATIC | STRUCT | TRAIT | TUPLE_FIELD | TYPE_ALIAS | UNION | USE | VARIANT
+ | EXTERN_CRATE => {
let mut res = 0;
let mut trivias = trivias.enumerate().peekable();
diff --git a/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs b/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs
index a8fbcfacf..48f407623 100644
--- a/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs
@@ -262,6 +262,7 @@ pub enum SyntaxKind {
TYPE_BOUND_LIST,
MACRO_ITEMS,
MACRO_STMTS,
+ MACRO_EAGER_INPUT,
#[doc(hidden)]
__LAST,
}
diff --git a/src/tools/rust-analyzer/crates/parser/src/tests/prefix_entries.rs b/src/tools/rust-analyzer/crates/parser/src/tests/prefix_entries.rs
index 11f9c34ab..2f3c7febc 100644
--- a/src/tools/rust-analyzer/crates/parser/src/tests/prefix_entries.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/tests/prefix_entries.rs
@@ -6,7 +6,6 @@ fn vis() {
check(PrefixEntryPoint::Vis, "fn foo() {}", "");
check(PrefixEntryPoint::Vis, "pub(fn foo() {}", "pub");
check(PrefixEntryPoint::Vis, "pub(crate fn foo() {}", "pub(crate");
- check(PrefixEntryPoint::Vis, "crate fn foo() {}", "crate");
}
#[test]
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0021_recover_from_missing_assoc_item_binding.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0021_recover_from_missing_assoc_item_binding.rast
new file mode 100644
index 000000000..fc59db84e
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0021_recover_from_missing_assoc_item_binding.rast
@@ -0,0 +1,48 @@
+SOURCE_FILE
+ FN
+ FN_KW "fn"
+ WHITESPACE " "
+ NAME
+ IDENT "f"
+ PARAM_LIST
+ L_PAREN "("
+ R_PAREN ")"
+ WHITESPACE " "
+ RET_TYPE
+ THIN_ARROW "->"
+ WHITESPACE " "
+ IMPL_TRAIT_TYPE
+ IMPL_KW "impl"
+ WHITESPACE " "
+ TYPE_BOUND_LIST
+ TYPE_BOUND
+ PATH_TYPE
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "Iterator"
+ GENERIC_ARG_LIST
+ L_ANGLE "<"
+ ASSOC_TYPE_ARG
+ NAME_REF
+ IDENT "Item"
+ WHITESPACE " "
+ EQ "="
+ WHITESPACE " "
+ COMMA ","
+ WHITESPACE " "
+ ASSOC_TYPE_ARG
+ NAME_REF
+ IDENT "Item"
+ WHITESPACE " "
+ EQ "="
+ WHITESPACE " "
+ R_ANGLE ">"
+ WHITESPACE " "
+ BLOCK_EXPR
+ STMT_LIST
+ L_CURLY "{"
+ R_CURLY "}"
+ WHITESPACE "\n"
+error 30: missing associated item binding
+error 39: missing associated item binding
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0021_recover_from_missing_assoc_item_binding.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0021_recover_from_missing_assoc_item_binding.rs
new file mode 100644
index 000000000..e484e433a
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0021_recover_from_missing_assoc_item_binding.rs
@@ -0,0 +1 @@
+fn f() -> impl Iterator<Item = , Item = > {}
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0022_recover_from_missing_const_default.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0022_recover_from_missing_const_default.rast
new file mode 100644
index 000000000..809ad1b8d
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0022_recover_from_missing_const_default.rast
@@ -0,0 +1,44 @@
+SOURCE_FILE
+ STRUCT
+ STRUCT_KW "struct"
+ WHITESPACE " "
+ NAME
+ IDENT "A"
+ GENERIC_PARAM_LIST
+ L_ANGLE "<"
+ CONST_PARAM
+ CONST_KW "const"
+ WHITESPACE " "
+ NAME
+ IDENT "N"
+ COLON ":"
+ WHITESPACE " "
+ PATH_TYPE
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "i32"
+ WHITESPACE " "
+ EQ "="
+ WHITESPACE " "
+ COMMA ","
+ WHITESPACE " "
+ CONST_PARAM
+ CONST_KW "const"
+ WHITESPACE " "
+ NAME
+ IDENT "M"
+ COLON ":"
+ WHITESPACE " "
+ PATH_TYPE
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "i32"
+ WHITESPACE " "
+ EQ "="
+ R_ANGLE ">"
+ SEMICOLON ";"
+ WHITESPACE "\n"
+error 23: expected a generic const argument
+error 40: expected a generic const argument
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0022_recover_from_missing_const_default.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0022_recover_from_missing_const_default.rs
new file mode 100644
index 000000000..5bab13da9
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0022_recover_from_missing_const_default.rs
@@ -0,0 +1 @@
+struct A<const N: i32 = , const M: i32 =>;
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0040_crate_keyword_vis.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0040_crate_keyword_vis.rast
deleted file mode 100644
index 07b0210e4..000000000
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0040_crate_keyword_vis.rast
+++ /dev/null
@@ -1,63 +0,0 @@
-SOURCE_FILE
- FN
- VISIBILITY
- CRATE_KW "crate"
- WHITESPACE " "
- FN_KW "fn"
- WHITESPACE " "
- NAME
- IDENT "main"
- PARAM_LIST
- L_PAREN "("
- R_PAREN ")"
- WHITESPACE " "
- BLOCK_EXPR
- STMT_LIST
- L_CURLY "{"
- WHITESPACE " "
- R_CURLY "}"
- WHITESPACE "\n"
- STRUCT
- STRUCT_KW "struct"
- WHITESPACE " "
- NAME
- IDENT "S"
- WHITESPACE " "
- RECORD_FIELD_LIST
- L_CURLY "{"
- WHITESPACE " "
- RECORD_FIELD
- VISIBILITY
- CRATE_KW "crate"
- WHITESPACE " "
- NAME
- IDENT "field"
- COLON ":"
- WHITESPACE " "
- PATH_TYPE
- PATH
- PATH_SEGMENT
- NAME_REF
- IDENT "u32"
- WHITESPACE " "
- R_CURLY "}"
- WHITESPACE "\n"
- STRUCT
- STRUCT_KW "struct"
- WHITESPACE " "
- NAME
- IDENT "T"
- TUPLE_FIELD_LIST
- L_PAREN "("
- TUPLE_FIELD
- VISIBILITY
- CRATE_KW "crate"
- WHITESPACE " "
- PATH_TYPE
- PATH
- PATH_SEGMENT
- NAME_REF
- IDENT "u32"
- R_PAREN ")"
- SEMICOLON ";"
- WHITESPACE "\n"
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0040_crate_keyword_vis.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0040_crate_keyword_vis.rs
deleted file mode 100644
index e2b5f2161..000000000
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0040_crate_keyword_vis.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-crate fn main() { }
-struct S { crate field: u32 }
-struct T(crate u32);
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0125_crate_keyword_path.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0125_crate_keyword_path.rast
deleted file mode 100644
index 8d9b61630..000000000
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0125_crate_keyword_path.rast
+++ /dev/null
@@ -1,33 +0,0 @@
-SOURCE_FILE
- FN
- FN_KW "fn"
- WHITESPACE " "
- NAME
- IDENT "foo"
- PARAM_LIST
- L_PAREN "("
- R_PAREN ")"
- WHITESPACE " "
- BLOCK_EXPR
- STMT_LIST
- L_CURLY "{"
- WHITESPACE " "
- EXPR_STMT
- CALL_EXPR
- PATH_EXPR
- PATH
- PATH
- PATH_SEGMENT
- NAME_REF
- CRATE_KW "crate"
- COLON2 "::"
- PATH_SEGMENT
- NAME_REF
- IDENT "foo"
- ARG_LIST
- L_PAREN "("
- R_PAREN ")"
- SEMICOLON ";"
- WHITESPACE " "
- R_CURLY "}"
- WHITESPACE "\n"
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0125_crate_keyword_path.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0125_crate_keyword_path.rs
deleted file mode 100644
index 0f454d121..000000000
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0125_crate_keyword_path.rs
+++ /dev/null
@@ -1 +0,0 @@
-fn foo() { crate::foo(); }
diff --git a/src/tools/rust-analyzer/crates/paths/src/lib.rs b/src/tools/rust-analyzer/crates/paths/src/lib.rs
index e0c20a414..88b8d0aee 100644
--- a/src/tools/rust-analyzer/crates/paths/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/paths/src/lib.rs
@@ -6,7 +6,7 @@
use std::{
borrow::Borrow,
ffi::OsStr,
- ops,
+ fmt, ops,
path::{Component, Path, PathBuf},
};
@@ -95,6 +95,12 @@ impl AbsPathBuf {
}
}
+impl fmt::Display for AbsPathBuf {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Display::fmt(&self.0.display(), f)
+ }
+}
+
/// Wrapper around an absolute [`Path`].
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
#[repr(transparent)]
@@ -217,6 +223,7 @@ impl AbsPath {
pub fn as_os_str(&self) -> &OsStr {
self.0.as_os_str()
}
+ #[deprecated(note = "use Display instead")]
pub fn display(&self) -> std::path::Display<'_> {
self.0.display()
}
@@ -227,6 +234,12 @@ impl AbsPath {
// endregion:delegate-methods
}
+impl fmt::Display for AbsPath {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Display::fmt(&self.0.display(), f)
+ }
+}
+
/// Wrapper around a relative [`PathBuf`].
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct RelPathBuf(PathBuf);
diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/Cargo.toml b/src/tools/rust-analyzer/crates/proc-macro-api/Cargo.toml
index d3486e755..4229f2891 100644
--- a/src/tools/rust-analyzer/crates/proc-macro-api/Cargo.toml
+++ b/src/tools/rust-analyzer/crates/proc-macro-api/Cargo.toml
@@ -12,7 +12,7 @@ rust-version.workspace = true
doctest = false
[dependencies]
-object = { version = "0.30.2", default-features = false, features = [
+object = { version = "0.32.0", default-features = false, features = [
"std",
"read_core",
"elf",
diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/version.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/version.rs
index 13f67a012..48efbf589 100644
--- a/src/tools/rust-analyzer/crates/proc-macro-api/src/version.rs
+++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/version.rs
@@ -135,7 +135,12 @@ pub fn read_version(dylib_path: &AbsPath) -> io::Result<String> {
}
};
- let mut snappy_decoder = SnapDecoder::new(snappy_portion);
+ let mut uncompressed: Box<dyn Read> = if &snappy_portion[0..4] == b"rust" {
+ // Not compressed.
+ Box::new(snappy_portion)
+ } else {
+ Box::new(SnapDecoder::new(snappy_portion))
+ };
// the bytes before version string bytes, so this basically is:
// 8 bytes for [b'r',b'u',b's',b't',0,0,0,5]
@@ -144,11 +149,11 @@ pub fn read_version(dylib_path: &AbsPath) -> io::Result<String> {
// so 13 bytes in total, and we should check the 13th byte
// to know the length
let mut bytes_before_version = [0u8; 13];
- snappy_decoder.read_exact(&mut bytes_before_version)?;
+ uncompressed.read_exact(&mut bytes_before_version)?;
let length = bytes_before_version[12];
let mut version_string_utf8 = vec![0u8; length as usize];
- snappy_decoder.read_exact(&mut version_string_utf8)?;
+ uncompressed.read_exact(&mut version_string_utf8)?;
let version_string = String::from_utf8(version_string_utf8);
version_string.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}
diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/Cargo.toml b/src/tools/rust-analyzer/crates/proc-macro-srv/Cargo.toml
index d5eb157bf..99993f16e 100644
--- a/src/tools/rust-analyzer/crates/proc-macro-srv/Cargo.toml
+++ b/src/tools/rust-analyzer/crates/proc-macro-srv/Cargo.toml
@@ -12,14 +12,14 @@ rust-version.workspace = true
doctest = false
[dependencies]
-object = { version = "0.30.2", default-features = false, features = [
+object = { version = "0.32.0", default-features = false, features = [
"std",
"read_core",
"elf",
"macho",
"pe",
] }
-libloading = "0.7.3"
+libloading = "0.8.0"
memmap2 = "0.5.4"
stdx.workspace = true
diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server.rs
index 1980d4c78..fe18451d3 100644
--- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server.rs
+++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server.rs
@@ -17,7 +17,10 @@ use token_stream::TokenStreamBuilder;
mod symbol;
pub use symbol::*;
-use std::ops::{Bound, Range};
+use std::{
+ iter,
+ ops::{Bound, Range},
+};
use crate::tt;
@@ -80,9 +83,7 @@ impl server::TokenStream for RustAnalyzer {
stream.is_empty()
}
fn from_str(&mut self, src: &str) -> Self::TokenStream {
- use std::str::FromStr;
-
- Self::TokenStream::from_str(src).expect("cannot parse string")
+ src.parse().expect("cannot parse string")
}
fn to_string(&mut self, stream: &Self::TokenStream) -> String {
stream.to_string()
@@ -101,7 +102,7 @@ impl server::TokenStream for RustAnalyzer {
},
};
let tree = TokenTree::from(group);
- Self::TokenStream::from_iter(vec![tree])
+ Self::TokenStream::from_iter(iter::once(tree))
}
bridge::TokenTree::Ident(ident) => {
@@ -111,7 +112,7 @@ impl server::TokenStream for RustAnalyzer {
let ident: tt::Ident = tt::Ident { text, span: ident.span };
let leaf = tt::Leaf::from(ident);
let tree = TokenTree::from(leaf);
- Self::TokenStream::from_iter(vec![tree])
+ Self::TokenStream::from_iter(iter::once(tree))
}
bridge::TokenTree::Literal(literal) => {
@@ -123,7 +124,7 @@ impl server::TokenStream for RustAnalyzer {
let literal = tt::Literal { text, span: literal.0.span };
let leaf = tt::Leaf::from(literal);
let tree = TokenTree::from(leaf);
- Self::TokenStream::from_iter(vec![tree])
+ Self::TokenStream::from_iter(iter::once(tree))
}
bridge::TokenTree::Punct(p) => {
@@ -134,7 +135,7 @@ impl server::TokenStream for RustAnalyzer {
};
let leaf = tt::Leaf::from(punct);
let tree = TokenTree::from(leaf);
- Self::TokenStream::from_iter(vec![tree])
+ Self::TokenStream::from_iter(iter::once(tree))
}
}
}
@@ -355,12 +356,12 @@ impl server::Server for RustAnalyzer {
}
fn intern_symbol(ident: &str) -> Self::Symbol {
- // FIXME: should be self.interner once the proc-macro api allows is
+ // FIXME: should be `self.interner` once the proc-macro api allows it.
Symbol::intern(&SYMBOL_INTERNER, &::tt::SmolStr::from(ident))
}
fn with_symbol_string(symbol: &Self::Symbol, f: impl FnOnce(&str)) {
- // FIXME: should be self.interner once the proc-macro api allows is
+ // FIXME: should be `self.interner` once the proc-macro api allows it.
f(symbol.text(&SYMBOL_INTERNER).as_str())
}
}
diff --git a/src/tools/rust-analyzer/crates/profile/Cargo.toml b/src/tools/rust-analyzer/crates/profile/Cargo.toml
index 602e74275..937834a82 100644
--- a/src/tools/rust-analyzer/crates/profile/Cargo.toml
+++ b/src/tools/rust-analyzer/crates/profile/Cargo.toml
@@ -15,7 +15,7 @@ doctest = false
once_cell = "1.17.0"
cfg-if = "1.0.0"
libc = "0.2.135"
-la-arena = { version = "0.3.0", path = "../../lib/la-arena" }
+la-arena.workspace = true
countme = { version = "3.0.1", features = ["enable"] }
jemalloc-ctl = { version = "0.5.0", package = "tikv-jemalloc-ctl", optional = true }
diff --git a/src/tools/rust-analyzer/crates/profile/src/tree.rs b/src/tools/rust-analyzer/crates/profile/src/tree.rs
index 62f0c30b5..1290fba36 100644
--- a/src/tools/rust-analyzer/crates/profile/src/tree.rs
+++ b/src/tools/rust-analyzer/crates/profile/src/tree.rs
@@ -72,7 +72,7 @@ struct NodeIter<'a, T> {
next: Option<Idx<T>>,
}
-impl<'a, T> Iterator for NodeIter<'a, T> {
+impl<T> Iterator for NodeIter<'_, T> {
type Item = Idx<T>;
fn next(&mut self) -> Option<Idx<T>> {
diff --git a/src/tools/rust-analyzer/crates/project-model/Cargo.toml b/src/tools/rust-analyzer/crates/project-model/Cargo.toml
index 3abff64a8..75977fc5b 100644
--- a/src/tools/rust-analyzer/crates/project-model/Cargo.toml
+++ b/src/tools/rust-analyzer/crates/project-model/Cargo.toml
@@ -20,7 +20,7 @@ serde_json.workspace = true
serde.workspace = true
triomphe.workspace = true
anyhow = "1.0.62"
-la-arena = { version = "0.3.0", path = "../../lib/la-arena" }
+la-arena.workspace = true
itertools = "0.10.5"
# local deps
diff --git a/src/tools/rust-analyzer/crates/project-model/src/build_scripts.rs b/src/tools/rust-analyzer/crates/project-model/src/build_scripts.rs
index 6cbf403cb..fb0f3ab7d 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/build_scripts.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/build_scripts.rs
@@ -225,9 +225,8 @@ impl WorkspaceBuildScripts {
let package_build_data = &mut res[idx].outputs[package];
if !package_build_data.is_unchanged() {
tracing::info!(
- "{}: {:?}",
- workspace[package].manifest.parent().display(),
- package_build_data,
+ "{}: {package_build_data:?}",
+ workspace[package].manifest.parent(),
);
}
}
@@ -270,9 +269,8 @@ impl WorkspaceBuildScripts {
let package_build_data = &outputs[package];
if !package_build_data.is_unchanged() {
tracing::info!(
- "{}: {:?}",
- workspace[package].manifest.parent().display(),
- package_build_data,
+ "{}: {package_build_data:?}",
+ workspace[package].manifest.parent(),
);
}
}
@@ -424,7 +422,7 @@ impl WorkspaceBuildScripts {
let target_libdir = AbsPathBuf::try_from(PathBuf::from(target_libdir))
.map_err(|_| anyhow::format_err!("target-libdir was not an absolute path"))?;
- tracing::info!("Loading rustc proc-macro paths from {}", target_libdir.display());
+ tracing::info!("Loading rustc proc-macro paths from {target_libdir}");
let proc_macro_dylibs: Vec<(String, AbsPathBuf)> = std::fs::read_dir(target_libdir)?
.filter_map(|entry| {
@@ -458,9 +456,8 @@ impl WorkspaceBuildScripts {
let package_build_data = &bs.outputs[package];
if !package_build_data.is_unchanged() {
tracing::info!(
- "{}: {:?}",
- rustc[package].manifest.parent().display(),
- package_build_data,
+ "{}: {package_build_data:?}",
+ rustc[package].manifest.parent(),
);
}
}
diff --git a/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs b/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs
index 92b454150..e47808a2c 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs
@@ -4,7 +4,7 @@ use std::path::PathBuf;
use std::str::from_utf8;
use std::{ops, process::Command};
-use anyhow::{Context, Result};
+use anyhow::Context;
use base_db::Edition;
use cargo_metadata::{CargoOpt, MetadataCommand};
use la_arena::{Arena, Idx};
@@ -145,7 +145,7 @@ pub struct PackageDependency {
pub kind: DepKind,
}
-#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DepKind {
/// Available to the library, binary, and dev targets in the package (but not the build script).
Normal,
@@ -156,23 +156,20 @@ pub enum DepKind {
}
impl DepKind {
- fn iter(list: &[cargo_metadata::DepKindInfo]) -> impl Iterator<Item = Self> + '_ {
- let mut dep_kinds = Vec::new();
+ fn iter(list: &[cargo_metadata::DepKindInfo]) -> impl Iterator<Item = Self> {
+ let mut dep_kinds = [None; 3];
if list.is_empty() {
- dep_kinds.push(Self::Normal);
+ dep_kinds[0] = Some(Self::Normal);
}
for info in list {
- let kind = match info.kind {
- cargo_metadata::DependencyKind::Normal => Self::Normal,
- cargo_metadata::DependencyKind::Development => Self::Dev,
- cargo_metadata::DependencyKind::Build => Self::Build,
+ match info.kind {
+ cargo_metadata::DependencyKind::Normal => dep_kinds[0] = Some(Self::Normal),
+ cargo_metadata::DependencyKind::Development => dep_kinds[1] = Some(Self::Dev),
+ cargo_metadata::DependencyKind::Build => dep_kinds[2] = Some(Self::Build),
cargo_metadata::DependencyKind::Unknown => continue,
- };
- dep_kinds.push(kind);
+ }
}
- dep_kinds.sort_unstable();
- dep_kinds.dedup();
- dep_kinds.into_iter()
+ dep_kinds.into_iter().flatten()
}
}
@@ -236,7 +233,7 @@ impl CargoWorkspace {
current_dir: &AbsPath,
config: &CargoConfig,
progress: &dyn Fn(String),
- ) -> Result<cargo_metadata::Metadata> {
+ ) -> anyhow::Result<cargo_metadata::Metadata> {
let targets = find_list_of_build_targets(config, cargo_toml);
let mut meta = MetadataCommand::new();
diff --git a/src/tools/rust-analyzer/crates/project-model/src/lib.rs b/src/tools/rust-analyzer/crates/project-model/src/lib.rs
index 61acc646f..901dcfd2b 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/lib.rs
@@ -31,12 +31,13 @@ pub mod target_data_layout;
mod tests;
use std::{
+ fmt,
fs::{self, read_dir, ReadDir},
io,
process::Command,
};
-use anyhow::{bail, format_err, Context, Result};
+use anyhow::{bail, format_err, Context};
use paths::{AbsPath, AbsPathBuf};
use rustc_hash::FxHashSet;
@@ -59,19 +60,19 @@ pub enum ProjectManifest {
}
impl ProjectManifest {
- pub fn from_manifest_file(path: AbsPathBuf) -> Result<ProjectManifest> {
+ pub fn from_manifest_file(path: AbsPathBuf) -> anyhow::Result<ProjectManifest> {
let path = ManifestPath::try_from(path)
- .map_err(|path| format_err!("bad manifest path: {}", path.display()))?;
+ .map_err(|path| format_err!("bad manifest path: {path}"))?;
if path.file_name().unwrap_or_default() == "rust-project.json" {
return Ok(ProjectManifest::ProjectJson(path));
}
if path.file_name().unwrap_or_default() == "Cargo.toml" {
return Ok(ProjectManifest::CargoToml(path));
}
- bail!("project root must point to Cargo.toml or rust-project.json: {}", path.display());
+ bail!("project root must point to Cargo.toml or rust-project.json: {path}");
}
- pub fn discover_single(path: &AbsPath) -> Result<ProjectManifest> {
+ pub fn discover_single(path: &AbsPath) -> anyhow::Result<ProjectManifest> {
let mut candidates = ProjectManifest::discover(path)?;
let res = match candidates.pop() {
None => bail!("no projects"),
@@ -145,7 +146,17 @@ impl ProjectManifest {
}
}
-fn utf8_stdout(mut cmd: Command) -> Result<String> {
+impl fmt::Display for ProjectManifest {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ ProjectManifest::ProjectJson(it) | ProjectManifest::CargoToml(it) => {
+ fmt::Display::fmt(&it, f)
+ }
+ }
+ }
+}
+
+fn utf8_stdout(mut cmd: Command) -> anyhow::Result<String> {
let output = cmd.output().with_context(|| format!("{cmd:?} failed"))?;
if !output.status.success() {
match String::from_utf8(output.stderr) {
diff --git a/src/tools/rust-analyzer/crates/project-model/src/manifest_path.rs b/src/tools/rust-analyzer/crates/project-model/src/manifest_path.rs
index 3f60e4dd9..490e1a4ea 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/manifest_path.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/manifest_path.rs
@@ -1,5 +1,5 @@
//! See [`ManifestPath`].
-use std::{ops, path::Path};
+use std::{fmt, ops, path::Path};
use paths::{AbsPath, AbsPathBuf};
@@ -40,6 +40,12 @@ impl ManifestPath {
}
}
+impl fmt::Display for ManifestPath {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Display::fmt(&self.file, f)
+ }
+}
+
impl ops::Deref for ManifestPath {
type Target = AbsPath;
diff --git a/src/tools/rust-analyzer/crates/project-model/src/rustc_cfg.rs b/src/tools/rust-analyzer/crates/project-model/src/rustc_cfg.rs
index 0066f6717..8392718b2 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/rustc_cfg.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/rustc_cfg.rs
@@ -2,7 +2,6 @@
use std::process::Command;
-use anyhow::Result;
use rustc_hash::FxHashMap;
use crate::{cfg_flag::CfgFlag, utf8_stdout, ManifestPath};
@@ -23,6 +22,9 @@ pub(crate) fn get(
}
}
+ // Add miri cfg, which is useful for mir eval in stdlib
+ res.push(CfgFlag::Atom("miri".into()));
+
match get_rust_cfgs(cargo_toml, target, extra_env) {
Ok(rustc_cfgs) => {
tracing::debug!(
@@ -44,7 +46,7 @@ fn get_rust_cfgs(
cargo_toml: Option<&ManifestPath>,
target: Option<&str>,
extra_env: &FxHashMap<String, String>,
-) -> Result<String> {
+) -> anyhow::Result<String> {
if let Some(cargo_toml) = cargo_toml {
let mut cargo_config = Command::new(toolchain::cargo());
cargo_config.envs(extra_env);
diff --git a/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs b/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs
index e3a2de927..da862c9e8 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs
@@ -85,9 +85,8 @@ impl Sysroot {
" try running `rustup component add rust-src` to possible fix this"
};
Some(format!(
- "could not find libcore in loaded sysroot at `{}`{}",
- self.src_root.as_path().display(),
- var_note,
+ "could not find libcore in loaded sysroot at `{}`{var_note}",
+ self.src_root.as_path(),
))
} else {
None
@@ -99,7 +98,7 @@ impl Sysroot {
impl Sysroot {
/// Attempts to discover the toolchain's sysroot from the given `dir`.
pub fn discover(dir: &AbsPath, extra_env: &FxHashMap<String, String>) -> Result<Sysroot> {
- tracing::debug!("discovering sysroot for {}", dir.display());
+ tracing::debug!("discovering sysroot for {dir}");
let sysroot_dir = discover_sysroot_dir(dir, extra_env)?;
let sysroot_src_dir =
discover_sysroot_src_dir_or_add_component(&sysroot_dir, dir, extra_env)?;
@@ -111,7 +110,7 @@ impl Sysroot {
extra_env: &FxHashMap<String, String>,
src: AbsPathBuf,
) -> Result<Sysroot> {
- tracing::debug!("discovering sysroot for {}", current_dir.display());
+ tracing::debug!("discovering sysroot for {current_dir}");
let sysroot_dir = discover_sysroot_dir(current_dir, extra_env)?;
Ok(Sysroot::load(sysroot_dir, src))
}
@@ -122,7 +121,7 @@ impl Sysroot {
pub fn with_sysroot_dir(sysroot_dir: AbsPathBuf) -> Result<Sysroot> {
let sysroot_src_dir = discover_sysroot_src_dir(&sysroot_dir).ok_or_else(|| {
- format_err!("can't load standard library from sysroot path {}", sysroot_dir.display())
+ format_err!("can't load standard library from sysroot path {sysroot_dir}")
})?;
Ok(Sysroot::load(sysroot_dir, sysroot_src_dir))
}
@@ -220,10 +219,10 @@ fn discover_sysroot_src_dir(sysroot_path: &AbsPathBuf) -> Option<AbsPathBuf> {
if let Ok(path) = AbsPathBuf::try_from(path.as_str()) {
let core = path.join("core");
if fs::metadata(&core).is_ok() {
- tracing::debug!("Discovered sysroot by RUST_SRC_PATH: {}", path.display());
+ tracing::debug!("Discovered sysroot by RUST_SRC_PATH: {path}");
return Some(path);
}
- tracing::debug!("RUST_SRC_PATH is set, but is invalid (no core: {:?}), ignoring", core);
+ tracing::debug!("RUST_SRC_PATH is set, but is invalid (no core: {core:?}), ignoring");
} else {
tracing::debug!("RUST_SRC_PATH is set, but is invalid, ignoring");
}
@@ -250,10 +249,9 @@ fn discover_sysroot_src_dir_or_add_component(
format_err!(
"\
can't load standard library from sysroot
-{}
+{sysroot_path}
(discovered via `rustc --print sysroot`)
try installing the Rust source the same way you installed rustc",
- sysroot_path.display(),
)
})
}
@@ -261,7 +259,7 @@ try installing the Rust source the same way you installed rustc",
fn get_rustc_src(sysroot_path: &AbsPath) -> Option<ManifestPath> {
let rustc_src = sysroot_path.join("lib/rustlib/rustc-src/rust/compiler/rustc/Cargo.toml");
let rustc_src = ManifestPath::try_from(rustc_src).ok()?;
- tracing::debug!("checking for rustc source code: {}", rustc_src.display());
+ tracing::debug!("checking for rustc source code: {rustc_src}");
if fs::metadata(&rustc_src).is_ok() {
Some(rustc_src)
} else {
@@ -271,7 +269,7 @@ fn get_rustc_src(sysroot_path: &AbsPath) -> Option<ManifestPath> {
fn get_rust_src(sysroot_path: &AbsPath) -> Option<AbsPathBuf> {
let rust_src = sysroot_path.join("lib/rustlib/src/rust/library");
- tracing::debug!("checking sysroot library: {}", rust_src.display());
+ tracing::debug!("checking sysroot library: {rust_src}");
if fs::metadata(&rust_src).is_ok() {
Some(rust_src)
} else {
diff --git a/src/tools/rust-analyzer/crates/project-model/src/target_data_layout.rs b/src/tools/rust-analyzer/crates/project-model/src/target_data_layout.rs
index 30ca7b348..cb995857e 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/target_data_layout.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/target_data_layout.rs
@@ -1,7 +1,6 @@
//! Runs `rustc --print target-spec-json` to get the target_data_layout.
use std::process::Command;
-use anyhow::Result;
use rustc_hash::FxHashMap;
use crate::{utf8_stdout, ManifestPath};
@@ -10,7 +9,7 @@ pub fn get(
cargo_toml: Option<&ManifestPath>,
target: Option<&str>,
extra_env: &FxHashMap<String, String>,
-) -> Result<String> {
+) -> anyhow::Result<String> {
let output = (|| {
if let Some(cargo_toml) = cargo_toml {
let mut cmd = Command::new(toolchain::rustc());
diff --git a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs
index b5fe237fc..f51ea7eeb 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs
@@ -4,7 +4,7 @@
use std::{collections::VecDeque, fmt, fs, process::Command, sync};
-use anyhow::{format_err, Context, Result};
+use anyhow::{format_err, Context};
use base_db::{
CrateDisplayName, CrateGraph, CrateId, CrateName, CrateOrigin, Dependency, Edition, Env,
FileId, LangCrateOrigin, ProcMacroPaths, ReleaseChannel, TargetLayoutLoadResult,
@@ -151,7 +151,16 @@ impl ProjectWorkspace {
manifest: ProjectManifest,
config: &CargoConfig,
progress: &dyn Fn(String),
- ) -> Result<ProjectWorkspace> {
+ ) -> anyhow::Result<ProjectWorkspace> {
+ ProjectWorkspace::load_inner(&manifest, config, progress)
+ .with_context(|| format!("Failed to load the project at {manifest}"))
+ }
+
+ fn load_inner(
+ manifest: &ProjectManifest,
+ config: &CargoConfig,
+ progress: &dyn Fn(String),
+ ) -> anyhow::Result<ProjectWorkspace> {
let version = |current_dir, cmd_path, prefix: &str| {
let cargo_version = utf8_stdout({
let mut cmd = Command::new(cmd_path);
@@ -167,12 +176,10 @@ impl ProjectWorkspace {
};
let res = match manifest {
ProjectManifest::ProjectJson(project_json) => {
- let file = fs::read_to_string(&project_json).with_context(|| {
- format!("Failed to read json file {}", project_json.display())
- })?;
- let data = serde_json::from_str(&file).with_context(|| {
- format!("Failed to deserialize json file {}", project_json.display())
- })?;
+ let file = fs::read_to_string(&project_json)
+ .with_context(|| format!("Failed to read json file {project_json}"))?;
+ let data = serde_json::from_str(&file)
+ .with_context(|| format!("Failed to deserialize json file {project_json}"))?;
let project_location = project_json.parent().to_path_buf();
let toolchain = version(&*project_location, toolchain::rustc(), "rustc ")?;
let project_json = ProjectJson::new(&project_location, data);
@@ -193,9 +200,7 @@ impl ProjectWorkspace {
)
.with_context(|| {
format!(
- "Failed to read Cargo metadata from Cargo.toml file {}, {:?}",
- cargo_toml.display(),
- toolchain
+ "Failed to read Cargo metadata from Cargo.toml file {cargo_toml}, {toolchain:?}",
)
})?;
let cargo = CargoWorkspace::new(meta);
@@ -203,12 +208,12 @@ impl ProjectWorkspace {
let sysroot = match (&config.sysroot, &config.sysroot_src) {
(Some(RustLibSource::Path(path)), None) => {
Sysroot::with_sysroot_dir(path.clone()).map_err(|e| {
- Some(format!("Failed to find sysroot at {}:{e}", path.display()))
+ Some(format!("Failed to find sysroot at {path}:{e}"))
})
}
(Some(RustLibSource::Discover), None) => {
Sysroot::discover(cargo_toml.parent(), &config.extra_env).map_err(|e| {
- Some(format!("Failed to find sysroot for Cargo.toml file {}. Is rust-src installed? {e}", cargo_toml.display()))
+ Some(format!("Failed to find sysroot for Cargo.toml file {cargo_toml}. Is rust-src installed? {e}"))
})
}
(Some(RustLibSource::Path(sysroot)), Some(sysroot_src)) => {
@@ -220,21 +225,19 @@ impl ProjectWorkspace {
&config.extra_env,
sysroot_src.clone(),
).map_err(|e| {
- Some(format!("Failed to find sysroot for Cargo.toml file {}. Is rust-src installed? {e}", cargo_toml.display()))
+ Some(format!("Failed to find sysroot for Cargo.toml file {cargo_toml}. Is rust-src installed? {e}"))
})
}
(None, _) => Err(None),
};
if let Ok(sysroot) = &sysroot {
- tracing::info!(workspace = %cargo_toml.display(), src_root = %sysroot.src_root().display(), root = %sysroot.root().display(), "Using sysroot");
+ tracing::info!(workspace = %cargo_toml, src_root = %sysroot.src_root(), root = %sysroot.root(), "Using sysroot");
}
let rustc_dir = match &config.rustc_source {
Some(RustLibSource::Path(path)) => ManifestPath::try_from(path.clone())
- .map_err(|p| {
- Some(format!("rustc source path is not absolute: {}", p.display()))
- }),
+ .map_err(|p| Some(format!("rustc source path is not absolute: {p}"))),
Some(RustLibSource::Discover) => {
sysroot.as_ref().ok().and_then(Sysroot::discover_rustc).ok_or_else(|| {
Some(format!("Failed to discover rustc source for sysroot."))
@@ -244,7 +247,7 @@ impl ProjectWorkspace {
};
let rustc = rustc_dir.and_then(|rustc_dir| {
- tracing::info!(workspace = %cargo_toml.display(), rustc_dir = %rustc_dir.display(), "Using rustc source");
+ tracing::info!(workspace = %cargo_toml, rustc_dir = %rustc_dir, "Using rustc source");
match CargoWorkspace::fetch_metadata(
&rustc_dir,
cargo_toml.parent(),
@@ -266,13 +269,11 @@ impl ProjectWorkspace {
Err(e) => {
tracing::error!(
%e,
- "Failed to read Cargo metadata from rustc source at {}",
- rustc_dir.display()
+ "Failed to read Cargo metadata from rustc source at {rustc_dir}",
);
Err(Some(format!(
- "Failed to read Cargo metadata from rustc source at {}: {e}",
- rustc_dir.display())
- ))
+ "Failed to read Cargo metadata from rustc source at {rustc_dir}: {e}"
+ )))
}
}
});
@@ -330,7 +331,7 @@ impl ProjectWorkspace {
(None, None) => Err(None),
};
if let Ok(sysroot) = &sysroot {
- tracing::info!(src_root = %sysroot.src_root().display(), root = %sysroot.root().display(), "Using sysroot");
+ tracing::info!(src_root = %sysroot.src_root(), root = %sysroot.root(), "Using sysroot");
}
let rustc_cfg = rustc_cfg::get(None, target, extra_env);
@@ -340,26 +341,23 @@ impl ProjectWorkspace {
pub fn load_detached_files(
detached_files: Vec<AbsPathBuf>,
config: &CargoConfig,
- ) -> Result<ProjectWorkspace> {
+ ) -> anyhow::Result<ProjectWorkspace> {
let sysroot = match &config.sysroot {
Some(RustLibSource::Path(path)) => Sysroot::with_sysroot_dir(path.clone())
- .map_err(|e| Some(format!("Failed to find sysroot at {}:{e}", path.display()))),
+ .map_err(|e| Some(format!("Failed to find sysroot at {path}:{e}"))),
Some(RustLibSource::Discover) => {
let dir = &detached_files
.first()
.and_then(|it| it.parent())
.ok_or_else(|| format_err!("No detached files to load"))?;
Sysroot::discover(dir, &config.extra_env).map_err(|e| {
- Some(format!(
- "Failed to find sysroot for {}. Is rust-src installed? {e}",
- dir.display()
- ))
+ Some(format!("Failed to find sysroot for {dir}. Is rust-src installed? {e}"))
})
}
None => Err(None),
};
if let Ok(sysroot) = &sysroot {
- tracing::info!(src_root = %sysroot.src_root().display(), root = %sysroot.root().display(), "Using sysroot");
+ tracing::info!(src_root = %sysroot.src_root(), root = %sysroot.root(), "Using sysroot");
}
let rustc_cfg = rustc_cfg::get(None, None, &Default::default());
Ok(ProjectWorkspace::DetachedFiles { files: detached_files, sysroot, rustc_cfg })
@@ -370,15 +368,12 @@ impl ProjectWorkspace {
&self,
config: &CargoConfig,
progress: &dyn Fn(String),
- ) -> Result<WorkspaceBuildScripts> {
+ ) -> anyhow::Result<WorkspaceBuildScripts> {
match self {
ProjectWorkspace::Cargo { cargo, toolchain, .. } => {
WorkspaceBuildScripts::run_for_workspace(config, cargo, progress, toolchain)
.with_context(|| {
- format!(
- "Failed to run build scripts for {}",
- &cargo.workspace_root().display()
- )
+ format!("Failed to run build scripts for {}", cargo.workspace_root())
})
}
ProjectWorkspace::Json { .. } | ProjectWorkspace::DetachedFiles { .. } => {
@@ -393,7 +388,7 @@ impl ProjectWorkspace {
workspaces: &[ProjectWorkspace],
config: &CargoConfig,
progress: &dyn Fn(String),
- ) -> Vec<Result<WorkspaceBuildScripts>> {
+ ) -> Vec<anyhow::Result<WorkspaceBuildScripts>> {
if matches!(config.invocation_strategy, InvocationStrategy::PerWorkspace)
|| config.run_build_script_command.is_none()
{
@@ -419,10 +414,7 @@ impl ProjectWorkspace {
ProjectWorkspace::Cargo { cargo, .. } => match outputs {
Ok(outputs) => Ok(outputs.next().unwrap()),
Err(e) => Err(e.clone()).with_context(|| {
- format!(
- "Failed to run build scripts for {}",
- &cargo.workspace_root().display()
- )
+ format!("Failed to run build scripts for {}", cargo.workspace_root())
}),
},
_ => Ok(WorkspaceBuildScripts::default()),
@@ -447,7 +439,7 @@ impl ProjectWorkspace {
}
}
- pub fn find_sysroot_proc_macro_srv(&self) -> Result<AbsPathBuf> {
+ pub fn find_sysroot_proc_macro_srv(&self) -> anyhow::Result<AbsPathBuf> {
match self {
ProjectWorkspace::Cargo { sysroot: Ok(sysroot), .. }
| ProjectWorkspace::Json { sysroot: Ok(sysroot), .. }
@@ -459,22 +451,22 @@ impl ProjectWorkspace {
.map(|segment| sysroot.root().join(segment).join(&standalone_server_name))
.find(|server_path| std::fs::metadata(server_path).is_ok())
.ok_or_else(|| {
- anyhow::anyhow!(
+ anyhow::format_err!(
"cannot find proc-macro server in sysroot `{}`",
- sysroot.root().display()
+ sysroot.root()
)
})
}
ProjectWorkspace::DetachedFiles { .. } => {
- Err(anyhow::anyhow!("cannot find proc-macro server, no sysroot was found"))
+ Err(anyhow::format_err!("cannot find proc-macro server, no sysroot was found"))
}
- ProjectWorkspace::Cargo { cargo, .. } => Err(anyhow::anyhow!(
+ ProjectWorkspace::Cargo { cargo, .. } => Err(anyhow::format_err!(
"cannot find proc-macro-srv, the workspace `{}` is missing a sysroot",
- cargo.workspace_root().display()
+ cargo.workspace_root()
)),
- ProjectWorkspace::Json { project, .. } => Err(anyhow::anyhow!(
+ ProjectWorkspace::Json { project, .. } => Err(anyhow::format_err!(
"cannot find proc-macro-srv, the workspace `{}` is missing a sysroot",
- project.path().display()
+ project.path()
)),
}
}
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml b/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml
index 5b72d5756..5bfac7ee4 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml
@@ -34,10 +34,9 @@ serde.workspace = true
rayon = "1.6.1"
num_cpus = "1.15.0"
mimalloc = { version = "0.1.30", default-features = false, optional = true }
-lsp-server = { version = "0.7.0", path = "../../lib/lsp-server" }
+lsp-server.workspace = true
tracing = "0.1.35"
tracing-subscriber = { version = "0.3.16", default-features = false, features = [
- "env-filter",
"registry",
"fmt",
"tracing-log",
@@ -48,12 +47,8 @@ triomphe.workspace = true
nohash-hasher.workspace = true
always-assert = "0.1.2"
-# These dependencies are unused, but we pin them to a version here to restrict them for our transitive dependencies
-# so that we don't pull in duplicates of their dependencies like windows-sys and syn 1 vs 2
-# these would pull in serde 2
-thiserror = "=1.0.39"
-serde_repr = "=0.1.11"
-# these would pull in windows-sys 0.45.0
+# These 3 deps are not used by r-a directly, but we list them here to lock in their versions
+# in our transitive deps to prevent them from pulling in windows-sys 0.45.0
mio = "=0.8.5"
filetime = "=0.2.19"
parking_lot_core = "=0.9.6"
@@ -67,13 +62,13 @@ ide-db.workspace = true
# This should only be used in CLI
ide-ssr.workspace = true
ide.workspace = true
+load-cargo.workspace = true
proc-macro-api.workspace = true
profile.workspace = true
project-model.workspace = true
stdx.workspace = true
syntax.workspace = true
toolchain.workspace = true
-tt.workspace = true
vfs-notify.workspace = true
vfs.workspace = true
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/logger.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/logger.rs
index 8caadecd8..1f923f6cf 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/logger.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/logger.rs
@@ -8,10 +8,11 @@ use std::{
sync::Arc,
};
-use rust_analyzer::Result;
+use anyhow::Context;
use tracing::{level_filters::LevelFilter, Event, Subscriber};
use tracing_log::NormalizeEvent;
use tracing_subscriber::{
+ filter::Targets,
fmt::{
format::Writer, writer::BoxMakeWriter, FmtContext, FormatEvent, FormatFields,
FormattedFields, MakeWriter,
@@ -19,81 +20,62 @@ use tracing_subscriber::{
layer::SubscriberExt,
registry::LookupSpan,
util::SubscriberInitExt,
- EnvFilter, Registry,
+ Registry,
};
use tracing_tree::HierarchicalLayer;
-pub(crate) struct Logger {
- filter: EnvFilter,
- file: Option<File>,
+pub(crate) struct LoggerConfig {
+ pub(crate) log_file: Option<File>,
+ pub(crate) filter: String,
+ pub(crate) chalk_filter: Option<String>,
}
struct MakeWriterStderr;
-impl<'a> MakeWriter<'a> for MakeWriterStderr {
+impl MakeWriter<'_> for MakeWriterStderr {
type Writer = Stderr;
- fn make_writer(&'a self) -> Self::Writer {
+ fn make_writer(&self) -> Self::Writer {
io::stderr()
}
}
-impl Logger {
- pub(crate) fn new(file: Option<File>, filter: Option<&str>) -> Logger {
- let filter = filter.map_or(EnvFilter::default(), EnvFilter::new);
-
- Logger { filter, file }
- }
+impl LoggerConfig {
+ pub(crate) fn init(self) -> anyhow::Result<()> {
+ let mut filter: Targets = self
+ .filter
+ .parse()
+ .with_context(|| format!("invalid log filter: `{}`", self.filter))?;
+
+ let mut chalk_layer = None;
+ if let Some(chalk_filter) = self.chalk_filter {
+ let level: LevelFilter =
+ chalk_filter.parse().with_context(|| "invalid chalk log filter")?;
+ chalk_layer = Some(
+ HierarchicalLayer::default()
+ .with_indent_lines(true)
+ .with_ansi(false)
+ .with_indent_amount(2)
+ .with_writer(io::stderr),
+ );
+ filter = filter
+ .with_target("chalk_solve", level)
+ .with_target("chalk_ir", level)
+ .with_target("chalk_recursive", level);
+ };
- pub(crate) fn install(self) -> Result<()> {
- // The meaning of CHALK_DEBUG I suspected is to tell chalk crates
- // (i.e. chalk-solve, chalk-ir, chalk-recursive) how to filter tracing
- // logs. But now we can only have just one filter, which means we have to
- // merge chalk filter to our main filter (from RA_LOG env).
- //
- // The acceptable syntax of CHALK_DEBUG is `target[span{field=value}]=level`.
- // As the value should only affect chalk crates, we'd better manually
- // specify the target. And for simplicity, CHALK_DEBUG only accept the value
- // that specify level.
- let chalk_level_dir = std::env::var("CHALK_DEBUG")
- .map(|val| {
- val.parse::<LevelFilter>().expect(
- "invalid CHALK_DEBUG value, expect right log level (like debug or trace)",
- )
- })
- .ok();
-
- let chalk_layer = HierarchicalLayer::default()
- .with_indent_lines(true)
- .with_ansi(false)
- .with_indent_amount(2)
- .with_writer(io::stderr);
-
- let writer = match self.file {
+ let writer = match self.log_file {
Some(file) => BoxMakeWriter::new(Arc::new(file)),
None => BoxMakeWriter::new(io::stderr),
};
let ra_fmt_layer =
tracing_subscriber::fmt::layer().event_format(LoggerFormatter).with_writer(writer);
- match chalk_level_dir {
- Some(val) => {
- Registry::default()
- .with(
- self.filter
- .add_directive(format!("chalk_solve={val}").parse()?)
- .add_directive(format!("chalk_ir={val}").parse()?)
- .add_directive(format!("chalk_recursive={val}").parse()?),
- )
- .with(ra_fmt_layer)
- .with(chalk_layer)
- .init();
- }
- None => {
- Registry::default().with(self.filter).with(ra_fmt_layer).init();
- }
- };
-
+ let registry = Registry::default().with(filter).with(ra_fmt_layer);
+ match chalk_layer {
+ Some(chalk_layer) => registry.with(chalk_layer).init(),
+ None => registry.init(),
+ }
Ok(())
}
}
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs
index 91911dd18..2fa14fc7e 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs
@@ -7,14 +7,11 @@
mod logger;
mod rustc_wrapper;
-use std::{
- env, fs,
- path::{Path, PathBuf},
- process,
-};
+use std::{env, fs, path::PathBuf, process};
+use anyhow::Context;
use lsp_server::Connection;
-use rust_analyzer::{cli::flags, config::Config, from_json, Result};
+use rust_analyzer::{cli::flags, config::Config, from_json};
use vfs::AbsPathBuf;
#[cfg(all(feature = "mimalloc"))]
@@ -25,7 +22,7 @@ static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
#[global_allocator]
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
-fn main() {
+fn main() -> anyhow::Result<()> {
if std::env::var("RA_RUSTC_WRAPPER").is_ok() {
let mut args = std::env::args_os();
let _me = args.next().unwrap();
@@ -41,14 +38,7 @@ fn main() {
}
let flags = flags::RustAnalyzer::from_env_or_exit();
- if let Err(err) = try_main(flags) {
- tracing::error!("Unexpected error: {}", err);
- eprintln!("{err}");
- process::exit(101);
- }
-}
-fn try_main(flags: flags::RustAnalyzer) -> Result<()> {
#[cfg(debug_assertions)]
if flags.wait_dbg || env::var("RA_WAIT_DBG").is_ok() {
#[allow(unused_mut)]
@@ -58,14 +48,8 @@ fn try_main(flags: flags::RustAnalyzer) -> Result<()> {
}
}
- let mut log_file = flags.log_file.as_deref();
-
- let env_log_file = env::var("RA_LOG_FILE").ok();
- if let Some(env_log_file) = env_log_file.as_deref() {
- log_file = Some(Path::new(env_log_file));
- }
+ setup_logging(flags.log_file.clone())?;
- setup_logging(log_file)?;
let verbosity = flags.verbosity();
match flags.subcommand {
@@ -98,11 +82,12 @@ fn try_main(flags: flags::RustAnalyzer) -> Result<()> {
flags::RustAnalyzerCmd::Search(cmd) => cmd.run()?,
flags::RustAnalyzerCmd::Lsif(cmd) => cmd.run()?,
flags::RustAnalyzerCmd::Scip(cmd) => cmd.run()?,
+ flags::RustAnalyzerCmd::RunTests(cmd) => cmd.run()?,
}
Ok(())
}
-fn setup_logging(log_file: Option<&Path>) -> Result<()> {
+fn setup_logging(log_file_flag: Option<PathBuf>) -> anyhow::Result<()> {
if cfg!(windows) {
// This is required so that windows finds our pdb that is placed right beside the exe.
// By default it doesn't look at the folder the exe resides in, only in the current working
@@ -115,23 +100,42 @@ fn setup_logging(log_file: Option<&Path>) -> Result<()> {
}
}
}
+
if env::var("RUST_BACKTRACE").is_err() {
env::set_var("RUST_BACKTRACE", "short");
}
+ let log_file = env::var("RA_LOG_FILE").ok().map(PathBuf::from).or(log_file_flag);
let log_file = match log_file {
Some(path) => {
if let Some(parent) = path.parent() {
let _ = fs::create_dir_all(parent);
}
- Some(fs::File::create(path)?)
+ Some(
+ fs::File::create(&path)
+ .with_context(|| format!("can't create log file at {}", path.display()))?,
+ )
}
None => None,
};
- let filter = env::var("RA_LOG").ok();
- // deliberately enable all `error` logs if the user has not set RA_LOG, as there is usually useful
- // information in there for debugging
- logger::Logger::new(log_file, filter.as_deref().or(Some("error"))).install()?;
+
+ logger::LoggerConfig {
+ log_file,
+ // Deliberately enable all `error` logs if the user has not set RA_LOG, as there is usually
+ // useful information in there for debugging.
+ filter: env::var("RA_LOG").ok().unwrap_or_else(|| "error".to_string()),
+ // The meaning of CHALK_DEBUG I suspected is to tell chalk crates
+ // (i.e. chalk-solve, chalk-ir, chalk-recursive) how to filter tracing
+ // logs. But now we can only have just one filter, which means we have to
+ // merge chalk filter to our main filter (from RA_LOG env).
+ //
+ // The acceptable syntax of CHALK_DEBUG is `target[span{field=value}]=level`.
+ // As the value should only affect chalk crates, we'd better manually
+ // specify the target. And for simplicity, CHALK_DEBUG only accept the value
+ // that specify level.
+ chalk_filter: env::var("CHALK_DEBUG").ok(),
+ }
+ .init()?;
profile::init();
@@ -146,8 +150,8 @@ const STACK_SIZE: usize = 1024 * 1024 * 8;
fn with_extra_thread(
thread_name: impl Into<String>,
thread_intent: stdx::thread::ThreadIntent,
- f: impl FnOnce() -> Result<()> + Send + 'static,
-) -> Result<()> {
+ f: impl FnOnce() -> anyhow::Result<()> + Send + 'static,
+) -> anyhow::Result<()> {
let handle = stdx::thread::Builder::new(thread_intent)
.name(thread_name.into())
.stack_size(STACK_SIZE)
@@ -158,7 +162,7 @@ fn with_extra_thread(
Ok(())
}
-fn run_server() -> Result<()> {
+fn run_server() -> anyhow::Result<()> {
tracing::info!("server version {} will start", rust_analyzer::version());
let (connection, io_threads) = Connection::stdio();
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli.rs
index e35201921..64646b33a 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli.rs
@@ -1,7 +1,6 @@
//! Various batch processing tasks, intended primarily for debugging.
pub mod flags;
-pub mod load_cargo;
mod parse;
mod symbols;
mod highlight;
@@ -10,13 +9,17 @@ mod diagnostics;
mod ssr;
mod lsif;
mod scip;
+mod run_tests;
mod progress_report;
use std::io::Read;
use anyhow::Result;
+use hir::{Module, Name};
+use hir_ty::db::HirDatabase;
use ide::AnalysisHost;
+use itertools::Itertools;
use vfs::Vfs;
#[derive(Clone, Copy)]
@@ -36,7 +39,7 @@ impl Verbosity {
}
}
-fn read_stdin() -> Result<String> {
+fn read_stdin() -> anyhow::Result<String> {
let mut buff = String::new();
std::io::stdin().read_to_string(&mut buff)?;
Ok(buff)
@@ -71,3 +74,14 @@ fn print_memory_usage(mut host: AnalysisHost, vfs: Vfs) {
eprintln!("{remaining:>8} Remaining");
}
+
+fn full_name_of_item(db: &dyn HirDatabase, module: Module, name: Name) -> String {
+ module
+ .path_to_root(db)
+ .into_iter()
+ .rev()
+ .filter_map(|it| it.name(db))
+ .chain(Some(name))
+ .map(|it| it.display(db.upcast()).to_string())
+ .join("::")
+}
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs
index 4cb917ce2..f446a7c05 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -8,14 +8,14 @@ use std::{
use hir::{
db::{DefDatabase, ExpandDatabase, HirDatabase},
- Adt, AssocItem, Crate, DefWithBody, HasCrate, HasSource, HirDisplay, ModuleDef, Name,
+ Adt, AssocItem, Crate, DefWithBody, HasSource, HirDisplay, ModuleDef, Name,
};
use hir_def::{
body::{BodySourceMap, SyntheticSyntax},
hir::{ExprId, PatId},
};
use hir_ty::{Interner, Substitution, TyExt, TypeFlags};
-use ide::{LineCol, RootDatabase};
+use ide::{Analysis, AnnotationConfig, DiagnosticsConfig, InlayHintsConfig, LineCol, RootDatabase};
use ide_db::{
base_db::{
salsa::{self, debug::DebugQueryTable, ParallelDatabase},
@@ -24,20 +24,20 @@ use ide_db::{
LineIndexDatabase,
};
use itertools::Itertools;
+use load_cargo::{load_workspace, LoadCargoConfig, ProcMacroServerChoice};
use oorandom::Rand32;
use profile::{Bytes, StopWatch};
use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace, RustLibSource};
use rayon::prelude::*;
use rustc_hash::FxHashSet;
use syntax::{AstNode, SyntaxNode};
-use vfs::{AbsPathBuf, Vfs, VfsPath};
+use vfs::{AbsPathBuf, FileId, Vfs, VfsPath};
use crate::cli::{
flags::{self, OutputFormat},
- load_cargo::{load_workspace, LoadCargoConfig, ProcMacroServerChoice},
- print_memory_usage,
+ full_name_of_item, print_memory_usage,
progress_report::ProgressReport,
- report_metric, Result, Verbosity,
+ report_metric, Verbosity,
};
/// Need to wrap Snapshot to provide `Clone` impl for `map_with`
@@ -49,7 +49,7 @@ impl<DB: ParallelDatabase> Clone for Snap<salsa::Snapshot<DB>> {
}
impl flags::AnalysisStats {
- pub fn run(self, verbosity: Verbosity) -> Result<()> {
+ pub fn run(self, verbosity: Verbosity) -> anyhow::Result<()> {
let mut rng = {
let seed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u64;
Rand32::new(seed)
@@ -95,17 +95,41 @@ impl flags::AnalysisStats {
eprintln!(")");
let mut analysis_sw = self.stop_watch();
- let mut num_crates = 0;
- let mut visited_modules = FxHashSet::default();
- let mut visit_queue = Vec::new();
let mut krates = Crate::all(db);
if self.randomize {
shuffle(&mut rng, &mut krates);
}
+
+ let mut item_tree_sw = self.stop_watch();
+ let mut num_item_trees = 0;
+ let source_roots =
+ krates.iter().cloned().map(|krate| db.file_source_root(krate.root_file(db))).unique();
+ for source_root_id in source_roots {
+ let source_root = db.source_root(source_root_id);
+ if !source_root.is_library || self.with_deps {
+ for file_id in source_root.iter() {
+ if let Some(p) = source_root.path_for_file(&file_id) {
+ if let Some((_, Some("rs"))) = p.name_and_extension() {
+ db.file_item_tree(file_id.into());
+ num_item_trees += 1;
+ }
+ }
+ }
+ }
+ }
+ eprintln!(" item trees: {num_item_trees}");
+ let item_tree_time = item_tree_sw.elapsed();
+ eprintln!("{:<20} {}", "Item Tree Collection:", item_tree_time);
+ report_metric("item tree time", item_tree_time.time.as_millis() as u64, "ms");
+
+ let mut crate_def_map_sw = self.stop_watch();
+ let mut num_crates = 0;
+ let mut visited_modules = FxHashSet::default();
+ let mut visit_queue = Vec::new();
for krate in krates {
- let module = krate.root_module(db);
- let file_id = module.definition_source(db).file_id;
+ let module = krate.root_module();
+ let file_id = module.definition_source_file_id(db);
let file_id = file_id.original_file(db);
let source_root = db.file_source_root(file_id);
let source_root = db.source_root(source_root);
@@ -124,8 +148,10 @@ impl flags::AnalysisStats {
let mut bodies = Vec::new();
let mut adts = Vec::new();
let mut consts = Vec::new();
+ let mut file_ids = Vec::new();
while let Some(module) = visit_queue.pop() {
if visited_modules.insert(module) {
+ file_ids.extend(module.as_source_file_id(db));
visit_queue.extend(module.children(db));
for decl in module.declarations(db) {
@@ -171,7 +197,9 @@ impl flags::AnalysisStats {
adts.len(),
consts.len(),
);
- eprintln!("{:<20} {}", "Item Collection:", analysis_sw.elapsed());
+ let crate_def_map_time = crate_def_map_sw.elapsed();
+ eprintln!("{:<20} {}", "Item Collection:", crate_def_map_time);
+ report_metric("crate def map time", crate_def_map_time.time.as_millis() as u64, "ms");
if self.randomize {
shuffle(&mut rng, &mut bodies);
@@ -197,6 +225,10 @@ impl flags::AnalysisStats {
self.run_const_eval(db, &consts, verbosity);
}
+ if self.run_all_ide_things {
+ self.run_ide_things(host.analysis(), file_ids);
+ }
+
let total_span = analysis_sw.elapsed();
eprintln!("{:<20} {total_span}", "Total:");
report_metric("total time", total_span.time.as_millis() as u64, "ms");
@@ -242,21 +274,15 @@ impl flags::AnalysisStats {
continue;
}
all += 1;
- let Err(e)
- = db.layout_of_adt(hir_def::AdtId::from(a).into(), Substitution::empty(Interner), a.krate(db).into())
- else {
- continue
+ let Err(e) = db.layout_of_adt(
+ hir_def::AdtId::from(a).into(),
+ Substitution::empty(Interner),
+ db.trait_environment(a.into()),
+ ) else {
+ continue;
};
if verbosity.is_spammy() {
- let full_name = a
- .module(db)
- .path_to_root(db)
- .into_iter()
- .rev()
- .filter_map(|it| it.name(db))
- .chain(Some(a.name(db)))
- .map(|it| it.display(db).to_string())
- .join("::");
+ let full_name = full_name_of_item(db, a.module(db), a.name(db));
println!("Data layout for {full_name} failed due {e:?}");
}
fail += 1;
@@ -278,15 +304,8 @@ impl flags::AnalysisStats {
continue;
};
if verbosity.is_spammy() {
- let full_name = c
- .module(db)
- .path_to_root(db)
- .into_iter()
- .rev()
- .filter_map(|it| it.name(db))
- .chain(c.name(db))
- .map(|it| it.display(db).to_string())
- .join("::");
+ let full_name =
+ full_name_of_item(db, c.module(db), c.name(db).unwrap_or(Name::missing()));
println!("Const eval for {full_name} failed due {e:?}");
}
fail += 1;
@@ -717,6 +736,83 @@ impl flags::AnalysisStats {
report_metric("body lowering time", body_lowering_time.time.as_millis() as u64, "ms");
}
+ fn run_ide_things(&self, analysis: Analysis, mut file_ids: Vec<FileId>) {
+ file_ids.sort();
+ file_ids.dedup();
+ let mut sw = self.stop_watch();
+
+ for &file_id in &file_ids {
+ _ = analysis.diagnostics(
+ &DiagnosticsConfig {
+ enabled: true,
+ proc_macros_enabled: true,
+ proc_attr_macros_enabled: true,
+ disable_experimental: false,
+ disabled: Default::default(),
+ expr_fill_default: Default::default(),
+ insert_use: ide_db::imports::insert_use::InsertUseConfig {
+ granularity: ide_db::imports::insert_use::ImportGranularity::Crate,
+ enforce_granularity: true,
+ prefix_kind: hir::PrefixKind::ByCrate,
+ group: true,
+ skip_glob_imports: true,
+ },
+ prefer_no_std: Default::default(),
+ },
+ ide::AssistResolveStrategy::All,
+ file_id,
+ );
+ }
+ for &file_id in &file_ids {
+ _ = analysis.inlay_hints(
+ &InlayHintsConfig {
+ render_colons: false,
+ type_hints: true,
+ discriminant_hints: ide::DiscriminantHints::Always,
+ parameter_hints: true,
+ chaining_hints: true,
+ adjustment_hints: ide::AdjustmentHints::Always,
+ adjustment_hints_mode: ide::AdjustmentHintsMode::Postfix,
+ adjustment_hints_hide_outside_unsafe: false,
+ closure_return_type_hints: ide::ClosureReturnTypeHints::Always,
+ closure_capture_hints: true,
+ binding_mode_hints: true,
+ lifetime_elision_hints: ide::LifetimeElisionHints::Always,
+ param_names_for_lifetime_elision_hints: true,
+ hide_named_constructor_hints: false,
+ hide_closure_initialization_hints: false,
+ closure_style: hir::ClosureStyle::ImplFn,
+ max_length: Some(25),
+ closing_brace_hints_min_lines: Some(20),
+ },
+ file_id,
+ None,
+ );
+ }
+ for &file_id in &file_ids {
+ analysis
+ .annotations(
+ &AnnotationConfig {
+ binary_target: true,
+ annotate_runnables: true,
+ annotate_impls: true,
+ annotate_references: false,
+ annotate_method_references: false,
+ annotate_enum_variant_references: false,
+ location: ide::AnnotationLocation::AboveName,
+ },
+ file_id,
+ )
+ .unwrap()
+ .into_iter()
+ .for_each(|annotation| {
+ _ = analysis.resolve_annotation(annotation);
+ });
+ }
+ let ide_time = sw.elapsed();
+ eprintln!("{:<20} {} ({} files)", "IDE:", ide_time, file_ids.len());
+ }
+
fn stop_watch(&self) -> StopWatch {
StopWatch::start().memory(self.memory_usage)
}
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs
index 4306d7212..8541be715 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs
@@ -7,11 +7,9 @@ use rustc_hash::FxHashSet;
use hir::{db::HirDatabase, Crate, Module};
use ide::{AssistResolveStrategy, DiagnosticsConfig, Severity};
use ide_db::base_db::SourceDatabaseExt;
+use load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice};
-use crate::cli::{
- flags,
- load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice},
-};
+use crate::cli::flags;
impl flags::Diagnostics {
pub fn run(self) -> anyhow::Result<()> {
@@ -37,14 +35,14 @@ impl flags::Diagnostics {
let mut visited_files = FxHashSet::default();
let work = all_modules(db).into_iter().filter(|module| {
- let file_id = module.definition_source(db).file_id.original_file(db);
+ let file_id = module.definition_source_file_id(db).original_file(db);
let source_root = db.file_source_root(file_id);
let source_root = db.source_root(source_root);
!source_root.is_library
});
for module in work {
- let file_id = module.definition_source(db).file_id.original_file(db);
+ let file_id = module.definition_source_file_id(db).original_file(db);
if !visited_files.contains(&file_id) {
let crate_name =
module.krate().display_name(db).as_deref().unwrap_or("unknown").to_string();
@@ -82,7 +80,7 @@ impl flags::Diagnostics {
fn all_modules(db: &dyn HirDatabase) -> Vec<Module> {
let mut worklist: Vec<_> =
- Crate::all(db).into_iter().map(|krate| krate.root_module(db)).collect();
+ Crate::all(db).into_iter().map(|krate| krate.root_module()).collect();
let mut modules = Vec::new();
while let Some(module) = worklist.pop() {
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/flags.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/flags.rs
index 208a4e6ec..13b7f039b 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/flags.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/flags.rs
@@ -12,7 +12,7 @@ xflags::xflags! {
/// LSP server for the Rust programming language.
///
/// Subcommands and their flags do not provide any stability guarantees and may be removed or
- /// changed without notice. Top-level flags that are not are marked as [Unstable] provide
+ /// changed without notice. Top-level flags that are not marked as [Unstable] provide
/// backwards-compatibility and may be relied on.
cmd rust-analyzer {
/// Verbosity level, can be repeated multiple times.
@@ -88,6 +88,16 @@ xflags::xflags! {
optional --skip-data-layout
/// Skip const evaluation
optional --skip-const-eval
+ /// Runs several IDE features after analysis, including semantics highlighting, diagnostics
+ /// and annotations. This is useful for benchmarking the memory usage on a project that has
+ /// been worked on for a bit in a longer running session.
+ optional --run-all-ide-things
+ }
+
+ /// Run unit tests of the project using mir interpreter
+ cmd run-tests {
+ /// Directory with Cargo.toml.
+ required path: PathBuf
}
cmd diagnostics {
@@ -103,7 +113,7 @@ xflags::xflags! {
}
cmd ssr {
- /// A structured search replace rule (`$a.foo($b) ==> bar($a, $b)`)
+ /// A structured search replace rule (`$a.foo($b) ==>> bar($a, $b)`)
repeated rule: SsrRule
}
@@ -147,6 +157,7 @@ pub enum RustAnalyzerCmd {
Symbols(Symbols),
Highlight(Highlight),
AnalysisStats(AnalysisStats),
+ RunTests(RunTests),
Diagnostics(Diagnostics),
Ssr(Ssr),
Search(Search),
@@ -182,16 +193,22 @@ pub struct AnalysisStats {
pub parallel: bool,
pub memory_usage: bool,
pub source_stats: bool,
- pub skip_lowering: bool,
- pub skip_inference: bool,
- pub skip_mir_stats: bool,
- pub skip_data_layout: bool,
- pub skip_const_eval: bool,
pub only: Option<String>,
pub with_deps: bool,
pub no_sysroot: bool,
pub disable_build_scripts: bool,
pub disable_proc_macros: bool,
+ pub skip_lowering: bool,
+ pub skip_inference: bool,
+ pub skip_mir_stats: bool,
+ pub skip_data_layout: bool,
+ pub skip_const_eval: bool,
+ pub run_all_ide_things: bool,
+}
+
+#[derive(Debug)]
+pub struct RunTests {
+ pub path: PathBuf,
}
#[derive(Debug)]
@@ -223,6 +240,7 @@ pub struct Lsif {
#[derive(Debug)]
pub struct Scip {
pub path: PathBuf,
+
pub output: Option<PathBuf>,
}
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/load_cargo.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/load_cargo.rs
deleted file mode 100644
index 4e8f99971..000000000
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/load_cargo.rs
+++ /dev/null
@@ -1,205 +0,0 @@
-//! Loads a Cargo project into a static instance of analysis, without support
-//! for incorporating changes.
-use std::path::Path;
-
-use anyhow::{anyhow, Result};
-use crossbeam_channel::{unbounded, Receiver};
-use ide::{AnalysisHost, Change};
-use ide_db::{
- base_db::{CrateGraph, ProcMacros},
- FxHashMap,
-};
-use proc_macro_api::ProcMacroServer;
-use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace};
-use triomphe::Arc;
-use vfs::{loader::Handle, AbsPath, AbsPathBuf};
-
-use crate::reload::{load_proc_macro, ProjectFolders, SourceRootConfig};
-
-// Note: Since this type is used by external tools that use rust-analyzer as a library
-// what otherwise would be `pub(crate)` has to be `pub` here instead.
-pub struct LoadCargoConfig {
- pub load_out_dirs_from_check: bool,
- pub with_proc_macro_server: ProcMacroServerChoice,
- pub prefill_caches: bool,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum ProcMacroServerChoice {
- Sysroot,
- Explicit(AbsPathBuf),
- None,
-}
-
-// Note: Since this function is used by external tools that use rust-analyzer as a library
-// what otherwise would be `pub(crate)` has to be `pub` here instead.
-pub fn load_workspace_at(
- root: &Path,
- cargo_config: &CargoConfig,
- load_config: &LoadCargoConfig,
- progress: &dyn Fn(String),
-) -> Result<(AnalysisHost, vfs::Vfs, Option<ProcMacroServer>)> {
- let root = AbsPathBuf::assert(std::env::current_dir()?.join(root));
- let root = ProjectManifest::discover_single(&root)?;
- let mut workspace = ProjectWorkspace::load(root, cargo_config, progress)?;
-
- if load_config.load_out_dirs_from_check {
- let build_scripts = workspace.run_build_scripts(cargo_config, progress)?;
- workspace.set_build_scripts(build_scripts)
- }
-
- load_workspace(workspace, &cargo_config.extra_env, load_config)
-}
-
-// Note: Since this function is used by external tools that use rust-analyzer as a library
-// what otherwise would be `pub(crate)` has to be `pub` here instead.
-//
-// The reason both, `load_workspace_at` and `load_workspace` are `pub` is that some of
-// these tools need access to `ProjectWorkspace`, too, which `load_workspace_at` hides.
-pub fn load_workspace(
- ws: ProjectWorkspace,
- extra_env: &FxHashMap<String, String>,
- load_config: &LoadCargoConfig,
-) -> Result<(AnalysisHost, vfs::Vfs, Option<ProcMacroServer>)> {
- let (sender, receiver) = unbounded();
- let mut vfs = vfs::Vfs::default();
- let mut loader = {
- let loader =
- vfs_notify::NotifyHandle::spawn(Box::new(move |msg| sender.send(msg).unwrap()));
- Box::new(loader)
- };
-
- let proc_macro_server = match &load_config.with_proc_macro_server {
- ProcMacroServerChoice::Sysroot => ws
- .find_sysroot_proc_macro_srv()
- .and_then(|it| ProcMacroServer::spawn(it).map_err(Into::into)),
- ProcMacroServerChoice::Explicit(path) => {
- ProcMacroServer::spawn(path.clone()).map_err(Into::into)
- }
- ProcMacroServerChoice::None => Err(anyhow!("proc macro server disabled")),
- };
-
- let (crate_graph, proc_macros) = ws.to_crate_graph(
- &mut |path: &AbsPath| {
- let contents = loader.load_sync(path);
- let path = vfs::VfsPath::from(path.to_path_buf());
- vfs.set_file_contents(path.clone(), contents);
- vfs.file_id(&path)
- },
- extra_env,
- );
- let proc_macros = {
- let proc_macro_server = match &proc_macro_server {
- Ok(it) => Ok(it),
- Err(e) => Err(e.to_string()),
- };
- proc_macros
- .into_iter()
- .map(|(crate_id, path)| {
- (
- crate_id,
- path.map_or_else(
- |_| Err("proc macro crate is missing dylib".to_owned()),
- |(_, path)| {
- proc_macro_server.as_ref().map_err(Clone::clone).and_then(
- |proc_macro_server| load_proc_macro(proc_macro_server, &path, &[]),
- )
- },
- ),
- )
- })
- .collect()
- };
-
- let project_folders = ProjectFolders::new(&[ws], &[]);
- loader.set_config(vfs::loader::Config {
- load: project_folders.load,
- watch: vec![],
- version: 0,
- });
-
- tracing::debug!("crate graph: {:?}", crate_graph);
- let host = load_crate_graph(
- crate_graph,
- proc_macros,
- project_folders.source_root_config,
- &mut vfs,
- &receiver,
- );
-
- if load_config.prefill_caches {
- host.analysis().parallel_prime_caches(1, |_| {})?;
- }
- Ok((host, vfs, proc_macro_server.ok()))
-}
-
-fn load_crate_graph(
- crate_graph: CrateGraph,
- proc_macros: ProcMacros,
- source_root_config: SourceRootConfig,
- vfs: &mut vfs::Vfs,
- receiver: &Receiver<vfs::loader::Message>,
-) -> AnalysisHost {
- let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<usize>().ok());
- let mut host = AnalysisHost::new(lru_cap);
- let mut analysis_change = Change::new();
-
- host.raw_database_mut().enable_proc_attr_macros();
-
- // wait until Vfs has loaded all roots
- for task in receiver {
- match task {
- vfs::loader::Message::Progress { n_done, n_total, config_version: _ } => {
- if n_done == n_total {
- break;
- }
- }
- vfs::loader::Message::Loaded { files } => {
- for (path, contents) in files {
- vfs.set_file_contents(path.into(), contents);
- }
- }
- }
- }
- let changes = vfs.take_changes();
- for file in changes {
- if file.exists() {
- let contents = vfs.file_contents(file.file_id);
- if let Ok(text) = std::str::from_utf8(contents) {
- analysis_change.change_file(file.file_id, Some(Arc::from(text)))
- }
- }
- }
- let source_roots = source_root_config.partition(vfs);
- analysis_change.set_roots(source_roots);
-
- analysis_change.set_crate_graph(crate_graph);
- analysis_change.set_proc_macros(proc_macros);
-
- host.apply_change(analysis_change);
- host
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- use hir::Crate;
-
- #[test]
- fn test_loading_rust_analyzer() {
- let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap();
- let cargo_config = CargoConfig::default();
- let load_cargo_config = LoadCargoConfig {
- load_out_dirs_from_check: false,
- with_proc_macro_server: ProcMacroServerChoice::None,
- prefill_caches: false,
- };
- let (host, _vfs, _proc_macro) =
- load_workspace_at(path, &cargo_config, &load_cargo_config, &|_| {}).unwrap();
-
- let n_crates = Crate::all(host.raw_database()).len();
- // RA has quite a few crates, but the exact count doesn't matter
- assert!(n_crates > 20);
- }
-}
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/lsif.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/lsif.rs
index 7f5d08449..42d180114 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/lsif.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/lsif.rs
@@ -8,23 +8,22 @@ use ide::{
Analysis, FileId, FileRange, MonikerKind, PackageInformation, RootDatabase, StaticIndex,
StaticIndexedFile, TokenId, TokenStaticData,
};
-use ide_db::LineIndexDatabase;
-
-use ide_db::base_db::salsa::{self, ParallelDatabase};
-use ide_db::line_index::WideEncoding;
+use ide_db::{
+ base_db::salsa::{self, ParallelDatabase},
+ line_index::WideEncoding,
+ LineIndexDatabase,
+};
+use load_cargo::{load_workspace, LoadCargoConfig, ProcMacroServerChoice};
use lsp_types::{self, lsif};
use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace, RustLibSource};
use vfs::{AbsPathBuf, Vfs};
-use crate::cli::load_cargo::ProcMacroServerChoice;
-use crate::cli::{
- flags,
- load_cargo::{load_workspace, LoadCargoConfig},
- Result,
+use crate::{
+ cli::flags,
+ line_index::{LineEndings, LineIndex, PositionEncoding},
+ to_proto,
+ version::version,
};
-use crate::line_index::{LineEndings, LineIndex, PositionEncoding};
-use crate::to_proto;
-use crate::version::version;
/// Need to wrap Snapshot to provide `Clone` impl for `map_with`
struct Snap<DB>(DB);
@@ -49,8 +48,8 @@ struct LsifManager<'a> {
struct Id(i32);
impl From<Id> for lsp_types::NumberOrString {
- fn from(Id(x): Id) -> Self {
- lsp_types::NumberOrString::Number(x)
+ fn from(Id(it): Id) -> Self {
+ lsp_types::NumberOrString::Number(it)
}
}
@@ -89,8 +88,8 @@ impl LsifManager<'_> {
}
fn get_token_id(&mut self, id: TokenId) -> Id {
- if let Some(x) = self.token_map.get(&id) {
- return *x;
+ if let Some(it) = self.token_map.get(&id) {
+ return *it;
}
let result_set_id = self.add_vertex(lsif::Vertex::ResultSet(lsif::ResultSet { key: None }));
self.token_map.insert(id, result_set_id);
@@ -98,8 +97,8 @@ impl LsifManager<'_> {
}
fn get_package_id(&mut self, package_information: PackageInformation) -> Id {
- if let Some(x) = self.package_map.get(&package_information) {
- return *x;
+ if let Some(it) = self.package_map.get(&package_information) {
+ return *it;
}
let pi = package_information.clone();
let result_set_id =
@@ -120,8 +119,8 @@ impl LsifManager<'_> {
}
fn get_range_id(&mut self, id: FileRange) -> Id {
- if let Some(x) = self.range_map.get(&id) {
- return *x;
+ if let Some(it) = self.range_map.get(&id) {
+ return *it;
}
let file_id = id.file_id;
let doc_id = self.get_file_id(file_id);
@@ -143,8 +142,8 @@ impl LsifManager<'_> {
}
fn get_file_id(&mut self, id: FileId) -> Id {
- if let Some(x) = self.file_map.get(&id) {
- return *x;
+ if let Some(it) = self.file_map.get(&id) {
+ return *it;
}
let path = self.vfs.file_path(id);
let path = path.as_path().unwrap();
@@ -217,18 +216,18 @@ impl LsifManager<'_> {
}));
let mut edges = token.references.iter().fold(
HashMap::<_, Vec<lsp_types::NumberOrString>>::new(),
- |mut edges, x| {
+ |mut edges, it| {
let entry =
- edges.entry((x.range.file_id, x.is_definition)).or_insert_with(Vec::new);
- entry.push((*self.range_map.get(&x.range).unwrap()).into());
+ edges.entry((it.range.file_id, it.is_definition)).or_insert_with(Vec::new);
+ entry.push((*self.range_map.get(&it.range).unwrap()).into());
edges
},
);
- for x in token.references {
- if let Some(vertices) = edges.remove(&(x.range.file_id, x.is_definition)) {
+ for it in token.references {
+ if let Some(vertices) = edges.remove(&(it.range.file_id, it.is_definition)) {
self.add_edge(lsif::Edge::Item(lsif::Item {
- document: (*self.file_map.get(&x.range.file_id).unwrap()).into(),
- property: Some(if x.is_definition {
+ document: (*self.file_map.get(&it.range.file_id).unwrap()).into(),
+ property: Some(if it.is_definition {
lsif::ItemKind::Definitions
} else {
lsif::ItemKind::References
@@ -286,7 +285,7 @@ impl LsifManager<'_> {
}
impl flags::Lsif {
- pub fn run(self) -> Result<()> {
+ pub fn run(self) -> anyhow::Result<()> {
eprintln!("Generating LSIF started...");
let now = Instant::now();
let mut cargo_config = CargoConfig::default();
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/run_tests.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/run_tests.rs
new file mode 100644
index 000000000..e17041991
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/run_tests.rs
@@ -0,0 +1,89 @@
+//! Run all tests in a project, similar to `cargo test`, but using the mir interpreter.
+
+use hir::{Crate, Module};
+use hir_ty::db::HirDatabase;
+use ide_db::{base_db::SourceDatabaseExt, LineIndexDatabase};
+use profile::StopWatch;
+use project_model::{CargoConfig, RustLibSource};
+use syntax::TextRange;
+
+use load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice};
+
+use crate::cli::{flags, full_name_of_item, Result};
+
+impl flags::RunTests {
+ pub fn run(self) -> Result<()> {
+ let mut cargo_config = CargoConfig::default();
+ cargo_config.sysroot = Some(RustLibSource::Discover);
+ let load_cargo_config = LoadCargoConfig {
+ load_out_dirs_from_check: true,
+ with_proc_macro_server: ProcMacroServerChoice::Sysroot,
+ prefill_caches: false,
+ };
+ let (host, _vfs, _proc_macro) =
+ load_workspace_at(&self.path, &cargo_config, &load_cargo_config, &|_| {})?;
+ let db = host.raw_database();
+
+ let tests = all_modules(db)
+ .into_iter()
+ .flat_map(|x| x.declarations(db))
+ .filter_map(|x| match x {
+ hir::ModuleDef::Function(f) => Some(f),
+ _ => None,
+ })
+ .filter(|x| x.is_test(db));
+ let span_formatter = |file_id, text_range: TextRange| {
+ let line_col = match db.line_index(file_id).try_line_col(text_range.start()) {
+ None => " (unknown line col)".to_string(),
+ Some(x) => format!("#{}:{}", x.line + 1, x.col),
+ };
+ let path = &db
+ .source_root(db.file_source_root(file_id))
+ .path_for_file(&file_id)
+ .map(|x| x.to_string());
+ let path = path.as_deref().unwrap_or("<unknown file>");
+ format!("file://{path}{line_col}")
+ };
+ let mut pass_count = 0;
+ let mut ignore_count = 0;
+ let mut fail_count = 0;
+ let mut sw_all = StopWatch::start();
+ for test in tests {
+ let full_name = full_name_of_item(db, test.module(db), test.name(db));
+ println!("test {}", full_name);
+ if test.is_ignore(db) {
+ println!("ignored");
+ ignore_count += 1;
+ continue;
+ }
+ let mut sw_one = StopWatch::start();
+ let result = test.eval(db, span_formatter);
+ if result.trim() == "pass" {
+ pass_count += 1;
+ } else {
+ fail_count += 1;
+ }
+ println!("{}", result);
+ eprintln!("{:<20} {}", format!("test {}", full_name), sw_one.elapsed());
+ }
+ println!("{pass_count} passed, {fail_count} failed, {ignore_count} ignored");
+ eprintln!("{:<20} {}", "All tests", sw_all.elapsed());
+ Ok(())
+ }
+}
+
+fn all_modules(db: &dyn HirDatabase) -> Vec<Module> {
+ let mut worklist: Vec<_> = Crate::all(db)
+ .into_iter()
+ .filter(|x| x.origin(db).is_local())
+ .map(|krate| krate.root_module())
+ .collect();
+ let mut modules = Vec::new();
+
+ while let Some(module) = worklist.pop() {
+ modules.push(module);
+ worklist.extend(module.children(db));
+ }
+
+ modules
+}
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs
index b0b724bdf..44337f955 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs
@@ -6,27 +6,23 @@ use std::{
time::Instant,
};
-use crate::{
- cli::load_cargo::ProcMacroServerChoice,
- line_index::{LineEndings, LineIndex, PositionEncoding},
-};
use ide::{
LineCol, MonikerDescriptorKind, StaticIndex, StaticIndexedFile, TextRange, TokenId,
TokenStaticData,
};
use ide_db::LineIndexDatabase;
+use load_cargo::{load_workspace, LoadCargoConfig, ProcMacroServerChoice};
use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace, RustLibSource};
use scip::types as scip_types;
use std::env;
-use crate::cli::{
- flags,
- load_cargo::{load_workspace, LoadCargoConfig},
- Result,
+use crate::{
+ cli::flags,
+ line_index::{LineEndings, LineIndex, PositionEncoding},
};
impl flags::Scip {
- pub fn run(self) -> Result<()> {
+ pub fn run(self) -> anyhow::Result<()> {
eprintln!("Generating SCIP start...");
let now = Instant::now();
let mut cargo_config = CargoConfig::default();
@@ -65,7 +61,7 @@ impl flags::Scip {
path.normalize()
.as_os_str()
.to_str()
- .ok_or(anyhow::anyhow!("Unable to normalize project_root path"))?
+ .ok_or(anyhow::format_err!("Unable to normalize project_root path"))?
),
text_document_encoding: scip_types::TextEncoding::UTF8.into(),
special_fields: Default::default(),
@@ -168,7 +164,7 @@ impl flags::Scip {
let out_path = self.output.unwrap_or_else(|| PathBuf::from(r"index.scip"));
scip::write_message_to_file(out_path, index)
- .map_err(|err| anyhow::anyhow!("Failed to write scip to file: {}", err))?;
+ .map_err(|err| anyhow::format_err!("Failed to write scip to file: {}", err))?;
eprintln!("Generating SCIP finished {:?}", now.elapsed());
Ok(())
@@ -276,7 +272,7 @@ mod test {
let change_fixture = ChangeFixture::parse(ra_fixture);
host.raw_database_mut().apply_change(change_fixture.change);
let (file_id, range_or_offset) =
- change_fixture.file_position.expect("expected a marker ($0)");
+ change_fixture.file_position.expect("expected a marker ()");
let offset = range_or_offset.expect_offset();
(host, FilePosition { file_id, offset })
}
@@ -325,7 +321,7 @@ use foo::example_mod::func;
fn main() {
func$0();
}
-//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
pub mod example_mod {
pub fn func() {}
}
@@ -338,7 +334,7 @@ pub mod example_mod {
fn symbol_for_trait() {
check_symbol(
r#"
-//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
pub mod module {
pub trait MyTrait {
pub fn func$0() {}
@@ -353,7 +349,7 @@ pub mod module {
fn symbol_for_trait_constant() {
check_symbol(
r#"
- //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+ //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
pub mod module {
pub trait MyTrait {
const MY_CONST$0: u8;
@@ -368,7 +364,7 @@ pub mod module {
fn symbol_for_trait_type() {
check_symbol(
r#"
- //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+ //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
pub mod module {
pub trait MyTrait {
type MyType$0;
@@ -384,7 +380,7 @@ pub mod module {
fn symbol_for_trait_impl_function() {
check_symbol(
r#"
- //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+ //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
pub mod module {
pub trait MyTrait {
pub fn func() {}
@@ -411,7 +407,7 @@ pub mod module {
fn main() {
let x = St { a$0: 2 };
}
- //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+ //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
pub struct St {
pub a: i32,
}
@@ -421,6 +417,44 @@ pub mod module {
}
#[test]
+ fn symbol_for_param() {
+ check_symbol(
+ r#"
+//- /lib.rs crate:main deps:foo
+use foo::example_mod::func;
+fn main() {
+ func(42);
+}
+//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
+pub mod example_mod {
+ pub fn func(x$0: usize) {}
+}
+"#,
+ "rust-analyzer cargo foo 0.1.0 example_mod/func().(x)",
+ );
+ }
+
+ #[test]
+ fn symbol_for_closure_param() {
+ check_symbol(
+ r#"
+//- /lib.rs crate:main deps:foo
+use foo::example_mod::func;
+fn main() {
+ func();
+}
+//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
+pub mod example_mod {
+ pub fn func() {
+ let f = |x$0: usize| {};
+ }
+}
+"#,
+ "rust-analyzer cargo foo 0.1.0 example_mod/func().(x)",
+ );
+ }
+
+ #[test]
fn local_symbol_for_local() {
check_symbol(
r#"
@@ -429,7 +463,7 @@ pub mod module {
fn main() {
func();
}
- //- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
+ //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
pub mod module {
pub fn func() {
let x$0 = 2;
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs
index 82a769347..f87dcb889 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs
@@ -1,16 +1,14 @@
//! Applies structured search replace rules from the command line.
+use anyhow::Context;
use ide_ssr::MatchFinder;
+use load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice};
use project_model::{CargoConfig, RustLibSource};
-use crate::cli::{
- flags,
- load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice},
- Result,
-};
+use crate::cli::flags;
impl flags::Ssr {
- pub fn run(self) -> Result<()> {
+ pub fn run(self) -> anyhow::Result<()> {
use ide_db::base_db::SourceDatabaseExt;
let mut cargo_config = CargoConfig::default();
cargo_config.sysroot = Some(RustLibSource::Discover);
@@ -35,7 +33,8 @@ impl flags::Ssr {
if let Some(path) = vfs.file_path(file_id).as_path() {
let mut contents = db.file_text(file_id).to_string();
edit.apply(&mut contents);
- std::fs::write(path, contents)?;
+ std::fs::write(path, contents)
+ .with_context(|| format!("failed to write {path}"))?;
}
}
Ok(())
@@ -46,7 +45,7 @@ impl flags::Search {
/// Searches for `patterns`, printing debug information for any nodes whose text exactly matches
/// `debug_snippet`. This is intended for debugging and probably isn't in it's current form useful
/// for much else.
- pub fn run(self) -> Result<()> {
+ pub fn run(self) -> anyhow::Result<()> {
use ide_db::base_db::SourceDatabaseExt;
use ide_db::symbol_index::SymbolsDatabase;
let cargo_config = CargoConfig::default();
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs
index 6355c620f..fa20c796e 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs
@@ -1079,6 +1079,7 @@ impl Config {
pub fn diagnostics(&self) -> DiagnosticsConfig {
DiagnosticsConfig {
+ enabled: self.data.diagnostics_enable,
proc_attr_macros_enabled: self.expand_proc_attr_macros(),
proc_macros_enabled: self.data.procMacro_enable,
disable_experimental: !self.data.diagnostics_experimental_enable,
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/dispatch.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/dispatch.rs
index 4e57c6eb6..5e5cd9a02 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/dispatch.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/dispatch.rs
@@ -10,7 +10,7 @@ use crate::{
global_state::{GlobalState, GlobalStateSnapshot},
main_loop::Task,
version::version,
- LspError, Result,
+ LspError,
};
/// A visitor for routing a raw JSON request to an appropriate handler function.
@@ -32,13 +32,13 @@ pub(crate) struct RequestDispatcher<'a> {
pub(crate) global_state: &'a mut GlobalState,
}
-impl<'a> RequestDispatcher<'a> {
+impl RequestDispatcher<'_> {
/// Dispatches the request onto the current thread, given full access to
/// mutable global state. Unlike all other methods here, this one isn't
/// guarded by `catch_unwind`, so, please, don't make bugs :-)
pub(crate) fn on_sync_mut<R>(
&mut self,
- f: fn(&mut GlobalState, R::Params) -> Result<R::Result>,
+ f: fn(&mut GlobalState, R::Params) -> anyhow::Result<R::Result>,
) -> &mut Self
where
R: lsp_types::request::Request,
@@ -63,7 +63,7 @@ impl<'a> RequestDispatcher<'a> {
/// Dispatches the request onto the current thread.
pub(crate) fn on_sync<R>(
&mut self,
- f: fn(GlobalStateSnapshot, R::Params) -> Result<R::Result>,
+ f: fn(GlobalStateSnapshot, R::Params) -> anyhow::Result<R::Result>,
) -> &mut Self
where
R: lsp_types::request::Request,
@@ -92,7 +92,7 @@ impl<'a> RequestDispatcher<'a> {
/// without retrying it if it panics.
pub(crate) fn on_no_retry<R>(
&mut self,
- f: fn(GlobalStateSnapshot, R::Params) -> Result<R::Result>,
+ f: fn(GlobalStateSnapshot, R::Params) -> anyhow::Result<R::Result>,
) -> &mut Self
where
R: lsp_types::request::Request + 'static,
@@ -128,7 +128,7 @@ impl<'a> RequestDispatcher<'a> {
/// Dispatches a non-latency-sensitive request onto the thread pool.
pub(crate) fn on<R>(
&mut self,
- f: fn(GlobalStateSnapshot, R::Params) -> Result<R::Result>,
+ f: fn(GlobalStateSnapshot, R::Params) -> anyhow::Result<R::Result>,
) -> &mut Self
where
R: lsp_types::request::Request + 'static,
@@ -141,7 +141,7 @@ impl<'a> RequestDispatcher<'a> {
/// Dispatches a latency-sensitive request onto the thread pool.
pub(crate) fn on_latency_sensitive<R>(
&mut self,
- f: fn(GlobalStateSnapshot, R::Params) -> Result<R::Result>,
+ f: fn(GlobalStateSnapshot, R::Params) -> anyhow::Result<R::Result>,
) -> &mut Self
where
R: lsp_types::request::Request + 'static,
@@ -156,7 +156,7 @@ impl<'a> RequestDispatcher<'a> {
/// We can't run this on the main thread though as we invoke rustfmt which may take arbitrary time to complete!
pub(crate) fn on_fmt_thread<R>(
&mut self,
- f: fn(GlobalStateSnapshot, R::Params) -> Result<R::Result>,
+ f: fn(GlobalStateSnapshot, R::Params) -> anyhow::Result<R::Result>,
) -> &mut Self
where
R: lsp_types::request::Request + 'static,
@@ -181,7 +181,7 @@ impl<'a> RequestDispatcher<'a> {
fn on_with_thread_intent<const MAIN_POOL: bool, R>(
&mut self,
intent: ThreadIntent,
- f: fn(GlobalStateSnapshot, R::Params) -> Result<R::Result>,
+ f: fn(GlobalStateSnapshot, R::Params) -> anyhow::Result<R::Result>,
) -> &mut Self
where
R: lsp_types::request::Request + 'static,
@@ -245,7 +245,7 @@ impl<'a> RequestDispatcher<'a> {
fn thread_result_to_response<R>(
id: lsp_server::RequestId,
- result: thread::Result<Result<R::Result>>,
+ result: thread::Result<anyhow::Result<R::Result>>,
) -> Result<lsp_server::Response, Cancelled>
where
R: lsp_types::request::Request,
@@ -277,7 +277,7 @@ where
fn result_to_response<R>(
id: lsp_server::RequestId,
- result: Result<R::Result>,
+ result: anyhow::Result<R::Result>,
) -> Result<lsp_server::Response, Cancelled>
where
R: lsp_types::request::Request,
@@ -289,7 +289,7 @@ where
Err(e) => match e.downcast::<LspError>() {
Ok(lsp_error) => lsp_server::Response::new_err(id, lsp_error.code, lsp_error.message),
Err(e) => match e.downcast::<Cancelled>() {
- Ok(cancelled) => return Err(*cancelled),
+ Ok(cancelled) => return Err(cancelled),
Err(e) => lsp_server::Response::new_err(
id,
lsp_server::ErrorCode::InternalError as i32,
@@ -306,11 +306,11 @@ pub(crate) struct NotificationDispatcher<'a> {
pub(crate) global_state: &'a mut GlobalState,
}
-impl<'a> NotificationDispatcher<'a> {
+impl NotificationDispatcher<'_> {
pub(crate) fn on_sync_mut<N>(
&mut self,
- f: fn(&mut GlobalState, N::Params) -> Result<()>,
- ) -> Result<&mut Self>
+ f: fn(&mut GlobalState, N::Params) -> anyhow::Result<()>,
+ ) -> anyhow::Result<&mut Self>
where
N: lsp_types::notification::Notification,
N::Params: DeserializeOwned + Send,
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/from_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/from_proto.rs
index cd74a5500..c247e1bb2 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/from_proto.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/from_proto.rs
@@ -14,19 +14,21 @@ use crate::{
line_index::{LineIndex, PositionEncoding},
lsp_ext,
lsp_utils::invalid_params_error,
- Result,
};
-pub(crate) fn abs_path(url: &lsp_types::Url) -> Result<AbsPathBuf> {
- let path = url.to_file_path().map_err(|()| "url is not a file")?;
+pub(crate) fn abs_path(url: &lsp_types::Url) -> anyhow::Result<AbsPathBuf> {
+ let path = url.to_file_path().map_err(|()| anyhow::format_err!("url is not a file"))?;
Ok(AbsPathBuf::try_from(path).unwrap())
}
-pub(crate) fn vfs_path(url: &lsp_types::Url) -> Result<vfs::VfsPath> {
+pub(crate) fn vfs_path(url: &lsp_types::Url) -> anyhow::Result<vfs::VfsPath> {
abs_path(url).map(vfs::VfsPath::from)
}
-pub(crate) fn offset(line_index: &LineIndex, position: lsp_types::Position) -> Result<TextSize> {
+pub(crate) fn offset(
+ line_index: &LineIndex,
+ position: lsp_types::Position,
+) -> anyhow::Result<TextSize> {
let line_col = match line_index.encoding {
PositionEncoding::Utf8 => LineCol { line: position.line, col: position.character },
PositionEncoding::Wide(enc) => {
@@ -42,7 +44,10 @@ pub(crate) fn offset(line_index: &LineIndex, position: lsp_types::Position) -> R
Ok(text_size)
}
-pub(crate) fn text_range(line_index: &LineIndex, range: lsp_types::Range) -> Result<TextRange> {
+pub(crate) fn text_range(
+ line_index: &LineIndex,
+ range: lsp_types::Range,
+) -> anyhow::Result<TextRange> {
let start = offset(line_index, range.start)?;
let end = offset(line_index, range.end)?;
match end < start {
@@ -51,14 +56,14 @@ pub(crate) fn text_range(line_index: &LineIndex, range: lsp_types::Range) -> Res
}
}
-pub(crate) fn file_id(snap: &GlobalStateSnapshot, url: &lsp_types::Url) -> Result<FileId> {
+pub(crate) fn file_id(snap: &GlobalStateSnapshot, url: &lsp_types::Url) -> anyhow::Result<FileId> {
snap.url_to_file_id(url)
}
pub(crate) fn file_position(
snap: &GlobalStateSnapshot,
tdpp: lsp_types::TextDocumentPositionParams,
-) -> Result<FilePosition> {
+) -> anyhow::Result<FilePosition> {
let file_id = file_id(snap, &tdpp.text_document.uri)?;
let line_index = snap.file_line_index(file_id)?;
let offset = offset(&line_index, tdpp.position)?;
@@ -69,7 +74,7 @@ pub(crate) fn file_range(
snap: &GlobalStateSnapshot,
text_document_identifier: lsp_types::TextDocumentIdentifier,
range: lsp_types::Range,
-) -> Result<FileRange> {
+) -> anyhow::Result<FileRange> {
file_range_uri(snap, &text_document_identifier.uri, range)
}
@@ -77,7 +82,7 @@ pub(crate) fn file_range_uri(
snap: &GlobalStateSnapshot,
document: &lsp_types::Url,
range: lsp_types::Range,
-) -> Result<FileRange> {
+) -> anyhow::Result<FileRange> {
let file_id = file_id(snap, document)?;
let line_index = snap.file_line_index(file_id)?;
let range = text_range(&line_index, range)?;
@@ -101,7 +106,7 @@ pub(crate) fn assist_kind(kind: lsp_types::CodeActionKind) -> Option<AssistKind>
pub(crate) fn annotation(
snap: &GlobalStateSnapshot,
code_lens: lsp_types::CodeLens,
-) -> Result<Option<Annotation>> {
+) -> anyhow::Result<Option<Annotation>> {
let data =
code_lens.data.ok_or_else(|| invalid_params_error("code lens without data".to_string()))?;
let resolve = from_json::<lsp_ext::CodeLensResolveData>("CodeLensResolveData", &data)?;
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs
index d5b0e3a57..ea8a69751 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs
@@ -9,6 +9,7 @@ use crossbeam_channel::{unbounded, Receiver, Sender};
use flycheck::FlycheckHandle;
use ide::{Analysis, AnalysisHost, Cancellable, Change, FileId};
use ide_db::base_db::{CrateId, FileLoader, ProcMacroPaths, SourceDatabase};
+use load_cargo::SourceRootConfig;
use lsp_types::{SemanticTokens, Url};
use nohash_hasher::IntMap;
use parking_lot::{Mutex, RwLock};
@@ -27,10 +28,9 @@ use crate::{
main_loop::Task,
mem_docs::MemDocs,
op_queue::OpQueue,
- reload::{self, SourceRootConfig},
+ reload,
task_pool::TaskPool,
to_proto::url_from_abs_path,
- Result,
};
// Enforces drop order
@@ -319,7 +319,7 @@ impl GlobalState {
// crate see https://github.com/rust-lang/rust-analyzer/issues/13029
if let Some((path, force_crate_graph_reload)) = workspace_structure_change {
self.fetch_workspaces_queue.request_op(
- format!("workspace vfs file change: {}", path.display()),
+ format!("workspace vfs file change: {path}"),
force_crate_graph_reload,
);
}
@@ -422,7 +422,7 @@ impl Drop for GlobalState {
}
impl GlobalStateSnapshot {
- pub(crate) fn url_to_file_id(&self, url: &Url) -> Result<FileId> {
+ pub(crate) fn url_to_file_id(&self, url: &Url) -> anyhow::Result<FileId> {
url_to_file_id(&self.vfs.read().0, url)
}
@@ -481,8 +481,8 @@ pub(crate) fn file_id_to_url(vfs: &vfs::Vfs, id: FileId) -> Url {
url_from_abs_path(path)
}
-pub(crate) fn url_to_file_id(vfs: &vfs::Vfs, url: &Url) -> Result<FileId> {
+pub(crate) fn url_to_file_id(vfs: &vfs::Vfs, url: &Url) -> anyhow::Result<FileId> {
let path = from_proto::vfs_path(url)?;
- let res = vfs.file_id(&path).ok_or_else(|| format!("file not found: {path}"))?;
+ let res = vfs.file_id(&path).ok_or_else(|| anyhow::format_err!("file not found: {path}"))?;
Ok(res)
}
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs
index ae1dc2315..e830e5e9a 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs
@@ -14,10 +14,10 @@ use vfs::{AbsPathBuf, ChangeKind, VfsPath};
use crate::{
config::Config, from_proto, global_state::GlobalState, lsp_ext::RunFlycheckParams,
- lsp_utils::apply_document_changes, mem_docs::DocumentData, reload, Result,
+ lsp_utils::apply_document_changes, mem_docs::DocumentData, reload,
};
-pub(crate) fn handle_cancel(state: &mut GlobalState, params: CancelParams) -> Result<()> {
+pub(crate) fn handle_cancel(state: &mut GlobalState, params: CancelParams) -> anyhow::Result<()> {
let id: lsp_server::RequestId = match params.id {
lsp_types::NumberOrString::Number(id) => id.into(),
lsp_types::NumberOrString::String(id) => id.into(),
@@ -29,7 +29,7 @@ pub(crate) fn handle_cancel(state: &mut GlobalState, params: CancelParams) -> Re
pub(crate) fn handle_work_done_progress_cancel(
state: &mut GlobalState,
params: WorkDoneProgressCancelParams,
-) -> Result<()> {
+) -> anyhow::Result<()> {
if let lsp_types::NumberOrString::String(s) = &params.token {
if let Some(id) = s.strip_prefix("rust-analyzer/flycheck/") {
if let Ok(id) = u32::from_str_radix(id, 10) {
@@ -49,7 +49,7 @@ pub(crate) fn handle_work_done_progress_cancel(
pub(crate) fn handle_did_open_text_document(
state: &mut GlobalState,
params: DidOpenTextDocumentParams,
-) -> Result<()> {
+) -> anyhow::Result<()> {
let _p = profile::span("handle_did_open_text_document");
if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) {
@@ -68,7 +68,7 @@ pub(crate) fn handle_did_open_text_document(
pub(crate) fn handle_did_change_text_document(
state: &mut GlobalState,
params: DidChangeTextDocumentParams,
-) -> Result<()> {
+) -> anyhow::Result<()> {
let _p = profile::span("handle_did_change_text_document");
if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) {
@@ -100,7 +100,7 @@ pub(crate) fn handle_did_change_text_document(
pub(crate) fn handle_did_close_text_document(
state: &mut GlobalState,
params: DidCloseTextDocumentParams,
-) -> Result<()> {
+) -> anyhow::Result<()> {
let _p = profile::span("handle_did_close_text_document");
if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) {
@@ -120,14 +120,14 @@ pub(crate) fn handle_did_close_text_document(
pub(crate) fn handle_did_save_text_document(
state: &mut GlobalState,
params: DidSaveTextDocumentParams,
-) -> Result<()> {
+) -> anyhow::Result<()> {
if let Ok(vfs_path) = from_proto::vfs_path(&params.text_document.uri) {
// Re-fetch workspaces if a workspace related file has changed
if let Some(abs_path) = vfs_path.as_path() {
if reload::should_refresh_for_change(abs_path, ChangeKind::Modify) {
state
.fetch_workspaces_queue
- .request_op(format!("DidSaveTextDocument {}", abs_path.display()), false);
+ .request_op(format!("DidSaveTextDocument {abs_path}"), false);
}
}
@@ -146,7 +146,7 @@ pub(crate) fn handle_did_save_text_document(
pub(crate) fn handle_did_change_configuration(
state: &mut GlobalState,
_params: DidChangeConfigurationParams,
-) -> Result<()> {
+) -> anyhow::Result<()> {
// As stated in https://github.com/microsoft/language-server-protocol/issues/676,
// this notification's parameters should be ignored and the actual config queried separately.
state.send_request::<lsp_types::request::WorkspaceConfiguration>(
@@ -186,7 +186,7 @@ pub(crate) fn handle_did_change_configuration(
pub(crate) fn handle_did_change_workspace_folders(
state: &mut GlobalState,
params: DidChangeWorkspaceFoldersParams,
-) -> Result<()> {
+) -> anyhow::Result<()> {
let config = Arc::make_mut(&mut state.config);
for workspace in params.event.removed {
@@ -214,7 +214,7 @@ pub(crate) fn handle_did_change_workspace_folders(
pub(crate) fn handle_did_change_watched_files(
state: &mut GlobalState,
params: DidChangeWatchedFilesParams,
-) -> Result<()> {
+) -> anyhow::Result<()> {
for change in params.changes {
if let Ok(path) = from_proto::abs_path(&change.uri) {
state.loader.handle.invalidate(path);
@@ -302,13 +302,13 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool {
}
}
-pub(crate) fn handle_cancel_flycheck(state: &mut GlobalState, _: ()) -> Result<()> {
+pub(crate) fn handle_cancel_flycheck(state: &mut GlobalState, _: ()) -> anyhow::Result<()> {
let _p = profile::span("handle_stop_flycheck");
state.flycheck.iter().for_each(|flycheck| flycheck.cancel());
Ok(())
}
-pub(crate) fn handle_clear_flycheck(state: &mut GlobalState, _: ()) -> Result<()> {
+pub(crate) fn handle_clear_flycheck(state: &mut GlobalState, _: ()) -> anyhow::Result<()> {
let _p = profile::span("handle_clear_flycheck");
state.diagnostics.clear_check_all();
Ok(())
@@ -317,7 +317,7 @@ pub(crate) fn handle_clear_flycheck(state: &mut GlobalState, _: ()) -> Result<()
pub(crate) fn handle_run_flycheck(
state: &mut GlobalState,
params: RunFlycheckParams,
-) -> Result<()> {
+) -> anyhow::Result<()> {
let _p = profile::span("handle_run_flycheck");
if let Some(text_document) = params.text_document {
if let Ok(vfs_path) = from_proto::vfs_path(&text_document.uri) {
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs
index a6a72552d..5f1f731cf 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs
@@ -8,6 +8,7 @@ use std::{
};
use anyhow::Context;
+
use ide::{
AnnotationConfig, AssistKind, AssistResolveStrategy, Cancellable, FilePosition, FileRange,
HoverAction, HoverGotoTypeData, Query, RangeInfo, ReferenceCategory, Runnable, RunnableKind,
@@ -20,9 +21,9 @@ use lsp_types::{
CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams,
CodeLens, CompletionItem, FoldingRange, FoldingRangeParams, HoverContents, InlayHint,
InlayHintParams, Location, LocationLink, Position, PrepareRenameResponse, Range, RenameParams,
- SemanticTokensDeltaParams, SemanticTokensFullDeltaResult, SemanticTokensParams,
- SemanticTokensRangeParams, SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation,
- SymbolTag, TextDocumentIdentifier, Url, WorkspaceEdit,
+ ResourceOp, ResourceOperationKind, SemanticTokensDeltaParams, SemanticTokensFullDeltaResult,
+ SemanticTokensParams, SemanticTokensRangeParams, SemanticTokensRangeResult,
+ SemanticTokensResult, SymbolInformation, SymbolTag, TextDocumentIdentifier, Url, WorkspaceEdit,
};
use project_model::{ManifestPath, ProjectWorkspace, TargetKind};
use serde_json::json;
@@ -33,7 +34,7 @@ use vfs::{AbsPath, AbsPathBuf, VfsPath};
use crate::{
cargo_target_spec::CargoTargetSpec,
- config::{RustfmtConfig, WorkspaceSymbolConfig},
+ config::{Config, RustfmtConfig, WorkspaceSymbolConfig},
diff::diff,
from_proto,
global_state::{GlobalState, GlobalStateSnapshot},
@@ -43,10 +44,10 @@ use crate::{
FetchDependencyListResult, PositionOrRange, ViewCrateGraphParams, WorkspaceSymbolParams,
},
lsp_utils::{all_edits_are_disjoint, invalid_params_error},
- to_proto, LspError, Result,
+ to_proto, LspError,
};
-pub(crate) fn handle_workspace_reload(state: &mut GlobalState, _: ()) -> Result<()> {
+pub(crate) fn handle_workspace_reload(state: &mut GlobalState, _: ()) -> anyhow::Result<()> {
// FIXME: use `Arc::from_iter` when it becomes available
state.proc_macro_clients = Arc::from(Vec::new());
state.proc_macro_changed = false;
@@ -55,7 +56,7 @@ pub(crate) fn handle_workspace_reload(state: &mut GlobalState, _: ()) -> Result<
Ok(())
}
-pub(crate) fn handle_proc_macros_rebuild(state: &mut GlobalState, _: ()) -> Result<()> {
+pub(crate) fn handle_proc_macros_rebuild(state: &mut GlobalState, _: ()) -> anyhow::Result<()> {
// FIXME: use `Arc::from_iter` when it becomes available
state.proc_macro_clients = Arc::from(Vec::new());
state.proc_macro_changed = false;
@@ -67,7 +68,7 @@ pub(crate) fn handle_proc_macros_rebuild(state: &mut GlobalState, _: ()) -> Resu
pub(crate) fn handle_analyzer_status(
snap: GlobalStateSnapshot,
params: lsp_ext::AnalyzerStatusParams,
-) -> Result<String> {
+) -> anyhow::Result<String> {
let _p = profile::span("handle_analyzer_status");
let mut buf = String::new();
@@ -112,7 +113,7 @@ pub(crate) fn handle_analyzer_status(
Ok(buf)
}
-pub(crate) fn handle_memory_usage(state: &mut GlobalState, _: ()) -> Result<String> {
+pub(crate) fn handle_memory_usage(state: &mut GlobalState, _: ()) -> anyhow::Result<String> {
let _p = profile::span("handle_memory_usage");
let mem = state.analysis_host.per_query_memory_usage();
@@ -125,7 +126,7 @@ pub(crate) fn handle_memory_usage(state: &mut GlobalState, _: ()) -> Result<Stri
Ok(out)
}
-pub(crate) fn handle_shuffle_crate_graph(state: &mut GlobalState, _: ()) -> Result<()> {
+pub(crate) fn handle_shuffle_crate_graph(state: &mut GlobalState, _: ()) -> anyhow::Result<()> {
state.analysis_host.shuffle_crate_graph();
Ok(())
}
@@ -133,7 +134,7 @@ pub(crate) fn handle_shuffle_crate_graph(state: &mut GlobalState, _: ()) -> Resu
pub(crate) fn handle_syntax_tree(
snap: GlobalStateSnapshot,
params: lsp_ext::SyntaxTreeParams,
-) -> Result<String> {
+) -> anyhow::Result<String> {
let _p = profile::span("handle_syntax_tree");
let id = from_proto::file_id(&snap, &params.text_document.uri)?;
let line_index = snap.file_line_index(id)?;
@@ -145,7 +146,7 @@ pub(crate) fn handle_syntax_tree(
pub(crate) fn handle_view_hir(
snap: GlobalStateSnapshot,
params: lsp_types::TextDocumentPositionParams,
-) -> Result<String> {
+) -> anyhow::Result<String> {
let _p = profile::span("handle_view_hir");
let position = from_proto::file_position(&snap, params)?;
let res = snap.analysis.view_hir(position)?;
@@ -155,7 +156,7 @@ pub(crate) fn handle_view_hir(
pub(crate) fn handle_view_mir(
snap: GlobalStateSnapshot,
params: lsp_types::TextDocumentPositionParams,
-) -> Result<String> {
+) -> anyhow::Result<String> {
let _p = profile::span("handle_view_mir");
let position = from_proto::file_position(&snap, params)?;
let res = snap.analysis.view_mir(position)?;
@@ -165,7 +166,7 @@ pub(crate) fn handle_view_mir(
pub(crate) fn handle_interpret_function(
snap: GlobalStateSnapshot,
params: lsp_types::TextDocumentPositionParams,
-) -> Result<String> {
+) -> anyhow::Result<String> {
let _p = profile::span("handle_interpret_function");
let position = from_proto::file_position(&snap, params)?;
let res = snap.analysis.interpret_function(position)?;
@@ -175,7 +176,7 @@ pub(crate) fn handle_interpret_function(
pub(crate) fn handle_view_file_text(
snap: GlobalStateSnapshot,
params: lsp_types::TextDocumentIdentifier,
-) -> Result<String> {
+) -> anyhow::Result<String> {
let file_id = from_proto::file_id(&snap, &params.uri)?;
Ok(snap.analysis.file_text(file_id)?.to_string())
}
@@ -183,7 +184,7 @@ pub(crate) fn handle_view_file_text(
pub(crate) fn handle_view_item_tree(
snap: GlobalStateSnapshot,
params: lsp_ext::ViewItemTreeParams,
-) -> Result<String> {
+) -> anyhow::Result<String> {
let _p = profile::span("handle_view_item_tree");
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let res = snap.analysis.view_item_tree(file_id)?;
@@ -193,16 +194,16 @@ pub(crate) fn handle_view_item_tree(
pub(crate) fn handle_view_crate_graph(
snap: GlobalStateSnapshot,
params: ViewCrateGraphParams,
-) -> Result<String> {
+) -> anyhow::Result<String> {
let _p = profile::span("handle_view_crate_graph");
- let dot = snap.analysis.view_crate_graph(params.full)??;
+ let dot = snap.analysis.view_crate_graph(params.full)?.map_err(anyhow::Error::msg)?;
Ok(dot)
}
pub(crate) fn handle_expand_macro(
snap: GlobalStateSnapshot,
params: lsp_ext::ExpandMacroParams,
-) -> Result<Option<lsp_ext::ExpandedMacro>> {
+) -> anyhow::Result<Option<lsp_ext::ExpandedMacro>> {
let _p = profile::span("handle_expand_macro");
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let line_index = snap.file_line_index(file_id)?;
@@ -215,11 +216,11 @@ pub(crate) fn handle_expand_macro(
pub(crate) fn handle_selection_range(
snap: GlobalStateSnapshot,
params: lsp_types::SelectionRangeParams,
-) -> Result<Option<Vec<lsp_types::SelectionRange>>> {
+) -> anyhow::Result<Option<Vec<lsp_types::SelectionRange>>> {
let _p = profile::span("handle_selection_range");
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let line_index = snap.file_line_index(file_id)?;
- let res: Result<Vec<lsp_types::SelectionRange>> = params
+ let res: anyhow::Result<Vec<lsp_types::SelectionRange>> = params
.positions
.into_iter()
.map(|position| {
@@ -258,7 +259,7 @@ pub(crate) fn handle_selection_range(
pub(crate) fn handle_matching_brace(
snap: GlobalStateSnapshot,
params: lsp_ext::MatchingBraceParams,
-) -> Result<Vec<Position>> {
+) -> anyhow::Result<Vec<Position>> {
let _p = profile::span("handle_matching_brace");
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let line_index = snap.file_line_index(file_id)?;
@@ -281,7 +282,7 @@ pub(crate) fn handle_matching_brace(
pub(crate) fn handle_join_lines(
snap: GlobalStateSnapshot,
params: lsp_ext::JoinLinesParams,
-) -> Result<Vec<lsp_types::TextEdit>> {
+) -> anyhow::Result<Vec<lsp_types::TextEdit>> {
let _p = profile::span("handle_join_lines");
let config = snap.config.join_lines();
@@ -306,7 +307,7 @@ pub(crate) fn handle_join_lines(
pub(crate) fn handle_on_enter(
snap: GlobalStateSnapshot,
params: lsp_types::TextDocumentPositionParams,
-) -> Result<Option<Vec<lsp_ext::SnippetTextEdit>>> {
+) -> anyhow::Result<Option<Vec<lsp_ext::SnippetTextEdit>>> {
let _p = profile::span("handle_on_enter");
let position = from_proto::file_position(&snap, params)?;
let edit = match snap.analysis.on_enter(position)? {
@@ -321,7 +322,7 @@ pub(crate) fn handle_on_enter(
pub(crate) fn handle_on_type_formatting(
snap: GlobalStateSnapshot,
params: lsp_types::DocumentOnTypeFormattingParams,
-) -> Result<Option<Vec<lsp_ext::SnippetTextEdit>>> {
+) -> anyhow::Result<Option<Vec<lsp_ext::SnippetTextEdit>>> {
let _p = profile::span("handle_on_type_formatting");
let mut position = from_proto::file_position(&snap, params.text_document_position)?;
let line_index = snap.file_line_index(position.file_id)?;
@@ -352,7 +353,8 @@ pub(crate) fn handle_on_type_formatting(
};
// This should be a single-file edit
- let (_, text_edit) = edit.source_file_edits.into_iter().next().unwrap();
+ let (_, (text_edit, snippet_edit)) = edit.source_file_edits.into_iter().next().unwrap();
+ stdx::never!(snippet_edit.is_none(), "on type formatting shouldn't use structured snippets");
let change = to_proto::snippet_text_edit_vec(&line_index, edit.is_snippet, text_edit);
Ok(Some(change))
@@ -361,7 +363,7 @@ pub(crate) fn handle_on_type_formatting(
pub(crate) fn handle_document_symbol(
snap: GlobalStateSnapshot,
params: lsp_types::DocumentSymbolParams,
-) -> Result<Option<lsp_types::DocumentSymbolResponse>> {
+) -> anyhow::Result<Option<lsp_types::DocumentSymbolResponse>> {
let _p = profile::span("handle_document_symbol");
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let line_index = snap.file_line_index(file_id)?;
@@ -450,7 +452,7 @@ pub(crate) fn handle_document_symbol(
pub(crate) fn handle_workspace_symbol(
snap: GlobalStateSnapshot,
params: WorkspaceSymbolParams,
-) -> Result<Option<Vec<SymbolInformation>>> {
+) -> anyhow::Result<Option<Vec<SymbolInformation>>> {
let _p = profile::span("handle_workspace_symbol");
let config = snap.config.workspace_symbol();
@@ -513,7 +515,10 @@ pub(crate) fn handle_workspace_symbol(
(all_symbols, libs)
}
- fn exec_query(snap: &GlobalStateSnapshot, query: Query) -> Result<Vec<SymbolInformation>> {
+ fn exec_query(
+ snap: &GlobalStateSnapshot,
+ query: Query,
+ ) -> anyhow::Result<Vec<SymbolInformation>> {
let mut res = Vec::new();
for nav in snap.analysis.symbol_search(query)? {
let container_name = nav.container_name.as_ref().map(|v| v.to_string());
@@ -542,7 +547,7 @@ pub(crate) fn handle_workspace_symbol(
pub(crate) fn handle_will_rename_files(
snap: GlobalStateSnapshot,
params: lsp_types::RenameFilesParams,
-) -> Result<Option<lsp_types::WorkspaceEdit>> {
+) -> anyhow::Result<Option<lsp_types::WorkspaceEdit>> {
let _p = profile::span("handle_will_rename_files");
let source_changes: Vec<SourceChange> = params
@@ -604,7 +609,7 @@ pub(crate) fn handle_will_rename_files(
pub(crate) fn handle_goto_definition(
snap: GlobalStateSnapshot,
params: lsp_types::GotoDefinitionParams,
-) -> Result<Option<lsp_types::GotoDefinitionResponse>> {
+) -> anyhow::Result<Option<lsp_types::GotoDefinitionResponse>> {
let _p = profile::span("handle_goto_definition");
let position = from_proto::file_position(&snap, params.text_document_position_params)?;
let nav_info = match snap.analysis.goto_definition(position)? {
@@ -619,7 +624,7 @@ pub(crate) fn handle_goto_definition(
pub(crate) fn handle_goto_declaration(
snap: GlobalStateSnapshot,
params: lsp_types::request::GotoDeclarationParams,
-) -> Result<Option<lsp_types::request::GotoDeclarationResponse>> {
+) -> anyhow::Result<Option<lsp_types::request::GotoDeclarationResponse>> {
let _p = profile::span("handle_goto_declaration");
let position = from_proto::file_position(&snap, params.text_document_position_params.clone())?;
let nav_info = match snap.analysis.goto_declaration(position)? {
@@ -634,7 +639,7 @@ pub(crate) fn handle_goto_declaration(
pub(crate) fn handle_goto_implementation(
snap: GlobalStateSnapshot,
params: lsp_types::request::GotoImplementationParams,
-) -> Result<Option<lsp_types::request::GotoImplementationResponse>> {
+) -> anyhow::Result<Option<lsp_types::request::GotoImplementationResponse>> {
let _p = profile::span("handle_goto_implementation");
let position = from_proto::file_position(&snap, params.text_document_position_params)?;
let nav_info = match snap.analysis.goto_implementation(position)? {
@@ -649,7 +654,7 @@ pub(crate) fn handle_goto_implementation(
pub(crate) fn handle_goto_type_definition(
snap: GlobalStateSnapshot,
params: lsp_types::request::GotoTypeDefinitionParams,
-) -> Result<Option<lsp_types::request::GotoTypeDefinitionResponse>> {
+) -> anyhow::Result<Option<lsp_types::request::GotoTypeDefinitionResponse>> {
let _p = profile::span("handle_goto_type_definition");
let position = from_proto::file_position(&snap, params.text_document_position_params)?;
let nav_info = match snap.analysis.goto_type_definition(position)? {
@@ -664,7 +669,7 @@ pub(crate) fn handle_goto_type_definition(
pub(crate) fn handle_parent_module(
snap: GlobalStateSnapshot,
params: lsp_types::TextDocumentPositionParams,
-) -> Result<Option<lsp_types::GotoDefinitionResponse>> {
+) -> anyhow::Result<Option<lsp_types::GotoDefinitionResponse>> {
let _p = profile::span("handle_parent_module");
if let Ok(file_path) = &params.text_document.uri.to_file_path() {
if file_path.file_name().unwrap_or_default() == "Cargo.toml" {
@@ -731,7 +736,7 @@ pub(crate) fn handle_parent_module(
pub(crate) fn handle_runnables(
snap: GlobalStateSnapshot,
params: lsp_ext::RunnablesParams,
-) -> Result<Vec<lsp_ext::Runnable>> {
+) -> anyhow::Result<Vec<lsp_ext::Runnable>> {
let _p = profile::span("handle_runnables");
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let line_index = snap.file_line_index(file_id)?;
@@ -826,7 +831,7 @@ fn should_skip_for_offset(runnable: &Runnable, offset: Option<TextSize>) -> bool
pub(crate) fn handle_related_tests(
snap: GlobalStateSnapshot,
params: lsp_types::TextDocumentPositionParams,
-) -> Result<Vec<lsp_ext::TestInfo>> {
+) -> anyhow::Result<Vec<lsp_ext::TestInfo>> {
let _p = profile::span("handle_related_tests");
let position = from_proto::file_position(&snap, params)?;
@@ -844,7 +849,7 @@ pub(crate) fn handle_related_tests(
pub(crate) fn handle_completion(
snap: GlobalStateSnapshot,
params: lsp_types::CompletionParams,
-) -> Result<Option<lsp_types::CompletionResponse>> {
+) -> anyhow::Result<Option<lsp_types::CompletionResponse>> {
let _p = profile::span("handle_completion");
let text_document_position = params.text_document_position.clone();
let position = from_proto::file_position(&snap, params.text_document_position)?;
@@ -872,7 +877,7 @@ pub(crate) fn handle_completion(
pub(crate) fn handle_completion_resolve(
snap: GlobalStateSnapshot,
mut original_completion: CompletionItem,
-) -> Result<CompletionItem> {
+) -> anyhow::Result<CompletionItem> {
let _p = profile::span("handle_completion_resolve");
if !all_edits_are_disjoint(&original_completion, &[]) {
@@ -928,7 +933,7 @@ pub(crate) fn handle_completion_resolve(
pub(crate) fn handle_folding_range(
snap: GlobalStateSnapshot,
params: FoldingRangeParams,
-) -> Result<Option<Vec<FoldingRange>>> {
+) -> anyhow::Result<Option<Vec<FoldingRange>>> {
let _p = profile::span("handle_folding_range");
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let folds = snap.analysis.folding_ranges(file_id)?;
@@ -945,7 +950,7 @@ pub(crate) fn handle_folding_range(
pub(crate) fn handle_signature_help(
snap: GlobalStateSnapshot,
params: lsp_types::SignatureHelpParams,
-) -> Result<Option<lsp_types::SignatureHelp>> {
+) -> anyhow::Result<Option<lsp_types::SignatureHelp>> {
let _p = profile::span("handle_signature_help");
let position = from_proto::file_position(&snap, params.text_document_position_params)?;
let help = match snap.analysis.signature_help(position)? {
@@ -960,7 +965,7 @@ pub(crate) fn handle_signature_help(
pub(crate) fn handle_hover(
snap: GlobalStateSnapshot,
params: lsp_ext::HoverParams,
-) -> Result<Option<lsp_ext::Hover>> {
+) -> anyhow::Result<Option<lsp_ext::Hover>> {
let _p = profile::span("handle_hover");
let range = match params.position {
PositionOrRange::Position(position) => Range::new(position, position),
@@ -997,7 +1002,7 @@ pub(crate) fn handle_hover(
pub(crate) fn handle_prepare_rename(
snap: GlobalStateSnapshot,
params: lsp_types::TextDocumentPositionParams,
-) -> Result<Option<PrepareRenameResponse>> {
+) -> anyhow::Result<Option<PrepareRenameResponse>> {
let _p = profile::span("handle_prepare_rename");
let position = from_proto::file_position(&snap, params)?;
@@ -1011,7 +1016,7 @@ pub(crate) fn handle_prepare_rename(
pub(crate) fn handle_rename(
snap: GlobalStateSnapshot,
params: RenameParams,
-) -> Result<Option<WorkspaceEdit>> {
+) -> anyhow::Result<Option<WorkspaceEdit>> {
let _p = profile::span("handle_rename");
let position = from_proto::file_position(&snap, params.text_document_position)?;
@@ -1027,14 +1032,30 @@ pub(crate) fn handle_rename(
if !change.file_system_edits.is_empty() && snap.config.will_rename() {
change.source_file_edits.clear();
}
+
let workspace_edit = to_proto::workspace_edit(&snap, change)?;
+
+ if let Some(lsp_types::DocumentChanges::Operations(ops)) =
+ workspace_edit.document_changes.as_ref()
+ {
+ for op in ops {
+ if let lsp_types::DocumentChangeOperation::Op(doc_change_op) = op {
+ if let Err(err) =
+ resource_ops_supported(&snap.config, resolve_resource_op(doc_change_op))
+ {
+ return Err(err);
+ }
+ }
+ }
+ }
+
Ok(Some(workspace_edit))
}
pub(crate) fn handle_references(
snap: GlobalStateSnapshot,
params: lsp_types::ReferenceParams,
-) -> Result<Option<Vec<Location>>> {
+) -> anyhow::Result<Option<Vec<Location>>> {
let _p = profile::span("handle_references");
let position = from_proto::file_position(&snap, params.text_document_position)?;
@@ -1077,7 +1098,7 @@ pub(crate) fn handle_references(
pub(crate) fn handle_formatting(
snap: GlobalStateSnapshot,
params: lsp_types::DocumentFormattingParams,
-) -> Result<Option<Vec<lsp_types::TextEdit>>> {
+) -> anyhow::Result<Option<Vec<lsp_types::TextEdit>>> {
let _p = profile::span("handle_formatting");
run_rustfmt(&snap, params.text_document, None)
@@ -1086,7 +1107,7 @@ pub(crate) fn handle_formatting(
pub(crate) fn handle_range_formatting(
snap: GlobalStateSnapshot,
params: lsp_types::DocumentRangeFormattingParams,
-) -> Result<Option<Vec<lsp_types::TextEdit>>> {
+) -> anyhow::Result<Option<Vec<lsp_types::TextEdit>>> {
let _p = profile::span("handle_range_formatting");
run_rustfmt(&snap, params.text_document, Some(params.range))
@@ -1095,7 +1116,7 @@ pub(crate) fn handle_range_formatting(
pub(crate) fn handle_code_action(
snap: GlobalStateSnapshot,
params: lsp_types::CodeActionParams,
-) -> Result<Option<Vec<lsp_ext::CodeAction>>> {
+) -> anyhow::Result<Option<Vec<lsp_ext::CodeAction>>> {
let _p = profile::span("handle_code_action");
if !snap.config.code_action_literals() {
@@ -1134,6 +1155,21 @@ pub(crate) fn handle_code_action(
let resolve_data =
if code_action_resolve_cap { Some((index, params.clone())) } else { None };
let code_action = to_proto::code_action(&snap, assist, resolve_data)?;
+
+ // Check if the client supports the necessary `ResourceOperation`s.
+ let changes = code_action.edit.as_ref().and_then(|it| it.document_changes.as_ref());
+ if let Some(changes) = changes {
+ for change in changes {
+ if let lsp_ext::SnippetDocumentChangeOperation::Op(res_op) = change {
+ if let Err(err) =
+ resource_ops_supported(&snap.config, resolve_resource_op(res_op))
+ {
+ return Err(err);
+ }
+ }
+ }
+ }
+
res.push(code_action)
}
@@ -1158,7 +1194,7 @@ pub(crate) fn handle_code_action(
pub(crate) fn handle_code_action_resolve(
snap: GlobalStateSnapshot,
mut code_action: lsp_ext::CodeAction,
-) -> Result<lsp_ext::CodeAction> {
+) -> anyhow::Result<lsp_ext::CodeAction> {
let _p = profile::span("handle_code_action_resolve");
let params = match code_action.data.take() {
Some(it) => it,
@@ -1216,10 +1252,25 @@ pub(crate) fn handle_code_action_resolve(
let ca = to_proto::code_action(&snap, assist.clone(), None)?;
code_action.edit = ca.edit;
code_action.command = ca.command;
+
+ if let Some(edit) = code_action.edit.as_ref() {
+ if let Some(changes) = edit.document_changes.as_ref() {
+ for change in changes {
+ if let lsp_ext::SnippetDocumentChangeOperation::Op(res_op) = change {
+ if let Err(err) =
+ resource_ops_supported(&snap.config, resolve_resource_op(res_op))
+ {
+ return Err(err);
+ }
+ }
+ }
+ }
+ }
+
Ok(code_action)
}
-fn parse_action_id(action_id: &str) -> Result<(usize, SingleResolve), String> {
+fn parse_action_id(action_id: &str) -> anyhow::Result<(usize, SingleResolve), String> {
let id_parts = action_id.split(':').collect::<Vec<_>>();
match id_parts.as_slice() {
[assist_id_string, assist_kind_string, index_string] => {
@@ -1237,7 +1288,7 @@ fn parse_action_id(action_id: &str) -> Result<(usize, SingleResolve), String> {
pub(crate) fn handle_code_lens(
snap: GlobalStateSnapshot,
params: lsp_types::CodeLensParams,
-) -> Result<Option<Vec<CodeLens>>> {
+) -> anyhow::Result<Option<Vec<CodeLens>>> {
let _p = profile::span("handle_code_lens");
let lens_config = snap.config.lens();
@@ -1280,8 +1331,10 @@ pub(crate) fn handle_code_lens(
pub(crate) fn handle_code_lens_resolve(
snap: GlobalStateSnapshot,
code_lens: CodeLens,
-) -> Result<CodeLens> {
- let Some(annotation) = from_proto::annotation(&snap, code_lens.clone())? else { return Ok(code_lens) };
+) -> anyhow::Result<CodeLens> {
+ let Some(annotation) = from_proto::annotation(&snap, code_lens.clone())? else {
+ return Ok(code_lens);
+ };
let annotation = snap.analysis.resolve_annotation(annotation)?;
let mut acc = Vec::new();
@@ -1301,7 +1354,7 @@ pub(crate) fn handle_code_lens_resolve(
pub(crate) fn handle_document_highlight(
snap: GlobalStateSnapshot,
params: lsp_types::DocumentHighlightParams,
-) -> Result<Option<Vec<lsp_types::DocumentHighlight>>> {
+) -> anyhow::Result<Option<Vec<lsp_types::DocumentHighlight>>> {
let _p = profile::span("handle_document_highlight");
let position = from_proto::file_position(&snap, params.text_document_position_params)?;
let line_index = snap.file_line_index(position.file_id)?;
@@ -1323,7 +1376,7 @@ pub(crate) fn handle_document_highlight(
pub(crate) fn handle_ssr(
snap: GlobalStateSnapshot,
params: lsp_ext::SsrParams,
-) -> Result<lsp_types::WorkspaceEdit> {
+) -> anyhow::Result<lsp_types::WorkspaceEdit> {
let _p = profile::span("handle_ssr");
let selections = params
.selections
@@ -1343,7 +1396,7 @@ pub(crate) fn handle_ssr(
pub(crate) fn handle_inlay_hints(
snap: GlobalStateSnapshot,
params: InlayHintParams,
-) -> Result<Option<Vec<InlayHint>>> {
+) -> anyhow::Result<Option<Vec<InlayHint>>> {
let _p = profile::span("handle_inlay_hints");
let document_uri = &params.text_document.uri;
let FileRange { file_id, range } = from_proto::file_range(
@@ -1365,7 +1418,7 @@ pub(crate) fn handle_inlay_hints(
pub(crate) fn handle_inlay_hints_resolve(
_snap: GlobalStateSnapshot,
hint: InlayHint,
-) -> Result<InlayHint> {
+) -> anyhow::Result<InlayHint> {
let _p = profile::span("handle_inlay_hints_resolve");
Ok(hint)
}
@@ -1373,7 +1426,7 @@ pub(crate) fn handle_inlay_hints_resolve(
pub(crate) fn handle_call_hierarchy_prepare(
snap: GlobalStateSnapshot,
params: CallHierarchyPrepareParams,
-) -> Result<Option<Vec<CallHierarchyItem>>> {
+) -> anyhow::Result<Option<Vec<CallHierarchyItem>>> {
let _p = profile::span("handle_call_hierarchy_prepare");
let position = from_proto::file_position(&snap, params.text_document_position_params)?;
@@ -1395,7 +1448,7 @@ pub(crate) fn handle_call_hierarchy_prepare(
pub(crate) fn handle_call_hierarchy_incoming(
snap: GlobalStateSnapshot,
params: CallHierarchyIncomingCallsParams,
-) -> Result<Option<Vec<CallHierarchyIncomingCall>>> {
+) -> anyhow::Result<Option<Vec<CallHierarchyIncomingCall>>> {
let _p = profile::span("handle_call_hierarchy_incoming");
let item = params.item;
@@ -1430,7 +1483,7 @@ pub(crate) fn handle_call_hierarchy_incoming(
pub(crate) fn handle_call_hierarchy_outgoing(
snap: GlobalStateSnapshot,
params: CallHierarchyOutgoingCallsParams,
-) -> Result<Option<Vec<CallHierarchyOutgoingCall>>> {
+) -> anyhow::Result<Option<Vec<CallHierarchyOutgoingCall>>> {
let _p = profile::span("handle_call_hierarchy_outgoing");
let item = params.item;
@@ -1465,7 +1518,7 @@ pub(crate) fn handle_call_hierarchy_outgoing(
pub(crate) fn handle_semantic_tokens_full(
snap: GlobalStateSnapshot,
params: SemanticTokensParams,
-) -> Result<Option<SemanticTokensResult>> {
+) -> anyhow::Result<Option<SemanticTokensResult>> {
let _p = profile::span("handle_semantic_tokens_full");
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
@@ -1495,7 +1548,7 @@ pub(crate) fn handle_semantic_tokens_full(
pub(crate) fn handle_semantic_tokens_full_delta(
snap: GlobalStateSnapshot,
params: SemanticTokensDeltaParams,
-) -> Result<Option<SemanticTokensFullDeltaResult>> {
+) -> anyhow::Result<Option<SemanticTokensFullDeltaResult>> {
let _p = profile::span("handle_semantic_tokens_full_delta");
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
@@ -1535,7 +1588,7 @@ pub(crate) fn handle_semantic_tokens_full_delta(
pub(crate) fn handle_semantic_tokens_range(
snap: GlobalStateSnapshot,
params: SemanticTokensRangeParams,
-) -> Result<Option<SemanticTokensRangeResult>> {
+) -> anyhow::Result<Option<SemanticTokensRangeResult>> {
let _p = profile::span("handle_semantic_tokens_range");
let frange = from_proto::file_range(&snap, params.text_document, params.range)?;
@@ -1561,7 +1614,7 @@ pub(crate) fn handle_semantic_tokens_range(
pub(crate) fn handle_open_docs(
snap: GlobalStateSnapshot,
params: lsp_types::TextDocumentPositionParams,
-) -> Result<ExternalDocsResponse> {
+) -> anyhow::Result<ExternalDocsResponse> {
let _p = profile::span("handle_open_docs");
let position = from_proto::file_position(&snap, params)?;
@@ -1582,9 +1635,9 @@ pub(crate) fn handle_open_docs(
let Ok(remote_urls) = snap.analysis.external_docs(position, target_dir, sysroot) else {
return if snap.config.local_docs() {
Ok(ExternalDocsResponse::WithLocal(Default::default()))
- } else {
+ } else {
Ok(ExternalDocsResponse::Simple(None))
- }
+ };
};
let web = remote_urls.web_url.and_then(|it| Url::parse(&it).ok());
@@ -1600,7 +1653,7 @@ pub(crate) fn handle_open_docs(
pub(crate) fn handle_open_cargo_toml(
snap: GlobalStateSnapshot,
params: lsp_ext::OpenCargoTomlParams,
-) -> Result<Option<lsp_types::GotoDefinitionResponse>> {
+) -> anyhow::Result<Option<lsp_types::GotoDefinitionResponse>> {
let _p = profile::span("handle_open_cargo_toml");
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
@@ -1618,7 +1671,7 @@ pub(crate) fn handle_open_cargo_toml(
pub(crate) fn handle_move_item(
snap: GlobalStateSnapshot,
params: lsp_ext::MoveItemParams,
-) -> Result<Vec<lsp_ext::SnippetTextEdit>> {
+) -> anyhow::Result<Vec<lsp_ext::SnippetTextEdit>> {
let _p = profile::span("handle_move_item");
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let range = from_proto::file_range(&snap, params.text_document, params.range)?;
@@ -1637,6 +1690,34 @@ pub(crate) fn handle_move_item(
}
}
+pub(crate) fn handle_view_recursive_memory_layout(
+ snap: GlobalStateSnapshot,
+ params: lsp_types::TextDocumentPositionParams,
+) -> anyhow::Result<Option<lsp_ext::RecursiveMemoryLayout>> {
+ let _p = profile::span("view_recursive_memory_layout");
+ let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
+ let line_index = snap.file_line_index(file_id)?;
+ let offset = from_proto::offset(&line_index, params.position)?;
+
+ let res = snap.analysis.get_recursive_memory_layout(FilePosition { file_id, offset })?;
+ Ok(res.map(|it| lsp_ext::RecursiveMemoryLayout {
+ nodes: it
+ .nodes
+ .iter()
+ .map(|n| lsp_ext::MemoryLayoutNode {
+ item_name: n.item_name.clone(),
+ typename: n.typename.clone(),
+ size: n.size,
+ offset: n.offset,
+ alignment: n.alignment,
+ parent_idx: n.parent_idx,
+ children_start: n.children_start,
+ children_len: n.children_len,
+ })
+ .collect(),
+ }))
+}
+
fn to_command_link(command: lsp_types::Command, tooltip: String) -> lsp_ext::CommandLink {
lsp_ext::CommandLink { tooltip: Some(tooltip), command }
}
@@ -1792,7 +1873,7 @@ fn run_rustfmt(
snap: &GlobalStateSnapshot,
text_document: TextDocumentIdentifier,
range: Option<lsp_types::Range>,
-) -> Result<Option<Vec<lsp_types::TextEdit>>> {
+) -> anyhow::Result<Option<Vec<lsp_types::TextEdit>>> {
let file_id = from_proto::file_id(snap, &text_document.uri)?;
let file = snap.analysis.file_text(file_id)?;
@@ -1942,7 +2023,7 @@ fn run_rustfmt(
pub(crate) fn fetch_dependency_list(
state: GlobalStateSnapshot,
_params: FetchDependencyListParams,
-) -> Result<FetchDependencyListResult> {
+) -> anyhow::Result<FetchDependencyListResult> {
let crates = state.analysis.fetch_crates()?;
let crate_infos = crates
.into_iter()
@@ -1987,3 +2068,42 @@ fn to_url(path: VfsPath) -> Option<Url> {
let str_path = path.as_os_str().to_str()?;
Url::from_file_path(str_path).ok()
}
+
+fn resource_ops_supported(config: &Config, kind: ResourceOperationKind) -> anyhow::Result<()> {
+ #[rustfmt::skip]
+ let resops = (|| {
+ config
+ .caps()
+ .workspace
+ .as_ref()?
+ .workspace_edit
+ .as_ref()?
+ .resource_operations
+ .as_ref()
+ })();
+
+ if !matches!(resops, Some(resops) if resops.contains(&kind)) {
+ return Err(LspError::new(
+ ErrorCode::RequestFailed as i32,
+ format!(
+ "Client does not support {} capability.",
+ match kind {
+ ResourceOperationKind::Create => "create",
+ ResourceOperationKind::Rename => "rename",
+ ResourceOperationKind::Delete => "delete",
+ }
+ ),
+ )
+ .into());
+ }
+
+ Ok(())
+}
+
+fn resolve_resource_op(op: &ResourceOp) -> ResourceOperationKind {
+ match op {
+ ResourceOp::Create(_) => ResourceOperationKind::Create,
+ ResourceOp::Rename(_) => ResourceOperationKind::Rename,
+ ResourceOp::Delete(_) => ResourceOperationKind::Delete,
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs
index bd9f471a4..5a11012b9 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs
@@ -20,7 +20,7 @@ use test_utils::project_root;
use triomphe::Arc;
use vfs::{AbsPathBuf, VfsPath};
-use crate::cli::load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice};
+use load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice};
#[test]
fn integrated_highlighting_benchmark() {
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs
index 65de4366e..57e26c241 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs
@@ -54,13 +54,12 @@ use serde::de::DeserializeOwned;
pub use crate::{caps::server_capabilities, main_loop::main_loop, version::version};
-pub type Error = Box<dyn std::error::Error + Send + Sync>;
-pub type Result<T, E = Error> = std::result::Result<T, E>;
-
-pub fn from_json<T: DeserializeOwned>(what: &'static str, json: &serde_json::Value) -> Result<T> {
- let res = serde_json::from_value(json.clone())
- .map_err(|e| format!("Failed to deserialize {what}: {e}; {json}"))?;
- Ok(res)
+pub fn from_json<T: DeserializeOwned>(
+ what: &'static str,
+ json: &serde_json::Value,
+) -> anyhow::Result<T> {
+ serde_json::from_value(json.clone())
+ .map_err(|e| anyhow::format_err!("Failed to deserialize {what}: {e}; {json}"))
}
#[derive(Debug)]
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp_ext.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp_ext.rs
index 4d67c8b30..d0989b323 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp_ext.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp_ext.rs
@@ -182,6 +182,33 @@ pub struct ExpandedMacro {
pub expansion: String,
}
+pub enum ViewRecursiveMemoryLayout {}
+
+impl Request for ViewRecursiveMemoryLayout {
+ type Params = lsp_types::TextDocumentPositionParams;
+ type Result = Option<RecursiveMemoryLayout>;
+ const METHOD: &'static str = "rust-analyzer/viewRecursiveMemoryLayout";
+}
+
+#[derive(Deserialize, Serialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct RecursiveMemoryLayout {
+ pub nodes: Vec<MemoryLayoutNode>,
+}
+
+#[derive(Deserialize, Serialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct MemoryLayoutNode {
+ pub item_name: String,
+ pub typename: String,
+ pub size: u64,
+ pub offset: u64,
+ pub alignment: u64,
+ pub parent_idx: i64,
+ pub children_start: i64,
+ pub children_len: u64,
+}
+
pub enum CancelFlycheck {}
impl Notification for CancelFlycheck {
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs
index 02dd94e5f..74036710f 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs
@@ -23,10 +23,9 @@ use crate::{
lsp_ext,
lsp_utils::{notification_is, Progress},
reload::{BuildDataProgress, ProcMacroProgress, ProjectWorkspaceProgress},
- Result,
};
-pub fn main_loop(config: Config, connection: Connection) -> Result<()> {
+pub fn main_loop(config: Config, connection: Connection) -> anyhow::Result<()> {
tracing::info!("initial config: {:#?}", config);
// Windows scheduler implements priority boosts: if thread waits for an
@@ -109,7 +108,7 @@ impl fmt::Debug for Event {
}
impl GlobalState {
- fn run(mut self, inbox: Receiver<lsp_server::Message>) -> Result<()> {
+ fn run(mut self, inbox: Receiver<lsp_server::Message>) -> anyhow::Result<()> {
self.update_status_or_notify();
if self.config.did_save_text_document_dynamic_registration() {
@@ -134,7 +133,7 @@ impl GlobalState {
self.handle_event(event)?;
}
- Err("client exited without proper shutdown sequence".into())
+ anyhow::bail!("client exited without proper shutdown sequence")
}
fn register_did_save_capability(&mut self) {
@@ -191,7 +190,7 @@ impl GlobalState {
}
}
- fn handle_event(&mut self, event: Event) -> Result<()> {
+ fn handle_event(&mut self, event: Event) -> anyhow::Result<()> {
let loop_start = Instant::now();
// NOTE: don't count blocking select! call as a loop-turn time
let _p = profile::span("GlobalState::handle_event");
@@ -754,11 +753,12 @@ impl GlobalState {
)
.on::<lsp_types::request::WillRenameFiles>(handlers::handle_will_rename_files)
.on::<lsp_ext::Ssr>(handlers::handle_ssr)
+ .on::<lsp_ext::ViewRecursiveMemoryLayout>(handlers::handle_view_recursive_memory_layout)
.finish();
}
/// Handles an incoming notification.
- fn on_notification(&mut self, not: Notification) -> Result<()> {
+ fn on_notification(&mut self, not: Notification) -> anyhow::Result<()> {
use crate::handlers::notification as handlers;
use lsp_types::notification as notifs;
@@ -843,11 +843,7 @@ impl GlobalState {
d.code.as_str().to_string(),
)),
code_description: Some(lsp_types::CodeDescription {
- href: lsp_types::Url::parse(&format!(
- "https://rust-analyzer.github.io/manual.html#{}",
- d.code.as_str()
- ))
- .unwrap(),
+ href: lsp_types::Url::parse(&d.code.url()).unwrap(),
}),
source: Some("rust-analyzer".to_string()),
message: d.message,
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs
index 310c6b076..0a2bb8224 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs
@@ -12,26 +12,22 @@
//! correct. Instead, we try to provide a best-effort service. Even if the
//! project is currently loading and we don't have a full project model, we
//! still want to respond to various requests.
-use std::{collections::hash_map::Entry, iter, mem, sync};
+use std::{iter, mem};
use flycheck::{FlycheckConfig, FlycheckHandle};
use hir::db::DefDatabase;
use ide::Change;
use ide_db::{
- base_db::{
- salsa::Durability, CrateGraph, Env, ProcMacro, ProcMacroExpander, ProcMacroExpansionError,
- ProcMacroKind, ProcMacroLoadResult, ProcMacroPaths, ProcMacros, SourceRoot, VfsPath,
- },
+ base_db::{salsa::Durability, CrateGraph, ProcMacroPaths, ProcMacros},
FxHashMap,
};
-use itertools::Itertools;
-use proc_macro_api::{MacroDylib, ProcMacroServer};
-use project_model::{PackageRoot, ProjectWorkspace, WorkspaceBuildScripts};
+use load_cargo::{load_proc_macro, ProjectFolders};
+use proc_macro_api::ProcMacroServer;
+use project_model::{ProjectWorkspace, WorkspaceBuildScripts};
use rustc_hash::FxHashSet;
use stdx::{format_to, thread::ThreadIntent};
-use syntax::SmolStr;
use triomphe::Arc;
-use vfs::{file_set::FileSetConfig, AbsPath, AbsPathBuf, ChangeKind};
+use vfs::{AbsPath, ChangeKind};
use crate::{
config::{Config, FilesWatcher, LinkedProject},
@@ -41,8 +37,6 @@ use crate::{
op_queue::Cause,
};
-use ::tt::token_id as tt;
-
#[derive(Debug)]
pub(crate) enum ProjectWorkspaceProgress {
Begin,
@@ -120,6 +114,11 @@ impl GlobalState {
if self.proc_macro_clients.iter().any(|it| it.is_err()) {
status.health = lsp_ext::Health::Warning;
message.push_str("Failed to spawn one or more proc-macro servers.\n\n");
+ for err in self.proc_macro_clients.iter() {
+ if let Err(err) = err {
+ format_to!(message, "- {err}\n");
+ }
+ }
}
if !self.config.cargo_autoreload()
&& self.is_quiescent()
@@ -307,7 +306,7 @@ impl GlobalState {
res.map_or_else(
|_| Err("proc macro crate is missing dylib".to_owned()),
|(crate_name, path)| {
- progress(path.display().to_string());
+ progress(path.to_string());
client.as_ref().map_err(Clone::clone).and_then(|client| {
load_proc_macro(
client,
@@ -340,7 +339,11 @@ impl GlobalState {
let _p = profile::span("GlobalState::switch_workspaces");
tracing::info!(%cause, "will switch workspaces");
- let Some((workspaces, force_reload_crate_graph)) = self.fetch_workspaces_queue.last_op_result() else { return; };
+ let Some((workspaces, force_reload_crate_graph)) =
+ self.fetch_workspaces_queue.last_op_result()
+ else {
+ return;
+ };
if let Err(_) = self.fetch_workspace_error() {
if !self.workspaces.is_empty() {
@@ -407,9 +410,9 @@ impl GlobalState {
.flat_map(|root| {
root.include.into_iter().flat_map(|it| {
[
- format!("{}/**/*.rs", it.display()),
- format!("{}/**/Cargo.toml", it.display()),
- format!("{}/**/Cargo.lock", it.display()),
+ format!("{it}/**/*.rs"),
+ format!("{it}/**/Cargo.toml"),
+ format!("{it}/**/Cargo.lock"),
]
})
})
@@ -447,17 +450,13 @@ impl GlobalState {
None => ws.find_sysroot_proc_macro_srv()?,
};
- tracing::info!("Using proc-macro server at {}", path.display(),);
+ tracing::info!("Using proc-macro server at {path}");
ProcMacroServer::spawn(path.clone()).map_err(|err| {
tracing::error!(
- "Failed to run proc-macro server from path {}, error: {:?}",
- path.display(),
- err
+ "Failed to run proc-macro server from path {path}, error: {err:?}",
);
- anyhow::anyhow!(
- "Failed to run proc-macro server from path {}, error: {:?}",
- path.display(),
- err
+ anyhow::format_err!(
+ "Failed to run proc-macro server from path {path}, error: {err:?}",
)
})
})
@@ -534,7 +533,9 @@ impl GlobalState {
pub(super) fn fetch_workspace_error(&self) -> Result<(), String> {
let mut buf = String::new();
- let Some((last_op_result, _)) = self.fetch_workspaces_queue.last_op_result() else { return Ok(()) };
+ let Some((last_op_result, _)) = self.fetch_workspaces_queue.last_op_result() else {
+ return Ok(());
+ };
if last_op_result.is_empty() {
stdx::format_to!(buf, "rust-analyzer failed to discover workspace");
} else {
@@ -621,254 +622,6 @@ impl GlobalState {
}
}
-#[derive(Default)]
-pub(crate) struct ProjectFolders {
- pub(crate) load: Vec<vfs::loader::Entry>,
- pub(crate) watch: Vec<usize>,
- pub(crate) source_root_config: SourceRootConfig,
-}
-
-impl ProjectFolders {
- pub(crate) fn new(
- workspaces: &[ProjectWorkspace],
- global_excludes: &[AbsPathBuf],
- ) -> ProjectFolders {
- let mut res = ProjectFolders::default();
- let mut fsc = FileSetConfig::builder();
- let mut local_filesets = vec![];
-
- // Dedup source roots
- // Depending on the project setup, we can have duplicated source roots, or for example in
- // the case of the rustc workspace, we can end up with two source roots that are almost the
- // same but not quite, like:
- // PackageRoot { is_local: false, include: [AbsPathBuf(".../rust/src/tools/miri/cargo-miri")], exclude: [] }
- // PackageRoot {
- // is_local: true,
- // include: [AbsPathBuf(".../rust/src/tools/miri/cargo-miri"), AbsPathBuf(".../rust/build/x86_64-pc-windows-msvc/stage0-tools/x86_64-pc-windows-msvc/release/build/cargo-miri-85801cd3d2d1dae4/out")],
- // exclude: [AbsPathBuf(".../rust/src/tools/miri/cargo-miri/.git"), AbsPathBuf(".../rust/src/tools/miri/cargo-miri/target")]
- // }
- //
- // The first one comes from the explicit rustc workspace which points to the rustc workspace itself
- // The second comes from the rustc workspace that we load as the actual project workspace
- // These `is_local` differing in this kind of way gives us problems, especially when trying to filter diagnostics as we don't report diagnostics for external libraries.
- // So we need to deduplicate these, usually it would be enough to deduplicate by `include`, but as the rustc example shows here that doesn't work,
- // so we need to also coalesce the includes if they overlap.
-
- let mut roots: Vec<_> = workspaces
- .iter()
- .flat_map(|ws| ws.to_roots())
- .update(|root| root.include.sort())
- .sorted_by(|a, b| a.include.cmp(&b.include))
- .collect();
-
- // map that tracks indices of overlapping roots
- let mut overlap_map = FxHashMap::<_, Vec<_>>::default();
- let mut done = false;
-
- while !mem::replace(&mut done, true) {
- // maps include paths to indices of the corresponding root
- let mut include_to_idx = FxHashMap::default();
- // Find and note down the indices of overlapping roots
- for (idx, root) in roots.iter().enumerate().filter(|(_, it)| !it.include.is_empty()) {
- for include in &root.include {
- match include_to_idx.entry(include) {
- Entry::Occupied(e) => {
- overlap_map.entry(*e.get()).or_default().push(idx);
- }
- Entry::Vacant(e) => {
- e.insert(idx);
- }
- }
- }
- }
- for (k, v) in overlap_map.drain() {
- done = false;
- for v in v {
- let r = mem::replace(
- &mut roots[v],
- PackageRoot { is_local: false, include: vec![], exclude: vec![] },
- );
- roots[k].is_local |= r.is_local;
- roots[k].include.extend(r.include);
- roots[k].exclude.extend(r.exclude);
- }
- roots[k].include.sort();
- roots[k].exclude.sort();
- roots[k].include.dedup();
- roots[k].exclude.dedup();
- }
- }
-
- for root in roots.into_iter().filter(|it| !it.include.is_empty()) {
- let file_set_roots: Vec<VfsPath> =
- root.include.iter().cloned().map(VfsPath::from).collect();
-
- let entry = {
- let mut dirs = vfs::loader::Directories::default();
- dirs.extensions.push("rs".into());
- dirs.include.extend(root.include);
- dirs.exclude.extend(root.exclude);
- for excl in global_excludes {
- if dirs
- .include
- .iter()
- .any(|incl| incl.starts_with(excl) || excl.starts_with(incl))
- {
- dirs.exclude.push(excl.clone());
- }
- }
-
- vfs::loader::Entry::Directories(dirs)
- };
-
- if root.is_local {
- res.watch.push(res.load.len());
- }
- res.load.push(entry);
-
- if root.is_local {
- local_filesets.push(fsc.len());
- }
- fsc.add_file_set(file_set_roots)
- }
-
- let fsc = fsc.build();
- res.source_root_config = SourceRootConfig { fsc, local_filesets };
-
- res
- }
-}
-
-#[derive(Default, Debug)]
-pub(crate) struct SourceRootConfig {
- pub(crate) fsc: FileSetConfig,
- pub(crate) local_filesets: Vec<usize>,
-}
-
-impl SourceRootConfig {
- pub(crate) fn partition(&self, vfs: &vfs::Vfs) -> Vec<SourceRoot> {
- let _p = profile::span("SourceRootConfig::partition");
- self.fsc
- .partition(vfs)
- .into_iter()
- .enumerate()
- .map(|(idx, file_set)| {
- let is_local = self.local_filesets.contains(&idx);
- if is_local {
- SourceRoot::new_local(file_set)
- } else {
- SourceRoot::new_library(file_set)
- }
- })
- .collect()
- }
-}
-
-/// Load the proc-macros for the given lib path, replacing all expanders whose names are in `dummy_replace`
-/// with an identity dummy expander.
-pub(crate) fn load_proc_macro(
- server: &ProcMacroServer,
- path: &AbsPath,
- dummy_replace: &[Box<str>],
-) -> ProcMacroLoadResult {
- let res: Result<Vec<_>, String> = (|| {
- let dylib = MacroDylib::new(path.to_path_buf());
- let vec = server.load_dylib(dylib).map_err(|e| format!("{e}"))?;
- if vec.is_empty() {
- return Err("proc macro library returned no proc macros".to_string());
- }
- Ok(vec
- .into_iter()
- .map(|expander| expander_to_proc_macro(expander, dummy_replace))
- .collect())
- })();
- return match res {
- Ok(proc_macros) => {
- tracing::info!(
- "Loaded proc-macros for {}: {:?}",
- path.display(),
- proc_macros.iter().map(|it| it.name.clone()).collect::<Vec<_>>()
- );
- Ok(proc_macros)
- }
- Err(e) => {
- tracing::warn!("proc-macro loading for {} failed: {e}", path.display());
- Err(e)
- }
- };
-
- fn expander_to_proc_macro(
- expander: proc_macro_api::ProcMacro,
- dummy_replace: &[Box<str>],
- ) -> ProcMacro {
- let name = SmolStr::from(expander.name());
- let kind = match expander.kind() {
- proc_macro_api::ProcMacroKind::CustomDerive => ProcMacroKind::CustomDerive,
- proc_macro_api::ProcMacroKind::FuncLike => ProcMacroKind::FuncLike,
- proc_macro_api::ProcMacroKind::Attr => ProcMacroKind::Attr,
- };
- let expander: sync::Arc<dyn ProcMacroExpander> =
- if dummy_replace.iter().any(|replace| &**replace == name) {
- match kind {
- ProcMacroKind::Attr => sync::Arc::new(IdentityExpander),
- _ => sync::Arc::new(EmptyExpander),
- }
- } else {
- sync::Arc::new(Expander(expander))
- };
- ProcMacro { name, kind, expander }
- }
-
- #[derive(Debug)]
- struct Expander(proc_macro_api::ProcMacro);
-
- impl ProcMacroExpander for Expander {
- fn expand(
- &self,
- subtree: &tt::Subtree,
- attrs: Option<&tt::Subtree>,
- env: &Env,
- ) -> Result<tt::Subtree, ProcMacroExpansionError> {
- let env = env.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect();
- match self.0.expand(subtree, attrs, env) {
- Ok(Ok(subtree)) => Ok(subtree),
- Ok(Err(err)) => Err(ProcMacroExpansionError::Panic(err.0)),
- Err(err) => Err(ProcMacroExpansionError::System(err.to_string())),
- }
- }
- }
-
- /// Dummy identity expander, used for attribute proc-macros that are deliberately ignored by the user.
- #[derive(Debug)]
- struct IdentityExpander;
-
- impl ProcMacroExpander for IdentityExpander {
- fn expand(
- &self,
- subtree: &tt::Subtree,
- _: Option<&tt::Subtree>,
- _: &Env,
- ) -> Result<tt::Subtree, ProcMacroExpansionError> {
- Ok(subtree.clone())
- }
- }
-
- /// Empty expander, used for proc-macros that are deliberately ignored by the user.
- #[derive(Debug)]
- struct EmptyExpander;
-
- impl ProcMacroExpander for EmptyExpander {
- fn expand(
- &self,
- _: &tt::Subtree,
- _: Option<&tt::Subtree>,
- _: &Env,
- ) -> Result<tt::Subtree, ProcMacroExpansionError> {
- Ok(tt::Subtree::empty())
- }
- }
-}
-
pub(crate) fn should_refresh_for_change(path: &AbsPath, change_kind: ChangeKind) -> bool {
const IMPLICIT_TARGET_FILES: &[&str] = &["build.rs", "src/main.rs", "src/lib.rs"];
const IMPLICIT_TARGET_DIRS: &[&str] = &["src/bin", "examples", "tests", "benches"];
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/semantic_tokens.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/semantic_tokens.rs
index d4bb20c8f..1fe02fc7e 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/semantic_tokens.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/semantic_tokens.rs
@@ -78,6 +78,7 @@ define_semantic_token_types![
(DERIVE_HELPER, "deriveHelper") => DECORATOR,
(DOT, "dot"),
(ESCAPE_SEQUENCE, "escapeSequence") => STRING,
+ (INVALID_ESCAPE_SEQUENCE, "invalidEscapeSequence") => STRING,
(FORMAT_SPECIFIER, "formatSpecifier") => STRING,
(GENERIC, "generic") => TYPE_PARAMETER,
(LABEL, "label"),
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/to_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/to_proto.rs
index 648bc995a..7b32180e3 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/to_proto.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/to_proto.rs
@@ -10,8 +10,8 @@ use ide::{
CompletionItemKind, CompletionRelevance, Documentation, FileId, FileRange, FileSystemEdit,
Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel, InlayHint,
InlayHintLabel, InlayHintLabelPart, InlayKind, Markup, NavigationTarget, ReferenceCategory,
- RenameError, Runnable, Severity, SignatureHelp, SourceChange, StructureNodeKind, SymbolKind,
- TextEdit, TextRange, TextSize,
+ RenameError, Runnable, Severity, SignatureHelp, SnippetEdit, SourceChange, StructureNodeKind,
+ SymbolKind, TextEdit, TextRange, TextSize,
};
use itertools::Itertools;
use serde_json::to_value;
@@ -22,7 +22,7 @@ use crate::{
config::{CallInfoConfig, Config},
global_state::GlobalStateSnapshot,
line_index::{LineEndings, LineIndex, PositionEncoding},
- lsp_ext,
+ lsp_ext::{self, SnippetTextEdit},
lsp_utils::invalid_params_error,
semantic_tokens::{self, standard_fallback_type},
};
@@ -94,7 +94,10 @@ pub(crate) fn document_highlight_kind(
pub(crate) fn diagnostic_severity(severity: Severity) -> lsp_types::DiagnosticSeverity {
match severity {
Severity::Error => lsp_types::DiagnosticSeverity::ERROR,
+ Severity::Warning => lsp_types::DiagnosticSeverity::WARNING,
Severity::WeakWarning => lsp_types::DiagnosticSeverity::HINT,
+ // unreachable
+ Severity::Allow => lsp_types::DiagnosticSeverity::INFORMATION,
}
}
@@ -637,6 +640,7 @@ fn semantic_token_type_and_modifiers(
HlTag::CharLiteral => semantic_tokens::CHAR,
HlTag::Comment => semantic_tokens::COMMENT,
HlTag::EscapeSequence => semantic_tokens::ESCAPE_SEQUENCE,
+ HlTag::InvalidEscapeSequence => semantic_tokens::INVALID_ESCAPE_SEQUENCE,
HlTag::FormatSpecifier => semantic_tokens::FORMAT_SPECIFIER,
HlTag::Keyword => semantic_tokens::KEYWORD,
HlTag::None => semantic_tokens::GENERIC,
@@ -881,16 +885,136 @@ fn outside_workspace_annotation_id() -> String {
String::from("OutsideWorkspace")
}
+fn merge_text_and_snippet_edits(
+ line_index: &LineIndex,
+ edit: TextEdit,
+ snippet_edit: SnippetEdit,
+) -> Vec<SnippetTextEdit> {
+ let mut edits: Vec<SnippetTextEdit> = vec![];
+ let mut snippets = snippet_edit.into_edit_ranges().into_iter().peekable();
+ let mut text_edits = edit.into_iter();
+
+ while let Some(current_indel) = text_edits.next() {
+ let new_range = {
+ let insert_len =
+ TextSize::try_from(current_indel.insert.len()).unwrap_or(TextSize::from(u32::MAX));
+ TextRange::at(current_indel.delete.start(), insert_len)
+ };
+
+ // insert any snippets before the text edit
+ for (snippet_index, snippet_range) in
+ snippets.take_while_ref(|(_, range)| range.end() < new_range.start())
+ {
+ let snippet_range = if !stdx::always!(
+ snippet_range.is_empty(),
+ "placeholder range {:?} is before current text edit range {:?}",
+ snippet_range,
+ new_range
+ ) {
+ // only possible for tabstops, so make sure it's an empty/insert range
+ TextRange::empty(snippet_range.start())
+ } else {
+ snippet_range
+ };
+
+ let range = range(&line_index, snippet_range);
+ let new_text = format!("${snippet_index}");
+
+ edits.push(SnippetTextEdit {
+ range,
+ new_text,
+ insert_text_format: Some(lsp_types::InsertTextFormat::SNIPPET),
+ annotation_id: None,
+ })
+ }
+
+ if snippets.peek().is_some_and(|(_, range)| new_range.intersect(*range).is_some()) {
+ // at least one snippet edit intersects this text edit,
+ // so gather all of the edits that intersect this text edit
+ let mut all_snippets = snippets
+ .take_while_ref(|(_, range)| new_range.intersect(*range).is_some())
+ .collect_vec();
+
+ // ensure all of the ranges are wholly contained inside of the new range
+ all_snippets.retain(|(_, range)| {
+ stdx::always!(
+ new_range.contains_range(*range),
+ "found placeholder range {:?} which wasn't fully inside of text edit's new range {:?}", range, new_range
+ )
+ });
+
+ let mut text_edit = text_edit(line_index, current_indel);
+
+ // escape out snippet text
+ stdx::replace(&mut text_edit.new_text, '\\', r"\\");
+ stdx::replace(&mut text_edit.new_text, '$', r"\$");
+
+ // ...and apply!
+ for (index, range) in all_snippets.iter().rev() {
+ let start = (range.start() - new_range.start()).into();
+ let end = (range.end() - new_range.start()).into();
+
+ if range.is_empty() {
+ text_edit.new_text.insert_str(start, &format!("${index}"));
+ } else {
+ text_edit.new_text.insert(end, '}');
+ text_edit.new_text.insert_str(start, &format!("${{{index}:"));
+ }
+ }
+
+ edits.push(SnippetTextEdit {
+ range: text_edit.range,
+ new_text: text_edit.new_text,
+ insert_text_format: Some(lsp_types::InsertTextFormat::SNIPPET),
+ annotation_id: None,
+ })
+ } else {
+ // snippet edit was beyond the current one
+ // since it wasn't consumed, it's available for the next pass
+ edits.push(snippet_text_edit(line_index, false, current_indel));
+ }
+ }
+
+ // insert any remaining tabstops
+ edits.extend(snippets.map(|(snippet_index, snippet_range)| {
+ let snippet_range = if !stdx::always!(
+ snippet_range.is_empty(),
+ "found placeholder snippet {:?} without a text edit",
+ snippet_range
+ ) {
+ TextRange::empty(snippet_range.start())
+ } else {
+ snippet_range
+ };
+
+ let range = range(&line_index, snippet_range);
+ let new_text = format!("${snippet_index}");
+
+ SnippetTextEdit {
+ range,
+ new_text,
+ insert_text_format: Some(lsp_types::InsertTextFormat::SNIPPET),
+ annotation_id: None,
+ }
+ }));
+
+ edits
+}
+
pub(crate) fn snippet_text_document_edit(
snap: &GlobalStateSnapshot,
is_snippet: bool,
file_id: FileId,
edit: TextEdit,
+ snippet_edit: Option<SnippetEdit>,
) -> Cancellable<lsp_ext::SnippetTextDocumentEdit> {
let text_document = optional_versioned_text_document_identifier(snap, file_id);
let line_index = snap.file_line_index(file_id)?;
- let mut edits: Vec<_> =
- edit.into_iter().map(|it| snippet_text_edit(&line_index, is_snippet, it)).collect();
+ let mut edits = if let Some(snippet_edit) = snippet_edit {
+ merge_text_and_snippet_edits(&line_index, edit, snippet_edit)
+ } else {
+ edit.into_iter().map(|it| snippet_text_edit(&line_index, is_snippet, it)).collect()
+ };
if snap.analysis.is_library_file(file_id)? && snap.config.change_annotation_support() {
for edit in &mut edits {
@@ -970,8 +1094,14 @@ pub(crate) fn snippet_workspace_edit(
let ops = snippet_text_document_ops(snap, op)?;
document_changes.extend_from_slice(&ops);
}
- for (file_id, edit) in source_change.source_file_edits {
- let edit = snippet_text_document_edit(snap, source_change.is_snippet, file_id, edit)?;
+ for (file_id, (edit, snippet_edit)) in source_change.source_file_edits {
+ let edit = snippet_text_document_edit(
+ snap,
+ source_change.is_snippet,
+ file_id,
+ edit,
+ snippet_edit,
+ )?;
document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit));
}
let mut workspace_edit = lsp_ext::SnippetWorkspaceEdit {
@@ -1410,7 +1540,9 @@ pub(crate) fn rename_error(err: RenameError) -> crate::LspError {
#[cfg(test)]
mod tests {
+ use expect_test::{expect, Expect};
use ide::{Analysis, FilePosition};
+ use ide_db::source_change::Snippet;
use test_utils::extract_offset;
use triomphe::Arc;
@@ -1480,6 +1612,481 @@ fn bar(_: usize) {}
assert!(!docs.contains("use crate::bar"));
}
+ fn check_rendered_snippets(edit: TextEdit, snippets: SnippetEdit, expect: Expect) {
+ let text = r#"/* place to put all ranges in */"#;
+ let line_index = LineIndex {
+ index: Arc::new(ide::LineIndex::new(text)),
+ endings: LineEndings::Unix,
+ encoding: PositionEncoding::Utf8,
+ };
+
+ let res = merge_text_and_snippet_edits(&line_index, edit, snippets);
+ expect.assert_debug_eq(&res);
+ }
+
+ #[test]
+ fn snippet_rendering_only_tabstops() {
+ let edit = TextEdit::builder().finish();
+ let snippets = SnippetEdit::new(vec![
+ Snippet::Tabstop(0.into()),
+ Snippet::Tabstop(0.into()),
+ Snippet::Tabstop(1.into()),
+ Snippet::Tabstop(1.into()),
+ ]);
+
+ check_rendered_snippets(
+ edit,
+ snippets,
+ expect![[r#"
+ [
+ SnippetTextEdit {
+ range: Range {
+ start: Position {
+ line: 0,
+ character: 0,
+ },
+ end: Position {
+ line: 0,
+ character: 0,
+ },
+ },
+ new_text: "$1",
+ insert_text_format: Some(
+ Snippet,
+ ),
+ annotation_id: None,
+ },
+ SnippetTextEdit {
+ range: Range {
+ start: Position {
+ line: 0,
+ character: 0,
+ },
+ end: Position {
+ line: 0,
+ character: 0,
+ },
+ },
+ new_text: "$2",
+ insert_text_format: Some(
+ Snippet,
+ ),
+ annotation_id: None,
+ },
+ SnippetTextEdit {
+ range: Range {
+ start: Position {
+ line: 0,
+ character: 1,
+ },
+ end: Position {
+ line: 0,
+ character: 1,
+ },
+ },
+ new_text: "$3",
+ insert_text_format: Some(
+ Snippet,
+ ),
+ annotation_id: None,
+ },
+ SnippetTextEdit {
+ range: Range {
+ start: Position {
+ line: 0,
+ character: 1,
+ },
+ end: Position {
+ line: 0,
+ character: 1,
+ },
+ },
+ new_text: "$0",
+ insert_text_format: Some(
+ Snippet,
+ ),
+ annotation_id: None,
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn snippet_rendering_only_text_edits() {
+ let mut edit = TextEdit::builder();
+ edit.insert(0.into(), "abc".to_owned());
+ edit.insert(3.into(), "def".to_owned());
+ let edit = edit.finish();
+ let snippets = SnippetEdit::new(vec![]);
+
+ check_rendered_snippets(
+ edit,
+ snippets,
+ expect![[r#"
+ [
+ SnippetTextEdit {
+ range: Range {
+ start: Position {
+ line: 0,
+ character: 0,
+ },
+ end: Position {
+ line: 0,
+ character: 0,
+ },
+ },
+ new_text: "abc",
+ insert_text_format: None,
+ annotation_id: None,
+ },
+ SnippetTextEdit {
+ range: Range {
+ start: Position {
+ line: 0,
+ character: 3,
+ },
+ end: Position {
+ line: 0,
+ character: 3,
+ },
+ },
+ new_text: "def",
+ insert_text_format: None,
+ annotation_id: None,
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn snippet_rendering_tabstop_after_text_edit() {
+ let mut edit = TextEdit::builder();
+ edit.insert(0.into(), "abc".to_owned());
+ let edit = edit.finish();
+ let snippets = SnippetEdit::new(vec![Snippet::Tabstop(7.into())]);
+
+ check_rendered_snippets(
+ edit,
+ snippets,
+ expect![[r#"
+ [
+ SnippetTextEdit {
+ range: Range {
+ start: Position {
+ line: 0,
+ character: 0,
+ },
+ end: Position {
+ line: 0,
+ character: 0,
+ },
+ },
+ new_text: "abc",
+ insert_text_format: None,
+ annotation_id: None,
+ },
+ SnippetTextEdit {
+ range: Range {
+ start: Position {
+ line: 0,
+ character: 7,
+ },
+ end: Position {
+ line: 0,
+ character: 7,
+ },
+ },
+ new_text: "$0",
+ insert_text_format: Some(
+ Snippet,
+ ),
+ annotation_id: None,
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn snippet_rendering_tabstops_before_text_edit() {
+ let mut edit = TextEdit::builder();
+ edit.insert(2.into(), "abc".to_owned());
+ let edit = edit.finish();
+ let snippets =
+ SnippetEdit::new(vec![Snippet::Tabstop(0.into()), Snippet::Tabstop(0.into())]);
+
+ check_rendered_snippets(
+ edit,
+ snippets,
+ expect![[r#"
+ [
+ SnippetTextEdit {
+ range: Range {
+ start: Position {
+ line: 0,
+ character: 0,
+ },
+ end: Position {
+ line: 0,
+ character: 0,
+ },
+ },
+ new_text: "$1",
+ insert_text_format: Some(
+ Snippet,
+ ),
+ annotation_id: None,
+ },
+ SnippetTextEdit {
+ range: Range {
+ start: Position {
+ line: 0,
+ character: 0,
+ },
+ end: Position {
+ line: 0,
+ character: 0,
+ },
+ },
+ new_text: "$0",
+ insert_text_format: Some(
+ Snippet,
+ ),
+ annotation_id: None,
+ },
+ SnippetTextEdit {
+ range: Range {
+ start: Position {
+ line: 0,
+ character: 2,
+ },
+ end: Position {
+ line: 0,
+ character: 2,
+ },
+ },
+ new_text: "abc",
+ insert_text_format: None,
+ annotation_id: None,
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn snippet_rendering_tabstops_between_text_edits() {
+ let mut edit = TextEdit::builder();
+ edit.insert(0.into(), "abc".to_owned());
+ edit.insert(7.into(), "abc".to_owned());
+ let edit = edit.finish();
+ let snippets =
+ SnippetEdit::new(vec![Snippet::Tabstop(4.into()), Snippet::Tabstop(4.into())]);
+
+ check_rendered_snippets(
+ edit,
+ snippets,
+ expect![[r#"
+ [
+ SnippetTextEdit {
+ range: Range {
+ start: Position {
+ line: 0,
+ character: 0,
+ },
+ end: Position {
+ line: 0,
+ character: 0,
+ },
+ },
+ new_text: "abc",
+ insert_text_format: None,
+ annotation_id: None,
+ },
+ SnippetTextEdit {
+ range: Range {
+ start: Position {
+ line: 0,
+ character: 4,
+ },
+ end: Position {
+ line: 0,
+ character: 4,
+ },
+ },
+ new_text: "$1",
+ insert_text_format: Some(
+ Snippet,
+ ),
+ annotation_id: None,
+ },
+ SnippetTextEdit {
+ range: Range {
+ start: Position {
+ line: 0,
+ character: 4,
+ },
+ end: Position {
+ line: 0,
+ character: 4,
+ },
+ },
+ new_text: "$0",
+ insert_text_format: Some(
+ Snippet,
+ ),
+ annotation_id: None,
+ },
+ SnippetTextEdit {
+ range: Range {
+ start: Position {
+ line: 0,
+ character: 7,
+ },
+ end: Position {
+ line: 0,
+ character: 7,
+ },
+ },
+ new_text: "abc",
+ insert_text_format: None,
+ annotation_id: None,
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn snippet_rendering_multiple_tabstops_in_text_edit() {
+ let mut edit = TextEdit::builder();
+ edit.insert(0.into(), "abcdefghijkl".to_owned());
+ let edit = edit.finish();
+ let snippets = SnippetEdit::new(vec![
+ Snippet::Tabstop(0.into()),
+ Snippet::Tabstop(5.into()),
+ Snippet::Tabstop(12.into()),
+ ]);
+
+ check_rendered_snippets(
+ edit,
+ snippets,
+ expect![[r#"
+ [
+ SnippetTextEdit {
+ range: Range {
+ start: Position {
+ line: 0,
+ character: 0,
+ },
+ end: Position {
+ line: 0,
+ character: 0,
+ },
+ },
+ new_text: "$1abcde$2fghijkl$0",
+ insert_text_format: Some(
+ Snippet,
+ ),
+ annotation_id: None,
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn snippet_rendering_multiple_placeholders_in_text_edit() {
+ let mut edit = TextEdit::builder();
+ edit.insert(0.into(), "abcdefghijkl".to_owned());
+ let edit = edit.finish();
+ let snippets = SnippetEdit::new(vec![
+ Snippet::Placeholder(TextRange::new(0.into(), 3.into())),
+ Snippet::Placeholder(TextRange::new(5.into(), 7.into())),
+ Snippet::Placeholder(TextRange::new(10.into(), 12.into())),
+ ]);
+
+ check_rendered_snippets(
+ edit,
+ snippets,
+ expect![[r#"
+ [
+ SnippetTextEdit {
+ range: Range {
+ start: Position {
+ line: 0,
+ character: 0,
+ },
+ end: Position {
+ line: 0,
+ character: 0,
+ },
+ },
+ new_text: "${1:abc}de${2:fg}hij${0:kl}",
+ insert_text_format: Some(
+ Snippet,
+ ),
+ annotation_id: None,
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn snippet_rendering_escape_snippet_bits() {
+ // only needed for snippet formats
+ let mut edit = TextEdit::builder();
+ edit.insert(0.into(), r"abc\def$".to_owned());
+ edit.insert(8.into(), r"ghi\jkl$".to_owned());
+ let edit = edit.finish();
+ let snippets =
+ SnippetEdit::new(vec![Snippet::Placeholder(TextRange::new(0.into(), 3.into()))]);
+
+ check_rendered_snippets(
+ edit,
+ snippets,
+ expect![[r#"
+ [
+ SnippetTextEdit {
+ range: Range {
+ start: Position {
+ line: 0,
+ character: 0,
+ },
+ end: Position {
+ line: 0,
+ character: 0,
+ },
+ },
+ new_text: "${0:abc}\\\\def\\$",
+ insert_text_format: Some(
+ Snippet,
+ ),
+ annotation_id: None,
+ },
+ SnippetTextEdit {
+ range: Range {
+ start: Position {
+ line: 0,
+ character: 8,
+ },
+ end: Position {
+ line: 0,
+ character: 8,
+ },
+ },
+ new_text: "ghi\\jkl$",
+ insert_text_format: None,
+ annotation_id: None,
+ },
+ ]
+ "#]],
+ );
+ }
+
// `Url` is not able to parse windows paths on unix machines.
#[test]
#[cfg(target_os = "windows")]
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs
index b2a8041ae..3c52ef5ef 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs
@@ -13,6 +13,7 @@ use rust_analyzer::{config::Config, lsp_ext, main_loop};
use serde::Serialize;
use serde_json::{json, to_string_pretty, Value};
use test_utils::FixtureWithProjectMeta;
+use tracing_subscriber::{prelude::*, Layer};
use vfs::AbsPathBuf;
use crate::testdir::TestDir;
@@ -24,7 +25,7 @@ pub(crate) struct Project<'a> {
config: serde_json::Value,
}
-impl<'a> Project<'a> {
+impl Project<'_> {
pub(crate) fn with_fixture(fixture: &str) -> Project<'_> {
Project {
fixture,
@@ -47,17 +48,17 @@ impl<'a> Project<'a> {
}
}
- pub(crate) fn tmp_dir(mut self, tmp_dir: TestDir) -> Project<'a> {
+ pub(crate) fn tmp_dir(mut self, tmp_dir: TestDir) -> Self {
self.tmp_dir = Some(tmp_dir);
self
}
- pub(crate) fn root(mut self, path: &str) -> Project<'a> {
+ pub(crate) fn root(mut self, path: &str) -> Self {
self.roots.push(path.into());
self
}
- pub(crate) fn with_config(mut self, config: serde_json::Value) -> Project<'a> {
+ pub(crate) fn with_config(mut self, config: serde_json::Value) -> Self {
fn merge(dst: &mut serde_json::Value, src: serde_json::Value) {
match (dst, src) {
(Value::Object(dst), Value::Object(src)) => {
@@ -76,10 +77,11 @@ impl<'a> Project<'a> {
let tmp_dir = self.tmp_dir.unwrap_or_else(TestDir::new);
static INIT: Once = Once::new();
INIT.call_once(|| {
- tracing_subscriber::fmt()
- .with_test_writer()
- .with_env_filter(tracing_subscriber::EnvFilter::from_env("RA_LOG"))
- .init();
+ let filter: tracing_subscriber::filter::Targets =
+ std::env::var("RA_LOG").ok().and_then(|it| it.parse().ok()).unwrap_or_default();
+ let layer =
+ tracing_subscriber::fmt::Layer::new().with_test_writer().with_filter(filter);
+ tracing_subscriber::Registry::default().with(layer).init();
profile::init_from(crate::PROFILE);
});
@@ -111,6 +113,14 @@ impl<'a> Project<'a> {
relative_pattern_support: None,
},
),
+ workspace_edit: Some(lsp_types::WorkspaceEditClientCapabilities {
+ resource_operations: Some(vec![
+ lsp_types::ResourceOperationKind::Create,
+ lsp_types::ResourceOperationKind::Delete,
+ lsp_types::ResourceOperationKind::Rename,
+ ]),
+ ..Default::default()
+ }),
..Default::default()
}),
text_document: Some(lsp_types::TextDocumentClientCapabilities {
diff --git a/src/tools/rust-analyzer/crates/sourcegen/src/lib.rs b/src/tools/rust-analyzer/crates/sourcegen/src/lib.rs
index c5da6ceb4..1514c6c7d 100644
--- a/src/tools/rust-analyzer/crates/sourcegen/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/sourcegen/src/lib.rs
@@ -61,7 +61,9 @@ impl CommentBlock {
let mut blocks = CommentBlock::extract_untagged(text);
blocks.retain_mut(|block| {
let first = block.contents.remove(0);
- let Some(id) = first.strip_prefix(&tag) else { return false; };
+ let Some(id) = first.strip_prefix(&tag) else {
+ return false;
+ };
if block.is_doc {
panic!("Use plain (non-doc) comments with tags like {tag}:\n {first}");
diff --git a/src/tools/rust-analyzer/crates/stdx/Cargo.toml b/src/tools/rust-analyzer/crates/stdx/Cargo.toml
index a67f36ae9..536f000a4 100644
--- a/src/tools/rust-analyzer/crates/stdx/Cargo.toml
+++ b/src/tools/rust-analyzer/crates/stdx/Cargo.toml
@@ -13,7 +13,7 @@ doctest = false
[dependencies]
libc = "0.2.135"
-backtrace = { version = "0.3.65", optional = true }
+backtrace = { version = "0.3.67", optional = true }
always-assert = { version = "0.1.2", features = ["log"] }
jod-thread = "0.1.2"
crossbeam-channel = "0.5.5"
diff --git a/src/tools/rust-analyzer/crates/syntax/Cargo.toml b/src/tools/rust-analyzer/crates/syntax/Cargo.toml
index fb38d25ab..5ee0c4792 100644
--- a/src/tools/rust-analyzer/crates/syntax/Cargo.toml
+++ b/src/tools/rust-analyzer/crates/syntax/Cargo.toml
@@ -19,7 +19,7 @@ itertools = "0.10.5"
rowan = "0.15.11"
rustc-hash = "1.1.0"
once_cell = "1.17.0"
-indexmap = "1.9.1"
+indexmap = "2.0.0"
smol_str.workspace = true
triomphe.workspace = true
diff --git a/src/tools/rust-analyzer/crates/syntax/rust.ungram b/src/tools/rust-analyzer/crates/syntax/rust.ungram
index b096c9974..138ddd208 100644
--- a/src/tools/rust-analyzer/crates/syntax/rust.ungram
+++ b/src/tools/rust-analyzer/crates/syntax/rust.ungram
@@ -72,6 +72,12 @@ TokenTree =
MacroItems =
Item*
+MacroEagerInput =
+ '(' (Expr (',' Expr)* ','?)? ')'
+| '{' (Expr (',' Expr)* ','?)? '}'
+| '[' (Expr (',' Expr)* ','?)? ']'
+
+
MacroStmts =
statements:Stmt*
Expr?
diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/edit_in_place.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/edit_in_place.rs
index b3ea6ca8d..a150d9e6c 100644
--- a/src/tools/rust-analyzer/crates/syntax/src/ast/edit_in_place.rs
+++ b/src/tools/rust-analyzer/crates/syntax/src/ast/edit_in_place.rs
@@ -213,6 +213,28 @@ pub trait AttrsOwnerEdit: ast::HasAttrs {
}
}
}
+
+ fn add_attr(&self, attr: ast::Attr) {
+ add_attr(self.syntax(), attr);
+
+ fn add_attr(node: &SyntaxNode, attr: ast::Attr) {
+ let indent = IndentLevel::from_node(node);
+ attr.reindent_to(indent);
+
+ let after_attrs_and_comments = node
+ .children_with_tokens()
+ .find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | ATTR))
+ .map_or(Position::first_child_of(node), |it| Position::before(it));
+
+ ted::insert_all(
+ after_attrs_and_comments,
+ vec![
+ attr.syntax().clone().into(),
+ make::tokens::whitespace(&format!("\n{indent}")).into(),
+ ],
+ )
+ }
+ }
}
impl<T: ast::HasAttrs> AttrsOwnerEdit for T {}
@@ -358,6 +380,26 @@ impl Removable for ast::UseTree {
}
impl ast::UseTree {
+ /// Deletes the usetree node represented by the input. Recursively removes parents, including use nodes that become empty.
+ pub fn remove_recursive(self) {
+ let parent = self.syntax().parent();
+
+ self.remove();
+
+ if let Some(u) = parent.clone().and_then(ast::Use::cast) {
+ if u.use_tree().is_none() {
+ u.remove();
+ }
+ } else if let Some(u) = parent.and_then(ast::UseTreeList::cast) {
+ if u.use_trees().next().is_none() {
+ let parent = u.syntax().parent().and_then(ast::UseTree::cast);
+ if let Some(u) = parent {
+ u.remove_recursive();
+ }
+ }
+ }
+ }
+
pub fn get_or_create_use_tree_list(&self) -> ast::UseTreeList {
match self.use_tree_list() {
Some(it) => it,
@@ -465,6 +507,22 @@ impl Removable for ast::Use {
}
}
}
+ let prev_ws = self
+ .syntax()
+ .prev_sibling_or_token()
+ .and_then(|it| it.into_token())
+ .and_then(ast::Whitespace::cast);
+ if let Some(prev_ws) = prev_ws {
+ let ws_text = prev_ws.syntax().text();
+ let prev_newline = ws_text.rfind('\n').map(|x| x + 1).unwrap_or(0);
+ let rest = &ws_text[0..prev_newline];
+ if rest.is_empty() {
+ ted::remove(prev_ws.syntax());
+ } else {
+ ted::replace(prev_ws.syntax(), make::tokens::whitespace(rest));
+ }
+ }
+
ted::remove(self.syntax());
}
}
@@ -676,12 +734,6 @@ fn get_or_insert_comma_after(syntax: &SyntaxNode) -> SyntaxToken {
}
}
-impl ast::StmtList {
- pub fn push_front(&self, statement: ast::Stmt) {
- ted::insert(Position::after(self.l_curly_token().unwrap()), statement.syntax());
- }
-}
-
impl ast::VariantList {
pub fn add_variant(&self, variant: ast::Variant) {
let (indent, position) = match self.variants().last() {
@@ -732,6 +784,27 @@ fn normalize_ws_between_braces(node: &SyntaxNode) -> Option<()> {
Some(())
}
+pub trait HasVisibilityEdit: ast::HasVisibility {
+ fn set_visibility(&self, visbility: ast::Visibility) {
+ match self.visibility() {
+ Some(current_visibility) => {
+ ted::replace(current_visibility.syntax(), visbility.syntax())
+ }
+ None => {
+ let vis_before = self
+ .syntax()
+ .children_with_tokens()
+ .find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | ATTR))
+ .unwrap_or_else(|| self.syntax().first_child_or_token().unwrap());
+
+ ted::insert(ted::Position::before(vis_before), visbility.syntax());
+ }
+ }
+ }
+}
+
+impl<T: ast::HasVisibility> HasVisibilityEdit for T {}
+
pub trait Indent: AstNode + Clone + Sized {
fn indent_level(&self) -> IndentLevel {
IndentLevel::from_node(self.syntax())
diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs
index e520801ea..0b27faa53 100644
--- a/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs
+++ b/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs
@@ -198,6 +198,20 @@ impl ast::HasModuleItem for MacroItems {}
impl MacroItems {}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct MacroEagerInput {
+ pub(crate) syntax: SyntaxNode,
+}
+impl MacroEagerInput {
+ pub fn l_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['(']) }
+ pub fn exprs(&self) -> AstChildren<Expr> { support::children(&self.syntax) }
+ pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) }
+ pub fn l_curly_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['{']) }
+ pub fn r_curly_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['}']) }
+ pub fn l_brack_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['[']) }
+ pub fn r_brack_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![']']) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct MacroStmts {
pub(crate) syntax: SyntaxNode,
}
@@ -1922,6 +1936,17 @@ impl AstNode for MacroItems {
}
fn syntax(&self) -> &SyntaxNode { &self.syntax }
}
+impl AstNode for MacroEagerInput {
+ fn can_cast(kind: SyntaxKind) -> bool { kind == MACRO_EAGER_INPUT }
+ fn cast(syntax: SyntaxNode) -> Option<Self> {
+ if Self::can_cast(syntax.kind()) {
+ Some(Self { syntax })
+ } else {
+ None
+ }
+ }
+ fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
impl AstNode for MacroStmts {
fn can_cast(kind: SyntaxKind) -> bool { kind == MACRO_STMTS }
fn cast(syntax: SyntaxNode) -> Option<Self> {
@@ -4360,6 +4385,11 @@ impl std::fmt::Display for MacroItems {
std::fmt::Display::fmt(self.syntax(), f)
}
}
+impl std::fmt::Display for MacroEagerInput {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ std::fmt::Display::fmt(self.syntax(), f)
+ }
+}
impl std::fmt::Display for MacroStmts {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f)
diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs
index 3c2b7e56b..4c6db0ef0 100644
--- a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs
+++ b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs
@@ -10,6 +10,8 @@
//! `parse(format!())` we use internally is an implementation detail -- long
//! term, it will be replaced with direct tree manipulation.
use itertools::Itertools;
+use parser::T;
+use rowan::NodeOrToken;
use stdx::{format_to, never};
use crate::{ast, utils::is_raw_identifier, AstNode, SourceFile, SyntaxKind, SyntaxToken};
@@ -447,6 +449,21 @@ pub fn block_expr(
ast_from_text(&format!("fn f() {buf}"))
}
+pub fn async_move_block_expr(
+ stmts: impl IntoIterator<Item = ast::Stmt>,
+ tail_expr: Option<ast::Expr>,
+) -> ast::BlockExpr {
+ let mut buf = "async move {\n".to_string();
+ for stmt in stmts.into_iter() {
+ format_to!(buf, " {stmt}\n");
+ }
+ if let Some(tail_expr) = tail_expr {
+ format_to!(buf, " {tail_expr}\n");
+ }
+ buf += "}";
+ ast_from_text(&format!("const _: () = {buf};"))
+}
+
pub fn tail_only_block_expr(tail_expr: ast::Expr) -> ast::BlockExpr {
ast_from_text(&format!("fn f() {{ {tail_expr} }}"))
}
@@ -848,6 +865,36 @@ pub fn param_list(
ast_from_text(&list)
}
+pub fn trait_(
+ is_unsafe: bool,
+ ident: &str,
+ gen_params: Option<ast::GenericParamList>,
+ where_clause: Option<ast::WhereClause>,
+ assoc_items: ast::AssocItemList,
+) -> ast::Trait {
+ let mut text = String::new();
+
+ if is_unsafe {
+ format_to!(text, "unsafe ");
+ }
+
+ format_to!(text, "trait {ident}");
+
+ if let Some(gen_params) = gen_params {
+ format_to!(text, "{} ", gen_params.to_string());
+ } else {
+ text.push(' ');
+ }
+
+ if let Some(where_clause) = where_clause {
+ format_to!(text, "{} ", where_clause.to_string());
+ }
+
+ format_to!(text, "{}", assoc_items.to_string());
+
+ ast_from_text(&text)
+}
+
pub fn type_bound(bound: &str) -> ast::TypeBound {
ast_from_text(&format!("fn f<T: {bound}>() {{ }}"))
}
@@ -985,6 +1032,41 @@ pub fn struct_(
ast_from_text(&format!("{visibility}struct {strukt_name}{type_params}{field_list}{semicolon}",))
}
+pub fn attr_outer(meta: ast::Meta) -> ast::Attr {
+ ast_from_text(&format!("#[{meta}]"))
+}
+
+pub fn attr_inner(meta: ast::Meta) -> ast::Attr {
+ ast_from_text(&format!("#![{meta}]"))
+}
+
+pub fn meta_expr(path: ast::Path, expr: ast::Expr) -> ast::Meta {
+ ast_from_text(&format!("#[{path} = {expr}]"))
+}
+
+pub fn meta_token_tree(path: ast::Path, tt: ast::TokenTree) -> ast::Meta {
+ ast_from_text(&format!("#[{path}{tt}]"))
+}
+
+pub fn meta_path(path: ast::Path) -> ast::Meta {
+ ast_from_text(&format!("#[{path}]"))
+}
+
+pub fn token_tree(
+ delimiter: SyntaxKind,
+ tt: Vec<NodeOrToken<ast::TokenTree, SyntaxToken>>,
+) -> ast::TokenTree {
+ let (l_delimiter, r_delimiter) = match delimiter {
+ T!['('] => ('(', ')'),
+ T!['['] => ('[', ']'),
+ T!['{'] => ('{', '}'),
+ _ => panic!("invalid delimiter `{delimiter:?}`"),
+ };
+ let tt = tt.into_iter().join("");
+
+ ast_from_text(&format!("tt!{l_delimiter}{tt}{r_delimiter}"))
+}
+
#[track_caller]
fn ast_from_text<N: AstNode>(text: &str) -> N {
let parse = SourceFile::parse(text);
@@ -1022,6 +1104,17 @@ pub mod tokens {
)
});
+ pub fn semicolon() -> SyntaxToken {
+ SOURCE_FILE
+ .tree()
+ .syntax()
+ .clone_for_update()
+ .descendants_with_tokens()
+ .filter_map(|it| it.into_token())
+ .find(|it| it.kind() == SEMICOLON)
+ .unwrap()
+ }
+
pub fn single_space() -> SyntaxToken {
SOURCE_FILE
.tree()
diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/token_ext.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/token_ext.rs
index 090eb89f4..87fd51d70 100644
--- a/src/tools/rust-analyzer/crates/syntax/src/ast/token_ext.rs
+++ b/src/tools/rust-analyzer/crates/syntax/src/ast/token_ext.rs
@@ -2,7 +2,9 @@
use std::borrow::Cow;
-use rustc_lexer::unescape::{unescape_byte, unescape_char, unescape_literal, Mode};
+use rustc_lexer::unescape::{
+ unescape_byte, unescape_c_string, unescape_char, unescape_literal, CStrUnit, Mode,
+};
use crate::{
ast::{self, AstToken},
@@ -146,6 +148,7 @@ impl QuoteOffsets {
pub trait IsString: AstToken {
const RAW_PREFIX: &'static str;
+ const MODE: Mode;
fn is_raw(&self) -> bool {
self.text().starts_with(Self::RAW_PREFIX)
}
@@ -181,7 +184,7 @@ pub trait IsString: AstToken {
let text = &self.text()[text_range_no_quotes - start];
let offset = text_range_no_quotes.start() - start;
- unescape_literal(text, Mode::Str, &mut |range, unescaped_char| {
+ unescape_literal(text, Self::MODE, &mut |range, unescaped_char| {
let text_range =
TextRange::new(range.start.try_into().unwrap(), range.end.try_into().unwrap());
cb(text_range + offset, unescaped_char);
@@ -196,6 +199,7 @@ pub trait IsString: AstToken {
impl IsString for ast::String {
const RAW_PREFIX: &'static str = "r";
+ const MODE: Mode = Mode::Str;
}
impl ast::String {
@@ -213,7 +217,7 @@ impl ast::String {
let mut buf = String::new();
let mut prev_end = 0;
let mut has_error = false;
- unescape_literal(text, Mode::Str, &mut |char_range, unescaped_char| match (
+ unescape_literal(text, Self::MODE, &mut |char_range, unescaped_char| match (
unescaped_char,
buf.capacity() == 0,
) {
@@ -239,6 +243,7 @@ impl ast::String {
impl IsString for ast::ByteString {
const RAW_PREFIX: &'static str = "br";
+ const MODE: Mode = Mode::ByteStr;
}
impl ast::ByteString {
@@ -256,7 +261,7 @@ impl ast::ByteString {
let mut buf: Vec<u8> = Vec::new();
let mut prev_end = 0;
let mut has_error = false;
- unescape_literal(text, Mode::ByteStr, &mut |char_range, unescaped_char| match (
+ unescape_literal(text, Self::MODE, &mut |char_range, unescaped_char| match (
unescaped_char,
buf.capacity() == 0,
) {
@@ -282,42 +287,70 @@ impl ast::ByteString {
impl IsString for ast::CString {
const RAW_PREFIX: &'static str = "cr";
+ const MODE: Mode = Mode::CStr;
+
+ fn escaped_char_ranges(
+ &self,
+ cb: &mut dyn FnMut(TextRange, Result<char, rustc_lexer::unescape::EscapeError>),
+ ) {
+ let text_range_no_quotes = match self.text_range_between_quotes() {
+ Some(it) => it,
+ None => return,
+ };
+
+ let start = self.syntax().text_range().start();
+ let text = &self.text()[text_range_no_quotes - start];
+ let offset = text_range_no_quotes.start() - start;
+
+ unescape_c_string(text, Self::MODE, &mut |range, unescaped_char| {
+ let text_range =
+ TextRange::new(range.start.try_into().unwrap(), range.end.try_into().unwrap());
+ // XXX: This method should only be used for highlighting ranges. The unescaped
+ // char/byte is not used. For simplicity, we return an arbitrary placeholder char.
+ cb(text_range + offset, unescaped_char.map(|_| ' '));
+ });
+ }
}
impl ast::CString {
- pub fn value(&self) -> Option<Cow<'_, str>> {
+ pub fn value(&self) -> Option<Cow<'_, [u8]>> {
if self.is_raw() {
let text = self.text();
let text =
&text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
- return Some(Cow::Borrowed(text));
+ return Some(Cow::Borrowed(text.as_bytes()));
}
let text = self.text();
let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
- let mut buf = String::new();
+ let mut buf = Vec::new();
let mut prev_end = 0;
let mut has_error = false;
- unescape_literal(text, Mode::Str, &mut |char_range, unescaped_char| match (
- unescaped_char,
+ let mut char_buf = [0u8; 4];
+ let mut extend_unit = |buf: &mut Vec<u8>, unit: CStrUnit| match unit {
+ CStrUnit::Byte(b) => buf.push(b),
+ CStrUnit::Char(c) => buf.extend(c.encode_utf8(&mut char_buf).as_bytes()),
+ };
+ unescape_c_string(text, Self::MODE, &mut |char_range, unescaped| match (
+ unescaped,
buf.capacity() == 0,
) {
- (Ok(c), false) => buf.push(c),
+ (Ok(u), false) => extend_unit(&mut buf, u),
(Ok(_), true) if char_range.len() == 1 && char_range.start == prev_end => {
prev_end = char_range.end
}
- (Ok(c), true) => {
+ (Ok(u), true) => {
buf.reserve_exact(text.len());
- buf.push_str(&text[..prev_end]);
- buf.push(c);
+ buf.extend(text[..prev_end].as_bytes());
+ extend_unit(&mut buf, u);
}
(Err(_), _) => has_error = true,
});
match (has_error, buf.capacity() == 0) {
(true, _) => None,
- (false, true) => Some(Cow::Borrowed(text)),
+ (false, true) => Some(Cow::Borrowed(text.as_bytes())),
(false, false) => Some(Cow::Owned(buf)),
}
}
diff --git a/src/tools/rust-analyzer/crates/syntax/src/lib.rs b/src/tools/rust-analyzer/crates/syntax/src/lib.rs
index efbf87966..4cd668a0c 100644
--- a/src/tools/rust-analyzer/crates/syntax/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/syntax/src/lib.rs
@@ -171,6 +171,109 @@ impl SourceFile {
}
}
+impl ast::TokenTree {
+ pub fn reparse_as_comma_separated_expr(self) -> Parse<ast::MacroEagerInput> {
+ let tokens = self.syntax().descendants_with_tokens().filter_map(NodeOrToken::into_token);
+
+ let mut parser_input = parser::Input::default();
+ let mut was_joint = false;
+ for t in tokens {
+ let kind = t.kind();
+ if kind.is_trivia() {
+ was_joint = false
+ } else {
+ if kind == SyntaxKind::IDENT {
+ let token_text = t.text();
+ let contextual_kw = SyntaxKind::from_contextual_keyword(token_text)
+ .unwrap_or(SyntaxKind::IDENT);
+ parser_input.push_ident(contextual_kw);
+ } else {
+ if was_joint {
+ parser_input.was_joint();
+ }
+ parser_input.push(kind);
+ // Tag the token as joint if it is float with a fractional part
+ // we use this jointness to inform the parser about what token split
+ // event to emit when we encounter a float literal in a field access
+ if kind == SyntaxKind::FLOAT_NUMBER && !t.text().ends_with('.') {
+ parser_input.was_joint();
+ }
+ }
+ was_joint = true;
+ }
+ }
+
+ let parser_output = parser::TopEntryPoint::MacroEagerInput.parse(&parser_input);
+
+ let mut tokens =
+ self.syntax().descendants_with_tokens().filter_map(NodeOrToken::into_token);
+ let mut text = String::new();
+ let mut pos = TextSize::from(0);
+ let mut builder = SyntaxTreeBuilder::default();
+ for event in parser_output.iter() {
+ match event {
+ parser::Step::Token { kind, n_input_tokens } => {
+ let mut token = tokens.next().unwrap();
+ while token.kind().is_trivia() {
+ let text = token.text();
+ pos += TextSize::from(text.len() as u32);
+ builder.token(token.kind(), text);
+
+ token = tokens.next().unwrap();
+ }
+ text.push_str(token.text());
+ for _ in 1..n_input_tokens {
+ let token = tokens.next().unwrap();
+ text.push_str(token.text());
+ }
+
+ pos += TextSize::from(text.len() as u32);
+ builder.token(kind, &text);
+ text.clear();
+ }
+ parser::Step::FloatSplit { ends_in_dot: has_pseudo_dot } => {
+ let token = tokens.next().unwrap();
+ let text = token.text();
+
+ match text.split_once('.') {
+ Some((left, right)) => {
+ assert!(!left.is_empty());
+ builder.start_node(SyntaxKind::NAME_REF);
+ builder.token(SyntaxKind::INT_NUMBER, left);
+ builder.finish_node();
+
+ // here we move the exit up, the original exit has been deleted in process
+ builder.finish_node();
+
+ builder.token(SyntaxKind::DOT, ".");
+
+ if has_pseudo_dot {
+ assert!(right.is_empty(), "{left}.{right}");
+ } else {
+ builder.start_node(SyntaxKind::NAME_REF);
+ builder.token(SyntaxKind::INT_NUMBER, right);
+ builder.finish_node();
+
+ // the parser creates an unbalanced start node, we are required to close it here
+ builder.finish_node();
+ }
+ }
+ None => unreachable!(),
+ }
+ pos += TextSize::from(text.len() as u32);
+ }
+ parser::Step::Enter { kind } => builder.start_node(kind),
+ parser::Step::Exit => builder.finish_node(),
+ parser::Step::Error { msg } => builder.error(msg.to_owned(), pos),
+ }
+ }
+
+ let (green, errors) = builder.finish_raw();
+
+ Parse { green, errors: Arc::new(errors), _ty: PhantomData }
+ }
+}
+
/// Matches a `SyntaxNode` against an `ast` type.
///
/// # Example:
diff --git a/src/tools/rust-analyzer/crates/syntax/src/tests/ast_src.rs b/src/tools/rust-analyzer/crates/syntax/src/tests/ast_src.rs
index c5783b91a..e4db33f1c 100644
--- a/src/tools/rust-analyzer/crates/syntax/src/tests/ast_src.rs
+++ b/src/tools/rust-analyzer/crates/syntax/src/tests/ast_src.rs
@@ -216,6 +216,7 @@ pub(crate) const KINDS_SRC: KindsSrc<'_> = KindsSrc {
// macro related
"MACRO_ITEMS",
"MACRO_STMTS",
+ "MACRO_EAGER_INPUT",
],
};
diff --git a/src/tools/rust-analyzer/crates/test-utils/src/fixture.rs b/src/tools/rust-analyzer/crates/test-utils/src/fixture.rs
index 602baed37..75e7a3fec 100644
--- a/src/tools/rust-analyzer/crates/test-utils/src/fixture.rs
+++ b/src/tools/rust-analyzer/crates/test-utils/src/fixture.rs
@@ -47,13 +47,10 @@
//! ```
//!
//! Metadata allows specifying all settings and variables
-//! that are available in a real rust project:
-//! - crate names via `crate:cratename`
-//! - dependencies via `deps:dep1,dep2`
-//! - configuration settings via `cfg:dbg=false,opt_level=2`
-//! - environment variables via `env:PATH=/bin,RUST_LOG=debug`
+//! that are available in a real rust project. See [`Fixture`]
+//! for the syntax.
//!
-//! Example using all available metadata:
+//! Example using some available metadata:
//! ```
//! "
//! //- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b env:OUTDIR=path/to,OTHER=foo
@@ -68,17 +65,74 @@ use stdx::trim_indent;
#[derive(Debug, Eq, PartialEq)]
pub struct Fixture {
+ /// Specifies the path for this file. It must start with "/".
pub path: String,
- pub text: String,
+ /// Defines a new crate and make this file its root module.
+ ///
+ /// Version and repository URL of the crate can optionally be specified; if
+ /// either one is specified, the other must also be specified.
+ ///
+ /// Syntax:
+ /// - `crate:my_awesome_lib`
+ /// - `crate:my_awesome_lib@0.0.1,https://example.com/repo.git`
pub krate: Option<String>,
+ /// Specifies dependencies of this crate. This must be used with `crate` meta.
+ ///
+ /// Syntax: `deps:hir-def,ide-assists`
pub deps: Vec<String>,
+ /// Limits crates in the extern prelude. The set of crate names must be a
+ /// subset of `deps`. This must be used with `crate` meta.
+ ///
+ /// If this is not specified, all the dependencies will be in the extern prelude.
+ ///
+ /// Syntax: `extern-prelude:hir-def,ide-assists`
pub extern_prelude: Option<Vec<String>>,
- pub cfg_atoms: Vec<String>,
- pub cfg_key_values: Vec<(String, String)>,
+ /// Specifies configuration options to be enabled. Options may have associated
+ /// values.
+ ///
+ /// Syntax: `cfg:test,dbg=false,opt_level=2`
+ pub cfgs: Vec<(String, Option<String>)>,
+ /// Specifies the edition of this crate. This must be used with `crate` meta. If
+ /// this is not specified, ([`base_db::input::Edition::CURRENT`]) will be used.
+ /// This must be used with `crate` meta.
+ ///
+ /// Syntax: `edition:2021`
pub edition: Option<String>,
+ /// Specifies environment variables.
+ ///
+ /// Syntax: `env:PATH=/bin,RUST_LOG=debug`
pub env: FxHashMap<String, String>,
+ /// Introduces a new [source root](base_db::input::SourceRoot). This file **and
+ /// the following files** will belong the new source root. This must be used
+ /// with `crate` meta.
+ ///
+ /// Use this if you want to test something that uses `SourceRoot::is_library()`
+ /// to check editability.
+ ///
+ /// Note that files before the first fixture with `new_source_root` meta will
+ /// belong to an implicitly defined local source root.
+ ///
+ /// Syntax:
+ /// - `new_source_root:library`
+ /// - `new_source_root:local`
pub introduce_new_source_root: Option<String>,
+ /// Explicitly declares this crate as a library outside current workspace. This
+ /// must be used with `crate` meta.
+ ///
+ /// This is implied if this file belongs to a library source root.
+ ///
+ /// Use this if you want to test something that checks if a crate is a workspace
+ /// member via [`CrateOrigin`](base_db::input::CrateOrigin).
+ ///
+ /// Syntax: `library`
+ pub library: bool,
+ /// Specifies LLVM data layout to be used.
+ ///
+ /// You probably don't want to manually specify this. See LLVM manual for the
+ /// syntax, if you must: https://llvm.org/docs/LangRef.html#data-layout
pub target_data_layout: Option<String>,
+ /// Actual file contents. All meta comments are stripped.
+ pub text: String,
}
pub struct MiniCore {
@@ -178,23 +232,28 @@ impl FixtureWithProjectMeta {
fn parse_meta_line(meta: &str) -> Fixture {
assert!(meta.starts_with("//-"));
let meta = meta["//-".len()..].trim();
- let components = meta.split_ascii_whitespace().collect::<Vec<_>>();
+ let mut components = meta.split_ascii_whitespace();
- let path = components[0].to_string();
+ let path = components.next().expect("fixture meta must start with a path").to_string();
assert!(path.starts_with('/'), "fixture path does not start with `/`: {path:?}");
let mut krate = None;
let mut deps = Vec::new();
let mut extern_prelude = None;
let mut edition = None;
- let mut cfg_atoms = Vec::new();
- let mut cfg_key_values = Vec::new();
+ let mut cfgs = Vec::new();
let mut env = FxHashMap::default();
let mut introduce_new_source_root = None;
+ let mut library = false;
let mut target_data_layout = Some(
"e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128".to_string(),
);
- for component in components[1..].iter() {
+ for component in components {
+ if component == "library" {
+ library = true;
+ continue;
+ }
+
let (key, value) =
component.split_once(':').unwrap_or_else(|| panic!("invalid meta line: {meta:?}"));
match key {
@@ -212,8 +271,8 @@ impl FixtureWithProjectMeta {
"cfg" => {
for entry in value.split(',') {
match entry.split_once('=') {
- Some((k, v)) => cfg_key_values.push((k.to_string(), v.to_string())),
- None => cfg_atoms.push(entry.to_string()),
+ Some((k, v)) => cfgs.push((k.to_string(), Some(v.to_string()))),
+ None => cfgs.push((entry.to_string(), None)),
}
}
}
@@ -243,11 +302,11 @@ impl FixtureWithProjectMeta {
krate,
deps,
extern_prelude,
- cfg_atoms,
- cfg_key_values,
+ cfgs,
edition,
env,
introduce_new_source_root,
+ library,
target_data_layout,
}
}
diff --git a/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs b/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs
index 266bc2391..c765f4244 100644
--- a/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs
+++ b/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs
@@ -20,6 +20,7 @@
//! deref_mut: deref
//! deref: sized
//! derive:
+//! discriminant:
//! drop:
//! eq: sized
//! error: fmt
@@ -36,6 +37,7 @@
//! iterator: option
//! iterators: iterator, fn
//! manually_drop: drop
+//! non_null:
//! non_zero:
//! option: panic
//! ord: eq, option
@@ -129,6 +131,14 @@ pub mod marker {
#[lang = "phantom_data"]
pub struct PhantomData<T: ?Sized>;
// endregion:phantom_data
+
+ // region:discriminant
+ #[lang = "discriminant_kind"]
+ pub trait DiscriminantKind {
+ #[lang = "discriminant_type"]
+ type Discriminant;
+ }
+ // endregion:discriminant
}
// region:default
@@ -354,6 +364,11 @@ pub mod mem {
pub fn size_of<T>() -> usize;
}
// endregion:size_of
+
+ // region:discriminant
+ use crate::marker::DiscriminantKind;
+ pub struct Discriminant<T>(<T as DiscriminantKind>::Discriminant);
+ // endregion:discriminant
}
pub mod ptr {
@@ -377,6 +392,19 @@ pub mod ptr {
type Metadata;
}
// endregion:pointee
+ // region:non_null
+ #[rustc_layout_scalar_valid_range_start(1)]
+ #[rustc_nonnull_optimization_guaranteed]
+ pub struct NonNull<T: ?Sized> {
+ pointer: *const T,
+ }
+ // region:coerce_unsized
+ impl<T: ?Sized, U: ?Sized> crate::ops::CoerceUnsized<NonNull<U>> for NonNull<T> where
+ T: crate::marker::Unsize<U>
+ {
+ }
+ // endregion:coerce_unsized
+ // endregion:non_null
}
pub mod ops {
@@ -1287,6 +1315,11 @@ mod macros {
pub macro derive($item:item) {
/* compiler built-in */
}
+
+ #[rustc_builtin_macro]
+ pub macro derive_const($item:item) {
+ /* compiler built-in */
+ }
}
// endregion:derive
@@ -1354,24 +1387,24 @@ pub mod error {
pub mod prelude {
pub mod v1 {
pub use crate::{
- clone::Clone, // :clone
- cmp::{Eq, PartialEq}, // :eq
- cmp::{Ord, PartialOrd}, // :ord
- convert::AsRef, // :as_ref
- convert::{From, Into}, // :from
- default::Default, // :default
- iter::{IntoIterator, Iterator}, // :iterator
- macros::builtin::derive, // :derive
- marker::Copy, // :copy
- marker::Send, // :send
- marker::Sized, // :sized
- marker::Sync, // :sync
- mem::drop, // :drop
- ops::Drop, // :drop
- ops::{Fn, FnMut, FnOnce}, // :fn
- option::Option::{self, None, Some}, // :option
- panic, // :panic
- result::Result::{self, Err, Ok}, // :result
+ clone::Clone, // :clone
+ cmp::{Eq, PartialEq}, // :eq
+ cmp::{Ord, PartialOrd}, // :ord
+ convert::AsRef, // :as_ref
+ convert::{From, Into}, // :from
+ default::Default, // :default
+ iter::{IntoIterator, Iterator}, // :iterator
+ macros::builtin::{derive, derive_const}, // :derive
+ marker::Copy, // :copy
+ marker::Send, // :send
+ marker::Sized, // :sized
+ marker::Sync, // :sync
+ mem::drop, // :drop
+ ops::Drop, // :drop
+ ops::{Fn, FnMut, FnOnce}, // :fn
+ option::Option::{self, None, Some}, // :option
+ panic, // :panic
+ result::Result::{self, Err, Ok}, // :result
};
}
diff --git a/src/tools/rust-analyzer/crates/tt/src/buffer.rs b/src/tools/rust-analyzer/crates/tt/src/buffer.rs
index 0615a3763..cade2e9f6 100644
--- a/src/tools/rust-analyzer/crates/tt/src/buffer.rs
+++ b/src/tools/rust-analyzer/crates/tt/src/buffer.rs
@@ -134,7 +134,7 @@ pub enum TokenTreeRef<'a, Span> {
Leaf(&'a Leaf<Span>, &'a TokenTree<Span>),
}
-impl<'a, Span: Clone> TokenTreeRef<'a, Span> {
+impl<Span: Clone> TokenTreeRef<'_, Span> {
pub fn cloned(&self) -> TokenTree<Span> {
match self {
TokenTreeRef::Subtree(subtree, tt) => match tt {
@@ -153,13 +153,13 @@ pub struct Cursor<'a, Span> {
ptr: EntryPtr,
}
-impl<'a, Span> PartialEq for Cursor<'a, Span> {
+impl<Span> PartialEq for Cursor<'_, Span> {
fn eq(&self, other: &Cursor<'_, Span>) -> bool {
self.ptr == other.ptr && std::ptr::eq(self.buffer, other.buffer)
}
}
-impl<'a, Span> Eq for Cursor<'a, Span> {}
+impl<Span> Eq for Cursor<'_, Span> {}
impl<'a, Span> Cursor<'a, Span> {
/// Check whether it is eof
diff --git a/src/tools/rust-analyzer/crates/tt/src/lib.rs b/src/tools/rust-analyzer/crates/tt/src/lib.rs
index c2ebf0374..b5a72bec0 100644
--- a/src/tools/rust-analyzer/crates/tt/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/tt/src/lib.rs
@@ -65,7 +65,22 @@ pub mod token_id {
}
impl TokenTree {
pub const fn empty() -> Self {
- Self::Subtree(Subtree { delimiter: Delimiter::unspecified(), token_trees: vec![] })
+ Self::Subtree(Subtree::empty())
+ }
+ }
+
+ impl Subtree {
+ pub fn visit_ids(&mut self, f: &mut impl FnMut(TokenId) -> TokenId) {
+ self.delimiter.open = f(self.delimiter.open);
+ self.delimiter.close = f(self.delimiter.close);
+ self.token_trees.iter_mut().for_each(|tt| match tt {
+ crate::TokenTree::Leaf(leaf) => match leaf {
+ crate::Leaf::Literal(it) => it.span = f(it.span),
+ crate::Leaf::Punct(it) => it.span = f(it.span),
+ crate::Leaf::Ident(it) => it.span = f(it.span),
+ },
+ crate::TokenTree::Subtree(s) => s.visit_ids(f),
+ })
}
}
}
@@ -107,7 +122,6 @@ impl_from!(Literal<Span>, Punct<Span>, Ident<Span> for Leaf);
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Subtree<Span> {
- // FIXME, this should not be Option
pub delimiter: Delimiter<Span>,
pub token_trees: Vec<TokenTree<Span>>,
}
diff --git a/src/tools/rust-analyzer/crates/vfs-notify/Cargo.toml b/src/tools/rust-analyzer/crates/vfs-notify/Cargo.toml
index 5d61a2272..95c514251 100644
--- a/src/tools/rust-analyzer/crates/vfs-notify/Cargo.toml
+++ b/src/tools/rust-analyzer/crates/vfs-notify/Cargo.toml
@@ -15,7 +15,8 @@ doctest = false
tracing = "0.1.35"
walkdir = "2.3.2"
crossbeam-channel = "0.5.5"
-notify = "5.0"
+# We demand 5.1.0 as any higher version pulls in a new windows-sys dupe
+notify = "=5.1.0"
stdx.workspace = true
vfs.workspace = true
diff --git a/src/tools/rust-analyzer/crates/vfs/Cargo.toml b/src/tools/rust-analyzer/crates/vfs/Cargo.toml
index 3ae3dc83c..c35785cf9 100644
--- a/src/tools/rust-analyzer/crates/vfs/Cargo.toml
+++ b/src/tools/rust-analyzer/crates/vfs/Cargo.toml
@@ -14,7 +14,7 @@ doctest = false
[dependencies]
rustc-hash = "1.1.0"
fst = "0.4.7"
-indexmap = "1.9.1"
+indexmap = "2.0.0"
nohash-hasher.workspace = true
paths.workspace = true
diff --git a/src/tools/rust-analyzer/crates/vfs/src/vfs_path.rs b/src/tools/rust-analyzer/crates/vfs/src/vfs_path.rs
index d327f2edf..52ada32bd 100644
--- a/src/tools/rust-analyzer/crates/vfs/src/vfs_path.rs
+++ b/src/tools/rust-analyzer/crates/vfs/src/vfs_path.rs
@@ -292,8 +292,8 @@ impl From<AbsPathBuf> for VfsPath {
impl fmt::Display for VfsPath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.0 {
- VfsPathRepr::PathBuf(it) => fmt::Display::fmt(&it.display(), f),
- VfsPathRepr::VirtualPath(VirtualPath(it)) => fmt::Display::fmt(it, f),
+ VfsPathRepr::PathBuf(it) => it.fmt(f),
+ VfsPathRepr::VirtualPath(VirtualPath(it)) => it.fmt(f),
}
}
}
@@ -307,8 +307,8 @@ impl fmt::Debug for VfsPath {
impl fmt::Debug for VfsPathRepr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self {
- VfsPathRepr::PathBuf(it) => fmt::Debug::fmt(&it.display(), f),
- VfsPathRepr::VirtualPath(VirtualPath(it)) => fmt::Debug::fmt(&it, f),
+ VfsPathRepr::PathBuf(it) => it.fmt(f),
+ VfsPathRepr::VirtualPath(VirtualPath(it)) => it.fmt(f),
}
}
}