Unnamed repository; edit this file 'description' to name the repository.
Auto merge of #129817 - matthiaskrgr:rollup-ll2ld5m, r=matthiaskrgr
Rollup of 12 pull requests Successful merges: - #129659 (const fn stability checking: also check declared language features) - #129711 (Expand NLL MIR dumps) - #129730 (f32 docs: define 'arithmetic' operations) - #129733 (Subtree update of `rust-analyzer`) - #129749 (llvm-wrapper: adapt for LLVM API changes) - #129757 (Add a test for trait solver overflow in MIR inliner cycle detection) - #129760 (Make the "detect-old-time" UI test more representative) - #129767 (Remove `#[macro_use] extern crate tracing`, round 4) - #129774 (Remove `#[macro_use] extern crate tracing` from rustdoc and rustfmt) - #129785 (Miri subtree update) - #129791 (mark joboet as on vacation) - #129812 (interpret, codegen: tweak some comments and checks regarding Box with custom allocator) Failed merges: - #129777 (Add `unreachable_pub`, round 4) r? `@ghost` `@rustbot` modify labels: rollup
bors 2024-08-31
parent 3dfff01 · parent fb3a0ea · commit 883439d
-rw-r--r--.git-blame-ignore-revs7
-rw-r--r--Cargo.lock7
-rw-r--r--Cargo.toml5
-rw-r--r--LICENSE-APACHE25
-rw-r--r--crates/hir-def/Cargo.toml1
-rw-r--r--crates/hir-def/src/body.rs14
-rw-r--r--crates/hir-def/src/body/pretty.rs65
-rw-r--r--crates/hir-def/src/body/tests.rs6
-rw-r--r--crates/hir-def/src/find_path.rs7
-rw-r--r--crates/hir-def/src/generics.rs24
-rw-r--r--crates/hir-def/src/hir/format_args.rs2
-rw-r--r--crates/hir-def/src/hir/type_ref.rs13
-rw-r--r--crates/hir-def/src/import_map.rs16
-rw-r--r--crates/hir-def/src/item_scope.rs8
-rw-r--r--crates/hir-def/src/item_tree.rs18
-rw-r--r--crates/hir-def/src/item_tree/pretty.rs86
-rw-r--r--crates/hir-def/src/item_tree/tests.rs3
-rw-r--r--crates/hir-def/src/lang_item.rs27
-rw-r--r--crates/hir-def/src/lib.rs2
-rw-r--r--crates/hir-def/src/macro_expansion_tests/mod.rs20
-rw-r--r--crates/hir-def/src/nameres.rs6
-rw-r--r--crates/hir-def/src/nameres/collector.rs16
-rw-r--r--crates/hir-def/src/nameres/tests/macros.rs5
-rw-r--r--crates/hir-def/src/nameres/tests/mod_resolution.rs8
-rw-r--r--crates/hir-def/src/path.rs19
-rw-r--r--crates/hir-def/src/pretty.rs84
-rw-r--r--crates/hir-expand/src/eager.rs3
-rw-r--r--crates/hir-expand/src/files.rs9
-rw-r--r--crates/hir-expand/src/lib.rs9
-rw-r--r--crates/hir-expand/src/mod_path.rs46
-rw-r--r--crates/hir-expand/src/name.rs90
-rw-r--r--crates/hir-ty/Cargo.toml1
-rw-r--r--crates/hir-ty/src/autoderef.rs73
-rw-r--r--crates/hir-ty/src/chalk_db.rs18
-rw-r--r--crates/hir-ty/src/consteval/tests.rs21
-rw-r--r--crates/hir-ty/src/diagnostics/decl_check.rs70
-rw-r--r--crates/hir-ty/src/diagnostics/expr.rs30
-rw-r--r--crates/hir-ty/src/diagnostics/match_check.rs35
-rw-r--r--crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs7
-rw-r--r--crates/hir-ty/src/display.rs181
-rw-r--r--crates/hir-ty/src/infer.rs113
-rw-r--r--crates/hir-ty/src/infer/closure.rs482
-rw-r--r--crates/hir-ty/src/infer/coerce.rs37
-rw-r--r--crates/hir-ty/src/infer/expr.rs60
-rw-r--r--crates/hir-ty/src/infer/mutability.rs4
-rw-r--r--crates/hir-ty/src/infer/pat.rs17
-rw-r--r--crates/hir-ty/src/infer/unify.rs14
-rw-r--r--crates/hir-ty/src/layout/tests.rs13
-rw-r--r--crates/hir-ty/src/lib.rs9
-rw-r--r--crates/hir-ty/src/lower.rs92
-rw-r--r--crates/hir-ty/src/method_resolution.rs11
-rw-r--r--crates/hir-ty/src/mir.rs30
-rw-r--r--crates/hir-ty/src/mir/eval.rs111
-rw-r--r--crates/hir-ty/src/mir/eval/shim.rs6
-rw-r--r--crates/hir-ty/src/mir/eval/tests.rs6
-rw-r--r--crates/hir-ty/src/mir/lower.rs75
-rw-r--r--crates/hir-ty/src/mir/lower/pattern_matching.rs3
-rw-r--r--crates/hir-ty/src/mir/pretty.rs48
-rw-r--r--crates/hir-ty/src/tests.rs20
-rw-r--r--crates/hir-ty/src/tests/closure_captures.rs433
-rw-r--r--crates/hir-ty/src/tests/coercion.rs22
-rw-r--r--crates/hir-ty/src/tests/method_resolution.rs14
-rw-r--r--crates/hir-ty/src/tests/regression.rs87
-rw-r--r--crates/hir-ty/src/tests/simple.rs37
-rw-r--r--crates/hir-ty/src/tls.rs19
-rw-r--r--crates/hir-ty/src/traits.rs17
-rw-r--r--crates/hir/Cargo.toml1
-rw-r--r--crates/hir/src/attrs.rs8
-rw-r--r--crates/hir/src/display.rs69
-rw-r--r--crates/hir/src/lib.rs193
-rw-r--r--crates/hir/src/semantics.rs391
-rw-r--r--crates/hir/src/symbols.rs16
-rw-r--r--crates/hir/src/term_search/expr.rs55
-rw-r--r--crates/ide-assists/src/handlers/add_missing_impl_members.rs23
-rw-r--r--crates/ide-assists/src/handlers/add_missing_match_arms.rs3
-rw-r--r--crates/ide-assists/src/handlers/auto_import.rs9
-rw-r--r--crates/ide-assists/src/handlers/bool_to_enum.rs6
-rw-r--r--crates/ide-assists/src/handlers/convert_bool_then.rs2
-rw-r--r--crates/ide-assists/src/handlers/convert_closure_to_fn.rs1271
-rw-r--r--crates/ide-assists/src/handlers/convert_into_to_from.rs5
-rw-r--r--crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs12
-rw-r--r--crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs31
-rw-r--r--crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs2
-rw-r--r--crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs29
-rw-r--r--crates/ide-assists/src/handlers/convert_while_to_loop.rs97
-rw-r--r--crates/ide-assists/src/handlers/destructure_struct_binding.rs8
-rw-r--r--crates/ide-assists/src/handlers/expand_glob_import.rs4
-rw-r--r--crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs14
-rw-r--r--crates/ide-assists/src/handlers/extract_function.rs78
-rw-r--r--crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs14
-rw-r--r--crates/ide-assists/src/handlers/extract_variable.rs148
-rw-r--r--crates/ide-assists/src/handlers/fill_record_pattern_fields.rs3
-rw-r--r--crates/ide-assists/src/handlers/fix_visibility.rs14
-rw-r--r--crates/ide-assists/src/handlers/generate_delegate_methods.rs5
-rw-r--r--crates/ide-assists/src/handlers/generate_delegate_trait.rs24
-rw-r--r--crates/ide-assists/src/handlers/generate_deref.rs12
-rw-r--r--crates/ide-assists/src/handlers/generate_documentation_template.rs9
-rw-r--r--crates/ide-assists/src/handlers/generate_function.rs25
-rw-r--r--crates/ide-assists/src/handlers/generate_getter_or_setter.rs30
-rw-r--r--crates/ide-assists/src/handlers/generate_new.rs6
-rw-r--r--crates/ide-assists/src/handlers/inline_const_as_literal.rs11
-rw-r--r--crates/ide-assists/src/handlers/move_const_to_impl.rs10
-rw-r--r--crates/ide-assists/src/handlers/move_from_mod_rs.rs2
-rw-r--r--crates/ide-assists/src/handlers/move_module_to_file.rs2
-rw-r--r--crates/ide-assists/src/handlers/move_to_mod_rs.rs2
-rw-r--r--crates/ide-assists/src/handlers/qualify_method_call.rs2
-rw-r--r--crates/ide-assists/src/handlers/qualify_path.rs22
-rw-r--r--crates/ide-assists/src/handlers/remove_dbg.rs4
-rw-r--r--crates/ide-assists/src/handlers/reorder_fields.rs13
-rw-r--r--crates/ide-assists/src/handlers/reorder_impl_items.rs5
-rw-r--r--crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs5
-rw-r--r--crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs38
-rw-r--r--crates/ide-assists/src/handlers/term_search.rs6
-rw-r--r--crates/ide-assists/src/handlers/toggle_async_sugar.rs3
-rw-r--r--crates/ide-assists/src/handlers/toggle_macro_delimiter.rs256
-rw-r--r--crates/ide-assists/src/handlers/unwrap_block.rs58
-rw-r--r--crates/ide-assists/src/lib.rs4
-rw-r--r--crates/ide-assists/src/tests/generated.rs57
-rw-r--r--crates/ide-assists/src/utils.rs20
-rw-r--r--crates/ide-assists/src/utils/suggest_name.rs19
-rw-r--r--crates/ide-completion/Cargo.toml1
-rw-r--r--crates/ide-completion/src/completions.rs13
-rw-r--r--crates/ide-completion/src/completions/attribute.rs40
-rw-r--r--crates/ide-completion/src/completions/attribute/cfg.rs13
-rw-r--r--crates/ide-completion/src/completions/attribute/derive.rs10
-rw-r--r--crates/ide-completion/src/completions/attribute/lint.rs3
-rw-r--r--crates/ide-completion/src/completions/attribute/macro_use.rs3
-rw-r--r--crates/ide-completion/src/completions/attribute/repr.rs7
-rw-r--r--crates/ide-completion/src/completions/dot.rs1
-rw-r--r--crates/ide-completion/src/completions/env_vars.rs2
-rw-r--r--crates/ide-completion/src/completions/extern_abi.rs9
-rw-r--r--crates/ide-completion/src/completions/extern_crate.rs3
-rw-r--r--crates/ide-completion/src/completions/flyimport.rs2
-rw-r--r--crates/ide-completion/src/completions/fn_param.rs8
-rw-r--r--crates/ide-completion/src/completions/format_string.rs6
-rw-r--r--crates/ide-completion/src/completions/item_list/trait_impl.rs14
-rw-r--r--crates/ide-completion/src/completions/lifetime.rs2
-rw-r--r--crates/ide-completion/src/completions/mod_.rs9
-rw-r--r--crates/ide-completion/src/completions/postfix.rs13
-rw-r--r--crates/ide-completion/src/completions/record.rs2
-rw-r--r--crates/ide-completion/src/completions/snippet.rs3
-rw-r--r--crates/ide-completion/src/completions/use_.rs6
-rw-r--r--crates/ide-completion/src/context.rs10
-rw-r--r--crates/ide-completion/src/context/analysis.rs2
-rw-r--r--crates/ide-completion/src/item.rs11
-rw-r--r--crates/ide-completion/src/lib.rs9
-rw-r--r--crates/ide-completion/src/render.rs70
-rw-r--r--crates/ide-completion/src/render/const_.rs13
-rw-r--r--crates/ide-completion/src/render/function.rs44
-rw-r--r--crates/ide-completion/src/render/literal.rs12
-rw-r--r--crates/ide-completion/src/render/macro_.rs9
-rw-r--r--crates/ide-completion/src/render/pattern.rs35
-rw-r--r--crates/ide-completion/src/render/type_alias.rs18
-rw-r--r--crates/ide-completion/src/render/union_literal.rs34
-rw-r--r--crates/ide-completion/src/render/variant.rs20
-rw-r--r--crates/ide-completion/src/tests.rs24
-rw-r--r--crates/ide-completion/src/tests/flyimport.rs36
-rw-r--r--crates/ide-completion/src/tests/predicate.rs4
-rw-r--r--crates/ide-completion/src/tests/raw_identifiers.rs84
-rw-r--r--crates/ide-completion/src/tests/type_pos.rs8
-rw-r--r--crates/ide-db/Cargo.toml1
-rw-r--r--crates/ide-db/src/defs.rs81
-rw-r--r--crates/ide-db/src/famous_defs.rs9
-rw-r--r--crates/ide-db/src/helpers.rs17
-rw-r--r--crates/ide-db/src/imports/import_assets.rs87
-rw-r--r--crates/ide-db/src/path_transform.rs18
-rw-r--r--crates/ide-db/src/rename.rs74
-rw-r--r--crates/ide-db/src/search.rs514
-rw-r--r--crates/ide-db/src/syntax_helpers/format_string_exprs.rs1
-rw-r--r--crates/ide-db/src/syntax_helpers/insert_whitespace_into_node.rs3
-rw-r--r--crates/ide-db/src/syntax_helpers/node_ext.rs18
-rw-r--r--crates/ide-db/src/traits.rs21
-rw-r--r--crates/ide-db/src/ty_filter.rs7
-rw-r--r--crates/ide-db/src/use_trivial_constructor.rs4
-rw-r--r--crates/ide-diagnostics/Cargo.toml1
-rw-r--r--crates/ide-diagnostics/src/handlers/expected_function.rs2
-rw-r--r--crates/ide-diagnostics/src/handlers/json_is_not_rust.rs15
-rw-r--r--crates/ide-diagnostics/src/handlers/missing_fields.rs24
-rw-r--r--crates/ide-diagnostics/src/handlers/moved_out_of_ref.rs2
-rw-r--r--crates/ide-diagnostics/src/handlers/mutability_errors.rs2
-rw-r--r--crates/ide-diagnostics/src/handlers/private_assoc_item.rs2
-rw-r--r--crates/ide-diagnostics/src/handlers/private_field.rs4
-rw-r--r--crates/ide-diagnostics/src/handlers/trait_impl_missing_assoc_item.rs2
-rw-r--r--crates/ide-diagnostics/src/handlers/trait_impl_redundant_assoc_item.rs10
-rw-r--r--crates/ide-diagnostics/src/handlers/type_mismatch.rs12
-rw-r--r--crates/ide-diagnostics/src/handlers/typed_hole.rs5
-rw-r--r--crates/ide-diagnostics/src/handlers/undeclared_label.rs2
-rw-r--r--crates/ide-diagnostics/src/handlers/unlinked_file.rs11
-rw-r--r--crates/ide-diagnostics/src/handlers/unreachable_label.rs2
-rw-r--r--crates/ide-diagnostics/src/handlers/unresolved_field.rs4
-rw-r--r--crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs2
-rw-r--r--crates/ide-diagnostics/src/handlers/unresolved_method.rs9
-rw-r--r--crates/ide-diagnostics/src/handlers/unused_variables.rs20
-rw-r--r--crates/ide-diagnostics/src/lib.rs60
-rw-r--r--crates/ide-ssr/src/matching.rs10
-rw-r--r--crates/ide-ssr/src/replacing.rs8
-rw-r--r--crates/ide/src/call_hierarchy.rs4
-rw-r--r--crates/ide/src/doc_links.rs72
-rw-r--r--crates/ide/src/expand_macro.rs65
-rw-r--r--crates/ide/src/extend_selection.rs10
-rw-r--r--crates/ide/src/goto_declaration.rs4
-rw-r--r--crates/ide/src/goto_definition.rs39
-rw-r--r--crates/ide/src/goto_implementation.rs93
-rw-r--r--crates/ide/src/goto_type_definition.rs4
-rw-r--r--crates/ide/src/highlight_related.rs6
-rw-r--r--crates/ide/src/hover.rs262
-rw-r--r--crates/ide/src/hover/render.rs154
-rw-r--r--crates/ide/src/hover/tests.rs106
-rw-r--r--crates/ide/src/inlay_hints.rs14
-rw-r--r--crates/ide/src/inlay_hints/adjustment.rs6
-rw-r--r--crates/ide/src/inlay_hints/bind_pat.rs6
-rw-r--r--crates/ide/src/inlay_hints/chaining.rs4
-rw-r--r--crates/ide/src/inlay_hints/closing_brace.rs8
-rw-r--r--crates/ide/src/inlay_hints/closure_ret.rs4
-rw-r--r--crates/ide/src/inlay_hints/implicit_drop.rs4
-rw-r--r--crates/ide/src/inlay_hints/param_name.rs4
-rw-r--r--crates/ide/src/lib.rs4
-rw-r--r--crates/ide/src/markup.rs2
-rw-r--r--crates/ide/src/moniker.rs49
-rw-r--r--crates/ide/src/navigation_target.rs100
-rw-r--r--crates/ide/src/references.rs249
-rw-r--r--crates/ide/src/rename.rs259
-rw-r--r--crates/ide/src/runnables.rs44
-rw-r--r--crates/ide/src/signature_help.rs110
-rw-r--r--crates/ide/src/static_index.rs81
-rw-r--r--crates/ide/src/syntax_highlighting.rs70
-rw-r--r--crates/ide/src/syntax_highlighting/highlight.rs9
-rw-r--r--crates/ide/src/syntax_highlighting/macro_.rs2
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_macros.html2
-rw-r--r--crates/ide/src/view_item_tree.rs2
-rw-r--r--crates/ide/src/view_memory_layout.rs16
-rw-r--r--crates/intern/src/symbol/symbols.rs2
-rw-r--r--crates/parser/src/grammar/generic_args.rs13
-rw-r--r--crates/parser/src/grammar/generic_params.rs3
-rw-r--r--crates/parser/src/grammar/paths.rs21
-rw-r--r--crates/parser/src/lexed_str.rs13
-rw-r--r--crates/parser/src/shortcuts.rs10
-rw-r--r--crates/parser/src/syntax_kind.rs9
-rw-r--r--crates/parser/src/syntax_kind/generated.rs146
-rw-r--r--crates/parser/test_data/generated/runner.rs14
-rw-r--r--crates/parser/test_data/parser/inline/ok/associated_return_type_bounds.rs1
-rw-r--r--crates/parser/test_data/parser/inline/ok/return_type_syntax_assoc_type_bound.rast49
-rw-r--r--crates/parser/test_data/parser/inline/ok/return_type_syntax_assoc_type_bound.rs1
-rw-r--r--crates/parser/test_data/parser/inline/ok/return_type_syntax_in_path.rast50
-rw-r--r--crates/parser/test_data/parser/inline/ok/return_type_syntax_in_path.rs4
-rw-r--r--crates/proc-macro-api/src/lib.rs3
-rw-r--r--crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs17
-rw-r--r--crates/proc-macro-srv/src/server_impl/token_id.rs8
-rw-r--r--crates/proc-macro-srv/src/server_impl/token_stream.rs2
-rw-r--r--crates/project-model/src/build_dependencies.rs24
-rw-r--r--crates/project-model/src/cargo_workspace.rs61
-rw-r--r--crates/project-model/src/env.rs14
-rw-r--r--crates/project-model/src/lib.rs9
-rw-r--r--crates/project-model/src/project_json.rs34
-rw-r--r--crates/project-model/src/sysroot.rs25
-rw-r--r--crates/project-model/src/tests.rs9
-rw-r--r--crates/project-model/src/workspace.rs60
-rw-r--r--crates/project-model/test_data/cfg-groups.json26
-rw-r--r--crates/project-model/test_data/output/rust_project_cfg_groups.txt545
-rw-r--r--crates/rust-analyzer/src/bin/main.rs2
-rw-r--r--crates/rust-analyzer/src/cli.rs4
-rw-r--r--crates/rust-analyzer/src/cli/analysis_stats.rs77
-rw-r--r--crates/rust-analyzer/src/cli/flags.rs9
-rw-r--r--crates/rust-analyzer/src/cli/lsif.rs10
-rw-r--r--crates/rust-analyzer/src/cli/scip.rs31
-rw-r--r--crates/rust-analyzer/src/command.rs1
-rw-r--r--crates/rust-analyzer/src/config.rs383
-rw-r--r--crates/rust-analyzer/src/diagnostics.rs8
-rw-r--r--crates/rust-analyzer/src/diagnostics/to_proto.rs9
-rw-r--r--crates/rust-analyzer/src/flycheck.rs78
-rw-r--r--crates/rust-analyzer/src/global_state.rs7
-rw-r--r--crates/rust-analyzer/src/handlers/dispatch.rs18
-rw-r--r--crates/rust-analyzer/src/handlers/notification.rs56
-rw-r--r--crates/rust-analyzer/src/handlers/request.rs10
-rw-r--r--crates/rust-analyzer/src/main_loop.rs4
-rw-r--r--crates/rust-analyzer/src/op_queue.rs20
-rw-r--r--crates/rust-analyzer/src/reload.rs81
-rw-r--r--crates/rust-analyzer/src/target_spec.rs1
-rw-r--r--crates/rust-analyzer/src/test_runner.rs5
-rw-r--r--crates/rust-analyzer/tests/crate_graph.rs5
-rw-r--r--crates/rust-analyzer/tests/slow-tests/ratoml.rs30
-rw-r--r--crates/rust-analyzer/tests/slow-tests/support.rs34
-rw-r--r--crates/salsa/LICENSE-APACHE25
-rw-r--r--crates/syntax-bridge/src/lib.rs4
-rw-r--r--crates/syntax-bridge/src/to_parser_input.rs10
-rw-r--r--crates/syntax/Cargo.toml3
-rw-r--r--crates/syntax/rust.ungram32
-rw-r--r--crates/syntax/src/ast/edit.rs2
-rw-r--r--crates/syntax/src/ast/generated/nodes.rs38
-rw-r--r--crates/syntax/src/ast/make.rs11
-rw-r--r--crates/syntax/src/ast/node_ext.rs11
-rw-r--r--crates/syntax/src/hacks.rs4
-rw-r--r--crates/syntax/src/utils.rs5
-rw-r--r--crates/test-utils/src/minicore.rs11
-rw-r--r--crates/tt/src/lib.rs2
-rw-r--r--crates/vfs/src/vfs_path.rs14
-rw-r--r--docs/user/generated_config.adoc32
-rw-r--r--docs/user/manual.adoc12
-rw-r--r--editors/code/package.json58
-rw-r--r--editors/code/src/config.ts4
-rw-r--r--rust-version2
-rw-r--r--xtask/src/codegen/grammar.rs113
-rw-r--r--xtask/src/codegen/grammar/ast_src.rs74
303 files changed, 9203 insertions, 2888 deletions
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index d5951a9420..2ccdc8c042 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -7,9 +7,10 @@
# prettier format
f247090558c9ba3c551566eae5882b7ca865225f
-# subtree syncs
-932d85b52946d917deab2c23ead552f7f713b828
+# pre-josh subtree syncs
3e358a6827d83e8d6473913a5e304734aadfed04
+932d85b52946d917deab2c23ead552f7f713b828
9d2cb42a413e51deb50b36794a2e1605381878fc
-f532576ac53ddcc666bc8d59e0b6437065e2f599
+b2f6fd4f961fc7e4fbfdb80cae2e6065f8436f15
c48062fe2ab9a2d913d1985a6b0aec4bf936bfc1
+f532576ac53ddcc666bc8d59e0b6437065e2f599
diff --git a/Cargo.lock b/Cargo.lock
index 41dc440509..4e9319f13a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -495,7 +495,6 @@ dependencies = [
"hir-ty",
"intern",
"itertools",
- "once_cell",
"rustc-hash",
"smallvec",
"span",
@@ -528,7 +527,6 @@ dependencies = [
"la-arena 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"limit",
"mbe",
- "once_cell",
"ra-ap-rustc_abi",
"ra-ap-rustc_parse_format",
"rustc-hash",
@@ -595,7 +593,6 @@ dependencies = [
"la-arena 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"limit",
"nohash-hasher",
- "once_cell",
"oorandom",
"project-model",
"ra-ap-rustc_abi",
@@ -691,7 +688,6 @@ dependencies = [
"hir",
"ide-db",
"itertools",
- "once_cell",
"smallvec",
"stdx",
"syntax",
@@ -720,7 +716,6 @@ dependencies = [
"line-index 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr",
"nohash-hasher",
- "once_cell",
"parser",
"profile",
"rayon",
@@ -746,7 +741,6 @@ dependencies = [
"hir",
"ide-db",
"itertools",
- "once_cell",
"paths",
"serde_json",
"stdx",
@@ -1933,7 +1927,6 @@ dependencies = [
"expect-test",
"indexmap",
"itertools",
- "once_cell",
"parser",
"ra-ap-rustc_lexer",
"rayon",
diff --git a/Cargo.toml b/Cargo.toml
index 56e80e11e7..aa7bd2dc5f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,9 +19,10 @@ rowan.opt-level = 3
rustc-hash.opt-level = 3
smol_str.opt-level = 3
text-size.opt-level = 3
+serde.opt-level = 3
+salsa.opt-level = 3
# This speeds up `cargo xtask dist`.
miniz_oxide.opt-level = 3
-salsa.opt-level = 3
[profile.release]
incremental = true
@@ -184,6 +185,8 @@ style = { level = "warn", priority = -1 }
suspicious = { level = "warn", priority = -1 }
## allow following lints
+# subjective
+single_match = "allow"
# () makes a fine error in most cases
result_unit_err = "allow"
# We don't expose public APIs that matter like this
diff --git a/LICENSE-APACHE b/LICENSE-APACHE
index 16fe87b06e..1b5ec8b78e 100644
--- a/LICENSE-APACHE
+++ b/LICENSE-APACHE
@@ -174,28 +174,3 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
-
-APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
-Copyright [yyyy] [name of copyright owner]
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
diff --git a/crates/hir-def/Cargo.toml b/crates/hir-def/Cargo.toml
index 5b9d227e31..c8ba5da449 100644
--- a/crates/hir-def/Cargo.toml
+++ b/crates/hir-def/Cargo.toml
@@ -23,7 +23,6 @@ fst = { version = "0.4.7", default-features = false }
indexmap.workspace = true
itertools.workspace = true
la-arena.workspace = true
-once_cell = "1.17.0"
rustc-hash.workspace = true
tracing.workspace = true
smallvec.workspace = true
diff --git a/crates/hir-def/src/body.rs b/crates/hir-def/src/body.rs
index a988317e04..f5fe8f8770 100644
--- a/crates/hir-def/src/body.rs
+++ b/crates/hir-def/src/body.rs
@@ -14,7 +14,7 @@ use hir_expand::{name::Name, ExpandError, InFile};
use la_arena::{Arena, ArenaMap, Idx, RawIdx};
use rustc_hash::FxHashMap;
use smallvec::SmallVec;
-use span::MacroFileId;
+use span::{Edition, MacroFileId};
use syntax::{ast, AstPtr, SyntaxNodePtr};
use triomphe::Arc;
@@ -201,8 +201,13 @@ impl Body {
self.block_scopes.iter().map(move |&block| (block, db.block_def_map(block)))
}
- pub fn pretty_print(&self, db: &dyn DefDatabase, owner: DefWithBodyId) -> String {
- pretty::print_body_hir(db, self, owner)
+ pub fn pretty_print(
+ &self,
+ db: &dyn DefDatabase,
+ owner: DefWithBodyId,
+ edition: Edition,
+ ) -> String {
+ pretty::print_body_hir(db, self, owner, edition)
}
pub fn pretty_print_expr(
@@ -210,8 +215,9 @@ impl Body {
db: &dyn DefDatabase,
owner: DefWithBodyId,
expr: ExprId,
+ edition: Edition,
) -> String {
- pretty::print_expr_hir(db, self, owner, expr)
+ pretty::print_expr_hir(db, self, owner, expr, edition)
}
fn new(
diff --git a/crates/hir-def/src/body/pretty.rs b/crates/hir-def/src/body/pretty.rs
index edaee60937..55740a68ac 100644
--- a/crates/hir-def/src/body/pretty.rs
+++ b/crates/hir-def/src/body/pretty.rs
@@ -3,6 +3,7 @@
use std::fmt::{self, Write};
use itertools::Itertools;
+use span::Edition;
use crate::{
hir::{
@@ -15,20 +16,26 @@ use crate::{
use super::*;
-pub(super) fn print_body_hir(db: &dyn DefDatabase, body: &Body, owner: DefWithBodyId) -> String {
+pub(super) fn print_body_hir(
+ db: &dyn DefDatabase,
+ body: &Body,
+ owner: DefWithBodyId,
+ edition: Edition,
+) -> String {
let header = match owner {
- DefWithBodyId::FunctionId(it) => {
- it.lookup(db).id.resolved(db, |it| format!("fn {}", it.name.display(db.upcast())))
- }
+ DefWithBodyId::FunctionId(it) => it
+ .lookup(db)
+ .id
+ .resolved(db, |it| format!("fn {}", it.name.display(db.upcast(), edition))),
DefWithBodyId::StaticId(it) => it
.lookup(db)
.id
- .resolved(db, |it| format!("static {} = ", it.name.display(db.upcast()))),
+ .resolved(db, |it| format!("static {} = ", it.name.display(db.upcast(), edition))),
DefWithBodyId::ConstId(it) => it.lookup(db).id.resolved(db, |it| {
format!(
"const {} = ",
match &it.name {
- Some(name) => name.display(db.upcast()).to_string(),
+ Some(name) => name.display(db.upcast(), edition).to_string(),
None => "_".to_owned(),
}
)
@@ -39,13 +46,13 @@ pub(super) fn print_body_hir(db: &dyn DefDatabase, body: &Body, owner: DefWithBo
let enum_loc = loc.parent.lookup(db);
format!(
"enum {}::{}",
- enum_loc.id.item_tree(db)[enum_loc.id.value].name.display(db.upcast()),
- loc.id.item_tree(db)[loc.id.value].name.display(db.upcast()),
+ enum_loc.id.item_tree(db)[enum_loc.id.value].name.display(db.upcast(), edition),
+ loc.id.item_tree(db)[loc.id.value].name.display(db.upcast(), edition),
)
}
};
- let mut p = Printer { db, body, buf: header, indent_level: 0, needs_indent: false };
+ let mut p = Printer { db, body, buf: header, indent_level: 0, needs_indent: false, edition };
if let DefWithBodyId::FunctionId(it) = owner {
p.buf.push('(');
let function_data = &db.function_data(it);
@@ -86,8 +93,10 @@ pub(super) fn print_expr_hir(
body: &Body,
_owner: DefWithBodyId,
expr: ExprId,
+ edition: Edition,
) -> String {
- let mut p = Printer { db, body, buf: String::new(), indent_level: 0, needs_indent: false };
+ let mut p =
+ Printer { db, body, buf: String::new(), indent_level: 0, needs_indent: false, edition };
p.print_expr(expr);
p.buf
}
@@ -113,6 +122,7 @@ struct Printer<'a> {
buf: String,
indent_level: usize,
needs_indent: bool,
+ edition: Edition,
}
impl Write for Printer<'_> {
@@ -173,13 +183,14 @@ impl Printer<'_> {
Expr::OffsetOf(offset_of) => {
w!(self, "builtin#offset_of(");
self.print_type_ref(&offset_of.container);
+ let edition = self.edition;
w!(
self,
", {})",
offset_of
.fields
.iter()
- .format_with(".", |field, f| f(&field.display(self.db.upcast())))
+ .format_with(".", |field, f| f(&field.display(self.db.upcast(), edition)))
);
}
Expr::Path(path) => self.print_path(path),
@@ -201,7 +212,7 @@ impl Printer<'_> {
}
Expr::Loop { body, label } => {
if let Some(lbl) = label {
- w!(self, "{}: ", self.body[*lbl].name.display(self.db.upcast()));
+ w!(self, "{}: ", self.body[*lbl].name.display(self.db.upcast(), self.edition));
}
w!(self, "loop ");
self.print_expr(*body);
@@ -221,10 +232,11 @@ impl Printer<'_> {
}
Expr::MethodCall { receiver, method_name, args, generic_args } => {
self.print_expr(*receiver);
- w!(self, ".{}", method_name.display(self.db.upcast()));
+ w!(self, ".{}", method_name.display(self.db.upcast(), self.edition));
if let Some(args) = generic_args {
w!(self, "::<");
- print_generic_args(self.db, args, self).unwrap();
+ let edition = self.edition;
+ print_generic_args(self.db, args, self, edition).unwrap();
w!(self, ">");
}
w!(self, "(");
@@ -259,13 +271,13 @@ impl Printer<'_> {
Expr::Continue { label } => {
w!(self, "continue");
if let Some(lbl) = label {
- w!(self, " {}", self.body[*lbl].name.display(self.db.upcast()));
+ w!(self, " {}", self.body[*lbl].name.display(self.db.upcast(), self.edition));
}
}
Expr::Break { expr, label } => {
w!(self, "break");
if let Some(lbl) = label {
- w!(self, " {}", self.body[*lbl].name.display(self.db.upcast()));
+ w!(self, " {}", self.body[*lbl].name.display(self.db.upcast(), self.edition));
}
if let Some(expr) = expr {
self.whitespace();
@@ -307,9 +319,10 @@ impl Printer<'_> {
}
w!(self, "{{");
+ let edition = self.edition;
self.indented(|p| {
for field in &**fields {
- w!(p, "{}: ", field.name.display(self.db.upcast()));
+ w!(p, "{}: ", field.name.display(self.db.upcast(), edition));
p.print_expr(field.expr);
wln!(p, ",");
}
@@ -326,7 +339,7 @@ impl Printer<'_> {
}
Expr::Field { expr, name } => {
self.print_expr(*expr);
- w!(self, ".{}", name.display(self.db.upcast()));
+ w!(self, ".{}", name.display(self.db.upcast(), self.edition));
}
Expr::Await { expr } => {
self.print_expr(*expr);
@@ -464,8 +477,9 @@ impl Printer<'_> {
}
Expr::Literal(lit) => self.print_literal(lit),
Expr::Block { id: _, statements, tail, label } => {
- let label =
- label.map(|lbl| format!("{}: ", self.body[lbl].name.display(self.db.upcast())));
+ let label = label.map(|lbl| {
+ format!("{}: ", self.body[lbl].name.display(self.db.upcast(), self.edition))
+ });
self.print_block(label.as_deref(), statements, tail);
}
Expr::Unsafe { id: _, statements, tail } => {
@@ -539,9 +553,10 @@ impl Printer<'_> {
}
w!(self, " {{");
+ let edition = self.edition;
self.indented(|p| {
for arg in args.iter() {
- w!(p, "{}: ", arg.name.display(self.db.upcast()));
+ w!(p, "{}: ", arg.name.display(self.db.upcast(), edition));
p.print_pat(arg.pat);
wln!(p, ",");
}
@@ -686,11 +701,13 @@ impl Printer<'_> {
}
fn print_type_ref(&mut self, ty: &TypeRef) {
- print_type_ref(self.db, ty, self).unwrap();
+ let edition = self.edition;
+ print_type_ref(self.db, ty, self, edition).unwrap();
}
fn print_path(&mut self, path: &Path) {
- print_path(self.db, path, self).unwrap();
+ let edition = self.edition;
+ print_path(self.db, path, self, edition).unwrap();
}
fn print_binding(&mut self, id: BindingId) {
@@ -701,6 +718,6 @@ impl Printer<'_> {
BindingAnnotation::Ref => "ref ",
BindingAnnotation::RefMut => "ref mut ",
};
- w!(self, "{}{}", mode, name.display(self.db.upcast()));
+ w!(self, "{}{}", mode, name.display(self.db.upcast(), self.edition));
}
}
diff --git a/crates/hir-def/src/body/tests.rs b/crates/hir-def/src/body/tests.rs
index 0011d3a20c..38fc01d8fc 100644
--- a/crates/hir-def/src/body/tests.rs
+++ b/crates/hir-def/src/body/tests.rs
@@ -219,7 +219,7 @@ fn main() {
},
);
}"#]]
- .assert_eq(&body.pretty_print(&db, def))
+ .assert_eq(&body.pretty_print(&db, def, Edition::CURRENT))
}
#[test]
@@ -285,7 +285,7 @@ impl SsrError {
),
);
}"#]]
- .assert_eq(&body.pretty_print(&db, def))
+ .assert_eq(&body.pretty_print(&db, def, Edition::CURRENT))
}
#[test]
@@ -333,5 +333,5 @@ fn f(a: i32, b: u32) -> String {
);
};
}"#]]
- .assert_eq(&body.pretty_print(&db, def))
+ .assert_eq(&body.pretty_print(&db, def, Edition::CURRENT))
}
diff --git a/crates/hir-def/src/find_path.rs b/crates/hir-def/src/find_path.rs
index 5a3a3e9189..f5e03e5281 100644
--- a/crates/hir-def/src/find_path.rs
+++ b/crates/hir-def/src/find_path.rs
@@ -651,6 +651,7 @@ mod tests {
use expect_test::{expect, Expect};
use hir_expand::db::ExpandDatabase;
use itertools::Itertools;
+ use span::Edition;
use stdx::format_to;
use syntax::ast::AstNode;
use test_fixture::WithFixture;
@@ -717,8 +718,10 @@ mod tests {
"{:7}(imports {}): {}\n",
format!("{:?}", prefix),
if ignore_local_imports { '✖' } else { '✔' },
- found_path
- .map_or_else(|| "<unresolvable>".to_owned(), |it| it.display(&db).to_string()),
+ found_path.map_or_else(
+ || "<unresolvable>".to_owned(),
+ |it| it.display(&db, Edition::CURRENT).to_string()
+ ),
);
}
expect.assert_eq(&res);
diff --git a/crates/hir-def/src/generics.rs b/crates/hir-def/src/generics.rs
index ebaaef66db..6c34ee086a 100644
--- a/crates/hir-def/src/generics.rs
+++ b/crates/hir-def/src/generics.rs
@@ -12,7 +12,6 @@ use hir_expand::{
};
use intern::Interned;
use la_arena::{Arena, RawIdx};
-use once_cell::unsync::Lazy;
use stdx::impl_from;
use syntax::ast::{self, HasGenericParams, HasName, HasTypeBounds};
use triomphe::Arc;
@@ -394,11 +393,16 @@ impl GenericParams {
// Don't create an `Expander` if not needed since this
// could cause a reparse after the `ItemTree` has been created due to the spanmap.
- let mut expander = Lazy::new(|| {
- (module.def_map(db), Expander::new(db, loc.id.file_id(), module))
- });
+ let mut expander = None;
for param in func_data.params.iter() {
- generic_params.fill_implicit_impl_trait_args(db, &mut expander, param);
+ generic_params.fill_implicit_impl_trait_args(
+ db,
+ &mut expander,
+ &mut || {
+ (module.def_map(db), Expander::new(db, loc.id.file_id(), module))
+ },
+ param,
+ );
}
Interned::new(generic_params.finish())
}
@@ -597,7 +601,9 @@ impl GenericParamsCollector {
fn fill_implicit_impl_trait_args(
&mut self,
db: &dyn DefDatabase,
- exp: &mut Lazy<(Arc<DefMap>, Expander), impl FnOnce() -> (Arc<DefMap>, Expander)>,
+ // FIXME: Change this back to `LazyCell` if https://github.com/rust-lang/libs-team/issues/429 is accepted.
+ exp: &mut Option<(Arc<DefMap>, Expander)>,
+ exp_fill: &mut dyn FnMut() -> (Arc<DefMap>, Expander),
type_ref: &TypeRef,
) {
type_ref.walk(&mut |type_ref| {
@@ -617,7 +623,7 @@ impl GenericParamsCollector {
}
if let TypeRef::Macro(mc) = type_ref {
let macro_call = mc.to_node(db.upcast());
- let (def_map, expander) = &mut **exp;
+ let (def_map, expander) = exp.get_or_insert_with(&mut *exp_fill);
let module = expander.module.local_id;
let resolver = |path: &_| {
@@ -637,8 +643,8 @@ impl GenericParamsCollector {
{
let ctx = expander.ctx(db);
let type_ref = TypeRef::from_ast(&ctx, expanded.tree());
- self.fill_implicit_impl_trait_args(db, &mut *exp, &type_ref);
- exp.1.exit(mark);
+ self.fill_implicit_impl_trait_args(db, &mut *exp, exp_fill, &type_ref);
+ exp.get_or_insert_with(&mut *exp_fill).1.exit(mark);
}
}
});
diff --git a/crates/hir-def/src/hir/format_args.rs b/crates/hir-def/src/hir/format_args.rs
index 390e7da677..e1c3bd25bc 100644
--- a/crates/hir-def/src/hir/format_args.rs
+++ b/crates/hir-def/src/hir/format_args.rs
@@ -250,7 +250,7 @@ pub(crate) fn parse(
}
}
ArgRef::Name(name, span) => {
- let name = Name::new(name, tt::IdentIsRaw::No, call_ctx);
+ let name = Name::new(name, call_ctx);
if let Some((index, _)) = args.by_name(&name) {
record_usage(name, span);
// Name found in `args`, so we resolve it to its index.
diff --git a/crates/hir-def/src/hir/type_ref.rs b/crates/hir-def/src/hir/type_ref.rs
index 8f618b2d30..b74cd90f69 100644
--- a/crates/hir-def/src/hir/type_ref.rs
+++ b/crates/hir-def/src/hir/type_ref.rs
@@ -10,6 +10,7 @@ use hir_expand::{
AstId,
};
use intern::{sym, Interned, Symbol};
+use span::Edition;
use syntax::ast::{self, HasGenericArgs, HasName, IsString};
use crate::{
@@ -419,18 +420,22 @@ impl ConstRef {
param.default_val().map(|default| Self::from_const_arg(lower_ctx, Some(default)))
}
- pub fn display<'a>(&'a self, db: &'a dyn ExpandDatabase) -> impl fmt::Display + 'a {
- struct Display<'a>(&'a dyn ExpandDatabase, &'a ConstRef);
+ pub fn display<'a>(
+ &'a self,
+ db: &'a dyn ExpandDatabase,
+ edition: Edition,
+ ) -> impl fmt::Display + 'a {
+ struct Display<'a>(&'a dyn ExpandDatabase, &'a ConstRef, Edition);
impl fmt::Display for Display<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.1 {
ConstRef::Scalar(s) => s.fmt(f),
- ConstRef::Path(n) => n.display(self.0).fmt(f),
+ ConstRef::Path(n) => n.display(self.0, self.2).fmt(f),
ConstRef::Complex(_) => f.write_str("{const}"),
}
}
}
- Display(db, self)
+ Display(db, self, edition)
}
// We special case literals and single identifiers, to speed up things.
diff --git a/crates/hir-def/src/import_map.rs b/crates/hir-def/src/import_map.rs
index 8cc022e4c6..9574e5d9cd 100644
--- a/crates/hir-def/src/import_map.rs
+++ b/crates/hir-def/src/import_map.rs
@@ -8,6 +8,7 @@ use hir_expand::name::Name;
use itertools::Itertools;
use rustc_hash::FxHashSet;
use smallvec::SmallVec;
+use span::Edition;
use stdx::{format_to, TupleExt};
use syntax::ToSmolStr;
use triomphe::Arc;
@@ -66,7 +67,12 @@ impl ImportMap {
for (k, v) in self.item_to_info_map.iter() {
format_to!(out, "{:?} ({:?}) -> ", k, v.1);
for v in &v.0 {
- format_to!(out, "{}:{:?}, ", v.name.display(db.upcast()), v.container);
+ format_to!(
+ out,
+ "{}:{:?}, ",
+ v.name.display(db.upcast(), Edition::CURRENT),
+ v.container
+ );
}
format_to!(out, "\n");
}
@@ -83,7 +89,7 @@ impl ImportMap {
// We've only collected items, whose name cannot be tuple field so unwrapping is fine.
.flat_map(|(&item, (info, _))| {
info.iter().enumerate().map(move |(idx, info)| {
- (item, info.name.display(db.upcast()).to_smolstr(), idx as u32)
+ (item, info.name.unescaped().display(db.upcast()).to_smolstr(), idx as u32)
})
})
.collect();
@@ -461,7 +467,7 @@ fn search_maps(
query.search_mode.check(
&query.query,
query.case_sensitive,
- &info.name.display(db.upcast()).to_smolstr(),
+ &info.name.unescaped().display(db.upcast()).to_smolstr(),
)
});
res.extend(iter.map(TupleExt::head));
@@ -577,7 +583,7 @@ mod tests {
Some(format!(
"{}::{}",
render_path(db, &trait_info[0]),
- assoc_item_name.display(db.upcast())
+ assoc_item_name.display(db.upcast(), Edition::CURRENT)
))
}
@@ -616,7 +622,7 @@ mod tests {
module = parent;
}
- segments.iter().rev().map(|it| it.display(db.upcast())).join("::")
+ segments.iter().rev().map(|it| it.display(db.upcast(), Edition::CURRENT)).join("::")
}
#[test]
diff --git a/crates/hir-def/src/item_scope.rs b/crates/hir-def/src/item_scope.rs
index df6b1f55c1..a04f12cab7 100644
--- a/crates/hir-def/src/item_scope.rs
+++ b/crates/hir-def/src/item_scope.rs
@@ -1,14 +1,16 @@
//! Describes items defined or visible (ie, imported) in a certain scope.
//! This is shared between modules and blocks.
+use std::sync::LazyLock;
+
use base_db::CrateId;
use hir_expand::{attrs::AttrId, db::ExpandDatabase, name::Name, AstId, MacroCallId};
use indexmap::map::Entry;
use itertools::Itertools;
use la_arena::Idx;
-use once_cell::sync::Lazy;
use rustc_hash::{FxHashMap, FxHashSet};
use smallvec::{smallvec, SmallVec};
+use span::Edition;
use stdx::format_to;
use syntax::ast;
@@ -129,7 +131,7 @@ struct DeriveMacroInvocation {
derive_call_ids: SmallVec<[Option<MacroCallId>; 1]>,
}
-pub(crate) static BUILTIN_SCOPE: Lazy<FxIndexMap<Name, PerNs>> = Lazy::new(|| {
+pub(crate) static BUILTIN_SCOPE: LazyLock<FxIndexMap<Name, PerNs>> = LazyLock::new(|| {
BuiltinType::all_builtin_types()
.iter()
.map(|(name, ty)| (name.clone(), PerNs::types((*ty).into(), Visibility::Public, None)))
@@ -706,7 +708,7 @@ impl ItemScope {
format_to!(
buf,
"{}:",
- name.map_or("_".to_owned(), |name| name.display(db).to_string())
+ name.map_or("_".to_owned(), |name| name.display(db, Edition::LATEST).to_string())
);
if let Some((.., i)) = def.types {
diff --git a/crates/hir-def/src/item_tree.rs b/crates/hir-def/src/item_tree.rs
index 28eebb286e..f16230e1dc 100644
--- a/crates/hir-def/src/item_tree.rs
+++ b/crates/hir-def/src/item_tree.rs
@@ -40,6 +40,7 @@ use std::{
fmt::{self, Debug},
hash::{Hash, Hasher},
ops::{Index, Range},
+ sync::OnceLock,
};
use ast::{AstNode, StructKind};
@@ -48,10 +49,9 @@ use either::Either;
use hir_expand::{attrs::RawAttrs, name::Name, ExpandTo, HirFileId, InFile};
use intern::{Interned, Symbol};
use la_arena::{Arena, Idx, RawIdx};
-use once_cell::sync::OnceCell;
use rustc_hash::FxHashMap;
use smallvec::SmallVec;
-use span::{AstIdNode, FileAstId, SyntaxContextId};
+use span::{AstIdNode, Edition, FileAstId, SyntaxContextId};
use stdx::never;
use syntax::{ast, match_ast, SyntaxKind};
use triomphe::Arc;
@@ -101,7 +101,7 @@ pub struct ItemTree {
impl ItemTree {
pub(crate) fn file_item_tree_query(db: &dyn DefDatabase, file_id: HirFileId) -> Arc<ItemTree> {
let _p = tracing::info_span!("file_item_tree_query", ?file_id).entered();
- static EMPTY: OnceCell<Arc<ItemTree>> = OnceCell::new();
+ static EMPTY: OnceLock<Arc<ItemTree>> = OnceLock::new();
let syntax = db.parse_or_expand(file_id);
@@ -152,7 +152,7 @@ impl ItemTree {
pub(crate) fn block_item_tree_query(db: &dyn DefDatabase, block: BlockId) -> Arc<ItemTree> {
let _p = tracing::info_span!("block_item_tree_query", ?block).entered();
- static EMPTY: OnceCell<Arc<ItemTree>> = OnceCell::new();
+ static EMPTY: OnceLock<Arc<ItemTree>> = OnceLock::new();
let loc = block.lookup(db);
let block = loc.ast_id.to_node(db.upcast());
@@ -199,8 +199,8 @@ impl ItemTree {
Attrs::filter(db, krate, self.raw_attrs(of).clone())
}
- pub fn pretty_print(&self, db: &dyn DefDatabase) -> String {
- pretty::print_item_tree(db, self)
+ pub fn pretty_print(&self, db: &dyn DefDatabase, edition: Edition) -> String {
+ pretty::print_item_tree(db, self, edition)
}
fn data(&self) -> &ItemTreeData {
@@ -626,9 +626,9 @@ impl Index<RawVisibilityId> for ItemTree {
type Output = RawVisibility;
fn index(&self, index: RawVisibilityId) -> &Self::Output {
static VIS_PUB: RawVisibility = RawVisibility::Public;
- static VIS_PRIV_IMPLICIT: OnceCell<RawVisibility> = OnceCell::new();
- static VIS_PRIV_EXPLICIT: OnceCell<RawVisibility> = OnceCell::new();
- static VIS_PUB_CRATE: OnceCell<RawVisibility> = OnceCell::new();
+ static VIS_PRIV_IMPLICIT: OnceLock<RawVisibility> = OnceLock::new();
+ static VIS_PRIV_EXPLICIT: OnceLock<RawVisibility> = OnceLock::new();
+ static VIS_PUB_CRATE: OnceLock<RawVisibility> = OnceLock::new();
match index {
RawVisibilityId::PRIV_IMPLICIT => VIS_PRIV_IMPLICIT.get_or_init(|| {
diff --git a/crates/hir-def/src/item_tree/pretty.rs b/crates/hir-def/src/item_tree/pretty.rs
index 740759e6e3..b5a65abce8 100644
--- a/crates/hir-def/src/item_tree/pretty.rs
+++ b/crates/hir-def/src/item_tree/pretty.rs
@@ -3,7 +3,7 @@
use std::fmt::{self, Write};
use la_arena::{Idx, RawIdx};
-use span::ErasedFileAstId;
+use span::{Edition, ErasedFileAstId};
use crate::{
generics::{TypeOrConstParamData, WherePredicate, WherePredicateTypeTarget},
@@ -18,8 +18,9 @@ use crate::{
visibility::RawVisibility,
};
-pub(super) fn print_item_tree(db: &dyn DefDatabase, tree: &ItemTree) -> String {
- let mut p = Printer { db, tree, buf: String::new(), indent_level: 0, needs_indent: true };
+pub(super) fn print_item_tree(db: &dyn DefDatabase, tree: &ItemTree, edition: Edition) -> String {
+ let mut p =
+ Printer { db, tree, buf: String::new(), indent_level: 0, needs_indent: true, edition };
if let Some(attrs) = tree.attrs.get(&AttrOwner::TopLevel) {
p.print_attrs(attrs, true, "\n");
@@ -56,6 +57,7 @@ struct Printer<'a> {
buf: String,
indent_level: usize,
needs_indent: bool,
+ edition: Edition,
}
impl Printer<'_> {
@@ -97,7 +99,7 @@ impl Printer<'_> {
self,
"#{}[{}{}]{}",
inner,
- attr.path.display(self.db.upcast()),
+ attr.path.display(self.db.upcast(), self.edition),
attr.input.as_ref().map(|it| it.to_string()).unwrap_or_default(),
separated_by,
);
@@ -113,13 +115,14 @@ impl Printer<'_> {
fn print_visibility(&mut self, vis: RawVisibilityId) {
match &self.tree[vis] {
RawVisibility::Module(path, _expl) => {
- w!(self, "pub({}) ", path.display(self.db.upcast()))
+ w!(self, "pub({}) ", path.display(self.db.upcast(), self.edition))
}
RawVisibility::Public => w!(self, "pub "),
};
}
fn print_fields(&mut self, parent: FieldParent, kind: FieldsShape, fields: &[Field]) {
+ let edition = self.edition;
match kind {
FieldsShape::Record => {
self.whitespace();
@@ -131,7 +134,7 @@ impl Printer<'_> {
"\n",
);
this.print_visibility(*visibility);
- w!(this, "{}: ", name.display(self.db.upcast()));
+ w!(this, "{}: ", name.display(self.db.upcast(), edition));
this.print_type_ref(type_ref);
wln!(this, ",");
}
@@ -147,7 +150,7 @@ impl Printer<'_> {
"\n",
);
this.print_visibility(*visibility);
- w!(this, "{}: ", name.display(self.db.upcast()));
+ w!(this, "{}: ", name.display(self.db.upcast(), edition));
this.print_type_ref(type_ref);
wln!(this, ",");
}
@@ -186,20 +189,20 @@ impl Printer<'_> {
fn print_use_tree(&mut self, use_tree: &UseTree) {
match &use_tree.kind {
UseTreeKind::Single { path, alias } => {
- w!(self, "{}", path.display(self.db.upcast()));
+ w!(self, "{}", path.display(self.db.upcast(), self.edition));
if let Some(alias) = alias {
- w!(self, " as {}", alias);
+ w!(self, " as {}", alias.display(self.edition));
}
}
UseTreeKind::Glob { path } => {
if let Some(path) = path {
- w!(self, "{}::", path.display(self.db.upcast()));
+ w!(self, "{}::", path.display(self.db.upcast(), self.edition));
}
w!(self, "*");
}
UseTreeKind::Prefixed { prefix, list } => {
if let Some(prefix) = prefix {
- w!(self, "{}::", prefix.display(self.db.upcast()));
+ w!(self, "{}::", prefix.display(self.db.upcast(), self.edition));
}
w!(self, "{{");
for (i, tree) in list.iter().enumerate() {
@@ -229,9 +232,9 @@ impl Printer<'_> {
let ExternCrate { name, alias, visibility, ast_id } = &self.tree[it];
self.print_ast_id(ast_id.erase());
self.print_visibility(*visibility);
- w!(self, "extern crate {}", name.display(self.db.upcast()));
+ w!(self, "extern crate {}", name.display(self.db.upcast(), self.edition));
if let Some(alias) = alias {
- w!(self, " as {}", alias);
+ w!(self, " as {}", alias.display(self.edition));
}
wln!(self, ";");
}
@@ -278,7 +281,7 @@ impl Printer<'_> {
if let Some(abi) = abi {
w!(self, "extern \"{}\" ", abi);
}
- w!(self, "fn {}", name.display(self.db.upcast()));
+ w!(self, "fn {}", name.display(self.db.upcast(), self.edition));
self.print_generic_params(explicit_generic_params, it.into());
w!(self, "(");
if !params.is_empty() {
@@ -314,7 +317,7 @@ impl Printer<'_> {
&self.tree[it];
self.print_ast_id(ast_id.erase());
self.print_visibility(*visibility);
- w!(self, "struct {}", name.display(self.db.upcast()));
+ w!(self, "struct {}", name.display(self.db.upcast(), self.edition));
self.print_generic_params(generic_params, it.into());
self.print_fields_and_where_clause(
FieldParent::Struct(it),
@@ -332,7 +335,7 @@ impl Printer<'_> {
let Union { name, visibility, fields, generic_params, ast_id } = &self.tree[it];
self.print_ast_id(ast_id.erase());
self.print_visibility(*visibility);
- w!(self, "union {}", name.display(self.db.upcast()));
+ w!(self, "union {}", name.display(self.db.upcast(), self.edition));
self.print_generic_params(generic_params, it.into());
self.print_fields_and_where_clause(
FieldParent::Union(it),
@@ -346,15 +349,16 @@ impl Printer<'_> {
let Enum { name, visibility, variants, generic_params, ast_id } = &self.tree[it];
self.print_ast_id(ast_id.erase());
self.print_visibility(*visibility);
- w!(self, "enum {}", name.display(self.db.upcast()));
+ w!(self, "enum {}", name.display(self.db.upcast(), self.edition));
self.print_generic_params(generic_params, it.into());
self.print_where_clause_and_opening_brace(generic_params);
+ let edition = self.edition;
self.indented(|this| {
for variant in FileItemTreeId::range_iter(variants.clone()) {
let Variant { name, fields, shape: kind, ast_id } = &this.tree[variant];
this.print_ast_id(ast_id.erase());
this.print_attrs_of(variant, "\n");
- w!(this, "{}", name.display(self.db.upcast()));
+ w!(this, "{}", name.display(self.db.upcast(), edition));
this.print_fields(FieldParent::Variant(variant), *kind, fields);
wln!(this, ",");
}
@@ -367,7 +371,7 @@ impl Printer<'_> {
self.print_visibility(*visibility);
w!(self, "const ");
match name {
- Some(name) => w!(self, "{}", name.display(self.db.upcast())),
+ Some(name) => w!(self, "{}", name.display(self.db.upcast(), self.edition)),
None => w!(self, "_"),
}
w!(self, ": ");
@@ -382,7 +386,7 @@ impl Printer<'_> {
if *mutable {
w!(self, "mut ");
}
- w!(self, "{}: ", name.display(self.db.upcast()));
+ w!(self, "{}: ", name.display(self.db.upcast(), self.edition));
self.print_type_ref(type_ref);
w!(self, " = _;");
wln!(self);
@@ -398,7 +402,7 @@ impl Printer<'_> {
if *is_auto {
w!(self, "auto ");
}
- w!(self, "trait {}", name.display(self.db.upcast()));
+ w!(self, "trait {}", name.display(self.db.upcast(), self.edition));
self.print_generic_params(generic_params, it.into());
self.print_where_clause_and_opening_brace(generic_params);
self.indented(|this| {
@@ -412,7 +416,7 @@ impl Printer<'_> {
let TraitAlias { name, visibility, generic_params, ast_id } = &self.tree[it];
self.print_ast_id(ast_id.erase());
self.print_visibility(*visibility);
- w!(self, "trait {}", name.display(self.db.upcast()));
+ w!(self, "trait {}", name.display(self.db.upcast(), self.edition));
self.print_generic_params(generic_params, it.into());
w!(self, " = ");
self.print_where_clause(generic_params);
@@ -457,7 +461,7 @@ impl Printer<'_> {
&self.tree[it];
self.print_ast_id(ast_id.erase());
self.print_visibility(*visibility);
- w!(self, "type {}", name.display(self.db.upcast()));
+ w!(self, "type {}", name.display(self.db.upcast(), self.edition));
self.print_generic_params(generic_params, it.into());
if !bounds.is_empty() {
w!(self, ": ");
@@ -475,7 +479,7 @@ impl Printer<'_> {
let Mod { name, visibility, kind, ast_id } = &self.tree[it];
self.print_ast_id(ast_id.erase());
self.print_visibility(*visibility);
- w!(self, "mod {}", name.display(self.db.upcast()));
+ w!(self, "mod {}", name.display(self.db.upcast(), self.edition));
match kind {
ModKind::Inline { items } => {
w!(self, " {{");
@@ -500,18 +504,22 @@ impl Printer<'_> {
ctxt,
expand_to
);
- wln!(self, "{}!(...);", path.display(self.db.upcast()));
+ wln!(self, "{}!(...);", path.display(self.db.upcast(), self.edition));
}
ModItem::MacroRules(it) => {
let MacroRules { name, ast_id } = &self.tree[it];
self.print_ast_id(ast_id.erase());
- wln!(self, "macro_rules! {} {{ ... }}", name.display(self.db.upcast()));
+ wln!(
+ self,
+ "macro_rules! {} {{ ... }}",
+ name.display(self.db.upcast(), self.edition)
+ );
}
ModItem::Macro2(it) => {
let Macro2 { name, visibility, ast_id } = &self.tree[it];
self.print_ast_id(ast_id.erase());
self.print_visibility(*visibility);
- wln!(self, "macro {} {{ ... }}", name.display(self.db.upcast()));
+ wln!(self, "macro {} {{ ... }}", name.display(self.db.upcast(), self.edition));
}
}
@@ -519,15 +527,18 @@ impl Printer<'_> {
}
fn print_type_ref(&mut self, type_ref: &TypeRef) {
- print_type_ref(self.db, type_ref, self).unwrap();
+ let edition = self.edition;
+ print_type_ref(self.db, type_ref, self, edition).unwrap();
}
fn print_type_bounds(&mut self, bounds: &[Interned<TypeBound>]) {
- print_type_bounds(self.db, bounds, self).unwrap();
+ let edition = self.edition;
+ print_type_bounds(self.db, bounds, self, edition).unwrap();
}
fn print_path(&mut self, path: &Path) {
- print_path(self.db, path, self).unwrap();
+ let edition = self.edition;
+ print_path(self.db, path, self, edition).unwrap();
}
fn print_generic_params(&mut self, params: &GenericParams, parent: GenericModItem) {
@@ -543,7 +554,7 @@ impl Printer<'_> {
}
first = false;
self.print_attrs_of(AttrOwner::LifetimeParamData(parent, idx), " ");
- w!(self, "{}", lt.name.display(self.db.upcast()));
+ w!(self, "{}", lt.name.display(self.db.upcast(), self.edition));
}
for (idx, x) in params.iter_type_or_consts() {
if !first {
@@ -553,11 +564,11 @@ impl Printer<'_> {
self.print_attrs_of(AttrOwner::TypeOrConstParamData(parent, idx), " ");
match x {
TypeOrConstParamData::TypeParamData(ty) => match &ty.name {
- Some(name) => w!(self, "{}", name.display(self.db.upcast())),
+ Some(name) => w!(self, "{}", name.display(self.db.upcast(), self.edition)),
None => w!(self, "_anon_{}", idx.into_raw()),
},
TypeOrConstParamData::ConstParamData(konst) => {
- w!(self, "const {}: ", konst.name.display(self.db.upcast()));
+ w!(self, "const {}: ", konst.name.display(self.db.upcast(), self.edition));
self.print_type_ref(&konst.ty);
}
}
@@ -580,6 +591,7 @@ impl Printer<'_> {
}
w!(self, "\nwhere");
+ let edition = self.edition;
self.indented(|this| {
for (i, pred) in params.where_predicates().enumerate() {
if i != 0 {
@@ -592,8 +604,8 @@ impl Printer<'_> {
wln!(
this,
"{}: {},",
- target.name.display(self.db.upcast()),
- bound.name.display(self.db.upcast())
+ target.name.display(self.db.upcast(), edition),
+ bound.name.display(self.db.upcast(), edition)
);
continue;
}
@@ -603,7 +615,7 @@ impl Printer<'_> {
if i != 0 {
w!(this, ", ");
}
- w!(this, "{}", lt.display(self.db.upcast()));
+ w!(this, "{}", lt.display(self.db.upcast(), edition));
}
w!(this, "> ");
(target, bound)
@@ -613,7 +625,7 @@ impl Printer<'_> {
match target {
WherePredicateTypeTarget::TypeRef(ty) => this.print_type_ref(ty),
WherePredicateTypeTarget::TypeOrConstParam(id) => match params[*id].name() {
- Some(name) => w!(this, "{}", name.display(self.db.upcast())),
+ Some(name) => w!(this, "{}", name.display(self.db.upcast(), edition)),
None => w!(this, "_anon_{}", id.into_raw()),
},
}
diff --git a/crates/hir-def/src/item_tree/tests.rs b/crates/hir-def/src/item_tree/tests.rs
index c6930401a6..5c07369f4b 100644
--- a/crates/hir-def/src/item_tree/tests.rs
+++ b/crates/hir-def/src/item_tree/tests.rs
@@ -1,4 +1,5 @@
use expect_test::{expect, Expect};
+use span::Edition;
use test_fixture::WithFixture;
use crate::{db::DefDatabase, test_db::TestDB};
@@ -6,7 +7,7 @@ use crate::{db::DefDatabase, test_db::TestDB};
fn check(ra_fixture: &str, expect: Expect) {
let (db, file_id) = TestDB::with_single_file(ra_fixture);
let item_tree = db.file_item_tree(file_id.into());
- let pretty = item_tree.pretty_print(&db);
+ let pretty = item_tree.pretty_print(&db, Edition::CURRENT);
expect.assert_eq(&pretty);
}
diff --git a/crates/hir-def/src/lang_item.rs b/crates/hir-def/src/lang_item.rs
index a09fd658ae..166c965d14 100644
--- a/crates/hir-def/src/lang_item.rs
+++ b/crates/hir-def/src/lang_item.rs
@@ -74,6 +74,13 @@ impl LangItemTarget {
_ => None,
}
}
+
+ pub fn as_type_alias(self) -> Option<TypeAliasId> {
+ match self {
+ LangItemTarget::TypeAlias(id) => Some(id),
+ _ => None,
+ }
+ }
}
#[derive(Default, Debug, Clone, PartialEq, Eq)]
@@ -117,11 +124,19 @@ impl LangItems {
match def {
ModuleDefId::TraitId(trait_) => {
lang_items.collect_lang_item(db, trait_, LangItemTarget::Trait);
- db.trait_data(trait_).items.iter().for_each(|&(_, assoc_id)| {
- if let AssocItemId::FunctionId(f) = assoc_id {
- lang_items.collect_lang_item(db, f, LangItemTarget::Function);
- }
- });
+ db.trait_data(trait_).items.iter().for_each(
+ |&(_, assoc_id)| match assoc_id {
+ AssocItemId::FunctionId(f) => {
+ lang_items.collect_lang_item(db, f, LangItemTarget::Function);
+ }
+ AssocItemId::TypeAliasId(alias) => lang_items.collect_lang_item(
+ db,
+ alias,
+ LangItemTarget::TypeAlias,
+ ),
+ AssocItemId::ConstId(_) => {}
+ },
+ );
}
ModuleDefId::AdtId(AdtId::EnumId(e)) => {
lang_items.collect_lang_item(db, e, LangItemTarget::EnumId);
@@ -453,6 +468,7 @@ language_item_table! {
Context, sym::Context, context, Target::Struct, GenericRequirement::None;
FuturePoll, sym::poll, future_poll_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None;
+ FutureOutput, sym::future_output, future_output, Target::TypeAlias, GenericRequirement::None;
Option, sym::Option, option_type, Target::Enum, GenericRequirement::None;
OptionSome, sym::Some, option_some_variant, Target::Variant, GenericRequirement::None;
@@ -467,6 +483,7 @@ language_item_table! {
IntoFutureIntoFuture, sym::into_future, into_future_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None;
IntoIterIntoIter, sym::into_iter, into_iter_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None;
IteratorNext, sym::next, next_fn, Target::Method(MethodKind::Trait { body: false}), GenericRequirement::None;
+ Iterator, sym::iterator, iterator, Target::Trait, GenericRequirement::None;
PinNewUnchecked, sym::new_unchecked, new_unchecked_fn, Target::Method(MethodKind::Inherent), GenericRequirement::None;
diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs
index 4ced30c81d..0213bd904b 100644
--- a/crates/hir-def/src/lib.rs
+++ b/crates/hir-def/src/lib.rs
@@ -241,7 +241,7 @@ pub type StaticLoc = AssocItemLoc<Static>;
impl_intern!(StaticId, StaticLoc, intern_static, lookup_intern_static);
impl_loc!(StaticLoc, id: Static, container: ItemContainerId);
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct TraitId(salsa::InternId);
pub type TraitLoc = ItemLoc<Trait>;
impl_intern!(TraitId, TraitLoc, intern_trait, lookup_intern_trait);
diff --git a/crates/hir-def/src/macro_expansion_tests/mod.rs b/crates/hir-def/src/macro_expansion_tests/mod.rs
index b430e2cefb..7f76119251 100644
--- a/crates/hir-def/src/macro_expansion_tests/mod.rs
+++ b/crates/hir-def/src/macro_expansion_tests/mod.rs
@@ -25,7 +25,7 @@ use hir_expand::{
InFile, MacroFileId, MacroFileIdExt,
};
use intern::Symbol;
-use span::Span;
+use span::{Edition, Span};
use stdx::{format_to, format_to_acc};
use syntax::{
ast::{self, edit::IndentLevel},
@@ -257,21 +257,25 @@ fn pretty_print_macro_expansion(
(T![;] | T!['{'] | T!['}'], _) => "\n",
(_, T!['}']) => "\n",
(IDENT | LIFETIME_IDENT, IDENT | LIFETIME_IDENT) => " ",
- _ if prev_kind.is_keyword() && curr_kind.is_keyword() => " ",
- (IDENT, _) if curr_kind.is_keyword() => " ",
- (_, IDENT) if prev_kind.is_keyword() => " ",
+ _ if prev_kind.is_keyword(Edition::CURRENT)
+ && curr_kind.is_keyword(Edition::CURRENT) =>
+ {
+ " "
+ }
+ (IDENT, _) if curr_kind.is_keyword(Edition::CURRENT) => " ",
+ (_, IDENT) if prev_kind.is_keyword(Edition::CURRENT) => " ",
(T![>], IDENT) => " ",
- (T![>], _) if curr_kind.is_keyword() => " ",
+ (T![>], _) if curr_kind.is_keyword(Edition::CURRENT) => " ",
(T![->], _) | (_, T![->]) => " ",
(T![&&], _) | (_, T![&&]) => " ",
(T![,], _) => " ",
(T![:], IDENT | T!['(']) => " ",
- (T![:], _) if curr_kind.is_keyword() => " ",
+ (T![:], _) if curr_kind.is_keyword(Edition::CURRENT) => " ",
(T![fn], T!['(']) => "",
- (T![']'], _) if curr_kind.is_keyword() => " ",
+ (T![']'], _) if curr_kind.is_keyword(Edition::CURRENT) => " ",
(T![']'], T![#]) => "\n",
(T![Self], T![::]) => "",
- _ if prev_kind.is_keyword() => " ",
+ _ if prev_kind.is_keyword(Edition::CURRENT) => " ",
_ => "",
};
diff --git a/crates/hir-def/src/nameres.rs b/crates/hir-def/src/nameres.rs
index 8825e46336..11601c683e 100644
--- a/crates/hir-def/src/nameres.rs
+++ b/crates/hir-def/src/nameres.rs
@@ -328,6 +328,10 @@ impl DefMap {
/// The module id of a crate or block root.
pub const ROOT: LocalModuleId = LocalModuleId::from_raw(la_arena::RawIdx::from_u32(0));
+ pub fn edition(&self) -> Edition {
+ self.data.edition
+ }
+
pub(crate) fn crate_def_map_query(db: &dyn DefDatabase, crate_id: CrateId) -> Arc<DefMap> {
let crate_graph = db.crate_graph();
let krate = &crate_graph[crate_id];
@@ -550,7 +554,7 @@ impl DefMap {
for (name, child) in
map.modules[module].children.iter().sorted_by(|a, b| Ord::cmp(&a.0, &b.0))
{
- let path = format!("{path}::{}", name.display(db.upcast()));
+ let path = format!("{path}::{}", name.display(db.upcast(), Edition::LATEST));
buf.push('\n');
go(buf, db, map, &path, *child);
}
diff --git a/crates/hir-def/src/nameres/collector.rs b/crates/hir-def/src/nameres/collector.rs
index debc5a4432..96db3db8f0 100644
--- a/crates/hir-def/src/nameres/collector.rs
+++ b/crates/hir-def/src/nameres/collector.rs
@@ -548,7 +548,7 @@ impl DefCollector<'_> {
types => {
tracing::debug!(
"could not resolve prelude path `{}` to module (resolved to {:?})",
- path.display(self.db.upcast()),
+ path.display(self.db.upcast(), Edition::LATEST),
types
);
}
@@ -768,7 +768,7 @@ impl DefCollector<'_> {
}
fn resolve_import(&self, module_id: LocalModuleId, import: &Import) -> PartialResolvedImport {
- let _p = tracing::info_span!("resolve_import", import_path = %import.path.display(self.db.upcast()))
+ let _p = tracing::info_span!("resolve_import", import_path = %import.path.display(self.db.upcast(), Edition::LATEST))
.entered();
tracing::debug!("resolving import: {:?} ({:?})", import, self.def_map.data.edition);
match import.source {
@@ -1606,7 +1606,11 @@ impl ModCollector<'_, '_> {
// Prelude module is always considered to be `#[macro_use]`.
if let Some((prelude_module, _use)) = self.def_collector.def_map.prelude {
- if prelude_module.krate != krate && is_crate_root {
+ // Don't insert macros from the prelude into blocks, as they can be shadowed by other macros.
+ if prelude_module.krate != krate
+ && is_crate_root
+ && self.def_collector.def_map.block.is_none()
+ {
cov_mark::hit!(prelude_is_macro_use);
self.def_collector.import_macros_from_extern_crate(
prelude_module.krate,
@@ -2151,7 +2155,7 @@ impl ModCollector<'_, '_> {
}
tracing::debug!(
"non-builtin attribute {}",
- attr.path.display(self.def_collector.db.upcast())
+ attr.path.display(self.def_collector.db.upcast(), Edition::LATEST)
);
let ast_id = AstIdWithPath::new(
@@ -2286,8 +2290,8 @@ impl ModCollector<'_, '_> {
stdx::always!(
name == mac.name,
"built-in macro {} has #[rustc_builtin_macro] which declares different name {}",
- mac.name.display(self.def_collector.db.upcast()),
- name.display(self.def_collector.db.upcast())
+ mac.name.display(self.def_collector.db.upcast(), Edition::LATEST),
+ name.display(self.def_collector.db.upcast(), Edition::LATEST),
);
helpers_opt = Some(helpers);
}
diff --git a/crates/hir-def/src/nameres/tests/macros.rs b/crates/hir-def/src/nameres/tests/macros.rs
index 390c934f6d..a05c4dcf9b 100644
--- a/crates/hir-def/src/nameres/tests/macros.rs
+++ b/crates/hir-def/src/nameres/tests/macros.rs
@@ -1,6 +1,7 @@
use expect_test::expect;
use itertools::Itertools;
+use span::Edition;
use super::*;
@@ -1100,7 +1101,7 @@ pub fn derive_macro_2(_item: TokenStream) -> TokenStream {
assert_eq!(def_map.data.exported_derives.len(), 1);
match def_map.data.exported_derives.values().next() {
Some(helpers) => match &**helpers {
- [attr] => assert_eq!(attr.display(&db).to_string(), "helper_attr"),
+ [attr] => assert_eq!(attr.display(&db, Edition::CURRENT).to_string(), "helper_attr"),
_ => unreachable!(),
},
_ => unreachable!(),
@@ -1456,7 +1457,7 @@ fn proc_attr(a: TokenStream, b: TokenStream) -> TokenStream { a }
let actual = def_map
.macro_use_prelude
.keys()
- .map(|name| name.display(&db).to_string())
+ .map(|name| name.display(&db, Edition::CURRENT).to_string())
.sorted()
.join("\n");
diff --git a/crates/hir-def/src/nameres/tests/mod_resolution.rs b/crates/hir-def/src/nameres/tests/mod_resolution.rs
index 1327d9aa62..071b55c83d 100644
--- a/crates/hir-def/src/nameres/tests/mod_resolution.rs
+++ b/crates/hir-def/src/nameres/tests/mod_resolution.rs
@@ -144,14 +144,14 @@ pub struct Baz;
crate::r#async
Bar: t v
- foo: t
r#async: t
-
- crate::r#async::foo
- Foo: t v
+ foo: t
crate::r#async::r#async
Baz: t v
+
+ crate::r#async::foo
+ Foo: t v
"#]],
);
}
diff --git a/crates/hir-def/src/path.rs b/crates/hir-def/src/path.rs
index f90bc954a9..077863c0c9 100644
--- a/crates/hir-def/src/path.rs
+++ b/crates/hir-def/src/path.rs
@@ -13,7 +13,8 @@ use crate::{
};
use hir_expand::name::Name;
use intern::Interned;
-use syntax::{ast, ToSmolStr};
+use span::Edition;
+use syntax::ast;
pub use hir_expand::mod_path::{path, ModPath, PathKind};
@@ -25,11 +26,21 @@ pub enum ImportAlias {
Alias(Name),
}
-impl Display for ImportAlias {
+impl ImportAlias {
+ pub fn display(&self, edition: Edition) -> impl Display + '_ {
+ ImportAliasDisplay { value: self, edition }
+ }
+}
+
+struct ImportAliasDisplay<'a> {
+ value: &'a ImportAlias,
+ edition: Edition,
+}
+impl Display for ImportAliasDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
+ match self.value {
ImportAlias::Underscore => f.write_str("_"),
- ImportAlias::Alias(name) => f.write_str(&name.display_no_db().to_smolstr()),
+ ImportAlias::Alias(name) => Display::fmt(&name.display_no_db(self.edition), f),
}
}
}
diff --git a/crates/hir-def/src/pretty.rs b/crates/hir-def/src/pretty.rs
index 3ee88b536f..d5ef17a91f 100644
--- a/crates/hir-def/src/pretty.rs
+++ b/crates/hir-def/src/pretty.rs
@@ -5,6 +5,7 @@ use std::fmt::{self, Write};
use hir_expand::mod_path::PathKind;
use intern::Interned;
use itertools::Itertools;
+use span::Edition;
use crate::{
db::DefDatabase,
@@ -13,46 +14,51 @@ use crate::{
type_ref::{Mutability, TraitBoundModifier, TypeBound, TypeRef},
};
-pub(crate) fn print_path(db: &dyn DefDatabase, path: &Path, buf: &mut dyn Write) -> fmt::Result {
+pub(crate) fn print_path(
+ db: &dyn DefDatabase,
+ path: &Path,
+ buf: &mut dyn Write,
+ edition: Edition,
+) -> fmt::Result {
if let Path::LangItem(it, s) = path {
write!(buf, "builtin#lang(")?;
match *it {
LangItemTarget::ImplDef(it) => write!(buf, "{it:?}")?,
LangItemTarget::EnumId(it) => {
- write!(buf, "{}", db.enum_data(it).name.display(db.upcast()))?
+ write!(buf, "{}", db.enum_data(it).name.display(db.upcast(), edition))?
}
LangItemTarget::Function(it) => {
- write!(buf, "{}", db.function_data(it).name.display(db.upcast()))?
+ write!(buf, "{}", db.function_data(it).name.display(db.upcast(), edition))?
}
LangItemTarget::Static(it) => {
- write!(buf, "{}", db.static_data(it).name.display(db.upcast()))?
+ write!(buf, "{}", db.static_data(it).name.display(db.upcast(), edition))?
}
LangItemTarget::Struct(it) => {
- write!(buf, "{}", db.struct_data(it).name.display(db.upcast()))?
+ write!(buf, "{}", db.struct_data(it).name.display(db.upcast(), edition))?
}
LangItemTarget::Union(it) => {
- write!(buf, "{}", db.union_data(it).name.display(db.upcast()))?
+ write!(buf, "{}", db.union_data(it).name.display(db.upcast(), edition))?
}
LangItemTarget::TypeAlias(it) => {
- write!(buf, "{}", db.type_alias_data(it).name.display(db.upcast()))?
+ write!(buf, "{}", db.type_alias_data(it).name.display(db.upcast(), edition))?
}
LangItemTarget::Trait(it) => {
- write!(buf, "{}", db.trait_data(it).name.display(db.upcast()))?
+ write!(buf, "{}", db.trait_data(it).name.display(db.upcast(), edition))?
}
LangItemTarget::EnumVariant(it) => {
- write!(buf, "{}", db.enum_variant_data(it).name.display(db.upcast()))?
+ write!(buf, "{}", db.enum_variant_data(it).name.display(db.upcast(), edition))?
}
}
if let Some(s) = s {
- write!(buf, "::{}", s.display(db.upcast()))?;
+ write!(buf, "::{}", s.display(db.upcast(), edition))?;
}
return write!(buf, ")");
}
match path.type_anchor() {
Some(anchor) => {
write!(buf, "<")?;
- print_type_ref(db, anchor, buf)?;
+ print_type_ref(db, anchor, buf, edition)?;
write!(buf, ">::")?;
}
None => match path.kind() {
@@ -78,10 +84,10 @@ pub(crate) fn print_path(db: &dyn DefDatabase, path: &Path, buf: &mut dyn Write)
write!(buf, "::")?;
}
- write!(buf, "{}", segment.name.display(db.upcast()))?;
+ write!(buf, "{}", segment.name.display(db.upcast(), edition))?;
if let Some(generics) = segment.args_and_bindings {
write!(buf, "::<")?;
- print_generic_args(db, generics, buf)?;
+ print_generic_args(db, generics, buf, edition)?;
write!(buf, ">")?;
}
@@ -94,12 +100,13 @@ pub(crate) fn print_generic_args(
db: &dyn DefDatabase,
generics: &GenericArgs,
buf: &mut dyn Write,
+ edition: Edition,
) -> fmt::Result {
let mut first = true;
let args = if generics.has_self_type {
let (self_ty, args) = generics.args.split_first().unwrap();
write!(buf, "Self=")?;
- print_generic_arg(db, self_ty, buf)?;
+ print_generic_arg(db, self_ty, buf, edition)?;
first = false;
args
} else {
@@ -110,21 +117,21 @@ pub(crate) fn print_generic_args(
write!(buf, ", ")?;
}
first = false;
- print_generic_arg(db, arg, buf)?;
+ print_generic_arg(db, arg, buf, edition)?;
}
for binding in generics.bindings.iter() {
if !first {
write!(buf, ", ")?;
}
first = false;
- write!(buf, "{}", binding.name.display(db.upcast()))?;
+ write!(buf, "{}", binding.name.display(db.upcast(), edition))?;
if !binding.bounds.is_empty() {
write!(buf, ": ")?;
- print_type_bounds(db, &binding.bounds, buf)?;
+ print_type_bounds(db, &binding.bounds, buf, edition)?;
}
if let Some(ty) = &binding.type_ref {
write!(buf, " = ")?;
- print_type_ref(db, ty, buf)?;
+ print_type_ref(db, ty, buf, edition)?;
}
}
Ok(())
@@ -134,11 +141,12 @@ pub(crate) fn print_generic_arg(
db: &dyn DefDatabase,
arg: &GenericArg,
buf: &mut dyn Write,
+ edition: Edition,
) -> fmt::Result {
match arg {
- GenericArg::Type(ty) => print_type_ref(db, ty, buf),
- GenericArg::Const(c) => write!(buf, "{}", c.display(db.upcast())),
- GenericArg::Lifetime(lt) => write!(buf, "{}", lt.name.display(db.upcast())),
+ GenericArg::Type(ty) => print_type_ref(db, ty, buf, edition),
+ GenericArg::Const(c) => write!(buf, "{}", c.display(db.upcast(), edition)),
+ GenericArg::Lifetime(lt) => write!(buf, "{}", lt.name.display(db.upcast(), edition)),
}
}
@@ -146,6 +154,7 @@ pub(crate) fn print_type_ref(
db: &dyn DefDatabase,
type_ref: &TypeRef,
buf: &mut dyn Write,
+ edition: Edition,
) -> fmt::Result {
// FIXME: deduplicate with `HirDisplay` impl
match type_ref {
@@ -157,18 +166,18 @@ pub(crate) fn print_type_ref(
if i != 0 {
write!(buf, ", ")?;
}
- print_type_ref(db, field, buf)?;
+ print_type_ref(db, field, buf, edition)?;
}
write!(buf, ")")?;
}
- TypeRef::Path(path) => print_path(db, path, buf)?,
+ TypeRef::Path(path) => print_path(db, path, buf, edition)?,
TypeRef::RawPtr(pointee, mtbl) => {
let mtbl = match mtbl {
Mutability::Shared => "*const",
Mutability::Mut => "*mut",
};
write!(buf, "{mtbl} ")?;
- print_type_ref(db, pointee, buf)?;
+ print_type_ref(db, pointee, buf, edition)?;
}
TypeRef::Reference(pointee, lt, mtbl) => {
let mtbl = match mtbl {
@@ -177,19 +186,19 @@ pub(crate) fn print_type_ref(
};
write!(buf, "&")?;
if let Some(lt) = lt {
- write!(buf, "{} ", lt.name.display(db.upcast()))?;
+ write!(buf, "{} ", lt.name.display(db.upcast(), edition))?;
}
write!(buf, "{mtbl}")?;
- print_type_ref(db, pointee, buf)?;
+ print_type_ref(db, pointee, buf, edition)?;
}
TypeRef::Array(elem, len) => {
write!(buf, "[")?;
- print_type_ref(db, elem, buf)?;
- write!(buf, "; {}]", len.display(db.upcast()))?;
+ print_type_ref(db, elem, buf, edition)?;
+ write!(buf, "; {}]", len.display(db.upcast(), edition))?;
}
TypeRef::Slice(elem) => {
write!(buf, "[")?;
- print_type_ref(db, elem, buf)?;
+ print_type_ref(db, elem, buf, edition)?;
write!(buf, "]")?;
}
TypeRef::Fn(args_and_ret, varargs, is_unsafe, abi) => {
@@ -208,7 +217,7 @@ pub(crate) fn print_type_ref(
if i != 0 {
write!(buf, ", ")?;
}
- print_type_ref(db, typeref, buf)?;
+ print_type_ref(db, typeref, buf, edition)?;
}
if *varargs {
if !args.is_empty() {
@@ -217,7 +226,7 @@ pub(crate) fn print_type_ref(
write!(buf, "...")?;
}
write!(buf, ") -> ")?;
- print_type_ref(db, return_type, buf)?;
+ print_type_ref(db, return_type, buf, edition)?;
}
TypeRef::Macro(_ast_id) => {
write!(buf, "<macro>")?;
@@ -225,11 +234,11 @@ pub(crate) fn print_type_ref(
TypeRef::Error => write!(buf, "{{unknown}}")?,
TypeRef::ImplTrait(bounds) => {
write!(buf, "impl ")?;
- print_type_bounds(db, bounds, buf)?;
+ print_type_bounds(db, bounds, buf, edition)?;
}
TypeRef::DynTrait(bounds) => {
write!(buf, "dyn ")?;
- print_type_bounds(db, bounds, buf)?;
+ print_type_bounds(db, bounds, buf, edition)?;
}
}
@@ -240,6 +249,7 @@ pub(crate) fn print_type_bounds(
db: &dyn DefDatabase,
bounds: &[Interned<TypeBound>],
buf: &mut dyn Write,
+ edition: Edition,
) -> fmt::Result {
for (i, bound) in bounds.iter().enumerate() {
if i != 0 {
@@ -252,17 +262,17 @@ pub(crate) fn print_type_bounds(
TraitBoundModifier::None => (),
TraitBoundModifier::Maybe => write!(buf, "?")?,
}
- print_path(db, path, buf)?;
+ print_path(db, path, buf, edition)?;
}
TypeBound::ForLifetime(lifetimes, path) => {
write!(
buf,
"for<{}> ",
- lifetimes.iter().map(|it| it.display(db.upcast())).format(", ")
+ lifetimes.iter().map(|it| it.display(db.upcast(), edition)).format(", ")
)?;
- print_path(db, path, buf)?;
+ print_path(db, path, buf, edition)?;
}
- TypeBound::Lifetime(lt) => write!(buf, "{}", lt.name.display(db.upcast()))?,
+ TypeBound::Lifetime(lt) => write!(buf, "{}", lt.name.display(db.upcast(), edition))?,
TypeBound::Error => write!(buf, "{{unknown}}")?,
}
}
diff --git a/crates/hir-expand/src/eager.rs b/crates/hir-expand/src/eager.rs
index 3528b2dde7..1dadfe2ba9 100644
--- a/crates/hir-expand/src/eager.rs
+++ b/crates/hir-expand/src/eager.rs
@@ -176,9 +176,10 @@ fn eager_macro_recur(
Some(path) => match macro_resolver(&path) {
Some(def) => def,
None => {
+ let edition = db.crate_graph()[krate].edition;
error = Some(ExpandError::other(
span_map.span_at(call.syntax().text_range().start()),
- format!("unresolved macro {}", path.display(db)),
+ format!("unresolved macro {}", path.display(db, edition)),
));
offset += call.syntax().text_range().len();
continue;
diff --git a/crates/hir-expand/src/files.rs b/crates/hir-expand/src/files.rs
index 20f484f672..d41f69812e 100644
--- a/crates/hir-expand/src/files.rs
+++ b/crates/hir-expand/src/files.rs
@@ -461,3 +461,12 @@ impl<N: AstNode> InFile<N> {
Some(InRealFile::new(file_id, value))
}
}
+
+impl<T> InFile<T> {
+ pub fn into_real_file(self) -> Result<InRealFile<T>, InFile<T>> {
+ match self.file_id.repr() {
+ HirFileIdRepr::FileId(file_id) => Ok(InRealFile { file_id, value: self.value }),
+ HirFileIdRepr::MacroFile(_) => Err(self),
+ }
+ }
+}
diff --git a/crates/hir-expand/src/lib.rs b/crates/hir-expand/src/lib.rs
index 2bea902626..19c3c9c43f 100644
--- a/crates/hir-expand/src/lib.rs
+++ b/crates/hir-expand/src/lib.rs
@@ -192,7 +192,7 @@ impl ExpandErrorKind {
("overflow expanding the original macro".to_owned(), true)
}
ExpandErrorKind::Other(e) => ((**e).to_owned(), true),
- ExpandErrorKind::ProcMacroPanic(e) => ((**e).to_owned(), true),
+ ExpandErrorKind::ProcMacroPanic(e) => (format!("proc-macro panicked: {e}"), true),
}
}
}
@@ -279,6 +279,7 @@ pub enum MacroCallKind {
}
pub trait HirFileIdExt {
+ fn edition(self, db: &dyn ExpandDatabase) -> Edition;
/// Returns the original file of this macro call hierarchy.
fn original_file(self, db: &dyn ExpandDatabase) -> EditionedFileId;
@@ -293,6 +294,12 @@ pub trait HirFileIdExt {
}
impl HirFileIdExt for HirFileId {
+ fn edition(self, db: &dyn ExpandDatabase) -> Edition {
+ match self.repr() {
+ HirFileIdRepr::FileId(file_id) => file_id.edition(),
+ HirFileIdRepr::MacroFile(m) => m.macro_call_id.lookup(db).def.edition,
+ }
+ }
fn original_file(self, db: &dyn ExpandDatabase) -> EditionedFileId {
let mut file_id = self;
loop {
diff --git a/crates/hir-expand/src/mod_path.rs b/crates/hir-expand/src/mod_path.rs
index 2c26fe414d..dcf2af3997 100644
--- a/crates/hir-expand/src/mod_path.rs
+++ b/crates/hir-expand/src/mod_path.rs
@@ -14,7 +14,7 @@ use crate::{
use base_db::CrateId;
use intern::sym;
use smallvec::SmallVec;
-use span::SyntaxContextId;
+use span::{Edition, SyntaxContextId};
use syntax::{ast, AstNode};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -140,8 +140,12 @@ impl ModPath {
UnescapedModPath(self)
}
- pub fn display<'a>(&'a self, db: &'a dyn crate::db::ExpandDatabase) -> impl fmt::Display + 'a {
- Display { db, path: self }
+ pub fn display<'a>(
+ &'a self,
+ db: &'a dyn crate::db::ExpandDatabase,
+ edition: Edition,
+ ) -> impl fmt::Display + 'a {
+ Display { db, path: self, edition }
}
}
@@ -154,11 +158,12 @@ impl Extend<Name> for ModPath {
struct Display<'a> {
db: &'a dyn ExpandDatabase,
path: &'a ModPath,
+ edition: Edition,
}
impl fmt::Display for Display<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- display_fmt_path(self.db, self.path, f, true)
+ display_fmt_path(self.db, self.path, f, Escape::IfNeeded(self.edition))
}
}
@@ -169,7 +174,7 @@ struct 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)
+ display_fmt_path(self.db, self.path.0, f, Escape::No)
}
}
@@ -178,11 +183,17 @@ impl From<Name> for ModPath {
ModPath::from_segments(PathKind::Plain, iter::once(name))
}
}
+
+enum Escape {
+ No,
+ IfNeeded(Edition),
+}
+
fn display_fmt_path(
db: &dyn ExpandDatabase,
path: &ModPath,
f: &mut fmt::Formatter<'_>,
- escaped: bool,
+ escaped: Escape,
) -> fmt::Result {
let mut first_segment = true;
let mut add_segment = |s| -> fmt::Result {
@@ -210,10 +221,9 @@ fn display_fmt_path(
f.write_str("::")?;
}
first_segment = false;
- if escaped {
- segment.display(db).fmt(f)?;
- } else {
- segment.unescaped().display(db).fmt(f)?;
+ match escaped {
+ Escape::IfNeeded(edition) => segment.display(db, edition).fmt(f)?,
+ Escape::No => segment.unescaped().display(db).fmt(f)?,
}
}
Ok(())
@@ -322,9 +332,11 @@ fn convert_path_tt(db: &dyn ExpandDatabase, tt: &[tt::TokenTree]) -> Option<ModP
tt::Leaf::Ident(tt::Ident { sym: text, .. }) if *text == sym::self_ => PathKind::SELF,
tt::Leaf::Ident(tt::Ident { sym: text, .. }) if *text == sym::super_ => {
let mut deg = 1;
- while let Some(tt::Leaf::Ident(tt::Ident { sym: text, span, is_raw })) = leaves.next() {
+ while let Some(tt::Leaf::Ident(tt::Ident { sym: text, span, is_raw: _ })) =
+ leaves.next()
+ {
if *text != sym::super_ {
- segments.push(Name::new_symbol_maybe_raw(text.clone(), *is_raw, span.ctx));
+ segments.push(Name::new_symbol(text.clone(), span.ctx));
break;
}
deg += 1;
@@ -333,19 +345,13 @@ fn convert_path_tt(db: &dyn ExpandDatabase, tt: &[tt::TokenTree]) -> Option<ModP
}
tt::Leaf::Ident(tt::Ident { sym: text, .. }) if *text == sym::crate_ => PathKind::Crate,
tt::Leaf::Ident(ident) => {
- segments.push(Name::new_symbol_maybe_raw(
- ident.sym.clone(),
- ident.is_raw,
- ident.span.ctx,
- ));
+ segments.push(Name::new_symbol(ident.sym.clone(), ident.span.ctx));
PathKind::Plain
}
_ => return None,
};
segments.extend(leaves.filter_map(|leaf| match leaf {
- ::tt::Leaf::Ident(ident) => {
- Some(Name::new_symbol_maybe_raw(ident.sym.clone(), ident.is_raw, ident.span.ctx))
- }
+ ::tt::Leaf::Ident(ident) => Some(Name::new_symbol(ident.sym.clone(), ident.span.ctx)),
_ => None,
}));
Some(ModPath { kind, segments })
diff --git a/crates/hir-expand/src/name.rs b/crates/hir-expand/src/name.rs
index d012d272d7..54313904a7 100644
--- a/crates/hir-expand/src/name.rs
+++ b/crates/hir-expand/src/name.rs
@@ -3,22 +3,22 @@
use std::fmt;
use intern::{sym, Symbol};
-use span::SyntaxContextId;
-use syntax::{ast, utils::is_raw_identifier};
+use span::{Edition, SyntaxContextId};
+use syntax::ast;
+use syntax::utils::is_raw_identifier;
/// `Name` is a wrapper around string, which is used in hir for both references
/// and declarations. In theory, names should also carry hygiene info, but we are
/// not there yet!
///
-/// Note that `Name` holds and prints escaped name i.e. prefixed with "r#" when it
-/// is a raw identifier. Use [`unescaped()`][Name::unescaped] when you need the
-/// name without "r#".
+/// Note that the rawness (`r#`) of names does not depend on whether they are written raw.
+/// This is because we want to show (in completions etc.) names as raw depending on the needs
+/// of the current crate, for example if it is edition 2021 complete `gen` even if the defining
+/// crate is in edition 2024 and wrote `r#gen`, and the opposite holds as well.
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Name {
symbol: Symbol,
ctx: (),
- // FIXME: We should probably encode rawness as a property here instead, once we have hygiene
- // in here we've got 4 bytes of padding to fill anyways
}
impl fmt::Debug for Name {
@@ -42,6 +42,7 @@ impl PartialOrd for Name {
}
}
+// No need to strip `r#`, all comparisons are done against well-known symbols.
impl PartialEq<Symbol> for Name {
fn eq(&self, sym: &Symbol) -> bool {
self.symbol == *sym
@@ -55,16 +56,16 @@ impl PartialEq<Name> for Symbol {
}
/// Wrapper of `Name` to print the name without "r#" even when it is a raw identifier.
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct UnescapedName<'a>(&'a Name);
-impl UnescapedName<'_> {
- pub fn display(&self, db: &dyn crate::db::ExpandDatabase) -> impl fmt::Display + '_ {
+impl<'a> UnescapedName<'a> {
+ pub fn display(self, db: &dyn crate::db::ExpandDatabase) -> impl fmt::Display + 'a {
_ = db;
UnescapedDisplay { name: self }
}
#[doc(hidden)]
- pub fn display_no_db(&self) -> impl fmt::Display + '_ {
+ pub fn display_no_db(self) -> impl fmt::Display + 'a {
UnescapedDisplay { name: self }
}
}
@@ -77,16 +78,9 @@ impl Name {
Name { symbol: Symbol::intern(text), ctx: () }
}
- pub fn new(text: &str, raw: tt::IdentIsRaw, ctx: SyntaxContextId) -> Name {
+ pub fn new(text: &str, ctx: SyntaxContextId) -> Name {
_ = ctx;
- Name {
- symbol: if raw.yes() {
- Symbol::intern(&format!("{}{text}", raw.as_str()))
- } else {
- Symbol::intern(text)
- },
- ctx: (),
- }
+ Self::new_text(text)
}
pub fn new_tuple_field(idx: usize) -> Name {
@@ -97,23 +91,9 @@ impl Name {
Name { symbol: Symbol::intern(lt.text().as_str()), ctx: () }
}
- /// Shortcut to create a name from a string literal.
- fn new_ref(text: &str) -> Name {
- Name { symbol: Symbol::intern(text), ctx: () }
- }
-
/// Resolve a name from the text of token.
fn resolve(raw_text: &str) -> Name {
- match raw_text.strip_prefix("r#") {
- // When `raw_text` starts with "r#" but the name does not coincide with any
- // keyword, we never need the prefix so we strip it.
- Some(text) if !is_raw_identifier(text) => Name::new_ref(text),
- // Keywords (in the current edition) *can* be used as a name in earlier editions of
- // Rust, e.g. "try" in Rust 2015. Even in such cases, we keep track of them in their
- // escaped form.
- None if is_raw_identifier(raw_text) => Name::new_text(&format!("r#{}", raw_text)),
- _ => Name::new_text(raw_text),
- }
+ Name::new_text(raw_text.trim_start_matches("r#"))
}
/// A fake name for things missing in the source code.
@@ -159,19 +139,23 @@ impl Name {
UnescapedName(self)
}
- pub fn is_escaped(&self) -> bool {
- self.symbol.as_str().starts_with("r#")
+ pub fn is_escaped(&self, edition: Edition) -> bool {
+ is_raw_identifier(self.symbol.as_str(), edition)
}
- pub fn display<'a>(&'a self, db: &dyn crate::db::ExpandDatabase) -> impl fmt::Display + 'a {
+ pub fn display<'a>(
+ &'a self,
+ db: &dyn crate::db::ExpandDatabase,
+ edition: Edition,
+ ) -> impl fmt::Display + 'a {
_ = db;
- Display { name: self }
+ self.display_no_db(edition)
}
// FIXME: Remove this
#[doc(hidden)]
- pub fn display_no_db(&self) -> impl fmt::Display + '_ {
- Display { name: self }
+ pub fn display_no_db(&self, edition: Edition) -> impl fmt::Display + '_ {
+ Display { name: self, needs_escaping: is_raw_identifier(self.symbol.as_str(), edition) }
}
pub fn symbol(&self) -> &Symbol {
@@ -183,39 +167,39 @@ impl Name {
Self { symbol, ctx: () }
}
- pub fn new_symbol_maybe_raw(sym: Symbol, raw: tt::IdentIsRaw, ctx: SyntaxContextId) -> Self {
- if raw.no() {
- Self { symbol: sym, ctx: () }
- } else {
- Name::new(sym.as_str(), raw, ctx)
- }
- }
-
// FIXME: This needs to go once we have hygiene
pub const fn new_symbol_root(sym: Symbol) -> Self {
Self { symbol: sym, ctx: () }
}
+
+ #[inline]
+ pub fn eq_ident(&self, ident: &str) -> bool {
+ self.as_str() == ident.trim_start_matches("r#")
+ }
}
struct Display<'a> {
name: &'a Name,
+ needs_escaping: bool,
}
impl fmt::Display for Display<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ if self.needs_escaping {
+ write!(f, "r#")?;
+ }
fmt::Display::fmt(self.name.symbol.as_str(), f)
}
}
struct UnescapedDisplay<'a> {
- name: &'a UnescapedName<'a>,
+ name: UnescapedName<'a>,
}
impl fmt::Display for UnescapedDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- let symbol = &self.name.0.symbol.as_str();
- let text = symbol.strip_prefix("r#").unwrap_or(symbol);
- fmt::Display::fmt(&text, f)
+ let symbol = self.name.0.symbol.as_str();
+ fmt::Display::fmt(symbol, f)
}
}
diff --git a/crates/hir-ty/Cargo.toml b/crates/hir-ty/Cargo.toml
index b079b5675b..989f0955e1 100644
--- a/crates/hir-ty/Cargo.toml
+++ b/crates/hir-ty/Cargo.toml
@@ -29,7 +29,6 @@ chalk-ir.workspace = true
chalk-recursive.workspace = true
chalk-derive.workspace = true
la-arena.workspace = true
-once_cell = "1.17.0"
triomphe.workspace = true
nohash-hasher.workspace = true
typed-arena = "2.0.1"
diff --git a/crates/hir-ty/src/autoderef.rs b/crates/hir-ty/src/autoderef.rs
index ecfc1ff99e..7a3846df40 100644
--- a/crates/hir-ty/src/autoderef.rs
+++ b/crates/hir-ty/src/autoderef.rs
@@ -3,6 +3,8 @@
//! reference to a type with the field `bar`. This is an approximation of the
//! logic in rustc (which lives in rustc_hir_analysis/check/autoderef.rs).
+use std::mem;
+
use chalk_ir::cast::Cast;
use hir_def::lang_item::LangItem;
use hir_expand::name::Name;
@@ -37,7 +39,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, false);
+ let mut autoderef = Autoderef::new_no_tracking(&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
@@ -58,41 +60,76 @@ pub fn autoderef(
v.into_iter()
}
+trait TrackAutoderefSteps {
+ fn len(&self) -> usize;
+ fn push(&mut self, kind: AutoderefKind, ty: &Ty);
+}
+
+impl TrackAutoderefSteps for usize {
+ fn len(&self) -> usize {
+ *self
+ }
+ fn push(&mut self, _: AutoderefKind, _: &Ty) {
+ *self += 1;
+ }
+}
+impl TrackAutoderefSteps for Vec<(AutoderefKind, Ty)> {
+ fn len(&self) -> usize {
+ self.len()
+ }
+ fn push(&mut self, kind: AutoderefKind, ty: &Ty) {
+ self.push((kind, ty.clone()));
+ }
+}
+
#[derive(Debug)]
-pub(crate) struct Autoderef<'a, 'db> {
- pub(crate) table: &'a mut InferenceTable<'db>,
+pub(crate) struct Autoderef<'table, 'db, T = Vec<(AutoderefKind, Ty)>> {
+ pub(crate) table: &'table mut InferenceTable<'db>,
ty: Ty,
at_start: bool,
- steps: Vec<(AutoderefKind, Ty)>,
+ steps: T,
explicit: bool,
}
-impl<'a, 'db> Autoderef<'a, 'db> {
- pub(crate) fn new(table: &'a mut InferenceTable<'db>, ty: Ty, explicit: bool) -> Self {
+impl<'table, 'db> Autoderef<'table, 'db> {
+ pub(crate) fn new(table: &'table mut InferenceTable<'db>, ty: Ty, explicit: bool) -> Self {
let ty = table.resolve_ty_shallow(&ty);
Autoderef { table, ty, at_start: true, steps: Vec::new(), explicit }
}
- pub(crate) fn step_count(&self) -> usize {
- self.steps.len()
- }
-
pub(crate) fn steps(&self) -> &[(AutoderefKind, Ty)] {
&self.steps
}
+}
+
+impl<'table, 'db> Autoderef<'table, 'db, usize> {
+ pub(crate) fn new_no_tracking(
+ table: &'table mut InferenceTable<'db>,
+ ty: Ty,
+ explicit: bool,
+ ) -> Self {
+ let ty = table.resolve_ty_shallow(&ty);
+ Autoderef { table, ty, at_start: true, steps: 0, explicit }
+ }
+}
+
+#[allow(private_bounds)]
+impl<'table, 'db, T: TrackAutoderefSteps> Autoderef<'table, 'db, T> {
+ pub(crate) fn step_count(&self) -> usize {
+ self.steps.len()
+ }
pub(crate) fn final_ty(&self) -> Ty {
self.ty.clone()
}
}
-impl Iterator for Autoderef<'_, '_> {
+impl<T: TrackAutoderefSteps> Iterator for Autoderef<'_, '_, T> {
type Item = (Ty, usize);
#[tracing::instrument(skip_all)]
fn next(&mut self) -> Option<Self::Item> {
- if self.at_start {
- self.at_start = false;
+ if mem::take(&mut self.at_start) {
return Some((self.ty.clone(), 0));
}
@@ -102,7 +139,7 @@ impl Iterator for Autoderef<'_, '_> {
let (kind, new_ty) = autoderef_step(self.table, self.ty.clone(), self.explicit)?;
- self.steps.push((kind, self.ty.clone()));
+ self.steps.push(kind, &self.ty);
self.ty = new_ty;
Some((self.ty.clone(), self.step_count()))
@@ -129,12 +166,8 @@ pub(crate) fn builtin_deref<'ty>(
match ty.kind(Interner) {
TyKind::Ref(.., ty) => Some(ty),
TyKind::Raw(.., ty) if explicit => Some(ty),
- &TyKind::Adt(chalk_ir::AdtId(adt), ref substs) => {
- if crate::lang_items::is_box(db, adt) {
- substs.at(Interner, 0).ty(Interner)
- } else {
- None
- }
+ &TyKind::Adt(chalk_ir::AdtId(adt), ref substs) if crate::lang_items::is_box(db, adt) => {
+ substs.at(Interner, 0).ty(Interner)
}
_ => None,
}
diff --git a/crates/hir-ty/src/chalk_db.rs b/crates/hir-ty/src/chalk_db.rs
index a151ee01e6..a3e4da5d1a 100644
--- a/crates/hir-ty/src/chalk_db.rs
+++ b/crates/hir-ty/src/chalk_db.rs
@@ -5,6 +5,7 @@ use std::{iter, ops::ControlFlow, sync::Arc};
use hir_expand::name::Name;
use intern::sym;
+use span::Edition;
use tracing::debug;
use chalk_ir::{cast::Caster, fold::shift::Shift, CanonicalVarKinds};
@@ -424,18 +425,19 @@ impl chalk_solve::RustIrDatabase<Interner> for ChalkContext<'_> {
fn trait_name(&self, trait_id: chalk_ir::TraitId<Interner>) -> String {
let id = from_chalk_trait_id(trait_id);
- self.db.trait_data(id).name.display(self.db.upcast()).to_string()
+ self.db.trait_data(id).name.display(self.db.upcast(), self.edition()).to_string()
}
fn adt_name(&self, chalk_ir::AdtId(adt_id): AdtId) -> String {
+ let edition = self.edition();
match adt_id {
hir_def::AdtId::StructId(id) => {
- self.db.struct_data(id).name.display(self.db.upcast()).to_string()
+ self.db.struct_data(id).name.display(self.db.upcast(), edition).to_string()
}
hir_def::AdtId::EnumId(id) => {
- self.db.enum_data(id).name.display(self.db.upcast()).to_string()
+ self.db.enum_data(id).name.display(self.db.upcast(), edition).to_string()
}
hir_def::AdtId::UnionId(id) => {
- self.db.union_data(id).name.display(self.db.upcast()).to_string()
+ self.db.union_data(id).name.display(self.db.upcast(), edition).to_string()
}
}
}
@@ -445,7 +447,7 @@ impl chalk_solve::RustIrDatabase<Interner> for ChalkContext<'_> {
}
fn assoc_type_name(&self, assoc_ty_id: chalk_ir::AssocTypeId<Interner>) -> String {
let id = self.db.associated_ty_data(assoc_ty_id).name;
- self.db.type_alias_data(id).name.display(self.db.upcast()).to_string()
+ self.db.type_alias_data(id).name.display(self.db.upcast(), self.edition()).to_string()
}
fn opaque_type_name(&self, opaque_ty_id: chalk_ir::OpaqueTyId<Interner>) -> String {
format!("Opaque_{}", opaque_ty_id.0)
@@ -519,6 +521,10 @@ impl chalk_solve::RustIrDatabase<Interner> for ChalkContext<'_> {
}
impl<'a> ChalkContext<'a> {
+ fn edition(&self) -> Edition {
+ self.db.crate_graph()[self.krate].edition
+ }
+
fn for_trait_impls(
&self,
trait_id: hir_def::TraitId,
@@ -843,7 +849,7 @@ fn impl_def_datum(
"impl {:?}: {}{} where {:?}",
chalk_id,
if negative { "!" } else { "" },
- trait_ref.display(db),
+ trait_ref.display(db, db.crate_graph()[krate].edition),
where_clauses
);
diff --git a/crates/hir-ty/src/consteval/tests.rs b/crates/hir-ty/src/consteval/tests.rs
index dc3817ce3f..86228250c2 100644
--- a/crates/hir-ty/src/consteval/tests.rs
+++ b/crates/hir-ty/src/consteval/tests.rs
@@ -1,3 +1,4 @@
+use base_db::SourceDatabase;
use chalk_ir::Substitution;
use hir_def::db::DefDatabase;
use rustc_apfloat::{
@@ -94,9 +95,10 @@ fn check_answer(ra_fixture: &str, check: impl FnOnce(&[u8], &MemoryMap)) {
fn pretty_print_err(e: ConstEvalError, db: TestDB) -> String {
let mut err = String::new();
let span_formatter = |file, range| format!("{file:?} {range:?}");
+ let edition = db.crate_graph()[db.test_crate()].edition;
match e {
- ConstEvalError::MirLowerError(e) => e.pretty_print(&mut err, &db, span_formatter),
- ConstEvalError::MirEvalError(e) => e.pretty_print(&mut err, &db, span_formatter),
+ ConstEvalError::MirLowerError(e) => e.pretty_print(&mut err, &db, span_formatter, edition),
+ ConstEvalError::MirEvalError(e) => e.pretty_print(&mut err, &db, span_formatter, edition),
}
.unwrap();
err
@@ -110,7 +112,9 @@ fn eval_goal(db: &TestDB, file_id: EditionedFileId) -> Result<Const, ConstEvalEr
.declarations()
.find_map(|x| match x {
hir_def::ModuleDefId::ConstId(x) => {
- if db.const_data(x).name.as_ref()?.display(db).to_string() == "GOAL" {
+ if db.const_data(x).name.as_ref()?.display(db, file_id.edition()).to_string()
+ == "GOAL"
+ {
Some(x)
} else {
None
@@ -244,6 +248,17 @@ fn casts() {
}
#[test]
+fn floating_point_casts() {
+ check_number(r#"const GOAL: usize = 12i32 as f32 as usize"#, 12);
+ check_number(r#"const GOAL: i8 = -12i32 as f64 as i8"#, -12);
+ check_number(r#"const GOAL: i32 = (-1ui8 as f32 + 2u64 as f32) as i32"#, 1);
+ check_number(r#"const GOAL: i8 = (0./0.) as i8"#, 0);
+ check_number(r#"const GOAL: i8 = (1./0.) as i8"#, 127);
+ check_number(r#"const GOAL: i8 = (-1./0.) as i8"#, -128);
+ check_number(r#"const GOAL: i64 = 1e18f64 as f32 as i64"#, 999999984306749440);
+}
+
+#[test]
fn raw_pointer_equality() {
check_number(
r#"
diff --git a/crates/hir-ty/src/diagnostics/decl_check.rs b/crates/hir-ty/src/diagnostics/decl_check.rs
index b093440060..024fc32f86 100644
--- a/crates/hir-ty/src/diagnostics/decl_check.rs
+++ b/crates/hir-ty/src/diagnostics/decl_check.rs
@@ -17,17 +17,18 @@ use std::fmt;
use hir_def::{
data::adt::VariantData, db::DefDatabase, hir::Pat, src::HasSource, AdtId, AttrDefId, ConstId,
- EnumId, EnumVariantId, FunctionId, ItemContainerId, Lookup, ModuleDefId, ModuleId, StaticId,
- StructId, TraitId, TypeAliasId,
+ EnumId, EnumVariantId, FunctionId, HasModule, ItemContainerId, Lookup, ModuleDefId, ModuleId,
+ StaticId, StructId, TraitId, TypeAliasId,
};
use hir_expand::{
name::{AsName, Name},
- HirFileId, MacroFileIdExt,
+ HirFileId, HirFileIdExt, MacroFileIdExt,
};
use intern::sym;
use stdx::{always, never};
use syntax::{
ast::{self, HasName},
+ utils::is_raw_identifier,
AstNode, AstPtr, ToSmolStr,
};
@@ -318,17 +319,21 @@ impl<'a> DeclValidator<'a> {
/// This includes function parameters except for trait implementation associated functions.
fn validate_func_body(&mut self, func: FunctionId) {
let body = self.db.body(func.into());
+ let edition = self.edition(func);
let mut pats_replacements = body
.pats
.iter()
.filter_map(|(pat_id, pat)| match pat {
Pat::Bind { id, .. } => {
let bind_name = &body.bindings[*id].name;
+ let mut suggested_text =
+ to_lower_snake_case(&bind_name.unescaped().display_no_db().to_smolstr())?;
+ if is_raw_identifier(&suggested_text, edition) {
+ suggested_text.insert_str(0, "r#");
+ }
let replacement = Replacement {
current_name: bind_name.clone(),
- suggested_text: to_lower_snake_case(
- &bind_name.display_no_db().to_smolstr(),
- )?,
+ suggested_text,
expected_case: CaseType::LowerSnakeCase,
};
Some((pat_id, replacement))
@@ -377,6 +382,11 @@ impl<'a> DeclValidator<'a> {
}
}
+ fn edition(&self, id: impl HasModule) -> span::Edition {
+ let krate = id.krate(self.db.upcast());
+ self.db.crate_graph()[krate].edition
+ }
+
fn validate_struct(&mut self, struct_id: StructId) {
// Check the structure name.
let non_camel_case_allowed =
@@ -405,16 +415,17 @@ impl<'a> DeclValidator<'a> {
let VariantData::Record(fields) = data.variant_data.as_ref() else {
return;
};
+ let edition = self.edition(struct_id);
let mut struct_fields_replacements = fields
.iter()
.filter_map(|(_, field)| {
- to_lower_snake_case(&field.name.display_no_db().to_smolstr()).map(|new_name| {
- Replacement {
+ to_lower_snake_case(&field.name.display_no_db(edition).to_smolstr()).map(
+ |new_name| Replacement {
current_name: field.name.clone(),
suggested_text: new_name,
expected_case: CaseType::LowerSnakeCase,
- }
- })
+ },
+ )
})
.peekable();
@@ -498,14 +509,17 @@ impl<'a> DeclValidator<'a> {
self.validate_enum_variant_fields(*variant_id);
}
+ let edition = self.edition(enum_id);
let mut enum_variants_replacements = data
.variants
.iter()
.filter_map(|(_, name)| {
- to_camel_case(&name.display_no_db().to_smolstr()).map(|new_name| Replacement {
- current_name: name.clone(),
- suggested_text: new_name,
- expected_case: CaseType::UpperCamelCase,
+ to_camel_case(&name.display_no_db(edition).to_smolstr()).map(|new_name| {
+ Replacement {
+ current_name: name.clone(),
+ suggested_text: new_name,
+ expected_case: CaseType::UpperCamelCase,
+ }
})
})
.peekable();
@@ -566,16 +580,17 @@ impl<'a> DeclValidator<'a> {
let VariantData::Record(fields) = variant_data.variant_data.as_ref() else {
return;
};
+ let edition = self.edition(variant_id);
let mut variant_field_replacements = fields
.iter()
.filter_map(|(_, field)| {
- to_lower_snake_case(&field.name.display_no_db().to_smolstr()).map(|new_name| {
- Replacement {
+ to_lower_snake_case(&field.name.display_no_db(edition).to_smolstr()).map(
+ |new_name| Replacement {
current_name: field.name.clone(),
suggested_text: new_name,
expected_case: CaseType::LowerSnakeCase,
- }
- })
+ },
+ )
})
.peekable();
@@ -704,18 +719,22 @@ impl<'a> DeclValidator<'a> {
) where
N: AstNode + HasName + fmt::Debug,
S: HasSource<Value = N>,
- L: Lookup<Data = S, Database<'a> = dyn DefDatabase + 'a>,
+ L: Lookup<Data = S, Database<'a> = dyn DefDatabase + 'a> + HasModule + Copy,
{
let to_expected_case_type = match expected_case {
CaseType::LowerSnakeCase => to_lower_snake_case,
CaseType::UpperSnakeCase => to_upper_snake_case,
CaseType::UpperCamelCase => to_camel_case,
};
- let Some(replacement) =
- to_expected_case_type(&name.display(self.db.upcast()).to_smolstr()).map(|new_name| {
- Replacement { current_name: name.clone(), suggested_text: new_name, expected_case }
- })
- else {
+ let edition = self.edition(item_id);
+ let Some(replacement) = to_expected_case_type(
+ &name.display(self.db.upcast(), edition).to_smolstr(),
+ )
+ .map(|new_name| Replacement {
+ current_name: name.clone(),
+ suggested_text: new_name,
+ expected_case,
+ }) else {
return;
};
@@ -748,12 +767,13 @@ impl<'a> DeclValidator<'a> {
return;
};
+ let edition = file_id.original_file(self.db.upcast()).edition();
let diagnostic = IncorrectCase {
file: file_id,
ident_type,
ident: AstPtr::new(&name_ast),
expected_case: replacement.expected_case,
- ident_text: replacement.current_name.display(self.db.upcast()).to_string(),
+ ident_text: replacement.current_name.display(self.db.upcast(), edition).to_string(),
suggested_text: replacement.suggested_text,
};
diff --git a/crates/hir-ty/src/diagnostics/expr.rs b/crates/hir-ty/src/diagnostics/expr.rs
index 6e5a7cce9c..f8b5c7d0ce 100644
--- a/crates/hir-ty/src/diagnostics/expr.rs
+++ b/crates/hir-ty/src/diagnostics/expr.rs
@@ -4,6 +4,7 @@
use std::fmt;
+use base_db::CrateId;
use chalk_solve::rust_ir::AdtKind;
use either::Either;
use hir_def::{
@@ -15,6 +16,7 @@ use intern::sym;
use itertools::Itertools;
use rustc_hash::FxHashSet;
use rustc_pattern_analysis::constructor::Constructor;
+use span::Edition;
use syntax::{
ast::{self, UnaryOp},
AstNode,
@@ -258,7 +260,13 @@ impl ExprValidator {
if !witnesses.is_empty() {
self.diagnostics.push(BodyValidationDiagnostic::MissingMatchArms {
match_expr,
- uncovered_patterns: missing_match_arms(&cx, scrut_ty, witnesses, m_arms.is_empty()),
+ uncovered_patterns: missing_match_arms(
+ &cx,
+ scrut_ty,
+ witnesses,
+ m_arms.is_empty(),
+ self.owner.krate(db.upcast()),
+ ),
});
}
}
@@ -345,7 +353,13 @@ impl ExprValidator {
if !witnesses.is_empty() {
self.diagnostics.push(BodyValidationDiagnostic::NonExhaustiveLet {
pat,
- uncovered_patterns: missing_match_arms(&cx, ty, witnesses, false),
+ uncovered_patterns: missing_match_arms(
+ &cx,
+ ty,
+ witnesses,
+ false,
+ self.owner.krate(db.upcast()),
+ ),
});
}
}
@@ -616,24 +630,26 @@ fn missing_match_arms<'p>(
scrut_ty: &Ty,
witnesses: Vec<WitnessPat<'p>>,
arms_is_empty: bool,
+ krate: CrateId,
) -> String {
- struct DisplayWitness<'a, 'p>(&'a WitnessPat<'p>, &'a MatchCheckCtx<'p>);
+ struct DisplayWitness<'a, 'p>(&'a WitnessPat<'p>, &'a MatchCheckCtx<'p>, Edition);
impl fmt::Display for DisplayWitness<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- let DisplayWitness(witness, cx) = *self;
+ let DisplayWitness(witness, cx, edition) = *self;
let pat = cx.hoist_witness_pat(witness);
- write!(f, "{}", pat.display(cx.db))
+ write!(f, "{}", pat.display(cx.db, edition))
}
}
+ let edition = cx.db.crate_graph()[krate].edition;
let non_empty_enum = match scrut_ty.as_adt() {
Some((AdtId::EnumId(e), _)) => !cx.db.enum_data(e).variants.is_empty(),
_ => false,
};
if arms_is_empty && !non_empty_enum {
- format!("type `{}` is non-empty", scrut_ty.display(cx.db))
+ format!("type `{}` is non-empty", scrut_ty.display(cx.db, edition))
} else {
- let pat_display = |witness| DisplayWitness(witness, cx);
+ let pat_display = |witness| DisplayWitness(witness, cx, edition);
const LIMIT: usize = 3;
match &*witnesses {
[witness] => format!("`{}` not covered", pat_display(witness)),
diff --git a/crates/hir-ty/src/diagnostics/match_check.rs b/crates/hir-ty/src/diagnostics/match_check.rs
index a0ee7c0748..4bc07bc9ec 100644
--- a/crates/hir-ty/src/diagnostics/match_check.rs
+++ b/crates/hir-ty/src/diagnostics/match_check.rs
@@ -14,6 +14,7 @@ use hir_def::{
body::Body, data::adt::VariantData, hir::PatId, AdtId, EnumVariantId, LocalFieldId, VariantId,
};
use hir_expand::name::Name;
+use span::Edition;
use stdx::{always, never};
use crate::{
@@ -151,7 +152,11 @@ impl<'a> PatCtxt<'a> {
match (bm, ty.kind(Interner)) {
(BindingMode::Ref(_), TyKind::Ref(.., rty)) => ty = rty,
(BindingMode::Ref(_), _) => {
- never!("`ref {}` has wrong type {:?}", name.display(self.db.upcast()), ty);
+ never!(
+ "`ref {}` has wrong type {:?}",
+ name.display(self.db.upcast(), Edition::LATEST),
+ ty
+ );
self.errors.push(PatternError::UnexpectedType);
return Pat { ty: ty.clone(), kind: PatKind::Wild.into() };
}
@@ -297,7 +302,7 @@ impl HirDisplay for Pat {
PatKind::Wild => write!(f, "_"),
PatKind::Never => write!(f, "!"),
PatKind::Binding { name, subpattern } => {
- write!(f, "{}", name.display(f.db.upcast()))?;
+ write!(f, "{}", name.display(f.db.upcast(), f.edition()))?;
if let Some(subpattern) = subpattern {
write!(f, " @ ")?;
subpattern.hir_fmt(f)?;
@@ -317,14 +322,22 @@ impl HirDisplay for Pat {
if let Some(variant) = variant {
match variant {
VariantId::EnumVariantId(v) => {
- write!(f, "{}", f.db.enum_variant_data(v).name.display(f.db.upcast()))?;
- }
- VariantId::StructId(s) => {
- write!(f, "{}", f.db.struct_data(s).name.display(f.db.upcast()))?
- }
- VariantId::UnionId(u) => {
- write!(f, "{}", f.db.union_data(u).name.display(f.db.upcast()))?
+ write!(
+ f,
+ "{}",
+ f.db.enum_variant_data(v).name.display(f.db.upcast(), f.edition())
+ )?;
}
+ VariantId::StructId(s) => write!(
+ f,
+ "{}",
+ f.db.struct_data(s).name.display(f.db.upcast(), f.edition())
+ )?,
+ VariantId::UnionId(u) => write!(
+ f,
+ "{}",
+ f.db.union_data(u).name.display(f.db.upcast(), f.edition())
+ )?,
};
let variant_data = variant.variant_data(f.db.upcast());
@@ -341,7 +354,9 @@ impl HirDisplay for Pat {
write!(
f,
"{}: ",
- rec_fields[p.field].name.display(f.db.upcast())
+ rec_fields[p.field]
+ .name
+ .display(f.db.upcast(), f.edition())
)?;
p.pattern.hir_fmt(f)
})
diff --git a/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs b/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs
index 7b3abf501d..1066a28c3f 100644
--- a/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs
+++ b/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs
@@ -1,10 +1,10 @@
//! Interface with `rustc_pattern_analysis`.
+use std::cell::LazyCell;
use std::fmt;
use hir_def::{DefWithBodyId, EnumId, EnumVariantId, HasModule, LocalFieldId, ModuleId, VariantId};
use intern::sym;
-use once_cell::unsync::Lazy;
use rustc_pattern_analysis::{
constructor::{Constructor, ConstructorSet, VariantVisibility},
usefulness::{compute_match_usefulness, PlaceValidity, UsefulnessReport},
@@ -384,8 +384,9 @@ impl<'db> PatCx for MatchCheckCtx<'db> {
let variant = Self::variant_id_for_adt(self.db, ctor, adt).unwrap();
// Whether we must not match the fields of this variant exhaustively.
- let is_non_exhaustive = Lazy::new(|| self.is_foreign_non_exhaustive(adt));
- let visibilities = Lazy::new(|| self.db.field_visibilities(variant));
+ let is_non_exhaustive =
+ LazyCell::new(|| self.is_foreign_non_exhaustive(adt));
+ let visibilities = LazyCell::new(|| self.db.field_visibilities(variant));
self.list_variant_fields(ty, variant)
.map(move |(fid, ty)| {
diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs
index f406666ae5..70c03477c4 100644
--- a/crates/hir-ty/src/display.rs
+++ b/crates/hir-ty/src/display.rs
@@ -33,7 +33,8 @@ use rustc_apfloat::{
Float,
};
use smallvec::SmallVec;
-use stdx::{never, IsNoneOr};
+use span::Edition;
+use stdx::never;
use triomphe::Arc;
use crate::{
@@ -131,7 +132,11 @@ pub trait HirDisplay {
/// Returns a `Display`able type that is human-readable.
/// Use this for showing types to the user (e.g. diagnostics)
- fn display<'a>(&'a self, db: &'a dyn HirDatabase) -> HirDisplayWrapper<'a, Self>
+ fn display<'a>(
+ &'a self,
+ db: &'a dyn HirDatabase,
+ edition: Edition,
+ ) -> HirDisplayWrapper<'a, Self>
where
Self: Sized,
{
@@ -142,7 +147,7 @@ pub trait HirDisplay {
limited_size: None,
omit_verbose_types: false,
closure_style: ClosureStyle::ImplFn,
- display_target: DisplayTarget::Diagnostics,
+ display_target: DisplayTarget::Diagnostics { edition },
show_container_bounds: false,
}
}
@@ -153,6 +158,7 @@ pub trait HirDisplay {
&'a self,
db: &'a dyn HirDatabase,
max_size: Option<usize>,
+ edition: Edition,
) -> HirDisplayWrapper<'a, Self>
where
Self: Sized,
@@ -164,7 +170,7 @@ pub trait HirDisplay {
limited_size: None,
omit_verbose_types: true,
closure_style: ClosureStyle::ImplFn,
- display_target: DisplayTarget::Diagnostics,
+ display_target: DisplayTarget::Diagnostics { edition },
show_container_bounds: false,
}
}
@@ -175,6 +181,7 @@ pub trait HirDisplay {
&'a self,
db: &'a dyn HirDatabase,
limited_size: Option<usize>,
+ edition: Edition,
) -> HirDisplayWrapper<'a, Self>
where
Self: Sized,
@@ -186,7 +193,7 @@ pub trait HirDisplay {
limited_size,
omit_verbose_types: true,
closure_style: ClosureStyle::ImplFn,
- display_target: DisplayTarget::Diagnostics,
+ display_target: DisplayTarget::Diagnostics { edition },
show_container_bounds: false,
}
}
@@ -242,6 +249,7 @@ pub trait HirDisplay {
&'a self,
db: &'a dyn HirDatabase,
show_container_bounds: bool,
+ edition: Edition,
) -> HirDisplayWrapper<'a, Self>
where
Self: Sized,
@@ -253,13 +261,23 @@ pub trait HirDisplay {
limited_size: None,
omit_verbose_types: false,
closure_style: ClosureStyle::ImplFn,
- display_target: DisplayTarget::Diagnostics,
+ display_target: DisplayTarget::Diagnostics { edition },
show_container_bounds,
}
}
}
impl HirFormatter<'_> {
+ pub fn edition(&self) -> Edition {
+ match self.display_target {
+ DisplayTarget::Diagnostics { edition } => edition,
+ DisplayTarget::SourceCode { module_id, .. } => {
+ self.db.crate_graph()[module_id.krate()].edition
+ }
+ DisplayTarget::Test => Edition::CURRENT,
+ }
+ }
+
pub fn write_joined<T: HirDisplay>(
&mut self,
iter: impl IntoIterator<Item = T>,
@@ -324,7 +342,7 @@ pub enum DisplayTarget {
/// Display types for inlays, doc popups, autocompletion, etc...
/// Showing `{unknown}` or not qualifying paths is fine here.
/// There's no reason for this to fail.
- Diagnostics,
+ Diagnostics { edition: Edition },
/// Display types for inserting them in source files.
/// The generated code should compile, so paths need to be qualified.
SourceCode { module_id: ModuleId, allow_opaque: bool },
@@ -460,7 +478,7 @@ impl HirDisplay for ProjectionTy {
">::{}",
f.db.type_alias_data(from_assoc_type_id(self.associated_ty_id))
.name
- .display(f.db.upcast())
+ .display(f.db.upcast(), f.edition())
)?;
let proj_params_count =
self.substitution.len(Interner) - trait_ref.substitution.len(Interner);
@@ -499,7 +517,7 @@ impl HirDisplay for Const {
let id = from_placeholder_idx(f.db, *idx);
let generics = generics(f.db.upcast(), id.parent);
let param_data = &generics[id.local_id];
- write!(f, "{}", param_data.name().unwrap().display(f.db.upcast()))?;
+ write!(f, "{}", param_data.name().unwrap().display(f.db.upcast(), f.edition()))?;
Ok(())
}
ConstValue::Concrete(c) => match &c.interned {
@@ -633,7 +651,7 @@ fn render_const_scalar(
TyKind::Adt(adt, _) if b.len() == 2 * size_of::<usize>() => match adt.0 {
hir_def::AdtId::StructId(s) => {
let data = f.db.struct_data(s);
- write!(f, "&{}", data.name.display(f.db.upcast()))?;
+ write!(f, "&{}", data.name.display(f.db.upcast(), f.edition()))?;
Ok(())
}
_ => f.write_str("<unsized-enum-or-union>"),
@@ -691,7 +709,7 @@ fn render_const_scalar(
match adt.0 {
hir_def::AdtId::StructId(s) => {
let data = f.db.struct_data(s);
- write!(f, "{}", data.name.display(f.db.upcast()))?;
+ write!(f, "{}", data.name.display(f.db.upcast(), f.edition()))?;
let field_types = f.db.field_types(s.into());
render_variant_after_name(
&data.variant_data,
@@ -705,7 +723,7 @@ fn render_const_scalar(
)
}
hir_def::AdtId::UnionId(u) => {
- write!(f, "{}", f.db.union_data(u).name.display(f.db.upcast()))
+ write!(f, "{}", f.db.union_data(u).name.display(f.db.upcast(), f.edition()))
}
hir_def::AdtId::EnumId(e) => {
let Ok(target_data_layout) = f.db.target_data_layout(trait_env.krate) else {
@@ -717,7 +735,7 @@ fn render_const_scalar(
return f.write_str("<failed-to-detect-variant>");
};
let data = f.db.enum_variant_data(var_id);
- write!(f, "{}", data.name.display(f.db.upcast()))?;
+ write!(f, "{}", data.name.display(f.db.upcast(), f.edition()))?;
let field_types = f.db.field_types(var_id.into());
render_variant_after_name(
&data.variant_data,
@@ -802,11 +820,11 @@ fn render_variant_after_name(
if matches!(data, VariantData::Record(_)) {
write!(f, " {{")?;
if let Some((id, data)) = it.next() {
- write!(f, " {}: ", data.name.display(f.db.upcast()))?;
+ write!(f, " {}: ", data.name.display(f.db.upcast(), f.edition()))?;
render_field(f, id)?;
}
for (id, data) in it {
- write!(f, ", {}: ", data.name.display(f.db.upcast()))?;
+ write!(f, ", {}: ", data.name.display(f.db.upcast(), f.edition()))?;
render_field(f, id)?;
}
write!(f, " }}")?;
@@ -1000,15 +1018,23 @@ impl HirDisplay for Ty {
CallableDefId::FunctionId(ff) => {
write!(f, "fn ")?;
f.start_location_link(def.into());
- write!(f, "{}", db.function_data(ff).name.display(f.db.upcast()))?
+ write!(
+ f,
+ "{}",
+ db.function_data(ff).name.display(f.db.upcast(), f.edition())
+ )?
}
CallableDefId::StructId(s) => {
f.start_location_link(def.into());
- write!(f, "{}", db.struct_data(s).name.display(f.db.upcast()))?
+ write!(f, "{}", db.struct_data(s).name.display(f.db.upcast(), f.edition()))?
}
CallableDefId::EnumVariantId(e) => {
f.start_location_link(def.into());
- write!(f, "{}", db.enum_variant_data(e).name.display(f.db.upcast()))?
+ write!(
+ f,
+ "{}",
+ db.enum_variant_data(e).name.display(f.db.upcast(), f.edition())
+ )?
}
};
f.end_location_link();
@@ -1019,26 +1045,25 @@ impl HirDisplay for Ty {
let (parent_len, self_param, type_, const_, impl_, lifetime) =
generics.provenance_split();
let parameters = parameters.as_slice(Interner);
+ debug_assert_eq!(
+ parameters.len(),
+ parent_len + self_param as usize + type_ + const_ + impl_ + lifetime
+ );
// We print all params except implicit impl Trait params. Still a bit weird; should we leave out parent and self?
if parameters.len() - impl_ > 0 {
// `parameters` are in the order of fn's params (including impl traits), fn's lifetimes
+ let parameters =
+ generic_args_sans_defaults(f, Some(generic_def_id), parameters);
let without_impl = self_param as usize + type_ + const_ + lifetime;
// parent's params (those from enclosing impl or trait, if any).
let (fn_params, parent_params) = parameters.split_at(without_impl + impl_);
- debug_assert_eq!(parent_params.len(), parent_len);
-
- let parent_params =
- generic_args_sans_defaults(f, Some(generic_def_id), parent_params);
- let fn_params =
- &generic_args_sans_defaults(f, Some(generic_def_id), fn_params)
- [0..without_impl];
write!(f, "<")?;
hir_fmt_generic_arguments(f, parent_params, None)?;
if !parent_params.is_empty() && !fn_params.is_empty() {
write!(f, ", ")?;
}
- hir_fmt_generic_arguments(f, fn_params, None)?;
+ hir_fmt_generic_arguments(f, &fn_params[0..without_impl], None)?;
write!(f, ">")?;
}
}
@@ -1054,13 +1079,13 @@ impl HirDisplay for Ty {
TyKind::Adt(AdtId(def_id), parameters) => {
f.start_location_link((*def_id).into());
match f.display_target {
- DisplayTarget::Diagnostics | DisplayTarget::Test => {
+ DisplayTarget::Diagnostics { .. } | DisplayTarget::Test => {
let name = match *def_id {
hir_def::AdtId::StructId(it) => db.struct_data(it).name.clone(),
hir_def::AdtId::UnionId(it) => db.union_data(it).name.clone(),
hir_def::AdtId::EnumId(it) => db.enum_data(it).name.clone(),
};
- write!(f, "{}", name.display(f.db.upcast()))?;
+ write!(f, "{}", name.display(f.db.upcast(), f.edition()))?;
}
DisplayTarget::SourceCode { module_id, allow_opaque: _ } => {
if let Some(path) = find_path::find_path(
@@ -1076,7 +1101,7 @@ impl HirDisplay for Ty {
prefer_absolute: false,
},
) {
- write!(f, "{}", path.display(f.db.upcast()))?;
+ write!(f, "{}", path.display(f.db.upcast(), f.edition()))?;
} else {
return Err(HirDisplayError::DisplaySourceCodeError(
DisplaySourceCodeError::PathNotFound,
@@ -1102,12 +1127,12 @@ impl HirDisplay for Ty {
// Use placeholder associated types when the target is test (https://rust-lang.github.io/chalk/book/clauses/type_equality.html#placeholder-associated-types)
if f.display_target.is_test() {
f.start_location_link(trait_.into());
- write!(f, "{}", trait_data.name.display(f.db.upcast()))?;
+ write!(f, "{}", trait_data.name.display(f.db.upcast(), f.edition()))?;
f.end_location_link();
write!(f, "::")?;
f.start_location_link(type_alias.into());
- write!(f, "{}", type_alias_data.name.display(f.db.upcast()))?;
+ write!(f, "{}", type_alias_data.name.display(f.db.upcast(), f.edition()))?;
f.end_location_link();
// Note that the generic args for the associated type come before those for the
// trait (including the self type).
@@ -1125,7 +1150,7 @@ impl HirDisplay for Ty {
let alias = from_foreign_def_id(*type_alias);
let type_alias = db.type_alias_data(alias);
f.start_location_link(alias.into());
- write!(f, "{}", type_alias.name.display(f.db.upcast()))?;
+ write!(f, "{}", type_alias.name.display(f.db.upcast(), f.edition()))?;
f.end_location_link();
}
TyKind::OpaqueType(opaque_ty_id, parameters) => {
@@ -1257,7 +1282,10 @@ impl HirDisplay for Ty {
write!(
f,
"{}",
- p.name.clone().unwrap_or_else(Name::missing).display(f.db.upcast())
+ p.name
+ .clone()
+ .unwrap_or_else(Name::missing)
+ .display(f.db.upcast(), f.edition())
)?
}
TypeParamProvenance::ArgumentImplTrait => {
@@ -1290,7 +1318,7 @@ impl HirDisplay for Ty {
}
},
TypeOrConstParamData::ConstParamData(p) => {
- write!(f, "{}", p.name.display(f.db.upcast()))?;
+ write!(f, "{}", p.name.display(f.db.upcast(), f.edition()))?;
}
}
}
@@ -1410,17 +1438,7 @@ fn hir_fmt_generics(
let parameters_to_write = generic_args_sans_defaults(f, generic_def, parameters);
- // FIXME: Remote this
- // most of our lifetimes will be errors as we lack elision and inference
- // so don't render them for now
- let only_err_lifetimes = !cfg!(test)
- && parameters_to_write.iter().all(|arg| {
- matches!(
- arg.data(Interner),
- chalk_ir::GenericArgData::Lifetime(it) if *it.data(Interner) == LifetimeData::Error
- )
- });
- if !parameters_to_write.is_empty() && !only_err_lifetimes {
+ if !parameters_to_write.is_empty() {
write!(f, "<")?;
hir_fmt_generic_arguments(f, parameters_to_write, self_)?;
write!(f, ">")?;
@@ -1461,12 +1479,14 @@ fn generic_args_sans_defaults<'ga>(
}
// otherwise, if the arg is equal to the param default, hide it (unless the
// default is an error which can happen for the trait Self type)
- #[allow(unstable_name_collisions)]
- IsNoneOr::is_none_or(default_parameters.get(i), |default_parameter| {
- // !is_err(default_parameter.skip_binders())
- // &&
- arg != &default_parameter.clone().substitute(Interner, &parameters)
- })
+ match default_parameters.get(i) {
+ None => true,
+ Some(default_parameter) => {
+ // !is_err(default_parameter.skip_binders())
+ // &&
+ arg != &default_parameter.clone().substitute(Interner, &parameters)
+ }
+ }
};
let mut default_from = 0;
for (i, parameter) in parameters.iter().enumerate() {
@@ -1495,18 +1515,6 @@ fn hir_fmt_generic_arguments(
None => (parameters, &[][..]),
};
for generic_arg in lifetimes.iter().chain(ty_or_const) {
- // FIXME: Remove this
- // most of our lifetimes will be errors as we lack elision and inference
- // so don't render them for now
- if !cfg!(test)
- && matches!(
- generic_arg.lifetime(Interner),
- Some(l) if ***l.interned() == LifetimeData::Error
- )
- {
- continue;
- }
-
if !mem::take(&mut first) {
write!(f, ", ")?;
}
@@ -1633,7 +1641,7 @@ fn write_bounds_like_dyn_trait(
// existential) here, which is the only thing that's
// possible in actual Rust, and hence don't print it
f.start_location_link(trait_.into());
- write!(f, "{}", f.db.trait_data(trait_).name.display(f.db.upcast()))?;
+ write!(f, "{}", f.db.trait_data(trait_).name.display(f.db.upcast(), f.edition()))?;
f.end_location_link();
if is_fn_trait {
if let [self_, params @ ..] = trait_ref.substitution.as_slice(Interner) {
@@ -1707,7 +1715,7 @@ fn write_bounds_like_dyn_trait(
let assoc_ty_id = from_assoc_type_id(proj.associated_ty_id);
let type_alias = f.db.type_alias_data(assoc_ty_id);
f.start_location_link(assoc_ty_id.into());
- write!(f, "{}", type_alias.name.display(f.db.upcast()))?;
+ write!(f, "{}", type_alias.name.display(f.db.upcast(), f.edition()))?;
f.end_location_link();
let proj_arg_count = generics(f.db.upcast(), assoc_ty_id.into()).len_self();
@@ -1771,7 +1779,7 @@ fn fmt_trait_ref(
}
let trait_ = tr.hir_trait_id();
f.start_location_link(trait_.into());
- write!(f, "{}", f.db.trait_data(trait_).name.display(f.db.upcast()))?;
+ write!(f, "{}", f.db.trait_data(trait_).name.display(f.db.upcast(), f.edition()))?;
f.end_location_link();
let substs = tr.substitution.as_slice(Interner);
hir_fmt_generics(f, &substs[1..], None, substs[0].ty(Interner))
@@ -1797,7 +1805,11 @@ impl HirDisplay for WhereClause {
write!(f, ">::",)?;
let type_alias = from_assoc_type_id(projection_ty.associated_ty_id);
f.start_location_link(type_alias.into());
- write!(f, "{}", f.db.type_alias_data(type_alias).name.display(f.db.upcast()),)?;
+ write!(
+ f,
+ "{}",
+ f.db.type_alias_data(type_alias).name.display(f.db.upcast(), f.edition()),
+ )?;
f.end_location_link();
write!(f, " = ")?;
ty.hir_fmt(f)?;
@@ -1833,14 +1845,20 @@ impl HirDisplay for LifetimeData {
let id = lt_from_placeholder_idx(f.db, *idx);
let generics = generics(f.db.upcast(), id.parent);
let param_data = &generics[id.local_id];
- write!(f, "{}", param_data.name.display(f.db.upcast()))?;
+ write!(f, "{}", param_data.name.display(f.db.upcast(), f.edition()))?;
Ok(())
}
_ if f.display_target.is_source_code() => write!(f, "'_"),
LifetimeData::BoundVar(idx) => idx.hir_fmt(f),
LifetimeData::InferenceVar(_) => write!(f, "_"),
LifetimeData::Static => write!(f, "'static"),
- LifetimeData::Error => write!(f, "'?"),
+ LifetimeData::Error => {
+ if cfg!(test) {
+ write!(f, "'?")
+ } else {
+ write!(f, "'_")
+ }
+ }
LifetimeData::Erased => write!(f, "'<erased>"),
LifetimeData::Phantom(void, _) => match *void {},
}
@@ -1855,7 +1873,7 @@ impl HirDisplay for DomainGoal {
wc.hir_fmt(f)?;
write!(f, ")")?;
}
- _ => write!(f, "?")?,
+ _ => write!(f, "_")?,
}
Ok(())
}
@@ -1914,7 +1932,7 @@ impl HirDisplay for TypeRef {
};
write!(f, "&")?;
if let Some(lifetime) = lifetime {
- write!(f, "{} ", lifetime.name.display(f.db.upcast()))?;
+ write!(f, "{} ", lifetime.name.display(f.db.upcast(), f.edition()))?;
}
write!(f, "{mutability}")?;
inner.hir_fmt(f)?;
@@ -1922,7 +1940,7 @@ impl HirDisplay for TypeRef {
TypeRef::Array(inner, len) => {
write!(f, "[")?;
inner.hir_fmt(f)?;
- write!(f, "; {}]", len.display(f.db.upcast()))?;
+ write!(f, "; {}]", len.display(f.db.upcast(), f.edition()))?;
}
TypeRef::Slice(inner) => {
write!(f, "[")?;
@@ -1943,7 +1961,7 @@ impl HirDisplay for TypeRef {
for index in 0..function_parameters.len() {
let (param_name, param_type) = &function_parameters[index];
if let Some(name) = param_name {
- write!(f, "{}: ", name.display(f.db.upcast()))?;
+ write!(f, "{}: ", name.display(f.db.upcast(), f.edition()))?;
}
param_type.hir_fmt(f)?;
@@ -2001,12 +2019,15 @@ impl HirDisplay for TypeBound {
}
path.hir_fmt(f)
}
- TypeBound::Lifetime(lifetime) => write!(f, "{}", lifetime.name.display(f.db.upcast())),
+ TypeBound::Lifetime(lifetime) => {
+ write!(f, "{}", lifetime.name.display(f.db.upcast(), f.edition()))
+ }
TypeBound::ForLifetime(lifetimes, path) => {
+ let edition = f.edition();
write!(
f,
"for<{}> ",
- lifetimes.iter().map(|it| it.display(f.db.upcast())).format(", ")
+ lifetimes.iter().map(|it| it.display(f.db.upcast(), edition)).format(", ")
)?;
path.hir_fmt(f)
}
@@ -2072,7 +2093,7 @@ impl HirDisplay for Path {
if !matches!(self.kind(), PathKind::Plain) || seg_idx > 0 {
write!(f, "::")?;
}
- write!(f, "{}", segment.name.display(f.db.upcast()))?;
+ write!(f, "{}", segment.name.display(f.db.upcast(), f.edition()))?;
if let Some(generic_args) = segment.args_and_bindings {
// We should be in type context, so format as `Foo<Bar>` instead of `Foo::<Bar>`.
// Do we actually format expressions?
@@ -2117,7 +2138,7 @@ impl HirDisplay for Path {
} else {
write!(f, ", ")?;
}
- write!(f, "{}", binding.name.display(f.db.upcast()))?;
+ write!(f, "{}", binding.name.display(f.db.upcast(), f.edition()))?;
match &binding.type_ref {
Some(ty) => {
write!(f, " = ")?;
@@ -2151,9 +2172,11 @@ impl HirDisplay for hir_def::path::GenericArg {
fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
match self {
hir_def::path::GenericArg::Type(ty) => ty.hir_fmt(f),
- hir_def::path::GenericArg::Const(c) => write!(f, "{}", c.display(f.db.upcast())),
+ hir_def::path::GenericArg::Const(c) => {
+ write!(f, "{}", c.display(f.db.upcast(), f.edition()))
+ }
hir_def::path::GenericArg::Lifetime(lifetime) => {
- write!(f, "{}", lifetime.name.display(f.db.upcast()))
+ write!(f, "{}", lifetime.name.display(f.db.upcast(), f.edition()))
}
}
}
diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs
index 45d423d03c..062ea27815 100644
--- a/crates/hir-ty/src/infer.rs
+++ b/crates/hir-ty/src/infer.rs
@@ -22,7 +22,7 @@ mod pat;
mod path;
pub(crate) mod unify;
-use std::{convert::identity, iter, ops::Index};
+use std::{cell::OnceCell, convert::identity, iter, ops::Index};
use chalk_ir::{
cast::Cast,
@@ -49,17 +49,17 @@ use hir_expand::name::Name;
use indexmap::IndexSet;
use intern::sym;
use la_arena::{ArenaMap, Entry};
-use once_cell::unsync::OnceCell;
use rustc_hash::{FxHashMap, FxHashSet};
use stdx::{always, never};
use triomphe::Arc;
use crate::{
db::HirDatabase,
- error_lifetime, fold_tys,
+ fold_tys,
generics::Generics,
infer::{coerce::CoerceMany, unify::InferenceTable},
lower::ImplTraitLoweringMode,
+ mir::MirSpan,
to_assoc_type_id,
traits::FnTrait,
utils::{InTypeConstIdMetadata, UnevaluatedConstEvaluatorFolder},
@@ -328,13 +328,13 @@ pub struct Adjustment {
}
impl Adjustment {
- pub fn borrow(m: Mutability, ty: Ty) -> Self {
- let ty = TyKind::Ref(m, error_lifetime(), ty).intern(Interner);
- Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(m)), target: ty }
+ pub fn borrow(m: Mutability, ty: Ty, lt: Lifetime) -> Self {
+ let ty = TyKind::Ref(m, lt.clone(), ty).intern(Interner);
+ Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(lt, m)), target: ty }
}
}
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Adjust {
/// Go from ! to any type.
NeverToAny,
@@ -354,18 +354,18 @@ pub enum Adjust {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct OverloadedDeref(pub Option<Mutability>);
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum AutoBorrow {
/// Converts from T to &T.
- Ref(Mutability),
+ Ref(Lifetime, Mutability),
/// Converts from T to *T.
RawPtr(Mutability),
}
impl AutoBorrow {
- fn mutability(self) -> Mutability {
- let (AutoBorrow::Ref(m) | AutoBorrow::RawPtr(m)) = self;
- m
+ fn mutability(&self) -> Mutability {
+ let (AutoBorrow::Ref(_, m) | AutoBorrow::RawPtr(m)) = self;
+ *m
}
}
@@ -554,6 +554,12 @@ pub(crate) struct InferenceContext<'a> {
// fields related to closure capture
current_captures: Vec<CapturedItemWithoutTy>,
+ /// A stack that has an entry for each projection in the current capture.
+ ///
+ /// For example, in `a.b.c`, we capture the spans of `a`, `a.b`, and `a.b.c`.
+ /// We do that because sometimes we truncate projections (when a closure captures
+ /// both `a.b` and `a.b.c`), and we want to provide accurate spans in this case.
+ current_capture_span_stack: Vec<MirSpan>,
current_closure: Option<ClosureId>,
/// Stores the list of closure ids that need to be analyzed before this closure. See the
/// comment on `InferenceContext::sort_closures`
@@ -605,6 +611,11 @@ fn find_continuable(
}
}
+enum ImplTraitReplacingMode {
+ ReturnPosition(FxHashSet<Ty>),
+ TypeAlias,
+}
+
impl<'a> InferenceContext<'a> {
fn new(
db: &'a dyn HirDatabase,
@@ -630,6 +641,7 @@ impl<'a> InferenceContext<'a> {
breakables: Vec::new(),
deferred_cast_checks: Vec::new(),
current_captures: Vec::new(),
+ current_capture_span_stack: Vec::new(),
current_closure: None,
deferred_closures: FxHashMap::default(),
closure_dependencies: FxHashMap::default(),
@@ -826,13 +838,19 @@ impl<'a> InferenceContext<'a> {
self.write_binding_ty(self_param, ty);
}
}
- let mut params_and_ret_tys = Vec::new();
+ let mut tait_candidates = FxHashSet::default();
for (ty, pat) in param_tys.zip(&*self.body.params) {
let ty = self.insert_type_vars(ty);
let ty = self.normalize_associated_types_in(ty);
self.infer_top_pat(*pat, &ty);
- params_and_ret_tys.push(ty);
+ if ty
+ .data(Interner)
+ .flags
+ .intersects(TypeFlags::HAS_TY_OPAQUE.union(TypeFlags::HAS_TY_INFER))
+ {
+ tait_candidates.insert(ty);
+ }
}
let return_ty = &*data.ret_type;
@@ -845,7 +863,12 @@ impl<'a> InferenceContext<'a> {
let return_ty = if let Some(rpits) = self.db.return_type_impl_traits(func) {
// RPIT opaque types use substitution of their parent function.
let fn_placeholders = TyBuilder::placeholder_subst(self.db, func);
- let result = self.insert_inference_vars_for_impl_trait(return_ty, fn_placeholders);
+ let mut mode = ImplTraitReplacingMode::ReturnPosition(FxHashSet::default());
+ let result =
+ self.insert_inference_vars_for_impl_trait(return_ty, fn_placeholders, &mut mode);
+ if let ImplTraitReplacingMode::ReturnPosition(taits) = mode {
+ tait_candidates.extend(taits);
+ }
let rpits = rpits.skip_binders();
for (id, _) in rpits.impl_traits.iter() {
if let Entry::Vacant(e) = self.result.type_of_rpit.entry(id) {
@@ -864,11 +887,23 @@ impl<'a> InferenceContext<'a> {
// Functions might be defining usage sites of TAITs.
// To define an TAITs, that TAIT must appear in the function's signatures.
// So, it suffices to check for params and return types.
- params_and_ret_tys.push(self.return_ty.clone());
- self.make_tait_coercion_table(params_and_ret_tys.iter());
+ if self
+ .return_ty
+ .data(Interner)
+ .flags
+ .intersects(TypeFlags::HAS_TY_OPAQUE.union(TypeFlags::HAS_TY_INFER))
+ {
+ tait_candidates.insert(self.return_ty.clone());
+ }
+ self.make_tait_coercion_table(tait_candidates.iter());
}
- fn insert_inference_vars_for_impl_trait<T>(&mut self, t: T, placeholders: Substitution) -> T
+ fn insert_inference_vars_for_impl_trait<T>(
+ &mut self,
+ t: T,
+ placeholders: Substitution,
+ mode: &mut ImplTraitReplacingMode,
+ ) -> T
where
T: crate::HasInterner<Interner = Interner> + crate::TypeFoldable<Interner>,
{
@@ -881,10 +916,31 @@ impl<'a> InferenceContext<'a> {
};
let (impl_traits, idx) =
match self.db.lookup_intern_impl_trait_id(opaque_ty_id.into()) {
+ // We don't replace opaque types from other kind with inference vars
+ // because `insert_inference_vars_for_impl_traits` for each kinds
+ // and unreplaced opaque types of other kind are resolved while
+ // inferencing because of `tait_coercion_table`.
+ // Moreover, calling `insert_inference_vars_for_impl_traits` with same
+ // `placeholders` for other kind may cause trouble because
+ // the substs for the bounds of each impl traits do not match
ImplTraitId::ReturnTypeImplTrait(def, idx) => {
+ if matches!(mode, ImplTraitReplacingMode::TypeAlias) {
+ // RPITs don't have `tait_coercion_table`, so use inserted inference
+ // vars for them.
+ if let Some(ty) = self.result.type_of_rpit.get(idx) {
+ return ty.clone();
+ }
+ return ty;
+ }
(self.db.return_type_impl_traits(def), idx)
}
ImplTraitId::TypeAliasImplTrait(def, idx) => {
+ if let ImplTraitReplacingMode::ReturnPosition(taits) = mode {
+ // Gather TAITs while replacing RPITs because TAITs inside RPITs
+ // may not visited while replacing TAITs
+ taits.insert(ty.clone());
+ return ty;
+ }
(self.db.type_alias_impl_traits(def), idx)
}
_ => unreachable!(),
@@ -893,16 +949,20 @@ impl<'a> InferenceContext<'a> {
return ty;
};
let bounds = (*impl_traits)
- .map_ref(|rpits| rpits.impl_traits[idx].bounds.map_ref(|it| it.iter()));
+ .map_ref(|its| its.impl_traits[idx].bounds.map_ref(|it| it.iter()));
let var = self.table.new_type_var();
let var_subst = Substitution::from1(Interner, var.clone());
for bound in bounds {
- let predicate = bound.map(|it| it.cloned()).substitute(Interner, &placeholders);
+ let predicate = bound.map(|it| it.cloned());
+ let predicate = predicate.substitute(Interner, &placeholders);
let (var_predicate, binders) =
predicate.substitute(Interner, &var_subst).into_value_and_skipped_binders();
always!(binders.is_empty(Interner)); // quantified where clauses not yet handled
- let var_predicate = self
- .insert_inference_vars_for_impl_trait(var_predicate, placeholders.clone());
+ let var_predicate = self.insert_inference_vars_for_impl_trait(
+ var_predicate,
+ placeholders.clone(),
+ mode,
+ );
self.push_obligation(var_predicate.cast(Interner));
}
self.result.type_of_rpit.insert(idx, var.clone());
@@ -1039,7 +1099,11 @@ impl<'a> InferenceContext<'a> {
self.db.lookup_intern_impl_trait_id(id.into())
{
let subst = TyBuilder::placeholder_subst(self.db, alias_id);
- let ty = self.insert_inference_vars_for_impl_trait(ty, subst);
+ let ty = self.insert_inference_vars_for_impl_trait(
+ ty,
+ subst,
+ &mut ImplTraitReplacingMode::TypeAlias,
+ );
Some((id, ty))
} else {
None
@@ -1436,7 +1500,8 @@ impl<'a> InferenceContext<'a> {
let remaining = unresolved.map(|it| path.segments()[it..].len()).filter(|it| it > &0);
let ty = match ty.kind(Interner) {
TyKind::Alias(AliasTy::Projection(proj_ty)) => {
- self.db.normalize_projection(proj_ty.clone(), self.table.trait_env.clone())
+ let ty = self.table.normalize_projection_ty(proj_ty.clone());
+ self.table.resolve_ty_shallow(&ty)
}
_ => ty,
};
diff --git a/crates/hir-ty/src/infer/closure.rs b/crates/hir-ty/src/infer/closure.rs
index 034ed2d691..36327d1d49 100644
--- a/crates/hir-ty/src/infer/closure.rs
+++ b/crates/hir-ty/src/infer/closure.rs
@@ -18,8 +18,9 @@ use hir_def::{
use hir_expand::name::Name;
use intern::sym;
use rustc_hash::FxHashMap;
-use smallvec::SmallVec;
-use stdx::never;
+use smallvec::{smallvec, SmallVec};
+use stdx::{format_to, never};
+use syntax::utils::is_raw_identifier;
use crate::{
db::{HirDatabase, InternedClosure},
@@ -236,7 +237,13 @@ pub enum CaptureKind {
pub struct CapturedItem {
pub(crate) place: HirPlace,
pub(crate) kind: CaptureKind,
- pub(crate) span: MirSpan,
+ /// The inner vec is the stacks; the outer vec is for each capture reference.
+ ///
+ /// Even though we always report only the last span (i.e. the most inclusive span),
+ /// we need to keep them all, since when a closure occurs inside a closure, we
+ /// copy all captures of the inner closure to the outer closure, and then we may
+ /// truncate them, and we want the correct span to be reported.
+ span_stacks: SmallVec<[SmallVec<[MirSpan; 3]>; 3]>,
pub(crate) ty: Binders<Ty>,
}
@@ -245,6 +252,11 @@ impl CapturedItem {
self.place.local
}
+ /// Returns whether this place has any field (aka. non-deref) projections.
+ pub fn has_field_projections(&self) -> bool {
+ self.place.projections.iter().any(|it| !matches!(it, ProjectionElem::Deref))
+ }
+
pub fn ty(&self, subst: &Substitution) -> Ty {
self.ty.clone().substitute(Interner, utils::ClosureSubst(subst).parent_subst())
}
@@ -253,9 +265,106 @@ impl CapturedItem {
self.kind
}
+ pub fn spans(&self) -> SmallVec<[MirSpan; 3]> {
+ self.span_stacks.iter().map(|stack| *stack.last().expect("empty span stack")).collect()
+ }
+
+ /// Converts the place to a name that can be inserted into source code.
+ pub fn place_to_name(&self, owner: DefWithBodyId, db: &dyn HirDatabase) -> String {
+ let body = db.body(owner);
+ let mut result = body[self.place.local].name.unescaped().display(db.upcast()).to_string();
+ for proj in &self.place.projections {
+ match proj {
+ ProjectionElem::Deref => {}
+ ProjectionElem::Field(Either::Left(f)) => {
+ match &*f.parent.variant_data(db.upcast()) {
+ VariantData::Record(fields) => {
+ result.push('_');
+ result.push_str(fields[f.local_id].name.as_str())
+ }
+ VariantData::Tuple(fields) => {
+ let index = fields.iter().position(|it| it.0 == f.local_id);
+ if let Some(index) = index {
+ format_to!(result, "_{index}");
+ }
+ }
+ VariantData::Unit => {}
+ }
+ }
+ ProjectionElem::Field(Either::Right(f)) => format_to!(result, "_{}", f.index),
+ &ProjectionElem::ClosureField(field) => format_to!(result, "_{field}"),
+ ProjectionElem::Index(_)
+ | ProjectionElem::ConstantIndex { .. }
+ | ProjectionElem::Subslice { .. }
+ | ProjectionElem::OpaqueCast(_) => {
+ never!("Not happen in closure capture");
+ continue;
+ }
+ }
+ }
+ if is_raw_identifier(&result, db.crate_graph()[owner.module(db.upcast()).krate()].edition) {
+ result.insert_str(0, "r#");
+ }
+ result
+ }
+
+ pub fn display_place_source_code(&self, owner: DefWithBodyId, db: &dyn HirDatabase) -> String {
+ let body = db.body(owner);
+ let krate = owner.krate(db.upcast());
+ let edition = db.crate_graph()[krate].edition;
+ let mut result = body[self.place.local].name.display(db.upcast(), edition).to_string();
+ for proj in &self.place.projections {
+ match proj {
+ // In source code autoderef kicks in.
+ ProjectionElem::Deref => {}
+ ProjectionElem::Field(Either::Left(f)) => {
+ let variant_data = f.parent.variant_data(db.upcast());
+ match &*variant_data {
+ VariantData::Record(fields) => format_to!(
+ result,
+ ".{}",
+ fields[f.local_id].name.display(db.upcast(), edition)
+ ),
+ VariantData::Tuple(fields) => format_to!(
+ result,
+ ".{}",
+ fields.iter().position(|it| it.0 == f.local_id).unwrap_or_default()
+ ),
+ VariantData::Unit => {}
+ }
+ }
+ ProjectionElem::Field(Either::Right(f)) => {
+ let field = f.index;
+ format_to!(result, ".{field}");
+ }
+ &ProjectionElem::ClosureField(field) => {
+ format_to!(result, ".{field}");
+ }
+ ProjectionElem::Index(_)
+ | ProjectionElem::ConstantIndex { .. }
+ | ProjectionElem::Subslice { .. }
+ | ProjectionElem::OpaqueCast(_) => {
+ never!("Not happen in closure capture");
+ continue;
+ }
+ }
+ }
+ let final_derefs_count = self
+ .place
+ .projections
+ .iter()
+ .rev()
+ .take_while(|proj| matches!(proj, ProjectionElem::Deref))
+ .count();
+ result.insert_str(0, &"*".repeat(final_derefs_count));
+ result
+ }
+
pub fn display_place(&self, owner: DefWithBodyId, db: &dyn HirDatabase) -> String {
let body = db.body(owner);
- let mut result = body[self.place.local].name.display(db.upcast()).to_string();
+ let krate = owner.krate(db.upcast());
+ let edition = db.crate_graph()[krate].edition;
+ let mut result = body[self.place.local].name.display(db.upcast(), edition).to_string();
let mut field_need_paren = false;
for proj in &self.place.projections {
match proj {
@@ -312,7 +421,8 @@ impl CapturedItem {
pub(crate) struct CapturedItemWithoutTy {
pub(crate) place: HirPlace,
pub(crate) kind: CaptureKind,
- pub(crate) span: MirSpan,
+ /// The inner vec is the stacks; the outer vec is for each capture reference.
+ pub(crate) span_stacks: SmallVec<[SmallVec<[MirSpan; 3]>; 3]>,
}
impl CapturedItemWithoutTy {
@@ -331,7 +441,7 @@ impl CapturedItemWithoutTy {
return CapturedItem {
place: self.place,
kind: self.kind,
- span: self.span,
+ span_stacks: self.span_stacks,
ty: replace_placeholder_with_binder(ctx, ty),
};
@@ -391,22 +501,26 @@ impl InferenceContext<'_> {
let r = self.place_of_expr_without_adjust(tgt_expr)?;
let default = vec![];
let adjustments = self.result.expr_adjustments.get(&tgt_expr).unwrap_or(&default);
- apply_adjusts_to_place(r, adjustments)
+ apply_adjusts_to_place(&mut self.current_capture_span_stack, r, adjustments)
}
+ /// Changes `current_capture_span_stack` to contain the stack of spans for this expr.
fn place_of_expr_without_adjust(&mut self, tgt_expr: ExprId) -> Option<HirPlace> {
+ self.current_capture_span_stack.clear();
match &self.body[tgt_expr] {
Expr::Path(p) => {
let resolver = resolver_for_expr(self.db.upcast(), self.owner, tgt_expr);
if let Some(ResolveValueResult::ValueNs(ValueNs::LocalBinding(b), _)) =
resolver.resolve_path_in_value_ns(self.db.upcast(), p)
{
+ self.current_capture_span_stack.push(MirSpan::ExprId(tgt_expr));
return Some(HirPlace { local: b, projections: vec![] });
}
}
Expr::Field { expr, name: _ } => {
let mut place = self.place_of_expr(*expr)?;
let field = self.result.field_resolution(tgt_expr)?;
+ self.current_capture_span_stack.push(MirSpan::ExprId(tgt_expr));
place.projections.push(ProjectionElem::Field(field));
return Some(place);
}
@@ -416,6 +530,7 @@ impl InferenceContext<'_> {
TyKind::Ref(..) | TyKind::Raw(..)
) {
let mut place = self.place_of_expr(*expr)?;
+ self.current_capture_span_stack.push(MirSpan::ExprId(tgt_expr));
place.projections.push(ProjectionElem::Deref);
return Some(place);
}
@@ -425,29 +540,57 @@ impl InferenceContext<'_> {
None
}
- fn push_capture(&mut self, capture: CapturedItemWithoutTy) {
- self.current_captures.push(capture);
+ fn push_capture(&mut self, place: HirPlace, kind: CaptureKind) {
+ self.current_captures.push(CapturedItemWithoutTy {
+ place,
+ kind,
+ span_stacks: smallvec![self.current_capture_span_stack.iter().copied().collect()],
+ });
}
- fn ref_expr(&mut self, expr: ExprId) {
- if let Some(place) = self.place_of_expr(expr) {
- self.add_capture(place, CaptureKind::ByRef(BorrowKind::Shared), expr.into());
+ fn truncate_capture_spans(&self, capture: &mut CapturedItemWithoutTy, mut truncate_to: usize) {
+ // The first span is the identifier, and it must always remain.
+ truncate_to += 1;
+ for span_stack in &mut capture.span_stacks {
+ let mut remained = truncate_to;
+ let mut actual_truncate_to = 0;
+ for &span in &*span_stack {
+ actual_truncate_to += 1;
+ if !span.is_ref_span(self.body) {
+ remained -= 1;
+ if remained == 0 {
+ break;
+ }
+ }
+ }
+ if actual_truncate_to < span_stack.len()
+ && span_stack[actual_truncate_to].is_ref_span(self.body)
+ {
+ // Include the ref operator if there is one, we will fix it later (in `strip_captures_ref_span()`) if it's incorrect.
+ actual_truncate_to += 1;
+ }
+ span_stack.truncate(actual_truncate_to);
+ }
+ }
+
+ fn ref_expr(&mut self, expr: ExprId, place: Option<HirPlace>) {
+ if let Some(place) = place {
+ self.add_capture(place, CaptureKind::ByRef(BorrowKind::Shared));
}
self.walk_expr(expr);
}
- fn add_capture(&mut self, place: HirPlace, kind: CaptureKind, span: MirSpan) {
+ fn add_capture(&mut self, place: HirPlace, kind: CaptureKind) {
if self.is_upvar(&place) {
- self.push_capture(CapturedItemWithoutTy { place, kind, span });
+ self.push_capture(place, kind);
}
}
- fn mutate_expr(&mut self, expr: ExprId) {
- if let Some(place) = self.place_of_expr(expr) {
+ fn mutate_expr(&mut self, expr: ExprId, place: Option<HirPlace>) {
+ if let Some(place) = place {
self.add_capture(
place,
CaptureKind::ByRef(BorrowKind::Mut { kind: MutBorrowKind::Default }),
- expr.into(),
);
}
self.walk_expr(expr);
@@ -455,12 +598,12 @@ impl InferenceContext<'_> {
fn consume_expr(&mut self, expr: ExprId) {
if let Some(place) = self.place_of_expr(expr) {
- self.consume_place(place, expr.into());
+ self.consume_place(place);
}
self.walk_expr(expr);
}
- fn consume_place(&mut self, place: HirPlace, span: MirSpan) {
+ fn consume_place(&mut self, place: HirPlace) {
if self.is_upvar(&place) {
let ty = place.ty(self);
let kind = if self.is_ty_copy(ty) {
@@ -468,13 +611,13 @@ impl InferenceContext<'_> {
} else {
CaptureKind::ByValue
};
- self.push_capture(CapturedItemWithoutTy { place, kind, span });
+ self.push_capture(place, kind);
}
}
fn walk_expr_with_adjust(&mut self, tgt_expr: ExprId, adjustment: &[Adjustment]) {
if let Some((last, rest)) = adjustment.split_last() {
- match last.kind {
+ match &last.kind {
Adjust::NeverToAny | Adjust::Deref(None) | Adjust::Pointer(_) => {
self.walk_expr_with_adjust(tgt_expr, rest)
}
@@ -499,8 +642,10 @@ impl InferenceContext<'_> {
Mutability::Not => CaptureKind::ByRef(BorrowKind::Shared),
};
if let Some(place) = self.place_of_expr_without_adjust(tgt_expr) {
- if let Some(place) = apply_adjusts_to_place(place, rest) {
- self.add_capture(place, capture_kind, tgt_expr.into());
+ if let Some(place) =
+ apply_adjusts_to_place(&mut self.current_capture_span_stack, place, rest)
+ {
+ self.add_capture(place, capture_kind);
}
}
self.walk_expr_with_adjust(tgt_expr, rest);
@@ -582,11 +727,7 @@ impl InferenceContext<'_> {
self.walk_pat(&mut capture_mode, arm.pat);
}
if let Some(c) = capture_mode {
- self.push_capture(CapturedItemWithoutTy {
- place: discr_place,
- kind: c,
- span: (*expr).into(),
- })
+ self.push_capture(discr_place, c);
}
}
}
@@ -630,10 +771,11 @@ impl InferenceContext<'_> {
}
false
};
+ let place = self.place_of_expr(*expr);
if mutability {
- self.mutate_expr(*expr);
+ self.mutate_expr(*expr, place);
} else {
- self.ref_expr(*expr);
+ self.ref_expr(*expr, place);
}
} else {
self.select_from_expr(*expr);
@@ -648,16 +790,22 @@ impl InferenceContext<'_> {
| Expr::Cast { expr, type_ref: _ } => {
self.consume_expr(*expr);
}
- Expr::Ref { expr, rawness: _, mutability } => match mutability {
- hir_def::type_ref::Mutability::Shared => self.ref_expr(*expr),
- hir_def::type_ref::Mutability::Mut => self.mutate_expr(*expr),
- },
+ Expr::Ref { expr, rawness: _, mutability } => {
+ // We need to do this before we push the span so the order will be correct.
+ let place = self.place_of_expr(*expr);
+ self.current_capture_span_stack.push(MirSpan::ExprId(tgt_expr));
+ match mutability {
+ hir_def::type_ref::Mutability::Shared => self.ref_expr(*expr, place),
+ hir_def::type_ref::Mutability::Mut => self.mutate_expr(*expr, place),
+ }
+ }
Expr::BinaryOp { lhs, rhs, op } => {
let Some(op) = op else {
return;
};
if matches!(op, BinaryOp::Assignment { .. }) {
- self.mutate_expr(*lhs);
+ let place = self.place_of_expr(*lhs);
+ self.mutate_expr(*lhs, place);
self.consume_expr(*rhs);
return;
}
@@ -688,7 +836,11 @@ impl InferenceContext<'_> {
);
let mut cc = mem::take(&mut self.current_captures);
cc.extend(captures.iter().filter(|it| self.is_upvar(&it.place)).map(|it| {
- CapturedItemWithoutTy { place: it.place.clone(), kind: it.kind, span: it.span }
+ CapturedItemWithoutTy {
+ place: it.place.clone(),
+ kind: it.kind,
+ span_stacks: it.span_stacks.clone(),
+ }
}));
self.current_captures = cc;
}
@@ -810,10 +962,13 @@ impl InferenceContext<'_> {
}
fn restrict_precision_for_unsafe(&mut self) {
- for capture in &mut self.current_captures {
+ // FIXME: Borrow checker problems without this.
+ let mut current_captures = std::mem::take(&mut self.current_captures);
+ for capture in &mut current_captures {
let mut ty = self.table.resolve_completely(self.result[capture.place.local].clone());
if ty.as_raw_ptr().is_some() || ty.is_union() {
capture.kind = CaptureKind::ByRef(BorrowKind::Shared);
+ self.truncate_capture_spans(capture, 0);
capture.place.projections.truncate(0);
continue;
}
@@ -828,29 +983,35 @@ impl InferenceContext<'_> {
);
if ty.as_raw_ptr().is_some() || ty.is_union() {
capture.kind = CaptureKind::ByRef(BorrowKind::Shared);
+ self.truncate_capture_spans(capture, i + 1);
capture.place.projections.truncate(i + 1);
break;
}
}
}
+ self.current_captures = current_captures;
}
fn adjust_for_move_closure(&mut self) {
- for capture in &mut self.current_captures {
+ // FIXME: Borrow checker won't allow without this.
+ let mut current_captures = std::mem::take(&mut self.current_captures);
+ for capture in &mut current_captures {
if let Some(first_deref) =
capture.place.projections.iter().position(|proj| *proj == ProjectionElem::Deref)
{
+ self.truncate_capture_spans(capture, first_deref);
capture.place.projections.truncate(first_deref);
}
capture.kind = CaptureKind::ByValue;
}
+ self.current_captures = current_captures;
}
fn minimize_captures(&mut self) {
- self.current_captures.sort_by_key(|it| it.place.projections.len());
+ self.current_captures.sort_unstable_by_key(|it| it.place.projections.len());
let mut hash_map = FxHashMap::<HirPlace, usize>::default();
let result = mem::take(&mut self.current_captures);
- for item in result {
+ for mut item in result {
let mut lookup_place = HirPlace { local: item.place.local, projections: vec![] };
let mut it = item.place.projections.iter();
let prev_index = loop {
@@ -858,12 +1019,17 @@ impl InferenceContext<'_> {
break Some(*k);
}
match it.next() {
- Some(it) => lookup_place.projections.push(it.clone()),
+ Some(it) => {
+ lookup_place.projections.push(it.clone());
+ }
None => break None,
}
};
match prev_index {
Some(p) => {
+ let prev_projections_len = self.current_captures[p].place.projections.len();
+ self.truncate_capture_spans(&mut item, prev_projections_len);
+ self.current_captures[p].span_stacks.extend(item.span_stacks);
let len = self.current_captures[p].place.projections.len();
let kind_after_truncate =
item.place.capture_kind_of_truncated_place(item.kind, len);
@@ -878,113 +1044,128 @@ impl InferenceContext<'_> {
}
}
- fn consume_with_pat(&mut self, mut place: HirPlace, pat: PatId) {
- let cnt = self.result.pat_adjustments.get(&pat).map(|it| it.len()).unwrap_or_default();
- place.projections = place
- .projections
- .iter()
- .cloned()
- .chain((0..cnt).map(|_| ProjectionElem::Deref))
- .collect::<Vec<_>>();
- match &self.body[pat] {
- Pat::Missing | Pat::Wild => (),
- Pat::Tuple { args, ellipsis } => {
- let (al, ar) = args.split_at(ellipsis.map_or(args.len(), |it| it as usize));
- let field_count = match self.result[pat].kind(Interner) {
- TyKind::Tuple(_, s) => s.len(Interner),
- _ => return,
- };
- let fields = 0..field_count;
- let it = al.iter().zip(fields.clone()).chain(ar.iter().rev().zip(fields.rev()));
- for (arg, i) in it {
- let mut p = place.clone();
- p.projections.push(ProjectionElem::Field(Either::Right(TupleFieldId {
- tuple: TupleId(!0), // dummy this, as its unused anyways
- index: i as u32,
- })));
- self.consume_with_pat(p, *arg);
- }
- }
- Pat::Or(pats) => {
- for pat in pats.iter() {
- self.consume_with_pat(place.clone(), *pat);
+ fn consume_with_pat(&mut self, mut place: HirPlace, tgt_pat: PatId) {
+ let adjustments_count =
+ self.result.pat_adjustments.get(&tgt_pat).map(|it| it.len()).unwrap_or_default();
+ place.projections.extend((0..adjustments_count).map(|_| ProjectionElem::Deref));
+ self.current_capture_span_stack
+ .extend((0..adjustments_count).map(|_| MirSpan::PatId(tgt_pat)));
+ 'reset_span_stack: {
+ match &self.body[tgt_pat] {
+ Pat::Missing | Pat::Wild => (),
+ Pat::Tuple { args, ellipsis } => {
+ let (al, ar) = args.split_at(ellipsis.map_or(args.len(), |it| it as usize));
+ let field_count = match self.result[tgt_pat].kind(Interner) {
+ TyKind::Tuple(_, s) => s.len(Interner),
+ _ => break 'reset_span_stack,
+ };
+ let fields = 0..field_count;
+ let it = al.iter().zip(fields.clone()).chain(ar.iter().rev().zip(fields.rev()));
+ for (&arg, i) in it {
+ let mut p = place.clone();
+ self.current_capture_span_stack.push(MirSpan::PatId(arg));
+ p.projections.push(ProjectionElem::Field(Either::Right(TupleFieldId {
+ tuple: TupleId(!0), // dummy this, as its unused anyways
+ index: i as u32,
+ })));
+ self.consume_with_pat(p, arg);
+ self.current_capture_span_stack.pop();
+ }
}
- }
- Pat::Record { args, .. } => {
- let Some(variant) = self.result.variant_resolution_for_pat(pat) else {
- return;
- };
- match variant {
- VariantId::EnumVariantId(_) | VariantId::UnionId(_) => {
- self.consume_place(place, pat.into())
+ Pat::Or(pats) => {
+ for pat in pats.iter() {
+ self.consume_with_pat(place.clone(), *pat);
}
- VariantId::StructId(s) => {
- let vd = &*self.db.struct_data(s).variant_data;
- for field_pat in args.iter() {
- let arg = field_pat.pat;
- let Some(local_id) = vd.field(&field_pat.name) else {
- continue;
- };
- let mut p = place.clone();
- p.projections.push(ProjectionElem::Field(Either::Left(FieldId {
- parent: variant,
- local_id,
- })));
- self.consume_with_pat(p, arg);
+ }
+ Pat::Record { args, .. } => {
+ let Some(variant) = self.result.variant_resolution_for_pat(tgt_pat) else {
+ break 'reset_span_stack;
+ };
+ match variant {
+ VariantId::EnumVariantId(_) | VariantId::UnionId(_) => {
+ self.consume_place(place)
+ }
+ VariantId::StructId(s) => {
+ let vd = &*self.db.struct_data(s).variant_data;
+ for field_pat in args.iter() {
+ let arg = field_pat.pat;
+ let Some(local_id) = vd.field(&field_pat.name) else {
+ continue;
+ };
+ let mut p = place.clone();
+ self.current_capture_span_stack.push(MirSpan::PatId(arg));
+ p.projections.push(ProjectionElem::Field(Either::Left(FieldId {
+ parent: variant,
+ local_id,
+ })));
+ self.consume_with_pat(p, arg);
+ self.current_capture_span_stack.pop();
+ }
}
}
}
- }
- Pat::Range { .. }
- | Pat::Slice { .. }
- | Pat::ConstBlock(_)
- | Pat::Path(_)
- | Pat::Lit(_) => self.consume_place(place, pat.into()),
- Pat::Bind { id: _, subpat: _ } => {
- let mode = self.result.binding_modes[pat];
- let capture_kind = match mode {
- BindingMode::Move => {
- self.consume_place(place, pat.into());
- return;
- }
- BindingMode::Ref(Mutability::Not) => BorrowKind::Shared,
- BindingMode::Ref(Mutability::Mut) => {
- BorrowKind::Mut { kind: MutBorrowKind::Default }
- }
- };
- self.add_capture(place, CaptureKind::ByRef(capture_kind), pat.into());
- }
- Pat::TupleStruct { path: _, args, ellipsis } => {
- let Some(variant) = self.result.variant_resolution_for_pat(pat) else {
- return;
- };
- match variant {
- VariantId::EnumVariantId(_) | VariantId::UnionId(_) => {
- self.consume_place(place, pat.into())
- }
- VariantId::StructId(s) => {
- let vd = &*self.db.struct_data(s).variant_data;
- let (al, ar) = args.split_at(ellipsis.map_or(args.len(), |it| it as usize));
- let fields = vd.fields().iter();
- let it =
- al.iter().zip(fields.clone()).chain(ar.iter().rev().zip(fields.rev()));
- for (arg, (i, _)) in it {
- let mut p = place.clone();
- p.projections.push(ProjectionElem::Field(Either::Left(FieldId {
- parent: variant,
- local_id: i,
- })));
- self.consume_with_pat(p, *arg);
+ Pat::Range { .. }
+ | Pat::Slice { .. }
+ | Pat::ConstBlock(_)
+ | Pat::Path(_)
+ | Pat::Lit(_) => self.consume_place(place),
+ &Pat::Bind { id, subpat: _ } => {
+ let mode = self.result.binding_modes[tgt_pat];
+ let capture_kind = match mode {
+ BindingMode::Move => {
+ self.consume_place(place);
+ break 'reset_span_stack;
+ }
+ BindingMode::Ref(Mutability::Not) => BorrowKind::Shared,
+ BindingMode::Ref(Mutability::Mut) => {
+ BorrowKind::Mut { kind: MutBorrowKind::Default }
+ }
+ };
+ self.current_capture_span_stack.push(MirSpan::BindingId(id));
+ self.add_capture(place, CaptureKind::ByRef(capture_kind));
+ self.current_capture_span_stack.pop();
+ }
+ Pat::TupleStruct { path: _, args, ellipsis } => {
+ let Some(variant) = self.result.variant_resolution_for_pat(tgt_pat) else {
+ break 'reset_span_stack;
+ };
+ match variant {
+ VariantId::EnumVariantId(_) | VariantId::UnionId(_) => {
+ self.consume_place(place)
+ }
+ VariantId::StructId(s) => {
+ let vd = &*self.db.struct_data(s).variant_data;
+ let (al, ar) =
+ args.split_at(ellipsis.map_or(args.len(), |it| it as usize));
+ let fields = vd.fields().iter();
+ let it = al
+ .iter()
+ .zip(fields.clone())
+ .chain(ar.iter().rev().zip(fields.rev()));
+ for (&arg, (i, _)) in it {
+ let mut p = place.clone();
+ self.current_capture_span_stack.push(MirSpan::PatId(arg));
+ p.projections.push(ProjectionElem::Field(Either::Left(FieldId {
+ parent: variant,
+ local_id: i,
+ })));
+ self.consume_with_pat(p, arg);
+ self.current_capture_span_stack.pop();
+ }
}
}
}
+ Pat::Ref { pat, mutability: _ } => {
+ self.current_capture_span_stack.push(MirSpan::PatId(tgt_pat));
+ place.projections.push(ProjectionElem::Deref);
+ self.consume_with_pat(place, *pat);
+ self.current_capture_span_stack.pop();
+ }
+ Pat::Box { .. } => (), // not supported
}
- Pat::Ref { pat, mutability: _ } => {
- place.projections.push(ProjectionElem::Deref);
- self.consume_with_pat(place, *pat)
- }
- Pat::Box { .. } => (), // not supported
}
+ self.current_capture_span_stack
+ .truncate(self.current_capture_span_stack.len() - adjustments_count);
}
fn consume_exprs(&mut self, exprs: impl Iterator<Item = ExprId>) {
@@ -1042,12 +1223,28 @@ impl InferenceContext<'_> {
CaptureBy::Ref => (),
}
self.minimize_captures();
+ self.strip_captures_ref_span();
let result = mem::take(&mut self.current_captures);
let captures = result.into_iter().map(|it| it.with_ty(self)).collect::<Vec<_>>();
self.result.closure_info.insert(closure, (captures, closure_kind));
closure_kind
}
+ fn strip_captures_ref_span(&mut self) {
+ // FIXME: Borrow checker won't allow without this.
+ let mut captures = std::mem::take(&mut self.current_captures);
+ for capture in &mut captures {
+ if matches!(capture.kind, CaptureKind::ByValue) {
+ for span_stack in &mut capture.span_stacks {
+ if span_stack[span_stack.len() - 1].is_ref_span(self.body) {
+ span_stack.truncate(span_stack.len() - 1);
+ }
+ }
+ }
+ }
+ self.current_captures = captures;
+ }
+
pub(crate) fn infer_closures(&mut self) {
let deferred_closures = self.sort_closures();
for (closure, exprs) in deferred_closures.into_iter().rev() {
@@ -1108,10 +1305,17 @@ impl InferenceContext<'_> {
}
}
-fn apply_adjusts_to_place(mut r: HirPlace, adjustments: &[Adjustment]) -> Option<HirPlace> {
+/// Call this only when the last span in the stack isn't a split.
+fn apply_adjusts_to_place(
+ current_capture_span_stack: &mut Vec<MirSpan>,
+ mut r: HirPlace,
+ adjustments: &[Adjustment],
+) -> Option<HirPlace> {
+ let span = *current_capture_span_stack.last().expect("empty capture span stack");
for adj in adjustments {
match &adj.kind {
Adjust::Deref(None) => {
+ current_capture_span_stack.push(span);
r.projections.push(ProjectionElem::Deref);
}
_ => return None,
diff --git a/crates/hir-ty/src/infer/coerce.rs b/crates/hir-ty/src/infer/coerce.rs
index 6f85a4a424..7e758c0b51 100644
--- a/crates/hir-ty/src/infer/coerce.rs
+++ b/crates/hir-ty/src/infer/coerce.rs
@@ -18,14 +18,13 @@ use triomphe::Arc;
use crate::{
autoderef::{Autoderef, AutoderefKind},
db::HirDatabase,
- error_lifetime,
infer::{
Adjust, Adjustment, AutoBorrow, InferOk, InferenceContext, OverloadedDeref, PointerCast,
TypeError, TypeMismatch,
},
utils::ClosureSubst,
- Canonical, DomainGoal, FnAbi, FnPointer, FnSig, Guidance, InEnvironment, Interner, Solution,
- Substitution, TraitEnvironment, Ty, TyBuilder, TyExt,
+ Canonical, DomainGoal, FnAbi, FnPointer, FnSig, Guidance, InEnvironment, Interner, Lifetime,
+ Solution, Substitution, TraitEnvironment, Ty, TyBuilder, TyExt,
};
use super::unify::InferenceTable;
@@ -301,7 +300,7 @@ impl InferenceTable<'_> {
// Examine the supertype and consider auto-borrowing.
match to_ty.kind(Interner) {
TyKind::Raw(mt, _) => return self.coerce_ptr(from_ty, to_ty, *mt),
- TyKind::Ref(mt, _, _) => return self.coerce_ref(from_ty, to_ty, *mt),
+ TyKind::Ref(mt, lt, _) => return self.coerce_ref(from_ty, to_ty, *mt, lt),
_ => {}
}
@@ -377,11 +376,17 @@ impl InferenceTable<'_> {
/// Reborrows `&mut A` to `&mut B` and `&(mut) A` to `&B`.
/// To match `A` with `B`, autoderef will be performed,
/// calling `deref`/`deref_mut` where necessary.
- fn coerce_ref(&mut self, from_ty: Ty, to_ty: &Ty, to_mt: Mutability) -> CoerceResult {
- let from_mt = match from_ty.kind(Interner) {
- &TyKind::Ref(mt, _, _) => {
- coerce_mutabilities(mt, to_mt)?;
- mt
+ fn coerce_ref(
+ &mut self,
+ from_ty: Ty,
+ to_ty: &Ty,
+ to_mt: Mutability,
+ to_lt: &Lifetime,
+ ) -> CoerceResult {
+ let (_from_lt, from_mt) = match from_ty.kind(Interner) {
+ TyKind::Ref(mt, lt, _) => {
+ coerce_mutabilities(*mt, to_mt)?;
+ (lt.clone(), *mt) // clone is probably not good?
}
_ => return self.unify_and(&from_ty, to_ty, identity),
};
@@ -427,8 +432,8 @@ impl InferenceTable<'_> {
// compare those. Note that this means we use the target
// mutability [1], since it may be that we are coercing
// from `&mut T` to `&U`.
- let lt = error_lifetime(); // FIXME: handle lifetimes correctly, see rustc
- let derefd_from_ty = TyKind::Ref(to_mt, lt, referent_ty).intern(Interner);
+ let lt = to_lt; // FIXME: Involve rustc LUB and SUB flag checks
+ let derefd_from_ty = TyKind::Ref(to_mt, lt.clone(), referent_ty).intern(Interner);
match autoderef.table.try_unify(&derefd_from_ty, to_ty) {
Ok(result) => {
found = Some(result.map(|()| derefd_from_ty));
@@ -472,8 +477,10 @@ impl InferenceTable<'_> {
}
let mut adjustments = auto_deref_adjust_steps(&autoderef);
- adjustments
- .push(Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(to_mt)), target: ty.clone() });
+ adjustments.push(Adjustment {
+ kind: Adjust::Borrow(AutoBorrow::Ref(to_lt.clone(), to_mt)),
+ target: ty.clone(),
+ });
success(adjustments, ty, goals)
}
@@ -621,11 +628,11 @@ impl InferenceTable<'_> {
(TyKind::Ref(from_mt, _, from_inner), &TyKind::Ref(to_mt, _, _)) => {
coerce_mutabilities(*from_mt, to_mt)?;
- let lt = error_lifetime();
+ let lt = self.new_lifetime_var();
Some((
Adjustment { kind: Adjust::Deref(None), target: from_inner.clone() },
Adjustment {
- kind: Adjust::Borrow(AutoBorrow::Ref(to_mt)),
+ kind: Adjust::Borrow(AutoBorrow::Ref(lt.clone(), to_mt)),
target: TyKind::Ref(to_mt, lt, from_inner.clone()).intern(Interner),
},
))
diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs
index f5eb37f427..89d92ea9af 100644
--- a/crates/hir-ty/src/infer/expr.rs
+++ b/crates/hir-ty/src/infer/expr.rs
@@ -635,7 +635,10 @@ impl InferenceContext<'_> {
let inner_ty = self.infer_expr_inner(*expr, &expectation);
match rawness {
Rawness::RawPtr => TyKind::Raw(mutability, inner_ty),
- Rawness::Ref => TyKind::Ref(mutability, error_lifetime(), inner_ty),
+ Rawness::Ref => {
+ let lt = self.table.new_lifetime_var();
+ TyKind::Ref(mutability, lt, inner_ty)
+ }
}
.intern(Interner)
}
@@ -786,18 +789,23 @@ impl InferenceContext<'_> {
adj.apply(&mut self.table, base_ty)
});
// mutability will be fixed up in `InferenceContext::infer_mut`;
- adj.push(Adjustment::borrow(Mutability::Not, self_ty.clone()));
+ adj.push(Adjustment::borrow(
+ Mutability::Not,
+ self_ty.clone(),
+ self.table.new_lifetime_var(),
+ ));
self.write_expr_adj(*base, adj);
if let Some(func) = self
.db
.trait_data(index_trait)
.method_by_name(&Name::new_symbol_root(sym::index.clone()))
{
- let substs = TyBuilder::subst_for_def(self.db, index_trait, None)
- .push(self_ty.clone())
- .push(index_ty.clone())
- .build();
- self.write_method_resolution(tgt_expr, func, substs);
+ let subst = TyBuilder::subst_for_def(self.db, index_trait, None);
+ if subst.remaining() != 2 {
+ return self.err_ty();
+ }
+ let subst = subst.push(self_ty.clone()).push(index_ty.clone()).build();
+ self.write_method_resolution(tgt_expr, func, subst);
}
let assoc = self.resolve_ops_index_output();
let res = self.resolve_associated_type_with_params(
@@ -990,7 +998,7 @@ impl InferenceContext<'_> {
match fn_x {
FnTrait::FnOnce => (),
FnTrait::FnMut => {
- if let TyKind::Ref(Mutability::Mut, _, inner) = derefed_callee.kind(Interner) {
+ if let TyKind::Ref(Mutability::Mut, lt, inner) = derefed_callee.kind(Interner) {
if adjustments
.last()
.map(|it| matches!(it.kind, Adjust::Borrow(_)))
@@ -999,15 +1007,27 @@ impl InferenceContext<'_> {
// prefer reborrow to move
adjustments
.push(Adjustment { kind: Adjust::Deref(None), target: inner.clone() });
- adjustments.push(Adjustment::borrow(Mutability::Mut, inner.clone()))
+ adjustments.push(Adjustment::borrow(
+ Mutability::Mut,
+ inner.clone(),
+ lt.clone(),
+ ))
}
} else {
- adjustments.push(Adjustment::borrow(Mutability::Mut, derefed_callee.clone()));
+ adjustments.push(Adjustment::borrow(
+ Mutability::Mut,
+ derefed_callee.clone(),
+ self.table.new_lifetime_var(),
+ ));
}
}
FnTrait::Fn => {
if !matches!(derefed_callee.kind(Interner), TyKind::Ref(Mutability::Not, _, _)) {
- adjustments.push(Adjustment::borrow(Mutability::Not, derefed_callee.clone()));
+ adjustments.push(Adjustment::borrow(
+ Mutability::Not,
+ derefed_callee.clone(),
+ self.table.new_lifetime_var(),
+ ));
}
}
}
@@ -1295,10 +1315,12 @@ impl InferenceContext<'_> {
// HACK: We can use this substitution for the function because the function itself doesn't
// have its own generic parameters.
- let subst = TyBuilder::subst_for_def(self.db, trait_, None)
- .push(lhs_ty.clone())
- .push(rhs_ty.clone())
- .build();
+ let subst = TyBuilder::subst_for_def(self.db, trait_, None);
+ if subst.remaining() != 2 {
+ return Ty::new(Interner, TyKind::Error);
+ }
+ let subst = subst.push(lhs_ty.clone()).push(rhs_ty.clone()).build();
+
self.write_method_resolution(tgt_expr, func, subst.clone());
let method_ty = self.db.value_ty(func.into()).unwrap().substitute(Interner, &subst);
@@ -1310,11 +1332,11 @@ impl InferenceContext<'_> {
Some(sig) => {
let p_left = &sig.params()[0];
if matches!(op, BinaryOp::CmpOp(..) | BinaryOp::Assignment { .. }) {
- if let &TyKind::Ref(mtbl, _, _) = p_left.kind(Interner) {
+ if let TyKind::Ref(mtbl, lt, _) = p_left.kind(Interner) {
self.write_expr_adj(
lhs,
vec![Adjustment {
- kind: Adjust::Borrow(AutoBorrow::Ref(mtbl)),
+ kind: Adjust::Borrow(AutoBorrow::Ref(lt.clone(), *mtbl)),
target: p_left.clone(),
}],
);
@@ -1322,11 +1344,11 @@ impl InferenceContext<'_> {
}
let p_right = &sig.params()[1];
if matches!(op, BinaryOp::CmpOp(..)) {
- if let &TyKind::Ref(mtbl, _, _) = p_right.kind(Interner) {
+ if let TyKind::Ref(mtbl, lt, _) = p_right.kind(Interner) {
self.write_expr_adj(
rhs,
vec![Adjustment {
- kind: Adjust::Borrow(AutoBorrow::Ref(mtbl)),
+ kind: Adjust::Borrow(AutoBorrow::Ref(lt.clone(), *mtbl)),
target: p_right.clone(),
}],
);
diff --git a/crates/hir-ty/src/infer/mutability.rs b/crates/hir-ty/src/infer/mutability.rs
index 66267e08db..7fed5f0203 100644
--- a/crates/hir-ty/src/infer/mutability.rs
+++ b/crates/hir-ty/src/infer/mutability.rs
@@ -28,7 +28,7 @@ impl InferenceContext<'_> {
Adjust::NeverToAny | Adjust::Deref(None) | Adjust::Pointer(_) => (),
Adjust::Deref(Some(d)) => *d = OverloadedDeref(Some(mutability)),
Adjust::Borrow(b) => match b {
- AutoBorrow::Ref(m) | AutoBorrow::RawPtr(m) => mutability = *m,
+ AutoBorrow::Ref(_, m) | AutoBorrow::RawPtr(m) => mutability = *m,
},
}
}
@@ -125,7 +125,7 @@ impl InferenceContext<'_> {
.get_mut(&base)
.and_then(|it| it.last_mut());
if let Some(Adjustment {
- kind: Adjust::Borrow(AutoBorrow::Ref(mutability)),
+ kind: Adjust::Borrow(AutoBorrow::Ref(_, mutability)),
target,
}) = base_adjustments
{
diff --git a/crates/hir-ty/src/infer/pat.rs b/crates/hir-ty/src/infer/pat.rs
index f3c6f13a08..50542b2acd 100644
--- a/crates/hir-ty/src/infer/pat.rs
+++ b/crates/hir-ty/src/infer/pat.rs
@@ -12,7 +12,6 @@ use stdx::TupleExt;
use crate::{
consteval::{try_const_usize, usize_const},
- error_lifetime,
infer::{BindingMode, Expectation, InferenceContext, TypeMismatch},
lower::lower_to_chalk_mutability,
primitive::UintTy,
@@ -394,19 +393,20 @@ impl InferenceContext<'_> {
expected: &Ty,
default_bm: BindingMode,
) -> Ty {
- let expectation = match expected.as_reference() {
- Some((inner_ty, _lifetime, _exp_mut)) => inner_ty.clone(),
+ let (expectation_type, expectation_lt) = match expected.as_reference() {
+ Some((inner_ty, lifetime, _exp_mut)) => (inner_ty.clone(), lifetime.clone()),
None => {
let inner_ty = self.table.new_type_var();
+ let inner_lt = self.table.new_lifetime_var();
let ref_ty =
- TyKind::Ref(mutability, error_lifetime(), inner_ty.clone()).intern(Interner);
+ TyKind::Ref(mutability, inner_lt.clone(), inner_ty.clone()).intern(Interner);
// Unification failure will be reported by the caller.
self.unify(&ref_ty, expected);
- inner_ty
+ (inner_ty, inner_lt)
}
};
- let subty = self.infer_pat(inner_pat, &expectation, default_bm);
- TyKind::Ref(mutability, error_lifetime(), subty).intern(Interner)
+ let subty = self.infer_pat(inner_pat, &expectation_type, default_bm);
+ TyKind::Ref(mutability, expectation_lt, subty).intern(Interner)
}
fn infer_bind_pat(
@@ -433,7 +433,8 @@ impl InferenceContext<'_> {
let bound_ty = match mode {
BindingMode::Ref(mutability) => {
- TyKind::Ref(mutability, error_lifetime(), inner_ty.clone()).intern(Interner)
+ let inner_lt = self.table.new_lifetime_var();
+ TyKind::Ref(mutability, inner_lt, inner_ty.clone()).intern(Interner)
}
BindingMode::Move => inner_ty.clone(),
};
diff --git a/crates/hir-ty/src/infer/unify.rs b/crates/hir-ty/src/infer/unify.rs
index 3e3578b9f9..c0f5ddddcb 100644
--- a/crates/hir-ty/src/infer/unify.rs
+++ b/crates/hir-ty/src/infer/unify.rs
@@ -17,12 +17,12 @@ use triomphe::Arc;
use super::{InferOk, InferResult, InferenceContext, TypeError};
use crate::{
- consteval::unknown_const, db::HirDatabase, error_lifetime, fold_generic_args,
- fold_tys_and_consts, to_chalk_trait_id, traits::FnTrait, AliasEq, AliasTy, BoundVar, Canonical,
- Const, ConstValue, DebruijnIndex, DomainGoal, GenericArg, GenericArgData, Goal, GoalData,
- Guidance, InEnvironment, InferenceVar, Interner, Lifetime, OpaqueTyId, ParamKind, ProjectionTy,
- ProjectionTyExt, Scalar, Solution, Substitution, TraitEnvironment, Ty, TyBuilder, TyExt,
- TyKind, VariableKind, WhereClause,
+ consteval::unknown_const, db::HirDatabase, fold_generic_args, fold_tys_and_consts,
+ to_chalk_trait_id, traits::FnTrait, AliasEq, AliasTy, BoundVar, Canonical, Const, ConstValue,
+ DebruijnIndex, DomainGoal, GenericArg, GenericArgData, Goal, GoalData, Guidance, InEnvironment,
+ InferenceVar, Interner, Lifetime, OpaqueTyId, ParamKind, ProjectionTy, ProjectionTyExt, Scalar,
+ Solution, Substitution, TraitEnvironment, Ty, TyBuilder, TyExt, TyKind, VariableKind,
+ WhereClause,
};
impl InferenceContext<'_> {
@@ -105,7 +105,7 @@ impl<T: HasInterner<Interner = Interner>> Canonicalized<T> {
VariableKind::Ty(TyVariableKind::Float) => ctx.new_float_var().cast(Interner),
// Chalk can sometimes return new lifetime variables. We just replace them by errors
// for now.
- VariableKind::Lifetime => error_lifetime().cast(Interner),
+ VariableKind::Lifetime => ctx.new_lifetime_var().cast(Interner),
VariableKind::Const(ty) => ctx.new_const_var(ty.clone()).cast(Interner),
}),
);
diff --git a/crates/hir-ty/src/layout/tests.rs b/crates/hir-ty/src/layout/tests.rs
index 8cb428a610..f40d508f75 100644
--- a/crates/hir-ty/src/layout/tests.rs
+++ b/crates/hir-ty/src/layout/tests.rs
@@ -42,19 +42,20 @@ fn eval_goal(ra_fixture: &str, minicore: &str) -> Result<Arc<Layout>, LayoutErro
hir_def::ModuleDefId::AdtId(x) => {
let name = match x {
hir_def::AdtId::StructId(x) => {
- db.struct_data(x).name.display_no_db().to_smolstr()
+ db.struct_data(x).name.display_no_db(file_id.edition()).to_smolstr()
}
hir_def::AdtId::UnionId(x) => {
- db.union_data(x).name.display_no_db().to_smolstr()
+ db.union_data(x).name.display_no_db(file_id.edition()).to_smolstr()
}
hir_def::AdtId::EnumId(x) => {
- db.enum_data(x).name.display_no_db().to_smolstr()
+ db.enum_data(x).name.display_no_db(file_id.edition()).to_smolstr()
}
};
(name == "Goal").then_some(Either::Left(x))
}
hir_def::ModuleDefId::TypeAliasId(x) => {
- let name = db.type_alias_data(x).name.display_no_db().to_smolstr();
+ let name =
+ db.type_alias_data(x).name.display_no_db(file_id.edition()).to_smolstr();
(name == "Goal").then_some(Either::Right(x))
}
_ => None,
@@ -94,7 +95,7 @@ fn eval_expr(ra_fixture: &str, minicore: &str) -> Result<Arc<Layout>, LayoutErro
.declarations()
.find_map(|x| match x {
hir_def::ModuleDefId::FunctionId(x) => {
- let name = db.function_data(x).name.display_no_db().to_smolstr();
+ let name = db.function_data(x).name.display_no_db(file_id.edition()).to_smolstr();
(name == "main").then_some(x)
}
_ => None,
@@ -104,7 +105,7 @@ fn eval_expr(ra_fixture: &str, minicore: &str) -> Result<Arc<Layout>, LayoutErro
let b = hir_body
.bindings
.iter()
- .find(|x| x.1.name.display_no_db().to_smolstr() == "goal")
+ .find(|x| x.1.name.display_no_db(file_id.edition()).to_smolstr() == "goal")
.unwrap()
.0;
let infer = db.infer(function_id.into());
diff --git a/crates/hir-ty/src/lib.rs b/crates/hir-ty/src/lib.rs
index 21c84511dc..2f4e764f4c 100644
--- a/crates/hir-ty/src/lib.rs
+++ b/crates/hir-ty/src/lib.rs
@@ -68,6 +68,7 @@ use intern::{sym, Symbol};
use la_arena::{Arena, Idx};
use mir::{MirEvalError, VTableMap};
use rustc_hash::{FxHashMap, FxHashSet};
+use span::Edition;
use syntax::ast::{make, ConstArg};
use traits::FnTrait;
use triomphe::Arc;
@@ -1027,7 +1028,11 @@ where
collector.placeholders.into_iter().collect()
}
-pub fn known_const_to_ast(konst: &Const, db: &dyn HirDatabase) -> Option<ConstArg> {
+pub fn known_const_to_ast(
+ konst: &Const,
+ db: &dyn HirDatabase,
+ edition: Edition,
+) -> Option<ConstArg> {
if let ConstValue::Concrete(c) = &konst.interned().value {
match c.interned {
ConstScalar::UnevaluatedConst(GeneralConstId::InTypeConstId(cid), _) => {
@@ -1037,5 +1042,5 @@ pub fn known_const_to_ast(konst: &Const, db: &dyn HirDatabase) -> Option<ConstAr
_ => (),
}
}
- Some(make::expr_const_value(konst.display(db).to_string().as_str()))
+ Some(make::expr_const_value(konst.display(db, edition).to_string().as_str()))
}
diff --git a/crates/hir-ty/src/lower.rs b/crates/hir-ty/src/lower.rs
index 67cdb99744..370d9ba99c 100644
--- a/crates/hir-ty/src/lower.rs
+++ b/crates/hir-ty/src/lower.rs
@@ -6,7 +6,7 @@
//!
//! This usually involves resolving names, collecting generic arguments etc.
use std::{
- cell::{Cell, RefCell, RefMut},
+ cell::{Cell, OnceCell, RefCell, RefMut},
iter,
ops::{self, Not as _},
};
@@ -43,7 +43,6 @@ use hir_def::{
use hir_expand::{name::Name, ExpandResult};
use intern::Interned;
use la_arena::{Arena, ArenaMap};
-use once_cell::unsync::OnceCell;
use rustc_hash::FxHashSet;
use rustc_pattern_analysis::Captures;
use smallvec::SmallVec;
@@ -378,26 +377,25 @@ impl<'a> TyLoweringContext<'a> {
// Count the number of `impl Trait` things that appear within our bounds.
// Since t hose have been emitted as implicit type args already.
counter.set(idx + count_impl_traits(type_ref) as u16);
- let (
- _parent_params,
- self_param,
- type_params,
- const_params,
- _impl_trait_params,
- lifetime_params,
- ) = self
+ let kind = self
.generics()
.expect("variable impl trait lowering must be in a generic def")
- .provenance_split();
- TyKind::BoundVar(BoundVar::new(
- self.in_binders,
- idx as usize
- + self_param as usize
- + type_params
- + const_params
- + lifetime_params,
- ))
- .intern(Interner)
+ .iter()
+ .enumerate()
+ .filter_map(|(i, (id, data))| match (id, data) {
+ (
+ GenericParamId::TypeParamId(_),
+ GenericParamDataRef::TypeParamData(data),
+ ) if data.provenance == TypeParamProvenance::ArgumentImplTrait => {
+ Some(i)
+ }
+ _ => None,
+ })
+ .nth(idx as usize)
+ .map_or(TyKind::Error, |id| {
+ TyKind::BoundVar(BoundVar { debruijn: self.in_binders, index: id })
+ });
+ kind.intern(Interner)
}
ImplTraitLoweringState::Disallowed => {
// FIXME: report error
@@ -1553,6 +1551,10 @@ pub(crate) fn generic_predicates_for_param_query(
}
};
if invalid_target {
+ // If this is filtered out without lowering, `?Sized` is not gathered into `ctx.unsized_types`
+ if let TypeBound::Path(_, TraitBoundModifier::Maybe) = &**bound {
+ ctx.lower_where_predicate(pred, &def, true).for_each(drop);
+ }
return false;
}
@@ -1741,15 +1743,39 @@ fn implicitly_sized_clauses<'a, 'subst: 'a>(
substitution: &'subst Substitution,
resolver: &Resolver,
) -> Option<impl Iterator<Item = WhereClause> + Captures<'a> + Captures<'subst>> {
- let is_trait_def = matches!(def, GenericDefId::TraitId(..));
- let generic_args = &substitution.as_slice(Interner)[is_trait_def as usize..];
let sized_trait = db
.lang_item(resolver.krate(), LangItem::Sized)
- .and_then(|lang_item| lang_item.as_trait().map(to_chalk_trait_id));
+ .and_then(|lang_item| lang_item.as_trait().map(to_chalk_trait_id))?;
- sized_trait.map(move |sized_trait| {
- generic_args
- .iter()
+ let get_trait_self_idx = |container: ItemContainerId| {
+ if matches!(container, ItemContainerId::TraitId(_)) {
+ let generics = generics(db.upcast(), def);
+ Some(generics.len_self())
+ } else {
+ None
+ }
+ };
+ let trait_self_idx = match def {
+ GenericDefId::TraitId(_) => Some(0),
+ GenericDefId::FunctionId(it) => get_trait_self_idx(it.lookup(db.upcast()).container),
+ GenericDefId::ConstId(it) => get_trait_self_idx(it.lookup(db.upcast()).container),
+ GenericDefId::TypeAliasId(it) => get_trait_self_idx(it.lookup(db.upcast()).container),
+ _ => None,
+ };
+
+ Some(
+ substitution
+ .iter(Interner)
+ .enumerate()
+ .filter_map(
+ move |(idx, generic_arg)| {
+ if Some(idx) == trait_self_idx {
+ None
+ } else {
+ Some(generic_arg)
+ }
+ },
+ )
.filter_map(|generic_arg| generic_arg.ty(Interner))
.filter(move |&self_ty| !explicitly_unsized_tys.contains(self_ty))
.map(move |self_ty| {
@@ -1757,8 +1783,8 @@ fn implicitly_sized_clauses<'a, 'subst: 'a>(
trait_id: sized_trait,
substitution: Substitution::from1(Interner, self_ty.clone()),
})
- })
- })
+ }),
+ )
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -1978,13 +2004,13 @@ fn type_for_type_alias(db: &dyn HirDatabase, t: TypeAliasId) -> Binders<Ty> {
.with_impl_trait_mode(ImplTraitLoweringMode::Opaque)
.with_type_param_mode(ParamLoweringMode::Variable);
let type_alias_data = db.type_alias_data(t);
- if type_alias_data.is_extern {
- Binders::empty(Interner, TyKind::Foreign(crate::to_foreign_def_id(t)).intern(Interner))
+ let inner = if type_alias_data.is_extern {
+ TyKind::Foreign(crate::to_foreign_def_id(t)).intern(Interner)
} else {
let type_ref = &type_alias_data.type_ref;
- let inner = ctx.lower_ty(type_ref.as_deref().unwrap_or(&TypeRef::Error));
- make_binders(db, &generics, inner)
- }
+ ctx.lower_ty(type_ref.as_deref().unwrap_or(&TypeRef::Error))
+ };
+ make_binders(db, &generics, inner)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
diff --git a/crates/hir-ty/src/method_resolution.rs b/crates/hir-ty/src/method_resolution.rs
index 8ba8071d36..5a72b97653 100644
--- a/crates/hir-ty/src/method_resolution.rs
+++ b/crates/hir-ty/src/method_resolution.rs
@@ -35,7 +35,7 @@ use crate::{
};
/// This is used as a key for indexing impls.
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum TyFingerprint {
// These are lang item impls:
Str,
@@ -542,7 +542,8 @@ impl ReceiverAdjustments {
}
}
if let Some(m) = self.autoref {
- let a = Adjustment::borrow(m, ty);
+ let lt = table.new_lifetime_var();
+ let a = Adjustment::borrow(m, ty, lt);
ty = a.target.clone();
adjust.push(a);
}
@@ -1066,7 +1067,7 @@ fn iterate_method_candidates_by_receiver(
// be found in any of the derefs of receiver_ty, so we have to go through
// that, including raw derefs.
table.run_in_snapshot(|table| {
- let mut autoderef = autoderef::Autoderef::new(table, receiver_ty.clone(), true);
+ let mut autoderef = autoderef::Autoderef::new_no_tracking(table, receiver_ty.clone(), true);
while let Some((self_ty, _)) = autoderef.next() {
iterate_inherent_methods(
&self_ty,
@@ -1081,7 +1082,7 @@ fn iterate_method_candidates_by_receiver(
ControlFlow::Continue(())
})?;
table.run_in_snapshot(|table| {
- let mut autoderef = autoderef::Autoderef::new(table, receiver_ty.clone(), true);
+ let mut autoderef = autoderef::Autoderef::new_no_tracking(table, receiver_ty.clone(), true);
while let Some((self_ty, _)) = autoderef.next() {
if matches!(self_ty.kind(Interner), TyKind::InferenceVar(_, TyVariableKind::General)) {
// don't try to resolve methods on unknown types
@@ -1656,7 +1657,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, false);
+ let mut autoderef = autoderef::Autoderef::new_no_tracking(table, ty, false);
while let Some((ty, derefs)) = autoderef.next() {
deref_chain.push((
autoderef.table.canonicalize(ty),
diff --git a/crates/hir-ty/src/mir.rs b/crates/hir-ty/src/mir.rs
index 06a4236e0a..22d4da0e75 100644
--- a/crates/hir-ty/src/mir.rs
+++ b/crates/hir-ty/src/mir.rs
@@ -16,7 +16,8 @@ use base_db::CrateId;
use chalk_ir::Mutability;
use either::Either;
use hir_def::{
- hir::{BindingId, Expr, ExprId, Ordering, PatId},
+ body::Body,
+ hir::{BindingAnnotation, BindingId, Expr, ExprId, Ordering, PatId},
DefWithBodyId, FieldId, StaticId, TupleFieldId, UnionId, VariantId,
};
use la_arena::{Arena, ArenaMap, Idx, RawIdx};
@@ -158,7 +159,10 @@ impl<V, T> ProjectionElem<V, T> {
subst.at(Interner, 0).assert_ty_ref(Interner).clone()
}
_ => {
- never!("Overloaded deref on type {} is not a projection", base.display(db));
+ never!(
+ "Overloaded deref on type {} is not a projection",
+ base.display(db, db.crate_graph()[krate].edition)
+ );
TyKind::Error.intern(Interner)
}
},
@@ -633,6 +637,7 @@ pub enum TerminatorKind {
},
}
+// Order of variants in this enum matter: they are used to compare borrow kinds.
#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
pub enum BorrowKind {
/// Data must be immutable and is aliasable.
@@ -663,15 +668,16 @@ pub enum BorrowKind {
Mut { kind: MutBorrowKind },
}
+// Order of variants in this enum matter: they are used to compare borrow kinds.
#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
pub enum MutBorrowKind {
+ /// Data must be immutable but not aliasable. This kind of borrow cannot currently
+ /// be expressed by the user and is used only in implicit closure bindings.
+ ClosureCapture,
Default,
/// This borrow arose from method-call auto-ref
/// (i.e., adjustment::Adjust::Borrow).
TwoPhasedBorrow,
- /// Data must be immutable but not aliasable. This kind of borrow cannot currently
- /// be expressed by the user and is used only in implicit closure bindings.
- ClosureCapture,
}
impl BorrowKind {
@@ -1169,6 +1175,20 @@ pub enum MirSpan {
Unknown,
}
+impl MirSpan {
+ pub fn is_ref_span(&self, body: &Body) -> bool {
+ match *self {
+ MirSpan::ExprId(expr) => matches!(body[expr], Expr::Ref { .. }),
+ // FIXME: Figure out if this is correct wrt. match ergonomics.
+ MirSpan::BindingId(binding) => matches!(
+ body.bindings[binding].mode,
+ BindingAnnotation::Ref | BindingAnnotation::RefMut
+ ),
+ MirSpan::PatId(_) | MirSpan::SelfParam | MirSpan::Unknown => false,
+ }
+ }
+}
+
impl_from!(ExprId, PatId for MirSpan);
impl From<&ExprId> for MirSpan {
diff --git a/crates/hir-ty/src/mir/eval.rs b/crates/hir-ty/src/mir/eval.rs
index f8083e8985..1bb0c18868 100644
--- a/crates/hir-ty/src/mir/eval.rs
+++ b/crates/hir-ty/src/mir/eval.rs
@@ -23,7 +23,7 @@ use rustc_apfloat::{
Float,
};
use rustc_hash::{FxHashMap, FxHashSet};
-use span::FileId;
+use span::{Edition, FileId};
use stdx::never;
use syntax::{SyntaxNodePtr, TextRange};
use triomphe::Arc;
@@ -358,6 +358,7 @@ impl MirEvalError {
f: &mut String,
db: &dyn HirDatabase,
span_formatter: impl Fn(FileId, TextRange) -> String,
+ edition: Edition,
) -> std::result::Result<(), std::fmt::Error> {
writeln!(f, "Mir eval error:")?;
let mut err = self;
@@ -370,7 +371,7 @@ impl MirEvalError {
writeln!(
f,
"In function {} ({:?})",
- function_name.name.display(db.upcast()),
+ function_name.name.display(db.upcast(), edition),
func
)?;
}
@@ -415,7 +416,7 @@ impl MirEvalError {
write!(
f,
"Layout for type `{}` is not available due {err:?}",
- ty.display(db).with_closure_style(ClosureStyle::ClosureWithId)
+ ty.display(db, edition).with_closure_style(ClosureStyle::ClosureWithId)
)?;
}
MirEvalError::MirLowerError(func, err) => {
@@ -423,16 +424,17 @@ impl MirEvalError {
writeln!(
f,
"MIR lowering for function `{}` ({:?}) failed due:",
- function_name.name.display(db.upcast()),
+ function_name.name.display(db.upcast(), edition),
func
)?;
- err.pretty_print(f, db, span_formatter)?;
+ err.pretty_print(f, db, span_formatter, edition)?;
}
MirEvalError::ConstEvalError(name, err) => {
MirLowerError::ConstEvalError((**name).into(), err.clone()).pretty_print(
f,
db,
span_formatter,
+ edition,
)?;
}
MirEvalError::UndefinedBehavior(_)
@@ -1516,9 +1518,97 @@ impl Evaluator<'_> {
self.size_of_sized(target_ty, locals, "destination of int to int cast")?;
Owned(current[0..dest_size].to_vec())
}
- CastKind::FloatToInt => not_supported!("float to int cast"),
- CastKind::FloatToFloat => not_supported!("float to float cast"),
- CastKind::IntToFloat => not_supported!("float to int cast"),
+ CastKind::FloatToInt => {
+ let ty = self.operand_ty(operand, locals)?;
+ let TyKind::Scalar(chalk_ir::Scalar::Float(ty)) = ty.kind(Interner) else {
+ not_supported!("invalid float to int cast");
+ };
+ let value = self.eval_operand(operand, locals)?.get(self)?;
+ let value = match ty {
+ chalk_ir::FloatTy::F32 => {
+ let value = value.try_into().unwrap();
+ f32::from_le_bytes(value) as f64
+ }
+ chalk_ir::FloatTy::F64 => {
+ let value = value.try_into().unwrap();
+ f64::from_le_bytes(value)
+ }
+ chalk_ir::FloatTy::F16 | chalk_ir::FloatTy::F128 => {
+ not_supported!("unstable floating point type f16 and f128");
+ }
+ };
+ let is_signed = matches!(
+ target_ty.kind(Interner),
+ TyKind::Scalar(chalk_ir::Scalar::Int(_))
+ );
+ let dest_size =
+ self.size_of_sized(target_ty, locals, "destination of float to int cast")?;
+ let dest_bits = dest_size * 8;
+ let (max, min) = if dest_bits == 128 {
+ (i128::MAX, i128::MIN)
+ } else if is_signed {
+ let max = 1i128 << (dest_bits - 1);
+ (max - 1, -max)
+ } else {
+ (1i128 << dest_bits, 0)
+ };
+ let value = (value as i128).min(max).max(min);
+ let result = value.to_le_bytes();
+ Owned(result[0..dest_size].to_vec())
+ }
+ CastKind::FloatToFloat => {
+ let ty = self.operand_ty(operand, locals)?;
+ let TyKind::Scalar(chalk_ir::Scalar::Float(ty)) = ty.kind(Interner) else {
+ not_supported!("invalid float to int cast");
+ };
+ let value = self.eval_operand(operand, locals)?.get(self)?;
+ let value = match ty {
+ chalk_ir::FloatTy::F32 => {
+ let value = value.try_into().unwrap();
+ f32::from_le_bytes(value) as f64
+ }
+ chalk_ir::FloatTy::F64 => {
+ let value = value.try_into().unwrap();
+ f64::from_le_bytes(value)
+ }
+ chalk_ir::FloatTy::F16 | chalk_ir::FloatTy::F128 => {
+ not_supported!("unstable floating point type f16 and f128");
+ }
+ };
+ let TyKind::Scalar(chalk_ir::Scalar::Float(target_ty)) =
+ target_ty.kind(Interner)
+ else {
+ not_supported!("invalid float to float cast");
+ };
+ match target_ty {
+ chalk_ir::FloatTy::F32 => Owned((value as f32).to_le_bytes().to_vec()),
+ chalk_ir::FloatTy::F64 => Owned((value as f64).to_le_bytes().to_vec()),
+ chalk_ir::FloatTy::F16 | chalk_ir::FloatTy::F128 => {
+ not_supported!("unstable floating point type f16 and f128");
+ }
+ }
+ }
+ CastKind::IntToFloat => {
+ let current_ty = self.operand_ty(operand, locals)?;
+ let is_signed = matches!(
+ current_ty.kind(Interner),
+ TyKind::Scalar(chalk_ir::Scalar::Int(_))
+ );
+ let value = pad16(self.eval_operand(operand, locals)?.get(self)?, is_signed);
+ let value = i128::from_le_bytes(value);
+ let TyKind::Scalar(chalk_ir::Scalar::Float(target_ty)) =
+ target_ty.kind(Interner)
+ else {
+ not_supported!("invalid int to float cast");
+ };
+ match target_ty {
+ chalk_ir::FloatTy::F32 => Owned((value as f32).to_le_bytes().to_vec()),
+ chalk_ir::FloatTy::F64 => Owned((value as f64).to_le_bytes().to_vec()),
+ chalk_ir::FloatTy::F16 | chalk_ir::FloatTy::F128 => {
+ not_supported!("unstable floating point type f16 and f128");
+ }
+ }
+ }
CastKind::FnPtrToPtr => not_supported!("fn ptr to ptr cast"),
},
})
@@ -2675,10 +2765,11 @@ impl Evaluator<'_> {
let db = self.db.upcast();
let loc = variant.lookup(db);
let enum_loc = loc.parent.lookup(db);
+ let edition = self.db.crate_graph()[self.crate_id].edition;
let name = format!(
"{}::{}",
- enum_loc.id.item_tree(db)[enum_loc.id.value].name.display(db.upcast()),
- loc.id.item_tree(db)[loc.id.value].name.display(db.upcast()),
+ enum_loc.id.item_tree(db)[enum_loc.id.value].name.display(db.upcast(), edition),
+ loc.id.item_tree(db)[loc.id.value].name.display(db.upcast(), edition),
);
Err(MirEvalError::ConstEvalError(name, Box::new(e)))
}
diff --git a/crates/hir-ty/src/mir/eval/shim.rs b/crates/hir-ty/src/mir/eval/shim.rs
index d76f538187..0cdad74a4f 100644
--- a/crates/hir-ty/src/mir/eval/shim.rs
+++ b/crates/hir-ty/src/mir/eval/shim.rs
@@ -856,7 +856,11 @@ impl Evaluator<'_> {
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(),
+ Err(_) => {
+ let krate = locals.body.owner.krate(self.db.upcast());
+ let edition = self.db.crate_graph()[krate].edition;
+ ty.display(self.db, edition).to_string()
+ }
};
let len = ty_name.len();
let addr = self.heap_allocate(len, 1)?;
diff --git a/crates/hir-ty/src/mir/eval/tests.rs b/crates/hir-ty/src/mir/eval/tests.rs
index b21a401fa7..371a5278dc 100644
--- a/crates/hir-ty/src/mir/eval/tests.rs
+++ b/crates/hir-ty/src/mir/eval/tests.rs
@@ -1,5 +1,5 @@
use hir_def::db::DefDatabase;
-use span::EditionedFileId;
+use span::{Edition, EditionedFileId};
use syntax::{TextRange, TextSize};
use test_fixture::WithFixture;
@@ -15,7 +15,7 @@ fn eval_main(db: &TestDB, file_id: EditionedFileId) -> Result<(String, String),
.declarations()
.find_map(|x| match x {
hir_def::ModuleDefId::FunctionId(x) => {
- if db.function_data(x).name.display(db).to_string() == "main" {
+ if db.function_data(x).name.display(db, Edition::CURRENT).to_string() == "main" {
Some(x)
} else {
None
@@ -63,7 +63,7 @@ fn check_pass_and_stdio(ra_fixture: &str, expected_stdout: &str, expected_stderr
let span_formatter = |file, range: TextRange| {
format!("{:?} {:?}..{:?}", file, line_index(range.start()), line_index(range.end()))
};
- e.pretty_print(&mut err, &db, span_formatter).unwrap();
+ e.pretty_print(&mut err, &db, span_formatter, Edition::CURRENT).unwrap();
panic!("Error in interpreting: {err}");
}
Ok((stdout, stderr)) => {
diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs
index 9aa2eeebc1..9e23550451 100644
--- a/crates/hir-ty/src/mir/lower.rs
+++ b/crates/hir-ty/src/mir/lower.rs
@@ -21,7 +21,7 @@ use hir_expand::name::Name;
use la_arena::ArenaMap;
use rustc_apfloat::Float;
use rustc_hash::FxHashMap;
-use span::FileId;
+use span::{Edition, FileId};
use syntax::TextRange;
use triomphe::Arc;
@@ -157,13 +157,18 @@ impl MirLowerError {
f: &mut String,
db: &dyn HirDatabase,
span_formatter: impl Fn(FileId, TextRange) -> String,
+ edition: Edition,
) -> std::result::Result<(), std::fmt::Error> {
match self {
MirLowerError::ConstEvalError(name, e) => {
writeln!(f, "In evaluating constant {name}")?;
match &**e {
- ConstEvalError::MirLowerError(e) => e.pretty_print(f, db, span_formatter)?,
- ConstEvalError::MirEvalError(e) => e.pretty_print(f, db, span_formatter)?,
+ ConstEvalError::MirLowerError(e) => {
+ e.pretty_print(f, db, span_formatter, edition)?
+ }
+ ConstEvalError::MirEvalError(e) => {
+ e.pretty_print(f, db, span_formatter, edition)?
+ }
}
}
MirLowerError::MissingFunctionDefinition(owner, it) => {
@@ -171,15 +176,15 @@ impl MirLowerError {
writeln!(
f,
"Missing function definition for {}",
- body.pretty_print_expr(db.upcast(), *owner, *it)
+ body.pretty_print_expr(db.upcast(), *owner, *it, edition)
)?;
}
MirLowerError::TypeMismatch(e) => match e {
Some(e) => writeln!(
f,
"Type mismatch: Expected {}, found {}",
- e.expected.display(db),
- e.actual.display(db),
+ e.expected.display(db, edition),
+ e.actual.display(db, edition),
)?,
None => writeln!(f, "Type mismatch: types mismatch with {{unknown}}",)?,
},
@@ -189,11 +194,11 @@ impl MirLowerError {
writeln!(
f,
"Generic arg not provided for {}",
- param.name().unwrap_or(&Name::missing()).display(db.upcast())
+ param.name().unwrap_or(&Name::missing()).display(db.upcast(), edition)
)?;
writeln!(f, "Provided args: [")?;
for g in subst.iter(Interner) {
- write!(f, " {},", g.display(db))?;
+ write!(f, " {},", g.display(db, edition))?;
}
writeln!(f, "]")?;
}
@@ -242,8 +247,8 @@ impl From<LayoutError> for MirLowerError {
}
impl MirLowerError {
- fn unresolved_path(db: &dyn HirDatabase, p: &Path) -> Self {
- Self::UnresolvedName(p.display(db).to_string())
+ fn unresolved_path(db: &dyn HirDatabase, p: &Path, edition: Edition) -> Self {
+ Self::UnresolvedName(p.display(db, edition).to_string())
}
}
@@ -337,7 +342,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
self.push_assignment(current, place, Operand::Copy(p).into(), expr_id.into());
Ok(Some(current))
}
- Adjust::Borrow(AutoBorrow::Ref(m) | AutoBorrow::RawPtr(m)) => {
+ 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 {
@@ -436,7 +441,8 @@ impl<'ctx> MirLowerCtx<'ctx> {
VariantId::UnionId(_) => implementation_error!("Union variant as path"),
}
} else {
- let unresolved_name = || MirLowerError::unresolved_path(self.db, p);
+ let unresolved_name =
+ || MirLowerError::unresolved_path(self.db, p, self.edition());
let resolver = resolver_for_expr(self.db.upcast(), self.owner, expr_id);
resolver
.resolve_path_in_value_ns_fully(self.db.upcast(), p)
@@ -662,7 +668,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
let (func_id, generic_args) =
self.infer.method_resolution(expr_id).ok_or_else(|| {
MirLowerError::UnresolvedMethod(
- method_name.display(self.db.upcast()).to_string(),
+ method_name.display(self.db.upcast(), self.edition()).to_string(),
)
})?;
let func = Operand::from_fn(self.db, func_id, generic_args);
@@ -803,7 +809,9 @@ impl<'ctx> MirLowerCtx<'ctx> {
};
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()),
+ Some(p) => MirLowerError::UnresolvedName(
+ p.display(self.db, self.edition()).to_string(),
+ ),
None => MirLowerError::RecordLiteralWithoutPath,
})?;
let subst = match self.expr_ty_without_adjust(expr_id).kind(Interner) {
@@ -1172,8 +1180,15 @@ impl<'ctx> MirLowerCtx<'ctx> {
let placeholder_subst = self.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, tmp, Rvalue::Ref(*bk, p), capture.span);
+ // FIXME: Handle more than one span.
+ let capture_spans = capture.spans();
+ let tmp: Place = self.temp(tmp_ty, current, capture_spans[0])?.into();
+ self.push_assignment(
+ current,
+ tmp,
+ Rvalue::Ref(*bk, p),
+ capture_spans[0],
+ );
operands.push(Operand::Move(tmp));
}
CaptureKind::ByValue => operands.push(Operand::Move(p)),
@@ -1378,7 +1393,9 @@ impl<'ctx> MirLowerCtx<'ctx> {
"only `char` and numeric types are allowed in range patterns"
),
};
- let unresolved_name = || MirLowerError::unresolved_path(self.db, c.as_ref());
+ let edition = self.edition();
+ let unresolved_name =
+ || MirLowerError::unresolved_path(self.db, c.as_ref(), edition);
let resolver = self.owner.resolver(self.db.upcast());
let pr = resolver
.resolve_path_in_value_ns(self.db.upcast(), c.as_ref())
@@ -1904,19 +1921,25 @@ impl<'ctx> MirLowerCtx<'ctx> {
match r {
Ok(r) => Ok(r),
Err(e) => {
+ let edition = self.edition();
let db = self.db.upcast();
let loc = variant.lookup(db);
let enum_loc = loc.parent.lookup(db);
let name = format!(
"{}::{}",
- enum_loc.id.item_tree(db)[enum_loc.id.value].name.display(db.upcast()),
- loc.id.item_tree(db)[loc.id.value].name.display(db.upcast()),
+ enum_loc.id.item_tree(db)[enum_loc.id.value].name.display(db.upcast(), edition),
+ loc.id.item_tree(db)[loc.id.value].name.display(db.upcast(), edition),
);
Err(MirLowerError::ConstEvalError(name.into(), Box::new(e)))
}
}
}
+ fn edition(&self) -> Edition {
+ let krate = self.owner.krate(self.db.upcast());
+ self.db.crate_graph()[krate].edition
+ }
+
fn drop_until_scope(
&mut self,
scope_index: usize,
@@ -2121,18 +2144,24 @@ pub fn mir_body_for_closure_query(
}
pub fn mir_body_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Result<Arc<MirBody>> {
+ let krate = def.krate(db.upcast());
+ let edition = db.crate_graph()[krate].edition;
let detail = match def {
- DefWithBodyId::FunctionId(it) => db.function_data(it).name.display(db.upcast()).to_string(),
- DefWithBodyId::StaticId(it) => db.static_data(it).name.display(db.upcast()).to_string(),
+ DefWithBodyId::FunctionId(it) => {
+ db.function_data(it).name.display(db.upcast(), edition).to_string()
+ }
+ DefWithBodyId::StaticId(it) => {
+ db.static_data(it).name.display(db.upcast(), edition).to_string()
+ }
DefWithBodyId::ConstId(it) => db
.const_data(it)
.name
.clone()
.unwrap_or_else(Name::missing)
- .display(db.upcast())
+ .display(db.upcast(), edition)
.to_string(),
DefWithBodyId::VariantId(it) => {
- db.enum_variant_data(it).name.display(db.upcast()).to_string()
+ db.enum_variant_data(it).name.display(db.upcast(), edition).to_string()
}
DefWithBodyId::InTypeConstId(it) => format!("in type const {it:?}"),
};
diff --git a/crates/hir-ty/src/mir/lower/pattern_matching.rs b/crates/hir-ty/src/mir/lower/pattern_matching.rs
index 34e0f30afb..b1c0d1f2b3 100644
--- a/crates/hir-ty/src/mir/lower/pattern_matching.rs
+++ b/crates/hir-ty/src/mir/lower/pattern_matching.rs
@@ -347,7 +347,8 @@ impl MirLowerCtx<'_> {
// A const don't bind anything. Only needs check.
return Ok((current, current_else));
}
- let unresolved_name = || MirLowerError::unresolved_path(self.db, p);
+ let unresolved_name =
+ || MirLowerError::unresolved_path(self.db, p, self.edition());
let resolver = self.owner.resolver(self.db.upcast());
let pr = resolver
.resolve_path_in_value_ns(self.db.upcast(), p)
diff --git a/crates/hir-ty/src/mir/pretty.rs b/crates/hir-ty/src/mir/pretty.rs
index 0c641d7c6c..df56071aa9 100644
--- a/crates/hir-ty/src/mir/pretty.rs
+++ b/crates/hir-ty/src/mir/pretty.rs
@@ -9,6 +9,7 @@ use either::Either;
use hir_def::{body::Body, hir::BindingId};
use hir_expand::{name::Name, Lookup};
use la_arena::ArenaMap;
+use span::Edition;
use crate::{
db::HirDatabase,
@@ -44,18 +45,21 @@ impl MirBody {
ctx.for_body(|this| match ctx.body.owner {
hir_def::DefWithBodyId::FunctionId(id) => {
let data = db.function_data(id);
- w!(this, "fn {}() ", data.name.display(db.upcast()));
+ w!(this, "fn {}() ", data.name.display(db.upcast(), Edition::LATEST));
}
hir_def::DefWithBodyId::StaticId(id) => {
let data = db.static_data(id);
- w!(this, "static {}: _ = ", data.name.display(db.upcast()));
+ w!(this, "static {}: _ = ", data.name.display(db.upcast(), Edition::LATEST));
}
hir_def::DefWithBodyId::ConstId(id) => {
let data = db.const_data(id);
w!(
this,
"const {}: _ = ",
- data.name.as_ref().unwrap_or(&Name::missing()).display(db.upcast())
+ data.name
+ .as_ref()
+ .unwrap_or(&Name::missing())
+ .display(db.upcast(), Edition::LATEST)
);
}
hir_def::DefWithBodyId::VariantId(id) => {
@@ -64,8 +68,12 @@ impl MirBody {
w!(
this,
"enum {}::{} = ",
- enum_loc.id.item_tree(db.upcast())[enum_loc.id.value].name.display(db.upcast()),
- loc.id.item_tree(db.upcast())[loc.id.value].name.display(db.upcast()),
+ enum_loc.id.item_tree(db.upcast())[enum_loc.id.value]
+ .name
+ .display(db.upcast(), Edition::LATEST),
+ loc.id.item_tree(db.upcast())[loc.id.value]
+ .name
+ .display(db.upcast(), Edition::LATEST),
)
}
hir_def::DefWithBodyId::InTypeConstId(id) => {
@@ -122,7 +130,7 @@ impl HirDisplay for LocalName {
match self {
LocalName::Unknown(l) => write!(f, "_{}", u32::from(l.into_raw())),
LocalName::Binding(n, l) => {
- write!(f, "{}_{}", n.display(f.db.upcast()), u32::from(l.into_raw()))
+ write!(f, "{}_{}", n.display(f.db.upcast(), f.edition()), u32::from(l.into_raw()))
}
}
}
@@ -200,7 +208,7 @@ impl<'a> MirPrettyCtx<'a> {
wln!(
self,
"let {}: {};",
- self.local_name(id).display(self.db),
+ self.local_name(id).display_test(self.db),
self.hir_display(&local.ty)
);
}
@@ -231,10 +239,18 @@ impl<'a> MirPrettyCtx<'a> {
wln!(this, ";");
}
StatementKind::StorageDead(p) => {
- wln!(this, "StorageDead({})", this.local_name(*p).display(self.db));
+ wln!(
+ this,
+ "StorageDead({})",
+ this.local_name(*p).display_test(self.db)
+ );
}
StatementKind::StorageLive(p) => {
- wln!(this, "StorageLive({})", this.local_name(*p).display(self.db));
+ wln!(
+ this,
+ "StorageLive({})",
+ this.local_name(*p).display_test(self.db)
+ );
}
StatementKind::Deinit(p) => {
w!(this, "Deinit(");
@@ -297,7 +313,7 @@ impl<'a> MirPrettyCtx<'a> {
fn f(this: &mut MirPrettyCtx<'_>, local: LocalId, projections: &[PlaceElem]) {
let Some((last, head)) = projections.split_last() else {
// no projection
- w!(this, "{}", this.local_name(local).display(this.db));
+ w!(this, "{}", this.local_name(local).display_test(this.db));
return;
};
match last {
@@ -317,13 +333,13 @@ impl<'a> MirPrettyCtx<'a> {
w!(
this,
" as {}).{}",
- variant_name.display(this.db.upcast()),
- name.display(this.db.upcast())
+ variant_name.display(this.db.upcast(), Edition::LATEST),
+ name.display(this.db.upcast(), Edition::LATEST)
);
}
hir_def::VariantId::StructId(_) | hir_def::VariantId::UnionId(_) => {
f(this, local, head);
- w!(this, ".{}", name.display(this.db.upcast()));
+ w!(this, ".{}", name.display(this.db.upcast(), Edition::LATEST));
}
}
}
@@ -337,7 +353,7 @@ impl<'a> MirPrettyCtx<'a> {
}
ProjectionElem::Index(l) => {
f(this, local, head);
- w!(this, "[{}]", this.local_name(*l).display(this.db));
+ w!(this, "[{}]", this.local_name(*l).display_test(this.db));
}
it => {
f(this, local, head);
@@ -387,7 +403,7 @@ impl<'a> MirPrettyCtx<'a> {
Rvalue::Repeat(op, len) => {
w!(self, "[");
self.operand(op);
- w!(self, "; {}]", len.display(self.db));
+ w!(self, "; {}]", len.display_test(self.db));
}
Rvalue::Aggregate(AggregateKind::Adt(_, _), it) => {
w!(self, "Adt(");
@@ -458,6 +474,6 @@ impl<'a> MirPrettyCtx<'a> {
}
fn hir_display<T: HirDisplay>(&self, ty: &'a T) -> impl Display + 'a {
- ty.display(self.db).with_closure_style(ClosureStyle::ClosureWithSubst)
+ ty.display_test(self.db).with_closure_style(ClosureStyle::ClosureWithSubst)
}
}
diff --git a/crates/hir-ty/src/tests.rs b/crates/hir-ty/src/tests.rs
index 0fcd789f76..bcf9d5ceff 100644
--- a/crates/hir-ty/src/tests.rs
+++ b/crates/hir-ty/src/tests.rs
@@ -1,3 +1,4 @@
+mod closure_captures;
mod coercion;
mod diagnostics;
mod display_source_code;
@@ -12,6 +13,7 @@ mod traits;
mod type_alias_impl_traits;
use std::env;
+use std::sync::LazyLock;
use base_db::SourceDatabaseFileInputExt as _;
use expect_test::Expect;
@@ -25,7 +27,7 @@ use hir_def::{
AssocItemId, DefWithBodyId, HasModule, LocalModuleId, Lookup, ModuleDefId,
};
use hir_expand::{db::ExpandDatabase, FileRange, InFile};
-use once_cell::race::OnceBool;
+use itertools::Itertools;
use rustc_hash::FxHashMap;
use stdx::format_to;
use syntax::{
@@ -50,8 +52,8 @@ use crate::{
// `env UPDATE_EXPECT=1 cargo test -p hir_ty` to update the snapshots.
fn setup_tracing() -> Option<tracing::subscriber::DefaultGuard> {
- static ENABLE: OnceBool = OnceBool::new();
- if !ENABLE.get_or_init(|| env::var("CHALK_DEBUG").is_ok()) {
+ static ENABLE: LazyLock<bool> = LazyLock::new(|| env::var("CHALK_DEBUG").is_ok());
+ if !*ENABLE {
return None;
}
@@ -94,7 +96,7 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour
let mut had_annotations = false;
let mut mismatches = FxHashMap::default();
let mut types = FxHashMap::default();
- let mut adjustments = FxHashMap::<_, Vec<_>>::default();
+ let mut adjustments = FxHashMap::default();
for (file_id, annotations) in db.extract_annotations() {
for (range, expected) in annotations {
let file_range = FileRange { file_id, range };
@@ -107,13 +109,7 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour
} else if expected.starts_with("adjustments:") {
adjustments.insert(
file_range,
- expected
- .trim_start_matches("adjustments:")
- .trim()
- .split(',')
- .map(|it| it.trim().to_owned())
- .filter(|it| !it.is_empty())
- .collect(),
+ expected.trim_start_matches("adjustments:").trim().to_owned(),
);
} else {
panic!("unexpected annotation: {expected}");
@@ -200,7 +196,7 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour
adjustments
.iter()
.map(|Adjustment { kind, .. }| format!("{kind:?}"))
- .collect::<Vec<_>>()
+ .join(", ")
);
}
}
diff --git a/crates/hir-ty/src/tests/closure_captures.rs b/crates/hir-ty/src/tests/closure_captures.rs
new file mode 100644
index 0000000000..22cef3505b
--- /dev/null
+++ b/crates/hir-ty/src/tests/closure_captures.rs
@@ -0,0 +1,433 @@
+use base_db::salsa::InternKey;
+use expect_test::{expect, Expect};
+use hir_def::db::DefDatabase;
+use hir_expand::files::InFileWrapper;
+use itertools::Itertools;
+use span::{HirFileId, TextRange};
+use syntax::{AstNode, AstPtr};
+use test_fixture::WithFixture;
+
+use crate::db::{HirDatabase, InternedClosureId};
+use crate::display::HirDisplay;
+use crate::mir::MirSpan;
+use crate::test_db::TestDB;
+
+use super::visit_module;
+
+fn check_closure_captures(ra_fixture: &str, expect: Expect) {
+ let (db, file_id) = TestDB::with_single_file(ra_fixture);
+ let module = db.module_for_file(file_id);
+ let def_map = module.def_map(&db);
+
+ let mut defs = Vec::new();
+ visit_module(&db, &def_map, module.local_id, &mut |it| defs.push(it));
+
+ let mut captures_info = Vec::new();
+ for def in defs {
+ let infer = db.infer(def);
+ let db = &db;
+ captures_info.extend(infer.closure_info.iter().flat_map(|(closure_id, (captures, _))| {
+ let closure = db.lookup_intern_closure(InternedClosureId::from_intern_id(closure_id.0));
+ let (_, source_map) = db.body_with_source_map(closure.0);
+ let closure_text_range = source_map
+ .expr_syntax(closure.1)
+ .expect("failed to map closure to SyntaxNode")
+ .value
+ .text_range();
+ captures.iter().map(move |capture| {
+ fn text_range<N: AstNode>(
+ db: &TestDB,
+ syntax: InFileWrapper<HirFileId, AstPtr<N>>,
+ ) -> TextRange {
+ let root = syntax.file_syntax(db);
+ syntax.value.to_node(&root).syntax().text_range()
+ }
+
+ // FIXME: Deduplicate this with hir::Local::sources().
+ let (body, source_map) = db.body_with_source_map(closure.0);
+ let local_text_range = match body.self_param.zip(source_map.self_param_syntax()) {
+ Some((param, source)) if param == capture.local() => {
+ format!("{:?}", text_range(db, source))
+ }
+ _ => source_map
+ .patterns_for_binding(capture.local())
+ .iter()
+ .map(|&definition| {
+ text_range(db, source_map.pat_syntax(definition).unwrap())
+ })
+ .map(|it| format!("{it:?}"))
+ .join(", "),
+ };
+ let place = capture.display_place(closure.0, db);
+ let capture_ty = capture.ty.skip_binders().display_test(db).to_string();
+ let spans = capture
+ .spans()
+ .iter()
+ .flat_map(|span| match *span {
+ MirSpan::ExprId(expr) => {
+ vec![text_range(db, source_map.expr_syntax(expr).unwrap())]
+ }
+ MirSpan::PatId(pat) => {
+ vec![text_range(db, source_map.pat_syntax(pat).unwrap())]
+ }
+ MirSpan::BindingId(binding) => source_map
+ .patterns_for_binding(binding)
+ .iter()
+ .map(|pat| text_range(db, source_map.pat_syntax(*pat).unwrap()))
+ .collect(),
+ MirSpan::SelfParam => {
+ vec![text_range(db, source_map.self_param_syntax().unwrap())]
+ }
+ MirSpan::Unknown => Vec::new(),
+ })
+ .sorted_by_key(|it| it.start())
+ .map(|it| format!("{it:?}"))
+ .join(",");
+
+ (closure_text_range, local_text_range, spans, place, capture_ty, capture.kind())
+ })
+ }));
+ }
+ captures_info.sort_unstable_by_key(|(closure_text_range, local_text_range, ..)| {
+ (closure_text_range.start(), local_text_range.clone())
+ });
+
+ let rendered = captures_info
+ .iter()
+ .map(|(closure_text_range, local_text_range, spans, place, capture_ty, capture_kind)| {
+ format!(
+ "{closure_text_range:?};{local_text_range};{spans} {capture_kind:?} {place} {capture_ty}"
+ )
+ })
+ .join("\n");
+
+ expect.assert_eq(&rendered);
+}
+
+#[test]
+fn deref_in_let() {
+ check_closure_captures(
+ r#"
+//- minicore:copy
+fn main() {
+ let a = &mut true;
+ let closure = || { let b = *a; };
+}
+"#,
+ expect!["53..71;20..21;66..68 ByRef(Shared) *a &'? bool"],
+ );
+}
+
+#[test]
+fn deref_then_ref_pattern() {
+ check_closure_captures(
+ r#"
+//- minicore:copy
+fn main() {
+ let a = &mut true;
+ let closure = || { let &mut ref b = a; };
+}
+"#,
+ expect!["53..79;20..21;67..72 ByRef(Shared) *a &'? bool"],
+ );
+ check_closure_captures(
+ r#"
+//- minicore:copy
+fn main() {
+ let a = &mut true;
+ let closure = || { let &mut ref mut b = a; };
+}
+"#,
+ expect!["53..83;20..21;67..76 ByRef(Mut { kind: Default }) *a &'? mut bool"],
+ );
+}
+
+#[test]
+fn unique_borrow() {
+ check_closure_captures(
+ r#"
+//- minicore:copy
+fn main() {
+ let a = &mut true;
+ let closure = || { *a = false; };
+}
+"#,
+ expect!["53..71;20..21;58..60 ByRef(Mut { kind: Default }) *a &'? mut bool"],
+ );
+}
+
+#[test]
+fn deref_ref_mut() {
+ check_closure_captures(
+ r#"
+//- minicore:copy
+fn main() {
+ let a = &mut true;
+ let closure = || { let ref mut b = *a; };
+}
+"#,
+ expect!["53..79;20..21;62..71 ByRef(Mut { kind: Default }) *a &'? mut bool"],
+ );
+}
+
+#[test]
+fn let_else_not_consuming() {
+ check_closure_captures(
+ r#"
+//- minicore:copy
+fn main() {
+ let a = &mut true;
+ let closure = || { let _ = *a else { return; }; };
+}
+"#,
+ expect!["53..88;20..21;66..68 ByRef(Shared) *a &'? bool"],
+ );
+}
+
+#[test]
+fn consume() {
+ check_closure_captures(
+ r#"
+//- minicore:copy
+struct NonCopy;
+fn main() {
+ let a = NonCopy;
+ let closure = || { let b = a; };
+}
+"#,
+ expect!["67..84;36..37;80..81 ByValue a NonCopy"],
+ );
+}
+
+#[test]
+fn ref_to_upvar() {
+ check_closure_captures(
+ r#"
+//- minicore:copy
+struct NonCopy;
+fn main() {
+ let mut a = NonCopy;
+ let closure = || { let b = &a; };
+ let closure = || { let c = &mut a; };
+}
+"#,
+ expect![[r#"
+ 71..89;36..41;84..86 ByRef(Shared) a &'? NonCopy
+ 109..131;36..41;122..128 ByRef(Mut { kind: Default }) a &'? mut NonCopy"#]],
+ );
+}
+
+#[test]
+fn field() {
+ check_closure_captures(
+ r#"
+//- minicore:copy
+struct Foo { a: i32, b: i32 }
+fn main() {
+ let a = Foo { a: 0, b: 0 };
+ let closure = || { let b = a.a; };
+}
+"#,
+ expect!["92..111;50..51;105..108 ByRef(Shared) a.a &'? i32"],
+ );
+}
+
+#[test]
+fn fields_different_mode() {
+ check_closure_captures(
+ r#"
+//- minicore:copy
+struct NonCopy;
+struct Foo { a: i32, b: i32, c: NonCopy, d: bool }
+fn main() {
+ let mut a = Foo { a: 0, b: 0 };
+ let closure = || {
+ let b = &a.a;
+ let c = &mut a.b;
+ let d = a.c;
+ };
+}
+"#,
+ expect![[r#"
+ 133..212;87..92;154..158 ByRef(Shared) a.a &'? i32
+ 133..212;87..92;176..184 ByRef(Mut { kind: Default }) a.b &'? mut i32
+ 133..212;87..92;202..205 ByValue a.c NonCopy"#]],
+ );
+}
+
+#[test]
+fn autoref() {
+ check_closure_captures(
+ r#"
+//- minicore:copy
+struct Foo;
+impl Foo {
+ fn imm(&self) {}
+ fn mut_(&mut self) {}
+}
+fn main() {
+ let mut a = Foo;
+ let closure = || a.imm();
+ let closure = || a.mut_();
+}
+"#,
+ expect![[r#"
+ 123..133;92..97;126..127 ByRef(Shared) a &'? Foo
+ 153..164;92..97;156..157 ByRef(Mut { kind: Default }) a &'? mut Foo"#]],
+ );
+}
+
+#[test]
+fn captures_priority() {
+ check_closure_captures(
+ r#"
+//- minicore:copy
+struct NonCopy;
+fn main() {
+ let mut a = &mut true;
+ // Max ByRef(Mut { kind: Default })
+ let closure = || {
+ *a = false;
+ let b = &mut a;
+ };
+ // Max ByValue
+ let mut a = NonCopy;
+ let closure = || {
+ let b = a;
+ let c = &mut a;
+ let d = &a;
+ };
+}
+"#,
+ expect![[r#"
+ 113..167;36..41;127..128,154..160 ByRef(Mut { kind: Default }) a &'? mut &'? mut bool
+ 231..304;196..201;252..253,276..277,296..297 ByValue a NonCopy"#]],
+ );
+}
+
+#[test]
+fn let_underscore() {
+ check_closure_captures(
+ r#"
+//- minicore:copy
+fn main() {
+ let mut a = true;
+ let closure = || { let _ = a; };
+}
+"#,
+ expect![""],
+ );
+}
+
+#[test]
+fn match_wildcard() {
+ check_closure_captures(
+ r#"
+//- minicore:copy
+struct NonCopy;
+fn main() {
+ let mut a = NonCopy;
+ let closure = || match a {
+ _ => {}
+ };
+ let closure = || match a {
+ ref b => {}
+ };
+ let closure = || match a {
+ ref mut b => {}
+ };
+}
+"#,
+ expect![[r#"
+ 125..163;36..41;134..135 ByRef(Shared) a &'? NonCopy
+ 183..225;36..41;192..193 ByRef(Mut { kind: Default }) a &'? mut NonCopy"#]],
+ );
+}
+
+#[test]
+fn multiple_bindings() {
+ check_closure_captures(
+ r#"
+//- minicore:copy
+fn main() {
+ let mut a = false;
+ let mut closure = || { let (b | b) = a; };
+}
+"#,
+ expect!["57..80;20..25;76..77,76..77 ByRef(Shared) a &'? bool"],
+ );
+}
+
+#[test]
+fn multiple_usages() {
+ check_closure_captures(
+ r#"
+//- minicore:copy
+fn main() {
+ let mut a = false;
+ let mut closure = || {
+ let b = &a;
+ let c = &a;
+ let d = &mut a;
+ a = true;
+ };
+}
+"#,
+ expect!["57..149;20..25;78..80,98..100,118..124,134..135 ByRef(Mut { kind: Default }) a &'? mut bool"],
+ );
+}
+
+#[test]
+fn ref_then_deref() {
+ check_closure_captures(
+ r#"
+//- minicore:copy
+fn main() {
+ let mut a = false;
+ let mut closure = || { let b = *&mut a; };
+}
+"#,
+ expect!["57..80;20..25;71..77 ByRef(Mut { kind: Default }) a &'? mut bool"],
+ );
+}
+
+#[test]
+fn ref_of_ref() {
+ check_closure_captures(
+ r#"
+//- minicore:copy
+fn main() {
+ let mut a = &false;
+ let closure = || { let b = &a; };
+ let closure = || { let b = &mut a; };
+ let a = &mut false;
+ let closure = || { let b = &a; };
+ let closure = || { let b = &mut a; };
+}
+"#,
+ expect![[r#"
+ 54..72;20..25;67..69 ByRef(Shared) a &'? &'? bool
+ 92..114;20..25;105..111 ByRef(Mut { kind: Default }) a &'? mut &'? bool
+ 158..176;124..125;171..173 ByRef(Shared) a &'? &'? mut bool
+ 196..218;124..125;209..215 ByRef(Mut { kind: Default }) a &'? mut &'? mut bool"#]],
+ );
+}
+
+#[test]
+fn multiple_capture_usages() {
+ check_closure_captures(
+ r#"
+//- minicore:copy
+struct A { a: i32, b: bool }
+fn main() {
+ let mut a = A { a: 123, b: false };
+ let closure = |$0| {
+ let b = a.b;
+ a = A { a: 456, b: true };
+ };
+ closure();
+}
+"#,
+ expect!["99..165;49..54;120..121,133..134 ByRef(Mut { kind: Default }) a &'? mut A"],
+ );
+}
diff --git a/crates/hir-ty/src/tests/coercion.rs b/crates/hir-ty/src/tests/coercion.rs
index 526db2af6d..908bbc248c 100644
--- a/crates/hir-ty/src/tests/coercion.rs
+++ b/crates/hir-ty/src/tests/coercion.rs
@@ -49,7 +49,7 @@ fn let_stmt_coerce() {
//- minicore: coerce_unsized
fn test() {
let x: &[isize] = &[1];
- // ^^^^ adjustments: Deref(None), Borrow(Ref(Not)), Pointer(Unsize)
+ // ^^^^ adjustments: Deref(None), Borrow(Ref('?3, Not)), Pointer(Unsize)
let x: *const [isize] = &[1];
// ^^^^ adjustments: Deref(None), Borrow(RawPtr(Not)), Pointer(Unsize)
}
@@ -96,7 +96,7 @@ fn foo<T>(x: &[T]) -> &[T] { x }
fn test() {
let x = if true {
foo(&[1])
- // ^^^^ adjustments: Deref(None), Borrow(Ref(Not)), Pointer(Unsize)
+ // ^^^^ adjustments: Deref(None), Borrow(Ref('?8, Not)), Pointer(Unsize)
} else {
&[1]
};
@@ -148,7 +148,7 @@ fn foo<T>(x: &[T]) -> &[T] { x }
fn test(i: i32) {
let x = match i {
2 => foo(&[2]),
- // ^^^^ adjustments: Deref(None), Borrow(Ref(Not)), Pointer(Unsize)
+ // ^^^^ adjustments: Deref(None), Borrow(Ref('?10, Not)), Pointer(Unsize)
1 => &[1],
_ => &[3],
};
@@ -267,7 +267,7 @@ fn takes_ref_str(x: &str) {}
fn returns_string() -> String { loop {} }
fn test() {
takes_ref_str(&{ returns_string() });
- // ^^^^^^^^^^^^^^^^^^^^^ adjustments: Deref(None), Deref(Some(OverloadedDeref(Some(Not)))), Borrow(Ref(Not))
+ // ^^^^^^^^^^^^^^^^^^^^^ adjustments: Deref(None), Deref(Some(OverloadedDeref(Some(Not)))), Borrow(Ref('{error}, Not))
}
"#,
);
@@ -849,8 +849,8 @@ impl core::cmp::PartialEq for Struct {
}
fn test() {
Struct == Struct;
- // ^^^^^^ adjustments: Borrow(Ref(Not))
- // ^^^^^^ adjustments: Borrow(Ref(Not))
+ // ^^^^^^ adjustments: Borrow(Ref('{error}, Not))
+ // ^^^^^^ adjustments: Borrow(Ref('{error}, Not))
}",
);
}
@@ -866,7 +866,7 @@ impl core::ops::AddAssign for Struct {
}
fn test() {
Struct += Struct;
- // ^^^^^^ adjustments: Borrow(Ref(Mut))
+ // ^^^^^^ adjustments: Borrow(Ref('{error}, Mut))
// ^^^^^^ adjustments:
}",
);
@@ -880,7 +880,7 @@ fn adjust_index() {
fn test() {
let x = [1, 2, 3];
x[2] = 6;
- // ^ adjustments: Borrow(Ref(Mut))
+ // ^ adjustments: Borrow(Ref('?8, Mut))
}
",
);
@@ -905,11 +905,11 @@ impl core::ops::IndexMut for StructMut {
}
fn test() {
Struct[0];
- // ^^^^^^ adjustments: Borrow(Ref(Not))
+ // ^^^^^^ adjustments: Borrow(Ref('?2, Not))
StructMut[0];
- // ^^^^^^^^^ adjustments: Borrow(Ref(Not))
+ // ^^^^^^^^^ adjustments: Borrow(Ref('?5, Not))
&mut StructMut[0];
- // ^^^^^^^^^ adjustments: Borrow(Ref(Mut))
+ // ^^^^^^^^^ adjustments: Borrow(Ref('?8, Mut))
}",
);
}
diff --git a/crates/hir-ty/src/tests/method_resolution.rs b/crates/hir-ty/src/tests/method_resolution.rs
index 14e2e74653..610fc9b6b4 100644
--- a/crates/hir-ty/src/tests/method_resolution.rs
+++ b/crates/hir-ty/src/tests/method_resolution.rs
@@ -1186,11 +1186,11 @@ fn test() {
89..109 '{ ... }': bool
99..103 'true': bool
123..167 '{ ...o(); }': ()
- 133..134 's': &'? S
+ 133..134 's': &'static S
137..151 'unsafe { f() }': &'static S
146..147 'f': fn f() -> &'static S
146..149 'f()': &'static S
- 157..158 's': &'? S
+ 157..158 's': &'static S
157..164 's.foo()': bool
"#]],
);
@@ -1847,9 +1847,9 @@ impl Foo {
}
fn test() {
Foo.foo();
- //^^^ adjustments: Borrow(Ref(Not))
+ //^^^ adjustments: Borrow(Ref('?1, Not))
(&Foo).foo();
- // ^^^^ adjustments: Deref(None), Borrow(Ref(Not))
+ // ^^^^ adjustments: Deref(None), Borrow(Ref('?3, Not))
}
"#,
);
@@ -1863,7 +1863,7 @@ fn receiver_adjustment_unsize_array() {
fn test() {
let a = [1, 2, 3];
a.len();
-} //^ adjustments: Borrow(Ref(Not)), Pointer(Unsize)
+} //^ adjustments: Borrow(Ref('?7, Not)), Pointer(Unsize)
"#,
);
}
@@ -2076,7 +2076,7 @@ impl Foo {
}
fn test() {
Box::new(Foo).foo();
- //^^^^^^^^^^^^^ adjustments: Deref(None), Borrow(Ref(Not))
+ //^^^^^^^^^^^^^ adjustments: Deref(None), Borrow(Ref('?3, Not))
}
"#,
);
@@ -2094,7 +2094,7 @@ impl Foo {
use core::mem::ManuallyDrop;
fn test() {
ManuallyDrop::new(Foo).foo();
- //^^^^^^^^^^^^^^^^^^^^^^ adjustments: Deref(Some(OverloadedDeref(Some(Not)))), Borrow(Ref(Not))
+ //^^^^^^^^^^^^^^^^^^^^^^ adjustments: Deref(Some(OverloadedDeref(Some(Not)))), Borrow(Ref('?4, Not))
}
"#,
);
diff --git a/crates/hir-ty/src/tests/regression.rs b/crates/hir-ty/src/tests/regression.rs
index 2819838612..17fbe4db31 100644
--- a/crates/hir-ty/src/tests/regression.rs
+++ b/crates/hir-ty/src/tests/regression.rs
@@ -2141,3 +2141,90 @@ fn test() {
}"#,
);
}
+
+#[test]
+fn issue_17866() {
+ check_infer(
+ r#"
+trait T {
+ type A;
+}
+
+type Foo = <S as T>::A;
+
+fn main() {
+ Foo {};
+}
+"#,
+ expect![[r#"
+ 60..75 '{ Foo {}; }': ()
+ 66..72 'Foo {}': {unknown}
+ "#]],
+ );
+}
+
+#[test]
+fn issue_17711() {
+ check_infer(
+ r#"
+//- minicore: deref
+use core::ops::Deref;
+
+struct Struct<'a, T>(&'a T);
+
+trait Trait {}
+
+impl<'a, T: Deref<Target = impl Trait>> Struct<'a, T> {
+ fn foo(&self) -> &Self { self }
+
+ fn bar(&self) {
+ let _ = self.foo();
+ }
+
+}
+"#,
+ expect![[r#"
+ 137..141 'self': &'? Struct<'a, T>
+ 152..160 '{ self }': &'? Struct<'a, T>
+ 154..158 'self': &'? Struct<'a, T>
+ 174..178 'self': &'? Struct<'a, T>
+ 180..215 '{ ... }': ()
+ 194..195 '_': &'? Struct<'?, T>
+ 198..202 'self': &'? Struct<'a, T>
+ 198..208 'self.foo()': &'? Struct<'?, T>
+ "#]],
+ );
+}
+
+#[test]
+fn issue_17767() {
+ check_infer(
+ r#"
+extern "C" {
+ type Foo<T>;
+}
+
+fn f() -> Foo {}
+"#,
+ expect![[r#"
+ 47..49 '{}': Foo
+ "#]],
+ );
+}
+
+#[test]
+fn issue_17921() {
+ check_infer(
+ r#"
+//- minicore: future
+trait Foo {}
+type Bar = impl Foo;
+
+async fn f<A, B, C>() -> Bar {}
+"#,
+ expect![[r#"
+ 64..66 '{}': ()
+ 64..66 '{}': impl Future<Output = ()>
+ "#]],
+ );
+}
diff --git a/crates/hir-ty/src/tests/simple.rs b/crates/hir-ty/src/tests/simple.rs
index d83a34298e..1c6fa62e30 100644
--- a/crates/hir-ty/src/tests/simple.rs
+++ b/crates/hir-ty/src/tests/simple.rs
@@ -1201,8 +1201,8 @@ fn infer_array() {
209..215 '[1, 2]': [i32; 2]
210..211 '1': i32
213..214 '2': i32
- 225..226 'i': [&'? str; 2]
- 229..239 '["a", "b"]': [&'? str; 2]
+ 225..226 'i': [&'static str; 2]
+ 229..239 '["a", "b"]': [&'static str; 2]
230..233 '"a"': &'static str
235..238 '"b"': &'static str
250..251 'b': [[&'? str; 1]; 2]
@@ -3686,3 +3686,36 @@ fn main() {
"#,
);
}
+
+#[test]
+fn infer_bad_lang_item() {
+ check_infer(
+ r#"
+#[lang="eq"]
+pub trait Eq {
+ fn eq(&self, ) -> bool;
+
+}
+
+#[lang="shr"]
+pub trait Shr<RHS,Result> {
+ fn shr(&self, rhs: &RHS) -> Result;
+}
+
+fn test() -> bool {
+ 1 >> 1;
+ 1 == 1;
+}
+"#,
+ expect![[r#"
+ 39..43 'self': &'? Self
+ 114..118 'self': &'? Self
+ 120..123 'rhs': &'? RHS
+ 163..190 '{ ...= 1; }': bool
+ 169..170 '1': i32
+ 169..175 '1 >> 1': {unknown}
+ 181..182 '1': i32
+ 181..187 '1 == 1': {unknown}
+ "#]],
+ );
+}
diff --git a/crates/hir-ty/src/tls.rs b/crates/hir-ty/src/tls.rs
index db5fa32057..6cb59491fa 100644
--- a/crates/hir-ty/src/tls.rs
+++ b/crates/hir-ty/src/tls.rs
@@ -2,6 +2,7 @@
use std::fmt::{self, Display};
use itertools::Itertools;
+use span::Edition;
use crate::{
chalk_db, db::HirDatabase, from_assoc_type_id, from_chalk_trait_id, mapping::from_chalk,
@@ -24,7 +25,7 @@ impl DebugContext<'_> {
AdtId::UnionId(it) => self.0.union_data(it).name.clone(),
AdtId::EnumId(it) => self.0.enum_data(it).name.clone(),
};
- name.display(self.0.upcast()).fmt(f)?;
+ name.display(self.0.upcast(), Edition::LATEST).fmt(f)?;
Ok(())
}
@@ -35,7 +36,7 @@ impl DebugContext<'_> {
) -> Result<(), fmt::Error> {
let trait_: hir_def::TraitId = from_chalk_trait_id(id);
let trait_data = self.0.trait_data(trait_);
- trait_data.name.display(self.0.upcast()).fmt(f)?;
+ trait_data.name.display(self.0.upcast(), Edition::LATEST).fmt(f)?;
Ok(())
}
@@ -54,8 +55,8 @@ impl DebugContext<'_> {
write!(
fmt,
"{}::{}",
- trait_data.name.display(self.0.upcast()),
- type_alias_data.name.display(self.0.upcast())
+ trait_data.name.display(self.0.upcast(), Edition::LATEST),
+ type_alias_data.name.display(self.0.upcast(), Edition::LATEST)
)?;
Ok(())
}
@@ -75,7 +76,7 @@ impl DebugContext<'_> {
let trait_ref = projection_ty.trait_ref(self.0);
let trait_params = trait_ref.substitution.as_slice(Interner);
let self_ty = trait_ref.self_type_parameter(Interner);
- write!(fmt, "<{self_ty:?} as {}", trait_name.display(self.0.upcast()))?;
+ write!(fmt, "<{self_ty:?} as {}", trait_name.display(self.0.upcast(), Edition::LATEST))?;
if trait_params.len() > 1 {
write!(
fmt,
@@ -83,7 +84,7 @@ impl DebugContext<'_> {
trait_params[1..].iter().format_with(", ", |x, f| f(&format_args!("{x:?}"))),
)?;
}
- write!(fmt, ">::{}", type_alias_data.name.display(self.0.upcast()))?;
+ write!(fmt, ">::{}", type_alias_data.name.display(self.0.upcast(), Edition::LATEST))?;
let proj_params_count = projection_ty.substitution.len(Interner) - trait_params.len();
let proj_params = &projection_ty.substitution.as_slice(Interner)[..proj_params_count];
@@ -110,9 +111,11 @@ impl DebugContext<'_> {
CallableDefId::EnumVariantId(e) => self.0.enum_variant_data(e).name.clone(),
};
match def {
- CallableDefId::FunctionId(_) => write!(fmt, "{{fn {}}}", name.display(self.0.upcast())),
+ CallableDefId::FunctionId(_) => {
+ write!(fmt, "{{fn {}}}", name.display(self.0.upcast(), Edition::LATEST))
+ }
CallableDefId::StructId(_) | CallableDefId::EnumVariantId(_) => {
- write!(fmt, "{{ctor {}}}", name.display(self.0.upcast()))
+ write!(fmt, "{{ctor {}}}", name.display(self.0.upcast(), Edition::LATEST))
}
}
}
diff --git a/crates/hir-ty/src/traits.rs b/crates/hir-ty/src/traits.rs
index c46382a0ea..51ccd4ef29 100644
--- a/crates/hir-ty/src/traits.rs
+++ b/crates/hir-ty/src/traits.rs
@@ -14,13 +14,14 @@ use hir_def::{
};
use hir_expand::name::Name;
use intern::sym;
-use stdx::panic_context;
+use span::Edition;
+use stdx::{never, panic_context};
use triomphe::Arc;
use crate::{
db::HirDatabase, infer::unify::InferenceTable, utils::UnevaluatedConstEvaluatorFolder, AliasEq,
AliasTy, Canonical, DomainGoal, Goal, Guidance, InEnvironment, Interner, ProjectionTy,
- ProjectionTyExt, Solution, TraitRefExt, Ty, TyKind, WhereClause,
+ ProjectionTyExt, Solution, TraitRefExt, Ty, TyKind, TypeFlags, WhereClause,
};
/// This controls how much 'time' we give the Chalk solver before giving up.
@@ -90,6 +91,16 @@ pub(crate) fn normalize_projection_query(
projection: ProjectionTy,
env: Arc<TraitEnvironment>,
) -> Ty {
+ if projection.substitution.iter(Interner).any(|arg| {
+ arg.ty(Interner)
+ .is_some_and(|ty| ty.data(Interner).flags.intersects(TypeFlags::HAS_TY_INFER))
+ }) {
+ never!(
+ "Invoking `normalize_projection_query` with a projection type containing inference var"
+ );
+ return TyKind::Error.intern(Interner);
+ }
+
let mut table = InferenceTable::new(db, env);
let ty = table.normalize_projection_ty(projection);
table.resolve_completely(ty)
@@ -104,7 +115,7 @@ pub(crate) fn trait_solve_query(
) -> Option<Solution> {
let detail = match &goal.value.goal.data(Interner) {
GoalData::DomainGoal(DomainGoal::Holds(WhereClause::Implemented(it))) => {
- db.trait_data(it.hir_trait_id()).name.display(db.upcast()).to_string()
+ db.trait_data(it.hir_trait_id()).name.display(db.upcast(), Edition::LATEST).to_string()
}
GoalData::DomainGoal(DomainGoal::Holds(WhereClause::AliasEq(_))) => "alias_eq".to_owned(),
_ => "??".to_owned(),
diff --git a/crates/hir/Cargo.toml b/crates/hir/Cargo.toml
index 324fa1c6a8..26666d6feb 100644
--- a/crates/hir/Cargo.toml
+++ b/crates/hir/Cargo.toml
@@ -20,7 +20,6 @@ itertools.workspace = true
smallvec.workspace = true
tracing.workspace = true
triomphe.workspace = true
-once_cell = "1.17.1"
# local deps
base-db.workspace = true
diff --git a/crates/hir/src/attrs.rs b/crates/hir/src/attrs.rs
index 02d92620e0..af60c233e5 100644
--- a/crates/hir/src/attrs.rs
+++ b/crates/hir/src/attrs.rs
@@ -328,11 +328,9 @@ fn doc_modpath_from_str(link: &str) -> Option<ModPath> {
};
let parts = first_segment.into_iter().chain(parts).map(|segment| match segment.parse() {
Ok(idx) => Name::new_tuple_field(idx),
- Err(_) => Name::new(
- segment.split_once('<').map_or(segment, |it| it.0),
- tt::IdentIsRaw::No,
- SyntaxContextId::ROOT,
- ),
+ Err(_) => {
+ Name::new(segment.split_once('<').map_or(segment, |it| it.0), SyntaxContextId::ROOT)
+ }
});
Some(ModPath::from_segments(kind, parts))
};
diff --git a/crates/hir/src/display.rs b/crates/hir/src/display.rs
index 12dd8b5bf4..923dca6466 100644
--- a/crates/hir/src/display.rs
+++ b/crates/hir/src/display.rs
@@ -84,7 +84,7 @@ impl HirDisplay for Function {
if let Some(abi) = &data.abi {
write!(f, "extern \"{}\" ", abi.as_str())?;
}
- write!(f, "fn {}", data.name.display(f.db.upcast()))?;
+ write!(f, "fn {}", data.name.display(f.db.upcast(), f.edition()))?;
write_generic_params(GenericDefId::FunctionId(self.id), f)?;
@@ -107,7 +107,7 @@ impl HirDisplay for Function {
first = false;
}
match local {
- Some(name) => write!(f, "{}: ", name.display(f.db.upcast()))?,
+ Some(name) => write!(f, "{}: ", name.display(f.db.upcast(), f.edition()))?,
None => f.write_str("_: ")?,
}
type_ref.hir_fmt(f)?;
@@ -177,7 +177,7 @@ fn write_impl_header(impl_: &Impl, f: &mut HirFormatter<'_>) -> Result<(), HirDi
if let Some(trait_) = impl_.trait_(db) {
let trait_data = db.trait_data(trait_.id);
- write!(f, " {} for", trait_data.name.display(db.upcast()))?;
+ write!(f, " {} for", trait_data.name.display(db.upcast(), f.edition()))?;
}
f.write_char(' ')?;
@@ -196,7 +196,7 @@ impl HirDisplay for SelfParam {
{
f.write_char('&')?;
if let Some(lifetime) = lifetime {
- write!(f, "{} ", lifetime.name.display(f.db.upcast()))?;
+ write!(f, "{} ", lifetime.name.display(f.db.upcast(), f.edition()))?;
}
if let hir_def::type_ref::Mutability::Mut = mut_ {
f.write_str("mut ")?;
@@ -227,7 +227,7 @@ impl HirDisplay for Struct {
// FIXME: Render repr if its set explicitly?
write_visibility(module_id, self.visibility(f.db), f)?;
f.write_str("struct ")?;
- write!(f, "{}", self.name(f.db).display(f.db.upcast()))?;
+ write!(f, "{}", self.name(f.db).display(f.db.upcast(), f.edition()))?;
let def_id = GenericDefId::AdtId(AdtId::StructId(self.id));
write_generic_params(def_id, f)?;
@@ -266,7 +266,7 @@ impl HirDisplay for Enum {
fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
write_visibility(self.module(f.db).id, self.visibility(f.db), f)?;
f.write_str("enum ")?;
- write!(f, "{}", self.name(f.db).display(f.db.upcast()))?;
+ write!(f, "{}", self.name(f.db).display(f.db.upcast(), f.edition()))?;
let def_id = GenericDefId::AdtId(AdtId::EnumId(self.id));
write_generic_params(def_id, f)?;
@@ -283,7 +283,7 @@ impl HirDisplay for Union {
fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
write_visibility(self.module(f.db).id, self.visibility(f.db), f)?;
f.write_str("union ")?;
- write!(f, "{}", self.name(f.db).display(f.db.upcast()))?;
+ write!(f, "{}", self.name(f.db).display(f.db.upcast(), f.edition()))?;
let def_id = GenericDefId::AdtId(AdtId::UnionId(self.id));
write_generic_params(def_id, f)?;
@@ -343,7 +343,7 @@ fn write_variants(
} else {
f.write_str("{\n")?;
for variant in &variants[..count] {
- write!(f, " {}", variant.name(f.db).display(f.db.upcast()))?;
+ write!(f, " {}", variant.name(f.db).display(f.db.upcast(), f.edition()))?;
match variant.kind(f.db) {
StructKind::Tuple => {
let fields_str =
@@ -372,21 +372,21 @@ fn write_variants(
impl HirDisplay for Field {
fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
write_visibility(self.parent.module(f.db).id, self.visibility(f.db), f)?;
- write!(f, "{}: ", self.name(f.db).display(f.db.upcast()))?;
+ write!(f, "{}: ", self.name(f.db).display(f.db.upcast(), f.edition()))?;
self.ty(f.db).hir_fmt(f)
}
}
impl HirDisplay for TupleField {
fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
- write!(f, "pub {}: ", self.name().display(f.db.upcast()))?;
+ write!(f, "pub {}: ", self.name().display(f.db.upcast(), f.edition()))?;
self.ty(f.db).hir_fmt(f)
}
}
impl HirDisplay for Variant {
fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
- write!(f, "{}", self.name(f.db).display(f.db.upcast()))?;
+ write!(f, "{}", self.name(f.db).display(f.db.upcast(), f.edition()))?;
let data = self.variant_data(f.db);
match &*data {
VariantData::Unit => {}
@@ -424,9 +424,9 @@ 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()))?;
+ write!(f, "{}", self.name(f.db).display(f.db.upcast(), f.edition()))?;
if let Some(alias) = self.alias(f.db) {
- write!(f, " as {alias}",)?;
+ write!(f, " as {}", alias.display(f.edition()))?;
}
Ok(())
}
@@ -478,7 +478,7 @@ impl HirDisplay for TypeParam {
match param_data {
TypeOrConstParamData::TypeParamData(p) => match p.provenance {
TypeParamProvenance::TypeParamList | TypeParamProvenance::TraitSelf => {
- write!(f, "{}", p.name.clone().unwrap().display(f.db.upcast()))?
+ write!(f, "{}", p.name.clone().unwrap().display(f.db.upcast(), f.edition()))?
}
TypeParamProvenance::ArgumentImplTrait => {
return write_bounds_like_dyn_trait_with_prefix(
@@ -491,7 +491,7 @@ impl HirDisplay for TypeParam {
}
},
TypeOrConstParamData::ConstParamData(p) => {
- write!(f, "{}", p.name.display(f.db.upcast()))?;
+ write!(f, "{}", p.name.display(f.db.upcast(), f.edition()))?;
}
}
@@ -525,13 +525,13 @@ impl HirDisplay for TypeParam {
impl HirDisplay for LifetimeParam {
fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
- write!(f, "{}", self.name(f.db).display(f.db.upcast()))
+ write!(f, "{}", self.name(f.db).display(f.db.upcast(), f.edition()))
}
}
impl HirDisplay for ConstParam {
fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
- write!(f, "const {}: ", self.name(f.db).display(f.db.upcast()))?;
+ write!(f, "const {}: ", self.name(f.db).display(f.db.upcast(), f.edition()))?;
self.ty(f.db).hir_fmt(f)
}
}
@@ -563,7 +563,7 @@ fn write_generic_params(
};
for (_, lifetime) in params.iter_lt() {
delim(f)?;
- write!(f, "{}", lifetime.name.display(f.db.upcast()))?;
+ write!(f, "{}", lifetime.name.display(f.db.upcast(), f.edition()))?;
}
for (_, ty) in params.iter_type_or_consts() {
if let Some(name) = &ty.name() {
@@ -573,7 +573,7 @@ fn write_generic_params(
continue;
}
delim(f)?;
- write!(f, "{}", name.display(f.db.upcast()))?;
+ write!(f, "{}", name.display(f.db.upcast(), f.edition()))?;
if let Some(default) = &ty.default {
f.write_str(" = ")?;
default.hir_fmt(f)?;
@@ -581,12 +581,12 @@ fn write_generic_params(
}
TypeOrConstParamData::ConstParamData(c) => {
delim(f)?;
- write!(f, "const {}: ", name.display(f.db.upcast()))?;
+ write!(f, "const {}: ", name.display(f.db.upcast(), f.edition()))?;
c.ty.hir_fmt(f)?;
if let Some(default) = &c.default {
f.write_str(" = ")?;
- write!(f, "{}", default.display(f.db.upcast()))?;
+ write!(f, "{}", default.display(f.db.upcast(), f.edition()))?;
}
}
}
@@ -639,7 +639,7 @@ fn write_where_predicates(
let write_target = |target: &WherePredicateTypeTarget, f: &mut HirFormatter<'_>| match target {
WherePredicateTypeTarget::TypeRef(ty) => ty.hir_fmt(f),
WherePredicateTypeTarget::TypeOrConstParam(id) => match params[*id].name() {
- Some(name) => write!(f, "{}", name.display(f.db.upcast())),
+ Some(name) => write!(f, "{}", name.display(f.db.upcast(), f.edition())),
None => f.write_str("{unnamed}"),
},
};
@@ -668,12 +668,13 @@ fn write_where_predicates(
bound.hir_fmt(f)?;
}
Lifetime { target, bound } => {
- let target = target.name.display(f.db.upcast());
- let bound = bound.name.display(f.db.upcast());
+ let target = target.name.display(f.db.upcast(), f.edition());
+ let bound = bound.name.display(f.db.upcast(), f.edition());
write!(f, "{target}: {bound}")?;
}
ForLifetime { lifetimes, target, bound } => {
- let lifetimes = lifetimes.iter().map(|it| it.display(f.db.upcast())).join(", ");
+ let lifetimes =
+ lifetimes.iter().map(|it| it.display(f.db.upcast(), f.edition())).join(", ");
write!(f, "for<{lifetimes}> ")?;
write_target(target, f)?;
f.write_str(": ")?;
@@ -685,7 +686,9 @@ fn write_where_predicates(
f.write_str(" + ")?;
match nxt {
TypeBound { bound, .. } | ForLifetime { bound, .. } => bound.hir_fmt(f)?,
- Lifetime { bound, .. } => write!(f, "{}", bound.name.display(f.db.upcast()))?,
+ Lifetime { bound, .. } => {
+ write!(f, "{}", bound.name.display(f.db.upcast(), f.edition()))?
+ }
}
}
f.write_str(",")?;
@@ -707,7 +710,7 @@ impl HirDisplay for Const {
let data = db.const_data(self.id);
f.write_str("const ")?;
match &data.name {
- Some(name) => write!(f, "{}: ", name.display(f.db.upcast()))?,
+ Some(name) => write!(f, "{}: ", name.display(f.db.upcast(), f.edition()))?,
None => f.write_str("_: ")?,
}
data.type_ref.hir_fmt(f)?;
@@ -723,7 +726,7 @@ impl HirDisplay for Static {
if data.mutable {
f.write_str("mut ")?;
}
- write!(f, "{}: ", data.name.display(f.db.upcast()))?;
+ write!(f, "{}: ", data.name.display(f.db.upcast(), f.edition()))?;
data.type_ref.hir_fmt(f)?;
Ok(())
}
@@ -777,7 +780,7 @@ fn write_trait_header(trait_: &Trait, f: &mut HirFormatter<'_>) -> Result<(), Hi
if data.is_auto {
f.write_str("auto ")?;
}
- write!(f, "trait {}", data.name.display(f.db.upcast()))?;
+ write!(f, "trait {}", data.name.display(f.db.upcast(), f.edition()))?;
write_generic_params(GenericDefId::TraitId(trait_.id), f)?;
Ok(())
}
@@ -786,7 +789,7 @@ impl HirDisplay for TraitAlias {
fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
write_visibility(self.module(f.db).id, self.visibility(f.db), f)?;
let data = f.db.trait_alias_data(self.id);
- write!(f, "trait {}", data.name.display(f.db.upcast()))?;
+ write!(f, "trait {}", data.name.display(f.db.upcast(), f.edition()))?;
let def_id = GenericDefId::TraitAliasId(self.id);
write_generic_params(def_id, f)?;
f.write_str(" = ")?;
@@ -802,7 +805,7 @@ impl HirDisplay for TypeAlias {
fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
write_visibility(self.module(f.db).id, self.visibility(f.db), f)?;
let data = f.db.type_alias_data(self.id);
- write!(f, "type {}", data.name.display(f.db.upcast()))?;
+ write!(f, "type {}", data.name.display(f.db.upcast(), f.edition()))?;
let def_id = GenericDefId::TypeAliasId(self.id);
write_generic_params(def_id, f)?;
if !data.bounds.is_empty() {
@@ -822,7 +825,7 @@ impl HirDisplay for Module {
fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
// FIXME: Module doesn't have visibility saved in data.
match self.name(f.db) {
- Some(name) => write!(f, "mod {}", name.display(f.db.upcast())),
+ Some(name) => write!(f, "mod {}", name.display(f.db.upcast(), f.edition())),
None if self.is_crate_root() => match self.krate(f.db).display_name(f.db) {
Some(name) => write!(f, "extern crate {name}"),
None => f.write_str("extern crate {unknown}"),
@@ -839,6 +842,6 @@ impl HirDisplay for Macro {
hir_def::MacroId::MacroRulesId(_) => f.write_str("macro_rules!"),
hir_def::MacroId::ProcMacroId(_) => f.write_str("proc_macro"),
}?;
- write!(f, " {}", self.name(f.db).display(f.db.upcast()))
+ write!(f, " {}", self.name(f.db).display(f.db.upcast(), f.edition()))
}
}
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 1a3becdf50..58340c74fe 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -78,7 +78,8 @@ use hir_ty::{
use itertools::Itertools;
use nameres::diagnostics::DefDiagnosticKind;
use rustc_hash::FxHashSet;
-use span::{Edition, EditionedFileId, FileId, MacroCallId};
+use smallvec::SmallVec;
+use span::{Edition, EditionedFileId, FileId, MacroCallId, SyntaxContextId};
use stdx::{impl_from, never};
use syntax::{
ast::{self, HasAttrs as _, HasGenericParams, HasName},
@@ -93,8 +94,7 @@ pub use crate::{
diagnostics::*,
has_source::HasSource,
semantics::{
- DescendPreference, PathResolution, Semantics, SemanticsImpl, SemanticsScope, TypeInfo,
- VisibleTraits,
+ PathResolution, Semantics, SemanticsImpl, SemanticsScope, TypeInfo, VisibleTraits,
},
};
pub use hir_ty::method_resolution::TyFingerprint;
@@ -340,13 +340,13 @@ impl ModuleDef {
}
}
- pub fn canonical_path(&self, db: &dyn HirDatabase) -> Option<String> {
+ pub fn canonical_path(&self, db: &dyn HirDatabase, edition: Edition) -> Option<String> {
let mut segments = vec![self.name(db)?];
for m in self.module(db)?.path_to_root(db) {
segments.extend(m.name(db))
}
segments.reverse();
- Some(segments.iter().map(|it| it.display(db.upcast())).join("::"))
+ Some(segments.iter().map(|it| it.display(db.upcast(), edition)).join("::"))
}
pub fn canonical_module_path(
@@ -556,13 +556,14 @@ impl Module {
style_lints: bool,
) {
let _p = tracing::info_span!("Module::diagnostics", name = ?self.name(db)).entered();
+ let edition = db.crate_graph()[self.id.krate()].edition;
let def_map = self.id.def_map(db.upcast());
for diag in def_map.diagnostics() {
if diag.in_module != self.id.local_id {
// FIXME: This is accidentally quadratic.
continue;
}
- emit_def_diagnostic(db, acc, diag);
+ emit_def_diagnostic(db, acc, diag, edition);
}
if !self.id.is_block_module() {
@@ -582,7 +583,7 @@ impl Module {
}
ModuleDef::Trait(t) => {
for diag in db.trait_data_with_diagnostics(t.id).1.iter() {
- emit_def_diagnostic(db, acc, diag);
+ emit_def_diagnostic(db, acc, diag, edition);
}
for item in t.items(db) {
@@ -599,19 +600,19 @@ impl Module {
match adt {
Adt::Struct(s) => {
for diag in db.struct_data_with_diagnostics(s.id).1.iter() {
- emit_def_diagnostic(db, acc, diag);
+ emit_def_diagnostic(db, acc, diag, edition);
}
}
Adt::Union(u) => {
for diag in db.union_data_with_diagnostics(u.id).1.iter() {
- emit_def_diagnostic(db, acc, diag);
+ emit_def_diagnostic(db, acc, diag, edition);
}
}
Adt::Enum(e) => {
for v in e.variants(db) {
acc.extend(ModuleDef::Variant(v).diagnostics(db, style_lints));
for diag in db.enum_variant_data_with_diagnostics(v.id).1.iter() {
- emit_def_diagnostic(db, acc, diag);
+ emit_def_diagnostic(db, acc, diag, edition);
}
}
}
@@ -645,7 +646,7 @@ impl Module {
let ast_id_map = db.ast_id_map(file_id);
for diag in db.impl_data_with_diagnostics(impl_def.id).1.iter() {
- emit_def_diagnostic(db, acc, diag);
+ emit_def_diagnostic(db, acc, diag, edition);
}
if inherent_impls.invalid_impls().contains(&impl_def.id) {
@@ -869,23 +870,32 @@ fn emit_macro_def_diagnostics(db: &dyn HirDatabase, acc: &mut Vec<AnyDiagnostic>
never!("declarative expander for non decl-macro: {:?}", e);
return;
};
+ let krate = HasModule::krate(&m.id, db.upcast());
+ let edition = db.crate_graph()[krate].edition;
emit_def_diagnostic_(
db,
acc,
&DefDiagnosticKind::MacroDefError { ast, message: e.to_string() },
+ edition,
);
}
}
}
-fn emit_def_diagnostic(db: &dyn HirDatabase, acc: &mut Vec<AnyDiagnostic>, diag: &DefDiagnostic) {
- emit_def_diagnostic_(db, acc, &diag.kind)
+fn emit_def_diagnostic(
+ db: &dyn HirDatabase,
+ acc: &mut Vec<AnyDiagnostic>,
+ diag: &DefDiagnostic,
+ edition: Edition,
+) {
+ emit_def_diagnostic_(db, acc, &diag.kind, edition)
}
fn emit_def_diagnostic_(
db: &dyn HirDatabase,
acc: &mut Vec<AnyDiagnostic>,
diag: &DefDiagnosticKind,
+ edition: Edition,
) {
match diag {
DefDiagnosticKind::UnresolvedModule { ast: declaration, candidates } => {
@@ -910,7 +920,7 @@ fn emit_def_diagnostic_(
MacroError {
node: InFile::new(ast.file_id, item.syntax_node_ptr()),
precise_location: None,
- message: format!("{}: {message}", path.display(db.upcast())),
+ message: format!("{}: {message}", path.display(db.upcast(), edition)),
error,
}
.into(),
@@ -1764,7 +1774,7 @@ impl DefWithBody {
/// A textual representation of the HIR of this def's body for debugging purposes.
pub fn debug_hir(self, db: &dyn HirDatabase) -> String {
let body = db.body(self.id());
- body.pretty_print(db.upcast(), self.id())
+ body.pretty_print(db.upcast(), self.id(), Edition::CURRENT)
}
/// A textual representation of the MIR of this def's body for debugging purposes.
@@ -2259,6 +2269,8 @@ impl Function {
db: &dyn HirDatabase,
span_formatter: impl Fn(FileId, TextRange) -> String,
) -> String {
+ let krate = HasModule::krate(&self.id, db.upcast());
+ let edition = db.crate_graph()[krate].edition;
let body = match db.monomorphized_mir_body(
self.id.into(),
Substitution::empty(Interner),
@@ -2267,7 +2279,7 @@ impl Function {
Ok(body) => body,
Err(e) => {
let mut r = String::new();
- _ = e.pretty_print(&mut r, db, &span_formatter);
+ _ = e.pretty_print(&mut r, db, &span_formatter, edition);
return r;
}
};
@@ -2276,7 +2288,7 @@ impl Function {
Ok(_) => "pass".to_owned(),
Err(e) => {
let mut r = String::new();
- _ = e.pretty_print(&mut r, db, &span_formatter);
+ _ = e.pretty_print(&mut r, db, &span_formatter, edition);
r
}
};
@@ -2510,7 +2522,11 @@ impl Const {
Type::from_value_def(db, self.id)
}
- pub fn render_eval(self, db: &dyn HirDatabase) -> Result<String, ConstEvalError> {
+ pub fn render_eval(
+ self,
+ db: &dyn HirDatabase,
+ edition: Edition,
+ ) -> Result<String, ConstEvalError> {
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) {
@@ -2532,7 +2548,7 @@ impl Const {
if let Ok(s) = mir::render_const_using_debug_impl(db, self.id, &c) {
Ok(s)
} else {
- Ok(format!("{}", c.display(db)))
+ Ok(format!("{}", c.display(db, edition)))
}
}
}
@@ -3728,9 +3744,9 @@ impl ConstParam {
Type::new(db, self.id.parent(), db.const_param_ty(self.id))
}
- pub fn default(self, db: &dyn HirDatabase) -> Option<ast::ConstArg> {
+ pub fn default(self, db: &dyn HirDatabase, edition: Edition) -> Option<ast::ConstArg> {
let arg = generic_arg_from_param(db, self.id.into())?;
- known_const_to_ast(arg.constant(Interner)?, db)
+ known_const_to_ast(arg.constant(Interner)?, db, edition)
}
}
@@ -4038,12 +4054,20 @@ impl Closure {
TyKind::Closure(self.id, self.subst).intern(Interner)
}
- pub fn display_with_id(&self, db: &dyn HirDatabase) -> String {
- self.clone().as_ty().display(db).with_closure_style(ClosureStyle::ClosureWithId).to_string()
+ pub fn display_with_id(&self, db: &dyn HirDatabase, edition: Edition) -> String {
+ self.clone()
+ .as_ty()
+ .display(db, edition)
+ .with_closure_style(ClosureStyle::ClosureWithId)
+ .to_string()
}
- pub fn display_with_impl(&self, db: &dyn HirDatabase) -> String {
- self.clone().as_ty().display(db).with_closure_style(ClosureStyle::ImplFn).to_string()
+ pub fn display_with_impl(&self, db: &dyn HirDatabase, edition: Edition) -> String {
+ self.clone()
+ .as_ty()
+ .display(db, edition)
+ .with_closure_style(ClosureStyle::ImplFn)
+ .to_string()
}
pub fn captured_items(&self, db: &dyn HirDatabase) -> Vec<ClosureCapture> {
@@ -4090,6 +4114,15 @@ impl ClosureCapture {
Local { parent: self.owner, binding_id: self.capture.local() }
}
+ /// Returns whether this place has any field (aka. non-deref) projections.
+ pub fn has_field_projections(&self) -> bool {
+ self.capture.has_field_projections()
+ }
+
+ pub fn usages(&self) -> CaptureUsages {
+ CaptureUsages { parent: self.owner, spans: self.capture.spans() }
+ }
+
pub fn kind(&self) -> CaptureKind {
match self.capture.kind() {
hir_ty::CaptureKind::ByRef(
@@ -4105,11 +4138,21 @@ impl ClosureCapture {
}
}
+ /// Converts the place to a name that can be inserted into source code.
+ pub fn place_to_name(&self, db: &dyn HirDatabase) -> String {
+ self.capture.place_to_name(self.owner, db)
+ }
+
+ pub fn display_place_source_code(&self, db: &dyn HirDatabase) -> String {
+ self.capture.display_place_source_code(self.owner, db)
+ }
+
pub fn display_place(&self, db: &dyn HirDatabase) -> String {
self.capture.display_place(self.owner, db)
}
}
+#[derive(Clone, Copy, PartialEq, Eq)]
pub enum CaptureKind {
SharedRef,
UniqueSharedRef,
@@ -4117,6 +4160,74 @@ pub enum CaptureKind {
Move,
}
+#[derive(Debug, Clone)]
+pub struct CaptureUsages {
+ parent: DefWithBodyId,
+ spans: SmallVec<[mir::MirSpan; 3]>,
+}
+
+impl CaptureUsages {
+ pub fn sources(&self, db: &dyn HirDatabase) -> Vec<CaptureUsageSource> {
+ let (body, source_map) = db.body_with_source_map(self.parent);
+ let mut result = Vec::with_capacity(self.spans.len());
+ for &span in self.spans.iter() {
+ let is_ref = span.is_ref_span(&body);
+ match span {
+ mir::MirSpan::ExprId(expr) => {
+ if let Ok(expr) = source_map.expr_syntax(expr) {
+ result.push(CaptureUsageSource {
+ is_ref,
+ source: expr.map(AstPtr::wrap_left),
+ })
+ }
+ }
+ mir::MirSpan::PatId(pat) => {
+ if let Ok(pat) = source_map.pat_syntax(pat) {
+ result.push(CaptureUsageSource {
+ is_ref,
+ source: pat.map(AstPtr::wrap_right),
+ });
+ }
+ }
+ mir::MirSpan::BindingId(binding) => result.extend(
+ source_map
+ .patterns_for_binding(binding)
+ .iter()
+ .filter_map(|&pat| source_map.pat_syntax(pat).ok())
+ .map(|pat| CaptureUsageSource {
+ is_ref,
+ source: pat.map(AstPtr::wrap_right),
+ }),
+ ),
+ mir::MirSpan::SelfParam | mir::MirSpan::Unknown => {
+ unreachable!("invalid capture usage span")
+ }
+ }
+ }
+ result
+ }
+}
+
+#[derive(Debug)]
+pub struct CaptureUsageSource {
+ is_ref: bool,
+ source: InFile<AstPtr<Either<ast::Expr, ast::Pat>>>,
+}
+
+impl CaptureUsageSource {
+ pub fn source(&self) -> AstPtr<Either<ast::Expr, ast::Pat>> {
+ self.source.value
+ }
+
+ pub fn file_id(&self) -> HirFileId {
+ self.source.file_id
+ }
+
+ pub fn is_ref(&self) -> bool {
+ self.is_ref
+ }
+}
+
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
pub struct Type {
env: Arc<TraitEnvironment>,
@@ -4355,6 +4466,22 @@ impl Type {
method_resolution::implements_trait(&canonical_ty, db, &self.env, trait_)
}
+ /// This does **not** resolve `IntoFuture`, only `Future`.
+ pub fn future_output(self, db: &dyn HirDatabase) -> Option<Type> {
+ let future_output =
+ db.lang_item(self.env.krate, LangItem::FutureOutput)?.as_type_alias()?;
+ self.normalize_trait_assoc_type(db, &[], future_output.into())
+ }
+
+ /// This does **not** resolve `IntoIterator`, only `Iterator`.
+ pub fn iterator_item(self, db: &dyn HirDatabase) -> Option<Type> {
+ let iterator_trait = db.lang_item(self.env.krate, LangItem::Iterator)?.as_trait()?;
+ let iterator_item = db
+ .trait_data(iterator_trait)
+ .associated_type_by_name(&Name::new_symbol(sym::Item.clone(), SyntaxContextId::ROOT))?;
+ self.normalize_trait_assoc_type(db, &[], iterator_item.into())
+ }
+
/// Checks that particular type `ty` implements `std::ops::FnOnce`.
///
/// This function can be used to check if a particular type is callable, since FnOnce is a
@@ -4704,18 +4831,20 @@ impl Type {
pub fn type_and_const_arguments<'a>(
&'a self,
db: &'a dyn HirDatabase,
+ edition: Edition,
) -> impl Iterator<Item = SmolStr> + 'a {
self.ty
.strip_references()
.as_adt()
.into_iter()
.flat_map(|(_, substs)| substs.iter(Interner))
- .filter_map(|arg| {
+ .filter_map(move |arg| {
// arg can be either a `Ty` or `constant`
if let Some(ty) = arg.ty(Interner) {
- Some(format_smolstr!("{}", ty.display(db)))
+ Some(format_smolstr!("{}", ty.display(db, edition)))
} else {
- arg.constant(Interner).map(|const_| format_smolstr!("{}", const_.display(db)))
+ arg.constant(Interner)
+ .map(|const_| format_smolstr!("{}", const_.display(db, edition)))
}
})
}
@@ -4724,13 +4853,17 @@ impl Type {
pub fn generic_parameters<'a>(
&'a self,
db: &'a dyn HirDatabase,
+ edition: Edition,
) -> impl Iterator<Item = SmolStr> + 'a {
// iterate the lifetime
self.as_adt()
- .and_then(|a| a.lifetime(db).map(|lt| lt.name.display_no_db().to_smolstr()))
+ .and_then(|a| {
+ // Lifetimes do not need edition-specific handling as they cannot be escaped.
+ a.lifetime(db).map(|lt| lt.name.display_no_db(Edition::Edition2015).to_smolstr())
+ })
.into_iter()
// add the type and const parameters
- .chain(self.type_and_const_arguments(db))
+ .chain(self.type_and_const_arguments(db, edition))
}
pub fn iterate_method_candidates_with_traits<T>(
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index a377163162..763f53031e 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -4,6 +4,7 @@ mod source_to_def;
use std::{
cell::RefCell,
+ convert::Infallible,
fmt, iter, mem,
ops::{self, ControlFlow, Not},
};
@@ -22,9 +23,11 @@ use hir_expand::{
builtin::{BuiltinFnLikeExpander, EagerExpander},
db::ExpandDatabase,
files::InRealFile,
+ inert_attr_macro::find_builtin_attr_idx,
name::AsName,
FileRange, InMacroFile, MacroCallId, MacroFileId, MacroFileIdExt,
};
+use intern::Symbol;
use itertools::Itertools;
use rustc_hash::{FxHashMap, FxHashSet};
use smallvec::{smallvec, SmallVec};
@@ -48,11 +51,7 @@ use crate::{
Variant, VariantDef,
};
-pub enum DescendPreference {
- SameText,
- SameKind,
- None,
-}
+const CONTINUE_NO_BREAKS: ControlFlow<Infallible, ()> = ControlFlow::Continue(());
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum PathResolution {
@@ -182,6 +181,7 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
/// Find an AstNode by offset inside SyntaxNode, if it is inside *MacroCall*,
/// descend it and find again
+ // FIXME: Rethink this API
pub fn find_node_at_offset_with_descend<N: AstNode>(
&self,
node: &SyntaxNode,
@@ -190,8 +190,9 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
self.imp.descend_node_at_offset(node, offset).flatten().find_map(N::cast)
}
- /// Find an AstNode by offset inside SyntaxNode, if it is inside *MacroCall*,
+ /// Find an AstNode by offset inside SyntaxNode, if it is inside an attribute macro call,
/// descend it and find again
+ // FIXME: Rethink this API
pub fn find_nodes_at_offset_with_descend<'slf, N: AstNode + 'slf>(
&'slf self,
node: &SyntaxNode,
@@ -545,51 +546,53 @@ impl<'db> SemanticsImpl<'db> {
)
}
+ /// Retrieves all the formatting parts of the format_args! template string.
pub fn as_format_args_parts(
&self,
string: &ast::String,
) -> Option<Vec<(TextRange, Option<PathResolution>)>> {
- if let Some(quote) = string.open_quote_text_range() {
- return self
- .descend_into_macros(DescendPreference::SameText, string.syntax().clone())
- .into_iter()
- .find_map(|token| {
- let string = ast::String::cast(token)?;
- let literal =
- string.syntax().parent().filter(|it| it.kind() == SyntaxKind::LITERAL)?;
- let format_args = ast::FormatArgsExpr::cast(literal.parent()?)?;
- let source_analyzer = self.analyze_no_infer(format_args.syntax())?;
- let format_args = self.wrap_node_infile(format_args);
- let res = source_analyzer
- .as_format_args_parts(self.db, format_args.as_ref())?
- .map(|(range, res)| (range + quote.end(), res))
- .collect();
- Some(res)
- });
- }
- None
+ let quote = string.open_quote_text_range()?;
+
+ let token = self.wrap_token_infile(string.syntax().clone()).into_real_file().ok()?;
+ self.descend_into_macros_breakable(token, |token| {
+ (|| {
+ let token = token.value;
+ let string = ast::String::cast(token)?;
+ let literal =
+ string.syntax().parent().filter(|it| it.kind() == SyntaxKind::LITERAL)?;
+ let format_args = ast::FormatArgsExpr::cast(literal.parent()?)?;
+ let source_analyzer = self.analyze_no_infer(format_args.syntax())?;
+ let format_args = self.wrap_node_infile(format_args);
+ let res = source_analyzer
+ .as_format_args_parts(self.db, format_args.as_ref())?
+ .map(|(range, res)| (range + quote.end(), res))
+ .collect();
+ Some(res)
+ })()
+ .map_or(ControlFlow::Continue(()), ControlFlow::Break)
+ })
}
+ /// Retrieves the formatting part of the format_args! template string at the given offset.
pub fn check_for_format_args_template(
&self,
original_token: SyntaxToken,
offset: TextSize,
) -> Option<(TextRange, Option<PathResolution>)> {
- if let Some(original_string) = ast::String::cast(original_token.clone()) {
- if let Some(quote) = original_string.open_quote_text_range() {
- return self
- .descend_into_macros(DescendPreference::SameText, original_token)
- .into_iter()
- .find_map(|token| {
- self.resolve_offset_in_format_args(
- ast::String::cast(token)?,
- offset.checked_sub(quote.end())?,
- )
- })
- .map(|(range, res)| (range + quote.end(), res));
- }
- }
- None
+ let original_string = ast::String::cast(original_token.clone())?;
+ let original_token = self.wrap_token_infile(original_token).into_real_file().ok()?;
+ let quote = original_string.open_quote_text_range()?;
+ self.descend_into_macros_breakable(original_token, |token| {
+ (|| {
+ let token = token.value;
+ self.resolve_offset_in_format_args(
+ ast::String::cast(token)?,
+ offset.checked_sub(quote.end())?,
+ )
+ .map(|(range, res)| (range + quote.end(), res))
+ })()
+ .map_or(ControlFlow::Continue(()), ControlFlow::Break)
+ })
}
fn resolve_offset_in_format_args(
@@ -619,30 +622,37 @@ impl<'db> SemanticsImpl<'db> {
Some(it) => it,
None => return res,
};
+ let file = self.find_file(node.syntax());
+ let Some(file_id) = file.file_id.file_id() else {
+ return res;
+ };
if first == last {
// node is just the token, so descend the token
- self.descend_into_macros_impl(first, &mut |InFile { value, .. }| {
- if let Some(node) = value
- .parent_ancestors()
- .take_while(|it| it.text_range() == value.text_range())
- .find_map(N::cast)
- {
- res.push(node)
- }
- ControlFlow::Continue(())
- });
+ self.descend_into_macros_impl(
+ InRealFile::new(file_id, first),
+ &mut |InFile { value, .. }| {
+ if let Some(node) = value
+ .parent_ancestors()
+ .take_while(|it| it.text_range() == value.text_range())
+ .find_map(N::cast)
+ {
+ res.push(node)
+ }
+ CONTINUE_NO_BREAKS
+ },
+ );
} else {
// Descend first and last token, then zip them to look for the node they belong to
let mut scratch: SmallVec<[_; 1]> = smallvec![];
- self.descend_into_macros_impl(first, &mut |token| {
+ self.descend_into_macros_impl(InRealFile::new(file_id, first), &mut |token| {
scratch.push(token);
- ControlFlow::Continue(())
+ CONTINUE_NO_BREAKS
});
let mut scratch = scratch.into_iter();
self.descend_into_macros_impl(
- last,
+ InRealFile::new(file_id, last),
&mut |InFile { value: last, file_id: last_fid }| {
if let Some(InFile { value: first, file_id: first_fid }) = scratch.next() {
if first_fid == last_fid {
@@ -659,130 +669,151 @@ impl<'db> SemanticsImpl<'db> {
}
}
}
- ControlFlow::Continue(())
+ CONTINUE_NO_BREAKS
},
);
}
res
}
- /// Descend the token into its macro call if it is part of one, returning the tokens in the
- /// expansion that it is associated with.
- pub fn descend_into_macros(
+ fn is_inside_macro_call(token: &SyntaxToken) -> bool {
+ token.parent_ancestors().any(|ancestor| {
+ if ast::MacroCall::can_cast(ancestor.kind()) {
+ return true;
+ }
+ // Check if it is an item (only items can have macro attributes) that has a non-builtin attribute.
+ let Some(item) = ast::Item::cast(ancestor) else { return false };
+ item.attrs().any(|attr| {
+ let Some(meta) = attr.meta() else { return false };
+ let Some(path) = meta.path() else { return false };
+ let Some(attr_name) = path.as_single_name_ref() else { return true };
+ let attr_name = attr_name.text();
+ let attr_name = attr_name.as_str();
+ attr_name == "derive" || find_builtin_attr_idx(&Symbol::intern(attr_name)).is_none()
+ })
+ })
+ }
+
+ pub fn descend_into_macros_exact_if_in_macro(
&self,
- mode: DescendPreference,
token: SyntaxToken,
) -> SmallVec<[SyntaxToken; 1]> {
- enum Dp<'t> {
- SameText(&'t str),
- SameKind(SyntaxKind),
- None,
+ if Self::is_inside_macro_call(&token) {
+ self.descend_into_macros_exact(token)
+ } else {
+ smallvec![token]
}
- let fetch_kind = |token: &SyntaxToken| match token.parent() {
- Some(node) => match node.kind() {
- kind @ (SyntaxKind::NAME | SyntaxKind::NAME_REF) => kind,
- _ => token.kind(),
- },
- None => token.kind(),
- };
- let mode = match mode {
- DescendPreference::SameText => Dp::SameText(token.text()),
- DescendPreference::SameKind => Dp::SameKind(fetch_kind(&token)),
- DescendPreference::None => Dp::None,
- };
+ }
+
+ pub fn descend_into_macros_cb(
+ &self,
+ token: SyntaxToken,
+ mut cb: impl FnMut(InFile<SyntaxToken>),
+ ) {
+ if let Ok(token) = self.wrap_token_infile(token).into_real_file() {
+ self.descend_into_macros_impl(token, &mut |t| {
+ cb(t);
+ CONTINUE_NO_BREAKS
+ });
+ }
+ }
+
+ pub fn descend_into_macros(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> {
let mut res = smallvec![];
- self.descend_into_macros_impl(token.clone(), &mut |InFile { value, .. }| {
- let is_a_match = match mode {
- Dp::SameText(text) => value.text() == text,
- Dp::SameKind(preferred_kind) => {
- let kind = fetch_kind(&value);
- kind == preferred_kind
- // special case for derive macros
- || (preferred_kind == SyntaxKind::IDENT && kind == SyntaxKind::NAME_REF)
- }
- Dp::None => true,
- };
- if is_a_match {
- res.push(value);
- }
- ControlFlow::Continue(())
- });
+ if let Ok(token) = self.wrap_token_infile(token.clone()).into_real_file() {
+ self.descend_into_macros_impl(token, &mut |t| {
+ res.push(t.value);
+ CONTINUE_NO_BREAKS
+ });
+ }
if res.is_empty() {
res.push(token);
}
res
}
- pub fn descend_into_macros_single(
+ pub fn descend_into_macros_breakable<T>(
&self,
- mode: DescendPreference,
- token: SyntaxToken,
- ) -> SyntaxToken {
- enum Dp<'t> {
- SameText(&'t str),
- SameKind(SyntaxKind),
- None,
- }
- let fetch_kind = |token: &SyntaxToken| match token.parent() {
- Some(node) => match node.kind() {
- kind @ (SyntaxKind::NAME | SyntaxKind::NAME_REF) => kind,
- _ => token.kind(),
- },
- None => token.kind(),
- };
- let mode = match mode {
- DescendPreference::SameText => Dp::SameText(token.text()),
- DescendPreference::SameKind => Dp::SameKind(fetch_kind(&token)),
- DescendPreference::None => Dp::None,
- };
- let mut res = token.clone();
- self.descend_into_macros_impl(token.clone(), &mut |InFile { value, .. }| {
- let is_a_match = match mode {
- Dp::SameText(text) => value.text() == text,
- Dp::SameKind(preferred_kind) => {
- let kind = fetch_kind(&value);
- kind == preferred_kind
- // special case for derive macros
- || (preferred_kind == SyntaxKind::IDENT && kind == SyntaxKind::NAME_REF)
- }
- Dp::None => true,
- };
- res = value;
- if is_a_match {
- ControlFlow::Break(())
- } else {
- ControlFlow::Continue(())
+ token: InRealFile<SyntaxToken>,
+ mut cb: impl FnMut(InFile<SyntaxToken>) -> ControlFlow<T>,
+ ) -> Option<T> {
+ self.descend_into_macros_impl(token.clone(), &mut cb)
+ }
+
+ /// Descends the token into expansions, returning the tokens that matches the input
+ /// token's [`SyntaxKind`] and text.
+ pub fn descend_into_macros_exact(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> {
+ let mut r = smallvec![];
+ let text = token.text();
+ let kind = token.kind();
+
+ self.descend_into_macros_cb(token.clone(), |InFile { value, file_id: _ }| {
+ let mapped_kind = value.kind();
+ let any_ident_match = || kind.is_any_identifier() && value.kind().is_any_identifier();
+ let matches = (kind == mapped_kind || any_ident_match()) && text == value.text();
+ if matches {
+ r.push(value);
}
});
- res
+ if r.is_empty() {
+ r.push(token);
+ }
+ r
+ }
+
+ /// Descends the token into expansions, returning the first token that matches the input
+ /// token's [`SyntaxKind`] and text.
+ pub fn descend_into_macros_single_exact(&self, token: SyntaxToken) -> SyntaxToken {
+ let text = token.text();
+ let kind = token.kind();
+ if let Ok(token) = self.wrap_token_infile(token.clone()).into_real_file() {
+ self.descend_into_macros_breakable(token.clone(), |InFile { value, file_id: _ }| {
+ let mapped_kind = value.kind();
+ let any_ident_match =
+ || kind.is_any_identifier() && value.kind().is_any_identifier();
+ let matches = (kind == mapped_kind || any_ident_match()) && text == value.text();
+ if matches {
+ ControlFlow::Break(value)
+ } else {
+ ControlFlow::Continue(())
+ }
+ })
+ } else {
+ None
+ }
+ .unwrap_or(token)
}
- fn descend_into_macros_impl(
+ fn descend_into_macros_impl<T>(
&self,
- token: SyntaxToken,
- f: &mut dyn FnMut(InFile<SyntaxToken>) -> ControlFlow<()>,
- ) {
+ InRealFile { value: token, file_id }: InRealFile<SyntaxToken>,
+ f: &mut dyn FnMut(InFile<SyntaxToken>) -> ControlFlow<T>,
+ ) -> Option<T> {
let _p = tracing::info_span!("descend_into_macros_impl").entered();
- let (sa, span, file_id) =
- match token.parent().and_then(|parent| self.analyze_no_infer(&parent)) {
- Some(sa) => match sa.file_id.file_id() {
- Some(file_id) => (
- sa,
- self.db.real_span_map(file_id).span_for_range(token.text_range()),
- file_id.into(),
- ),
- None => {
- stdx::never!();
- return;
- }
- },
- None => return,
- };
+ let (sa, span, file_id) = token
+ .parent()
+ .and_then(|parent| {
+ self.analyze_impl(InRealFile::new(file_id, &parent).into(), None, false)
+ })
+ .and_then(|sa| {
+ let file_id = sa.file_id.file_id()?;
+ Some((
+ sa,
+ self.db.real_span_map(file_id).span_for_range(token.text_range()),
+ HirFileId::from(file_id),
+ ))
+ })?;
let mut m_cache = self.macro_call_cache.borrow_mut();
let def_map = sa.resolver.def_map();
+ // A stack of tokens to process, along with the file they came from
+ // These are tracked to know which macro calls we still have to look into
+ // the tokens themselves aren't that interesting as the span that is being used to map
+ // things down never changes.
let mut stack: Vec<(_, SmallVec<[_; 2]>)> = vec![(file_id, smallvec![token])];
+
+ // Process the expansion of a call, pushing all tokens with our span in the expansion back onto our stack
let process_expansion_for_token = |stack: &mut Vec<_>, macro_file| {
let InMacroFile { file_id, value: mapped_tokens } = self.with_ctx(|ctx| {
Some(
@@ -809,7 +840,13 @@ impl<'db> SemanticsImpl<'db> {
res
};
- while let Some((file_id, mut tokens)) = stack.pop() {
+ // Filters out all tokens that contain the given range (usually the macro call), any such
+ // token is redundant as the corresponding macro call has already been processed
+ let filter_duplicates = |tokens: &mut SmallVec<_>, range: TextRange| {
+ tokens.retain(|t: &mut SyntaxToken| !range.contains_range(t.text_range()))
+ };
+
+ while let Some((expansion, ref mut tokens)) = stack.pop() {
while let Some(token) = tokens.pop() {
let was_not_remapped = (|| {
// First expand into attribute invocations
@@ -817,7 +854,7 @@ impl<'db> SemanticsImpl<'db> {
token.parent_ancestors().filter_map(ast::Item::cast).find_map(|item| {
// Don't force populate the dyn cache for items that don't have an attribute anyways
item.attrs().next()?;
- Some((ctx.item_to_macro_call(InFile::new(file_id, &item))?, item))
+ Some((ctx.item_to_macro_call(InFile::new(expansion, &item))?, item))
})
});
if let Some((call_id, item)) = containing_attribute_macro_call {
@@ -849,9 +886,7 @@ impl<'db> SemanticsImpl<'db> {
})
.unwrap_or_else(|| text_range.start());
let text_range = TextRange::new(start, text_range.end());
- // remove any other token in this macro input, all their mappings are the
- // same as this one
- tokens.retain(|t| !text_range.contains_range(t.text_range()));
+ filter_duplicates(tokens, text_range);
return process_expansion_for_token(&mut stack, file_id);
}
@@ -862,6 +897,7 @@ impl<'db> SemanticsImpl<'db> {
.map_while(Either::<ast::TokenTree, ast::Meta>::cast)
.last()?;
match tt {
+ // function-like macro call
Either::Left(tt) => {
if tt.left_delimiter_token().map_or(false, |it| it == token) {
return None;
@@ -870,7 +906,7 @@ impl<'db> SemanticsImpl<'db> {
return None;
}
let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?;
- let mcall = InFile::new(file_id, macro_call);
+ let mcall = InFile::new(expansion, macro_call);
let file_id = match m_cache.get(&mcall) {
Some(&it) => it,
None => {
@@ -888,9 +924,7 @@ impl<'db> SemanticsImpl<'db> {
}
};
let text_range = tt.syntax().text_range();
- // remove any other token in this macro input, all their mappings are the
- // same as this one
- tokens.retain(|t| !text_range.contains_range(t.text_range()));
+ filter_duplicates(tokens, text_range);
process_expansion_for_token(&mut stack, file_id).or(file_id
.eager_arg(self.db.upcast())
@@ -899,6 +933,7 @@ impl<'db> SemanticsImpl<'db> {
process_expansion_for_token(&mut stack, arg.as_macro_file())
}))
}
+ // derive or derive helper
Either::Right(meta) => {
// attribute we failed expansion for earlier, this might be a derive invocation
// or derive helper attribute
@@ -910,8 +945,8 @@ impl<'db> SemanticsImpl<'db> {
// so try downmapping the token into the pseudo derive expansion
// see [hir_expand::builtin_attr_macro] for how the pseudo derive expansion works
ctx.attr_to_derive_macro_call(
- InFile::new(file_id, &adt),
- InFile::new(file_id, attr.clone()),
+ InFile::new(expansion, &adt),
+ InFile::new(expansion, attr.clone()),
)
.map(|(_, call_id, _)| call_id)
});
@@ -945,28 +980,29 @@ impl<'db> SemanticsImpl<'db> {
)
}
}?;
- if !self.with_ctx(|ctx| ctx.has_derives(InFile::new(file_id, &adt))) {
+ if !self.with_ctx(|ctx| ctx.has_derives(InFile::new(expansion, &adt))) {
return None;
}
let attr_name =
attr.path().and_then(|it| it.as_single_name_ref())?.as_name();
- // Not an attribute, nor a derive, so it's either a builtin or a derive helper
+ // Not an attribute, nor a derive, so it's either an intert attribute or a derive helper
// Try to resolve to a derive helper and downmap
- let id = self.db.ast_id_map(file_id).ast_id(&adt);
+ let id = self.db.ast_id_map(expansion).ast_id(&adt);
let helpers =
- def_map.derive_helpers_in_scope(InFile::new(file_id, id))?;
+ def_map.derive_helpers_in_scope(InFile::new(expansion, id))?;
if !helpers.is_empty() {
let text_range = attr.syntax().text_range();
- // remove any other token in this macro input, all their mappings are the
- // same as this
- tokens.retain(|t| !text_range.contains_range(t.text_range()));
+ filter_duplicates(tokens, text_range);
}
let mut res = None;
for (.., derive) in
helpers.iter().filter(|(helper, ..)| *helper == attr_name)
{
+ // as there may be multiple derives registering the same helper
+ // name, we gotta make sure to call this for all of them!
+ // FIXME: We need to call `f` for all of them as well though!
res = res.or(process_expansion_for_token(
&mut stack,
derive.as_macro_file(),
@@ -978,11 +1014,14 @@ impl<'db> SemanticsImpl<'db> {
})()
.is_none();
- if was_not_remapped && f(InFile::new(file_id, token)).is_break() {
- break;
+ if was_not_remapped {
+ if let ControlFlow::Break(b) = f(InFile::new(expansion, token)) {
+ return Some(b);
+ }
}
}
}
+ None
}
// Note this return type is deliberate as [`find_nodes_at_offset_with_descend`] wants to stop
@@ -995,7 +1034,7 @@ impl<'db> SemanticsImpl<'db> {
offset: TextSize,
) -> impl Iterator<Item = impl Iterator<Item = SyntaxNode> + '_> + '_ {
node.token_at_offset(offset)
- .map(move |token| self.descend_into_macros(DescendPreference::None, token))
+ .map(move |token| self.descend_into_macros_exact(token))
.map(|descendants| {
descendants.into_iter().map(move |it| self.token_ancestors_with_macros(it))
})
@@ -1179,7 +1218,8 @@ impl<'db> SemanticsImpl<'db> {
hir_ty::Adjust::Borrow(hir_ty::AutoBorrow::RawPtr(m)) => {
Adjust::Borrow(AutoBorrow::RawPtr(mutability(m)))
}
- hir_ty::Adjust::Borrow(hir_ty::AutoBorrow::Ref(m)) => {
+ hir_ty::Adjust::Borrow(hir_ty::AutoBorrow::Ref(_, m)) => {
+ // FIXME: Handle lifetimes here
Adjust::Borrow(AutoBorrow::Ref(mutability(m)))
}
hir_ty::Adjust::Pointer(pc) => Adjust::Pointer(pc),
@@ -1413,11 +1453,13 @@ impl<'db> SemanticsImpl<'db> {
/// Returns none if the file of the node is not part of a crate.
fn analyze(&self, node: &SyntaxNode) -> Option<SourceAnalyzer> {
+ let node = self.find_file(node);
self.analyze_impl(node, None, true)
}
/// Returns none if the file of the node is not part of a crate.
fn analyze_no_infer(&self, node: &SyntaxNode) -> Option<SourceAnalyzer> {
+ let node = self.find_file(node);
self.analyze_impl(node, None, false)
}
@@ -1426,17 +1468,17 @@ impl<'db> SemanticsImpl<'db> {
node: &SyntaxNode,
offset: TextSize,
) -> Option<SourceAnalyzer> {
+ let node = self.find_file(node);
self.analyze_impl(node, Some(offset), false)
}
fn analyze_impl(
&self,
- node: &SyntaxNode,
+ node: InFile<&SyntaxNode>,
offset: Option<TextSize>,
infer_body: bool,
) -> Option<SourceAnalyzer> {
let _p = tracing::info_span!("SemanticsImpl::analyze_impl").entered();
- let node = self.find_file(node);
let container = self.with_ctx(|ctx| ctx.find_container(node))?;
@@ -1481,6 +1523,11 @@ impl<'db> SemanticsImpl<'db> {
InFile::new(file_id, node)
}
+ fn wrap_token_infile(&self, token: SyntaxToken) -> InFile<SyntaxToken> {
+ let InFile { file_id, .. } = self.find_file(&token.parent().unwrap());
+ InFile::new(file_id, token)
+ }
+
/// Wraps the node in a [`InFile`] with the file id it belongs to.
fn find_file<'node>(&self, node: &'node SyntaxNode) -> InFile<&'node SyntaxNode> {
let root_node = find_root(node);
diff --git a/crates/hir/src/symbols.rs b/crates/hir/src/symbols.rs
index b1f5df681f..cabb7e3db3 100644
--- a/crates/hir/src/symbols.rs
+++ b/crates/hir/src/symbols.rs
@@ -9,6 +9,7 @@ use hir_def::{
};
use hir_expand::HirFileId;
use hir_ty::{db::HirDatabase, display::HirDisplay};
+use span::Edition;
use syntax::{ast::HasName, AstNode, AstPtr, SmolStr, SyntaxNode, SyntaxNodePtr, ToSmolStr};
use crate::{Module, ModuleDef, Semantics};
@@ -54,6 +55,7 @@ pub struct SymbolCollector<'a> {
symbols: Vec<FileSymbol>,
work: Vec<SymbolCollectorWork>,
current_container_name: Option<SmolStr>,
+ edition: Edition,
}
/// Given a [`ModuleId`] and a [`HirDatabase`], use the DefMap for the module's crate to collect
@@ -65,10 +67,13 @@ impl<'a> SymbolCollector<'a> {
symbols: Default::default(),
work: Default::default(),
current_container_name: None,
+ edition: Edition::Edition2015,
}
}
pub fn collect(&mut self, module: Module) {
+ self.edition = module.krate().edition(self.db);
+
// The initial work is the root module we're collecting, additional work will
// be populated as we traverse the module's definitions.
self.work.push(SymbolCollectorWork { module_id: module.into(), parent: None });
@@ -209,7 +214,8 @@ impl<'a> SymbolCollector<'a> {
fn collect_from_impl(&mut self, impl_id: ImplId) {
let impl_data = self.db.impl_data(impl_id);
- let impl_name = Some(SmolStr::new(impl_data.self_ty.display(self.db).to_string()));
+ let impl_name =
+ Some(SmolStr::new(impl_data.self_ty.display(self.db, self.edition).to_string()));
self.with_container_name(impl_name, |s| {
for &assoc_item_id in impl_data.items.iter() {
s.push_assoc_item(assoc_item_id)
@@ -239,16 +245,16 @@ impl<'a> SymbolCollector<'a> {
fn def_with_body_id_name(&self, body_id: DefWithBodyId) -> Option<SmolStr> {
match body_id {
DefWithBodyId::FunctionId(id) => {
- Some(self.db.function_data(id).name.display_no_db().to_smolstr())
+ Some(self.db.function_data(id).name.display_no_db(self.edition).to_smolstr())
}
DefWithBodyId::StaticId(id) => {
- Some(self.db.static_data(id).name.display_no_db().to_smolstr())
+ Some(self.db.static_data(id).name.display_no_db(self.edition).to_smolstr())
}
DefWithBodyId::ConstId(id) => {
- Some(self.db.const_data(id).name.as_ref()?.display_no_db().to_smolstr())
+ Some(self.db.const_data(id).name.as_ref()?.display_no_db(self.edition).to_smolstr())
}
DefWithBodyId::VariantId(id) => {
- Some(self.db.enum_variant_data(id).name.display_no_db().to_smolstr())
+ Some(self.db.enum_variant_data(id).name.display_no_db(self.edition).to_smolstr())
}
DefWithBodyId::InTypeConstId(_) => Some("in type const".into()),
}
diff --git a/crates/hir/src/term_search/expr.rs b/crates/hir/src/term_search/expr.rs
index 0c8f6932c7..6ad074e8e5 100644
--- a/crates/hir/src/term_search/expr.rs
+++ b/crates/hir/src/term_search/expr.rs
@@ -7,6 +7,7 @@ use hir_ty::{
display::{DisplaySourceCodeError, HirDisplay},
};
use itertools::Itertools;
+use span::Edition;
use crate::{
Adt, AsAssocItem, AssocItemContainer, Const, ConstParam, Field, Function, Local, ModuleDef,
@@ -29,9 +30,10 @@ fn mod_item_path_str(
sema_scope: &SemanticsScope<'_>,
def: &ModuleDef,
cfg: ImportPathConfig,
+ edition: Edition,
) -> Result<String, DisplaySourceCodeError> {
let path = mod_item_path(sema_scope, def, cfg);
- path.map(|it| it.display(sema_scope.db.upcast()).to_string())
+ path.map(|it| it.display(sema_scope.db.upcast(), edition).to_string())
.ok_or(DisplaySourceCodeError::PathNotFound)
}
@@ -97,37 +99,38 @@ impl Expr {
sema_scope: &SemanticsScope<'_>,
many_formatter: &mut dyn FnMut(&Type) -> String,
cfg: ImportPathConfig,
+ edition: Edition,
) -> Result<String, DisplaySourceCodeError> {
let db = sema_scope.db;
- let mod_item_path_str = |s, def| mod_item_path_str(s, def, cfg);
+ let mod_item_path_str = |s, def| mod_item_path_str(s, def, cfg, edition);
match self {
Expr::Const(it) => match it.as_assoc_item(db).map(|it| it.container(db)) {
Some(container) => {
- let container_name = container_name(container, sema_scope, cfg)?;
+ let container_name = container_name(container, sema_scope, cfg, edition)?;
let const_name = it
.name(db)
- .map(|c| c.display(db.upcast()).to_string())
+ .map(|c| c.display(db.upcast(), edition).to_string())
.unwrap_or(String::new());
Ok(format!("{container_name}::{const_name}"))
}
None => mod_item_path_str(sema_scope, &ModuleDef::Const(*it)),
},
Expr::Static(it) => mod_item_path_str(sema_scope, &ModuleDef::Static(*it)),
- Expr::Local(it) => Ok(it.name(db).display(db.upcast()).to_string()),
- Expr::ConstParam(it) => Ok(it.name(db).display(db.upcast()).to_string()),
+ Expr::Local(it) => Ok(it.name(db).display(db.upcast(), edition).to_string()),
+ Expr::ConstParam(it) => Ok(it.name(db).display(db.upcast(), edition).to_string()),
Expr::FamousType { value, .. } => Ok(value.to_string()),
Expr::Function { func, params, .. } => {
let args = params
.iter()
- .map(|f| f.gen_source_code(sema_scope, many_formatter, cfg))
+ .map(|f| f.gen_source_code(sema_scope, many_formatter, cfg, edition))
.collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
.into_iter()
.join(", ");
match func.as_assoc_item(db).map(|it| it.container(db)) {
Some(container) => {
- let container_name = container_name(container, sema_scope, cfg)?;
- let fn_name = func.name(db).display(db.upcast()).to_string();
+ let container_name = container_name(container, sema_scope, cfg, edition)?;
+ let fn_name = func.name(db).display(db.upcast(), edition).to_string();
Ok(format!("{container_name}::{fn_name}({args})"))
}
None => {
@@ -141,12 +144,13 @@ impl Expr {
return Ok(many_formatter(&target.ty(db)));
}
- let func_name = func.name(db).display(db.upcast()).to_string();
+ let func_name = func.name(db).display(db.upcast(), edition).to_string();
let self_param = func.self_param(db).unwrap();
- let target_str = target.gen_source_code(sema_scope, many_formatter, cfg)?;
+ let target_str =
+ target.gen_source_code(sema_scope, many_formatter, cfg, edition)?;
let args = params
.iter()
- .map(|f| f.gen_source_code(sema_scope, many_formatter, cfg))
+ .map(|f| f.gen_source_code(sema_scope, many_formatter, cfg, edition))
.collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
.into_iter()
.join(", ");
@@ -176,7 +180,7 @@ impl Expr {
StructKind::Tuple => {
let args = params
.iter()
- .map(|f| f.gen_source_code(sema_scope, many_formatter, cfg))
+ .map(|f| f.gen_source_code(sema_scope, many_formatter, cfg, edition))
.collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
.into_iter()
.join(", ");
@@ -190,8 +194,8 @@ impl Expr {
.map(|(a, f)| {
let tmp = format!(
"{}: {}",
- f.name(db).display(db.upcast()),
- a.gen_source_code(sema_scope, many_formatter, cfg)?
+ f.name(db).display(db.upcast(), edition),
+ a.gen_source_code(sema_scope, many_formatter, cfg, edition)?
);
Ok(tmp)
})
@@ -211,7 +215,7 @@ impl Expr {
StructKind::Tuple => {
let args = params
.iter()
- .map(|a| a.gen_source_code(sema_scope, many_formatter, cfg))
+ .map(|a| a.gen_source_code(sema_scope, many_formatter, cfg, edition))
.collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
.into_iter()
.join(", ");
@@ -225,8 +229,8 @@ impl Expr {
.map(|(a, f)| {
let tmp = format!(
"{}: {}",
- f.name(db).display(db.upcast()),
- a.gen_source_code(sema_scope, many_formatter, cfg)?
+ f.name(db).display(db.upcast(), edition),
+ a.gen_source_code(sema_scope, many_formatter, cfg, edition)?
);
Ok(tmp)
})
@@ -244,7 +248,7 @@ impl Expr {
Expr::Tuple { params, .. } => {
let args = params
.iter()
- .map(|a| a.gen_source_code(sema_scope, many_formatter, cfg))
+ .map(|a| a.gen_source_code(sema_scope, many_formatter, cfg, edition))
.collect::<Result<Vec<String>, DisplaySourceCodeError>>()?
.into_iter()
.join(", ");
@@ -256,8 +260,8 @@ impl Expr {
return Ok(many_formatter(&expr.ty(db)));
}
- let strukt = expr.gen_source_code(sema_scope, many_formatter, cfg)?;
- let field = field.name(db).display(db.upcast()).to_string();
+ let strukt = expr.gen_source_code(sema_scope, many_formatter, cfg, edition)?;
+ let field = field.name(db).display(db.upcast(), edition).to_string();
Ok(format!("{strukt}.{field}"))
}
Expr::Reference(expr) => {
@@ -265,7 +269,7 @@ impl Expr {
return Ok(many_formatter(&expr.ty(db)));
}
- let inner = expr.gen_source_code(sema_scope, many_formatter, cfg)?;
+ let inner = expr.gen_source_code(sema_scope, many_formatter, cfg, edition)?;
Ok(format!("&{inner}"))
}
Expr::Many(ty) => Ok(many_formatter(ty)),
@@ -353,17 +357,18 @@ fn container_name(
container: AssocItemContainer,
sema_scope: &SemanticsScope<'_>,
cfg: ImportPathConfig,
+ edition: Edition,
) -> Result<String, DisplaySourceCodeError> {
let container_name = match container {
crate::AssocItemContainer::Trait(trait_) => {
- mod_item_path_str(sema_scope, &ModuleDef::Trait(trait_), cfg)?
+ mod_item_path_str(sema_scope, &ModuleDef::Trait(trait_), cfg, edition)?
}
crate::AssocItemContainer::Impl(imp) => {
let self_ty = imp.self_ty(sema_scope.db);
// Should it be guaranteed that `mod_item_path` always exists?
match self_ty.as_adt().and_then(|adt| mod_item_path(sema_scope, &adt.into(), cfg)) {
- Some(path) => path.display(sema_scope.db.upcast()).to_string(),
- None => self_ty.display(sema_scope.db).to_string(),
+ Some(path) => path.display(sema_scope.db.upcast(), edition).to_string(),
+ None => self_ty.display(sema_scope.db, edition).to_string(),
}
}
};
diff --git a/crates/ide-assists/src/handlers/add_missing_impl_members.rs b/crates/ide-assists/src/handlers/add_missing_impl_members.rs
index 4cd15f1c75..7f8ea44fb1 100644
--- a/crates/ide-assists/src/handlers/add_missing_impl_members.rs
+++ b/crates/ide-assists/src/handlers/add_missing_impl_members.rs
@@ -1,5 +1,8 @@
use hir::HasSource;
-use syntax::ast::{self, make, AstNode};
+use syntax::{
+ ast::{self, make, AstNode},
+ Edition,
+};
use crate::{
assist_context::{AssistContext, Assists},
@@ -150,14 +153,22 @@ fn add_missing_impl_members_inner(
&missing_items,
trait_,
&new_impl_def,
- target_scope,
+ &target_scope,
);
if let Some(cap) = ctx.config.snippet_cap {
let mut placeholder = None;
if let DefaultMethods::No = mode {
if let ast::AssocItem::Fn(func) = &first_new_item {
- if try_gen_trait_body(ctx, func, trait_ref, &impl_def).is_none() {
+ if try_gen_trait_body(
+ ctx,
+ func,
+ trait_ref,
+ &impl_def,
+ target_scope.krate().edition(ctx.sema.db),
+ )
+ .is_none()
+ {
if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast)
{
if m.syntax().text() == "todo!()" {
@@ -182,9 +193,11 @@ fn try_gen_trait_body(
func: &ast::Fn,
trait_ref: hir::TraitRef,
impl_def: &ast::Impl,
+ edition: Edition,
) -> Option<()> {
- let trait_path =
- make::ext::ident_path(&trait_ref.trait_().name(ctx.db()).display(ctx.db()).to_string());
+ let trait_path = make::ext::ident_path(
+ &trait_ref.trait_().name(ctx.db()).display(ctx.db(), edition).to_string(),
+ );
let hir_ty = ctx.sema.resolve_type(&impl_def.self_ty()?)?;
let adt = hir_ty.as_adt()?.source(ctx.db())?;
gen_trait_fn_body(func, &trait_path, &adt.value, Some(trait_ref))
diff --git a/crates/ide-assists/src/handlers/add_missing_match_arms.rs b/crates/ide-assists/src/handlers/add_missing_match_arms.rs
index f4569ca848..b6abb06a2a 100644
--- a/crates/ide-assists/src/handlers/add_missing_match_arms.rs
+++ b/crates/ide-assists/src/handlers/add_missing_match_arms.rs
@@ -445,7 +445,8 @@ fn build_pat(
) -> Option<ast::Pat> {
match var {
ExtendedVariant::Variant(var) => {
- let path = mod_path_to_ast(&module.find_path(db, ModuleDef::from(var), cfg)?);
+ let edition = module.krate().edition(db);
+ let path = mod_path_to_ast(&module.find_path(db, ModuleDef::from(var), cfg)?, edition);
// FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though
Some(match var.source(db)?.value.kind() {
ast::StructKind::Tuple(field_list) => {
diff --git a/crates/ide-assists/src/handlers/auto_import.rs b/crates/ide-assists/src/handlers/auto_import.rs
index db53e49d84..d86948818b 100644
--- a/crates/ide-assists/src/handlers/auto_import.rs
+++ b/crates/ide-assists/src/handlers/auto_import.rs
@@ -8,7 +8,7 @@ use ide_db::{
insert_use::{insert_use, insert_use_as_alias, ImportScope},
},
};
-use syntax::{ast, AstNode, NodeOrToken, SyntaxElement};
+use syntax::{ast, AstNode, Edition, NodeOrToken, SyntaxElement};
use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
@@ -120,13 +120,14 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
// prioritize more relevant imports
proposed_imports
.sort_by_key(|import| Reverse(relevance_score(ctx, import, current_module.as_ref())));
+ let edition = current_module.map(|it| it.krate().edition(ctx.db())).unwrap_or(Edition::CURRENT);
let group_label = group_label(import_assets.import_candidate());
for import in proposed_imports {
let import_path = import.import_path;
let (assist_id, import_name) =
- (AssistId("auto_import", AssistKind::QuickFix), import_path.display(ctx.db()));
+ (AssistId("auto_import", AssistKind::QuickFix), import_path.display(ctx.db(), edition));
acc.add_group(
&group_label,
assist_id,
@@ -138,7 +139,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)),
ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)),
};
- insert_use(&scope, mod_path_to_ast(&import_path), &ctx.config.insert_use);
+ insert_use(&scope, mod_path_to_ast(&import_path, edition), &ctx.config.insert_use);
},
);
@@ -165,7 +166,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
};
insert_use_as_alias(
&scope,
- mod_path_to_ast(&import_path),
+ mod_path_to_ast(&import_path, edition),
&ctx.config.insert_use,
);
},
diff --git a/crates/ide-assists/src/handlers/bool_to_enum.rs b/crates/ide-assists/src/handlers/bool_to_enum.rs
index 3a0754d60f..cd5fe0f862 100644
--- a/crates/ide-assists/src/handlers/bool_to_enum.rs
+++ b/crates/ide-assists/src/handlers/bool_to_enum.rs
@@ -339,6 +339,7 @@ fn augment_references_with_imports(
let cfg = ctx.config.import_path_config();
+ let edition = target_module.krate().edition(ctx.db());
references
.into_iter()
.filter_map(|FileReference { range, name, .. }| {
@@ -361,7 +362,10 @@ fn augment_references_with_imports(
cfg,
)
.map(|mod_path| {
- make::path_concat(mod_path_to_ast(&mod_path), make::path_from_text("Bool"))
+ make::path_concat(
+ mod_path_to_ast(&mod_path, edition),
+ make::path_from_text("Bool"),
+ )
});
import_scope.zip(path)
diff --git a/crates/ide-assists/src/handlers/convert_bool_then.rs b/crates/ide-assists/src/handlers/convert_bool_then.rs
index 77f9c66b35..a5c5b08d5b 100644
--- a/crates/ide-assists/src/handlers/convert_bool_then.rs
+++ b/crates/ide-assists/src/handlers/convert_bool_then.rs
@@ -159,7 +159,7 @@ pub(crate) fn convert_bool_then_to_if(acc: &mut Assists, ctx: &AssistContext<'_>
};
// Verify this is `bool::then` that is being called.
let func = ctx.sema.resolve_method_call(&mcall)?;
- if func.name(ctx.sema.db).display(ctx.db()).to_string() != "then" {
+ if !func.name(ctx.sema.db).eq_ident("then") {
return None;
}
let assoc = func.as_assoc_item(ctx.sema.db)?;
diff --git a/crates/ide-assists/src/handlers/convert_closure_to_fn.rs b/crates/ide-assists/src/handlers/convert_closure_to_fn.rs
new file mode 100644
index 0000000000..79f303b37a
--- /dev/null
+++ b/crates/ide-assists/src/handlers/convert_closure_to_fn.rs
@@ -0,0 +1,1271 @@
+use either::Either;
+use hir::{CaptureKind, ClosureCapture, FileRangeWrapper, HirDisplay};
+use ide_db::{
+ assists::{AssistId, AssistKind},
+ base_db::SourceDatabase,
+ defs::Definition,
+ search::FileReferenceNode,
+ source_change::SourceChangeBuilder,
+ FxHashSet,
+};
+use stdx::format_to;
+use syntax::{
+ algo::{skip_trivia_token, skip_whitespace_token},
+ ast::{
+ self,
+ edit::{AstNodeEdit, IndentLevel},
+ make, HasArgList, HasGenericParams, HasName,
+ },
+ hacks::parse_expr_from_str,
+ ted, AstNode, Direction, SyntaxKind, SyntaxNode, TextSize, ToSmolStr, T,
+};
+
+use crate::assist_context::{AssistContext, Assists};
+
+// Assist: convert_closure_to_fn
+//
+// This converts a closure to a freestanding function, changing all captures to parameters.
+//
+// ```
+// # //- minicore: copy
+// # struct String;
+// # impl String {
+// # fn new() -> Self {}
+// # fn push_str(&mut self, s: &str) {}
+// # }
+// fn main() {
+// let mut s = String::new();
+// let closure = |$0a| s.push_str(a);
+// closure("abc");
+// }
+// ```
+// ->
+// ```
+// # struct String;
+// # impl String {
+// # fn new() -> Self {}
+// # fn push_str(&mut self, s: &str) {}
+// # }
+// fn main() {
+// let mut s = String::new();
+// fn closure(a: &str, s: &mut String) {
+// s.push_str(a)
+// }
+// closure("abc", &mut s);
+// }
+// ```
+pub(crate) fn convert_closure_to_fn(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let closure = ctx.find_node_at_offset::<ast::ClosureExpr>()?;
+ if ctx.find_node_at_offset::<ast::Expr>() != Some(ast::Expr::ClosureExpr(closure.clone())) {
+ // Not inside the parameter list.
+ return None;
+ }
+ let closure_name = closure.syntax().parent().and_then(|parent| {
+ let closure_decl = ast::LetStmt::cast(parent)?;
+ match closure_decl.pat()? {
+ ast::Pat::IdentPat(pat) => Some((closure_decl, pat.clone(), pat.name()?)),
+ _ => None,
+ }
+ });
+ let module = ctx.sema.scope(closure.syntax())?.module();
+ let closure_ty = ctx.sema.type_of_expr(&closure.clone().into())?;
+ let callable = closure_ty.original.as_callable(ctx.db())?;
+ let closure_ty = closure_ty.original.as_closure()?;
+
+ let mut ret_ty = callable.return_type();
+ let mut closure_mentioned_generic_params = ret_ty.generic_params(ctx.db());
+
+ let mut params = callable
+ .params()
+ .into_iter()
+ .map(|param| {
+ let node = ctx.sema.source(param.clone())?.value.right()?;
+ let param_ty = param.ty();
+ closure_mentioned_generic_params.extend(param_ty.generic_params(ctx.db()));
+ match node.ty() {
+ Some(_) => Some(node),
+ None => {
+ let ty = param_ty
+ .display_source_code(ctx.db(), module.into(), true)
+ .unwrap_or_else(|_| "_".to_owned());
+ Some(make::param(node.pat()?, make::ty(&ty)))
+ }
+ }
+ })
+ .collect::<Option<Vec<_>>>()?;
+
+ let mut body = closure.body()?.clone_for_update();
+ let mut is_gen = false;
+ let mut is_async = closure.async_token().is_some();
+ if is_async {
+ ret_ty = ret_ty.future_output(ctx.db())?;
+ }
+ // We defer the wrapping of the body in the block, because `make::block()` will generate a new node,
+ // but we need to locate `AstPtr`s inside the body.
+ let mut wrap_body_in_block = true;
+ if let ast::Expr::BlockExpr(block) = &body {
+ if let Some(async_token) = block.async_token() {
+ if !is_async {
+ is_async = true;
+ ret_ty = ret_ty.future_output(ctx.db())?;
+ let token_idx = async_token.index();
+ let whitespace_tokens_after_count = async_token
+ .siblings_with_tokens(Direction::Next)
+ .skip(1)
+ .take_while(|token| token.kind() == SyntaxKind::WHITESPACE)
+ .count();
+ body.syntax().splice_children(
+ token_idx..token_idx + whitespace_tokens_after_count + 1,
+ Vec::new(),
+ );
+ }
+ }
+ if let Some(gen_token) = block.gen_token() {
+ is_gen = true;
+ ret_ty = ret_ty.iterator_item(ctx.db())?;
+ let token_idx = gen_token.index();
+ let whitespace_tokens_after_count = gen_token
+ .siblings_with_tokens(Direction::Next)
+ .skip(1)
+ .take_while(|token| token.kind() == SyntaxKind::WHITESPACE)
+ .count();
+ body.syntax().splice_children(
+ token_idx..token_idx + whitespace_tokens_after_count + 1,
+ Vec::new(),
+ );
+ }
+
+ if block.try_token().is_none()
+ && block.unsafe_token().is_none()
+ && block.label().is_none()
+ && block.const_token().is_none()
+ && block.async_token().is_none()
+ {
+ wrap_body_in_block = false;
+ }
+ };
+
+ acc.add(
+ AssistId("convert_closure_to_fn", AssistKind::RefactorRewrite),
+ "Convert closure to fn",
+ closure.param_list()?.syntax().text_range(),
+ |builder| {
+ let closure_name_or_default = closure_name
+ .as_ref()
+ .map(|(_, _, it)| it.clone())
+ .unwrap_or_else(|| make::name("fun_name"));
+ let captures = closure_ty.captured_items(ctx.db());
+ let capture_tys = closure_ty.capture_types(ctx.db());
+
+ let mut captures_as_args = Vec::with_capacity(captures.len());
+
+ let body_root = body.syntax().ancestors().last().unwrap();
+ // We need to defer this work because otherwise the text range of elements is being messed up, and
+ // replacements for the next captures won't work.
+ let mut capture_usages_replacement_map = Vec::with_capacity(captures.len());
+
+ for (capture, capture_ty) in std::iter::zip(&captures, &capture_tys) {
+ // FIXME: Allow configuring the replacement of `self`.
+ let capture_name =
+ if capture.local().is_self(ctx.db()) && !capture.has_field_projections() {
+ make::name("this")
+ } else {
+ make::name(&capture.place_to_name(ctx.db()))
+ };
+
+ closure_mentioned_generic_params.extend(capture_ty.generic_params(ctx.db()));
+
+ let capture_ty = capture_ty
+ .display_source_code(ctx.db(), module.into(), true)
+ .unwrap_or_else(|_| "_".to_owned());
+ params.push(make::param(
+ ast::Pat::IdentPat(make::ident_pat(false, false, capture_name.clone_subtree())),
+ make::ty(&capture_ty),
+ ));
+
+ for capture_usage in capture.usages().sources(ctx.db()) {
+ if capture_usage.file_id() != ctx.file_id() {
+ // This is from a macro, don't change it.
+ continue;
+ }
+
+ let capture_usage_source = capture_usage.source();
+ let capture_usage_source = capture_usage_source.to_node(&body_root);
+ let expr = match capture_usage_source {
+ Either::Left(expr) => expr,
+ Either::Right(pat) => {
+ let Some(expr) = expr_of_pat(pat) else { continue };
+ expr
+ }
+ };
+ let replacement = wrap_capture_in_deref_if_needed(
+ &expr,
+ &capture_name,
+ capture.kind(),
+ capture_usage.is_ref(),
+ )
+ .clone_for_update();
+ capture_usages_replacement_map.push((expr, replacement));
+ }
+
+ captures_as_args.push(capture_as_arg(ctx, capture));
+ }
+
+ let (closure_type_params, closure_where_clause) =
+ compute_closure_type_params(ctx, closure_mentioned_generic_params, &closure);
+
+ for (old, new) in capture_usages_replacement_map {
+ if old == body {
+ body = new;
+ } else {
+ ted::replace(old.syntax(), new.syntax());
+ }
+ }
+
+ let body = if wrap_body_in_block {
+ make::block_expr([], Some(body))
+ } else {
+ ast::BlockExpr::cast(body.syntax().clone()).unwrap()
+ };
+
+ let params = make::param_list(None, params);
+ let ret_ty = if ret_ty.is_unit() {
+ None
+ } else {
+ let ret_ty = ret_ty
+ .display_source_code(ctx.db(), module.into(), true)
+ .unwrap_or_else(|_| "_".to_owned());
+ Some(make::ret_type(make::ty(&ret_ty)))
+ };
+ let mut fn_ = make::fn_(
+ None,
+ closure_name_or_default.clone(),
+ closure_type_params,
+ closure_where_clause,
+ params,
+ body,
+ ret_ty,
+ is_async,
+ false,
+ false,
+ is_gen,
+ );
+ fn_ = fn_.dedent(IndentLevel::from_token(&fn_.syntax().last_token().unwrap()));
+
+ builder.edit_file(ctx.file_id());
+ match &closure_name {
+ Some((closure_decl, _, _)) => {
+ fn_ = fn_.indent(closure_decl.indent_level());
+ builder.replace(closure_decl.syntax().text_range(), fn_.to_string());
+ }
+ None => {
+ let Some(top_stmt) =
+ closure.syntax().ancestors().skip(1).find_map(|ancestor| {
+ ast::Stmt::cast(ancestor.clone()).map(Either::Left).or_else(|| {
+ ast::ClosureExpr::cast(ancestor.clone())
+ .map(Either::Left)
+ .or_else(|| ast::BlockExpr::cast(ancestor).map(Either::Right))
+ .map(Either::Right)
+ })
+ })
+ else {
+ return;
+ };
+ builder.replace(
+ closure.syntax().text_range(),
+ closure_name_or_default.to_string(),
+ );
+ match top_stmt {
+ Either::Left(stmt) => {
+ let indent = stmt.indent_level();
+ fn_ = fn_.indent(indent);
+ let range = stmt
+ .syntax()
+ .first_token()
+ .and_then(|token| {
+ skip_whitespace_token(token.prev_token()?, Direction::Prev)
+ })
+ .map(|it| it.text_range().end())
+ .unwrap_or_else(|| stmt.syntax().text_range().start());
+ builder.insert(range, format!("\n{indent}{fn_}"));
+ }
+ Either::Right(Either::Left(closure_inside_closure)) => {
+ let Some(closure_body) = closure_inside_closure.body() else { return };
+ // FIXME: Maybe we can indent this properly, adding newlines and all, but this is hard.
+ builder.insert(
+ closure_body.syntax().text_range().start(),
+ format!("{{ {fn_} "),
+ );
+ builder
+ .insert(closure_body.syntax().text_range().end(), " }".to_owned());
+ }
+ Either::Right(Either::Right(block_expr)) => {
+ let Some(tail_expr) = block_expr.tail_expr() else { return };
+ let Some(insert_in) =
+ tail_expr.syntax().first_token().and_then(|token| {
+ skip_whitespace_token(token.prev_token()?, Direction::Prev)
+ })
+ else {
+ return;
+ };
+ let indent = tail_expr.indent_level();
+ fn_ = fn_.indent(indent);
+ builder
+ .insert(insert_in.text_range().end(), format!("\n{indent}{fn_}"));
+ }
+ }
+ }
+ }
+
+ handle_calls(
+ builder,
+ ctx,
+ closure_name.as_ref().map(|(_, it, _)| it),
+ &captures_as_args,
+ &closure,
+ );
+
+ // FIXME: Place the cursor at `fun_name`, like rename does.
+ },
+ )?;
+ Some(())
+}
+
+fn compute_closure_type_params(
+ ctx: &AssistContext<'_>,
+ mentioned_generic_params: FxHashSet<hir::GenericParam>,
+ closure: &ast::ClosureExpr,
+) -> (Option<ast::GenericParamList>, Option<ast::WhereClause>) {
+ if mentioned_generic_params.is_empty() {
+ return (None, None);
+ }
+
+ let mut mentioned_names = mentioned_generic_params
+ .iter()
+ .filter_map(|param| match param {
+ hir::GenericParam::TypeParam(param) => {
+ Some(param.name(ctx.db()).unescaped().display(ctx.db()).to_smolstr())
+ }
+ hir::GenericParam::ConstParam(param) => {
+ Some(param.name(ctx.db()).unescaped().display(ctx.db()).to_smolstr())
+ }
+ hir::GenericParam::LifetimeParam(_) => None,
+ })
+ .collect::<FxHashSet<_>>();
+
+ let Some((container_params, container_where, container)) =
+ closure.syntax().ancestors().find_map(ast::AnyHasGenericParams::cast).and_then(
+ |container| {
+ Some((container.generic_param_list()?, container.where_clause(), container))
+ },
+ )
+ else {
+ return (None, None);
+ };
+ let containing_impl = if ast::AssocItem::can_cast(container.syntax().kind()) {
+ container
+ .syntax()
+ .ancestors()
+ .find_map(ast::Impl::cast)
+ .and_then(|impl_| Some((impl_.generic_param_list()?, impl_.where_clause())))
+ } else {
+ None
+ };
+
+ let all_params = container_params
+ .type_or_const_params()
+ .chain(containing_impl.iter().flat_map(|(param_list, _)| param_list.type_or_const_params()))
+ .filter_map(|param| Some(param.name()?.text().to_smolstr()))
+ .collect::<FxHashSet<_>>();
+
+ // A fixpoint algorithm to detect (very roughly) if we need to include a generic parameter
+ // by checking if it is mentioned by another parameter we need to include.
+ let mut reached_fixpoint = false;
+ let mut container_where_bounds_indices = Vec::new();
+ let mut impl_where_bounds_indices = Vec::new();
+ while !reached_fixpoint {
+ reached_fixpoint = true;
+
+ let mut insert_name = |syntax: &SyntaxNode| {
+ let has_name = syntax
+ .descendants()
+ .filter_map(ast::NameOrNameRef::cast)
+ .any(|name| mentioned_names.contains(&*name.text()));
+ let mut has_new_params = false;
+ if has_name {
+ syntax
+ .descendants()
+ .filter_map(ast::NameOrNameRef::cast)
+ .filter(|name| all_params.contains(name.text().trim_start_matches("r#")))
+ .for_each(|name| {
+ if mentioned_names.insert(name.text().trim_start_matches("r#").to_smolstr())
+ {
+ // We do this here so we don't do it if there are only matches that are not in `all_params`.
+ has_new_params = true;
+ reached_fixpoint = false;
+ }
+ });
+ }
+ has_new_params
+ };
+
+ for param in container_params.type_or_const_params() {
+ insert_name(param.syntax());
+ }
+ for (pred_index, pred) in container_where.iter().flat_map(|it| it.predicates()).enumerate()
+ {
+ if insert_name(pred.syntax()) {
+ container_where_bounds_indices.push(pred_index);
+ }
+ }
+ if let Some((impl_params, impl_where)) = &containing_impl {
+ for param in impl_params.type_or_const_params() {
+ insert_name(param.syntax());
+ }
+ for (pred_index, pred) in impl_where.iter().flat_map(|it| it.predicates()).enumerate() {
+ if insert_name(pred.syntax()) {
+ impl_where_bounds_indices.push(pred_index);
+ }
+ }
+ }
+ }
+
+ // Order matters here (for beauty). First the outer impl parameters, then the direct container's.
+ let include_params = containing_impl
+ .iter()
+ .flat_map(|(impl_params, _)| {
+ impl_params.type_or_const_params().filter(|param| {
+ param.name().is_some_and(|name| {
+ mentioned_names.contains(name.text().trim_start_matches("r#"))
+ })
+ })
+ })
+ .chain(container_params.type_or_const_params().filter(|param| {
+ param
+ .name()
+ .is_some_and(|name| mentioned_names.contains(name.text().trim_start_matches("r#")))
+ }))
+ .map(ast::TypeOrConstParam::into);
+ let include_where_bounds = containing_impl
+ .as_ref()
+ .and_then(|(_, it)| it.as_ref())
+ .into_iter()
+ .flat_map(|where_| {
+ impl_where_bounds_indices.iter().filter_map(|&index| where_.predicates().nth(index))
+ })
+ .chain(container_where.iter().flat_map(|where_| {
+ container_where_bounds_indices
+ .iter()
+ .filter_map(|&index| where_.predicates().nth(index))
+ }))
+ .collect::<Vec<_>>();
+ let where_clause =
+ (!include_where_bounds.is_empty()).then(|| make::where_clause(include_where_bounds));
+
+ // FIXME: Consider generic parameters that do not appear in params/return type/captures but
+ // written explicitly inside the closure.
+ (Some(make::generic_param_list(include_params)), where_clause)
+}
+
+fn wrap_capture_in_deref_if_needed(
+ expr: &ast::Expr,
+ capture_name: &ast::Name,
+ capture_kind: CaptureKind,
+ is_ref: bool,
+) -> ast::Expr {
+ fn peel_parens(mut expr: ast::Expr) -> ast::Expr {
+ loop {
+ if ast::ParenExpr::can_cast(expr.syntax().kind()) {
+ let Some(parent) = expr.syntax().parent().and_then(ast::Expr::cast) else { break };
+ expr = parent;
+ } else {
+ break;
+ }
+ }
+ expr
+ }
+
+ let capture_name = make::expr_path(make::path_from_text(&capture_name.text()));
+ if capture_kind == CaptureKind::Move || is_ref {
+ return capture_name;
+ }
+ let expr_parent = expr.syntax().parent().and_then(ast::Expr::cast);
+ let expr_parent_peeled_parens = expr_parent.map(peel_parens);
+ let does_autoderef = match expr_parent_peeled_parens {
+ Some(
+ ast::Expr::AwaitExpr(_)
+ | ast::Expr::CallExpr(_)
+ | ast::Expr::FieldExpr(_)
+ | ast::Expr::FormatArgsExpr(_)
+ | ast::Expr::MethodCallExpr(_),
+ ) => true,
+ Some(ast::Expr::IndexExpr(parent_expr)) if parent_expr.base().as_ref() == Some(expr) => {
+ true
+ }
+ _ => false,
+ };
+ if does_autoderef {
+ return capture_name;
+ }
+ make::expr_prefix(T![*], capture_name)
+}
+
+fn capture_as_arg(ctx: &AssistContext<'_>, capture: &ClosureCapture) -> ast::Expr {
+ let place =
+ parse_expr_from_str(&capture.display_place_source_code(ctx.db()), ctx.file_id().edition())
+ .expect("`display_place_source_code()` produced an invalid expr");
+ let needs_mut = match capture.kind() {
+ CaptureKind::SharedRef => false,
+ CaptureKind::MutableRef | CaptureKind::UniqueSharedRef => true,
+ CaptureKind::Move => return place,
+ };
+ if let ast::Expr::PrefixExpr(expr) = &place {
+ if expr.op_kind() == Some(ast::UnaryOp::Deref) {
+ return expr.expr().expect("`display_place_source_code()` produced an invalid expr");
+ }
+ }
+ make::expr_ref(place, needs_mut)
+}
+
+fn handle_calls(
+ builder: &mut SourceChangeBuilder,
+ ctx: &AssistContext<'_>,
+ closure_name: Option<&ast::IdentPat>,
+ captures_as_args: &[ast::Expr],
+ closure: &ast::ClosureExpr,
+) {
+ if captures_as_args.is_empty() {
+ return;
+ }
+
+ match closure_name {
+ Some(closure_name) => {
+ let Some(closure_def) = ctx.sema.to_def(closure_name) else { return };
+ let closure_usages = Definition::from(closure_def).usages(&ctx.sema).all();
+ for (_, usages) in closure_usages {
+ for usage in usages {
+ let name = match usage.name {
+ FileReferenceNode::Name(name) => name.syntax().clone(),
+ FileReferenceNode::NameRef(name_ref) => name_ref.syntax().clone(),
+ FileReferenceNode::FormatStringEntry(..) => continue,
+ FileReferenceNode::Lifetime(_) => {
+ unreachable!("impossible usage")
+ }
+ };
+ let Some(expr) = name.parent().and_then(|it| {
+ ast::Expr::cast(
+ ast::PathSegment::cast(it)?.parent_path().syntax().parent()?,
+ )
+ }) else {
+ continue;
+ };
+ handle_call(builder, ctx, expr, captures_as_args);
+ }
+ }
+ }
+ None => {
+ handle_call(builder, ctx, ast::Expr::ClosureExpr(closure.clone()), captures_as_args);
+ }
+ }
+}
+
+fn handle_call(
+ builder: &mut SourceChangeBuilder,
+ ctx: &AssistContext<'_>,
+ closure_ref: ast::Expr,
+ captures_as_args: &[ast::Expr],
+) -> Option<()> {
+ let call =
+ ast::CallExpr::cast(peel_blocks_and_refs_and_parens(closure_ref).syntax().parent()?)?;
+ let args = call.arg_list()?;
+ // The really last token is `)`; we need one before that.
+ let has_trailing_comma = args.syntax().last_token()?.prev_token().is_some_and(|token| {
+ skip_trivia_token(token, Direction::Prev).is_some_and(|token| token.kind() == T![,])
+ });
+ let has_existing_args = args.args().next().is_some();
+
+ let FileRangeWrapper { file_id, range } = ctx.sema.original_range_opt(args.syntax())?;
+ let first_arg_indent = args.args().next().map(|it| it.indent_level());
+ let arg_list_indent = args.indent_level();
+ let insert_newlines =
+ first_arg_indent.is_some_and(|first_arg_indent| first_arg_indent != arg_list_indent);
+ let indent =
+ if insert_newlines { first_arg_indent.unwrap().to_string() } else { String::new() };
+ // FIXME: This text manipulation seems risky.
+ let text = ctx.db().file_text(file_id.file_id());
+ let mut text = text[..u32::from(range.end()).try_into().unwrap()].trim_end();
+ if !text.ends_with(')') {
+ return None;
+ }
+ text = text[..text.len() - 1].trim_end();
+ let offset = TextSize::new(text.len().try_into().unwrap());
+
+ let mut to_insert = String::new();
+ if has_existing_args && !has_trailing_comma {
+ to_insert.push(',');
+ }
+ if insert_newlines {
+ to_insert.push('\n');
+ }
+ let (last_arg, rest_args) =
+ captures_as_args.split_last().expect("already checked has captures");
+ if !insert_newlines && has_existing_args {
+ to_insert.push(' ');
+ }
+ if let Some((first_arg, rest_args)) = rest_args.split_first() {
+ format_to!(to_insert, "{indent}{first_arg},",);
+ if insert_newlines {
+ to_insert.push('\n');
+ }
+ for new_arg in rest_args {
+ if !insert_newlines {
+ to_insert.push(' ');
+ }
+ format_to!(to_insert, "{indent}{new_arg},",);
+ if insert_newlines {
+ to_insert.push('\n');
+ }
+ }
+ if !insert_newlines {
+ to_insert.push(' ');
+ }
+ }
+ format_to!(to_insert, "{indent}{last_arg}");
+ if has_trailing_comma {
+ to_insert.push(',');
+ }
+
+ builder.edit_file(file_id);
+ builder.insert(offset, to_insert);
+
+ Some(())
+}
+
+fn peel_blocks_and_refs_and_parens(mut expr: ast::Expr) -> ast::Expr {
+ loop {
+ let Some(parent) = expr.syntax().parent() else { break };
+ if matches!(parent.kind(), SyntaxKind::PAREN_EXPR | SyntaxKind::REF_EXPR) {
+ expr = ast::Expr::cast(parent).unwrap();
+ continue;
+ }
+ if let Some(stmt_list) = ast::StmtList::cast(parent) {
+ if let Some(block) = stmt_list.syntax().parent().and_then(ast::BlockExpr::cast) {
+ expr = ast::Expr::BlockExpr(block);
+ continue;
+ }
+ }
+ break;
+ }
+ expr
+}
+
+// FIXME:
+// Somehow handle the case of `let Struct { field, .. } = capture`.
+// Replacing `capture` with `capture_field` won't work.
+fn expr_of_pat(pat: ast::Pat) -> Option<ast::Expr> {
+ 'find_expr: {
+ for ancestor in pat.syntax().ancestors() {
+ if let Some(let_stmt) = ast::LetStmt::cast(ancestor.clone()) {
+ break 'find_expr let_stmt.initializer();
+ }
+ if ast::MatchArm::can_cast(ancestor.kind()) {
+ if let Some(match_) =
+ ancestor.parent().and_then(|it| it.parent()).and_then(ast::MatchExpr::cast)
+ {
+ break 'find_expr match_.expr();
+ }
+ }
+ if ast::ExprStmt::can_cast(ancestor.kind()) {
+ break;
+ }
+ }
+ None
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn handles_unique_captures() {
+ check_assist(
+ convert_closure_to_fn,
+ r#"
+//- minicore:copy
+fn main() {
+ let s = &mut true;
+ let closure = |$0| { *s = false; };
+ closure();
+}
+"#,
+ r#"
+fn main() {
+ let s = &mut true;
+ fn closure(s: &mut bool) { *s = false; }
+ closure(s);
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn multiple_capture_usages() {
+ check_assist(
+ convert_closure_to_fn,
+ r#"
+//- minicore:copy
+struct A { a: i32, b: bool }
+fn main() {
+ let mut a = A { a: 123, b: false };
+ let closure = |$0| {
+ let b = a.b;
+ a = A { a: 456, b: true };
+ };
+ closure();
+}
+"#,
+ r#"
+struct A { a: i32, b: bool }
+fn main() {
+ let mut a = A { a: 123, b: false };
+ fn closure(a: &mut A) {
+ let b = a.b;
+ *a = A { a: 456, b: true };
+ }
+ closure(&mut a);
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn changes_names_of_place() {
+ check_assist(
+ convert_closure_to_fn,
+ r#"
+//- minicore:copy
+struct A { b: &'static B, c: i32 }
+struct B(bool, i32);
+struct C;
+impl C {
+ fn foo(&self) {
+ let a = A { b: &B(false, 0), c: 123 };
+ let closure = |$0| {
+ let b = a.b.1;
+ let c = &*self;
+ };
+ closure();
+ }
+}
+"#,
+ r#"
+struct A { b: &'static B, c: i32 }
+struct B(bool, i32);
+struct C;
+impl C {
+ fn foo(&self) {
+ let a = A { b: &B(false, 0), c: 123 };
+ fn closure(this: &C, a_b_1: &i32) {
+ let b = *a_b_1;
+ let c = this;
+ }
+ closure(self, &a.b.1);
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn self_with_fields_does_not_change_to_this() {
+ check_assist(
+ convert_closure_to_fn,
+ r#"
+//- minicore:copy
+struct A { b: &'static B, c: i32 }
+struct B(bool, i32);
+impl A {
+ fn foo(&self) {
+ let closure = |$0| {
+ let b = self.b.1;
+ };
+ closure();
+ }
+}
+"#,
+ r#"
+struct A { b: &'static B, c: i32 }
+struct B(bool, i32);
+impl A {
+ fn foo(&self) {
+ fn closure(self_b_1: &i32) {
+ let b = *self_b_1;
+ }
+ closure(&self.b.1);
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn replaces_async_closure_with_async_fn() {
+ check_assist(
+ convert_closure_to_fn,
+ r#"
+//- minicore: copy, future
+fn foo(&self) {
+ let closure = async |$0| 1;
+ closure();
+}
+"#,
+ r#"
+fn foo(&self) {
+ async fn closure() -> i32 {
+ 1
+ }
+ closure();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn replaces_async_block_with_async_fn() {
+ check_assist(
+ convert_closure_to_fn,
+ r#"
+//- minicore: copy, future
+fn foo() {
+ let closure = |$0| async { 1 };
+ closure();
+}
+"#,
+ r#"
+fn foo() {
+ async fn closure() -> i32 { 1 }
+ closure();
+}
+"#,
+ );
+ }
+
+ #[test]
+ #[ignore = "FIXME: we do not do type inference for gen blocks yet"]
+ fn replaces_gen_block_with_gen_fn() {
+ check_assist(
+ convert_closure_to_fn,
+ r#"
+//- minicore: copy, iterator
+//- /lib.rs edition:2024
+fn foo() {
+ let closure = |$0| gen {
+ yield 1;
+ };
+ closure();
+}
+"#,
+ r#"
+fn foo() {
+ gen fn closure() -> i32 {
+ yield 1;
+ }
+ closure();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn leaves_block_in_place() {
+ check_assist(
+ convert_closure_to_fn,
+ r#"
+//- minicore: copy
+fn foo() {
+ let closure = |$0| {};
+ closure();
+}
+"#,
+ r#"
+fn foo() {
+ fn closure() {}
+ closure();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn wraps_in_block_if_needed() {
+ check_assist(
+ convert_closure_to_fn,
+ r#"
+//- minicore: copy
+fn foo() {
+ let a = 1;
+ let closure = |$0| a;
+ closure();
+}
+"#,
+ r#"
+fn foo() {
+ let a = 1;
+ fn closure(a: &i32) -> i32 {
+ *a
+ }
+ closure(&a);
+}
+"#,
+ );
+ check_assist(
+ convert_closure_to_fn,
+ r#"
+//- minicore: copy
+fn foo() {
+ let closure = |$0| 'label: {};
+ closure();
+}
+"#,
+ r#"
+fn foo() {
+ fn closure() {
+ 'label: {}
+ }
+ closure();
+}
+"#,
+ );
+ check_assist(
+ convert_closure_to_fn,
+ r#"
+//- minicore: copy
+fn foo() {
+ let closure = |$0| {
+ const { () }
+ };
+ closure();
+}
+"#,
+ r#"
+fn foo() {
+ fn closure() {
+ const { () }
+ }
+ closure();
+}
+"#,
+ );
+ check_assist(
+ convert_closure_to_fn,
+ r#"
+//- minicore: copy
+fn foo() {
+ let closure = |$0| unsafe { };
+ closure();
+}
+"#,
+ r#"
+fn foo() {
+ fn closure() {
+ unsafe { }
+ }
+ closure();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn closure_in_closure() {
+ check_assist(
+ convert_closure_to_fn,
+ r#"
+//- minicore: copy
+fn foo() {
+ let a = 1;
+ || |$0| { let b = &a; };
+}
+"#,
+ r#"
+fn foo() {
+ let a = 1;
+ || { fn fun_name(a: &i32) { let b = a; } fun_name };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn closure_in_block() {
+ check_assist(
+ convert_closure_to_fn,
+ r#"
+//- minicore: copy
+fn foo() {
+ {
+ let a = 1;
+ |$0| { let b = &a; }
+ };
+}
+"#,
+ r#"
+fn foo() {
+ {
+ let a = 1;
+ fn fun_name(a: &i32) { let b = a; }
+ fun_name
+ };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn finds_pat_for_expr() {
+ check_assist(
+ convert_closure_to_fn,
+ r#"
+//- minicore: copy
+struct A { b: B }
+struct B(bool, i32);
+fn foo() {
+ let mut a = A { b: B(true, 0) };
+ let closure = |$0| {
+ let A { b: B(_, ref mut c) } = a;
+ };
+ closure();
+}
+"#,
+ r#"
+struct A { b: B }
+struct B(bool, i32);
+fn foo() {
+ let mut a = A { b: B(true, 0) };
+ fn closure(a_b_1: &mut i32) {
+ let A { b: B(_, ref mut c) } = a_b_1;
+ }
+ closure(&mut a.b.1);
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn with_existing_params() {
+ check_assist(
+ convert_closure_to_fn,
+ r#"
+//- minicore: copy
+fn foo() {
+ let (mut a, b) = (0.1, "abc");
+ let closure = |$0p1: i32, p2: &mut bool| {
+ a = 1.2;
+ let c = b;
+ };
+ closure(0, &mut false);
+}
+"#,
+ r#"
+fn foo() {
+ let (mut a, b) = (0.1, "abc");
+ fn closure(p1: i32, p2: &mut bool, a: &mut f64, b: &&str) {
+ *a = 1.2;
+ let c = *b;
+ }
+ closure(0, &mut false, &mut a, &b);
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn with_existing_params_newlines() {
+ check_assist(
+ convert_closure_to_fn,
+ r#"
+//- minicore: copy
+fn foo() {
+ let (mut a, b) = (0.1, "abc");
+ let closure = |$0p1: i32, p2| {
+ let _: &mut bool = p2;
+ a = 1.2;
+ let c = b;
+ };
+ closure(
+ 0,
+ &mut false
+ );
+}
+"#,
+ r#"
+fn foo() {
+ let (mut a, b) = (0.1, "abc");
+ fn closure(p1: i32, p2: &mut bool, a: &mut f64, b: &&str) {
+ let _: &mut bool = p2;
+ *a = 1.2;
+ let c = *b;
+ }
+ closure(
+ 0,
+ &mut false,
+ &mut a,
+ &b
+ );
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn with_existing_params_trailing_comma() {
+ check_assist(
+ convert_closure_to_fn,
+ r#"
+//- minicore: copy
+fn foo() {
+ let (mut a, b) = (0.1, "abc");
+ let closure = |$0p1: i32, p2| {
+ let _: &mut bool = p2;
+ a = 1.2;
+ let c = b;
+ };
+ closure(
+ 0,
+ &mut false,
+ );
+}
+"#,
+ r#"
+fn foo() {
+ let (mut a, b) = (0.1, "abc");
+ fn closure(p1: i32, p2: &mut bool, a: &mut f64, b: &&str) {
+ let _: &mut bool = p2;
+ *a = 1.2;
+ let c = *b;
+ }
+ closure(
+ 0,
+ &mut false,
+ &mut a,
+ &b,
+ );
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn closure_using_generic_params() {
+ check_assist(
+ convert_closure_to_fn,
+ r#"
+//- minicore: copy
+struct Foo<A, B, const C: usize>(A, B);
+impl<A, B: From<A>, const C: usize> Foo<A, B, C> {
+ fn foo<D, E, F, G>(a: A, b: D)
+ where
+ E: From<D>,
+ {
+ let closure = |$0c: F| {
+ let a = B::from(a);
+ let b = E::from(b);
+ };
+ }
+}
+"#,
+ r#"
+struct Foo<A, B, const C: usize>(A, B);
+impl<A, B: From<A>, const C: usize> Foo<A, B, C> {
+ fn foo<D, E, F, G>(a: A, b: D)
+ where
+ E: From<D>,
+ {
+ fn closure<A, B: From<A>, D, E, F>(c: F, a: A, b: D) where E: From<D> {
+ let a = B::from(a);
+ let b = E::from(b);
+ }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn closure_in_stmt() {
+ check_assist(
+ convert_closure_to_fn,
+ r#"
+//- minicore: copy
+fn bar(_: impl FnOnce() -> i32) {}
+fn foo() {
+ let a = 123;
+ bar(|$0| a);
+}
+"#,
+ r#"
+fn bar(_: impl FnOnce() -> i32) {}
+fn foo() {
+ let a = 123;
+ fn fun_name(a: &i32) -> i32 {
+ *a
+ }
+ bar(fun_name);
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unique_and_imm() {
+ check_assist(
+ convert_closure_to_fn,
+ r#"
+//- minicore:copy
+fn main() {
+ let a = &mut true;
+ let closure = |$0| {
+ let b = &a;
+ *a = false;
+ };
+ closure();
+}
+"#,
+ r#"
+fn main() {
+ let a = &mut true;
+ fn closure(a: &mut &mut bool) {
+ let b = a;
+ **a = false;
+ }
+ closure(&mut a);
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn only_applicable_in_param_list() {
+ check_assist_not_applicable(
+ convert_closure_to_fn,
+ r#"
+//- minicore:copy
+fn main() {
+ let closure = || { $0 };
+}
+"#,
+ );
+ check_assist_not_applicable(
+ convert_closure_to_fn,
+ r#"
+//- minicore:copy
+fn main() {
+ let $0closure = || { };
+}
+"#,
+ );
+ }
+}
diff --git a/crates/ide-assists/src/handlers/convert_into_to_from.rs b/crates/ide-assists/src/handlers/convert_into_to_from.rs
index 5aa94590e6..8c59ef4314 100644
--- a/crates/ide-assists/src/handlers/convert_into_to_from.rs
+++ b/crates/ide-assists/src/handlers/convert_into_to_from.rs
@@ -51,7 +51,10 @@ pub(crate) fn convert_into_to_from(acc: &mut Assists, ctx: &AssistContext<'_>) -
Some(hir::PathResolution::Def(module_def)) => module_def,
_ => return None,
};
- mod_path_to_ast(&module.find_path(ctx.db(), src_type_def, cfg)?)
+ mod_path_to_ast(
+ &module.find_path(ctx.db(), src_type_def, cfg)?,
+ module.krate().edition(ctx.db()),
+ )
};
let dest_type = match &ast_trait {
diff --git a/crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs b/crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs
index e86ff0dbeb..3c9a917410 100644
--- a/crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs
+++ b/crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs
@@ -114,12 +114,16 @@ pub(crate) fn convert_for_loop_with_for_each(
|builder| {
let mut buf = String::new();
- if let Some((expr_behind_ref, method)) =
+ if let Some((expr_behind_ref, method, krate)) =
is_ref_and_impls_iter_method(&ctx.sema, &iterable)
{
// We have either "for x in &col" and col implements a method called iter
// or "for x in &mut col" and col implements a method called iter_mut
- format_to!(buf, "{expr_behind_ref}.{}()", method.display(ctx.db()));
+ format_to!(
+ buf,
+ "{expr_behind_ref}.{}()",
+ method.display(ctx.db(), krate.edition(ctx.db()))
+ );
} else if let ast::Expr::RangeExpr(..) = iterable {
// range expressions need to be parenthesized for the syntax to be correct
format_to!(buf, "({iterable})");
@@ -144,7 +148,7 @@ pub(crate) fn convert_for_loop_with_for_each(
fn is_ref_and_impls_iter_method(
sema: &hir::Semantics<'_, ide_db::RootDatabase>,
iterable: &ast::Expr,
-) -> Option<(ast::Expr, hir::Name)> {
+) -> Option<(ast::Expr, hir::Name, hir::Crate)> {
let ref_expr = match iterable {
ast::Expr::RefExpr(r) => r,
_ => return None,
@@ -172,7 +176,7 @@ fn is_ref_and_impls_iter_method(
return None;
}
- Some((expr_behind_ref, wanted_method))
+ Some((expr_behind_ref, wanted_method, krate))
}
/// Whether iterable implements core::Iterator
diff --git a/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs b/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs
index 3705597927..8d4ff84084 100644
--- a/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs
+++ b/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs
@@ -2,8 +2,8 @@ use either::Either;
use ide_db::{defs::Definition, search::FileReference};
use itertools::Itertools;
use syntax::{
- ast::{self, AstNode, HasGenericParams, HasVisibility},
- match_ast, SyntaxKind,
+ ast::{self, AstNode, HasAttrs, HasGenericParams, HasVisibility},
+ match_ast, ted, SyntaxKind,
};
use crate::{assist_context::SourceChangeBuilder, AssistContext, AssistId, AssistKind, Assists};
@@ -87,9 +87,14 @@ fn edit_struct_def(
) {
// Note that we don't need to consider macro files in this function because this is
// currently not triggered for struct definitions inside macro calls.
- let tuple_fields = record_fields
- .fields()
- .filter_map(|f| Some(ast::make::tuple_field(f.visibility(), f.ty()?)));
+ let tuple_fields = record_fields.fields().filter_map(|f| {
+ let field = ast::make::tuple_field(f.visibility(), f.ty()?).clone_for_update();
+ ted::insert_all(
+ ted::Position::first_child_of(field.syntax()),
+ f.attrs().map(|attr| attr.syntax().clone_subtree().clone_for_update().into()).collect(),
+ );
+ Some(field)
+ });
let tuple_fields = ast::make::tuple_field_list(tuple_fields);
let record_fields_text_range = record_fields.syntax().text_range();
@@ -978,4 +983,20 @@ impl HasAssoc for Struct {
"#,
);
}
+
+ #[test]
+ fn fields_with_attrs() {
+ check_assist(
+ convert_named_struct_to_tuple_struct,
+ r#"
+pub struct $0Foo {
+ #[my_custom_attr]
+ value: u32,
+}
+"#,
+ r#"
+pub struct Foo(#[my_custom_attr] u32);
+"#,
+ );
+ }
}
diff --git a/crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs b/crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs
index 0f0b4442d8..91af9b05bb 100644
--- a/crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs
+++ b/crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs
@@ -211,7 +211,7 @@ fn augment_references_with_imports(
)
.map(|mod_path| {
make::path_concat(
- mod_path_to_ast(&mod_path),
+ mod_path_to_ast(&mod_path, target_module.krate().edition(ctx.db())),
make::path_from_text(struct_name),
)
});
diff --git a/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs b/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs
index c72bd411d6..f01b4ea0fd 100644
--- a/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs
+++ b/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs
@@ -1,8 +1,8 @@
use either::Either;
use ide_db::defs::{Definition, NameRefClass};
use syntax::{
- ast::{self, AstNode, HasGenericParams, HasVisibility},
- match_ast, SyntaxKind, SyntaxNode,
+ ast::{self, AstNode, HasAttrs, HasGenericParams, HasVisibility},
+ match_ast, ted, SyntaxKind, SyntaxNode,
};
use crate::{assist_context::SourceChangeBuilder, AssistContext, AssistId, AssistKind, Assists};
@@ -83,10 +83,14 @@ fn edit_struct_def(
tuple_fields: ast::TupleFieldList,
names: Vec<ast::Name>,
) {
- let record_fields = tuple_fields
- .fields()
- .zip(names)
- .filter_map(|(f, name)| Some(ast::make::record_field(f.visibility(), name, f.ty()?)));
+ let record_fields = tuple_fields.fields().zip(names).filter_map(|(f, name)| {
+ let field = ast::make::record_field(f.visibility(), name, f.ty()?).clone_for_update();
+ ted::insert_all(
+ ted::Position::first_child_of(field.syntax()),
+ f.attrs().map(|attr| attr.syntax().clone_subtree().clone_for_update().into()).collect(),
+ );
+ Some(field)
+ });
let record_fields = ast::make::record_field_list(record_fields);
let tuple_fields_text_range = tuple_fields.syntax().text_range();
@@ -907,4 +911,17 @@ where
"#,
);
}
+
+ #[test]
+ fn fields_with_attrs() {
+ check_assist(
+ convert_tuple_struct_to_named_struct,
+ r#"
+pub struct $0Foo(#[my_custom_attr] u32);
+"#,
+ r#"
+pub struct Foo { #[my_custom_attr] field1: u32 }
+"#,
+ );
+ }
}
diff --git a/crates/ide-assists/src/handlers/convert_while_to_loop.rs b/crates/ide-assists/src/handlers/convert_while_to_loop.rs
index c34b684112..434daa279c 100644
--- a/crates/ide-assists/src/handlers/convert_while_to_loop.rs
+++ b/crates/ide-assists/src/handlers/convert_while_to_loop.rs
@@ -1,5 +1,6 @@
-use std::iter::once;
+use std::iter;
+use either::Either;
use ide_db::syntax_helpers::node_ext::is_pattern_cond;
use syntax::{
ast::{
@@ -52,18 +53,30 @@ pub(crate) fn convert_while_to_loop(acc: &mut Assists, ctx: &AssistContext<'_>)
|edit| {
let while_indent_level = IndentLevel::from_node(while_expr.syntax());
- let break_block =
- make::block_expr(once(make::expr_stmt(make::expr_break(None, None)).into()), None)
- .indent(while_indent_level);
+ let break_block = make::block_expr(
+ iter::once(make::expr_stmt(make::expr_break(None, None)).into()),
+ None,
+ )
+ .indent(while_indent_level);
let block_expr = if is_pattern_cond(while_cond.clone()) {
let if_expr = make::expr_if(while_cond, while_body, Some(break_block.into()));
- let stmts = once(make::expr_stmt(if_expr).into());
+ let stmts = iter::once(make::expr_stmt(if_expr).into());
make::block_expr(stmts, None)
} else {
let if_cond = invert_boolean_expression(while_cond);
- let if_expr = make::expr_if(if_cond, break_block, None);
- let stmts = once(make::expr_stmt(if_expr).into()).chain(while_body.statements());
- make::block_expr(stmts, while_body.tail_expr())
+ let if_expr = make::expr_if(if_cond, break_block, None).syntax().clone().into();
+ let elements = while_body.stmt_list().map_or_else(
+ || Either::Left(iter::empty()),
+ |stmts| {
+ Either::Right(stmts.syntax().children_with_tokens().filter(|node_or_tok| {
+ // Filter out the trailing expr
+ !node_or_tok
+ .as_node()
+ .is_some_and(|node| ast::Expr::can_cast(node.kind()))
+ }))
+ },
+ );
+ make::hacky_block_expr(iter::once(if_expr).chain(elements), while_body.tail_expr())
};
let replacement = make::expr_loop(block_expr.indent(while_indent_level));
@@ -185,4 +198,72 @@ fn main() {
"#,
);
}
+
+ #[test]
+ fn preserve_comments() {
+ check_assist(
+ convert_while_to_loop,
+ r#"
+fn main() {
+ let mut i = 0;
+
+ $0while i < 5 {
+ // comment 1
+ dbg!(i);
+ // comment 2
+ i += 1;
+ // comment 3
+ }
+}
+"#,
+ r#"
+fn main() {
+ let mut i = 0;
+
+ loop {
+ if i >= 5 {
+ break;
+ }
+ // comment 1
+ dbg!(i);
+ // comment 2
+ i += 1;
+ // comment 3
+ }
+}
+"#,
+ );
+
+ check_assist(
+ convert_while_to_loop,
+ r#"
+fn main() {
+ let v = vec![1, 2, 3];
+ let iter = v.iter();
+
+ $0while let Some(i) = iter.next() {
+ // comment 1
+ dbg!(i);
+ // comment 2
+ }
+}
+"#,
+ r#"
+fn main() {
+ let v = vec![1, 2, 3];
+ let iter = v.iter();
+
+ loop {
+ if let Some(i) = iter.next() {
+ // comment 1
+ dbg!(i);
+ // comment 2
+ } else {
+ break;
+ }
+ }
+}
+"#,
+ );
+ }
}
diff --git a/crates/ide-assists/src/handlers/destructure_struct_binding.rs b/crates/ide-assists/src/handlers/destructure_struct_binding.rs
index 095b8f958d..b229b750e8 100644
--- a/crates/ide-assists/src/handlers/destructure_struct_binding.rs
+++ b/crates/ide-assists/src/handlers/destructure_struct_binding.rs
@@ -7,7 +7,7 @@ use ide_db::{
FxHashMap, FxHashSet,
};
use itertools::Itertools;
-use syntax::{ast, ted, AstNode, SmolStr, SyntaxNode, ToSmolStr};
+use syntax::{ast, ted, AstNode, Edition, SmolStr, SyntaxNode, ToSmolStr};
use text_edit::TextRange;
use crate::{
@@ -81,6 +81,7 @@ struct StructEditData {
has_private_members: bool,
is_nested: bool,
is_ref: bool,
+ edition: Edition,
}
fn collect_data(ident_pat: ast::IdentPat, ctx: &AssistContext<'_>) -> Option<StructEditData> {
@@ -145,6 +146,7 @@ fn collect_data(ident_pat: ast::IdentPat, ctx: &AssistContext<'_>) -> Option<Str
names_in_scope,
is_nested,
is_ref,
+ edition: module.krate().edition(ctx.db()),
})
}
@@ -180,7 +182,7 @@ fn build_assignment_edit(
) -> AssignmentEdit {
let ident_pat = builder.make_mut(data.ident_pat.clone());
- let struct_path = mod_path_to_ast(&data.struct_def_path);
+ let struct_path = mod_path_to_ast(&data.struct_def_path, data.edition);
let is_ref = ident_pat.ref_token().is_some();
let is_mut = ident_pat.mut_token().is_some();
@@ -247,7 +249,7 @@ fn generate_field_names(ctx: &AssistContext<'_>, data: &StructEditData) -> Vec<(
.visible_fields
.iter()
.map(|field| {
- let field_name = field.name(ctx.db()).display_no_db().to_smolstr();
+ let field_name = field.name(ctx.db()).display_no_db(data.edition).to_smolstr();
let new_name = new_field_name(field_name.clone(), &data.names_in_scope);
(field_name, new_name)
})
diff --git a/crates/ide-assists/src/handlers/expand_glob_import.rs b/crates/ide-assists/src/handlers/expand_glob_import.rs
index 9beb616d99..3d6d37ad93 100644
--- a/crates/ide-assists/src/handlers/expand_glob_import.rs
+++ b/crates/ide-assists/src/handlers/expand_glob_import.rs
@@ -66,7 +66,9 @@ pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext<'_>) ->
let names_to_import = find_names_to_import(ctx, refs_in_target, imported_defs);
let expanded = make::use_tree_list(names_to_import.iter().map(|n| {
- let path = make::ext::ident_path(&n.display(ctx.db()).to_string());
+ let path = make::ext::ident_path(
+ &n.display(ctx.db(), current_module.krate().edition(ctx.db())).to_string(),
+ );
make::use_tree(path, None, None, false)
}))
.clone_for_update();
diff --git a/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs b/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs
index 9180d8dfcb..e4d347ef16 100644
--- a/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs
+++ b/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs
@@ -1,11 +1,7 @@
use crate::{utils, AssistContext, Assists};
-use hir::DescendPreference;
use ide_db::{
assists::{AssistId, AssistKind},
- syntax_helpers::{
- format_string::is_format_string,
- format_string_exprs::{parse_format_exprs, Arg},
- },
+ syntax_helpers::format_string_exprs::{parse_format_exprs, Arg},
};
use itertools::Itertools;
use syntax::{
@@ -40,13 +36,7 @@ pub(crate) fn extract_expressions_from_format_string(
let tt = fmt_string.syntax().parent().and_then(ast::TokenTree::cast)?;
let tt_delimiter = tt.left_delimiter_token()?.kind();
- let expanded_t = ast::String::cast(
- ctx.sema
- .descend_into_macros_single(DescendPreference::SameKind, fmt_string.syntax().clone()),
- )?;
- if !is_format_string(&expanded_t) {
- return None;
- }
+ let _ = ctx.sema.as_format_args_parts(&fmt_string)?;
let (new_fmt, extracted_args) = parse_format_exprs(fmt_string.text()).ok()?;
if extracted_args.is_empty() {
diff --git a/crates/ide-assists/src/handlers/extract_function.rs b/crates/ide-assists/src/handlers/extract_function.rs
index 0a2cb6d5ef..ad0be89645 100644
--- a/crates/ide-assists/src/handlers/extract_function.rs
+++ b/crates/ide-assists/src/handlers/extract_function.rs
@@ -3,8 +3,8 @@ use std::{iter, ops::RangeInclusive};
use ast::make;
use either::Either;
use hir::{
- DescendPreference, HasSource, HirDisplay, InFile, Local, LocalSource, ModuleDef,
- PathResolution, Semantics, TypeInfo, TypeParam,
+ HasSource, HirDisplay, InFile, Local, LocalSource, ModuleDef, PathResolution, Semantics,
+ TypeInfo, TypeParam,
};
use ide_db::{
defs::{Definition, NameRefClass},
@@ -23,7 +23,7 @@ use syntax::{
self, edit::IndentLevel, edit_in_place::Indent, AstNode, AstToken, HasGenericParams,
HasName,
},
- match_ast, ted, SyntaxElement,
+ match_ast, ted, Edition, SyntaxElement,
SyntaxKind::{self, COMMENT},
SyntaxNode, SyntaxToken, TextRange, TextSize, TokenAtOffset, WalkEvent, T,
};
@@ -84,7 +84,6 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
};
let body = extraction_target(&node, range)?;
- let (container_info, contains_tail_expr) = body.analyze_container(&ctx.sema)?;
let (locals_used, self_param) = body.analyze(&ctx.sema);
@@ -92,6 +91,9 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
let insert_after = node_to_insert_after(&body, anchor)?;
let semantics_scope = ctx.sema.scope(&insert_after)?;
let module = semantics_scope.module();
+ let edition = semantics_scope.krate().edition(ctx.db());
+
+ let (container_info, contains_tail_expr) = body.analyze_container(&ctx.sema, edition)?;
let ret_ty = body.return_ty(ctx)?;
let control_flow = body.external_control_flow(ctx, &container_info)?;
@@ -217,7 +219,11 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
);
if let Some(mod_path) = mod_path {
- insert_use(&scope, mod_path_to_ast(&mod_path), &ctx.config.insert_use);
+ insert_use(
+ &scope,
+ mod_path_to_ast(&mod_path, edition),
+ &ctx.config.insert_use,
+ );
}
}
}
@@ -238,7 +244,13 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
fn make_function_name(semantics_scope: &hir::SemanticsScope<'_>) -> ast::NameRef {
let mut names_in_scope = vec![];
semantics_scope.process_all_names(&mut |name, _| {
- names_in_scope.push(name.display(semantics_scope.db.upcast()).to_string())
+ names_in_scope.push(
+ name.display(
+ semantics_scope.db.upcast(),
+ semantics_scope.krate().edition(semantics_scope.db),
+ )
+ .to_string(),
+ )
});
let default_name = "fun_name";
@@ -366,6 +378,7 @@ struct ContainerInfo {
ret_type: Option<hir::Type>,
generic_param_lists: Vec<ast::GenericParamList>,
where_clauses: Vec<ast::WhereClause>,
+ edition: Edition,
}
/// Control flow that is exported from extracted function
@@ -489,8 +502,8 @@ impl Param {
}
}
- fn to_arg(&self, ctx: &AssistContext<'_>) -> ast::Expr {
- let var = path_expr_from_local(ctx, self.var);
+ fn to_arg(&self, ctx: &AssistContext<'_>, edition: Edition) -> ast::Expr {
+ let var = path_expr_from_local(ctx, self.var, edition);
match self.kind() {
ParamKind::Value | ParamKind::MutValue => var,
ParamKind::SharedRef => make::expr_ref(var, false),
@@ -498,8 +511,13 @@ impl Param {
}
}
- fn to_param(&self, ctx: &AssistContext<'_>, module: hir::Module) -> ast::Param {
- let var = self.var.name(ctx.db()).display(ctx.db()).to_string();
+ fn to_param(
+ &self,
+ ctx: &AssistContext<'_>,
+ module: hir::Module,
+ edition: Edition,
+ ) -> ast::Param {
+ let var = self.var.name(ctx.db()).display(ctx.db(), edition).to_string();
let var_name = make::name(&var);
let pat = match self.kind() {
ParamKind::MutValue => make::ident_pat(false, true, var_name),
@@ -520,7 +538,7 @@ impl Param {
}
impl TryKind {
- fn of_ty(ty: hir::Type, ctx: &AssistContext<'_>) -> Option<TryKind> {
+ fn of_ty(ty: hir::Type, ctx: &AssistContext<'_>, edition: Edition) -> Option<TryKind> {
if ty.is_unknown() {
// We favour Result for `expr?`
return Some(TryKind::Result { ty });
@@ -529,7 +547,7 @@ impl TryKind {
let name = adt.name(ctx.db());
// FIXME: use lang items to determine if it is std type or user defined
// E.g. if user happens to define type named `Option`, we would have false positive
- let name = &name.display(ctx.db()).to_string();
+ let name = &name.display(ctx.db(), edition).to_string();
match name.as_str() {
"Option" => Some(TryKind::Option),
"Result" => Some(TryKind::Result { ty }),
@@ -816,7 +834,7 @@ impl FunctionBody {
.descendants_with_tokens()
.filter_map(SyntaxElement::into_token)
.filter(|it| matches!(it.kind(), SyntaxKind::IDENT | T![self]))
- .flat_map(|t| sema.descend_into_macros(DescendPreference::None, t))
+ .flat_map(|t| sema.descend_into_macros_exact(t))
.for_each(|t| add_name_if_local(t.parent().and_then(ast::NameRef::cast)));
}
}
@@ -828,6 +846,7 @@ impl FunctionBody {
fn analyze_container(
&self,
sema: &Semantics<'_, RootDatabase>,
+ edition: Edition,
) -> Option<(ContainerInfo, bool)> {
let mut ancestors = self.parent()?.ancestors();
let infer_expr_opt = |expr| sema.type_of_expr(&expr?).map(TypeInfo::adjusted);
@@ -927,6 +946,7 @@ impl FunctionBody {
ret_type: ty,
generic_param_lists,
where_clauses,
+ edition,
},
contains_tail_expr,
))
@@ -1015,7 +1035,7 @@ impl FunctionBody {
let kind = match (try_expr, ret_expr, break_expr, continue_expr) {
(Some(_), _, None, None) => {
let ret_ty = container_info.ret_type.clone()?;
- let kind = TryKind::of_ty(ret_ty, ctx)?;
+ let kind = TryKind::of_ty(ret_ty, ctx, container_info.edition)?;
Some(FlowKind::Try { kind })
}
@@ -1397,7 +1417,7 @@ fn fixup_call_site(builder: &mut SourceChangeBuilder, body: &FunctionBody) {
fn make_call(ctx: &AssistContext<'_>, fun: &Function, indent: IndentLevel) -> SyntaxNode {
let ret_ty = fun.return_type(ctx);
- let args = make::arg_list(fun.params.iter().map(|param| param.to_arg(ctx)));
+ let args = make::arg_list(fun.params.iter().map(|param| param.to_arg(ctx, fun.mods.edition)));
let name = fun.name.clone();
let mut call_expr = if fun.self_param.is_some() {
let self_arg = make::expr_path(make::ext::ident_path("self"));
@@ -1420,13 +1440,13 @@ fn make_call(ctx: &AssistContext<'_>, fun: &Function, indent: IndentLevel) -> Sy
[] => None,
[var] => {
let name = var.local.name(ctx.db());
- let name = make::name(&name.display(ctx.db()).to_string());
+ let name = make::name(&name.display(ctx.db(), fun.mods.edition).to_string());
Some(ast::Pat::IdentPat(make::ident_pat(false, var.mut_usage_outside_body, name)))
}
vars => {
let binding_pats = vars.iter().map(|var| {
let name = var.local.name(ctx.db());
- let name = make::name(&name.display(ctx.db()).to_string());
+ let name = make::name(&name.display(ctx.db(), fun.mods.edition).to_string());
make::ident_pat(false, var.mut_usage_outside_body, name).into()
});
Some(ast::Pat::TuplePat(make::tuple_pat(binding_pats)))
@@ -1569,8 +1589,8 @@ impl FlowHandler {
}
}
-fn path_expr_from_local(ctx: &AssistContext<'_>, var: Local) -> ast::Expr {
- let name = var.name(ctx.db()).display(ctx.db()).to_string();
+fn path_expr_from_local(ctx: &AssistContext<'_>, var: Local, edition: Edition) -> ast::Expr {
+ let name = var.name(ctx.db()).display(ctx.db(), edition).to_string();
make::expr_path(make::ext::ident_path(&name))
}
@@ -1581,7 +1601,7 @@ fn format_function(
old_indent: IndentLevel,
) -> ast::Fn {
let fun_name = make::name(&fun.name.text());
- let params = fun.make_param_list(ctx, module);
+ let params = fun.make_param_list(ctx, module, fun.mods.edition);
let ret_ty = fun.make_ret_ty(ctx, module);
let body = make_body(ctx, old_indent, fun);
let (generic_params, where_clause) = make_generic_params_and_where_clause(ctx, fun);
@@ -1597,6 +1617,7 @@ fn format_function(
fun.control_flow.is_async,
fun.mods.is_const,
fun.control_flow.is_unsafe,
+ false,
)
}
@@ -1707,9 +1728,14 @@ impl Function {
type_params_in_descendant_paths.chain(type_params_in_params).collect()
}
- fn make_param_list(&self, ctx: &AssistContext<'_>, module: hir::Module) -> ast::ParamList {
+ fn make_param_list(
+ &self,
+ ctx: &AssistContext<'_>,
+ module: hir::Module,
+ edition: Edition,
+ ) -> ast::ParamList {
let self_param = self.self_param.clone();
- let params = self.params.iter().map(|param| param.to_param(ctx, module));
+ let params = self.params.iter().map(|param| param.to_param(ctx, module, edition));
make::param_list(self_param, params)
}
@@ -1842,10 +1868,12 @@ fn make_body(ctx: &AssistContext<'_>, old_indent: IndentLevel, fun: &Function) -
None => match fun.outliving_locals.as_slice() {
[] => {}
[var] => {
- tail_expr = Some(path_expr_from_local(ctx, var.local));
+ tail_expr = Some(path_expr_from_local(ctx, var.local, fun.mods.edition));
}
vars => {
- let exprs = vars.iter().map(|var| path_expr_from_local(ctx, var.local));
+ let exprs = vars
+ .iter()
+ .map(|var| path_expr_from_local(ctx, var.local, fun.mods.edition));
let expr = make::expr_tuple(exprs);
tail_expr = Some(expr);
}
@@ -5622,7 +5650,7 @@ fn func<T: Debug>(i: Struct<'_, T>) {
fun_name(i);
}
-fn $0fun_name(i: Struct<T>) {
+fn $0fun_name(i: Struct<'_, T>) {
foo(i);
}
"#,
diff --git a/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs
index a62fdeb617..615b5d3f98 100644
--- a/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -1,7 +1,7 @@
use std::iter;
use either::Either;
-use hir::{Module, ModuleDef, Name, Variant};
+use hir::{HasCrate, Module, ModuleDef, Name, Variant};
use ide_db::{
defs::Definition,
helpers::mod_path_to_ast,
@@ -16,7 +16,7 @@ use syntax::{
self, edit::IndentLevel, edit_in_place::Indent, make, AstNode, HasAttrs, HasGenericParams,
HasName, HasVisibility,
},
- match_ast, ted, SyntaxElement,
+ match_ast, ted, Edition, SyntaxElement,
SyntaxKind::*,
SyntaxNode, T,
};
@@ -58,6 +58,7 @@ pub(crate) fn extract_struct_from_enum_variant(
"Extract struct from enum variant",
target,
|builder| {
+ let edition = enum_hir.krate(ctx.db()).edition(ctx.db());
let variant_hir_name = variant_hir.name(ctx.db());
let enum_module_def = ModuleDef::from(enum_hir);
let usages = Definition::Variant(variant_hir).usages(&ctx.sema).all();
@@ -82,7 +83,7 @@ pub(crate) fn extract_struct_from_enum_variant(
references,
);
processed.into_iter().for_each(|(path, node, import)| {
- apply_references(ctx.config.insert_use, path, node, import)
+ apply_references(ctx.config.insert_use, path, node, import, edition)
});
}
builder.edit_file(ctx.file_id());
@@ -98,7 +99,7 @@ pub(crate) fn extract_struct_from_enum_variant(
references,
);
processed.into_iter().for_each(|(path, node, import)| {
- apply_references(ctx.config.insert_use, path, node, import)
+ apply_references(ctx.config.insert_use, path, node, import, edition)
});
}
@@ -169,7 +170,7 @@ fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &Va
),
_ => false,
})
- .any(|(name, _)| name.display(db).to_string() == variant_name.to_string())
+ .any(|(name, _)| name.eq_ident(variant_name.text().as_str()))
}
fn extract_generic_params(
@@ -359,9 +360,10 @@ fn apply_references(
segment: ast::PathSegment,
node: SyntaxNode,
import: Option<(ImportScope, hir::ModPath)>,
+ edition: Edition,
) {
if let Some((scope, path)) = import {
- insert_use(&scope, mod_path_to_ast(&path), &insert_use_cfg);
+ insert_use(&scope, mod_path_to_ast(&path, edition), &insert_use_cfg);
}
// deep clone to prevent cycle
let path = make::path_from_segments(iter::once(segment.clone_subtree()), false);
diff --git a/crates/ide-assists/src/handlers/extract_variable.rs b/crates/ide-assists/src/handlers/extract_variable.rs
index 0ef71a3866..5ae75bb1ff 100644
--- a/crates/ide-assists/src/handlers/extract_variable.rs
+++ b/crates/ide-assists/src/handlers/extract_variable.rs
@@ -20,7 +20,7 @@ use crate::{utils::suggest_name, AssistContext, AssistId, AssistKind, Assists};
// ->
// ```
// fn main() {
-// let $0var_name = (1 + 2);
+// let $0var_name = 1 + 2;
// var_name * 4;
// }
// ```
@@ -58,9 +58,30 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
}
let parent = to_extract.syntax().parent().and_then(ast::Expr::cast);
- let needs_adjust = parent
- .as_ref()
- .map_or(false, |it| matches!(it, ast::Expr::FieldExpr(_) | ast::Expr::MethodCallExpr(_)));
+ // Any expression that autoderefs may need adjustment.
+ let mut needs_adjust = parent.as_ref().map_or(false, |it| match it {
+ ast::Expr::FieldExpr(_)
+ | ast::Expr::MethodCallExpr(_)
+ | ast::Expr::CallExpr(_)
+ | ast::Expr::AwaitExpr(_) => true,
+ ast::Expr::IndexExpr(index) if index.base().as_ref() == Some(&to_extract) => true,
+ _ => false,
+ });
+ let mut to_extract_no_ref = peel_parens(to_extract.clone());
+ let needs_ref = needs_adjust
+ && match &to_extract_no_ref {
+ ast::Expr::FieldExpr(_)
+ | ast::Expr::IndexExpr(_)
+ | ast::Expr::MacroExpr(_)
+ | ast::Expr::ParenExpr(_)
+ | ast::Expr::PathExpr(_) => true,
+ ast::Expr::PrefixExpr(prefix) if prefix.op_kind() == Some(ast::UnaryOp::Deref) => {
+ to_extract_no_ref = prefix.expr()?;
+ needs_adjust = false;
+ false
+ }
+ _ => false,
+ };
let anchor = Anchor::from(&to_extract)?;
let target = to_extract.syntax().text_range();
@@ -87,22 +108,28 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
Some(ast::Expr::RefExpr(expr)) if expr.mut_token().is_some() => {
make::ident_pat(false, true, make::name(&var_name))
}
+ _ if needs_adjust
+ && !needs_ref
+ && ty.as_ref().is_some_and(|ty| ty.is_mutable_reference()) =>
+ {
+ make::ident_pat(false, true, make::name(&var_name))
+ }
_ => make::ident_pat(false, false, make::name(&var_name)),
};
- let to_extract = match ty.as_ref().filter(|_| needs_adjust) {
+ let to_extract_no_ref = match ty.as_ref().filter(|_| needs_ref) {
Some(receiver_type) if receiver_type.is_mutable_reference() => {
- make::expr_ref(to_extract, true)
+ make::expr_ref(to_extract_no_ref, true)
}
Some(receiver_type) if receiver_type.is_reference() => {
- make::expr_ref(to_extract, false)
+ make::expr_ref(to_extract_no_ref, false)
}
- _ => to_extract,
+ _ => to_extract_no_ref,
};
let expr_replace = edit.make_syntax_mut(expr_replace);
let let_stmt =
- make::let_stmt(ident_pat.into(), None, Some(to_extract)).clone_for_update();
+ make::let_stmt(ident_pat.into(), None, Some(to_extract_no_ref)).clone_for_update();
let name_expr = make::expr_path(make::ext::ident_path(&var_name)).clone_for_update();
match anchor {
@@ -202,6 +229,14 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
)
}
+fn peel_parens(mut expr: ast::Expr) -> ast::Expr {
+ while let ast::Expr::ParenExpr(parens) = &expr {
+ let Some(expr_inside) = parens.expr() else { break };
+ expr = expr_inside;
+ }
+ expr
+}
+
/// Check whether the node is a valid expression which can be extracted to a variable.
/// In general that's true for any expression, but in some cases that would produce invalid code.
fn valid_target_expr(node: SyntaxNode) -> Option<ast::Expr> {
@@ -1221,6 +1256,45 @@ fn foo(s: &S) {
}
#[test]
+ fn test_extract_var_index_deref() {
+ check_assist(
+ extract_variable,
+ r#"
+//- minicore: index
+struct X;
+
+impl std::ops::Index<usize> for X {
+ type Output = i32;
+ fn index(&self) -> &Self::Output { 0 }
+}
+
+struct S {
+ sub: X
+}
+
+fn foo(s: &S) {
+ $0s.sub$0[0];
+}"#,
+ r#"
+struct X;
+
+impl std::ops::Index<usize> for X {
+ type Output = i32;
+ fn index(&self) -> &Self::Output { 0 }
+}
+
+struct S {
+ sub: X
+}
+
+fn foo(s: &S) {
+ let $0sub = &s.sub;
+ sub[0];
+}"#,
+ );
+ }
+
+ #[test]
fn test_extract_var_reference_parameter_deep_nesting() {
check_assist(
extract_variable,
@@ -1461,4 +1535,60 @@ fn foo() {
}"#,
);
}
+
+ #[test]
+ fn generates_no_ref_on_calls() {
+ check_assist(
+ extract_variable,
+ r#"
+struct S;
+impl S {
+ fn do_work(&mut self) {}
+}
+fn bar() -> S { S }
+fn foo() {
+ $0bar()$0.do_work();
+}"#,
+ r#"
+struct S;
+impl S {
+ fn do_work(&mut self) {}
+}
+fn bar() -> S { S }
+fn foo() {
+ let mut $0bar = bar();
+ bar.do_work();
+}"#,
+ );
+ }
+
+ #[test]
+ fn generates_no_ref_for_deref() {
+ check_assist(
+ extract_variable,
+ r#"
+struct S;
+impl S {
+ fn do_work(&mut self) {}
+}
+fn bar() -> S { S }
+fn foo() {
+ let v = &mut &mut bar();
+ $0(**v)$0.do_work();
+}
+"#,
+ r#"
+struct S;
+impl S {
+ fn do_work(&mut self) {}
+}
+fn bar() -> S { S }
+fn foo() {
+ let v = &mut &mut bar();
+ let $0s = *v;
+ s.do_work();
+}
+"#,
+ );
+ }
}
diff --git a/crates/ide-assists/src/handlers/fill_record_pattern_fields.rs b/crates/ide-assists/src/handlers/fill_record_pattern_fields.rs
index 758f50d3f4..ee32186480 100644
--- a/crates/ide-assists/src/handlers/fill_record_pattern_fields.rs
+++ b/crates/ide-assists/src/handlers/fill_record_pattern_fields.rs
@@ -45,8 +45,9 @@ pub(crate) fn fill_record_pattern_fields(acc: &mut Assists, ctx: &AssistContext<
let new_field_list =
make::record_pat_field_list(old_field_list.fields(), None).clone_for_update();
for (f, _) in missing_fields.iter() {
+ let edition = ctx.sema.scope(record_pat.syntax())?.krate().edition(ctx.db());
let field = make::record_pat_field_shorthand(make::name_ref(
- &f.name(ctx.sema.db).display_no_db().to_smolstr(),
+ &f.name(ctx.sema.db).display_no_db(edition).to_smolstr(),
));
new_field_list.add_field(field.clone_for_update());
}
diff --git a/crates/ide-assists/src/handlers/fix_visibility.rs b/crates/ide-assists/src/handlers/fix_visibility.rs
index 9950f9c147..7a92d8911b 100644
--- a/crates/ide-assists/src/handlers/fix_visibility.rs
+++ b/crates/ide-assists/src/handlers/fix_visibility.rs
@@ -4,7 +4,7 @@ use hir::{
use ide_db::FileId;
use syntax::{
ast::{self, edit_in_place::HasVisibilityEdit, make, HasVisibility as _},
- AstNode, TextRange, ToSmolStr,
+ AstNode, TextRange,
};
use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -48,7 +48,7 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext<'_>)
let (_, def) = module
.scope(ctx.db(), None)
.into_iter()
- .find(|(name, _)| name.display_no_db().to_smolstr() == name_ref.text().as_str())?;
+ .find(|(name, _)| name.eq_ident(name_ref.text().as_str()))?;
let ScopeDef::ModuleDef(def) = def else {
return None;
};
@@ -71,7 +71,10 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext<'_>)
let assist_label = match target_name {
None => format!("Change visibility to {missing_visibility}"),
Some(name) => {
- format!("Change visibility of {} to {missing_visibility}", name.display(ctx.db()))
+ format!(
+ "Change visibility of {} to {missing_visibility}",
+ name.display(ctx.db(), current_module.krate().edition(ctx.db()))
+ )
}
};
@@ -92,6 +95,7 @@ fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext<'_>
let (record_field_def, _, _) = ctx.sema.resolve_record_field(&record_field)?;
let current_module = ctx.sema.scope(record_field.syntax())?.module();
+ let current_edition = current_module.krate().edition(ctx.db());
let visibility = record_field_def.visibility(ctx.db());
if visibility.is_visible_from(ctx.db(), current_module.into()) {
return None;
@@ -123,8 +127,8 @@ fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext<'_>
let target_name = record_field_def.name(ctx.db());
let assist_label = format!(
"Change visibility of {}.{} to {missing_visibility}",
- parent_name.display(ctx.db()),
- target_name.display(ctx.db())
+ parent_name.display(ctx.db(), current_edition),
+ target_name.display(ctx.db(), current_edition)
);
acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |edit| {
diff --git a/crates/ide-assists/src/handlers/generate_delegate_methods.rs b/crates/ide-assists/src/handlers/generate_delegate_methods.rs
index 2150003bc1..081e36b4ff 100644
--- a/crates/ide-assists/src/handlers/generate_delegate_methods.rs
+++ b/crates/ide-assists/src/handlers/generate_delegate_methods.rs
@@ -51,6 +51,7 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<'
let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
let strukt_name = strukt.name()?;
let current_module = ctx.sema.scope(strukt.syntax())?.module();
+ let current_edition = current_module.krate().edition(ctx.db());
let (field_name, field_ty, target) = match ctx.find_node_at_offset::<ast::RecordField>() {
Some(field) => {
@@ -89,7 +90,7 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<'
methods.sort_by(|(a, _), (b, _)| a.cmp(b));
for (name, method) in methods {
let adt = ast::Adt::Struct(strukt.clone());
- let name = name.display(ctx.db()).to_string();
+ let name = name.display(ctx.db(), current_edition).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;
@@ -121,6 +122,7 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<'
let is_async = method_source.async_token().is_some();
let is_const = method_source.const_token().is_some();
let is_unsafe = method_source.unsafe_token().is_some();
+ let is_gen = method_source.gen_token().is_some();
let fn_name = make::name(&name);
@@ -153,6 +155,7 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<'
is_async,
is_const,
is_unsafe,
+ is_gen,
)
.clone_for_update();
diff --git a/crates/ide-assists/src/handlers/generate_delegate_trait.rs b/crates/ide-assists/src/handlers/generate_delegate_trait.rs
index 5a3457e5b7..bf4ce5c907 100644
--- a/crates/ide-assists/src/handlers/generate_delegate_trait.rs
+++ b/crates/ide-assists/src/handlers/generate_delegate_trait.rs
@@ -22,7 +22,7 @@ use syntax::{
WherePred,
},
ted::{self, Position},
- AstNode, NodeOrToken, SmolStr, SyntaxKind, ToSmolStr,
+ AstNode, Edition, NodeOrToken, SmolStr, SyntaxKind, ToSmolStr,
};
// Assist: generate_delegate_trait
@@ -109,6 +109,7 @@ struct Field {
ty: ast::Type,
range: syntax::TextRange,
impls: Vec<Delegee>,
+ edition: Edition,
}
impl Field {
@@ -119,6 +120,7 @@ impl Field {
let db = ctx.sema.db;
let module = ctx.sema.file_to_module_def(ctx.file_id())?;
+ let edition = module.krate().edition(ctx.db());
let (name, range, ty) = match f {
Either::Left(f) => {
@@ -147,7 +149,7 @@ impl Field {
}
}
- Some(Field { name, ty, range, impls })
+ Some(Field { name, ty, range, impls, edition })
}
}
@@ -163,18 +165,18 @@ enum Delegee {
}
impl Delegee {
- fn signature(&self, db: &dyn HirDatabase) -> String {
+ fn signature(&self, db: &dyn HirDatabase, edition: Edition) -> String {
let mut s = String::new();
let (Delegee::Bound(it) | Delegee::Impls(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.display_no_db().to_smolstr()));
+ s.push_str(&format!("{}::", name.display_no_db(edition).to_smolstr()));
}
}
- s.push_str(&it.name(db).display_no_db().to_smolstr());
+ s.push_str(&it.name(db).display_no_db(edition).to_smolstr());
s
}
}
@@ -212,9 +214,11 @@ impl Struct {
// if self.hir_ty.impls_trait(db, trait_, &[]) {
// continue;
// }
- let signature = delegee.signature(db);
+ let signature = delegee.signature(db, field.edition);
- let Some(delegate) = generate_impl(ctx, self, &field.ty, &field.name, delegee) else {
+ let Some(delegate) =
+ generate_impl(ctx, self, &field.ty, &field.name, delegee, field.edition)
+ else {
continue;
};
@@ -240,6 +244,7 @@ fn generate_impl(
field_ty: &ast::Type,
field_name: &str,
delegee: &Delegee,
+ edition: Edition,
) -> Option<ast::Impl> {
let delegate: ast::Impl;
let db = ctx.db();
@@ -259,7 +264,7 @@ fn generate_impl(
strukt_params.clone(),
strukt_params.map(|params| params.to_generic_args()),
delegee.is_auto(db),
- make::ty(&delegee.name(db).display_no_db().to_smolstr()),
+ make::ty(&delegee.name(db).display_no_db(edition).to_smolstr()),
strukt_ty,
bound_def.where_clause(),
ast_strukt.where_clause(),
@@ -350,7 +355,7 @@ fn generate_impl(
let type_gen_args = strukt_params.clone().map(|params| params.to_generic_args());
let path_type =
- make::ty(&trait_.name(db).display_no_db().to_smolstr()).clone_for_update();
+ make::ty(&trait_.name(db).display_no_db(edition).to_smolstr()).clone_for_update();
transform_impl(ctx, ast_strukt, &old_impl, &transform_args, path_type.syntax())?;
// 3) Generate delegate trait impl
@@ -735,6 +740,7 @@ fn func_assoc_item(
item.async_token().is_some(),
item.const_token().is_some(),
item.unsafe_token().is_some(),
+ item.gen_token().is_some(),
)
.clone_for_update();
diff --git a/crates/ide-assists/src/handlers/generate_deref.rs b/crates/ide-assists/src/handlers/generate_deref.rs
index 2ac7057fe7..e558bb6da8 100644
--- a/crates/ide-assists/src/handlers/generate_deref.rs
+++ b/crates/ide-assists/src/handlers/generate_deref.rs
@@ -4,7 +4,7 @@ use hir::{ModPath, ModuleDef};
use ide_db::{famous_defs::FamousDefs, RootDatabase};
use syntax::{
ast::{self, HasName},
- AstNode, SyntaxNode,
+ AstNode, Edition, SyntaxNode,
};
use crate::{
@@ -77,6 +77,7 @@ fn generate_record_deref(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<(
field_name.syntax(),
deref_type_to_generate,
trait_path,
+ module.krate().edition(ctx.db()),
)
},
)
@@ -117,6 +118,7 @@ fn generate_tuple_deref(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()
field_list_index,
deref_type_to_generate,
trait_path,
+ module.krate().edition(ctx.db()),
)
},
)
@@ -130,6 +132,7 @@ fn generate_edit(
field_name: impl Display,
deref_type: DerefType,
trait_path: ModPath,
+ edition: Edition,
) {
let start_offset = strukt.syntax().text_range().end();
let impl_code = match deref_type {
@@ -147,8 +150,11 @@ fn generate_edit(
),
};
let strukt_adt = ast::Adt::Struct(strukt);
- let deref_impl =
- generate_trait_impl_text(&strukt_adt, &trait_path.display(db).to_string(), &impl_code);
+ let deref_impl = generate_trait_impl_text(
+ &strukt_adt,
+ &trait_path.display(db, edition).to_string(),
+ &impl_code,
+ );
edit.insert(start_offset, deref_impl);
}
diff --git a/crates/ide-assists/src/handlers/generate_documentation_template.rs b/crates/ide-assists/src/handlers/generate_documentation_template.rs
index 51dd488454..c5c70c9f8e 100644
--- a/crates/ide-assists/src/handlers/generate_documentation_template.rs
+++ b/crates/ide-assists/src/handlers/generate_documentation_template.rs
@@ -5,7 +5,7 @@ use stdx::{format_to, to_lower_snake_case};
use syntax::{
algo::skip_whitespace_token,
ast::{self, edit::IndentLevel, HasDocComments, HasGenericArgs, HasName},
- match_ast, AstNode, AstToken,
+ match_ast, AstNode, AstToken, Edition,
};
use crate::assist_context::{AssistContext, Assists};
@@ -139,7 +139,8 @@ fn make_example_for_fn(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> Option<St
let mut example = String::new();
- let use_path = build_path(ast_func, ctx)?;
+ let edition = ctx.sema.scope(ast_func.syntax())?.krate().edition(ctx.db());
+ let use_path = build_path(ast_func, ctx, edition)?;
let is_unsafe = ast_func.unsafe_token().is_some();
let param_list = ast_func.param_list()?;
let ref_mut_params = ref_mut_params(&param_list);
@@ -472,13 +473,13 @@ fn string_vec_from(string_array: &[&str]) -> Vec<String> {
}
/// Helper function to build the path of the module in the which is the node
-fn build_path(ast_func: &ast::Fn, ctx: &AssistContext<'_>) -> Option<String> {
+fn build_path(ast_func: &ast::Fn, ctx: &AssistContext<'_>, edition: Edition) -> Option<String> {
let crate_name = crate_name(ast_func, ctx)?;
let leaf = self_partial_type(ast_func)
.or_else(|| ast_func.name().map(|n| n.to_string()))
.unwrap_or_else(|| "*".into());
let module_def: ModuleDef = ctx.sema.to_def(ast_func)?.module(ctx.db()).into();
- match module_def.canonical_path(ctx.db()) {
+ match module_def.canonical_path(ctx.db(), edition) {
Some(path) => Some(format!("{crate_name}::{path}::{leaf}")),
None => Some(format!("{crate_name}::{leaf}")),
}
diff --git a/crates/ide-assists/src/handlers/generate_function.rs b/crates/ide-assists/src/handlers/generate_function.rs
index b2980d5c63..76a647807c 100644
--- a/crates/ide-assists/src/handlers/generate_function.rs
+++ b/crates/ide-assists/src/handlers/generate_function.rs
@@ -17,7 +17,7 @@ use syntax::{
self, edit::IndentLevel, edit_in_place::Indent, make, AstNode, BlockExpr, CallExpr,
HasArgList, HasGenericParams, HasModuleItem, HasTypeBounds,
},
- ted, SyntaxKind, SyntaxNode, TextRange, T,
+ ted, Edition, SyntaxKind, SyntaxNode, TextRange, T,
};
use crate::{
@@ -175,6 +175,7 @@ fn add_func_to_accumulator(
edit.edit_file(file);
let target = function_builder.target.clone();
+ let edition = function_builder.target_edition;
let func = function_builder.render(ctx.config.snippet_cap, edit);
if let Some(adt) =
@@ -183,7 +184,7 @@ fn add_func_to_accumulator(
{
let name = make::ty_path(make::ext::ident_path(&format!(
"{}",
- adt.name(ctx.db()).display(ctx.db())
+ adt.name(ctx.db()).display(ctx.db(), edition)
)));
// FIXME: adt may have generic params.
@@ -222,6 +223,7 @@ struct FunctionBuilder {
should_focus_return_type: bool,
visibility: Visibility,
is_async: bool,
+ target_edition: Edition,
}
impl FunctionBuilder {
@@ -237,6 +239,7 @@ impl FunctionBuilder {
) -> Option<Self> {
let target_module =
target_module.or_else(|| ctx.sema.scope(target.syntax()).map(|it| it.module()))?;
+ let target_edition = target_module.krate().edition(ctx.db());
let current_module = ctx.sema.scope(call.syntax())?.module();
let visibility = calculate_necessary_visibility(current_module, target_module, ctx);
@@ -258,7 +261,9 @@ impl FunctionBuilder {
// If generated function has the name "new" and is an associated function, we generate fn body
// as a constructor and assume a "Self" return type.
- if let Some(body) = make_fn_body_as_new_function(ctx, &fn_name.text(), adt_info) {
+ if let Some(body) =
+ make_fn_body_as_new_function(ctx, &fn_name.text(), adt_info, target_edition)
+ {
ret_type = Some(make::ret_type(make::ty_path(make::ext::ident_path("Self"))));
should_focus_return_type = false;
fn_body = body;
@@ -288,6 +293,7 @@ impl FunctionBuilder {
should_focus_return_type,
visibility,
is_async,
+ target_edition,
})
}
@@ -299,6 +305,8 @@ impl FunctionBuilder {
target_module: Module,
target: GeneratedFunctionTarget,
) -> Option<Self> {
+ let target_edition = target_module.krate().edition(ctx.db());
+
let current_module = ctx.sema.scope(call.syntax())?.module();
let visibility = calculate_necessary_visibility(current_module, target_module, ctx);
@@ -336,6 +344,7 @@ impl FunctionBuilder {
should_focus_return_type,
visibility,
is_async,
+ target_edition,
})
}
@@ -356,6 +365,7 @@ impl FunctionBuilder {
self.is_async,
false, // FIXME : const and unsafe are not handled yet.
false,
+ false,
)
.clone_for_update();
@@ -425,6 +435,7 @@ fn make_fn_body_as_new_function(
ctx: &AssistContext<'_>,
fn_name: &str,
adt_info: &Option<AdtInfo>,
+ edition: Edition,
) -> Option<ast::BlockExpr> {
if fn_name != "new" {
return None;
@@ -441,7 +452,10 @@ fn make_fn_body_as_new_function(
.iter()
.map(|field| {
make::record_expr_field(
- make::name_ref(&format!("{}", field.name(ctx.db()).display(ctx.db()))),
+ make::name_ref(&format!(
+ "{}",
+ field.name(ctx.db()).display(ctx.db(), edition)
+ )),
Some(placeholder_expr.clone()),
)
})
@@ -1102,8 +1116,9 @@ fn fn_arg_type(
if ty.is_reference() || ty.is_mutable_reference() {
let famous_defs = &FamousDefs(&ctx.sema, ctx.sema.scope(fn_arg.syntax())?.krate());
+ let target_edition = target_module.krate().edition(ctx.db());
convert_reference_type(ty.strip_references(), ctx.db(), famous_defs)
- .map(|conversion| conversion.convert_type(ctx.db()).to_string())
+ .map(|conversion| conversion.convert_type(ctx.db(), target_edition).to_string())
.or_else(|| ty.display_source_code(ctx.db(), target_module.into(), true).ok())
} else {
ty.display_source_code(ctx.db(), target_module.into(), true).ok()
diff --git a/crates/ide-assists/src/handlers/generate_getter_or_setter.rs b/crates/ide-assists/src/handlers/generate_getter_or_setter.rs
index 60214aaaf6..c879a4a3d9 100644
--- a/crates/ide-assists/src/handlers/generate_getter_or_setter.rs
+++ b/crates/ide-assists/src/handlers/generate_getter_or_setter.rs
@@ -233,7 +233,7 @@ fn generate_getter_from_info(
.map(|conversion| {
cov_mark::hit!(convert_reference_type);
(
- conversion.convert_type(ctx.db()),
+ conversion.convert_type(ctx.db(), krate.edition(ctx.db())),
conversion.getter(record_field_info.field_name.to_string()),
)
})
@@ -261,7 +261,19 @@ fn generate_getter_from_info(
let ret_type = Some(make::ret_type(ty));
let body = make::block_expr([], Some(body));
- make::fn_(strukt.visibility(), fn_name, None, None, params, body, ret_type, false, false, false)
+ make::fn_(
+ strukt.visibility(),
+ fn_name,
+ None,
+ None,
+ params,
+ body,
+ ret_type,
+ false,
+ false,
+ false,
+ false,
+ )
}
fn generate_setter_from_info(info: &AssistInfo, record_field_info: &RecordFieldInfo) -> ast::Fn {
@@ -285,7 +297,19 @@ fn generate_setter_from_info(info: &AssistInfo, record_field_info: &RecordFieldI
let body = make::block_expr([assign_stmt.into()], None);
// Make the setter fn
- make::fn_(strukt.visibility(), fn_name, None, None, params, body, None, false, false, false)
+ make::fn_(
+ strukt.visibility(),
+ fn_name,
+ None,
+ None,
+ params,
+ body,
+ None,
+ false,
+ false,
+ false,
+ false,
+ )
}
fn extract_and_parse(
diff --git a/crates/ide-assists/src/handlers/generate_new.rs b/crates/ide-assists/src/handlers/generate_new.rs
index b985b5e66c..70d14d6b95 100644
--- a/crates/ide-assists/src/handlers/generate_new.rs
+++ b/crates/ide-assists/src/handlers/generate_new.rs
@@ -64,10 +64,13 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
ctx.config.import_path_config(),
)?;
+ let edition = current_module.krate().edition(ctx.db());
+
let expr = use_trivial_constructor(
ctx.sema.db,
- ide_db::helpers::mod_path_to_ast(&type_path),
+ ide_db::helpers::mod_path_to_ast(&type_path, edition),
&ty,
+ edition,
)?;
Some(make::record_expr_field(make::name_ref(&name.text()), Some(expr)))
@@ -112,6 +115,7 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
false,
false,
false,
+ false,
)
.clone_for_update();
fn_.indent(1.into());
diff --git a/crates/ide-assists/src/handlers/inline_const_as_literal.rs b/crates/ide-assists/src/handlers/inline_const_as_literal.rs
index 111ea50fdc..f1c2acdd3e 100644
--- a/crates/ide-assists/src/handlers/inline_const_as_literal.rs
+++ b/crates/ide-assists/src/handlers/inline_const_as_literal.rs
@@ -51,10 +51,13 @@ pub(crate) fn inline_const_as_literal(acc: &mut Assists, ctx: &AssistContext<'_>
| ast::Expr::MatchExpr(_)
| ast::Expr::MacroExpr(_)
| ast::Expr::BinExpr(_)
- | ast::Expr::CallExpr(_) => match konst.render_eval(ctx.sema.db) {
- Ok(result) => result,
- Err(_) => return None,
- },
+ | ast::Expr::CallExpr(_) => {
+ let edition = ctx.sema.scope(variable.syntax())?.krate().edition(ctx.db());
+ match konst.render_eval(ctx.sema.db, edition) {
+ Ok(result) => result,
+ Err(_) => return None,
+ }
+ }
_ => return None,
};
diff --git a/crates/ide-assists/src/handlers/move_const_to_impl.rs b/crates/ide-assists/src/handlers/move_const_to_impl.rs
index 3057745a97..743ea94761 100644
--- a/crates/ide-assists/src/handlers/move_const_to_impl.rs
+++ b/crates/ide-assists/src/handlers/move_const_to_impl.rs
@@ -104,9 +104,13 @@ 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.file_ranges().map(|it| it.range) {
- builder.replace(range, const_ref.clone());
+ let usages = usages.iter().flat_map(|(file_id, usages)| {
+ let edition = file_id.edition();
+ usages.iter().map(move |usage| (edition, usage.range))
+ });
+ for (edition, range) in usages {
+ let const_ref = format!("Self::{}", name.display(ctx.db(), edition));
+ builder.replace(range, const_ref);
}
// Heuristically inserting the extracted const after the consecutive existing consts
diff --git a/crates/ide-assists/src/handlers/move_from_mod_rs.rs b/crates/ide-assists/src/handlers/move_from_mod_rs.rs
index 14381085a7..8a7a06b380 100644
--- a/crates/ide-assists/src/handlers/move_from_mod_rs.rs
+++ b/crates/ide-assists/src/handlers/move_from_mod_rs.rs
@@ -39,7 +39,7 @@ pub(crate) fn move_from_mod_rs(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
}
let target = source_file.syntax().text_range();
- let module_name = module.name(ctx.db())?.display(ctx.db()).to_string();
+ let module_name = module.name(ctx.db())?.unescaped().display(ctx.db()).to_string();
let path = format!("../{module_name}.rs");
let dst = AnchoredPathBuf { anchor: ctx.file_id().into(), path };
acc.add(
diff --git a/crates/ide-assists/src/handlers/move_module_to_file.rs b/crates/ide-assists/src/handlers/move_module_to_file.rs
index e679a68f44..9692b70592 100644
--- a/crates/ide-assists/src/handlers/move_module_to_file.rs
+++ b/crates/ide-assists/src/handlers/move_module_to_file.rs
@@ -61,7 +61,7 @@ pub(crate) fn move_module_to_file(acc: &mut Assists, ctx: &AssistContext<'_>) ->
.string_value_unescape()
.is_none() =>
{
- format_to!(buf, "{}/", name.display(db))
+ format_to!(buf, "{}/", name.unescaped().display(db))
}
_ => (),
}
diff --git a/crates/ide-assists/src/handlers/move_to_mod_rs.rs b/crates/ide-assists/src/handlers/move_to_mod_rs.rs
index c89d54ff03..2925e2334b 100644
--- a/crates/ide-assists/src/handlers/move_to_mod_rs.rs
+++ b/crates/ide-assists/src/handlers/move_to_mod_rs.rs
@@ -39,7 +39,7 @@ pub(crate) fn move_to_mod_rs(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
}
let target = source_file.syntax().text_range();
- let module_name = module.name(ctx.db())?.display(ctx.db()).to_string();
+ let module_name = module.name(ctx.db())?.unescaped().display(ctx.db()).to_string();
let path = format!("./{module_name}/mod.rs");
let dst = AnchoredPathBuf { anchor: ctx.file_id().into(), path };
acc.add(
diff --git a/crates/ide-assists/src/handlers/qualify_method_call.rs b/crates/ide-assists/src/handlers/qualify_method_call.rs
index b1e98045fc..14518c4d2c 100644
--- a/crates/ide-assists/src/handlers/qualify_method_call.rs
+++ b/crates/ide-assists/src/handlers/qualify_method_call.rs
@@ -42,6 +42,7 @@ pub(crate) fn qualify_method_call(acc: &mut Assists, ctx: &AssistContext<'_>) ->
let resolved_call = ctx.sema.resolve_method_call(&call)?;
let current_module = ctx.sema.scope(call.syntax())?.module();
+ let current_edition = current_module.krate().edition(ctx.db());
let target_module_def = ModuleDef::from(resolved_call);
let item_in_ns = ItemInNs::from(target_module_def);
let receiver_path = current_module.find_path(
@@ -61,6 +62,7 @@ pub(crate) fn qualify_method_call(acc: &mut Assists, ctx: &AssistContext<'_>) ->
|replace_with: String| builder.replace(range, replace_with),
&receiver_path,
item_in_ns,
+ current_edition,
)
},
);
diff --git a/crates/ide-assists/src/handlers/qualify_path.rs b/crates/ide-assists/src/handlers/qualify_path.rs
index d8e7da15d5..ac88861fe4 100644
--- a/crates/ide-assists/src/handlers/qualify_path.rs
+++ b/crates/ide-assists/src/handlers/qualify_path.rs
@@ -8,6 +8,7 @@ use ide_db::{
imports::import_assets::{ImportCandidate, LocatedImport},
};
use syntax::ast::HasGenericArgs;
+use syntax::Edition;
use syntax::{
ast,
ast::{make, HasArgList},
@@ -93,6 +94,8 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
NodeOrToken::Token(t) => t.parent()?,
})
.map(|scope| scope.module());
+ let current_edition =
+ current_module.map(|it| it.krate().edition(ctx.db())).unwrap_or(Edition::CURRENT);
// prioritize more relevant imports
proposed_imports.sort_by_key(|import| {
Reverse(super::auto_import::relevance_score(ctx, import, current_module.as_ref()))
@@ -103,13 +106,14 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
acc.add_group(
&group_label,
AssistId("qualify_path", AssistKind::QuickFix),
- label(ctx.db(), candidate, &import),
+ label(ctx.db(), candidate, &import, current_edition),
range,
|builder| {
qualify_candidate.qualify(
|replace_with: String| builder.replace(range, replace_with),
&import.import_path,
import.item_to_import,
+ current_edition,
)
},
);
@@ -130,8 +134,9 @@ impl QualifyCandidate<'_> {
mut replacer: impl FnMut(String),
import: &hir::ModPath,
item: hir::ItemInNs,
+ edition: Edition,
) {
- let import = mod_path_to_ast(import);
+ let import = mod_path_to_ast(import, edition);
match self {
QualifyCandidate::QualifierStart(segment, generics) => {
let generics = generics.as_ref().map_or_else(String::new, ToString::to_string);
@@ -203,7 +208,7 @@ fn find_trait_method(
if let Some(hir::AssocItem::Function(method)) =
trait_.items(db).into_iter().find(|item: &hir::AssocItem| {
item.name(db)
- .map(|name| name.display(db).to_string() == trait_method_name.to_string())
+ .map(|name| name.eq_ident(trait_method_name.text().as_str()))
.unwrap_or(false)
})
{
@@ -233,14 +238,19 @@ fn group_label(candidate: &ImportCandidate) -> GroupLabel {
GroupLabel(format!("Qualify {name}"))
}
-fn label(db: &RootDatabase, candidate: &ImportCandidate, import: &LocatedImport) -> String {
+fn label(
+ db: &RootDatabase,
+ candidate: &ImportCandidate,
+ import: &LocatedImport,
+ edition: Edition,
+) -> String {
let import_path = &import.import_path;
match candidate {
ImportCandidate::Path(candidate) if candidate.qualifier.is_none() => {
- format!("Qualify as `{}`", import_path.display(db))
+ format!("Qualify as `{}`", import_path.display(db, edition))
}
- _ => format!("Qualify with `{}`", import_path.display(db)),
+ _ => format!("Qualify with `{}`", import_path.display(db, edition)),
}
}
diff --git a/crates/ide-assists/src/handlers/remove_dbg.rs b/crates/ide-assists/src/handlers/remove_dbg.rs
index cffa3f55c9..0e9c463e02 100644
--- a/crates/ide-assists/src/handlers/remove_dbg.rs
+++ b/crates/ide-assists/src/handlers/remove_dbg.rs
@@ -1,7 +1,7 @@
use itertools::Itertools;
use syntax::{
ast::{self, make, AstNode, AstToken},
- match_ast, ted, NodeOrToken, SyntaxElement, TextRange, TextSize, T,
+ match_ast, ted, Edition, NodeOrToken, SyntaxElement, TextRange, TextSize, T,
};
use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -77,7 +77,7 @@ fn compute_dbg_replacement(macro_expr: ast::MacroExpr) -> Option<(TextRange, Opt
let input_expressions = input_expressions
.into_iter()
.filter_map(|(is_sep, group)| (!is_sep).then_some(group))
- .map(|mut tokens| syntax::hacks::parse_expr_from_str(&tokens.join("")))
+ .map(|mut tokens| syntax::hacks::parse_expr_from_str(&tokens.join(""), Edition::CURRENT))
.collect::<Option<Vec<ast::Expr>>>()?;
let parent = macro_expr.syntax().parent()?;
diff --git a/crates/ide-assists/src/handlers/reorder_fields.rs b/crates/ide-assists/src/handlers/reorder_fields.rs
index 0256256697..df7a5112f1 100644
--- a/crates/ide-assists/src/handlers/reorder_fields.rs
+++ b/crates/ide-assists/src/handlers/reorder_fields.rs
@@ -1,7 +1,7 @@
use either::Either;
use ide_db::FxHashMap;
use itertools::Itertools;
-use syntax::{ast, ted, AstNode};
+use syntax::{ast, ted, AstNode, SmolStr, ToSmolStr};
use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -25,8 +25,9 @@ pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
path.syntax().parent().and_then(<Either<ast::RecordExpr, ast::RecordPat>>::cast)?;
let ranks = compute_fields_ranks(&path, ctx)?;
- let get_rank_of_field =
- |of: Option<_>| *ranks.get(&of.unwrap_or_default()).unwrap_or(&usize::MAX);
+ let get_rank_of_field = |of: Option<SmolStr>| {
+ *ranks.get(of.unwrap_or_default().trim_start_matches("r#")).unwrap_or(&usize::MAX)
+ };
let field_list = match &record {
Either::Left(it) => Either::Left(it.record_expr_field_list()?),
@@ -36,7 +37,7 @@ pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
Either::Left(it) => Either::Left((
it.fields()
.sorted_unstable_by_key(|field| {
- get_rank_of_field(field.field_name().map(|it| it.to_string()))
+ get_rank_of_field(field.field_name().map(|it| it.to_smolstr()))
})
.collect::<Vec<_>>(),
it,
@@ -44,7 +45,7 @@ pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
Either::Right(it) => Either::Right((
it.fields()
.sorted_unstable_by_key(|field| {
- get_rank_of_field(field.field_name().map(|it| it.to_string()))
+ get_rank_of_field(field.field_name().map(|it| it.to_smolstr()))
})
.collect::<Vec<_>>(),
it,
@@ -97,7 +98,7 @@ fn compute_fields_ranks(
.fields(ctx.db())
.into_iter()
.enumerate()
- .map(|(idx, field)| (field.name(ctx.db()).display(ctx.db()).to_string(), idx))
+ .map(|(idx, field)| (field.name(ctx.db()).unescaped().display(ctx.db()).to_string(), idx))
.collect();
Some(res)
diff --git a/crates/ide-assists/src/handlers/reorder_impl_items.rs b/crates/ide-assists/src/handlers/reorder_impl_items.rs
index cf135f83e7..ada89ce7c4 100644
--- a/crates/ide-assists/src/handlers/reorder_impl_items.rs
+++ b/crates/ide-assists/src/handlers/reorder_impl_items.rs
@@ -77,7 +77,8 @@ pub(crate) fn reorder_impl_items(acc: &mut Assists, ctx: &AssistContext<'_>) ->
ast::AssocItem::MacroCall(_) => None,
};
- name.and_then(|n| ranks.get(&n.to_string()).copied()).unwrap_or(usize::MAX)
+ name.and_then(|n| ranks.get(n.text().as_str().trim_start_matches("r#")).copied())
+ .unwrap_or(usize::MAX)
})
.collect();
@@ -114,7 +115,7 @@ fn compute_item_ranks(
.iter()
.flat_map(|i| i.name(ctx.db()))
.enumerate()
- .map(|(idx, name)| (name.display(ctx.db()).to_string(), idx))
+ .map(|(idx, name)| (name.unescaped().display(ctx.db()).to_string(), idx))
.collect(),
)
}
diff --git a/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
index 5ff4af19fb..248f18789c 100644
--- a/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
+++ b/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
@@ -70,6 +70,7 @@ pub(crate) fn replace_derive_with_manual_impl(
let current_module = ctx.sema.scope(adt.syntax())?.module();
let current_crate = current_module.krate();
+ let current_edition = current_crate.edition(ctx.db());
let found_traits = items_locator::items_with_name(
&ctx.sema,
@@ -85,7 +86,7 @@ pub(crate) fn replace_derive_with_manual_impl(
current_module
.find_path(ctx.sema.db, hir::ModuleDef::Trait(trait_), ctx.config.import_path_config())
.as_ref()
- .map(mod_path_to_ast)
+ .map(|path| mod_path_to_ast(path, current_edition))
.zip(Some(trait_))
});
@@ -214,7 +215,7 @@ fn impl_def_from_trait(
let impl_def = generate_trait_impl(adt, make::ty_path(trait_path.clone()));
let first_assoc_item =
- add_trait_assoc_items_to_impl(sema, &trait_items, trait_, &impl_def, target_scope);
+ add_trait_assoc_items_to_impl(sema, &trait_items, trait_, &impl_def, &target_scope);
// Generate a default `impl` function body for the derived trait.
if let ast::AssocItem::Fn(ref func) = first_assoc_item {
diff --git a/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs b/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs
index d0aa835e79..65330b34c4 100644
--- a/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs
+++ b/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs
@@ -5,7 +5,7 @@ use ide_db::{
};
use syntax::{
ast::{self, make, HasGenericArgs},
- match_ast, ted, AstNode, SyntaxNode,
+ match_ast, ted, AstNode, Edition, SyntaxNode,
};
use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -29,32 +29,32 @@ pub(crate) fn replace_qualified_name_with_use(
acc: &mut Assists,
ctx: &AssistContext<'_>,
) -> Option<()> {
- let path: ast::Path = ctx.find_node_at_offset()?;
+ let original_path: ast::Path = ctx.find_node_at_offset()?;
// We don't want to mess with use statements
- if path.syntax().ancestors().find_map(ast::UseTree::cast).is_some() {
+ if original_path.syntax().ancestors().find_map(ast::UseTree::cast).is_some() {
cov_mark::hit!(not_applicable_in_use);
return None;
}
- if path.qualifier().is_none() {
+ if original_path.qualifier().is_none() {
cov_mark::hit!(dont_import_trivial_paths);
return None;
}
// only offer replacement for non assoc items
- match ctx.sema.resolve_path(&path)? {
+ match ctx.sema.resolve_path(&original_path)? {
hir::PathResolution::Def(def) if def.as_assoc_item(ctx.sema.db).is_none() => (),
_ => return None,
}
// then search for an import for the first path segment of what we want to replace
// that way it is less likely that we import the item from a different location due re-exports
- let module = match ctx.sema.resolve_path(&path.first_qualifier_or_self())? {
+ let module = match ctx.sema.resolve_path(&original_path.first_qualifier_or_self())? {
hir::PathResolution::Def(module @ hir::ModuleDef::Module(_)) => module,
_ => return None,
};
let starts_with_name_ref = !matches!(
- path.first_segment().and_then(|it| it.kind()),
+ original_path.first_segment().and_then(|it| it.kind()),
Some(
ast::PathSegmentKind::CrateKw
| ast::PathSegmentKind::SuperKw
@@ -63,7 +63,7 @@ pub(crate) fn replace_qualified_name_with_use(
);
let path_to_qualifier = starts_with_name_ref
.then(|| {
- ctx.sema.scope(path.syntax())?.module().find_use_path(
+ ctx.sema.scope(original_path.syntax())?.module().find_use_path(
ctx.sema.db,
module,
ctx.config.insert_use.prefix_kind,
@@ -72,8 +72,8 @@ pub(crate) fn replace_qualified_name_with_use(
})
.flatten();
- let scope = ImportScope::find_insert_use_container(path.syntax(), &ctx.sema)?;
- let target = path.syntax().text_range();
+ let scope = ImportScope::find_insert_use_container(original_path.syntax(), &ctx.sema)?;
+ let target = original_path.syntax().text_range();
acc.add(
AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite),
"Replace qualified path with use",
@@ -86,13 +86,19 @@ pub(crate) fn replace_qualified_name_with_use(
ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)),
ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)),
};
- shorten_paths(scope.as_syntax_node(), &path);
- let path = drop_generic_args(&path);
+ shorten_paths(scope.as_syntax_node(), &original_path);
+ let path = drop_generic_args(&original_path);
+ let edition = ctx
+ .sema
+ .scope(original_path.syntax())
+ .map(|semantics_scope| semantics_scope.krate().edition(ctx.db()))
+ .unwrap_or(Edition::CURRENT);
// stick the found import in front of the to be replaced path
- let path = match path_to_qualifier.and_then(|it| mod_path_to_ast(&it).qualifier()) {
- Some(qualifier) => make::path_concat(qualifier, path),
- None => path,
- };
+ let path =
+ match path_to_qualifier.and_then(|it| mod_path_to_ast(&it, edition).qualifier()) {
+ Some(qualifier) => make::path_concat(qualifier, path),
+ None => path,
+ };
insert_use(&scope, path, &ctx.config.insert_use);
},
)
diff --git a/crates/ide-assists/src/handlers/term_search.rs b/crates/ide-assists/src/handlers/term_search.rs
index 4913cfdea9..66671c934c 100644
--- a/crates/ide-assists/src/handlers/term_search.rs
+++ b/crates/ide-assists/src/handlers/term_search.rs
@@ -48,15 +48,17 @@ pub(crate) fn term_search(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
let mut formatter = |_: &hir::Type| String::from("todo!()");
+ let edition = scope.krate().edition(ctx.db());
let paths = paths
.into_iter()
.filter_map(|path| {
- path.gen_source_code(&scope, &mut formatter, ctx.config.import_path_config()).ok()
+ path.gen_source_code(&scope, &mut formatter, ctx.config.import_path_config(), edition)
+ .ok()
})
.unique();
let macro_name = macro_call.name(ctx.sema.db);
- let macro_name = macro_name.display(ctx.sema.db);
+ let macro_name = macro_name.display(ctx.sema.db, edition);
for code in paths {
acc.add_group(
diff --git a/crates/ide-assists/src/handlers/toggle_async_sugar.rs b/crates/ide-assists/src/handlers/toggle_async_sugar.rs
index 98975a324d..9ab36bf775 100644
--- a/crates/ide-assists/src/handlers/toggle_async_sugar.rs
+++ b/crates/ide-assists/src/handlers/toggle_async_sugar.rs
@@ -141,7 +141,8 @@ pub(crate) fn desugar_async_into_impl_future(
ModuleDef::Trait(future_trait),
ctx.config.import_path_config(),
)?;
- let trait_path = trait_path.display(ctx.db());
+ let edition = scope.krate().edition(ctx.db());
+ let trait_path = trait_path.display(ctx.db(), edition);
acc.add(
AssistId("desugar_async_into_impl_future", AssistKind::RefactorRewrite),
diff --git a/crates/ide-assists/src/handlers/toggle_macro_delimiter.rs b/crates/ide-assists/src/handlers/toggle_macro_delimiter.rs
new file mode 100644
index 0000000000..eedb2ea3b9
--- /dev/null
+++ b/crates/ide-assists/src/handlers/toggle_macro_delimiter.rs
@@ -0,0 +1,256 @@
+use ide_db::assists::{AssistId, AssistKind};
+use syntax::{
+ ast::{self, make},
+ ted, AstNode, T,
+};
+
+use crate::{AssistContext, Assists};
+
+// Assist: toggle_macro_delimiter
+//
+// Change macro delimiters in the order of `( -> { -> [ -> (`.
+//
+// ```
+// macro_rules! sth {
+// () => {};
+// }
+//
+// sth!$0( );
+// ```
+// ->
+// ```
+// macro_rules! sth {
+// () => {};
+// }
+//
+// sth!{ }
+// ```
+pub(crate) fn toggle_macro_delimiter(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ #[derive(Debug)]
+ enum MacroDelims {
+ LPar,
+ RPar,
+ LBra,
+ RBra,
+ LCur,
+ RCur,
+ }
+
+ let makro = ctx.find_node_at_offset::<ast::MacroCall>()?.clone_for_update();
+ let makro_text_range = makro.syntax().text_range();
+
+ let cursor_offset = ctx.offset();
+ let semicolon = makro.semicolon_token();
+ let token_tree = makro.token_tree()?;
+
+ let ltoken = token_tree.left_delimiter_token()?;
+ let rtoken = token_tree.right_delimiter_token()?;
+
+ if !ltoken.text_range().contains(cursor_offset) && !rtoken.text_range().contains(cursor_offset)
+ {
+ return None;
+ }
+
+ let token = match ltoken.kind() {
+ T!['{'] => MacroDelims::LCur,
+ T!['('] => MacroDelims::LPar,
+ T!['['] => MacroDelims::LBra,
+ T!['}'] => MacroDelims::RBra,
+ T![')'] => MacroDelims::RPar,
+ T!['}'] => MacroDelims::RCur,
+ _ => return None,
+ };
+
+ acc.add(
+ AssistId("toggle_macro_delimiter", AssistKind::Refactor),
+ match token {
+ MacroDelims::LPar => "Replace delimiters with braces",
+ MacroDelims::RPar => "Replace delimiters with braces",
+ MacroDelims::LBra => "Replace delimiters with parentheses",
+ MacroDelims::RBra => "Replace delimiters with parentheses",
+ MacroDelims::LCur => "Replace delimiters with brackets",
+ MacroDelims::RCur => "Replace delimiters with brackets",
+ },
+ token_tree.syntax().text_range(),
+ |builder| {
+ match token {
+ MacroDelims::LPar | MacroDelims::RPar => {
+ ted::replace(ltoken, make::token(T!['{']));
+ ted::replace(rtoken, make::token(T!['}']));
+ if let Some(sc) = semicolon {
+ ted::remove(sc);
+ }
+ }
+ MacroDelims::LBra | MacroDelims::RBra => {
+ ted::replace(ltoken, make::token(T!['(']));
+ ted::replace(rtoken, make::token(T![')']));
+ }
+ MacroDelims::LCur | MacroDelims::RCur => {
+ ted::replace(ltoken, make::token(T!['[']));
+ ted::replace(rtoken, make::token(T![']']));
+ }
+ }
+ builder.replace(makro_text_range, makro.syntax().text());
+ },
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn test_par() {
+ check_assist(
+ toggle_macro_delimiter,
+ r#"
+macro_rules! sth {
+ () => {};
+}
+
+sth!$0( );
+ "#,
+ r#"
+macro_rules! sth {
+ () => {};
+}
+
+sth!{ }
+ "#,
+ )
+ }
+
+ #[test]
+ fn test_braces() {
+ check_assist(
+ toggle_macro_delimiter,
+ r#"
+macro_rules! sth {
+ () => {};
+}
+
+sth!$0{ };
+ "#,
+ r#"
+macro_rules! sth {
+ () => {};
+}
+
+sth![ ];
+ "#,
+ )
+ }
+
+ #[test]
+ fn test_brackets() {
+ check_assist(
+ toggle_macro_delimiter,
+ r#"
+macro_rules! sth {
+ () => {};
+}
+
+sth!$0[ ];
+ "#,
+ r#"
+macro_rules! sth {
+ () => {};
+}
+
+sth!( );
+ "#,
+ )
+ }
+
+ #[test]
+ fn test_indent() {
+ check_assist(
+ toggle_macro_delimiter,
+ r#"
+mod abc {
+ macro_rules! sth {
+ () => {};
+ }
+
+ sth!$0{ };
+}
+ "#,
+ r#"
+mod abc {
+ macro_rules! sth {
+ () => {};
+ }
+
+ sth![ ];
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn test_unrelated_par() {
+ check_assist_not_applicable(
+ toggle_macro_delimiter,
+ r#"
+macro_rules! prt {
+ ($e:expr) => {{
+ println!("{}", stringify!{$e});
+ }};
+}
+
+prt!(($03 + 5));
+
+ "#,
+ )
+ }
+
+ #[test]
+ fn test_longer_macros() {
+ check_assist(
+ toggle_macro_delimiter,
+ r#"
+macro_rules! prt {
+ ($e:expr) => {{
+ println!("{}", stringify!{$e});
+ }};
+}
+
+prt!$0((3 + 5));
+"#,
+ r#"
+macro_rules! prt {
+ ($e:expr) => {{
+ println!("{}", stringify!{$e});
+ }};
+}
+
+prt!{(3 + 5)}
+"#,
+ )
+ }
+
+ // FIXME @alibektas : Inner macro_call is not seen as such. So this doesn't work.
+ #[test]
+ fn test_nested_macros() {
+ check_assist_not_applicable(
+ toggle_macro_delimiter,
+ r#"
+macro_rules! prt {
+ ($e:expr) => {{
+ println!("{}", stringify!{$e});
+ }};
+}
+
+macro_rules! abc {
+ ($e:expr) => {{
+ println!("{}", stringify!{$e});
+ }};
+}
+
+prt!{abc!($03 + 5)};
+"#,
+ )
+ }
+}
diff --git a/crates/ide-assists/src/handlers/unwrap_block.rs b/crates/ide-assists/src/handlers/unwrap_block.rs
index de801279a0..f3e7f5f416 100644
--- a/crates/ide-assists/src/handlers/unwrap_block.rs
+++ b/crates/ide-assists/src/handlers/unwrap_block.rs
@@ -128,10 +128,12 @@ fn update_expr_string_without_newline(expr_string: String) -> String {
}
fn update_expr_string_with_pat(expr_str: String, whitespace_pat: &[char]) -> String {
- // Remove leading whitespace, index [1..] to remove the leading '{',
+ // Remove leading whitespace, index to remove the leading '{',
// then continue to remove leading whitespace.
- let expr_str =
- expr_str.trim_start_matches(whitespace_pat)[1..].trim_start_matches(whitespace_pat);
+ // We cannot assume the `{` is the first character because there are block modifiers
+ // (`unsafe`, `async` etc.).
+ let after_open_brace_index = expr_str.find('{').map_or(0, |it| it + 1);
+ let expr_str = expr_str[after_open_brace_index..].trim_start_matches(whitespace_pat);
// Remove trailing whitespace, index [..expr_str.len() - 1] to remove the trailing '}',
// then continue to remove trailing whitespace.
@@ -827,4 +829,54 @@ fn main() {
"#,
);
}
+
+ #[test]
+ fn unwrap_block_with_modifiers() {
+ // https://github.com/rust-lang/rust-analyzer/issues/17964
+ check_assist(
+ unwrap_block,
+ r#"
+fn main() {
+ unsafe $0{
+ bar;
+ }
+}
+"#,
+ r#"
+fn main() {
+ bar;
+}
+"#,
+ );
+ check_assist(
+ unwrap_block,
+ r#"
+fn main() {
+ async move $0{
+ bar;
+ }
+}
+"#,
+ r#"
+fn main() {
+ bar;
+}
+"#,
+ );
+ check_assist(
+ unwrap_block,
+ r#"
+fn main() {
+ try $0{
+ bar;
+ }
+}
+"#,
+ r#"
+fn main() {
+ bar;
+}
+"#,
+ );
+ }
}
diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs
index a9399ba6b7..c88cb3d5ea 100644
--- a/crates/ide-assists/src/lib.rs
+++ b/crates/ide-assists/src/lib.rs
@@ -116,6 +116,7 @@ mod handlers {
mod bool_to_enum;
mod change_visibility;
mod convert_bool_then;
+ mod convert_closure_to_fn;
mod convert_comment_block;
mod convert_comment_from_or_to_doc;
mod convert_from_to_tryfrom;
@@ -213,6 +214,7 @@ mod handlers {
mod term_search;
mod toggle_async_sugar;
mod toggle_ignore;
+ mod toggle_macro_delimiter;
mod unmerge_match_arm;
mod unmerge_use;
mod unnecessary_async;
@@ -245,6 +247,7 @@ mod handlers {
toggle_async_sugar::sugar_impl_future_into_async,
convert_comment_block::convert_comment_block,
convert_comment_from_or_to_doc::convert_comment_from_or_to_doc,
+ convert_closure_to_fn::convert_closure_to_fn,
convert_from_to_tryfrom::convert_from_to_tryfrom,
convert_integer_literal::convert_integer_literal,
convert_into_to_from::convert_into_to_from,
@@ -343,6 +346,7 @@ mod handlers {
split_import::split_import,
term_search::term_search,
toggle_ignore::toggle_ignore,
+ toggle_macro_delimiter::toggle_macro_delimiter,
unmerge_match_arm::unmerge_match_arm,
unmerge_use::unmerge_use,
unnecessary_async::unnecessary_async,
diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs
index a2287b2977..dce7bbf342 100644
--- a/crates/ide-assists/src/tests/generated.rs
+++ b/crates/ide-assists/src/tests/generated.rs
@@ -383,6 +383,40 @@ fn main() {
}
#[test]
+fn doctest_convert_closure_to_fn() {
+ check_doc_test(
+ "convert_closure_to_fn",
+ r#####"
+//- minicore: copy
+struct String;
+impl String {
+ fn new() -> Self {}
+ fn push_str(&mut self, s: &str) {}
+}
+fn main() {
+ let mut s = String::new();
+ let closure = |$0a| s.push_str(a);
+ closure("abc");
+}
+"#####,
+ r#####"
+struct String;
+impl String {
+ fn new() -> Self {}
+ fn push_str(&mut self, s: &str) {}
+}
+fn main() {
+ let mut s = String::new();
+ fn closure(a: &str, s: &mut String) {
+ s.push_str(a)
+ }
+ closure("abc", &mut s);
+}
+"#####,
+ )
+}
+
+#[test]
fn doctest_convert_for_loop_with_for_each() {
check_doc_test(
"convert_for_loop_with_for_each",
@@ -994,7 +1028,7 @@ fn main() {
"#####,
r#####"
fn main() {
- let $0var_name = (1 + 2);
+ let $0var_name = 1 + 2;
var_name * 4;
}
"#####,
@@ -3092,6 +3126,27 @@ fn arithmetics {
}
#[test]
+fn doctest_toggle_macro_delimiter() {
+ check_doc_test(
+ "toggle_macro_delimiter",
+ r#####"
+macro_rules! sth {
+ () => {};
+}
+
+sth!$0( );
+"#####,
+ r#####"
+macro_rules! sth {
+ () => {};
+}
+
+sth!{ }
+"#####,
+ )
+}
+
+#[test]
fn doctest_unmerge_match_arm() {
check_doc_test(
"unmerge_match_arm",
diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs
index c67693ea2b..b8a6f3b6db 100644
--- a/crates/ide-assists/src/utils.rs
+++ b/crates/ide-assists/src/utils.rs
@@ -14,7 +14,7 @@ use syntax::{
edit_in_place::{AttrsOwnerEdit, Indent, Removable},
make, HasArgList, HasAttrs, HasGenericParams, HasName, HasTypeBounds, Whitespace,
},
- ted, AstNode, AstToken, Direction, NodeOrToken, SourceFile,
+ ted, AstNode, AstToken, Direction, Edition, NodeOrToken, SourceFile,
SyntaxKind::*,
SyntaxNode, SyntaxToken, TextRange, TextSize, T,
};
@@ -174,7 +174,7 @@ pub fn add_trait_assoc_items_to_impl(
original_items: &[InFile<ast::AssocItem>],
trait_: hir::Trait,
impl_: &ast::Impl,
- target_scope: hir::SemanticsScope<'_>,
+ target_scope: &hir::SemanticsScope<'_>,
) -> ast::AssocItem {
let new_indent_level = IndentLevel::from_node(impl_.syntax()) + 1;
let items = original_items.iter().map(|InFile { file_id, value: original_item }| {
@@ -195,7 +195,7 @@ pub fn add_trait_assoc_items_to_impl(
// FIXME: Paths in nested macros are not handled well. See
// `add_missing_impl_members::paths_in_nested_macro_should_get_transformed` test.
let transform =
- PathTransform::trait_impl(&target_scope, &source_scope, trait_, impl_.clone());
+ PathTransform::trait_impl(target_scope, &source_scope, trait_, impl_.clone());
transform.apply(cloned_item.syntax());
}
cloned_item.remove_attrs_and_docs();
@@ -684,31 +684,31 @@ enum ReferenceConversionType {
}
impl ReferenceConversion {
- pub(crate) fn convert_type(&self, db: &dyn HirDatabase) -> ast::Type {
+ pub(crate) fn convert_type(&self, db: &dyn HirDatabase, edition: Edition) -> ast::Type {
let ty = match self.conversion {
- ReferenceConversionType::Copy => self.ty.display(db).to_string(),
+ ReferenceConversionType::Copy => self.ty.display(db, edition).to_string(),
ReferenceConversionType::AsRefStr => "&str".to_owned(),
ReferenceConversionType::AsRefSlice => {
let type_argument_name =
- self.ty.type_arguments().next().unwrap().display(db).to_string();
+ self.ty.type_arguments().next().unwrap().display(db, edition).to_string();
format!("&[{type_argument_name}]")
}
ReferenceConversionType::Dereferenced => {
let type_argument_name =
- self.ty.type_arguments().next().unwrap().display(db).to_string();
+ self.ty.type_arguments().next().unwrap().display(db, edition).to_string();
format!("&{type_argument_name}")
}
ReferenceConversionType::Option => {
let type_argument_name =
- self.ty.type_arguments().next().unwrap().display(db).to_string();
+ self.ty.type_arguments().next().unwrap().display(db, edition).to_string();
format!("Option<&{type_argument_name}>")
}
ReferenceConversionType::Result => {
let mut type_arguments = self.ty.type_arguments();
let first_type_argument_name =
- type_arguments.next().unwrap().display(db).to_string();
+ type_arguments.next().unwrap().display(db, edition).to_string();
let second_type_argument_name =
- type_arguments.next().unwrap().display(db).to_string();
+ type_arguments.next().unwrap().display(db, edition).to_string();
format!("Result<&{first_type_argument_name}, &{second_type_argument_name}>")
}
};
diff --git a/crates/ide-assists/src/utils/suggest_name.rs b/crates/ide-assists/src/utils/suggest_name.rs
index fc43d243b3..3130ef0695 100644
--- a/crates/ide-assists/src/utils/suggest_name.rs
+++ b/crates/ide-assists/src/utils/suggest_name.rs
@@ -6,7 +6,7 @@ use itertools::Itertools;
use stdx::to_lower_snake_case;
use syntax::{
ast::{self, HasName},
- match_ast, AstNode, SmolStr,
+ match_ast, AstNode, Edition, SmolStr,
};
/// Trait names, that will be ignored when in `impl Trait` and `dyn Trait`
@@ -271,24 +271,25 @@ fn var_name_from_pat(pat: &ast::Pat) -> Option<ast::Name> {
fn from_type(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> Option<String> {
let ty = sema.type_of_expr(expr)?.adjusted();
let ty = ty.remove_ref().unwrap_or(ty);
+ let edition = sema.scope(expr.syntax())?.krate().edition(sema.db);
- name_of_type(&ty, sema.db)
+ name_of_type(&ty, sema.db, edition)
}
-fn name_of_type(ty: &hir::Type, db: &RootDatabase) -> Option<String> {
+fn name_of_type(ty: &hir::Type, db: &RootDatabase, edition: Edition) -> Option<String> {
let name = if let Some(adt) = ty.as_adt() {
- let name = adt.name(db).display(db).to_string();
+ let name = adt.name(db).display(db, edition).to_string();
if WRAPPER_TYPES.contains(&name.as_str()) {
let inner_ty = ty.type_arguments().next()?;
- return name_of_type(&inner_ty, db);
+ return name_of_type(&inner_ty, db, edition);
}
name
} else if let Some(trait_) = ty.as_dyn_trait() {
- trait_name(&trait_, db)?
+ trait_name(&trait_, db, edition)?
} else if let Some(traits) = ty.as_impl_traits(db) {
- let mut iter = traits.filter_map(|t| trait_name(&t, db));
+ let mut iter = traits.filter_map(|t| trait_name(&t, db, edition));
let name = iter.next()?;
if iter.next().is_some() {
return None;
@@ -300,8 +301,8 @@ fn name_of_type(ty: &hir::Type, db: &RootDatabase) -> Option<String> {
normalize(&name)
}
-fn trait_name(trait_: &hir::Trait, db: &RootDatabase) -> Option<String> {
- let name = trait_.name(db).display(db).to_string();
+fn trait_name(trait_: &hir::Trait, db: &RootDatabase, edition: Edition) -> Option<String> {
+ let name = trait_.name(db).display(db, edition).to_string();
if USELESS_TRAITS.contains(&name.as_str()) {
return None;
}
diff --git a/crates/ide-completion/Cargo.toml b/crates/ide-completion/Cargo.toml
index 035b2fc0db..614465b4d0 100644
--- a/crates/ide-completion/Cargo.toml
+++ b/crates/ide-completion/Cargo.toml
@@ -17,7 +17,6 @@ cov-mark = "2.0.0-pre.1"
itertools.workspace = true
tracing.workspace = true
-once_cell = "1.17.0"
smallvec.workspace = true
diff --git a/crates/ide-completion/src/completions.rs b/crates/ide-completion/src/completions.rs
index 58e9b724df..b537150608 100644
--- a/crates/ide-completion/src/completions.rs
+++ b/crates/ide-completion/src/completions.rs
@@ -85,6 +85,7 @@ impl Completions {
CompletionItemKind::Keyword,
ctx.source_range(),
SmolStr::new_static(keyword),
+ ctx.edition,
);
item.add_to(self, ctx.db);
}
@@ -124,7 +125,8 @@ impl Completions {
kw: &str,
snippet: &str,
) {
- let mut item = CompletionItem::new(CompletionItemKind::Keyword, ctx.source_range(), kw);
+ let mut item =
+ CompletionItem::new(CompletionItemKind::Keyword, ctx.source_range(), kw, ctx.edition);
match ctx.config.snippet_cap {
Some(cap) => {
@@ -149,7 +151,8 @@ impl Completions {
kw: &str,
snippet: &str,
) {
- let mut item = CompletionItem::new(CompletionItemKind::Keyword, ctx.source_range(), kw);
+ let mut item =
+ CompletionItem::new(CompletionItemKind::Keyword, ctx.source_range(), kw, ctx.edition);
match ctx.config.snippet_cap {
Some(cap) => item.insert_snippet(cap, snippet),
@@ -544,7 +547,8 @@ impl Completions {
CompletionItem::new(
SymbolKind::LifetimeParam,
ctx.source_range(),
- name.display_no_db().to_smolstr(),
+ name.display_no_db(ctx.edition).to_smolstr(),
+ ctx.edition,
)
.add_to(self, ctx.db)
}
@@ -553,7 +557,8 @@ impl Completions {
CompletionItem::new(
SymbolKind::Label,
ctx.source_range(),
- name.display_no_db().to_smolstr(),
+ name.display_no_db(ctx.edition).to_smolstr(),
+ ctx.edition,
)
.add_to(self, ctx.db)
}
diff --git a/crates/ide-completion/src/completions/attribute.rs b/crates/ide-completion/src/completions/attribute.rs
index a7a6cdebd3..9821fb4a2f 100644
--- a/crates/ide-completion/src/completions/attribute.rs
+++ b/crates/ide-completion/src/completions/attribute.rs
@@ -2,6 +2,8 @@
//!
//! This module uses a bit of static metadata to provide completions for builtin-in attributes and lints.
+use std::sync::LazyLock;
+
use ide_db::{
generated::lints::{
Lint, CLIPPY_LINTS, CLIPPY_LINT_GROUPS, DEFAULT_LINTS, FEATURES, RUSTDOC_LINTS,
@@ -10,10 +12,9 @@ use ide_db::{
FxHashMap, SymbolKind,
};
use itertools::Itertools;
-use once_cell::sync::Lazy;
use syntax::{
ast::{self, AttrKind},
- AstNode, SyntaxKind, T,
+ AstNode, Edition, SyntaxKind, T,
};
use crate::{
@@ -48,11 +49,15 @@ pub(crate) fn complete_known_attribute_input(
match path.text().as_str() {
"repr" => repr::complete_repr(acc, ctx, tt),
- "feature" => {
- lint::complete_lint(acc, ctx, colon_prefix, &parse_tt_as_comma_sep_paths(tt)?, FEATURES)
- }
+ "feature" => lint::complete_lint(
+ acc,
+ ctx,
+ colon_prefix,
+ &parse_tt_as_comma_sep_paths(tt, ctx.edition)?,
+ FEATURES,
+ ),
"allow" | "warn" | "deny" | "forbid" => {
- let existing_lints = parse_tt_as_comma_sep_paths(tt)?;
+ let existing_lints = parse_tt_as_comma_sep_paths(tt, ctx.edition)?;
let lints: Vec<Lint> = CLIPPY_LINT_GROUPS
.iter()
@@ -66,9 +71,12 @@ pub(crate) fn complete_known_attribute_input(
lint::complete_lint(acc, ctx, colon_prefix, &existing_lints, &lints);
}
"cfg" => cfg::complete_cfg(acc, ctx),
- "macro_use" => {
- macro_use::complete_macro_use(acc, ctx, extern_crate, &parse_tt_as_comma_sep_paths(tt)?)
- }
+ "macro_use" => macro_use::complete_macro_use(
+ acc,
+ ctx,
+ extern_crate,
+ &parse_tt_as_comma_sep_paths(tt, ctx.edition)?,
+ ),
_ => (),
}
Some(())
@@ -130,8 +138,12 @@ pub(crate) fn complete_attribute_path(
});
let add_completion = |attr_completion: &AttrCompletion| {
- let mut item =
- CompletionItem::new(SymbolKind::Attribute, ctx.source_range(), attr_completion.label);
+ let mut item = CompletionItem::new(
+ SymbolKind::Attribute,
+ ctx.source_range(),
+ attr_completion.label,
+ ctx.edition,
+ );
if let Some(lookup) = attr_completion.lookup {
item.lookup_by(lookup);
@@ -215,7 +227,7 @@ macro_rules! attrs {
}
#[rustfmt::skip]
-static KIND_TO_ATTRIBUTES: Lazy<FxHashMap<SyntaxKind, &[&str]>> = Lazy::new(|| {
+static KIND_TO_ATTRIBUTES: LazyLock<FxHashMap<SyntaxKind, &[&str]>> = LazyLock::new(|| {
use SyntaxKind::*;
[
(
@@ -361,7 +373,9 @@ fn parse_comma_sep_expr(input: ast::TokenTree) -> Option<Vec<ast::Expr>> {
input_expressions
.into_iter()
.filter_map(|(is_sep, group)| (!is_sep).then_some(group))
- .filter_map(|mut tokens| syntax::hacks::parse_expr_from_str(&tokens.join("")))
+ .filter_map(|mut tokens| {
+ syntax::hacks::parse_expr_from_str(&tokens.join(""), Edition::CURRENT)
+ })
.collect::<Vec<ast::Expr>>(),
)
}
diff --git a/crates/ide-completion/src/completions/attribute/cfg.rs b/crates/ide-completion/src/completions/attribute/cfg.rs
index 6e7d50ede0..cda0da13b2 100644
--- a/crates/ide-completion/src/completions/attribute/cfg.rs
+++ b/crates/ide-completion/src/completions/attribute/cfg.rs
@@ -8,7 +8,8 @@ use crate::{completions::Completions, context::CompletionContext, CompletionItem
pub(crate) fn complete_cfg(acc: &mut Completions, ctx: &CompletionContext<'_>) {
let add_completion = |item: &str| {
- let mut completion = CompletionItem::new(SymbolKind::BuiltinAttr, ctx.source_range(), item);
+ let mut completion =
+ CompletionItem::new(SymbolKind::BuiltinAttr, ctx.source_range(), item, ctx.edition);
completion.insert_text(format!(r#""{item}""#));
acc.add(completion.build(ctx.db));
};
@@ -41,7 +42,12 @@ pub(crate) fn complete_cfg(acc: &mut Completions, ctx: &CompletionContext<'_>) {
name => ctx.krate.potential_cfg(ctx.db).get_cfg_values(name).cloned().for_each(|s| {
let s = s.as_str();
let insert_text = format!(r#""{s}""#);
- let mut item = CompletionItem::new(SymbolKind::BuiltinAttr, ctx.source_range(), s);
+ let mut item = CompletionItem::new(
+ SymbolKind::BuiltinAttr,
+ ctx.source_range(),
+ s,
+ ctx.edition,
+ );
item.insert_text(insert_text);
acc.add(item.build(ctx.db));
@@ -49,7 +55,8 @@ pub(crate) fn complete_cfg(acc: &mut Completions, ctx: &CompletionContext<'_>) {
},
None => ctx.krate.potential_cfg(ctx.db).get_cfg_keys().cloned().unique().for_each(|s| {
let s = s.as_str();
- let item = CompletionItem::new(SymbolKind::BuiltinAttr, ctx.source_range(), s);
+ let item =
+ CompletionItem::new(SymbolKind::BuiltinAttr, ctx.source_range(), s, ctx.edition);
acc.add(item.build(ctx.db));
}),
}
diff --git a/crates/ide-completion/src/completions/attribute/derive.rs b/crates/ide-completion/src/completions/attribute/derive.rs
index 0127a42824..1f8927401b 100644
--- a/crates/ide-completion/src/completions/attribute/derive.rs
+++ b/crates/ide-completion/src/completions/attribute/derive.rs
@@ -62,7 +62,7 @@ pub(crate) fn complete_derive_path(
_ => return acc.add_macro(ctx, path_ctx, mac, name),
};
- let name_ = name.display_no_db().to_smolstr();
+ let name_ = name.display_no_db(ctx.edition).to_smolstr();
let find = DEFAULT_DERIVE_DEPENDENCIES
.iter()
.find(|derive_completion| derive_completion.label == name_);
@@ -72,10 +72,9 @@ pub(crate) fn complete_derive_path(
let mut components = vec![derive_completion.label];
components.extend(derive_completion.dependencies.iter().filter(
|&&dependency| {
- !existing_derives
- .iter()
- .map(|it| it.name(ctx.db))
- .any(|it| it.display_no_db().to_smolstr() == dependency)
+ !existing_derives.iter().map(|it| it.name(ctx.db)).any(|it| {
+ it.display_no_db(ctx.edition).to_smolstr() == dependency
+ })
},
));
let lookup = components.join(", ");
@@ -85,6 +84,7 @@ pub(crate) fn complete_derive_path(
SymbolKind::Derive,
ctx.source_range(),
SmolStr::from_iter(label),
+ ctx.edition,
);
if let Some(docs) = mac.docs(ctx.db) {
item.documentation(docs);
diff --git a/crates/ide-completion/src/completions/attribute/lint.rs b/crates/ide-completion/src/completions/attribute/lint.rs
index f9dec53806..d5f9cd5fc7 100644
--- a/crates/ide-completion/src/completions/attribute/lint.rs
+++ b/crates/ide-completion/src/completions/attribute/lint.rs
@@ -54,7 +54,8 @@ pub(super) fn complete_lint(
Some(qual) if !is_qualified => format!("{qual}::{name}"),
_ => name.to_owned(),
};
- let mut item = CompletionItem::new(SymbolKind::Attribute, ctx.source_range(), label);
+ let mut item =
+ CompletionItem::new(SymbolKind::Attribute, ctx.source_range(), label, ctx.edition);
item.documentation(Documentation::new(description.to_owned()));
item.add_to(acc, ctx.db)
}
diff --git a/crates/ide-completion/src/completions/attribute/macro_use.rs b/crates/ide-completion/src/completions/attribute/macro_use.rs
index 7e3a62405a..deb12282c0 100644
--- a/crates/ide-completion/src/completions/attribute/macro_use.rs
+++ b/crates/ide-completion/src/completions/attribute/macro_use.rs
@@ -28,7 +28,8 @@ pub(super) fn complete_macro_use(
continue;
}
- let item = CompletionItem::new(SymbolKind::Macro, ctx.source_range(), mac_name);
+ let item =
+ CompletionItem::new(SymbolKind::Macro, ctx.source_range(), mac_name, ctx.edition);
item.add_to(acc, ctx.db);
}
}
diff --git a/crates/ide-completion/src/completions/attribute/repr.rs b/crates/ide-completion/src/completions/attribute/repr.rs
index 14f464b775..12652b4489 100644
--- a/crates/ide-completion/src/completions/attribute/repr.rs
+++ b/crates/ide-completion/src/completions/attribute/repr.rs
@@ -30,7 +30,12 @@ pub(super) fn complete_repr(
continue;
}
- let mut item = CompletionItem::new(SymbolKind::BuiltinAttr, ctx.source_range(), label);
+ let mut item = CompletionItem::new(
+ SymbolKind::BuiltinAttr,
+ ctx.source_range(),
+ label,
+ ctx.edition,
+ );
if let Some(lookup) = lookup {
item.lookup_by(lookup);
}
diff --git a/crates/ide-completion/src/completions/dot.rs b/crates/ide-completion/src/completions/dot.rs
index a07daf4c4e..d55bc3ea5d 100644
--- a/crates/ide-completion/src/completions/dot.rs
+++ b/crates/ide-completion/src/completions/dot.rs
@@ -29,6 +29,7 @@ pub(crate) fn complete_dot(
CompletionItemKind::Keyword,
ctx.source_range(),
SmolStr::new_static("await"),
+ ctx.edition,
);
item.detail("expr.await");
item.add_to(acc, ctx.db);
diff --git a/crates/ide-completion/src/completions/env_vars.rs b/crates/ide-completion/src/completions/env_vars.rs
index 23d93d3b74..c9013d1d17 100644
--- a/crates/ide-completion/src/completions/env_vars.rs
+++ b/crates/ide-completion/src/completions/env_vars.rs
@@ -56,7 +56,7 @@ pub(crate) fn complete_cargo_env_vars(
let range = original.text_range_between_quotes()?;
CARGO_DEFINED_VARS.iter().for_each(|&(var, detail)| {
- let mut item = CompletionItem::new(CompletionItemKind::Keyword, range, var);
+ let mut item = CompletionItem::new(CompletionItemKind::Keyword, range, var, ctx.edition);
item.detail(detail);
item.add_to(acc, ctx.db);
});
diff --git a/crates/ide-completion/src/completions/extern_abi.rs b/crates/ide-completion/src/completions/extern_abi.rs
index b5d5604c75..1a06e0a3a0 100644
--- a/crates/ide-completion/src/completions/extern_abi.rs
+++ b/crates/ide-completion/src/completions/extern_abi.rs
@@ -52,8 +52,13 @@ pub(crate) fn complete_extern_abi(
let abi_str = expanded;
let source_range = abi_str.text_range_between_quotes()?;
for &abi in SUPPORTED_CALLING_CONVENTIONS {
- CompletionItem::new(CompletionItemKind::Keyword, source_range, SmolStr::new_static(abi))
- .add_to(acc, ctx.db);
+ CompletionItem::new(
+ CompletionItemKind::Keyword,
+ source_range,
+ SmolStr::new_static(abi),
+ ctx.edition,
+ )
+ .add_to(acc, ctx.db);
}
Some(())
}
diff --git a/crates/ide-completion/src/completions/extern_crate.rs b/crates/ide-completion/src/completions/extern_crate.rs
index 2427f4e49f..7cb710c2d9 100644
--- a/crates/ide-completion/src/completions/extern_crate.rs
+++ b/crates/ide-completion/src/completions/extern_crate.rs
@@ -19,7 +19,8 @@ pub(crate) fn complete_extern_crate(acc: &mut Completions, ctx: &CompletionConte
let mut item = CompletionItem::new(
CompletionItemKind::SymbolKind(SymbolKind::Module),
ctx.source_range(),
- name.display_no_db().to_smolstr(),
+ name.display_no_db(ctx.edition).to_smolstr(),
+ ctx.edition,
);
item.set_documentation(module.docs(ctx.db));
diff --git a/crates/ide-completion/src/completions/flyimport.rs b/crates/ide-completion/src/completions/flyimport.rs
index fdce7c547a..2a6b310d3a 100644
--- a/crates/ide-completion/src/completions/flyimport.rs
+++ b/crates/ide-completion/src/completions/flyimport.rs
@@ -411,7 +411,7 @@ fn compute_fuzzy_completion_order_key(
cov_mark::hit!(certain_fuzzy_order_test);
let import_name = match proposed_mod_path.segments().last() {
// FIXME: nasty alloc, this is a hot path!
- Some(name) => name.display_no_db().to_smolstr().to_ascii_lowercase(),
+ Some(name) => name.unescaped().display_no_db().to_smolstr().to_ascii_lowercase(),
None => return usize::MAX,
};
match import_name.match_indices(user_input_lowercased).next() {
diff --git a/crates/ide-completion/src/completions/fn_param.rs b/crates/ide-completion/src/completions/fn_param.rs
index a59246229b..ee3b817ee8 100644
--- a/crates/ide-completion/src/completions/fn_param.rs
+++ b/crates/ide-completion/src/completions/fn_param.rs
@@ -32,7 +32,7 @@ pub(crate) fn complete_fn_param(
let comma_wrapper = comma_wrapper(ctx);
let mut add_new_item_to_acc = |label: &str| {
let mk_item = |label: &str, range: TextRange| {
- CompletionItem::new(CompletionItemKind::Binding, range, label)
+ CompletionItem::new(CompletionItemKind::Binding, range, label, ctx.edition)
};
let item = match &comma_wrapper {
Some((fmt, range)) => mk_item(&fmt(label), *range),
@@ -50,7 +50,7 @@ pub(crate) fn complete_fn_param(
ParamKind::Closure(closure) => {
let stmt_list = closure.syntax().ancestors().find_map(ast::StmtList::cast)?;
params_from_stmt_list_scope(ctx, stmt_list, |name, ty| {
- add_new_item_to_acc(&format!("{}: {ty}", name.display(ctx.db)));
+ add_new_item_to_acc(&format!("{}: {ty}", name.display(ctx.db, ctx.edition)));
});
}
}
@@ -101,8 +101,8 @@ fn fill_fn_params(
if let Some(stmt_list) = function.syntax().parent().and_then(ast::StmtList::cast) {
params_from_stmt_list_scope(ctx, stmt_list, |name, ty| {
file_params
- .entry(format!("{}: {ty}", name.display(ctx.db)))
- .or_insert(name.display(ctx.db).to_string());
+ .entry(format!("{}: {ty}", name.display(ctx.db, ctx.edition)))
+ .or_insert(name.display(ctx.db, ctx.edition).to_string());
});
}
remove_duplicated(&mut file_params, param_list.params());
diff --git a/crates/ide-completion/src/completions/format_string.rs b/crates/ide-completion/src/completions/format_string.rs
index 23affc3659..a87c60c694 100644
--- a/crates/ide-completion/src/completions/format_string.rs
+++ b/crates/ide-completion/src/completions/format_string.rs
@@ -35,7 +35,8 @@ pub(crate) fn format_string(
CompletionItem::new(
CompletionItemKind::Binding,
source_range,
- name.display_no_db().to_smolstr(),
+ name.display_no_db(ctx.edition).to_smolstr(),
+ ctx.edition,
)
.add_to(acc, ctx.db);
});
@@ -50,7 +51,8 @@ pub(crate) fn format_string(
CompletionItem::new(
CompletionItemKind::SymbolKind(symbol_kind),
source_range,
- name.display_no_db().to_smolstr(),
+ name.display_no_db(ctx.edition).to_smolstr(),
+ ctx.edition,
)
.add_to(acc, ctx.db);
}
diff --git a/crates/ide-completion/src/completions/item_list/trait_impl.rs b/crates/ide-completion/src/completions/item_list/trait_impl.rs
index 2fd7805e60..fc6e1ebf05 100644
--- a/crates/ide-completion/src/completions/item_list/trait_impl.rs
+++ b/crates/ide-completion/src/completions/item_list/trait_impl.rs
@@ -184,7 +184,7 @@ fn add_function_impl(
let label = format_smolstr!(
"{}fn {}({})",
if is_async { "async " } else { "" },
- fn_name.display(ctx.db),
+ fn_name.display(ctx.db, ctx.edition),
if func.assoc_fn_params(ctx.db).is_empty() { "" } else { ".." }
);
@@ -194,11 +194,11 @@ fn add_function_impl(
SymbolKind::Function
});
- let mut item = CompletionItem::new(completion_kind, replacement_range, label);
+ let mut item = CompletionItem::new(completion_kind, replacement_range, label, ctx.edition);
item.lookup_by(format!(
"{}fn {}",
if is_async { "async " } else { "" },
- fn_name.display(ctx.db)
+ fn_name.display(ctx.db, ctx.edition)
))
.set_documentation(func.docs(ctx.db))
.set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() });
@@ -262,7 +262,8 @@ fn add_type_alias_impl(
let label = format_smolstr!("type {alias_name} =");
- let mut item = CompletionItem::new(SymbolKind::TypeAlias, replacement_range, label);
+ let mut item =
+ CompletionItem::new(SymbolKind::TypeAlias, replacement_range, label, ctx.edition);
item.lookup_by(format!("type {alias_name}"))
.set_documentation(type_alias.docs(ctx.db))
.set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() });
@@ -320,7 +321,7 @@ fn add_const_impl(
const_: hir::Const,
impl_def: hir::Impl,
) {
- let const_name = const_.name(ctx.db).map(|n| n.display_no_db().to_smolstr());
+ let const_name = const_.name(ctx.db).map(|n| n.display_no_db(ctx.edition).to_smolstr());
if let Some(const_name) = const_name {
if let Some(source) = ctx.sema.source(const_) {
@@ -334,7 +335,8 @@ fn add_const_impl(
let label = make_const_compl_syntax(&transformed_const, source.file_id.is_macro());
let replacement = format!("{label} ");
- let mut item = CompletionItem::new(SymbolKind::Const, replacement_range, label);
+ let mut item =
+ CompletionItem::new(SymbolKind::Const, replacement_range, label, ctx.edition);
item.lookup_by(format_smolstr!("const {const_name}"))
.set_documentation(const_.docs(ctx.db))
.set_relevance(CompletionRelevance {
diff --git a/crates/ide-completion/src/completions/lifetime.rs b/crates/ide-completion/src/completions/lifetime.rs
index 03fe93c563..9efc52428e 100644
--- a/crates/ide-completion/src/completions/lifetime.rs
+++ b/crates/ide-completion/src/completions/lifetime.rs
@@ -41,7 +41,7 @@ pub(crate) fn complete_lifetime(
if matches!(
res,
ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_))
- if param_lifetime != Some(&*name.display_no_db().to_smolstr())
+ if param_lifetime != Some(&*name.display_no_db(ctx.edition).to_smolstr())
) {
acc.add_lifetime(ctx, name);
}
diff --git a/crates/ide-completion/src/completions/mod_.rs b/crates/ide-completion/src/completions/mod_.rs
index d9a10893bf..05e2892fdc 100644
--- a/crates/ide-completion/src/completions/mod_.rs
+++ b/crates/ide-completion/src/completions/mod_.rs
@@ -53,7 +53,7 @@ pub(crate) fn complete_mod(
let existing_mod_declarations = current_module
.children(ctx.db)
- .filter_map(|module| Some(module.name(ctx.db)?.display(ctx.db).to_string()))
+ .filter_map(|module| Some(module.name(ctx.db)?.display(ctx.db, ctx.edition).to_string()))
.filter(|module| module != ctx.original_token.text())
.collect::<FxHashSet<_>>();
@@ -99,7 +99,8 @@ pub(crate) fn complete_mod(
if mod_under_caret.semicolon_token().is_none() {
label.push(';');
}
- let item = CompletionItem::new(SymbolKind::Module, ctx.source_range(), &label);
+ let item =
+ CompletionItem::new(SymbolKind::Module, ctx.source_range(), &label, ctx.edition);
item.add_to(acc, ctx.db)
});
@@ -140,7 +141,9 @@ fn directory_to_look_for_submodules(
module_chain_to_containing_module_file(module, db)
.into_iter()
.filter_map(|module| module.name(db))
- .try_fold(base_directory, |path, name| path.join(&name.display_no_db().to_smolstr()))
+ .try_fold(base_directory, |path, name| {
+ path.join(&name.unescaped().display_no_db().to_smolstr())
+ })
}
fn module_chain_to_containing_module_file(
diff --git a/crates/ide-completion/src/completions/postfix.rs b/crates/ide-completion/src/completions/postfix.rs
index 977e0d80a4..a632f14893 100644
--- a/crates/ide-completion/src/completions/postfix.rs
+++ b/crates/ide-completion/src/completions/postfix.rs
@@ -72,7 +72,10 @@ pub(crate) fn complete_postfix(
let mut item = postfix_snippet(
"drop",
"fn drop(&mut self)",
- &format!("{path}($0{receiver_text})", path = path.display(ctx.db)),
+ &format!(
+ "{path}($0{receiver_text})",
+ path = path.display(ctx.db, ctx.edition)
+ ),
);
item.set_documentation(drop_fn.docs(ctx.db));
item.add_to(acc, ctx.db);
@@ -335,8 +338,12 @@ fn build_postfix_snippet_builder<'ctx>(
) -> impl Fn(&str, &str, &str) -> Builder + 'ctx {
move |label, detail, snippet| {
let edit = TextEdit::replace(delete_range, snippet.to_owned());
- let mut item =
- CompletionItem::new(CompletionItemKind::Snippet, ctx.source_range(), label);
+ let mut item = CompletionItem::new(
+ CompletionItemKind::Snippet,
+ ctx.source_range(),
+ label,
+ ctx.edition,
+ );
item.detail(detail).snippet_edit(cap, edit);
let postfix_match = if ctx.original_token.text() == label {
cov_mark::hit!(postfix_exact_match_is_high_priority);
diff --git a/crates/ide-completion/src/completions/record.rs b/crates/ide-completion/src/completions/record.rs
index 1dcf41f8dd..117a5e3d93 100644
--- a/crates/ide-completion/src/completions/record.rs
+++ b/crates/ide-completion/src/completions/record.rs
@@ -73,6 +73,7 @@ pub(crate) fn complete_record_expr_fields(
CompletionItemKind::Snippet,
ctx.source_range(),
SmolStr::new_static(".."),
+ ctx.edition,
);
item.insert_text(".");
item.add_to(acc, ctx.db);
@@ -101,6 +102,7 @@ pub(crate) fn add_default_update(
SymbolKind::Field,
ctx.source_range(),
SmolStr::new_static(completion_text),
+ ctx.edition,
);
let completion_text =
completion_text.strip_prefix(ctx.token.text()).unwrap_or(completion_text);
diff --git a/crates/ide-completion/src/completions/snippet.rs b/crates/ide-completion/src/completions/snippet.rs
index e831113350..357709e0c1 100644
--- a/crates/ide-completion/src/completions/snippet.rs
+++ b/crates/ide-completion/src/completions/snippet.rs
@@ -118,7 +118,8 @@ macro_rules! $1 {
}
fn snippet(ctx: &CompletionContext<'_>, cap: SnippetCap, label: &str, snippet: &str) -> Builder {
- let mut item = CompletionItem::new(CompletionItemKind::Snippet, ctx.source_range(), label);
+ let mut item =
+ CompletionItem::new(CompletionItemKind::Snippet, ctx.source_range(), label, ctx.edition);
item.insert_snippet(cap, snippet);
item
}
diff --git a/crates/ide-completion/src/completions/use_.rs b/crates/ide-completion/src/completions/use_.rs
index 8e5b55360d..45704549e6 100644
--- a/crates/ide-completion/src/completions/use_.rs
+++ b/crates/ide-completion/src/completions/use_.rs
@@ -107,7 +107,11 @@ pub(crate) fn complete_use_path(
let item = CompletionItem::new(
CompletionItemKind::SymbolKind(SymbolKind::Enum),
ctx.source_range(),
- format_smolstr!("{}::", e.name(ctx.db).display(ctx.db)),
+ format_smolstr!(
+ "{}::",
+ e.name(ctx.db).display(ctx.db, ctx.edition)
+ ),
+ ctx.edition,
);
acc.add(item.build(ctx.db));
}
diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs
index 952d9217c7..bcd9df9419 100644
--- a/crates/ide-completion/src/context.rs
+++ b/crates/ide-completion/src/context.rs
@@ -15,7 +15,7 @@ use ide_db::{
};
use syntax::{
ast::{self, AttrKind, NameOrNameRef},
- AstNode, SmolStr,
+ AstNode, Edition, SmolStr,
SyntaxKind::{self, *},
SyntaxToken, TextRange, TextSize, T,
};
@@ -437,6 +437,7 @@ pub(crate) struct CompletionContext<'a> {
pub(crate) module: hir::Module,
/// Whether nightly toolchain is used. Cached since this is looked up a lot.
is_nightly: bool,
+ pub(crate) edition: Edition,
/// The expected name of what we are completing.
/// This is usually the parameter name of the function argument we are completing.
@@ -467,8 +468,9 @@ impl CompletionContext<'_> {
cov_mark::hit!(completes_if_lifetime_without_idents);
TextRange::at(self.original_token.text_range().start(), TextSize::from(1))
}
- IDENT | LIFETIME_IDENT | UNDERSCORE | INT_NUMBER => self.original_token.text_range(),
- _ if kind.is_keyword() => self.original_token.text_range(),
+ LIFETIME_IDENT | UNDERSCORE | INT_NUMBER => self.original_token.text_range(),
+ // We want to consider all keywords in all editions.
+ _ if kind.is_any_identifier() => self.original_token.text_range(),
_ => TextRange::empty(self.position.offset),
}
}
@@ -716,6 +718,7 @@ impl<'a> CompletionContext<'a> {
let krate = scope.krate();
let module = scope.module();
+ let edition = krate.edition(db);
let toolchain = db.toolchain_channel(krate.into());
// `toolchain == None` means we're in some detached files. Since we have no information on
@@ -742,6 +745,7 @@ impl<'a> CompletionContext<'a> {
krate,
module,
is_nightly,
+ edition,
expected_name,
expected_type,
qualifier_ctx,
diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs
index 1e972b9b4c..ed359394f1 100644
--- a/crates/ide-completion/src/context/analysis.rs
+++ b/crates/ide-completion/src/context/analysis.rs
@@ -1385,7 +1385,7 @@ fn pattern_context_for(
}).map(|enum_| enum_.variants(sema.db))
})
}).map(|variants| variants.iter().filter_map(|variant| {
- let variant_name = variant.name(sema.db).display(sema.db).to_string();
+ let variant_name = variant.name(sema.db).unescaped().display(sema.db).to_string();
let variant_already_present = match_arm_list.arms().any(|arm| {
arm.pat().and_then(|pat| {
diff --git a/crates/ide-completion/src/item.rs b/crates/ide-completion/src/item.rs
index 3657a7d969..a30a115da1 100644
--- a/crates/ide-completion/src/item.rs
+++ b/crates/ide-completion/src/item.rs
@@ -10,7 +10,7 @@ use ide_db::{
use itertools::Itertools;
use smallvec::SmallVec;
use stdx::{impl_from, never};
-use syntax::{format_smolstr, SmolStr, TextRange, TextSize};
+use syntax::{format_smolstr, Edition, SmolStr, TextRange, TextSize};
use text_edit::TextEdit;
use crate::{
@@ -400,6 +400,7 @@ impl CompletionItem {
kind: impl Into<CompletionItemKind>,
source_range: TextRange,
label: impl Into<SmolStr>,
+ edition: Edition,
) -> Builder {
let label = label.into();
Builder {
@@ -419,6 +420,7 @@ impl CompletionItem {
ref_match: None,
imports_to_add: Default::default(),
doc_aliases: vec![],
+ edition,
}
}
@@ -464,6 +466,7 @@ pub(crate) struct Builder {
trigger_call_info: bool,
relevance: CompletionRelevance,
ref_match: Option<(Mutability, TextSize)>,
+ edition: Edition,
}
impl Builder {
@@ -517,7 +520,7 @@ impl Builder {
label_detail.replace(format_smolstr!(
"{} (use {})",
label_detail.as_deref().unwrap_or_default(),
- import_edit.import_path.display(db)
+ import_edit.import_path.display(db, self.edition)
));
} else if let Some(trait_name) = self.trait_name {
label_detail.replace(format_smolstr!(
@@ -536,8 +539,8 @@ impl Builder {
.into_iter()
.filter_map(|import| {
Some((
- import.import_path.display(db).to_string(),
- import.import_path.segments().last()?.display(db).to_string(),
+ import.import_path.display(db, self.edition).to_string(),
+ import.import_path.segments().last()?.display(db, self.edition).to_string(),
))
})
.collect();
diff --git a/crates/ide-completion/src/lib.rs b/crates/ide-completion/src/lib.rs
index 90c1728074..58d1fad095 100644
--- a/crates/ide-completion/src/lib.rs
+++ b/crates/ide-completion/src/lib.rs
@@ -245,6 +245,7 @@ pub fn resolve_completion_edits(
let current_module = sema.scope(position_for_import)?.module();
let current_crate = current_module.krate();
+ let current_edition = current_crate.edition(db);
let new_ast = scope.clone_for_update();
let mut import_insert = TextEdit::builder();
@@ -261,9 +262,13 @@ pub fn resolve_completion_edits(
.filter_map(|candidate| {
current_module.find_use_path(db, candidate, config.insert_use.prefix_kind, cfg)
})
- .find(|mod_path| mod_path.display(db).to_string() == full_import_path);
+ .find(|mod_path| mod_path.display(db, current_edition).to_string() == full_import_path);
if let Some(import_path) = import {
- insert_use::insert_use(&new_ast, mod_path_to_ast(&import_path), &config.insert_use);
+ insert_use::insert_use(
+ &new_ast,
+ mod_path_to_ast(&import_path, current_edition),
+ &config.insert_use,
+ );
}
});
diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs
index 02d667c520..ff5ec3a29f 100644
--- a/crates/ide-completion/src/render.rs
+++ b/crates/ide-completion/src/render.rs
@@ -17,7 +17,7 @@ use ide_db::{
imports::import_assets::LocatedImport,
RootDatabase, SnippetCap, SymbolKind,
};
-use syntax::{ast, format_smolstr, AstNode, SmolStr, SyntaxKind, TextRange, ToSmolStr};
+use syntax::{ast, format_smolstr, AstNode, Edition, SmolStr, SyntaxKind, TextRange, ToSmolStr};
use text_edit::TextEdit;
use crate::{
@@ -133,19 +133,22 @@ pub(crate) fn render_field(
let db = ctx.db();
let is_deprecated = ctx.is_deprecated(field);
let name = field.name(db);
- let (name, escaped_name) =
- (name.unescaped().display(db).to_smolstr(), name.display_no_db().to_smolstr());
+ let (name, escaped_name) = (
+ name.unescaped().display(db).to_smolstr(),
+ name.display_no_db(ctx.completion.edition).to_smolstr(),
+ );
let mut item = CompletionItem::new(
SymbolKind::Field,
ctx.source_range(),
- field_with_receiver(db, receiver.as_ref(), &name),
+ field_with_receiver(db, receiver.as_ref(), &name, ctx.completion.edition),
+ ctx.completion.edition,
);
item.set_relevance(CompletionRelevance {
type_match: compute_type_match(ctx.completion, ty),
exact_name_match: compute_exact_name_match(ctx.completion, name.as_str()),
..CompletionRelevance::default()
});
- item.detail(ty.display(db).to_string())
+ item.detail(ty.display(db, ctx.completion.edition).to_string())
.set_documentation(field.docs(db))
.set_deprecated(is_deprecated)
.lookup_by(name);
@@ -159,7 +162,8 @@ pub(crate) fn render_field(
builder.replace(
ctx.source_range(),
- field_with_receiver(db, receiver.as_ref(), &escaped_name).into(),
+ field_with_receiver(db, receiver.as_ref(), &escaped_name, ctx.completion.edition)
+ .into(),
);
let expected_fn_type =
@@ -183,7 +187,12 @@ pub(crate) fn render_field(
item.text_edit(builder.finish());
} else {
- item.insert_text(field_with_receiver(db, receiver.as_ref(), &escaped_name));
+ item.insert_text(field_with_receiver(
+ db,
+ receiver.as_ref(),
+ &escaped_name,
+ ctx.completion.edition,
+ ));
}
if let Some(receiver) = &dot_access.receiver {
if let Some(original) = ctx.completion.sema.original_ast_node(receiver.clone()) {
@@ -200,10 +209,11 @@ fn field_with_receiver(
db: &RootDatabase,
receiver: Option<&hir::Name>,
field_name: &str,
+ edition: Edition,
) -> SmolStr {
receiver.map_or_else(
|| field_name.into(),
- |receiver| format_smolstr!("{}.{field_name}", receiver.display(db)),
+ |receiver| format_smolstr!("{}.{field_name}", receiver.display(db, edition)),
)
}
@@ -216,9 +226,16 @@ pub(crate) fn render_tuple_field(
let mut item = CompletionItem::new(
SymbolKind::Field,
ctx.source_range(),
- field_with_receiver(ctx.db(), receiver.as_ref(), &field.to_string()),
+ field_with_receiver(
+ ctx.db(),
+ receiver.as_ref(),
+ &field.to_string(),
+ ctx.completion.edition,
+ ),
+ ctx.completion.edition,
);
- item.detail(ty.display(ctx.db()).to_string()).lookup_by(field.to_string());
+ item.detail(ty.display(ctx.db(), ctx.completion.edition).to_string())
+ .lookup_by(field.to_string());
item.build(ctx.db())
}
@@ -226,8 +243,12 @@ pub(crate) fn render_type_inference(
ty_string: String,
ctx: &CompletionContext<'_>,
) -> CompletionItem {
- let mut builder =
- CompletionItem::new(CompletionItemKind::InferredType, ctx.source_range(), ty_string);
+ let mut builder = CompletionItem::new(
+ CompletionItemKind::InferredType,
+ ctx.source_range(),
+ ty_string,
+ ctx.edition,
+ );
builder.set_relevance(CompletionRelevance { is_definite: true, ..Default::default() });
builder.build(ctx.db)
}
@@ -296,7 +317,7 @@ pub(crate) fn render_expr(
let cfg = ctx.config.import_path_config();
- let label = expr.gen_source_code(&ctx.scope, &mut label_formatter, cfg).ok()?;
+ let label = expr.gen_source_code(&ctx.scope, &mut label_formatter, cfg, ctx.edition).ok()?;
let source_range = match ctx.original_token.parent() {
Some(node) => match node.ancestors().find_map(ast::Path::cast) {
@@ -306,10 +327,13 @@ pub(crate) fn render_expr(
None => ctx.source_range(),
};
- let mut item = CompletionItem::new(CompletionItemKind::Expression, source_range, label);
+ let mut item =
+ CompletionItem::new(CompletionItemKind::Expression, source_range, label, ctx.edition);
- let snippet =
- format!("{}$0", expr.gen_source_code(&ctx.scope, &mut snippet_formatter, cfg).ok()?);
+ let snippet = format!(
+ "{}$0",
+ expr.gen_source_code(&ctx.scope, &mut snippet_formatter, cfg, ctx.edition).ok()?
+ );
let edit = TextEdit::replace(source_range, snippet);
item.snippet_edit(ctx.config.snippet_cap?, edit);
item.documentation(Documentation::new(String::from("Autogenerated expression by term search")));
@@ -396,10 +420,10 @@ fn render_resolution_path(
let config = completion.config;
let requires_import = import_to_add.is_some();
- let name = local_name.display_no_db().to_smolstr();
+ let name = local_name.display_no_db(ctx.completion.edition).to_smolstr();
let mut item = render_resolution_simple_(ctx, &local_name, import_to_add, resolution);
- if local_name.is_escaped() {
- item.insert_text(local_name.display_no_db().to_smolstr());
+ if local_name.is_escaped(completion.edition) {
+ item.insert_text(local_name.display_no_db(completion.edition).to_smolstr());
}
// Add `<>` for generic types
let type_path_no_ty_args = matches!(
@@ -421,14 +445,17 @@ fn render_resolution_path(
item.lookup_by(name.clone())
.label(SmolStr::from_iter([&name, "<…>"]))
.trigger_call_info()
- .insert_snippet(cap, format!("{}<$0>", local_name.display(db)));
+ .insert_snippet(
+ cap,
+ format!("{}<$0>", local_name.display(db, completion.edition)),
+ );
}
}
}
let mut set_item_relevance = |ty: Type| {
if !ty.is_unknown() {
- item.detail(ty.display(db).to_string());
+ item.detail(ty.display(db, completion.edition).to_string());
}
item.set_relevance(CompletionRelevance {
@@ -485,6 +512,7 @@ fn render_resolution_simple_(
kind,
ctx.source_range(),
local_name.unescaped().display(db).to_smolstr(),
+ ctx.completion.edition,
);
item.set_relevance(ctx.completion_relevance())
.set_documentation(scope_def_docs(db, resolution))
diff --git a/crates/ide-completion/src/render/const_.rs b/crates/ide-completion/src/render/const_.rs
index 3bfec0de6b..415d87c623 100644
--- a/crates/ide-completion/src/render/const_.rs
+++ b/crates/ide-completion/src/render/const_.rs
@@ -14,11 +14,14 @@ pub(crate) fn render_const(ctx: RenderContext<'_>, const_: hir::Const) -> Option
fn render(ctx: RenderContext<'_>, const_: hir::Const) -> Option<CompletionItem> {
let db = ctx.db();
let name = const_.name(db)?;
- let (name, escaped_name) =
- (name.unescaped().display(db).to_smolstr(), name.display(db).to_smolstr());
- let detail = const_.display(db).to_string();
+ let (name, escaped_name) = (
+ name.unescaped().display(db).to_smolstr(),
+ name.display(db, ctx.completion.edition).to_smolstr(),
+ );
+ let detail = const_.display(db, ctx.completion.edition).to_string();
- let mut item = CompletionItem::new(SymbolKind::Const, ctx.source_range(), name);
+ let mut item =
+ CompletionItem::new(SymbolKind::Const, ctx.source_range(), name, ctx.completion.edition);
item.set_documentation(ctx.docs(const_))
.set_deprecated(ctx.is_deprecated(const_) || ctx.is_deprecated_assoc_item(const_))
.detail(detail)
@@ -26,7 +29,7 @@ fn render(ctx: RenderContext<'_>, const_: hir::Const) -> Option<CompletionItem>
if let Some(actm) = const_.as_assoc_item(db) {
if let Some(trt) = actm.container_or_implemented_trait(db) {
- item.trait_name(trt.name(db).display_no_db().to_smolstr());
+ item.trait_name(trt.name(db).display_no_db(ctx.completion.edition).to_smolstr());
}
}
item.insert_text(escaped_name);
diff --git a/crates/ide-completion/src/render/function.rs b/crates/ide-completion/src/render/function.rs
index 05b2d0ae38..74092b53f5 100644
--- a/crates/ide-completion/src/render/function.rs
+++ b/crates/ide-completion/src/render/function.rs
@@ -4,7 +4,7 @@ use hir::{db::HirDatabase, AsAssocItem, HirDisplay};
use ide_db::{SnippetCap, SymbolKind};
use itertools::Itertools;
use stdx::{format_to, to_lower_snake_case};
-use syntax::{format_smolstr, AstNode, SmolStr, ToSmolStr};
+use syntax::{format_smolstr, AstNode, Edition, SmolStr, ToSmolStr};
use crate::{
context::{CompletionContext, DotAccess, DotAccessKind, PathCompletionCtx, PathKind},
@@ -62,9 +62,16 @@ fn render(
receiver.unescaped().display(ctx.db()),
name.unescaped().display(ctx.db())
),
- format_smolstr!("{}.{}", receiver.display(ctx.db()), name.display(ctx.db())),
+ format_smolstr!(
+ "{}.{}",
+ receiver.display(ctx.db(), completion.edition),
+ name.display(ctx.db(), completion.edition)
+ ),
+ ),
+ _ => (
+ name.unescaped().display(db).to_smolstr(),
+ name.display(db, completion.edition).to_smolstr(),
),
- _ => (name.unescaped().display(db).to_smolstr(), name.display(db).to_smolstr()),
};
let has_self_param = func.self_param(db).is_some();
let mut item = CompletionItem::new(
@@ -75,6 +82,7 @@ fn render(
}),
ctx.source_range(),
call.clone(),
+ completion.edition,
);
let ret_type = func.ret_type(db);
@@ -141,9 +149,9 @@ fn render(
}
let detail = if ctx.completion.config.full_function_signatures {
- detail_full(db, func)
+ detail_full(db, func, ctx.completion.edition)
} else {
- detail(db, func)
+ detail(db, func, ctx.completion.edition)
};
item.set_documentation(ctx.docs(func))
.set_deprecated(ctx.is_deprecated(func) || ctx.is_deprecated_assoc_item(func))
@@ -161,7 +169,9 @@ fn render(
None => {
if let Some(actm) = assoc_item {
if let Some(trt) = actm.container_or_implemented_trait(db) {
- item.trait_name(trt.name(db).display_no_db().to_smolstr());
+ item.trait_name(
+ trt.name(db).display_no_db(ctx.completion.edition).to_smolstr(),
+ );
}
}
}
@@ -219,7 +229,7 @@ pub(super) fn add_call_parens<'b>(
params.iter().enumerate().format_with(", ", |(index, param), f| {
match param.name(ctx.db) {
Some(n) => {
- let smol_str = n.display_no_db().to_smolstr();
+ let smol_str = n.display_no_db(ctx.edition).to_smolstr();
let text = smol_str.as_str().trim_start_matches('_');
let ref_ = ref_of_param(ctx, text, param.ty());
f(&format_args!("${{{}:{ref_}{text}}}", index + offset))
@@ -238,7 +248,7 @@ pub(super) fn add_call_parens<'b>(
format!(
"{}(${{1:{}}}{}{})$0",
escaped_name,
- self_param.display(ctx.db),
+ self_param.display(ctx.db, ctx.edition),
if params.is_empty() { "" } else { ", " },
function_params_snippet
)
@@ -276,7 +286,7 @@ fn ref_of_param(ctx: &CompletionContext<'_>, arg: &str, ty: &hir::Type) -> &'sta
""
}
-fn detail(db: &dyn HirDatabase, func: hir::Function) -> String {
+fn detail(db: &dyn HirDatabase, func: hir::Function, edition: Edition) -> String {
let mut ret_ty = func.ret_type(db);
let mut detail = String::new();
@@ -293,15 +303,15 @@ fn detail(db: &dyn HirDatabase, func: hir::Function) -> String {
format_to!(detail, "unsafe ");
}
- format_to!(detail, "fn({})", params_display(db, func));
+ format_to!(detail, "fn({})", params_display(db, func, edition));
if !ret_ty.is_unit() {
- format_to!(detail, " -> {}", ret_ty.display(db));
+ format_to!(detail, " -> {}", ret_ty.display(db, edition));
}
detail
}
-fn detail_full(db: &dyn HirDatabase, func: hir::Function) -> String {
- let signature = format!("{}", func.display(db));
+fn detail_full(db: &dyn HirDatabase, func: hir::Function, edition: Edition) -> String {
+ let signature = format!("{}", func.display(db, edition));
let mut detail = String::with_capacity(signature.len());
for segment in signature.split_whitespace() {
@@ -315,16 +325,16 @@ fn detail_full(db: &dyn HirDatabase, func: hir::Function) -> String {
detail
}
-fn params_display(db: &dyn HirDatabase, func: hir::Function) -> String {
+fn params_display(db: &dyn HirDatabase, func: hir::Function, edition: Edition) -> String {
if let Some(self_param) = func.self_param(db) {
let assoc_fn_params = func.assoc_fn_params(db);
let params = assoc_fn_params
.iter()
.skip(1) // skip the self param because we are manually handling that
- .map(|p| p.ty().display(db));
+ .map(|p| p.ty().display(db, edition));
format!(
"{}{}",
- self_param.display(db),
+ self_param.display(db, edition),
params.format_with("", |display, f| {
f(&", ")?;
f(&display)
@@ -332,7 +342,7 @@ fn params_display(db: &dyn HirDatabase, func: hir::Function) -> String {
)
} else {
let assoc_fn_params = func.assoc_fn_params(db);
- assoc_fn_params.iter().map(|p| p.ty().display(db)).join(", ")
+ assoc_fn_params.iter().map(|p| p.ty().display(db, edition)).join(", ")
}
}
diff --git a/crates/ide-completion/src/render/literal.rs b/crates/ide-completion/src/render/literal.rs
index 27435307d5..c71356e530 100644
--- a/crates/ide-completion/src/render/literal.rs
+++ b/crates/ide-completion/src/render/literal.rs
@@ -76,16 +76,16 @@ fn render(
};
let (qualified_name, escaped_qualified_name) = (
qualified_name.unescaped().display(ctx.db()).to_string(),
- qualified_name.display(ctx.db()).to_string(),
+ qualified_name.display(ctx.db(), completion.edition).to_string(),
);
let snippet_cap = ctx.snippet_cap();
let mut rendered = match kind {
StructKind::Tuple if should_add_parens => {
- render_tuple_lit(db, snippet_cap, &fields, &escaped_qualified_name)
+ render_tuple_lit(db, snippet_cap, &fields, &escaped_qualified_name, completion.edition)
}
StructKind::Record if should_add_parens => {
- render_record_lit(db, snippet_cap, &fields, &escaped_qualified_name)
+ render_record_lit(db, snippet_cap, &fields, &escaped_qualified_name, completion.edition)
}
_ => RenderedLiteral {
literal: escaped_qualified_name.clone(),
@@ -103,7 +103,10 @@ fn render(
}
let label = format_literal_label(&qualified_name, kind, snippet_cap);
let lookup = if qualified {
- format_literal_lookup(&short_qualified_name.display(ctx.db()).to_string(), kind)
+ format_literal_lookup(
+ &short_qualified_name.display(ctx.db(), completion.edition).to_string(),
+ kind,
+ )
} else {
format_literal_lookup(&qualified_name, kind)
};
@@ -112,6 +115,7 @@ fn render(
CompletionItemKind::SymbolKind(thing.symbol_kind()),
ctx.source_range(),
label,
+ completion.edition,
);
item.lookup_by(lookup);
diff --git a/crates/ide-completion/src/render/macro_.rs b/crates/ide-completion/src/render/macro_.rs
index de715bcbfa..6490171fbb 100644
--- a/crates/ide-completion/src/render/macro_.rs
+++ b/crates/ide-completion/src/render/macro_.rs
@@ -46,8 +46,10 @@ fn render(
ctx.source_range()
};
- let (name, escaped_name) =
- (name.unescaped().display(ctx.db()).to_smolstr(), name.display(ctx.db()).to_smolstr());
+ let (name, escaped_name) = (
+ name.unescaped().display(ctx.db()).to_smolstr(),
+ name.display(ctx.db(), completion.edition).to_smolstr(),
+ );
let docs = ctx.docs(macro_);
let docs_str = docs.as_ref().map(Documentation::as_str).unwrap_or_default();
let is_fn_like = macro_.is_fn_like(completion.db);
@@ -59,9 +61,10 @@ fn render(
SymbolKind::from(macro_.kind(completion.db)),
source_range,
label(&ctx, needs_bang, bra, ket, &name),
+ completion.edition,
);
item.set_deprecated(ctx.is_deprecated(macro_))
- .detail(macro_.display(completion.db).to_string())
+ .detail(macro_.display(completion.db, completion.edition).to_string())
.set_documentation(docs)
.set_relevance(ctx.completion_relevance());
diff --git a/crates/ide-completion/src/render/pattern.rs b/crates/ide-completion/src/render/pattern.rs
index 598b8762b6..5675dfb5c6 100644
--- a/crates/ide-completion/src/render/pattern.rs
+++ b/crates/ide-completion/src/render/pattern.rs
@@ -3,7 +3,7 @@
use hir::{db::HirDatabase, Name, StructKind};
use ide_db::{documentation::HasDocs, SnippetCap};
use itertools::Itertools;
-use syntax::{SmolStr, ToSmolStr};
+use syntax::{Edition, SmolStr, ToSmolStr};
use crate::{
context::{ParamContext, ParamKind, PathCompletionCtx, PatternContext},
@@ -31,8 +31,10 @@ pub(crate) fn render_struct_pat(
}
let name = local_name.unwrap_or_else(|| strukt.name(ctx.db()));
- let (name, escaped_name) =
- (name.unescaped().display(ctx.db()).to_smolstr(), name.display(ctx.db()).to_smolstr());
+ let (name, escaped_name) = (
+ name.unescaped().display(ctx.db()).to_smolstr(),
+ name.display(ctx.db(), ctx.completion.edition).to_smolstr(),
+ );
let kind = strukt.kind(ctx.db());
let label = format_literal_label(name.as_str(), kind, ctx.snippet_cap());
let lookup = format_literal_lookup(name.as_str(), kind);
@@ -60,13 +62,13 @@ pub(crate) fn render_variant_pat(
let (name, escaped_name) = match path {
Some(path) => (
path.unescaped().display(ctx.db()).to_string().into(),
- path.display(ctx.db()).to_string().into(),
+ path.display(ctx.db(), ctx.completion.edition).to_string().into(),
),
None => {
let name = local_name.unwrap_or_else(|| variant.name(ctx.db()));
let it = (
name.unescaped().display(ctx.db()).to_smolstr(),
- name.display(ctx.db()).to_smolstr(),
+ name.display(ctx.db(), ctx.completion.edition).to_smolstr(),
);
it
}
@@ -119,7 +121,12 @@ fn build_completion(
relevance.type_match = super::compute_type_match(ctx.completion, &adt_ty);
}
- let mut item = CompletionItem::new(CompletionItemKind::Binding, ctx.source_range(), label);
+ let mut item = CompletionItem::new(
+ CompletionItemKind::Binding,
+ ctx.source_range(),
+ label,
+ ctx.completion.edition,
+ );
item.set_documentation(ctx.docs(def))
.set_deprecated(ctx.is_deprecated(def))
.detail(&pat)
@@ -142,9 +149,14 @@ fn render_pat(
) -> Option<String> {
let mut pat = match kind {
StructKind::Tuple => render_tuple_as_pat(ctx.snippet_cap(), fields, name, fields_omitted),
- StructKind::Record => {
- render_record_as_pat(ctx.db(), ctx.snippet_cap(), fields, name, fields_omitted)
- }
+ StructKind::Record => render_record_as_pat(
+ ctx.db(),
+ ctx.snippet_cap(),
+ fields,
+ name,
+ fields_omitted,
+ ctx.completion.edition,
+ ),
StructKind::Unit => name.to_owned(),
};
@@ -173,6 +185,7 @@ fn render_record_as_pat(
fields: &[hir::Field],
name: &str,
fields_omitted: bool,
+ edition: Edition,
) -> String {
let fields = fields.iter();
match snippet_cap {
@@ -180,7 +193,7 @@ fn render_record_as_pat(
format!(
"{name} {{ {}{} }}",
fields.enumerate().format_with(", ", |(idx, field), f| {
- f(&format_args!("{}${}", field.name(db).display(db.upcast()), idx + 1))
+ f(&format_args!("{}${}", field.name(db).display(db.upcast(), edition), idx + 1))
}),
if fields_omitted { ", .." } else { "" },
name = name
@@ -189,7 +202,7 @@ fn render_record_as_pat(
None => {
format!(
"{name} {{ {}{} }}",
- fields.map(|field| field.name(db).display_no_db().to_smolstr()).format(", "),
+ fields.map(|field| field.name(db).display_no_db(edition).to_smolstr()).format(", "),
if fields_omitted { ", .." } else { "" },
name = name
)
diff --git a/crates/ide-completion/src/render/type_alias.rs b/crates/ide-completion/src/render/type_alias.rs
index b81caf2422..09eb19201c 100644
--- a/crates/ide-completion/src/render/type_alias.rs
+++ b/crates/ide-completion/src/render/type_alias.rs
@@ -33,14 +33,22 @@ fn render(
let (name, escaped_name) = if with_eq {
(
SmolStr::from_iter([&name.unescaped().display(db).to_smolstr(), " = "]),
- SmolStr::from_iter([&name.display_no_db().to_smolstr(), " = "]),
+ SmolStr::from_iter([&name.display_no_db(ctx.completion.edition).to_smolstr(), " = "]),
)
} else {
- (name.unescaped().display(db).to_smolstr(), name.display_no_db().to_smolstr())
+ (
+ name.unescaped().display(db).to_smolstr(),
+ name.display_no_db(ctx.completion.edition).to_smolstr(),
+ )
};
- let detail = type_alias.display(db).to_string();
+ let detail = type_alias.display(db, ctx.completion.edition).to_string();
- let mut item = CompletionItem::new(SymbolKind::TypeAlias, ctx.source_range(), name);
+ let mut item = CompletionItem::new(
+ SymbolKind::TypeAlias,
+ ctx.source_range(),
+ name,
+ ctx.completion.edition,
+ );
item.set_documentation(ctx.docs(type_alias))
.set_deprecated(ctx.is_deprecated(type_alias) || ctx.is_deprecated_assoc_item(type_alias))
.detail(detail)
@@ -48,7 +56,7 @@ fn render(
if let Some(actm) = type_alias.as_assoc_item(db) {
if let Some(trt) = actm.container_or_implemented_trait(db) {
- item.trait_name(trt.name(db).display_no_db().to_smolstr());
+ item.trait_name(trt.name(db).display_no_db(ctx.completion.edition).to_smolstr());
}
}
item.insert_text(escaped_name);
diff --git a/crates/ide-completion/src/render/union_literal.rs b/crates/ide-completion/src/render/union_literal.rs
index ca7593c122..e053e299d9 100644
--- a/crates/ide-completion/src/render/union_literal.rs
+++ b/crates/ide-completion/src/render/union_literal.rs
@@ -22,21 +22,29 @@ pub(crate) fn render_union_literal(
let name = local_name.unwrap_or_else(|| un.name(ctx.db()));
let (qualified_name, escaped_qualified_name) = match path {
- Some(p) => (p.unescaped().display(ctx.db()).to_string(), p.display(ctx.db()).to_string()),
- None => {
- (name.unescaped().display(ctx.db()).to_string(), name.display(ctx.db()).to_string())
- }
+ Some(p) => (
+ p.unescaped().display(ctx.db()).to_string(),
+ p.display(ctx.db(), ctx.completion.edition).to_string(),
+ ),
+ None => (
+ name.unescaped().display(ctx.db()).to_string(),
+ name.display(ctx.db(), ctx.completion.edition).to_string(),
+ ),
};
let label = format_literal_label(
- &name.display_no_db().to_smolstr(),
+ &name.display_no_db(ctx.completion.edition).to_smolstr(),
StructKind::Record,
ctx.snippet_cap(),
);
- let lookup = format_literal_lookup(&name.display_no_db().to_smolstr(), StructKind::Record);
+ let lookup = format_literal_lookup(
+ &name.display_no_db(ctx.completion.edition).to_smolstr(),
+ StructKind::Record,
+ );
let mut item = CompletionItem::new(
CompletionItemKind::SymbolKind(SymbolKind::Union),
ctx.source_range(),
label,
+ ctx.completion.edition,
);
item.lookup_by(lookup);
@@ -54,7 +62,10 @@ pub(crate) fn render_union_literal(
escaped_qualified_name,
fields
.iter()
- .map(|field| field.name(ctx.db()).display_no_db().to_smolstr())
+ .map(|field| field
+ .name(ctx.db())
+ .display_no_db(ctx.completion.edition)
+ .to_smolstr())
.format(",")
)
} else {
@@ -62,7 +73,10 @@ pub(crate) fn render_union_literal(
"{} {{ {} }}",
escaped_qualified_name,
fields.iter().format_with(", ", |field, f| {
- f(&format_args!("{}: ()", field.name(ctx.db()).display(ctx.db())))
+ f(&format_args!(
+ "{}: ()",
+ field.name(ctx.db()).display(ctx.db(), ctx.completion.edition)
+ ))
})
)
};
@@ -73,8 +87,8 @@ pub(crate) fn render_union_literal(
fields.iter().format_with(", ", |field, f| {
f(&format_args!(
"{}: {}",
- field.name(ctx.db()).display(ctx.db()),
- field.ty(ctx.db()).display(ctx.db())
+ field.name(ctx.db()).display(ctx.db(), ctx.completion.edition),
+ field.ty(ctx.db()).display(ctx.db(), ctx.completion.edition)
))
}),
if fields_omitted { ", .." } else { "" }
diff --git a/crates/ide-completion/src/render/variant.rs b/crates/ide-completion/src/render/variant.rs
index bc2df9e39f..d8516ea107 100644
--- a/crates/ide-completion/src/render/variant.rs
+++ b/crates/ide-completion/src/render/variant.rs
@@ -4,7 +4,7 @@ use crate::context::CompletionContext;
use hir::{db::HirDatabase, sym, HasAttrs, HasCrate, HasVisibility, HirDisplay, StructKind};
use ide_db::SnippetCap;
use itertools::Itertools;
-use syntax::SmolStr;
+use syntax::{Edition, SmolStr};
/// A rendered struct, union, or enum variant, split into fields for actual
/// auto-completion (`literal`, using `field: ()`) and display in the
@@ -21,20 +21,29 @@ pub(crate) fn render_record_lit(
snippet_cap: Option<SnippetCap>,
fields: &[hir::Field],
path: &str,
+ edition: Edition,
) -> RenderedLiteral {
if snippet_cap.is_none() {
return RenderedLiteral { literal: path.to_owned(), detail: path.to_owned() };
}
let completions = fields.iter().enumerate().format_with(", ", |(idx, field), f| {
if snippet_cap.is_some() {
- f(&format_args!("{}: ${{{}:()}}", field.name(db).display(db.upcast()), idx + 1))
+ f(&format_args!(
+ "{}: ${{{}:()}}",
+ field.name(db).display(db.upcast(), edition),
+ idx + 1
+ ))
} else {
- f(&format_args!("{}: ()", field.name(db).display(db.upcast())))
+ f(&format_args!("{}: ()", field.name(db).display(db.upcast(), edition)))
}
});
let types = fields.iter().format_with(", ", |field, f| {
- f(&format_args!("{}: {}", field.name(db).display(db.upcast()), field.ty(db).display(db)))
+ f(&format_args!(
+ "{}: {}",
+ field.name(db).display(db.upcast(), edition),
+ field.ty(db).display(db, edition)
+ ))
});
RenderedLiteral {
@@ -50,6 +59,7 @@ pub(crate) fn render_tuple_lit(
snippet_cap: Option<SnippetCap>,
fields: &[hir::Field],
path: &str,
+ edition: Edition,
) -> RenderedLiteral {
if snippet_cap.is_none() {
return RenderedLiteral { literal: path.to_owned(), detail: path.to_owned() };
@@ -62,7 +72,7 @@ pub(crate) fn render_tuple_lit(
}
});
- let types = fields.iter().format_with(", ", |field, f| f(&field.ty(db).display(db)));
+ let types = fields.iter().format_with(", ", |field, f| f(&field.ty(db).display(db, edition)));
RenderedLiteral {
literal: format!("{path}({completions})"),
diff --git a/crates/ide-completion/src/tests.rs b/crates/ide-completion/src/tests.rs
index 415f2afeeb..04ba7e1f41 100644
--- a/crates/ide-completion/src/tests.rs
+++ b/crates/ide-completion/src/tests.rs
@@ -17,6 +17,7 @@ mod item_list;
mod pattern;
mod predicate;
mod proc_macros;
+mod raw_identifiers;
mod record;
mod special;
mod type_pos;
@@ -105,22 +106,35 @@ pub(crate) fn completion_list_with_trigger_character(
completion_list_with_config(TEST_CONFIG, ra_fixture, true, trigger_character)
}
-fn completion_list_with_config(
+fn completion_list_with_config_raw(
config: CompletionConfig,
ra_fixture: &str,
include_keywords: bool,
trigger_character: Option<char>,
-) -> String {
+) -> Vec<CompletionItem> {
// filter out all but one built-in type completion for smaller test outputs
let items = get_all_items(config, ra_fixture, trigger_character);
- let items = items
+ items
.into_iter()
.filter(|it| it.kind != CompletionItemKind::BuiltinType || it.label == "u32")
.filter(|it| include_keywords || it.kind != CompletionItemKind::Keyword)
.filter(|it| include_keywords || it.kind != CompletionItemKind::Snippet)
.sorted_by_key(|it| (it.kind, it.label.clone(), it.detail.as_ref().map(ToOwned::to_owned)))
- .collect();
- render_completion_list(items)
+ .collect()
+}
+
+fn completion_list_with_config(
+ config: CompletionConfig,
+ ra_fixture: &str,
+ include_keywords: bool,
+ trigger_character: Option<char>,
+) -> String {
+ render_completion_list(completion_list_with_config_raw(
+ config,
+ ra_fixture,
+ include_keywords,
+ trigger_character,
+ ))
}
/// Creates analysis from a multi-file fixture, returns positions marked with $0.
diff --git a/crates/ide-completion/src/tests/flyimport.rs b/crates/ide-completion/src/tests/flyimport.rs
index 158dbaf1b1..8350fdcc4c 100644
--- a/crates/ide-completion/src/tests/flyimport.rs
+++ b/crates/ide-completion/src/tests/flyimport.rs
@@ -1633,3 +1633,39 @@ fn main() {
"#]],
);
}
+
+#[test]
+fn trait_impl_on_slice_method_on_deref_slice_type() {
+ check(
+ r#"
+//- minicore: deref, sized
+struct SliceDeref;
+impl core::ops::Deref for SliceDeref {
+ type Target = [()];
+
+ fn deref(&self) -> &Self::Target {
+ &[]
+ }
+}
+fn main() {
+ SliceDeref.choose$0();
+}
+mod module {
+ pub(super) trait SliceRandom {
+ type Item;
+
+ fn choose(&self);
+ }
+
+ impl<T> SliceRandom for [T] {
+ type Item = T;
+
+ fn choose(&self) {}
+ }
+}
+"#,
+ expect![[r#"
+ me choose (use module::SliceRandom) fn(&self)
+ "#]],
+ );
+}
diff --git a/crates/ide-completion/src/tests/predicate.rs b/crates/ide-completion/src/tests/predicate.rs
index 62eb642b3b..46a3e97d3e 100644
--- a/crates/ide-completion/src/tests/predicate.rs
+++ b/crates/ide-completion/src/tests/predicate.rs
@@ -19,7 +19,7 @@ struct Foo<'lt, T, const C: usize> where $0 {}
en Enum Enum
ma makro!(…) macro_rules! makro
md module
- st Foo<…> Foo<{unknown}, _>
+ st Foo<…> Foo<'_, {unknown}, _>
st Record Record
st Tuple Tuple
st Unit Unit
@@ -92,7 +92,7 @@ struct Foo<'lt, T, const C: usize> where for<'a> $0 {}
en Enum Enum
ma makro!(…) macro_rules! makro
md module
- st Foo<…> Foo<{unknown}, _>
+ st Foo<…> Foo<'_, {unknown}, _>
st Record Record
st Tuple Tuple
st Unit Unit
diff --git a/crates/ide-completion/src/tests/raw_identifiers.rs b/crates/ide-completion/src/tests/raw_identifiers.rs
new file mode 100644
index 0000000000..bc630189ed
--- /dev/null
+++ b/crates/ide-completion/src/tests/raw_identifiers.rs
@@ -0,0 +1,84 @@
+use base_db::SourceDatabase;
+use expect_test::{expect, Expect};
+use itertools::Itertools;
+
+use crate::tests::{completion_list_with_config_raw, position, TEST_CONFIG};
+
+fn check(ra_fixture: &str, expect: Expect) {
+ let completions = completion_list_with_config_raw(TEST_CONFIG, ra_fixture, true, None);
+ let (db, position) = position(ra_fixture);
+ let mut actual = db.file_text(position.file_id).to_string();
+ completions
+ .into_iter()
+ .exactly_one()
+ .expect("more than one completion")
+ .text_edit
+ .apply(&mut actual);
+ expect.assert_eq(&actual);
+}
+
+#[test]
+fn keyword_since_edition_completes_without_raw_on_old_edition() {
+ check(
+ r#"
+//- /a.rs crate:a edition:2015
+pub fn dyn() {}
+
+//- /b.rs crate:b edition:2015 deps:a new_source_root:local
+fn foo() {
+ a::dyn$0
+"#,
+ expect![[r#"
+ fn foo() {
+ a::dyn()$0
+ "#]],
+ );
+
+ check(
+ r#"
+//- /a.rs crate:a edition:2018
+pub fn r#dyn() {}
+
+//- /b.rs crate:b edition:2015 deps:a new_source_root:local
+fn foo() {
+ a::dyn$0
+"#,
+ expect![[r#"
+ fn foo() {
+ a::dyn()$0
+ "#]],
+ );
+}
+
+#[test]
+fn keyword_since_edition_completes_with_raw_on_new_edition() {
+ check(
+ r#"
+//- /a.rs crate:a edition:2015
+pub fn dyn() {}
+
+//- /b.rs crate:b edition:2018 deps:a new_source_root:local
+fn foo() {
+ a::dyn$0
+"#,
+ expect![[r#"
+ fn foo() {
+ a::r#dyn()$0
+ "#]],
+ );
+
+ check(
+ r#"
+//- /a.rs crate:a edition:2018
+pub fn r#dyn() {}
+
+//- /b.rs crate:b edition:2018 deps:a new_source_root:local
+fn foo() {
+ a::dyn$0
+"#,
+ expect![[r#"
+ fn foo() {
+ a::r#dyn()$0
+ "#]],
+ );
+}
diff --git a/crates/ide-completion/src/tests/type_pos.rs b/crates/ide-completion/src/tests/type_pos.rs
index ff38c16108..db4ac9381c 100644
--- a/crates/ide-completion/src/tests/type_pos.rs
+++ b/crates/ide-completion/src/tests/type_pos.rs
@@ -20,8 +20,8 @@ struct Foo<'lt, T, const C: usize> {
en Enum Enum
ma makro!(…) macro_rules! makro
md module
- sp Self Foo<{unknown}, _>
- st Foo<…> Foo<{unknown}, _>
+ sp Self Foo<'_, {unknown}, _>
+ st Foo<…> Foo<'_, {unknown}, _>
st Record Record
st Tuple Tuple
st Unit Unit
@@ -45,8 +45,8 @@ struct Foo<'lt, T, const C: usize>(f$0);
en Enum Enum
ma makro!(…) macro_rules! makro
md module
- sp Self Foo<{unknown}, _>
- st Foo<…> Foo<{unknown}, _>
+ sp Self Foo<'_, {unknown}, _>
+ st Foo<…> Foo<'_, {unknown}, _>
st Record Record
st Tuple Tuple
st Unit Unit
diff --git a/crates/ide-db/Cargo.toml b/crates/ide-db/Cargo.toml
index 8f3cae2fa1..c078188d6d 100644
--- a/crates/ide-db/Cargo.toml
+++ b/crates/ide-db/Cargo.toml
@@ -19,7 +19,6 @@ tracing.workspace = true
rayon.workspace = true
fst = { version = "0.4.7", default-features = false }
rustc-hash.workspace = true
-once_cell = "1.17.0"
either.workspace = true
itertools.workspace = true
arrayvec.workspace = true
diff --git a/crates/ide-db/src/defs.rs b/crates/ide-db/src/defs.rs
index 991bef344a..5d4b199908 100644
--- a/crates/ide-db/src/defs.rs
+++ b/crates/ide-db/src/defs.rs
@@ -14,10 +14,11 @@ use hir::{
ModuleDef, Name, PathResolution, Semantics, Static, StaticLifetime, ToolModule, Trait,
TraitAlias, TupleField, TypeAlias, Variant, VariantDef, Visibility,
};
+use span::Edition;
use stdx::{format_to, impl_from};
use syntax::{
ast::{self, AstNode},
- match_ast, SyntaxKind, SyntaxNode, SyntaxToken, ToSmolStr,
+ match_ast, SyntaxKind, SyntaxNode, SyntaxToken,
};
use crate::documentation::{Documentation, HasDocs};
@@ -157,6 +158,7 @@ impl Definition {
&self,
db: &RootDatabase,
famous_defs: Option<&FamousDefs<'_, '_>>,
+ edition: Edition,
) -> Option<Documentation> {
let docs = match self {
Definition::Macro(it) => it.docs(db),
@@ -173,8 +175,8 @@ impl Definition {
Definition::BuiltinType(it) => {
famous_defs.and_then(|fd| {
// std exposes prim_{} modules with docstrings on the root to document the builtins
- let primitive_mod = format!("prim_{}", it.name().display(fd.0.db));
- let doc_owner = find_std_module(fd, &primitive_mod)?;
+ let primitive_mod = format!("prim_{}", it.name().display(fd.0.db, edition));
+ let doc_owner = find_std_module(fd, &primitive_mod, edition)?;
doc_owner.docs(fd.0.db)
})
}
@@ -192,13 +194,18 @@ impl Definition {
let AttributeTemplate { word, list, name_value_str } = it.template(db)?;
let mut docs = "Valid forms are:".to_owned();
if word {
- format_to!(docs, "\n - #\\[{}]", name.display(db));
+ format_to!(docs, "\n - #\\[{}]", name.display(db, edition));
}
if let Some(list) = list {
- format_to!(docs, "\n - #\\[{}({})]", name.display(db), list);
+ format_to!(docs, "\n - #\\[{}({})]", name.display(db, edition), list);
}
if let Some(name_value_str) = name_value_str {
- format_to!(docs, "\n - #\\[{} = {}]", name.display(db), name_value_str);
+ format_to!(
+ docs,
+ "\n - #\\[{} = {}]",
+ name.display(db, edition),
+ name_value_str
+ );
}
Some(Documentation::new(docs.replace('*', "\\*")))
}
@@ -218,57 +225,63 @@ impl Definition {
})
}
- pub fn label(&self, db: &RootDatabase) -> String {
+ pub fn label(&self, db: &RootDatabase, edition: Edition) -> String {
match *self {
- Definition::Macro(it) => it.display(db).to_string(),
- Definition::Field(it) => it.display(db).to_string(),
- Definition::TupleField(it) => it.display(db).to_string(),
- Definition::Module(it) => it.display(db).to_string(),
- Definition::Function(it) => it.display(db).to_string(),
- Definition::Adt(it) => it.display(db).to_string(),
- Definition::Variant(it) => it.display(db).to_string(),
- Definition::Const(it) => it.display(db).to_string(),
- Definition::Static(it) => it.display(db).to_string(),
- Definition::Trait(it) => it.display(db).to_string(),
- Definition::TraitAlias(it) => it.display(db).to_string(),
- Definition::TypeAlias(it) => it.display(db).to_string(),
- Definition::BuiltinType(it) => it.name().display(db).to_string(),
- Definition::BuiltinLifetime(it) => it.name().display(db).to_string(),
+ Definition::Macro(it) => it.display(db, edition).to_string(),
+ Definition::Field(it) => it.display(db, edition).to_string(),
+ Definition::TupleField(it) => it.display(db, edition).to_string(),
+ Definition::Module(it) => it.display(db, edition).to_string(),
+ Definition::Function(it) => it.display(db, edition).to_string(),
+ Definition::Adt(it) => it.display(db, edition).to_string(),
+ Definition::Variant(it) => it.display(db, edition).to_string(),
+ Definition::Const(it) => it.display(db, edition).to_string(),
+ Definition::Static(it) => it.display(db, edition).to_string(),
+ Definition::Trait(it) => it.display(db, edition).to_string(),
+ Definition::TraitAlias(it) => it.display(db, edition).to_string(),
+ Definition::TypeAlias(it) => it.display(db, edition).to_string(),
+ Definition::BuiltinType(it) => it.name().display(db, edition).to_string(),
+ Definition::BuiltinLifetime(it) => it.name().display(db, edition).to_string(),
Definition::Local(it) => {
let ty = it.ty(db);
- let ty_display = ty.display_truncated(db, None);
+ let ty_display = ty.display_truncated(db, None, edition);
let is_mut = if it.is_mut(db) { "mut " } else { "" };
if it.is_self(db) {
format!("{is_mut}self: {ty_display}")
} else {
let name = it.name(db);
let let_kw = if it.is_param(db) { "" } else { "let " };
- format!("{let_kw}{is_mut}{}: {ty_display}", name.display(db))
+ format!("{let_kw}{is_mut}{}: {ty_display}", name.display(db, edition))
}
}
Definition::SelfType(impl_def) => {
let self_ty = &impl_def.self_ty(db);
match self_ty.as_adt() {
- Some(it) => it.display(db).to_string(),
- None => self_ty.display(db).to_string(),
+ Some(it) => it.display(db, edition).to_string(),
+ None => self_ty.display(db, edition).to_string(),
}
}
- Definition::GenericParam(it) => it.display(db).to_string(),
- Definition::Label(it) => it.name(db).display(db).to_string(),
- Definition::ExternCrateDecl(it) => it.display(db).to_string(),
- Definition::BuiltinAttr(it) => format!("#[{}]", it.name(db).display(db)),
- Definition::ToolModule(it) => it.name(db).display(db).to_string(),
- Definition::DeriveHelper(it) => format!("derive_helper {}", it.name(db).display(db)),
+ Definition::GenericParam(it) => it.display(db, edition).to_string(),
+ Definition::Label(it) => it.name(db).display(db, edition).to_string(),
+ Definition::ExternCrateDecl(it) => it.display(db, edition).to_string(),
+ Definition::BuiltinAttr(it) => format!("#[{}]", it.name(db).display(db, edition)),
+ Definition::ToolModule(it) => it.name(db).display(db, edition).to_string(),
+ Definition::DeriveHelper(it) => {
+ format!("derive_helper {}", it.name(db).display(db, edition))
+ }
}
}
}
-fn find_std_module(famous_defs: &FamousDefs<'_, '_>, name: &str) -> Option<hir::Module> {
+fn find_std_module(
+ famous_defs: &FamousDefs<'_, '_>,
+ name: &str,
+ edition: Edition,
+) -> Option<hir::Module> {
let db = famous_defs.0.db;
let std_crate = famous_defs.std()?;
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)
+ module.name(db).map_or(false, |module| module.display(db, edition).to_string() == name)
})
}
@@ -670,7 +683,7 @@ impl NameRefClass {
hir::AssocItem::TypeAlias(it) => Some(it),
_ => None,
})
- .find(|alias| alias.name(sema.db).display_no_db().to_smolstr() == name_ref.text().as_str())
+ .find(|alias| alias.name(sema.db).eq_ident(name_ref.text().as_str()))
{
return Some(NameRefClass::Definition(Definition::TypeAlias(ty)));
}
diff --git a/crates/ide-db/src/famous_defs.rs b/crates/ide-db/src/famous_defs.rs
index 1a16a567f3..ba6e50abf6 100644
--- a/crates/ide-db/src/famous_defs.rs
+++ b/crates/ide-db/src/famous_defs.rs
@@ -2,7 +2,6 @@
use base_db::{CrateOrigin, LangCrateOrigin, SourceDatabase};
use hir::{Crate, Enum, Function, Macro, Module, ScopeDef, Semantics, Trait};
-use syntax::ToSmolStr;
use crate::RootDatabase;
@@ -199,18 +198,14 @@ impl FamousDefs<'_, '_> {
for segment in path {
module = module.children(db).find_map(|child| {
let name = child.name(db)?;
- if name.display_no_db().to_smolstr() == segment {
+ if name.eq_ident(segment) {
Some(child)
} else {
None
}
})?;
}
- let def = module
- .scope(db, None)
- .into_iter()
- .find(|(name, _def)| name.display_no_db().to_smolstr() == trait_)?
- .1;
+ let def = module.scope(db, None).into_iter().find(|(name, _def)| name.eq_ident(trait_))?.1;
Some(def)
}
}
diff --git a/crates/ide-db/src/helpers.rs b/crates/ide-db/src/helpers.rs
index e6638dde5d..84fa58d743 100644
--- a/crates/ide-db/src/helpers.rs
+++ b/crates/ide-db/src/helpers.rs
@@ -3,8 +3,8 @@
use std::collections::VecDeque;
use base_db::SourceRootDatabase;
-use hir::{Crate, DescendPreference, ItemInNs, ModuleDef, Name, Semantics};
-use span::FileId;
+use hir::{Crate, ItemInNs, ModuleDef, Name, Semantics};
+use span::{Edition, FileId};
use syntax::{
ast::{self, make},
AstToken, SyntaxKind, SyntaxToken, ToSmolStr, TokenAtOffset,
@@ -35,7 +35,7 @@ pub fn pick_token<T: AstToken>(mut tokens: TokenAtOffset<SyntaxToken>) -> Option
}
/// Converts the mod path struct into its ast representation.
-pub fn mod_path_to_ast(path: &hir::ModPath) -> ast::Path {
+pub fn mod_path_to_ast(path: &hir::ModPath, edition: Edition) -> ast::Path {
let _p = tracing::info_span!("mod_path_to_ast").entered();
let mut segments = Vec::new();
@@ -50,11 +50,9 @@ pub fn mod_path_to_ast(path: &hir::ModPath) -> ast::Path {
hir::PathKind::Abs => is_abs = true,
}
- segments.extend(
- path.segments().iter().map(|segment| {
- make::path_segment(make::name_ref(&segment.display_no_db().to_smolstr()))
- }),
- );
+ segments.extend(path.segments().iter().map(|segment| {
+ make::path_segment(make::name_ref(&segment.display_no_db(edition).to_smolstr()))
+ }));
make::path_from_segments(segments, is_abs)
}
@@ -114,11 +112,12 @@ pub fn is_editable_crate(krate: Crate, db: &RootDatabase) -> bool {
!db.source_root(source_root_id).is_library
}
+// FIXME: This is a weird function
pub fn get_definition(
sema: &Semantics<'_, RootDatabase>,
token: SyntaxToken,
) -> Option<Definition> {
- for token in sema.descend_into_macros(DescendPreference::None, token) {
+ for token in sema.descend_into_macros_exact(token) {
let def = IdentClass::classify_token(sema, &token).map(IdentClass::definitions_no_ops);
if let Some(&[x]) = def.as_deref() {
return Some(x);
diff --git a/crates/ide-db/src/imports/import_assets.rs b/crates/ide-db/src/imports/import_assets.rs
index 1c4c15f255..82a182806a 100644
--- a/crates/ide-db/src/imports/import_assets.rs
+++ b/crates/ide-db/src/imports/import_assets.rs
@@ -9,7 +9,7 @@ use itertools::{EitherOrBoth, Itertools};
use rustc_hash::{FxHashMap, FxHashSet};
use syntax::{
ast::{self, make, HasName},
- AstNode, SmolStr, SyntaxNode, ToSmolStr,
+ AstNode, SmolStr, SyntaxNode,
};
use crate::{
@@ -459,7 +459,7 @@ fn find_import_for_segment(
unresolved_first_segment: &str,
) -> Option<ItemInNs> {
let segment_is_name = item_name(db, original_item)
- .map(|name| name.display_no_db().to_smolstr() == unresolved_first_segment)
+ .map(|name| name.eq_ident(unresolved_first_segment))
.unwrap_or(false);
Some(if segment_is_name {
@@ -483,7 +483,7 @@ fn module_with_segment_name(
};
while let Some(module) = current_module {
if let Some(module_name) = module.name(db) {
- if module_name.display_no_db().to_smolstr() == segment_name {
+ if module_name.eq_ident(segment_name) {
return Some(module);
}
}
@@ -531,40 +531,61 @@ fn trait_applicable_items(
})
.collect();
- trait_candidates.retain(|&candidate_trait_id| {
- // we care about the following cases:
- // 1. Trait's definition crate
- // 2. Definition crates for all trait's generic arguments
- // a. This is recursive for fundamental types: `Into<Box<A>> for ()`` is OK, but
- // `Into<Vec<A>> for ()`` is *not*.
- // 3. Receiver type definition crate
- // a. This is recursive for fundamental types
- let defining_crate_for_trait = Trait::from(candidate_trait_id).krate(db);
- let Some(receiver) = trait_candidate.receiver_ty.fingerprint_for_trait_impl() else {
- return false;
- };
-
- // in order to handle implied bounds through an associated type, keep any
- // method receiver that matches `TyFingerprint::Unnameable`. this receiver
- // won't be in `TraitImpls` anyways, as `TraitImpls` only contains actual
- // implementations.
- if matches!(receiver, TyFingerprint::Unnameable) {
- return true;
+ let autoderef_method_receiver = {
+ let mut deref_chain = trait_candidate.receiver_ty.autoderef(db).collect::<Vec<_>>();
+ // As a last step, we can do array unsizing (that's the only unsizing that rustc does for method receivers!)
+ if let Some((ty, _len)) = deref_chain.last().and_then(|ty| ty.as_array(db)) {
+ let slice = Type::new_slice(ty);
+ deref_chain.push(slice);
}
+ deref_chain
+ .into_iter()
+ .filter_map(|ty| Some((ty.krate(db).into(), ty.fingerprint_for_trait_impl()?)))
+ .sorted()
+ .unique()
+ .collect::<Vec<_>>()
+ };
- let definitions_exist_in_trait_crate = db
- .trait_impls_in_crate(defining_crate_for_trait.into())
- .has_impls_for_trait_and_self_ty(candidate_trait_id, receiver);
+ // can be empty if the entire deref chain is has no valid trait impl fingerprints
+ if autoderef_method_receiver.is_empty() {
+ return Default::default();
+ }
- // this is a closure for laziness: if `definitions_exist_in_trait_crate` is true,
- // we can avoid a second db lookup.
- let definitions_exist_in_receiver_crate = || {
- db.trait_impls_in_crate(trait_candidate.receiver_ty.krate(db).into())
- .has_impls_for_trait_and_self_ty(candidate_trait_id, receiver)
- };
+ // in order to handle implied bounds through an associated type, keep all traits if any
+ // type in the deref chain matches `TyFingerprint::Unnameable`. This fingerprint
+ // won't be in `TraitImpls` anyways, as `TraitImpls` only contains actual implementations.
+ if !autoderef_method_receiver
+ .iter()
+ .any(|(_, fingerprint)| matches!(fingerprint, TyFingerprint::Unnameable))
+ {
+ trait_candidates.retain(|&candidate_trait_id| {
+ // we care about the following cases:
+ // 1. Trait's definition crate
+ // 2. Definition crates for all trait's generic arguments
+ // a. This is recursive for fundamental types: `Into<Box<A>> for ()`` is OK, but
+ // `Into<Vec<A>> for ()`` is *not*.
+ // 3. Receiver type definition crate
+ // a. This is recursive for fundamental types
+ let defining_crate_for_trait = Trait::from(candidate_trait_id).krate(db);
+
+ let trait_impls_in_crate = db.trait_impls_in_crate(defining_crate_for_trait.into());
+ let definitions_exist_in_trait_crate =
+ autoderef_method_receiver.iter().any(|&(_, fingerprint)| {
+ trait_impls_in_crate
+ .has_impls_for_trait_and_self_ty(candidate_trait_id, fingerprint)
+ });
+ // this is a closure for laziness: if `definitions_exist_in_trait_crate` is true,
+ // we can avoid a second db lookup.
+ let definitions_exist_in_receiver_crate = || {
+ autoderef_method_receiver.iter().any(|&(krate, fingerprint)| {
+ db.trait_impls_in_crate(krate)
+ .has_impls_for_trait_and_self_ty(candidate_trait_id, fingerprint)
+ })
+ };
- definitions_exist_in_trait_crate || definitions_exist_in_receiver_crate()
- });
+ definitions_exist_in_trait_crate || definitions_exist_in_receiver_crate()
+ });
+ }
let mut located_imports = FxIndexSet::default();
let mut trait_import_paths = FxHashMap::default();
diff --git a/crates/ide-db/src/path_transform.rs b/crates/ide-db/src/path_transform.rs
index 0afa9163e3..49b3ca290f 100644
--- a/crates/ide-db/src/path_transform.rs
+++ b/crates/ide-db/src/path_transform.rs
@@ -5,6 +5,7 @@ use either::Either;
use hir::{AsAssocItem, HirDisplay, ImportPathConfig, ModuleDef, SemanticsScope};
use itertools::Itertools;
use rustc_hash::FxHashMap;
+use span::Edition;
use syntax::{
ast::{self, make, AstNode, HasGenericArgs},
ted, NodeOrToken, SyntaxNode,
@@ -146,6 +147,7 @@ impl<'a> PathTransform<'a> {
let mut type_substs: FxHashMap<hir::TypeParam, ast::Type> = Default::default();
let mut const_substs: FxHashMap<hir::ConstParam, SyntaxNode> = Default::default();
let mut defaulted_params: Vec<DefaultedParam> = Default::default();
+ let target_edition = target_module.krate().edition(self.source_scope.db);
self.generic_def
.into_iter()
.flat_map(|it| it.type_or_const_params(db))
@@ -190,7 +192,7 @@ impl<'a> PathTransform<'a> {
}
}
(Either::Left(k), None) => {
- if let Some(default) = k.default(db) {
+ if let Some(default) = k.default(db, target_edition) {
if let Some(default) = default.expr() {
const_substs.insert(k, default.syntax().clone_for_update());
defaulted_params.push(Either::Right(k));
@@ -204,7 +206,9 @@ impl<'a> PathTransform<'a> {
.into_iter()
.flat_map(|it| it.lifetime_params(db))
.zip(self.substs.lifetimes.clone())
- .filter_map(|(k, v)| Some((k.name(db).display(db.upcast()).to_string(), v.lifetime()?)))
+ .filter_map(|(k, v)| {
+ Some((k.name(db).display(db.upcast(), target_edition).to_string(), v.lifetime()?))
+ })
.collect();
let ctx = Ctx {
type_substs,
@@ -213,6 +217,7 @@ impl<'a> PathTransform<'a> {
target_module,
source_scope: self.source_scope,
same_self_type: self.target_scope.has_same_self_type(self.source_scope),
+ target_edition,
};
ctx.transform_default_values(defaulted_params);
ctx
@@ -226,6 +231,7 @@ struct Ctx<'a> {
target_module: hir::Module,
source_scope: &'a SemanticsScope<'a>,
same_self_type: bool,
+ target_edition: Edition,
}
fn preorder_rev(item: &SyntaxNode) -> impl Iterator<Item = SyntaxNode> {
@@ -318,7 +324,7 @@ impl Ctx<'_> {
hir::ModuleDef::Trait(trait_ref),
cfg,
)?;
- match make::ty_path(mod_path_to_ast(&found_path)) {
+ match make::ty_path(mod_path_to_ast(&found_path, self.target_edition)) {
ast::Type::PathType(path_ty) => Some(path_ty),
_ => None,
}
@@ -374,7 +380,7 @@ impl Ctx<'_> {
};
let found_path =
self.target_module.find_path(self.source_scope.db.upcast(), def, cfg)?;
- let res = mod_path_to_ast(&found_path).clone_for_update();
+ let res = mod_path_to_ast(&found_path, self.target_edition).clone_for_update();
if let Some(args) = path.segment().and_then(|it| it.generic_arg_list()) {
if let Some(segment) = res.segment() {
let old = segment.get_or_create_generic_arg_list();
@@ -417,7 +423,9 @@ impl Ctx<'_> {
cfg,
)?;
- if let Some(qual) = mod_path_to_ast(&found_path).qualifier() {
+ if let Some(qual) =
+ mod_path_to_ast(&found_path, self.target_edition).qualifier()
+ {
let res = make::path_concat(qual, path_ty.path()?).clone_for_update();
ted::replace(path.syntax(), res.syntax());
return Some(());
diff --git a/crates/ide-db/src/rename.rs b/crates/ide-db/src/rename.rs
index 232f242828..262eefeec0 100644
--- a/crates/ide-db/src/rename.rs
+++ b/crates/ide-db/src/rename.rs
@@ -29,6 +29,7 @@ use span::{Edition, EditionedFileId, FileId, SyntaxContextId};
use stdx::{never, TupleExt};
use syntax::{
ast::{self, HasName},
+ utils::is_raw_identifier,
AstNode, SyntaxKind, TextRange, T,
};
use text_edit::{TextEdit, TextEditBuilder};
@@ -72,6 +73,9 @@ impl Definition {
sema: &Semantics<'_, RootDatabase>,
new_name: &str,
) -> Result<SourceChange> {
+ // We append `r#` if needed.
+ let new_name = new_name.trim_start_matches("r#");
+
// self.krate() returns None if
// self is a built-in attr, built-in type or tool module.
// it is not allowed for these defs to be renamed.
@@ -227,8 +231,7 @@ fn rename_mod(
module: hir::Module,
new_name: &str,
) -> Result<SourceChange> {
- if IdentifierKind::classify(module.krate().edition(sema.db), new_name)? != IdentifierKind::Ident
- {
+ if IdentifierKind::classify(new_name)? != IdentifierKind::Ident {
bail!("Invalid name `{0}`: cannot rename module to {0}", new_name);
}
@@ -240,7 +243,6 @@ fn rename_mod(
let InFile { file_id, value: def_source } = module.definition_source(sema.db);
if let ModuleSource::SourceFile(..) = def_source {
- let new_name = new_name.trim_start_matches("r#");
let anchor = file_id.original_file(sema.db).file_id();
let is_mod_rs = module.is_mod_rs(sema.db);
@@ -289,9 +291,14 @@ fn rename_mod(
.original_file_range_opt(sema.db)
.map(TupleExt::head)
{
+ let new_name = if is_raw_identifier(new_name, file_id.edition()) {
+ format!("r#{new_name}")
+ } else {
+ new_name.to_owned()
+ };
source_change.insert_source_edit(
file_id.file_id(),
- TextEdit::replace(file_range.range, new_name.to_owned()),
+ TextEdit::replace(file_range.range, new_name),
)
};
}
@@ -302,7 +309,10 @@ fn rename_mod(
let def = Definition::Module(module);
let usages = def.usages(sema).all();
let ref_edits = usages.iter().map(|(file_id, references)| {
- (EditionedFileId::file_id(file_id), source_edit_from_references(references, def, new_name))
+ (
+ EditionedFileId::file_id(file_id),
+ source_edit_from_references(references, def, new_name, file_id.edition()),
+ )
});
source_change.extend(ref_edits);
@@ -314,12 +324,7 @@ fn rename_reference(
def: Definition,
new_name: &str,
) -> Result<SourceChange> {
- let ident_kind = IdentifierKind::classify(
- def.krate(sema.db)
- .ok_or_else(|| RenameError("definition has no krate?".into()))?
- .edition(sema.db),
- new_name,
- )?;
+ let ident_kind = IdentifierKind::classify(new_name)?;
if matches!(
def,
@@ -351,7 +356,10 @@ fn rename_reference(
}
let mut source_change = SourceChange::default();
source_change.extend(usages.iter().map(|(file_id, references)| {
- (EditionedFileId::file_id(file_id), source_edit_from_references(references, def, new_name))
+ (
+ EditionedFileId::file_id(file_id),
+ source_edit_from_references(references, def, new_name, file_id.edition()),
+ )
}));
let mut insert_def_edit = |def| {
@@ -367,7 +375,13 @@ pub fn source_edit_from_references(
references: &[FileReference],
def: Definition,
new_name: &str,
+ edition: Edition,
) -> TextEdit {
+ let new_name = if is_raw_identifier(new_name, edition) {
+ format!("r#{new_name}")
+ } else {
+ new_name.to_owned()
+ };
let mut edit = TextEdit::builder();
// macros can cause multiple refs to occur for the same text range, so keep track of what we have edited so far
let mut edited_ranges = Vec::new();
@@ -383,10 +397,10 @@ pub fn source_edit_from_references(
// to make special rewrites like shorthand syntax and such, so just rename the node in
// the macro input
FileReferenceNode::NameRef(name_ref) if name_range == range => {
- source_edit_from_name_ref(&mut edit, name_ref, new_name, def)
+ source_edit_from_name_ref(&mut edit, name_ref, &new_name, def)
}
FileReferenceNode::Name(name) if name_range == range => {
- source_edit_from_name(&mut edit, name, new_name)
+ source_edit_from_name(&mut edit, name, &new_name)
}
_ => false,
};
@@ -394,7 +408,7 @@ pub fn source_edit_from_references(
let (range, new_name) = match name {
FileReferenceNode::Lifetime(_) => (
TextRange::new(range.start() + syntax::TextSize::from(1), range.end()),
- new_name.strip_prefix('\'').unwrap_or(new_name).to_owned(),
+ new_name.strip_prefix('\'').unwrap_or(&new_name).to_owned(),
),
_ => (range, new_name.to_owned()),
};
@@ -522,6 +536,13 @@ fn source_edit_from_def(
def: Definition,
new_name: &str,
) -> Result<(FileId, TextEdit)> {
+ let new_name_edition_aware = |new_name: &str, file_id: EditionedFileId| {
+ if is_raw_identifier(new_name, file_id.edition()) {
+ format!("r#{new_name}")
+ } else {
+ new_name.to_owned()
+ }
+ };
let mut edit = TextEdit::builder();
if let Definition::Local(local) = def {
let mut file_id = None;
@@ -536,7 +557,7 @@ fn source_edit_from_def(
{
Some(FileRange { file_id: file_id2, range }) => {
file_id = Some(file_id2);
- edit.replace(range, new_name.to_owned());
+ edit.replace(range, new_name_edition_aware(new_name, file_id2));
continue;
}
None => {
@@ -550,7 +571,9 @@ fn source_edit_from_def(
// special cases required for renaming fields/locals in Record patterns
if let Some(pat_field) = pat.syntax().parent().and_then(ast::RecordPatField::cast) {
if let Some(name_ref) = pat_field.name_ref() {
- if new_name == name_ref.text() && pat.at_token().is_none() {
+ if new_name == name_ref.text().as_str().trim_start_matches("r#")
+ && pat.at_token().is_none()
+ {
// Foo { field: ref mut local } -> Foo { ref mut field }
// ^^^^^^ delete this
// ^^^^^ replace this with `field`
@@ -566,7 +589,10 @@ fn source_edit_from_def(
// Foo { field: ref mut local @ local 2} -> Foo { field: ref mut new_name @ local2 }
// Foo { field: ref mut local } -> Foo { field: ref mut new_name }
// ^^^^^ replace this with `new_name`
- edit.replace(name_range, new_name.to_owned());
+ edit.replace(
+ name_range,
+ new_name_edition_aware(new_name, source.file_id),
+ );
}
} else {
// Foo { ref mut field } -> Foo { field: ref mut new_name }
@@ -576,10 +602,10 @@ fn source_edit_from_def(
pat.syntax().text_range().start(),
format!("{}: ", pat_field.field_name().unwrap()),
);
- edit.replace(name_range, new_name.to_owned());
+ edit.replace(name_range, new_name_edition_aware(new_name, source.file_id));
}
} else {
- edit.replace(name_range, new_name.to_owned());
+ edit.replace(name_range, new_name_edition_aware(new_name, source.file_id));
}
}
}
@@ -599,7 +625,7 @@ fn source_edit_from_def(
}
_ => (range, new_name.to_owned()),
};
- edit.replace(range, new_name);
+ edit.replace(range, new_name_edition_aware(&new_name, file_id));
Ok((file_id.file_id(), edit.finish()))
}
@@ -611,8 +637,9 @@ pub enum IdentifierKind {
}
impl IdentifierKind {
- pub fn classify(edition: Edition, new_name: &str) -> Result<IdentifierKind> {
- match parser::LexedStr::single_token(edition, new_name) {
+ pub fn classify(new_name: &str) -> Result<IdentifierKind> {
+ let new_name = new_name.trim_start_matches("r#");
+ match parser::LexedStr::single_token(Edition::LATEST, new_name) {
Some(res) => match res {
(SyntaxKind::IDENT, _) => {
if let Some(inner) = new_name.strip_prefix("r#") {
@@ -626,6 +653,7 @@ impl IdentifierKind {
(SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => {
Ok(IdentifierKind::Lifetime)
}
+ _ if is_raw_identifier(new_name, Edition::LATEST) => Ok(IdentifierKind::Ident),
(_, Some(syntax_error)) => bail!("Invalid name `{}`: {}", new_name, syntax_error),
(_, None) => bail!("Invalid name `{}`: not an identifier", new_name),
},
diff --git a/crates/ide-db/src/search.rs b/crates/ide-db/src/search.rs
index 9e01a6d440..12ce5a403f 100644
--- a/crates/ide-db/src/search.rs
+++ b/crates/ide-db/src/search.rs
@@ -5,18 +5,23 @@
//! name resolution.
use std::mem;
+use std::{cell::LazyCell, cmp::Reverse};
use base_db::{salsa::Database, SourceDatabase, SourceRootDatabase};
use hir::{
- sym, AsAssocItem, DefWithBody, DescendPreference, FileRange, HasAttrs, HasSource, HirFileIdExt,
- InFile, InRealFile, ModuleSource, PathResolution, Semantics, Visibility,
+ sym, Adt, AsAssocItem, DefWithBody, FileRange, FileRangeWrapper, HasAttrs, HasContainer,
+ HasSource, HirFileIdExt, InFile, InFileWrapper, InRealFile, ItemContainer, ModuleSource,
+ PathResolution, Semantics, Visibility,
};
use memchr::memmem::Finder;
-use once_cell::unsync::Lazy;
use parser::SyntaxKind;
-use rustc_hash::FxHashMap;
+use rustc_hash::{FxHashMap, FxHashSet};
use span::EditionedFileId;
-use syntax::{ast, match_ast, AstNode, AstToken, SyntaxElement, TextRange, TextSize, ToSmolStr};
+use syntax::{
+ ast::{self, HasName},
+ match_ast, AstNode, AstToken, SmolStr, SyntaxElement, SyntaxNode, TextRange, TextSize,
+ ToSmolStr,
+};
use triomphe::Arc;
use crate::{
@@ -442,6 +447,411 @@ impl<'a> FindUsages<'a> {
res
}
+ fn scope_files<'b>(
+ db: &'b RootDatabase,
+ scope: &'b SearchScope,
+ ) -> impl Iterator<Item = (Arc<str>, EditionedFileId, TextRange)> + 'b {
+ scope.entries.iter().map(|(&file_id, &search_range)| {
+ let text = db.file_text(file_id.file_id());
+ let search_range =
+ search_range.unwrap_or_else(|| TextRange::up_to(TextSize::of(&*text)));
+
+ (text, file_id, search_range)
+ })
+ }
+
+ fn match_indices<'b>(
+ text: &'b str,
+ finder: &'b Finder<'b>,
+ search_range: TextRange,
+ ) -> impl Iterator<Item = TextSize> + 'b {
+ finder.find_iter(text.as_bytes()).filter_map(move |idx| {
+ let offset: TextSize = idx.try_into().unwrap();
+ if !search_range.contains_inclusive(offset) {
+ return None;
+ }
+ // If this is not a word boundary, that means this is only part of an identifier,
+ // so it can't be what we're looking for.
+ // This speeds up short identifiers significantly.
+ if text[..idx]
+ .chars()
+ .next_back()
+ .is_some_and(|ch| matches!(ch, 'A'..='Z' | 'a'..='z' | '_'))
+ || text[idx + finder.needle().len()..]
+ .chars()
+ .next()
+ .is_some_and(|ch| matches!(ch, 'A'..='Z' | 'a'..='z' | '_' | '0'..='9'))
+ {
+ return None;
+ }
+ Some(offset)
+ })
+ }
+
+ fn find_nodes<'b>(
+ sema: &'b Semantics<'_, RootDatabase>,
+ name: &str,
+ node: &syntax::SyntaxNode,
+ offset: TextSize,
+ ) -> impl Iterator<Item = SyntaxNode> + 'b {
+ node.token_at_offset(offset)
+ .find(|it| {
+ // `name` is stripped of raw ident prefix. See the comment on name retrieval below.
+ it.text().trim_start_matches("r#") == name
+ })
+ .into_iter()
+ .flat_map(move |token| {
+ sema.descend_into_macros_exact_if_in_macro(token)
+ .into_iter()
+ .filter_map(|it| it.parent())
+ })
+ }
+
+ /// Performs a special fast search for associated functions. This is mainly intended
+ /// to speed up `new()` which can take a long time.
+ ///
+ /// The trick is instead of searching for `func_name` search for `TypeThatContainsContainerName::func_name`.
+ /// We cannot search exactly that (not even in tokens), because `ContainerName` may be aliased.
+ /// Instead, we perform a textual search for `ContainerName`. Then, we look for all cases where
+ /// `ContainerName` may be aliased (that includes `use ContainerName as Xyz` and
+ /// `type Xyz = ContainerName`). We collect a list of all possible aliases of `ContainerName`.
+ /// The list can have false positives (because there may be multiple types named `ContainerName`),
+ /// but it cannot have false negatives. Then, we look for `TypeThatContainsContainerNameOrAnyAlias::func_name`.
+ /// Those that will be found are of high chance to be actual hits (of course, we will need to verify
+ /// that).
+ ///
+ /// Returns true if completed the search.
+ // FIXME: Extend this to other cases, such as associated types/consts/enum variants (note those can be `use`d).
+ fn short_associated_function_fast_search(
+ &self,
+ sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
+ search_scope: &SearchScope,
+ name: &str,
+ ) -> bool {
+ if self.scope.is_some() {
+ return false;
+ }
+
+ let _p = tracing::info_span!("short_associated_function_fast_search").entered();
+
+ let container = (|| {
+ let Definition::Function(function) = self.def else {
+ return None;
+ };
+ if function.has_self_param(self.sema.db) {
+ return None;
+ }
+ match function.container(self.sema.db) {
+ // Only freestanding `impl`s qualify; methods from trait
+ // can be called from within subtraits and bounds.
+ ItemContainer::Impl(impl_) => {
+ let has_trait = impl_.trait_(self.sema.db).is_some();
+ if has_trait {
+ return None;
+ }
+ let adt = impl_.self_ty(self.sema.db).as_adt()?;
+ Some(adt)
+ }
+ _ => None,
+ }
+ })();
+ let Some(container) = container else {
+ return false;
+ };
+
+ fn has_any_name(node: &SyntaxNode, mut predicate: impl FnMut(&str) -> bool) -> bool {
+ node.descendants().any(|node| {
+ match_ast! {
+ match node {
+ ast::Name(it) => predicate(it.text().trim_start_matches("r#")),
+ ast::NameRef(it) => predicate(it.text().trim_start_matches("r#")),
+ _ => false
+ }
+ }
+ })
+ }
+
+ // This is a fixpoint algorithm with O(number of aliases), but most types have no or few aliases,
+ // so this should stay fast.
+ //
+ /// Returns `(aliases, ranges_where_Self_can_refer_to_our_type)`.
+ fn collect_possible_aliases(
+ sema: &Semantics<'_, RootDatabase>,
+ container: Adt,
+ ) -> Option<(FxHashSet<SmolStr>, Vec<FileRangeWrapper<EditionedFileId>>)> {
+ fn insert_type_alias(
+ db: &RootDatabase,
+ to_process: &mut Vec<(SmolStr, SearchScope)>,
+ alias_name: &str,
+ def: Definition,
+ ) {
+ let alias = alias_name.trim_start_matches("r#").to_smolstr();
+ tracing::debug!("found alias: {alias}");
+ to_process.push((alias, def.search_scope(db)));
+ }
+
+ let _p = tracing::info_span!("collect_possible_aliases").entered();
+
+ let db = sema.db;
+ let container_name = container.name(db).unescaped().display(db).to_smolstr();
+ let search_scope = Definition::from(container).search_scope(db);
+ let mut seen = FxHashSet::default();
+ let mut completed = FxHashSet::default();
+ let mut to_process = vec![(container_name, search_scope)];
+ let mut is_possibly_self = Vec::new();
+ let mut total_files_searched = 0;
+
+ while let Some((current_to_process, current_to_process_search_scope)) = to_process.pop()
+ {
+ let is_alias = |alias: &ast::TypeAlias| {
+ let def = sema.to_def(alias)?;
+ let ty = def.ty(db);
+ let is_alias = ty.as_adt()? == container;
+ is_alias.then_some(def)
+ };
+
+ let finder = Finder::new(current_to_process.as_bytes());
+ for (file_text, file_id, search_range) in
+ FindUsages::scope_files(db, &current_to_process_search_scope)
+ {
+ let tree = LazyCell::new(move || sema.parse(file_id).syntax().clone());
+
+ for offset in FindUsages::match_indices(&file_text, &finder, search_range) {
+ let usages =
+ FindUsages::find_nodes(sema, &current_to_process, &tree, offset)
+ .filter(|it| {
+ matches!(it.kind(), SyntaxKind::NAME | SyntaxKind::NAME_REF)
+ });
+ for usage in usages {
+ if let Some(alias) = usage.parent().and_then(|it| {
+ let path = ast::PathSegment::cast(it)?.parent_path();
+ let use_tree = ast::UseTree::cast(path.syntax().parent()?)?;
+ use_tree.rename()?.name()
+ }) {
+ if seen.insert(InFileWrapper::new(
+ file_id,
+ alias.syntax().text_range(),
+ )) {
+ tracing::debug!("found alias: {alias}");
+ cov_mark::hit!(container_use_rename);
+ // FIXME: `use`s have no easy way to determine their search scope, but they are rare.
+ to_process.push((
+ alias.text().to_smolstr(),
+ current_to_process_search_scope.clone(),
+ ));
+ }
+ } else if let Some(alias) =
+ usage.ancestors().find_map(ast::TypeAlias::cast)
+ {
+ if let Some(name) = alias.name() {
+ if seen.insert(InFileWrapper::new(
+ file_id,
+ name.syntax().text_range(),
+ )) {
+ if let Some(def) = is_alias(&alias) {
+ cov_mark::hit!(container_type_alias);
+ insert_type_alias(
+ sema.db,
+ &mut to_process,
+ name.text().as_str(),
+ def.into(),
+ );
+ } else {
+ cov_mark::hit!(same_name_different_def_type_alias);
+ }
+ }
+ }
+ }
+
+ // We need to account for `Self`. It can only refer to our type inside an impl.
+ let impl_ = 'impl_: {
+ for ancestor in usage.ancestors() {
+ if let Some(parent) = ancestor.parent() {
+ if let Some(parent) = ast::Impl::cast(parent) {
+ // Only if the GENERIC_PARAM_LIST is directly under impl, otherwise it may be in the self ty.
+ if matches!(
+ ancestor.kind(),
+ SyntaxKind::ASSOC_ITEM_LIST
+ | SyntaxKind::WHERE_CLAUSE
+ | SyntaxKind::GENERIC_PARAM_LIST
+ ) {
+ break;
+ }
+ if parent
+ .trait_()
+ .is_some_and(|trait_| *trait_.syntax() == ancestor)
+ {
+ break;
+ }
+
+ // Otherwise, found an impl where its self ty may be our type.
+ break 'impl_ Some(parent);
+ }
+ }
+ }
+ None
+ };
+ (|| {
+ let impl_ = impl_?;
+ is_possibly_self.push(sema.original_range(impl_.syntax()));
+ let assoc_items = impl_.assoc_item_list()?;
+ let type_aliases = assoc_items
+ .syntax()
+ .descendants()
+ .filter_map(ast::TypeAlias::cast);
+ for type_alias in type_aliases {
+ let Some(ty) = type_alias.ty() else { continue };
+ let Some(name) = type_alias.name() else { continue };
+ let contains_self = ty
+ .syntax()
+ .descendants_with_tokens()
+ .any(|node| node.kind() == SyntaxKind::SELF_TYPE_KW);
+ if !contains_self {
+ continue;
+ }
+ if seen.insert(InFileWrapper::new(
+ file_id,
+ name.syntax().text_range(),
+ )) {
+ if let Some(def) = is_alias(&type_alias) {
+ cov_mark::hit!(self_type_alias);
+ insert_type_alias(
+ sema.db,
+ &mut to_process,
+ name.text().as_str(),
+ def.into(),
+ );
+ } else {
+ cov_mark::hit!(same_name_different_def_type_alias);
+ }
+ }
+ }
+ Some(())
+ })();
+ }
+ }
+ }
+
+ completed.insert(current_to_process);
+
+ total_files_searched += current_to_process_search_scope.entries.len();
+ // FIXME: Maybe this needs to be relative to the project size, or at least to the initial search scope?
+ if total_files_searched > 20_000 && completed.len() > 100 {
+ // This case is extremely unlikely (even searching for `Vec::new()` on rust-analyzer does not enter
+ // here - it searches less than 10,000 files, and it does so in five seconds), but if we get here,
+ // we at a risk of entering an almost-infinite loop of growing the aliases list. So just stop and
+ // let normal search handle this case.
+ tracing::info!(aliases_count = %completed.len(), "too much aliases; leaving fast path");
+ return None;
+ }
+ }
+
+ // Impls can contain each other, so we need to deduplicate their ranges.
+ is_possibly_self.sort_unstable_by_key(|position| {
+ (position.file_id, position.range.start(), Reverse(position.range.end()))
+ });
+ is_possibly_self.dedup_by(|pos2, pos1| {
+ pos1.file_id == pos2.file_id
+ && pos1.range.start() <= pos2.range.start()
+ && pos1.range.end() >= pos2.range.end()
+ });
+
+ tracing::info!(aliases_count = %completed.len(), "aliases search completed");
+
+ Some((completed, is_possibly_self))
+ }
+
+ fn search(
+ this: &FindUsages<'_>,
+ finder: &Finder<'_>,
+ name: &str,
+ files: impl Iterator<Item = (Arc<str>, EditionedFileId, TextRange)>,
+ mut container_predicate: impl FnMut(
+ &SyntaxNode,
+ InFileWrapper<EditionedFileId, TextRange>,
+ ) -> bool,
+ sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
+ ) {
+ for (file_text, file_id, search_range) in files {
+ let tree = LazyCell::new(move || this.sema.parse(file_id).syntax().clone());
+
+ for offset in FindUsages::match_indices(&file_text, finder, search_range) {
+ let usages = FindUsages::find_nodes(this.sema, name, &tree, offset)
+ .filter_map(ast::NameRef::cast);
+ for usage in usages {
+ let found_usage = usage
+ .syntax()
+ .parent()
+ .and_then(ast::PathSegment::cast)
+ .map(|path_segment| {
+ container_predicate(
+ path_segment.parent_path().syntax(),
+ InFileWrapper::new(file_id, usage.syntax().text_range()),
+ )
+ })
+ .unwrap_or(false);
+ if found_usage {
+ this.found_name_ref(&usage, sink);
+ }
+ }
+ }
+ }
+ }
+
+ let Some((container_possible_aliases, is_possibly_self)) =
+ collect_possible_aliases(self.sema, container)
+ else {
+ return false;
+ };
+
+ cov_mark::hit!(short_associated_function_fast_search);
+
+ // FIXME: If Rust ever gains the ability to `use Struct::method` we'll also need to account for free
+ // functions.
+ let finder = Finder::new(name.as_bytes());
+ // The search for `Self` may return duplicate results with `ContainerName`, so deduplicate them.
+ let mut self_positions = FxHashSet::default();
+ tracing::info_span!("Self_search").in_scope(|| {
+ search(
+ self,
+ &finder,
+ name,
+ is_possibly_self.into_iter().map(|position| {
+ (
+ self.sema.db.file_text(position.file_id.file_id()),
+ position.file_id,
+ position.range,
+ )
+ }),
+ |path, name_position| {
+ let has_self = path
+ .descendants_with_tokens()
+ .any(|node| node.kind() == SyntaxKind::SELF_TYPE_KW);
+ if has_self {
+ self_positions.insert(name_position);
+ }
+ has_self
+ },
+ sink,
+ )
+ });
+ tracing::info_span!("aliases_search").in_scope(|| {
+ search(
+ self,
+ &finder,
+ name,
+ FindUsages::scope_files(self.sema.db, search_scope),
+ |path, name_position| {
+ has_any_name(path, |name| container_possible_aliases.contains(name))
+ && !self_positions.contains(&name_position)
+ },
+ sink,
+ )
+ });
+
+ true
+ }
+
pub fn search(&self, sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool) {
let _p = tracing::info_span!("FindUsages:search").entered();
let sema = self.sema;
@@ -488,65 +898,23 @@ impl<'a> FindUsages<'a> {
Some(s) => s.as_str(),
None => return,
};
- let finder = &Finder::new(name);
- let include_self_kw_refs =
- self.include_self_kw_refs.as_ref().map(|ty| (ty, Finder::new("Self")));
- // for<'a> |text: &'a str, name: &'a str, search_range: TextRange| -> impl Iterator<Item = TextSize> + 'a { ... }
- fn match_indices<'a>(
- text: &'a str,
- finder: &'a Finder<'a>,
- search_range: TextRange,
- ) -> impl Iterator<Item = TextSize> + 'a {
- finder.find_iter(text.as_bytes()).filter_map(move |idx| {
- let offset: TextSize = idx.try_into().unwrap();
- if !search_range.contains_inclusive(offset) {
- return None;
- }
- Some(offset)
- })
+ // FIXME: This should probably depend on the number of the results (specifically, the number of false results).
+ if name.len() <= 7 && self.short_associated_function_fast_search(sink, &search_scope, name)
+ {
+ return;
}
- // for<'a> |scope: &'a SearchScope| -> impl Iterator<Item = (Arc<String>, EditionedFileId, TextRange)> + 'a { ... }
- fn scope_files<'a>(
- sema: &'a Semantics<'_, RootDatabase>,
- scope: &'a SearchScope,
- ) -> impl Iterator<Item = (Arc<str>, EditionedFileId, TextRange)> + 'a {
- scope.entries.iter().map(|(&file_id, &search_range)| {
- let text = sema.db.file_text(file_id.file_id());
- let search_range =
- search_range.unwrap_or_else(|| TextRange::up_to(TextSize::of(&*text)));
-
- (text, file_id, search_range)
- })
- }
-
- let find_nodes = move |name: &str, node: &syntax::SyntaxNode, offset: TextSize| {
- node.token_at_offset(offset)
- .find(|it| {
- // `name` is stripped of raw ident prefix. See the comment on name retrieval above.
- it.text().trim_start_matches("r#") == name
- })
- .into_iter()
- .flat_map(move |token| {
- // FIXME: There should be optimization potential here
- // Currently we try to descend everything we find which
- // means we call `Semantics::descend_into_macros` on
- // every textual hit. That function is notoriously
- // expensive even for things that do not get down mapped
- // into macros.
- sema.descend_into_macros(DescendPreference::None, token)
- .into_iter()
- .filter_map(|it| it.parent())
- })
- };
+ let finder = &Finder::new(name);
+ let include_self_kw_refs =
+ self.include_self_kw_refs.as_ref().map(|ty| (ty, Finder::new("Self")));
- for (text, file_id, search_range) in scope_files(sema, &search_scope) {
+ for (text, file_id, search_range) in Self::scope_files(sema.db, &search_scope) {
self.sema.db.unwind_if_cancelled();
- let tree = Lazy::new(move || sema.parse(file_id).syntax().clone());
+ let tree = LazyCell::new(move || sema.parse(file_id).syntax().clone());
// Search for occurrences of the items name
- for offset in match_indices(&text, finder, search_range) {
+ for offset in Self::match_indices(&text, finder, search_range) {
tree.token_at_offset(offset).for_each(|token| {
let Some(str_token) = ast::String::cast(token.clone()) else { return };
if let Some((range, nameres)) =
@@ -556,7 +924,9 @@ impl<'a> FindUsages<'a> {
}
});
- for name in find_nodes(name, &tree, offset).filter_map(ast::NameLike::cast) {
+ for name in
+ Self::find_nodes(sema, name, &tree, offset).filter_map(ast::NameLike::cast)
+ {
if match name {
ast::NameLike::NameRef(name_ref) => self.found_name_ref(&name_ref, sink),
ast::NameLike::Name(name) => self.found_name(&name, sink),
@@ -568,8 +938,9 @@ impl<'a> FindUsages<'a> {
}
// Search for occurrences of the `Self` referring to our type
if let Some((self_ty, finder)) = &include_self_kw_refs {
- for offset in match_indices(&text, finder, search_range) {
- for name_ref in find_nodes("Self", &tree, offset).filter_map(ast::NameRef::cast)
+ for offset in Self::match_indices(&text, finder, search_range) {
+ for name_ref in
+ Self::find_nodes(sema, "Self", &tree, offset).filter_map(ast::NameRef::cast)
{
if self.found_self_ty_name_ref(self_ty, &name_ref, sink) {
return;
@@ -587,13 +958,13 @@ impl<'a> FindUsages<'a> {
let is_crate_root = module.is_crate_root().then(|| Finder::new("crate"));
let finder = &Finder::new("super");
- for (text, file_id, search_range) in scope_files(sema, &scope) {
+ for (text, file_id, search_range) in Self::scope_files(sema.db, &scope) {
self.sema.db.unwind_if_cancelled();
- let tree = Lazy::new(move || sema.parse(file_id).syntax().clone());
+ let tree = LazyCell::new(move || sema.parse(file_id).syntax().clone());
- for offset in match_indices(&text, finder, search_range) {
- for name_ref in
- find_nodes("super", &tree, offset).filter_map(ast::NameRef::cast)
+ for offset in Self::match_indices(&text, finder, search_range) {
+ for name_ref in Self::find_nodes(sema, "super", &tree, offset)
+ .filter_map(ast::NameRef::cast)
{
if self.found_name_ref(&name_ref, sink) {
return;
@@ -601,9 +972,9 @@ impl<'a> FindUsages<'a> {
}
}
if let Some(finder) = &is_crate_root {
- for offset in match_indices(&text, finder, search_range) {
- for name_ref in
- find_nodes("crate", &tree, offset).filter_map(ast::NameRef::cast)
+ for offset in Self::match_indices(&text, finder, search_range) {
+ for name_ref in Self::find_nodes(sema, "crate", &tree, offset)
+ .filter_map(ast::NameRef::cast)
{
if self.found_name_ref(&name_ref, sink) {
return;
@@ -641,11 +1012,12 @@ impl<'a> FindUsages<'a> {
let search_range =
search_range.unwrap_or_else(|| TextRange::up_to(TextSize::of(&*text)));
- let tree = Lazy::new(|| sema.parse(file_id).syntax().clone());
+ let tree = LazyCell::new(|| sema.parse(file_id).syntax().clone());
let finder = &Finder::new("self");
- for offset in match_indices(&text, finder, search_range) {
- for name_ref in find_nodes("self", &tree, offset).filter_map(ast::NameRef::cast)
+ for offset in Self::match_indices(&text, finder, search_range) {
+ for name_ref in
+ Self::find_nodes(sema, "self", &tree, offset).filter_map(ast::NameRef::cast)
{
if self.found_self_module_name_ref(&name_ref, sink) {
return;
diff --git a/crates/ide-db/src/syntax_helpers/format_string_exprs.rs b/crates/ide-db/src/syntax_helpers/format_string_exprs.rs
index 8ab5a6ede3..c104aa5718 100644
--- a/crates/ide-db/src/syntax_helpers/format_string_exprs.rs
+++ b/crates/ide-db/src/syntax_helpers/format_string_exprs.rs
@@ -31,6 +31,7 @@ pub fn with_placeholders(args: Vec<Arg>) -> Vec<String> {
.collect()
}
+// FIXME Remove this, we have this information in the HIR now
/// Parser for a format-like string. It is more allowing in terms of string contents,
/// as we expect variable placeholders to be filled with expressions.
///
diff --git a/crates/ide-db/src/syntax_helpers/insert_whitespace_into_node.rs b/crates/ide-db/src/syntax_helpers/insert_whitespace_into_node.rs
index 97b6d4a572..dd4a665e8e 100644
--- a/crates/ide-db/src/syntax_helpers/insert_whitespace_into_node.rs
+++ b/crates/ide-db/src/syntax_helpers/insert_whitespace_into_node.rs
@@ -131,5 +131,6 @@ pub fn insert_ws_into(syn: SyntaxNode) -> SyntaxNode {
}
fn is_text(k: SyntaxKind) -> bool {
- k.is_keyword() || k.is_literal() || k == IDENT || k == UNDERSCORE
+ // Consider all keywords in all editions.
+ k.is_any_identifier() || k.is_literal() || k == UNDERSCORE
}
diff --git a/crates/ide-db/src/syntax_helpers/node_ext.rs b/crates/ide-db/src/syntax_helpers/node_ext.rs
index 37238cc61d..91e0b4495f 100644
--- a/crates/ide-db/src/syntax_helpers/node_ext.rs
+++ b/crates/ide-db/src/syntax_helpers/node_ext.rs
@@ -1,6 +1,7 @@
//! Various helper functions to work with SyntaxNodes.
use itertools::Itertools;
use parser::T;
+use span::Edition;
use syntax::{
ast::{self, HasLoopBody, MacroCall, PathSegmentKind, VisibilityKind},
AstNode, AstToken, Preorder, RustLanguage, WalkEvent,
@@ -456,12 +457,15 @@ impl Iterator for TreeWithDepthIterator {
}
/// Parses the input token tree as comma separated plain paths.
-pub fn parse_tt_as_comma_sep_paths(input: ast::TokenTree) -> Option<Vec<ast::Path>> {
+pub fn parse_tt_as_comma_sep_paths(
+ input: ast::TokenTree,
+ edition: Edition,
+) -> Option<Vec<ast::Path>> {
let r_paren = input.r_paren_token();
let tokens =
input.syntax().children_with_tokens().skip(1).map_while(|it| match it.into_token() {
// seeing a keyword means the attribute is unclosed so stop parsing here
- Some(tok) if tok.kind().is_keyword() => None,
+ Some(tok) if tok.kind().is_keyword(edition) => None,
// don't include the right token tree parenthesis if it exists
tok @ Some(_) if tok == r_paren => None,
// only nodes that we can find are other TokenTrees, those are unexpected in this parse though
@@ -473,10 +477,12 @@ pub fn parse_tt_as_comma_sep_paths(input: ast::TokenTree) -> Option<Vec<ast::Pat
.into_iter()
.filter_map(|(is_sep, group)| (!is_sep).then_some(group))
.filter_map(|mut tokens| {
- syntax::hacks::parse_expr_from_str(&tokens.join("")).and_then(|expr| match expr {
- ast::Expr::PathExpr(it) => it.path(),
- _ => None,
- })
+ syntax::hacks::parse_expr_from_str(&tokens.join(""), Edition::CURRENT).and_then(
+ |expr| match expr {
+ ast::Expr::PathExpr(it) => it.path(),
+ _ => None,
+ },
+ )
})
.collect();
Some(paths)
diff --git a/crates/ide-db/src/traits.rs b/crates/ide-db/src/traits.rs
index 48a585bf33..82aca50d03 100644
--- a/crates/ide-db/src/traits.rs
+++ b/crates/ide-db/src/traits.rs
@@ -34,19 +34,20 @@ pub fn get_missing_assoc_items(
// may share the same name as a function or constant.
let mut impl_fns_consts = FxHashSet::default();
let mut impl_type = FxHashSet::default();
+ let edition = imp.module(sema.db).krate().edition(sema.db);
for item in imp.items(sema.db) {
match item {
hir::AssocItem::Function(it) => {
- impl_fns_consts.insert(it.name(sema.db).display(sema.db).to_string());
+ impl_fns_consts.insert(it.name(sema.db).display(sema.db, edition).to_string());
}
hir::AssocItem::Const(it) => {
if let Some(name) = it.name(sema.db) {
- impl_fns_consts.insert(name.display(sema.db).to_string());
+ impl_fns_consts.insert(name.display(sema.db, edition).to_string());
}
}
hir::AssocItem::TypeAlias(it) => {
- impl_type.insert(it.name(sema.db).display(sema.db).to_string());
+ impl_type.insert(it.name(sema.db).display(sema.db, edition).to_string());
}
}
}
@@ -56,15 +57,14 @@ pub fn get_missing_assoc_items(
.items(sema.db)
.into_iter()
.filter(|i| match i {
- hir::AssocItem::Function(f) => {
- !impl_fns_consts.contains(&f.name(sema.db).display(sema.db).to_string())
- }
+ hir::AssocItem::Function(f) => !impl_fns_consts
+ .contains(&f.name(sema.db).display(sema.db, edition).to_string()),
hir::AssocItem::TypeAlias(t) => {
- !impl_type.contains(&t.name(sema.db).display(sema.db).to_string())
+ !impl_type.contains(&t.name(sema.db).display(sema.db, edition).to_string())
}
hir::AssocItem::Const(c) => c
.name(sema.db)
- .map(|n| !impl_fns_consts.contains(&n.display(sema.db).to_string()))
+ .map(|n| !impl_fns_consts.contains(&n.display(sema.db, edition).to_string()))
.unwrap_or_default(),
})
.collect()
@@ -116,6 +116,7 @@ mod tests {
use expect_test::{expect, Expect};
use hir::FilePosition;
use hir::Semantics;
+ use span::Edition;
use syntax::ast::{self, AstNode};
use test_fixture::ChangeFixture;
@@ -140,7 +141,7 @@ mod tests {
sema.find_node_at_offset_with_descend(file.syntax(), position.offset).unwrap();
let trait_ = crate::traits::resolve_target_trait(&sema, &impl_block);
let actual = match trait_ {
- Some(trait_) => trait_.name(&db).display(&db).to_string(),
+ Some(trait_) => trait_.name(&db).display(&db, Edition::CURRENT).to_string(),
None => String::new(),
};
expect.assert_eq(&actual);
@@ -155,7 +156,7 @@ mod tests {
let items = crate::traits::get_missing_assoc_items(&sema, &impl_block);
let actual = items
.into_iter()
- .map(|item| item.name(&db).unwrap().display(&db).to_string())
+ .map(|item| item.name(&db).unwrap().display(&db, Edition::CURRENT).to_string())
.collect::<Vec<_>>()
.join("\n");
expect.assert_eq(&actual);
diff --git a/crates/ide-db/src/ty_filter.rs b/crates/ide-db/src/ty_filter.rs
index 5b566c5067..515bc418cb 100644
--- a/crates/ide-db/src/ty_filter.rs
+++ b/crates/ide-db/src/ty_filter.rs
@@ -5,10 +5,7 @@
use std::iter;
use hir::Semantics;
-use syntax::{
- ast::{self, make, Pat},
- ToSmolStr,
-};
+use syntax::ast::{self, make, Pat};
use crate::RootDatabase;
@@ -29,7 +26,7 @@ impl TryEnum {
_ => return None,
};
TryEnum::ALL.iter().find_map(|&var| {
- if enum_.name(sema.db).display_no_db().to_smolstr() == var.type_name() {
+ if enum_.name(sema.db).eq_ident(var.type_name()) {
return Some(var);
}
None
diff --git a/crates/ide-db/src/use_trivial_constructor.rs b/crates/ide-db/src/use_trivial_constructor.rs
index 965f432407..c3f0bf3706 100644
--- a/crates/ide-db/src/use_trivial_constructor.rs
+++ b/crates/ide-db/src/use_trivial_constructor.rs
@@ -1,6 +1,7 @@
//! Functionality for generating trivial constructors
use hir::StructKind;
+use span::Edition;
use syntax::{
ast::{make, Expr, Path},
ToSmolStr,
@@ -11,6 +12,7 @@ pub fn use_trivial_constructor(
db: &crate::RootDatabase,
path: Path,
ty: &hir::Type,
+ edition: Edition,
) -> Option<Expr> {
match ty.as_adt() {
Some(hir::Adt::Enum(x)) => {
@@ -19,7 +21,7 @@ pub fn use_trivial_constructor(
let path = make::path_qualified(
path,
make::path_segment(make::name_ref(
- &variant.name(db).display_no_db().to_smolstr(),
+ &variant.name(db).display_no_db(edition).to_smolstr(),
)),
);
diff --git a/crates/ide-diagnostics/Cargo.toml b/crates/ide-diagnostics/Cargo.toml
index 9c3a279a94..bf54f4ab32 100644
--- a/crates/ide-diagnostics/Cargo.toml
+++ b/crates/ide-diagnostics/Cargo.toml
@@ -18,7 +18,6 @@ either.workspace = true
itertools.workspace = true
serde_json.workspace = true
tracing.workspace = true
-once_cell = "1.17.0"
# local deps
stdx.workspace = true
diff --git a/crates/ide-diagnostics/src/handlers/expected_function.rs b/crates/ide-diagnostics/src/handlers/expected_function.rs
index 05fb1c29b3..02299197b1 100644
--- a/crates/ide-diagnostics/src/handlers/expected_function.rs
+++ b/crates/ide-diagnostics/src/handlers/expected_function.rs
@@ -12,7 +12,7 @@ pub(crate) fn expected_function(
Diagnostic::new_with_syntax_node_ptr(
ctx,
DiagnosticCode::RustcHardError("E0618"),
- format!("expected function, found {}", d.found.display(ctx.sema.db)),
+ format!("expected function, found {}", d.found.display(ctx.sema.db, ctx.edition)),
d.call.map(|it| it.into()),
)
.experimental()
diff --git a/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs b/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs
index 117088ca09..ccb33fed10 100644
--- a/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs
+++ b/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs
@@ -12,7 +12,7 @@ use itertools::Itertools;
use stdx::{format_to, never};
use syntax::{
ast::{self, make},
- SyntaxKind, SyntaxNode,
+ Edition, SyntaxKind, SyntaxNode,
};
use text_edit::TextEdit;
@@ -104,6 +104,7 @@ pub(crate) fn json_in_items(
file_id: EditionedFileId,
node: &SyntaxNode,
config: &DiagnosticsConfig,
+ edition: Edition,
) {
(|| {
if node.kind() == SyntaxKind::ERROR
@@ -156,7 +157,11 @@ pub(crate) fn json_in_items(
config.insert_use.prefix_kind,
cfg,
) {
- insert_use(&scope, mod_path_to_ast(&it), &config.insert_use);
+ insert_use(
+ &scope,
+ mod_path_to_ast(&it, edition),
+ &config.insert_use,
+ );
}
}
}
@@ -168,7 +173,11 @@ pub(crate) fn json_in_items(
config.insert_use.prefix_kind,
cfg,
) {
- insert_use(&scope, mod_path_to_ast(&it), &config.insert_use);
+ insert_use(
+ &scope,
+ mod_path_to_ast(&it, edition),
+ &config.insert_use,
+ );
}
}
}
diff --git a/crates/ide-diagnostics/src/handlers/missing_fields.rs b/crates/ide-diagnostics/src/handlers/missing_fields.rs
index ea7908525a..86c237f7b5 100644
--- a/crates/ide-diagnostics/src/handlers/missing_fields.rs
+++ b/crates/ide-diagnostics/src/handlers/missing_fields.rs
@@ -11,7 +11,7 @@ use stdx::format_to;
use syntax::{
algo,
ast::{self, make},
- AstNode, SyntaxNode, SyntaxNodePtr, ToSmolStr,
+ AstNode, Edition, SyntaxNode, SyntaxNodePtr, ToSmolStr,
};
use text_edit::TextEdit;
@@ -31,7 +31,7 @@ use crate::{fix, Diagnostic, DiagnosticCode, DiagnosticsContext};
pub(crate) fn missing_fields(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Diagnostic {
let mut message = String::from("missing structure fields:\n");
for field in &d.missed_fields {
- format_to!(message, "- {}\n", field.display(ctx.sema.db));
+ format_to!(message, "- {}\n", field.display(ctx.sema.db, ctx.edition));
}
let ptr = InFile::new(
@@ -134,8 +134,9 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass
use_trivial_constructor(
ctx.sema.db,
- ide_db::helpers::mod_path_to_ast(&type_path),
+ ide_db::helpers::mod_path_to_ast(&type_path, ctx.edition),
ty,
+ ctx.edition,
)
})();
@@ -146,7 +147,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass
}
};
let field = make::record_expr_field(
- make::name_ref(&f.name(ctx.sema.db).display_no_db().to_smolstr()),
+ make::name_ref(&f.name(ctx.sema.db).display_no_db(ctx.edition).to_smolstr()),
field_expr,
);
new_field_list.add_field(field.clone_for_update());
@@ -160,7 +161,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass
let new_field_list = old_field_list.clone_for_update();
for (f, _) in missing_fields.iter() {
let field = make::record_pat_field_shorthand(make::name_ref(
- &f.name(ctx.sema.db).display_no_db().to_smolstr(),
+ &f.name(ctx.sema.db).display_no_db(ctx.edition).to_smolstr(),
));
new_field_list.add_field(field.clone_for_update());
}
@@ -169,9 +170,14 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass
}
}
-fn make_ty(ty: &hir::Type, db: &dyn HirDatabase, module: hir::Module) -> ast::Type {
+fn make_ty(
+ ty: &hir::Type,
+ db: &dyn HirDatabase,
+ module: hir::Module,
+ edition: Edition,
+) -> ast::Type {
let ty_str = match ty.as_adt() {
- Some(adt) => adt.name(db).display(db.upcast()).to_string(),
+ Some(adt) => adt.name(db).display(db.upcast(), edition).to_string(),
None => {
ty.display_source_code(db, module.into(), false).ok().unwrap_or_else(|| "_".to_owned())
}
@@ -223,13 +229,13 @@ fn get_default_constructor(
let famous_defs = FamousDefs(&ctx.sema, krate);
if has_new_func {
- Some(make::ext::expr_ty_new(&make_ty(ty, ctx.sema.db, module)))
+ Some(make::ext::expr_ty_new(&make_ty(ty, ctx.sema.db, module, ctx.edition)))
} else if ty.as_adt() == famous_defs.core_option_Option()?.ty(ctx.sema.db).as_adt() {
Some(make::ext::option_none())
} else if !ty.is_array()
&& ty.impls_trait(ctx.sema.db, famous_defs.core_default_Default()?, &[])
{
- Some(make::ext::expr_ty_default(&make_ty(ty, ctx.sema.db, module)))
+ Some(make::ext::expr_ty_default(&make_ty(ty, ctx.sema.db, module, ctx.edition)))
} else {
None
}
diff --git a/crates/ide-diagnostics/src/handlers/moved_out_of_ref.rs b/crates/ide-diagnostics/src/handlers/moved_out_of_ref.rs
index fa9a6577fc..06c6b0f3e4 100644
--- a/crates/ide-diagnostics/src/handlers/moved_out_of_ref.rs
+++ b/crates/ide-diagnostics/src/handlers/moved_out_of_ref.rs
@@ -8,7 +8,7 @@ pub(crate) fn moved_out_of_ref(ctx: &DiagnosticsContext<'_>, d: &hir::MovedOutOf
Diagnostic::new_with_syntax_node_ptr(
ctx,
DiagnosticCode::RustcHardError("E0507"),
- format!("cannot move `{}` out of reference", d.ty.display(ctx.sema.db)),
+ format!("cannot move `{}` out of reference", d.ty.display(ctx.sema.db, ctx.edition)),
d.span,
)
.experimental() // spans are broken, and I'm not sure how precise we can detect copy types
diff --git a/crates/ide-diagnostics/src/handlers/mutability_errors.rs b/crates/ide-diagnostics/src/handlers/mutability_errors.rs
index 00352266dd..e4b1f3ca95 100644
--- a/crates/ide-diagnostics/src/handlers/mutability_errors.rs
+++ b/crates/ide-diagnostics/src/handlers/mutability_errors.rs
@@ -40,7 +40,7 @@ pub(crate) fn need_mut(ctx: &DiagnosticsContext<'_>, d: &hir::NeedMut) -> Option
DiagnosticCode::RustcHardError("E0384"),
format!(
"cannot mutate immutable variable `{}`",
- d.local.name(ctx.sema.db).display(ctx.sema.db)
+ d.local.name(ctx.sema.db).display(ctx.sema.db, ctx.edition)
),
d.span,
)
diff --git a/crates/ide-diagnostics/src/handlers/private_assoc_item.rs b/crates/ide-diagnostics/src/handlers/private_assoc_item.rs
index f6ed0d7226..fe32c59049 100644
--- a/crates/ide-diagnostics/src/handlers/private_assoc_item.rs
+++ b/crates/ide-diagnostics/src/handlers/private_assoc_item.rs
@@ -12,7 +12,7 @@ pub(crate) fn private_assoc_item(
let name = d
.item
.name(ctx.sema.db)
- .map(|name| format!("`{}` ", name.display(ctx.sema.db)))
+ .map(|name| format!("`{}` ", name.display(ctx.sema.db, ctx.edition)))
.unwrap_or_default();
Diagnostic::new_with_syntax_node_ptr(
ctx,
diff --git a/crates/ide-diagnostics/src/handlers/private_field.rs b/crates/ide-diagnostics/src/handlers/private_field.rs
index e91e64c81b..237a9b8787 100644
--- a/crates/ide-diagnostics/src/handlers/private_field.rs
+++ b/crates/ide-diagnostics/src/handlers/private_field.rs
@@ -10,8 +10,8 @@ pub(crate) fn private_field(ctx: &DiagnosticsContext<'_>, d: &hir::PrivateField)
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)
+ d.field.name(ctx.sema.db).display(ctx.sema.db, ctx.edition),
+ d.field.parent_def(ctx.sema.db).name(ctx.sema.db).display(ctx.sema.db, ctx.edition)
),
d.expr.map(|it| it.into()),
)
diff --git a/crates/ide-diagnostics/src/handlers/trait_impl_missing_assoc_item.rs b/crates/ide-diagnostics/src/handlers/trait_impl_missing_assoc_item.rs
index 58d1b7f31d..a35b67ce98 100644
--- a/crates/ide-diagnostics/src/handlers/trait_impl_missing_assoc_item.rs
+++ b/crates/ide-diagnostics/src/handlers/trait_impl_missing_assoc_item.rs
@@ -17,7 +17,7 @@ pub(crate) fn trait_impl_missing_assoc_item(
hir::AssocItem::Const(_) => "`const ",
hir::AssocItem::TypeAlias(_) => "`type ",
})?;
- f(&name.display(ctx.sema.db))?;
+ f(&name.display(ctx.sema.db, ctx.edition))?;
f(&"`")
});
Diagnostic::new(
diff --git a/crates/ide-diagnostics/src/handlers/trait_impl_redundant_assoc_item.rs b/crates/ide-diagnostics/src/handlers/trait_impl_redundant_assoc_item.rs
index 6d756484eb..3de51ca4a3 100644
--- a/crates/ide-diagnostics/src/handlers/trait_impl_redundant_assoc_item.rs
+++ b/crates/ide-diagnostics/src/handlers/trait_impl_redundant_assoc_item.rs
@@ -18,11 +18,11 @@ pub(crate) fn trait_impl_redundant_assoc_item(
) -> Diagnostic {
let db = ctx.sema.db;
let name = d.assoc_item.0.clone();
- let redundant_assoc_item_name = name.display(db);
+ let redundant_assoc_item_name = name.display(db, ctx.edition);
let assoc_item = d.assoc_item.1;
let default_range = d.impl_.syntax_node_ptr().text_range();
- let trait_name = d.trait_.name(db).display_no_db().to_smolstr();
+ let trait_name = d.trait_.name(db).display_no_db(ctx.edition).to_smolstr();
let (redundant_item_name, diagnostic_range, redundant_item_def) = match assoc_item {
hir::AssocItem::Function(id) => {
@@ -30,7 +30,7 @@ pub(crate) fn trait_impl_redundant_assoc_item(
(
format!("`fn {redundant_assoc_item_name}`"),
function.source(db).map(|it| it.syntax().text_range()).unwrap_or(default_range),
- format!("\n {};", function.display(db)),
+ format!("\n {};", function.display(db, ctx.edition)),
)
}
hir::AssocItem::Const(id) => {
@@ -38,7 +38,7 @@ pub(crate) fn trait_impl_redundant_assoc_item(
(
format!("`const {redundant_assoc_item_name}`"),
constant.source(db).map(|it| it.syntax().text_range()).unwrap_or(default_range),
- format!("\n {};", constant.display(db)),
+ format!("\n {};", constant.display(db, ctx.edition)),
)
}
hir::AssocItem::TypeAlias(id) => {
@@ -48,7 +48,7 @@ pub(crate) fn trait_impl_redundant_assoc_item(
type_alias.source(db).map(|it| it.syntax().text_range()).unwrap_or(default_range),
format!(
"\n type {};",
- type_alias.name(ctx.sema.db).display_no_db().to_smolstr()
+ type_alias.name(ctx.sema.db).display_no_db(ctx.edition).to_smolstr()
),
)
}
diff --git a/crates/ide-diagnostics/src/handlers/type_mismatch.rs b/crates/ide-diagnostics/src/handlers/type_mismatch.rs
index 6f5c68d4b5..5cce7c4aed 100644
--- a/crates/ide-diagnostics/src/handlers/type_mismatch.rs
+++ b/crates/ide-diagnostics/src/handlers/type_mismatch.rs
@@ -40,8 +40,12 @@ pub(crate) fn type_mismatch(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch)
DiagnosticCode::RustcHardError("E0308"),
format!(
"expected {}, found {}",
- d.expected.display(ctx.sema.db).with_closure_style(ClosureStyle::ClosureWithId),
- d.actual.display(ctx.sema.db).with_closure_style(ClosureStyle::ClosureWithId),
+ d.expected
+ .display(ctx.sema.db, ctx.edition)
+ .with_closure_style(ClosureStyle::ClosureWithId),
+ d.actual
+ .display(ctx.sema.db, ctx.edition)
+ .with_closure_style(ClosureStyle::ClosureWithId),
),
display_range,
)
@@ -199,8 +203,8 @@ fn str_ref_to_owned(
expr_ptr: &InFile<AstPtr<ast::Expr>>,
acc: &mut Vec<Assist>,
) -> Option<()> {
- let expected = d.expected.display(ctx.sema.db);
- let actual = d.actual.display(ctx.sema.db);
+ let expected = d.expected.display(ctx.sema.db, ctx.edition);
+ let actual = d.actual.display(ctx.sema.db, ctx.edition);
// FIXME do this properly
if expected.to_string() != "String" || actual.to_string() != "&str" {
diff --git a/crates/ide-diagnostics/src/handlers/typed_hole.rs b/crates/ide-diagnostics/src/handlers/typed_hole.rs
index b4a566e318..b5c242e1e9 100644
--- a/crates/ide-diagnostics/src/handlers/typed_hole.rs
+++ b/crates/ide-diagnostics/src/handlers/typed_hole.rs
@@ -26,7 +26,9 @@ pub(crate) fn typed_hole(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Di
(
format!(
"invalid `_` expression, expected type `{}`",
- d.expected.display(ctx.sema.db).with_closure_style(ClosureStyle::ClosureWithId),
+ d.expected
+ .display(ctx.sema.db, ctx.edition)
+ .with_closure_style(ClosureStyle::ClosureWithId),
),
fixes(ctx, d),
)
@@ -69,6 +71,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Option<Vec<Assist>
prefer_prelude: ctx.config.prefer_prelude,
prefer_absolute: ctx.config.prefer_absolute,
},
+ ctx.edition,
)
.ok()
})
diff --git a/crates/ide-diagnostics/src/handlers/undeclared_label.rs b/crates/ide-diagnostics/src/handlers/undeclared_label.rs
index 97943b7e8b..6af36fb9e7 100644
--- a/crates/ide-diagnostics/src/handlers/undeclared_label.rs
+++ b/crates/ide-diagnostics/src/handlers/undeclared_label.rs
@@ -9,7 +9,7 @@ pub(crate) fn undeclared_label(
Diagnostic::new_with_syntax_node_ptr(
ctx,
DiagnosticCode::RustcHardError("undeclared-label"),
- format!("use of undeclared label `{}`", name.display(ctx.sema.db)),
+ format!("use of undeclared label `{}`", name.display(ctx.sema.db, ctx.edition)),
d.node.map(|it| it.into()),
)
}
diff --git a/crates/ide-diagnostics/src/handlers/unlinked_file.rs b/crates/ide-diagnostics/src/handlers/unlinked_file.rs
index a1573bab8a..e0822fc5b3 100644
--- a/crates/ide-diagnostics/src/handlers/unlinked_file.rs
+++ b/crates/ide-diagnostics/src/handlers/unlinked_file.rs
@@ -11,7 +11,7 @@ use ide_db::{
use paths::Utf8Component;
use syntax::{
ast::{self, edit::IndentLevel, HasModuleItem, HasName},
- AstNode, TextRange, ToSmolStr,
+ AstNode, TextRange,
};
use text_edit::TextEdit;
@@ -112,8 +112,7 @@ fn fixes(
// shouldn't occur
_ => continue 'crates,
};
- match current.children.iter().find(|(name, _)| name.display_no_db().to_smolstr() == seg)
- {
+ match current.children.iter().find(|(name, _)| name.eq_ident(seg)) {
Some((_, &child)) => current = &crate_def_map[child],
None => continue 'crates,
}
@@ -162,11 +161,7 @@ fn fixes(
// try finding a parent that has an inline tree from here on
let mut current = module;
for s in stack.iter().rev() {
- match module
- .children
- .iter()
- .find(|(name, _)| name.display_no_db().to_smolstr() == s)
- {
+ match module.children.iter().find(|(name, _)| name.eq_ident(s)) {
Some((_, child)) => {
current = &crate_def_map[*child];
}
diff --git a/crates/ide-diagnostics/src/handlers/unreachable_label.rs b/crates/ide-diagnostics/src/handlers/unreachable_label.rs
index 3601041fc7..bdff2417ca 100644
--- a/crates/ide-diagnostics/src/handlers/unreachable_label.rs
+++ b/crates/ide-diagnostics/src/handlers/unreachable_label.rs
@@ -9,7 +9,7 @@ pub(crate) fn unreachable_label(
Diagnostic::new_with_syntax_node_ptr(
ctx,
DiagnosticCode::RustcHardError("E0767"),
- format!("use of unreachable label `{}`", name.display(ctx.sema.db)),
+ format!("use of unreachable label `{}`", name.display(ctx.sema.db, ctx.edition)),
d.node.map(|it| it.into()),
)
}
diff --git a/crates/ide-diagnostics/src/handlers/unresolved_field.rs b/crates/ide-diagnostics/src/handlers/unresolved_field.rs
index eb8eea69f6..76d624c47a 100644
--- a/crates/ide-diagnostics/src/handlers/unresolved_field.rs
+++ b/crates/ide-diagnostics/src/handlers/unresolved_field.rs
@@ -36,8 +36,8 @@ pub(crate) fn unresolved_field(
DiagnosticCode::RustcHardError("E0559"),
format!(
"no field `{}` on type `{}`{method_suffix}",
- d.name.display(ctx.sema.db),
- d.receiver.display(ctx.sema.db)
+ d.name.display(ctx.sema.db, ctx.edition),
+ d.receiver.display(ctx.sema.db, ctx.edition)
),
adjusted_display_range(ctx, d.expr, &|expr| {
Some(
diff --git a/crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs b/crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs
index c8ff54cba3..5b596123e7 100644
--- a/crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs
+++ b/crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs
@@ -13,7 +13,7 @@ pub(crate) fn unresolved_macro_call(
let bang = if d.is_bang { "!" } else { "" };
Diagnostic::new(
DiagnosticCode::RustcHardError("unresolved-macro-call"),
- format!("unresolved macro `{}{bang}`", d.path.display(ctx.sema.db)),
+ format!("unresolved macro `{}{bang}`", d.path.display(ctx.sema.db, ctx.edition)),
display_range,
)
.experimental()
diff --git a/crates/ide-diagnostics/src/handlers/unresolved_method.rs b/crates/ide-diagnostics/src/handlers/unresolved_method.rs
index 387d56b890..c0d038a238 100644
--- a/crates/ide-diagnostics/src/handlers/unresolved_method.rs
+++ b/crates/ide-diagnostics/src/handlers/unresolved_method.rs
@@ -30,8 +30,8 @@ pub(crate) fn unresolved_method(
DiagnosticCode::RustcHardError("E0599"),
format!(
"no method `{}` on type `{}`{suffix}",
- d.name.display(ctx.sema.db),
- d.receiver.display(ctx.sema.db)
+ d.name.display(ctx.sema.db, ctx.edition),
+ d.receiver.display(ctx.sema.db, ctx.edition)
),
adjusted_display_range(ctx, d.expr, &|expr| {
Some(
@@ -154,9 +154,10 @@ fn assoc_func_fix(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedMethodCall) -
};
let mut receiver_type_adt_name =
- receiver_type.as_adt()?.name(db).display_no_db().to_smolstr();
+ receiver_type.as_adt()?.name(db).display_no_db(ctx.edition).to_smolstr();
- let generic_parameters: Vec<SmolStr> = receiver_type.generic_parameters(db).collect();
+ let generic_parameters: Vec<SmolStr> =
+ receiver_type.generic_parameters(db, ctx.edition).collect();
// if receiver should be pass as first arg in the assoc func,
// we could omit generic parameters cause compiler can deduce it automatically
if !need_to_take_receiver_as_first_arg && !generic_parameters.is_empty() {
diff --git a/crates/ide-diagnostics/src/handlers/unused_variables.rs b/crates/ide-diagnostics/src/handlers/unused_variables.rs
index bf19331d9f..84007b16aa 100644
--- a/crates/ide-diagnostics/src/handlers/unused_variables.rs
+++ b/crates/ide-diagnostics/src/handlers/unused_variables.rs
@@ -5,7 +5,7 @@ use ide_db::{
source_change::SourceChange,
FileRange, RootDatabase,
};
-use syntax::TextRange;
+use syntax::{Edition, TextRange};
use text_edit::TextEdit;
use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
@@ -23,7 +23,7 @@ pub(crate) fn unused_variables(
return None;
}
let diagnostic_range = ctx.sema.diagnostics_display_range(ast);
- // The range for the Actual Name. We don't want to replace the entire declarition. Using the diagnostic range causes issues within in Array Destructuring.
+ // The range for the Actual Name. We don't want to replace the entire declaration. Using the diagnostic range causes issues within in Array Destructuring.
let name_range = d
.local
.primary_source(ctx.sema.db)
@@ -42,7 +42,14 @@ pub(crate) fn unused_variables(
ast,
)
.with_fixes(name_range.and_then(|it| {
- fixes(ctx.sema.db, var_name, it.range, diagnostic_range.into(), ast.file_id.is_macro())
+ fixes(
+ ctx.sema.db,
+ var_name,
+ it.range,
+ diagnostic_range.into(),
+ ast.file_id.is_macro(),
+ ctx.edition,
+ )
}))
.experimental(),
)
@@ -54,6 +61,7 @@ fn fixes(
name_range: TextRange,
diagnostic_range: FileRange,
is_in_marco: bool,
+ edition: Edition,
) -> Option<Vec<Assist>> {
if is_in_marco {
return None;
@@ -63,14 +71,14 @@ fn fixes(
id: AssistId("unscore_unused_variable_name", AssistKind::QuickFix),
label: Label::new(format!(
"Rename unused {} to _{}",
- var_name.display(db),
- var_name.display(db)
+ var_name.display(db, edition),
+ var_name.display(db, edition)
)),
group: None,
target: diagnostic_range.range,
source_change: Some(SourceChange::from_text_edit(
diagnostic_range.file_id,
- TextEdit::replace(name_range, format!("_{}", var_name.display(db))),
+ TextEdit::replace(name_range, format!("_{}", var_name.display(db, edition))),
)),
command: None,
}])
diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs
index a61c5f0cd4..9b50a435e4 100644
--- a/crates/ide-diagnostics/src/lib.rs
+++ b/crates/ide-diagnostics/src/lib.rs
@@ -75,6 +75,8 @@ mod handlers {
#[cfg(test)]
mod tests;
+use std::sync::LazyLock;
+
use hir::{diagnostics::AnyDiagnostic, InFile, Semantics};
use ide_db::{
assists::{Assist, AssistId, AssistKind, AssistResolveStrategy},
@@ -86,11 +88,10 @@ use ide_db::{
syntax_helpers::node_ext::parse_tt_as_comma_sep_paths,
EditionedFileId, FileId, FileRange, FxHashMap, FxHashSet, RootDatabase, SnippetCap,
};
-use once_cell::sync::Lazy;
use stdx::never;
use syntax::{
ast::{self, AstNode},
- AstPtr, SyntaxNode, SyntaxNodePtr, TextRange,
+ AstPtr, Edition, SyntaxNode, SyntaxNodePtr, TextRange,
};
// FIXME: Make this an enum
@@ -279,6 +280,7 @@ struct DiagnosticsContext<'a> {
config: &'a DiagnosticsConfig,
sema: Semantics<'a, RootDatabase>,
resolve: &'a AssistResolveStrategy,
+ edition: Edition,
}
impl DiagnosticsContext<'_> {
@@ -359,12 +361,19 @@ pub fn semantic_diagnostics(
for node in parse.syntax().descendants() {
handlers::useless_braces::useless_braces(&mut res, file_id, &node);
handlers::field_shorthand::field_shorthand(&mut res, file_id, &node);
- handlers::json_is_not_rust::json_in_items(&sema, &mut res, file_id, &node, config);
+ handlers::json_is_not_rust::json_in_items(
+ &sema,
+ &mut res,
+ file_id,
+ &node,
+ config,
+ file_id.edition(),
+ );
}
let module = sema.file_to_module_def(file_id);
- let ctx = DiagnosticsContext { config, sema, resolve };
+ let ctx = DiagnosticsContext { config, sema, resolve, edition: file_id.edition() };
let mut diags = Vec::new();
match module {
@@ -490,6 +499,7 @@ pub fn semantic_diagnostics(
&mut rustc_stack,
&mut clippy_stack,
&mut diagnostics_of_range,
+ ctx.edition,
);
res.retain(|d| d.severity != Severity::Allow);
@@ -512,11 +522,11 @@ pub fn full_diagnostics(
// `__RA_EVERY_LINT` is a fake lint group to allow every lint in proc macros
-static RUSTC_LINT_GROUPS_DICT: Lazy<FxHashMap<&str, Vec<&str>>> =
- Lazy::new(|| build_group_dict(DEFAULT_LINT_GROUPS, &["warnings", "__RA_EVERY_LINT"], ""));
+static RUSTC_LINT_GROUPS_DICT: LazyLock<FxHashMap<&str, Vec<&str>>> =
+ LazyLock::new(|| build_group_dict(DEFAULT_LINT_GROUPS, &["warnings", "__RA_EVERY_LINT"], ""));
-static CLIPPY_LINT_GROUPS_DICT: Lazy<FxHashMap<&str, Vec<&str>>> =
- Lazy::new(|| build_group_dict(CLIPPY_LINT_GROUPS, &["__RA_EVERY_LINT"], "clippy::"));
+static CLIPPY_LINT_GROUPS_DICT: LazyLock<FxHashMap<&str, Vec<&str>>> =
+ LazyLock::new(|| build_group_dict(CLIPPY_LINT_GROUPS, &["__RA_EVERY_LINT"], "clippy::"));
fn build_group_dict(
lint_group: &'static [LintGroup],
@@ -544,6 +554,7 @@ fn handle_lint_attributes(
rustc_stack: &mut FxHashMap<String, Vec<Severity>>,
clippy_stack: &mut FxHashMap<String, Vec<Severity>>,
diagnostics_of_range: &mut FxHashMap<InFile<SyntaxNode>, &mut Diagnostic>,
+ edition: Edition,
) {
let _g = tracing::info_span!("handle_lint_attributes").entered();
let file_id = sema.hir_file_for(root);
@@ -552,9 +563,15 @@ fn handle_lint_attributes(
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);
- });
+ parse_lint_attribute(
+ attr,
+ rustc_stack,
+ clippy_stack,
+ |stack, severity| {
+ stack.push(severity);
+ },
+ edition,
+ );
}
if let Some(it) =
diagnostics_of_range.get_mut(&InFile { file_id, value: node.clone() })
@@ -591,6 +608,7 @@ fn handle_lint_attributes(
rustc_stack,
clippy_stack,
diagnostics_of_range,
+ edition,
);
for stack in [&mut *rustc_stack, &mut *clippy_stack] {
stack.entry("__RA_EVERY_LINT".to_owned()).or_default().pop();
@@ -605,17 +623,24 @@ fn handle_lint_attributes(
rustc_stack,
clippy_stack,
diagnostics_of_range,
+ edition,
);
}
}
}
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");
- }
- });
+ parse_lint_attribute(
+ attr,
+ rustc_stack,
+ clippy_stack,
+ |stack, severity| {
+ if stack.pop() != Some(severity) {
+ never!("Mismatched serevity in walking lint attributes");
+ }
+ },
+ edition,
+ );
}
}
}
@@ -627,6 +652,7 @@ fn parse_lint_attribute(
rustc_stack: &mut FxHashMap<String, Vec<Severity>>,
clippy_stack: &mut FxHashMap<String, Vec<Severity>>,
job: impl Fn(&mut Vec<Severity>, Severity),
+ edition: Edition,
) {
let Some((tag, args_tt)) = attr.as_simple_call() else {
return;
@@ -637,7 +663,7 @@ fn parse_lint_attribute(
"forbid" | "deny" => Severity::Error,
_ => return,
};
- for lint in parse_tt_as_comma_sep_paths(args_tt).into_iter().flatten() {
+ for lint in parse_tt_as_comma_sep_paths(args_tt, edition).into_iter().flatten() {
if let Some(lint) = lint.as_single_name_ref() {
job(rustc_stack.entry(lint.to_string()).or_default(), severity);
}
diff --git a/crates/ide-ssr/src/matching.rs b/crates/ide-ssr/src/matching.rs
index 5f6d77c064..6569f0f555 100644
--- a/crates/ide-ssr/src/matching.rs
+++ b/crates/ide-ssr/src/matching.rs
@@ -8,6 +8,7 @@ use crate::{
};
use hir::{FileRange, ImportPathConfig, Semantics};
use ide_db::FxHashMap;
+use parser::Edition;
use std::{cell::Cell, iter::Peekable};
use syntax::{
ast::{self, AstNode, AstToken, HasGenericArgs},
@@ -626,6 +627,11 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
match_error!("Failed to get receiver type for `{}`", expr.syntax().text())
})?
.original;
+ let edition = self
+ .sema
+ .scope(expr.syntax())
+ .map(|it| it.krate().edition(self.sema.db))
+ .unwrap_or(Edition::CURRENT);
// Temporary needed to make the borrow checker happy.
let res = code_type
.autoderef(self.sema.db)
@@ -635,8 +641,8 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
.ok_or_else(|| {
match_error!(
"Pattern type `{}` didn't match code type `{}`",
- pattern_type.display(self.sema.db),
- code_type.display(self.sema.db)
+ pattern_type.display(self.sema.db, edition),
+ code_type.display(self.sema.db, edition)
)
});
res
diff --git a/crates/ide-ssr/src/replacing.rs b/crates/ide-ssr/src/replacing.rs
index b4b83f62da..65756601f6 100644
--- a/crates/ide-ssr/src/replacing.rs
+++ b/crates/ide-ssr/src/replacing.rs
@@ -2,6 +2,7 @@
use ide_db::{FxHashMap, FxHashSet};
use itertools::Itertools;
+use parser::Edition;
use syntax::{
ast::{self, AstNode, AstToken},
SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize,
@@ -33,7 +34,7 @@ fn matches_to_edit_at_offset(
for m in &matches.matches {
edit_builder.replace(
m.range.range.checked_sub(relative_start).unwrap(),
- render_replace(db, m, file_src, rules),
+ render_replace(db, m, file_src, rules, m.range.file_id.edition()),
);
}
edit_builder.finish()
@@ -54,6 +55,7 @@ struct ReplacementRenderer<'a> {
// is parsed, placeholders don't get split. e.g. if a template of `$a.to_string()` results in `1
// + 2.to_string()` then the placeholder value `1 + 2` was split and needs parenthesis.
placeholder_tokens_requiring_parenthesis: FxHashSet<SyntaxToken>,
+ edition: Edition,
}
fn render_replace(
@@ -61,6 +63,7 @@ fn render_replace(
match_info: &Match,
file_src: &str,
rules: &[ResolvedRule],
+ edition: Edition,
) -> String {
let rule = &rules[match_info.rule_index];
let template = rule
@@ -76,6 +79,7 @@ fn render_replace(
out: String::new(),
placeholder_tokens_requiring_parenthesis: FxHashSet::default(),
placeholder_tokens_by_range: FxHashMap::default(),
+ edition,
};
renderer.render_node(&template.node);
renderer.maybe_rerender_with_extra_parenthesis(&template.node);
@@ -105,7 +109,7 @@ impl ReplacementRenderer<'_> {
fn render_node(&mut self, node: &SyntaxNode) {
if let Some(mod_path) = self.match_info.rendered_template_paths.get(node) {
- self.out.push_str(&mod_path.display(self.db).to_string());
+ self.out.push_str(&mod_path.display(self.db, self.edition).to_string());
// Emit everything except for the segment's name-ref, since we already effectively
// emitted that as part of `mod_path`.
if let Some(path) = ast::Path::cast(node.clone()) {
diff --git a/crates/ide/src/call_hierarchy.rs b/crates/ide/src/call_hierarchy.rs
index 8709310485..155259a138 100644
--- a/crates/ide/src/call_hierarchy.rs
+++ b/crates/ide/src/call_hierarchy.rs
@@ -2,7 +2,7 @@
use std::iter;
-use hir::{DescendPreference, Semantics};
+use hir::Semantics;
use ide_db::{
defs::{Definition, NameClass, NameRefClass},
helpers::pick_best_token,
@@ -86,7 +86,7 @@ pub(crate) fn outgoing_calls(
})?;
let mut calls = CallLocations::default();
- sema.descend_into_macros(DescendPreference::None, token)
+ sema.descend_into_macros_exact(token)
.into_iter()
.filter_map(|it| it.parent_ancestors().nth(1).and_then(ast::Item::cast))
.filter_map(|item| match item {
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs
index e9e5240897..925ae62023 100644
--- a/crates/ide/src/doc_links.rs
+++ b/crates/ide/src/doc_links.rs
@@ -10,10 +10,7 @@ use pulldown_cmark_to_cmark::{cmark_resume_with_options, Options as CMarkOptions
use stdx::format_to;
use url::Url;
-use hir::{
- db::HirDatabase, sym, Adt, AsAssocItem, AssocItem, AssocItemContainer, DescendPreference,
- HasAttrs,
-};
+use hir::{db::HirDatabase, sym, Adt, AsAssocItem, AssocItem, AssocItemContainer, HasAttrs};
use ide_db::{
base_db::{CrateOrigin, LangCrateOrigin, ReleaseChannel, SourceDatabase},
defs::{Definition, NameClass, NameRefClass},
@@ -144,7 +141,7 @@ pub(crate) fn external_docs(
kind if kind.is_trivia() => 0,
_ => 1,
})?;
- let token = sema.descend_into_macros_single(DescendPreference::None, token);
+ let token = sema.descend_into_macros_single_exact(token);
let node = token.parent()?;
let definition = match_ast! {
@@ -289,7 +286,7 @@ impl DocCommentToken {
let original_start = doc_token.text_range().start();
let relative_comment_offset = offset - original_start - prefix_len;
- sema.descend_into_macros(DescendPreference::None, doc_token).into_iter().find_map(|t| {
+ sema.descend_into_macros(doc_token).into_iter().find_map(|t| {
let (node, descended_prefix_len) = match_ast! {
match t {
ast::Comment(comment) => (t.parent()?, TextSize::try_from(comment.prefix().len()).ok()?),
@@ -413,7 +410,8 @@ fn rewrite_url_link(db: &RootDatabase, def: Definition, target: &str) -> Option<
fn mod_path_of_def(db: &RootDatabase, def: Definition) -> Option<String> {
def.canonical_module_path(db).map(|it| {
let mut path = String::new();
- it.flat_map(|it| it.name(db)).for_each(|name| format_to!(path, "{}/", name.display(db)));
+ it.flat_map(|it| it.name(db))
+ .for_each(|name| format_to!(path, "{}/", name.unescaped().display(db)));
path
})
}
@@ -588,9 +586,11 @@ fn filename_and_frag_for_def(
let res = match def {
Definition::Adt(adt) => match adt {
- Adt::Struct(s) => format!("struct.{}.html", s.name(db).display(db.upcast())),
- Adt::Enum(e) => format!("enum.{}.html", e.name(db).display(db.upcast())),
- Adt::Union(u) => format!("union.{}.html", u.name(db).display(db.upcast())),
+ Adt::Struct(s) => {
+ format!("struct.{}.html", s.name(db).unescaped().display(db.upcast()))
+ }
+ Adt::Enum(e) => format!("enum.{}.html", e.name(db).unescaped().display(db.upcast())),
+ Adt::Union(u) => format!("union.{}.html", u.name(db).unescaped().display(db.upcast())),
},
Definition::Module(m) => match m.name(db) {
// `#[doc(keyword = "...")]` is internal used only by rust compiler
@@ -599,34 +599,48 @@ fn filename_and_frag_for_def(
Some(kw) => {
format!("keyword.{}.html", kw)
}
- None => format!("{}/index.html", name.display(db.upcast())),
+ None => format!("{}/index.html", name.unescaped().display(db.upcast())),
}
}
None => String::from("index.html"),
},
- Definition::Trait(t) => format!("trait.{}.html", t.name(db).display(db.upcast())),
- Definition::TraitAlias(t) => format!("traitalias.{}.html", t.name(db).display(db.upcast())),
- Definition::TypeAlias(t) => format!("type.{}.html", t.name(db).display(db.upcast())),
- Definition::BuiltinType(t) => format!("primitive.{}.html", t.name().display(db.upcast())),
- Definition::Function(f) => format!("fn.{}.html", f.name(db).display(db.upcast())),
+ Definition::Trait(t) => {
+ format!("trait.{}.html", t.name(db).unescaped().display(db.upcast()))
+ }
+ Definition::TraitAlias(t) => {
+ format!("traitalias.{}.html", t.name(db).unescaped().display(db.upcast()))
+ }
+ Definition::TypeAlias(t) => {
+ format!("type.{}.html", t.name(db).unescaped().display(db.upcast()))
+ }
+ Definition::BuiltinType(t) => {
+ format!("primitive.{}.html", t.name().unescaped().display(db.upcast()))
+ }
+ Definition::Function(f) => {
+ format!("fn.{}.html", f.name(db).unescaped().display(db.upcast()))
+ }
Definition::Variant(ev) => {
format!(
"enum.{}.html#variant.{}",
- ev.parent_enum(db).name(db).display(db.upcast()),
- ev.name(db).display(db.upcast())
+ ev.parent_enum(db).name(db).unescaped().display(db.upcast()),
+ ev.name(db).unescaped().display(db.upcast())
)
}
- Definition::Const(c) => format!("const.{}.html", c.name(db)?.display(db.upcast())),
- Definition::Static(s) => format!("static.{}.html", s.name(db).display(db.upcast())),
+ Definition::Const(c) => {
+ format!("const.{}.html", c.name(db)?.unescaped().display(db.upcast()))
+ }
+ Definition::Static(s) => {
+ format!("static.{}.html", s.name(db).unescaped().display(db.upcast()))
+ }
Definition::Macro(mac) => match mac.kind(db) {
hir::MacroKind::Declarative
| hir::MacroKind::BuiltIn
| hir::MacroKind::Attr
| hir::MacroKind::ProcMacro => {
- format!("macro.{}.html", mac.name(db).display(db.upcast()))
+ format!("macro.{}.html", mac.name(db).unescaped().display(db.upcast()))
}
hir::MacroKind::Derive => {
- format!("derive.{}.html", mac.name(db).display(db.upcast()))
+ format!("derive.{}.html", mac.name(db).unescaped().display(db.upcast()))
}
},
Definition::Field(field) => {
@@ -639,7 +653,7 @@ fn filename_and_frag_for_def(
return Some((
def,
file,
- Some(format!("structfield.{}", field.name(db).display(db.upcast()))),
+ Some(format!("structfield.{}", field.name(db).unescaped().display(db.upcast()))),
));
}
Definition::SelfType(impl_) => {
@@ -649,7 +663,7 @@ fn filename_and_frag_for_def(
return Some((adt, file, Some(String::from("impl"))));
}
Definition::ExternCrateDecl(it) => {
- format!("{}/index.html", it.name(db).display(db.upcast()))
+ format!("{}/index.html", it.name(db).unescaped().display(db.upcast()))
}
Definition::Local(_)
| Definition::GenericParam(_)
@@ -679,14 +693,16 @@ fn get_assoc_item_fragment(db: &dyn HirDatabase, assoc_item: hir::AssocItem) ->
// Rustdoc makes this decision based on whether a method 'has defaultness'.
// Currently this is only the case for provided trait methods.
if is_trait_method && !function.has_body(db) {
- format!("tymethod.{}", function.name(db).display(db.upcast()))
+ format!("tymethod.{}", function.name(db).unescaped().display(db.upcast()))
} else {
- format!("method.{}", function.name(db).display(db.upcast()))
+ format!("method.{}", function.name(db).unescaped().display(db.upcast()))
}
}
AssocItem::Const(constant) => {
- format!("associatedconstant.{}", constant.name(db)?.display(db.upcast()))
+ format!("associatedconstant.{}", constant.name(db)?.unescaped().display(db.upcast()))
+ }
+ AssocItem::TypeAlias(ty) => {
+ format!("associatedtype.{}", ty.name(db).unescaped().display(db.upcast()))
}
- AssocItem::TypeAlias(ty) => format!("associatedtype.{}", ty.name(db).display(db.upcast())),
})
}
diff --git a/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs
index c8fe45c9cf..a939ed214a 100644
--- a/crates/ide/src/expand_macro.rs
+++ b/crates/ide/src/expand_macro.rs
@@ -1,8 +1,9 @@
-use hir::{DescendPreference, InFile, MacroFileIdExt, Semantics};
+use hir::{InFile, MacroFileIdExt, Semantics};
use ide_db::{
helpers::pick_best_token, syntax_helpers::insert_whitespace_into_node::insert_ws_into, FileId,
RootDatabase,
};
+use span::Edition;
use syntax::{ast, ted, AstNode, NodeOrToken, SyntaxKind, SyntaxNode, T};
use crate::FilePosition;
@@ -40,37 +41,30 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<
// struct Bar;
// ```
- let derive = sema
- .descend_into_macros(DescendPreference::None, tok.clone())
- .into_iter()
- .find_map(|descended| {
- let macro_file = sema.hir_file_for(&descended.parent()?).macro_file()?;
- if !macro_file.is_derive_attr_pseudo_expansion(db) {
- return None;
- }
+ let derive = sema.descend_into_macros_exact(tok.clone()).into_iter().find_map(|descended| {
+ let macro_file = sema.hir_file_for(&descended.parent()?).macro_file()?;
+ if !macro_file.is_derive_attr_pseudo_expansion(db) {
+ return None;
+ }
- let name = descended.parent_ancestors().filter_map(ast::Path::cast).last()?.to_string();
- // up map out of the #[derive] expansion
- let InFile { file_id, value: tokens } =
- hir::InMacroFile::new(macro_file, descended).upmap_once(db);
- let token = sema.parse_or_expand(file_id).covering_element(tokens[0]).into_token()?;
- let attr = token.parent_ancestors().find_map(ast::Attr::cast)?;
- let expansions = sema.expand_derive_macro(&attr)?;
- let idx = attr
- .token_tree()?
- .token_trees_and_tokens()
- .filter_map(NodeOrToken::into_token)
- .take_while(|it| it != &token)
- .filter(|it| it.kind() == T![,])
- .count();
- let expansion = format(
- db,
- SyntaxKind::MACRO_ITEMS,
- position.file_id,
- expansions.get(idx).cloned()?,
- );
- Some(ExpandedMacro { name, expansion })
- });
+ let name = descended.parent_ancestors().filter_map(ast::Path::cast).last()?.to_string();
+ // up map out of the #[derive] expansion
+ let InFile { file_id, value: tokens } =
+ hir::InMacroFile::new(macro_file, descended).upmap_once(db);
+ let token = sema.parse_or_expand(file_id).covering_element(tokens[0]).into_token()?;
+ let attr = token.parent_ancestors().find_map(ast::Attr::cast)?;
+ let expansions = sema.expand_derive_macro(&attr)?;
+ let idx = attr
+ .token_tree()?
+ .token_trees_and_tokens()
+ .filter_map(NodeOrToken::into_token)
+ .take_while(|it| it != &token)
+ .filter(|it| it.kind() == T![,])
+ .count();
+ let expansion =
+ format(db, SyntaxKind::MACRO_ITEMS, position.file_id, expansions.get(idx).cloned()?);
+ Some(ExpandedMacro { name, expansion })
+ });
if derive.is_some() {
return derive;
@@ -83,7 +77,14 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<
if let Some(item) = ast::Item::cast(node.clone()) {
if let Some(def) = sema.resolve_attr_macro_call(&item) {
break (
- def.name(db).display(db).to_string(),
+ def.name(db)
+ .display(
+ db,
+ sema.attach_first_edition(position.file_id)
+ .map(|it| it.edition())
+ .unwrap_or(Edition::CURRENT),
+ )
+ .to_string(),
expand_macro_recur(&sema, &item)?,
SyntaxKind::MACRO_ITEMS,
);
diff --git a/crates/ide/src/extend_selection.rs b/crates/ide/src/extend_selection.rs
index 5f6aaeaabb..3d49082f28 100644
--- a/crates/ide/src/extend_selection.rs
+++ b/crates/ide/src/extend_selection.rs
@@ -1,6 +1,6 @@
use std::iter::successors;
-use hir::{DescendPreference, Semantics};
+use hir::Semantics;
use ide_db::RootDatabase;
use syntax::{
algo::{self, skip_trivia_token},
@@ -140,10 +140,8 @@ fn extend_tokens_from_range(
// compute original mapped token range
let extended = {
- let fst_expanded =
- sema.descend_into_macros_single(DescendPreference::None, first_token.clone());
- let lst_expanded =
- sema.descend_into_macros_single(DescendPreference::None, last_token.clone());
+ let fst_expanded = sema.descend_into_macros_single_exact(first_token.clone());
+ let lst_expanded = sema.descend_into_macros_single_exact(last_token.clone());
let mut lca =
algo::least_common_ancestor(&fst_expanded.parent()?, &lst_expanded.parent()?)?;
lca = shallowest_node(&lca);
@@ -157,7 +155,7 @@ fn extend_tokens_from_range(
let validate = || {
let extended = &extended;
move |token: &SyntaxToken| -> bool {
- let expanded = sema.descend_into_macros_single(DescendPreference::None, token.clone());
+ let expanded = sema.descend_into_macros_single_exact(token.clone());
let parent = match expanded.parent() {
Some(it) => it,
None => return false,
diff --git a/crates/ide/src/goto_declaration.rs b/crates/ide/src/goto_declaration.rs
index 6076de54eb..6ae9dde84b 100644
--- a/crates/ide/src/goto_declaration.rs
+++ b/crates/ide/src/goto_declaration.rs
@@ -1,4 +1,4 @@
-use hir::{AsAssocItem, DescendPreference, Semantics};
+use hir::{AsAssocItem, Semantics};
use ide_db::{
defs::{Definition, NameClass, NameRefClass},
RootDatabase,
@@ -29,7 +29,7 @@ pub(crate) fn goto_declaration(
.find(|it| matches!(it.kind(), IDENT | T![self] | T![super] | T![crate] | T![Self]))?;
let range = original_token.text_range();
let info: Vec<NavigationTarget> = sema
- .descend_into_macros(DescendPreference::None, original_token)
+ .descend_into_macros(original_token)
.iter()
.filter_map(|token| {
let parent = token.parent()?;
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs
index 8a8bc07945..971cd3ef58 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -5,10 +5,7 @@ use crate::{
navigation_target::{self, ToNav},
FilePosition, NavigationTarget, RangeInfo, TryToNav, UpmappingResult,
};
-use hir::{
- AsAssocItem, AssocItem, DescendPreference, FileRange, InFile, MacroFileIdExt, ModuleDef,
- Semantics,
-};
+use hir::{AsAssocItem, AssocItem, FileRange, InFile, MacroFileIdExt, ModuleDef, Semantics};
use ide_db::{
base_db::{AnchoredPath, FileLoader, SourceDatabase},
defs::{Definition, IdentClass},
@@ -17,7 +14,7 @@ use ide_db::{
};
use itertools::Itertools;
-use span::FileId;
+use span::{Edition, FileId};
use syntax::{
ast::{self, HasLoopBody},
match_ast, AstNode, AstToken,
@@ -44,6 +41,8 @@ pub(crate) fn goto_definition(
) -> Option<RangeInfo<Vec<NavigationTarget>>> {
let sema = &Semantics::new(db);
let file = sema.parse_guess_edition(file_id).syntax().clone();
+ let edition =
+ sema.attach_first_edition(file_id).map(|it| it.edition()).unwrap_or(Edition::CURRENT);
let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
IDENT
| INT_NUMBER
@@ -55,7 +54,7 @@ pub(crate) fn goto_definition(
| COMMENT => 4,
// index and prefix ops
T!['['] | T![']'] | T![?] | T![*] | T![-] | T![!] => 3,
- kind if kind.is_keyword() => 2,
+ kind if kind.is_keyword(edition) => 2,
T!['('] | T![')'] => 2,
kind if kind.is_trivia() => 0,
_ => 1,
@@ -84,7 +83,7 @@ pub(crate) fn goto_definition(
}
let navs = sema
- .descend_into_macros(DescendPreference::None, original_token.clone())
+ .descend_into_macros(original_token.clone())
.into_iter()
.filter_map(|token| {
let parent = token.parent()?;
@@ -249,10 +248,7 @@ pub(crate) fn find_fn_or_blocks(
None
};
- sema.descend_into_macros(DescendPreference::None, token.clone())
- .into_iter()
- .filter_map(find_ancestors)
- .collect_vec()
+ sema.descend_into_macros(token.clone()).into_iter().filter_map(find_ancestors).collect_vec()
}
fn nav_for_exit_points(
@@ -367,7 +363,7 @@ pub(crate) fn find_loops(
None
};
- sema.descend_into_macros(DescendPreference::None, token.clone())
+ sema.descend_into_macros(token.clone())
.into_iter()
.filter_map(find_ancestors)
.collect_vec()
@@ -2735,4 +2731,23 @@ fn main() {
"#,
)
}
+
+ #[test]
+ fn shadow_builtin_macro() {
+ check(
+ r#"
+//- minicore: column
+//- /a.rs crate:a
+#[macro_export]
+macro_rules! column { () => {} }
+ // ^^^^^^
+
+//- /b.rs crate:b deps:a
+use a::column;
+fn foo() {
+ $0column!();
+}
+ "#,
+ );
+ }
}
diff --git a/crates/ide/src/goto_implementation.rs b/crates/ide/src/goto_implementation.rs
index 2eff7796d5..e36c8ee2f3 100644
--- a/crates/ide/src/goto_implementation.rs
+++ b/crates/ide/src/goto_implementation.rs
@@ -1,4 +1,4 @@
-use hir::{AsAssocItem, DescendPreference, Impl, Semantics};
+use hir::{AsAssocItem, Impl, Semantics};
use ide_db::{
defs::{Definition, NameClass, NameRefClass},
helpers::pick_best_token,
@@ -10,7 +10,7 @@ use crate::{FilePosition, NavigationTarget, RangeInfo, TryToNav};
// Feature: Go to Implementation
//
-// Navigates to the impl blocks of types.
+// Navigates to the impl items of types.
//
// |===
// | Editor | Shortcut
@@ -32,48 +32,55 @@ pub(crate) fn goto_implementation(
_ => 0,
})?;
let range = original_token.text_range();
- let navs =
- sema.descend_into_macros_single(DescendPreference::SameText, original_token)
- .parent()
- .and_then(ast::NameLike::cast)
- .and_then(|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,
- })
- .and_then(|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.container_or_implemented_trait(sema.db)?;
- impls_for_trait_item(&sema, trait_, name)
+ let navs = sema
+ .descend_into_macros_exact(original_token)
+ .iter()
+ .filter_map(|token| {
+ token
+ .parent()
+ .and_then(ast::NameLike::cast)
+ .and_then(|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,
+ })
}
- Definition::Const(c) => {
- let assoc = c.as_assoc_item(sema.db)?;
- let name = assoc.name(sema.db)?;
- let trait_ = assoc.container_or_implemented_trait(sema.db)?;
- impls_for_trait_item(&sema, trait_, name)
- }
- _ => return None,
- };
- Some(navs)
- })
- .unwrap_or_default();
+ 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,
+ })
+ .and_then(|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.container_or_implemented_trait(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.container_or_implemented_trait(sema.db)?;
+ impls_for_trait_item(&sema, trait_, name)
+ }
+ _ => return None,
+ };
+ Some(navs)
+ })
+ })
+ .flatten()
+ .collect();
Some(RangeInfo { range, info: navs })
}
diff --git a/crates/ide/src/goto_type_definition.rs b/crates/ide/src/goto_type_definition.rs
index f75b8fb7d0..ca04b7bb5a 100644
--- a/crates/ide/src/goto_type_definition.rs
+++ b/crates/ide/src/goto_type_definition.rs
@@ -1,4 +1,4 @@
-use hir::{DescendPreference, GenericParam};
+use hir::GenericParam;
use ide_db::{base_db::Upcast, defs::Definition, helpers::pick_best_token, RootDatabase};
use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, T};
@@ -69,7 +69,7 @@ pub(crate) fn goto_type_definition(
}
let range = token.text_range();
- sema.descend_into_macros(DescendPreference::None, token)
+ sema.descend_into_macros(token)
.into_iter()
.filter_map(|token| {
let ty = sema
diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs
index 8fcd38b4e3..5348e855be 100644
--- a/crates/ide/src/highlight_related.rs
+++ b/crates/ide/src/highlight_related.rs
@@ -1,6 +1,6 @@
use std::iter;
-use hir::{db, DescendPreference, FilePosition, FileRange, HirFileId, InFile, Semantics};
+use hir::{db, FilePosition, FileRange, HirFileId, InFile, Semantics};
use ide_db::{
defs::{Definition, IdentClass},
helpers::pick_best_token,
@@ -65,7 +65,7 @@ pub(crate) fn highlight_related(
let token = pick_best_token(syntax.token_at_offset(offset), |kind| match kind {
T![?] => 4, // prefer `?` when the cursor is sandwiched like in `await$0?`
T![->] => 4,
- kind if kind.is_keyword() => 3,
+ kind if kind.is_keyword(file_id.edition()) => 3,
IDENT | INT_NUMBER => 2,
T![|] => 1,
_ => 0,
@@ -542,7 +542,7 @@ fn cover_range(r0: Option<TextRange>, r1: Option<TextRange>) -> Option<TextRange
}
fn find_defs(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> FxHashSet<Definition> {
- sema.descend_into_macros(DescendPreference::None, token)
+ sema.descend_into_macros_exact(token)
.into_iter()
.filter_map(|token| IdentClass::classify_token(sema, &token))
.flat_map(IdentClass::definitions_no_ops)
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 500674e32b..124db2985b 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -6,7 +6,7 @@ mod tests;
use std::{iter, ops::Not};
use either::Either;
-use hir::{db::DefDatabase, DescendPreference, HasCrate, HasSource, LangItem, Semantics};
+use hir::{db::DefDatabase, HasCrate, HasSource, LangItem, Semantics};
use ide_db::{
defs::{Definition, IdentClass, NameRefClass, OperatorClass},
famous_defs::FamousDefs,
@@ -14,6 +14,7 @@ use ide_db::{
FileRange, FxIndexSet, RootDatabase,
};
use itertools::{multizip, Itertools};
+use span::Edition;
use syntax::{ast, AstNode, SyntaxKind::*, SyntaxNode, T};
use crate::{
@@ -57,7 +58,7 @@ pub enum HoverDocFormat {
PlainText,
}
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub enum HoverAction {
Runnable(Runnable),
Implementation(FilePosition),
@@ -66,7 +67,11 @@ pub enum HoverAction {
}
impl HoverAction {
- fn goto_type_from_targets(db: &RootDatabase, targets: Vec<hir::ModuleDef>) -> Option<Self> {
+ fn goto_type_from_targets(
+ db: &RootDatabase,
+ targets: Vec<hir::ModuleDef>,
+ edition: Edition,
+ ) -> Option<Self> {
let targets = targets
.into_iter()
.filter_map(|it| {
@@ -74,7 +79,8 @@ impl HoverAction {
mod_path: render::path(
db,
it.module(db)?,
- it.name(db).map(|name| name.display(db).to_string()),
+ it.name(db).map(|name| name.display(db, edition).to_string()),
+ edition,
),
nav: it.try_to_nav(db)?.call_site(),
})
@@ -91,7 +97,7 @@ pub struct HoverGotoTypeData {
}
/// Contains the results when hovering over an item
-#[derive(Debug, Default)]
+#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)]
pub struct HoverResult {
pub markup: Markup,
pub actions: Vec<HoverAction>,
@@ -110,10 +116,12 @@ pub(crate) fn hover(
) -> Option<RangeInfo<HoverResult>> {
let sema = &hir::Semantics::new(db);
let file = sema.parse_guess_edition(file_id).syntax().clone();
+ let edition =
+ sema.attach_first_edition(file_id).map(|it| it.edition()).unwrap_or(Edition::CURRENT);
let mut res = if range.is_empty() {
- hover_simple(sema, FilePosition { file_id, offset: range.start() }, file, config)
+ hover_offset(sema, FilePosition { file_id, offset: range.start() }, file, config, edition)
} else {
- hover_ranged(sema, frange, file, config)
+ hover_ranged(sema, frange, file, config, edition)
}?;
if let HoverDocFormat::PlainText = config.format {
@@ -123,11 +131,12 @@ pub(crate) fn hover(
}
#[allow(clippy::field_reassign_with_default)]
-fn hover_simple(
+fn hover_offset(
sema: &Semantics<'_, RootDatabase>,
FilePosition { file_id, offset }: FilePosition,
file: SyntaxNode,
config: &HoverConfig,
+ edition: Edition,
) -> Option<RangeInfo<HoverResult>> {
let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
IDENT
@@ -140,7 +149,7 @@ fn hover_simple(
| T![_] => 4,
// index and prefix ops and closure pipe
T!['['] | T![']'] | T![?] | T![*] | T![-] | T![!] | T![|] => 3,
- kind if kind.is_keyword() => 2,
+ kind if kind.is_keyword(edition) => 2,
T!['('] | T![')'] => 2,
kind if kind.is_trivia() => 0,
_ => 1,
@@ -149,7 +158,7 @@ fn hover_simple(
if let Some(doc_comment) = token_as_doc_comment(&original_token) {
cov_mark::hit!(no_highlight_on_comment_hover);
return doc_comment.get_definition_with_descend_at(sema, offset, |def, node, range| {
- let res = hover_for_definition(sema, file_id, def, &node, None, config);
+ let res = hover_for_definition(sema, file_id, def, &node, None, config, edition);
Some(RangeInfo::new(range, res))
});
}
@@ -164,38 +173,48 @@ fn hover_simple(
&original_token.parent()?,
None,
config,
+ edition,
);
return Some(RangeInfo::new(range, res));
}
- let in_attr = original_token
- .parent_ancestors()
- .filter_map(ast::Item::cast)
- .any(|item| sema.is_attr_macro_call(&item))
- && !matches!(
- original_token.parent().and_then(ast::TokenTree::cast),
- Some(tt) if tt.syntax().ancestors().any(|it| ast::Meta::can_cast(it.kind()))
- );
-
// prefer descending the same token kind in attribute expansions, in normal macros text
// equivalency is more important
- let descended = sema.descend_into_macros(
- if in_attr { DescendPreference::SameKind } else { DescendPreference::SameText },
- original_token.clone(),
- );
- let descended = || descended.iter();
+ let mut descended = sema.descend_into_macros(original_token.clone());
+
+ let kind = original_token.kind();
+ let text = original_token.text();
+ let ident_kind = kind.is_any_identifier();
+
+ descended.sort_by_cached_key(|tok| {
+ let tok_kind = tok.kind();
+
+ let exact_same_kind = tok_kind == kind;
+ let both_idents = exact_same_kind || (tok_kind.is_any_identifier() && ident_kind);
+ let same_text = tok.text() == text;
+ // anything that mapped into a token tree has likely no semantic information
+ let no_tt_parent = tok.parent().map_or(false, |it| it.kind() != TOKEN_TREE);
+ !((both_idents as usize)
+ | ((exact_same_kind as usize) << 1)
+ | ((same_text as usize) << 2)
+ | ((no_tt_parent as usize) << 3))
+ });
- let result = descended()
- // try lint hover
- .find_map(|token| {
+ let mut res = vec![];
+ for token in descended {
+ let is_same_kind = token.kind() == kind;
+ let lint_hover = (|| {
// FIXME: Definition should include known lints and the like instead of having this special case here
let attr = token.parent_ancestors().find_map(ast::Attr::cast)?;
- render::try_for_lint(&attr, token)
- })
- // try definitions
- .or_else(|| {
- descended()
- .filter_map(|token| {
+ render::try_for_lint(&attr, &token)
+ })();
+ if let Some(lint_hover) = lint_hover {
+ res.push(lint_hover);
+ continue;
+ }
+ let definitions = (|| {
+ Some(
+ 'a: {
let node = token.parent()?;
// special case macro calls, we wanna render the invoked arm index
@@ -210,11 +229,11 @@ fn hover_simple(
.and_then(ast::MacroCall::cast)
{
if let Some(macro_) = sema.resolve_macro_call(&macro_call) {
- return Some(vec![(
+ break 'a vec![(
Definition::Macro(macro_),
sema.resolve_macro_call_arm(&macro_call),
node,
- )]);
+ )];
}
}
}
@@ -223,88 +242,101 @@ fn hover_simple(
match IdentClass::classify_node(sema, &node)? {
// It's better for us to fall back to the keyword hover here,
// rendering poll is very confusing
- IdentClass::Operator(OperatorClass::Await(_)) => None,
+ IdentClass::Operator(OperatorClass::Await(_)) => return None,
IdentClass::NameRefClass(NameRefClass::ExternCrateShorthand {
decl,
..
- }) => Some(vec![(Definition::ExternCrateDecl(decl), None, node)]),
+ }) => {
+ vec![(Definition::ExternCrateDecl(decl), None, node)]
+ }
- class => Some(
+ class => {
multizip((class.definitions(), iter::repeat(None), iter::repeat(node)))
- .collect::<Vec<_>>(),
- ),
+ .collect::<Vec<_>>()
+ }
}
- })
- .flatten()
+ }
+ .into_iter()
.unique_by(|&(def, _, _)| def)
.map(|(def, macro_arm, node)| {
- hover_for_definition(sema, file_id, def, &node, macro_arm, config)
- })
- .reduce(|mut acc: HoverResult, HoverResult { markup, actions }| {
- acc.actions.extend(actions);
- acc.markup = Markup::from(format!("{}\n---\n{markup}", acc.markup));
- acc
+ hover_for_definition(sema, file_id, def, &node, macro_arm, config, edition)
})
- })
- // try keywords
- .or_else(|| descended().find_map(|token| render::keyword(sema, config, token)))
- // try _ hovers
- .or_else(|| descended().find_map(|token| render::underscore(sema, config, token)))
- // try rest pattern hover
- .or_else(|| {
- descended().find_map(|token| {
- if token.kind() != DOT2 {
- return None;
- }
+ .collect::<Vec<_>>(),
+ )
+ })();
+ if let Some(definitions) = definitions {
+ res.extend(definitions);
+ continue;
+ }
+ let keywords = || render::keyword(sema, config, &token, edition);
+ let underscore = || {
+ if !is_same_kind {
+ return None;
+ }
+ render::underscore(sema, config, &token, edition)
+ };
+ let rest_pat = || {
+ if !is_same_kind || token.kind() != DOT2 {
+ return None;
+ }
- let rest_pat = token.parent().and_then(ast::RestPat::cast)?;
- let record_pat_field_list =
- rest_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast)?;
+ let rest_pat = token.parent().and_then(ast::RestPat::cast)?;
+ let record_pat_field_list =
+ rest_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast)?;
- let record_pat =
- record_pat_field_list.syntax().parent().and_then(ast::RecordPat::cast)?;
+ let record_pat =
+ record_pat_field_list.syntax().parent().and_then(ast::RecordPat::cast)?;
- Some(render::struct_rest_pat(sema, config, &record_pat))
- })
- })
- // try () call hovers
- .or_else(|| {
- descended().find_map(|token| {
- if token.kind() != T!['('] && token.kind() != T![')'] {
- return None;
- }
- let arg_list = token.parent().and_then(ast::ArgList::cast)?.syntax().parent()?;
- let call_expr = syntax::match_ast! {
- match arg_list {
- ast::CallExpr(expr) => expr.into(),
- ast::MethodCallExpr(expr) => expr.into(),
- _ => return None,
- }
- };
- render::type_info_of(sema, config, &Either::Left(call_expr))
- })
- })
- // try closure
- .or_else(|| {
- descended().find_map(|token| {
- if token.kind() != T![|] {
- return None;
+ Some(render::struct_rest_pat(sema, config, &record_pat, edition))
+ };
+ let call = || {
+ if !is_same_kind || token.kind() != T!['('] && token.kind() != T![')'] {
+ return None;
+ }
+ let arg_list = token.parent().and_then(ast::ArgList::cast)?.syntax().parent()?;
+ let call_expr = syntax::match_ast! {
+ match arg_list {
+ ast::CallExpr(expr) => expr.into(),
+ ast::MethodCallExpr(expr) => expr.into(),
+ _ => return None,
}
- let c = token.parent().and_then(|x| x.parent()).and_then(ast::ClosureExpr::cast)?;
- render::closure_expr(sema, config, c)
- })
- })
- // tokens
- .or_else(|| {
- render::literal(sema, original_token.clone())
+ };
+ render::type_info_of(sema, config, &Either::Left(call_expr), edition)
+ };
+ let closure = || {
+ if !is_same_kind || token.kind() != T![|] {
+ return None;
+ }
+ let c = token.parent().and_then(|x| x.parent()).and_then(ast::ClosureExpr::cast)?;
+ render::closure_expr(sema, config, c, edition)
+ };
+ let literal = || {
+ render::literal(sema, original_token.clone(), edition)
.map(|markup| HoverResult { markup, actions: vec![] })
- });
+ };
+ if let Some(result) = keywords()
+ .or_else(underscore)
+ .or_else(rest_pat)
+ .or_else(call)
+ .or_else(closure)
+ .or_else(literal)
+ {
+ res.push(result)
+ }
+ }
- result.map(|mut res: HoverResult| {
- res.actions = dedupe_or_merge_hover_actions(res.actions);
- RangeInfo::new(original_token.text_range(), res)
- })
+ res.into_iter()
+ .unique()
+ .reduce(|mut acc: HoverResult, HoverResult { markup, actions }| {
+ acc.actions.extend(actions);
+ acc.markup = Markup::from(format!("{}\n---\n{markup}", acc.markup));
+ acc
+ })
+ .map(|mut res: HoverResult| {
+ res.actions = dedupe_or_merge_hover_actions(res.actions);
+ RangeInfo::new(original_token.text_range(), res)
+ })
}
fn hover_ranged(
@@ -312,6 +344,7 @@ fn hover_ranged(
FileRange { range, .. }: FileRange,
file: SyntaxNode,
config: &HoverConfig,
+ edition: Edition,
) -> Option<RangeInfo<HoverResult>> {
// FIXME: make this work in attributes
let expr_or_pat = file
@@ -320,15 +353,17 @@ fn hover_ranged(
.take_while(|it| ast::MacroCall::can_cast(it.kind()) || !ast::Item::can_cast(it.kind()))
.find_map(Either::<ast::Expr, ast::Pat>::cast)?;
let res = match &expr_or_pat {
- Either::Left(ast::Expr::TryExpr(try_expr)) => render::try_expr(sema, config, try_expr),
+ Either::Left(ast::Expr::TryExpr(try_expr)) => {
+ render::try_expr(sema, config, try_expr, edition)
+ }
Either::Left(ast::Expr::PrefixExpr(prefix_expr))
if prefix_expr.op_kind() == Some(ast::UnaryOp::Deref) =>
{
- render::deref_expr(sema, config, prefix_expr)
+ render::deref_expr(sema, config, prefix_expr, edition)
}
_ => None,
};
- let res = res.or_else(|| render::type_info_of(sema, config, &expr_or_pat));
+ let res = res.or_else(|| render::type_info_of(sema, config, &expr_or_pat, edition));
res.map(|it| {
let range = match expr_or_pat {
Either::Left(it) => it.syntax().text_range(),
@@ -346,6 +381,7 @@ pub(crate) fn hover_for_definition(
scope_node: &SyntaxNode,
macro_arm: Option<u32>,
config: &HoverConfig,
+ edition: Edition,
) -> HoverResult {
let famous_defs = match &def {
Definition::BuiltinType(_) => sema.scope(scope_node).map(|it| FamousDefs(sema, it.krate())),
@@ -369,15 +405,22 @@ pub(crate) fn hover_for_definition(
};
let notable_traits = def_ty.map(|ty| notable_traits(db, &ty)).unwrap_or_default();
- let markup =
- render::definition(sema.db, def, famous_defs.as_ref(), &notable_traits, macro_arm, config);
+ let markup = render::definition(
+ sema.db,
+ def,
+ famous_defs.as_ref(),
+ &notable_traits,
+ macro_arm,
+ config,
+ edition,
+ );
HoverResult {
markup: render::process_markup(sema.db, def, &markup, config),
actions: [
show_fn_references_action(sema.db, def),
show_implementations_action(sema.db, def),
runnable_action(sema, def, file_id),
- goto_type_action_for_def(sema.db, def, &notable_traits),
+ goto_type_action_for_def(sema.db, def, &notable_traits, edition),
]
.into_iter()
.flatten()
@@ -469,6 +512,7 @@ fn goto_type_action_for_def(
db: &RootDatabase,
def: Definition,
notable_traits: &[(hir::Trait, Vec<(Option<hir::Type>, hir::Name)>)],
+ edition: Edition,
) -> Option<HoverAction> {
let mut targets: Vec<hir::ModuleDef> = Vec::new();
let mut push_new_def = |item: hir::ModuleDef| {
@@ -499,13 +543,13 @@ fn goto_type_action_for_def(
Definition::GenericParam(hir::GenericParam::ConstParam(it)) => it.ty(db),
Definition::Field(field) => field.ty(db),
Definition::Function(function) => function.ret_type(db),
- _ => return HoverAction::goto_type_from_targets(db, targets),
+ _ => return HoverAction::goto_type_from_targets(db, targets, edition),
};
walk_and_push_ty(db, &ty, &mut push_new_def);
}
- HoverAction::goto_type_from_targets(db, targets)
+ HoverAction::goto_type_from_targets(db, targets, edition)
}
fn walk_and_push_ty(
diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs
index 7091b15b8a..3e41b42be4 100644
--- a/crates/ide/src/hover/render.rs
+++ b/crates/ide/src/hover/render.rs
@@ -20,6 +20,7 @@ use rustc_apfloat::{
ieee::{Half as f16, Quad as f128},
Float,
};
+use span::Edition;
use stdx::format_to;
use syntax::{algo, ast, match_ast, AstNode, AstToken, Direction, SyntaxToken, T};
@@ -34,27 +35,30 @@ pub(super) fn type_info_of(
sema: &Semantics<'_, RootDatabase>,
_config: &HoverConfig,
expr_or_pat: &Either<ast::Expr, ast::Pat>,
+ edition: Edition,
) -> Option<HoverResult> {
let ty_info = match expr_or_pat {
Either::Left(expr) => sema.type_of_expr(expr)?,
Either::Right(pat) => sema.type_of_pat(pat)?,
};
- type_info(sema, _config, ty_info)
+ type_info(sema, _config, ty_info, edition)
}
pub(super) fn closure_expr(
sema: &Semantics<'_, RootDatabase>,
config: &HoverConfig,
c: ast::ClosureExpr,
+ edition: Edition,
) -> Option<HoverResult> {
let TypeInfo { original, .. } = sema.type_of_expr(&c.into())?;
- closure_ty(sema, config, &TypeInfo { original, adjusted: None })
+ closure_ty(sema, config, &TypeInfo { original, adjusted: None }, edition)
}
pub(super) fn try_expr(
sema: &Semantics<'_, RootDatabase>,
_config: &HoverConfig,
try_expr: &ast::TryExpr,
+ edition: Edition,
) -> Option<HoverResult> {
let inner_ty = sema.type_of_expr(&try_expr.expr()?)?.original;
let mut ancestors = try_expr.syntax().ancestors();
@@ -117,12 +121,12 @@ pub(super) fn try_expr(
};
walk_and_push_ty(sema.db, &inner_ty, &mut push_new_def);
walk_and_push_ty(sema.db, &body_ty, &mut push_new_def);
- if let Some(actions) = HoverAction::goto_type_from_targets(sema.db, targets) {
+ if let Some(actions) = HoverAction::goto_type_from_targets(sema.db, targets, edition) {
res.actions.push(actions);
}
- let inner_ty = inner_ty.display(sema.db).to_string();
- let body_ty = body_ty.display(sema.db).to_string();
+ let inner_ty = inner_ty.display(sema.db, edition).to_string();
+ let body_ty = body_ty.display(sema.db, edition).to_string();
let ty_len_max = inner_ty.len().max(body_ty.len());
let l = "Propagated as: ".len() - " Type: ".len();
@@ -146,6 +150,7 @@ pub(super) fn deref_expr(
sema: &Semantics<'_, RootDatabase>,
_config: &HoverConfig,
deref_expr: &ast::PrefixExpr,
+ edition: Edition,
) -> Option<HoverResult> {
let inner_ty = sema.type_of_expr(&deref_expr.expr()?)?.original;
let TypeInfo { original, adjusted } =
@@ -163,9 +168,9 @@ pub(super) fn deref_expr(
res.markup = if let Some(adjusted_ty) = adjusted {
walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def);
- let original = original.display(sema.db).to_string();
- let adjusted = adjusted_ty.display(sema.db).to_string();
- let inner = inner_ty.display(sema.db).to_string();
+ let original = original.display(sema.db, edition).to_string();
+ let adjusted = adjusted_ty.display(sema.db, edition).to_string();
+ let inner = inner_ty.display(sema.db, edition).to_string();
let type_len = "To type: ".len();
let coerced_len = "Coerced to: ".len();
let deref_len = "Dereferenced from: ".len();
@@ -183,8 +188,8 @@ pub(super) fn deref_expr(
)
.into()
} else {
- let original = original.display(sema.db).to_string();
- let inner = inner_ty.display(sema.db).to_string();
+ let original = original.display(sema.db, edition).to_string();
+ let inner = inner_ty.display(sema.db, edition).to_string();
let type_len = "To type: ".len();
let deref_len = "Dereferenced from: ".len();
let max_len = (original.len() + type_len).max(inner.len() + deref_len);
@@ -197,7 +202,7 @@ pub(super) fn deref_expr(
)
.into()
};
- if let Some(actions) = HoverAction::goto_type_from_targets(sema.db, targets) {
+ if let Some(actions) = HoverAction::goto_type_from_targets(sema.db, targets, edition) {
res.actions.push(actions);
}
@@ -208,6 +213,7 @@ pub(super) fn underscore(
sema: &Semantics<'_, RootDatabase>,
config: &HoverConfig,
token: &SyntaxToken,
+ edition: Edition,
) -> Option<HoverResult> {
if token.kind() != T![_] {
return None;
@@ -216,8 +222,8 @@ pub(super) fn underscore(
let _it = match_ast! {
match parent {
ast::InferType(it) => it,
- ast::UnderscoreExpr(it) => return type_info_of(sema, config, &Either::Left(ast::Expr::UnderscoreExpr(it))),
- ast::WildcardPat(it) => return type_info_of(sema, config, &Either::Right(ast::Pat::WildcardPat(it))),
+ ast::UnderscoreExpr(it) => return type_info_of(sema, config, &Either::Left(ast::Expr::UnderscoreExpr(it)),edition),
+ ast::WildcardPat(it) => return type_info_of(sema, config, &Either::Right(ast::Pat::WildcardPat(it)),edition),
_ => return None,
}
};
@@ -250,16 +256,18 @@ pub(super) fn keyword(
sema: &Semantics<'_, RootDatabase>,
config: &HoverConfig,
token: &SyntaxToken,
+ edition: Edition,
) -> Option<HoverResult> {
- if !token.kind().is_keyword() || !config.documentation || !config.keywords {
+ if !token.kind().is_keyword(edition) || !config.documentation || !config.keywords {
return None;
}
let parent = token.parent()?;
let famous_defs = FamousDefs(sema, sema.scope(&parent)?.krate());
- let KeywordHint { description, keyword_mod, actions } = keyword_hints(sema, token, parent);
+ let KeywordHint { description, keyword_mod, actions } =
+ keyword_hints(sema, token, parent, edition);
- let doc_owner = find_std_module(&famous_defs, &keyword_mod)?;
+ let doc_owner = find_std_module(&famous_defs, &keyword_mod, edition)?;
let docs = doc_owner.docs(sema.db)?;
let markup = process_markup(
sema.db,
@@ -277,6 +285,7 @@ pub(super) fn struct_rest_pat(
sema: &Semantics<'_, RootDatabase>,
_config: &HoverConfig,
pattern: &ast::RecordPat,
+ edition: Edition,
) -> HoverResult {
let missing_fields = sema.record_pattern_missing_fields(pattern);
@@ -298,7 +307,7 @@ pub(super) fn struct_rest_pat(
res.markup = {
let mut s = String::from(".., ");
for (f, _) in &missing_fields {
- s += f.display(sema.db).to_string().as_ref();
+ s += f.display(sema.db, edition).to_string().as_ref();
s += ", ";
}
// get rid of trailing comma
@@ -306,7 +315,7 @@ pub(super) fn struct_rest_pat(
Markup::fenced_block(&s)
};
- if let Some(actions) = HoverAction::goto_type_from_targets(sema.db, targets) {
+ if let Some(actions) = HoverAction::goto_type_from_targets(sema.db, targets, edition) {
res.actions.push(actions);
}
res
@@ -365,7 +374,7 @@ pub(super) fn process_markup(
Markup::from(markup)
}
-fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String> {
+fn definition_owner_name(db: &RootDatabase, def: &Definition, edition: Edition) -> Option<String> {
match def {
Definition::Field(f) => Some(f.parent_def(db).name(db)),
Definition::Local(l) => l.parent(db).name(db),
@@ -384,17 +393,22 @@ fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String>
}
}
}
- .map(|name| name.display(db).to_string())
+ .map(|name| name.display(db, edition).to_string())
}
-pub(super) fn path(db: &RootDatabase, module: hir::Module, item_name: Option<String>) -> String {
+pub(super) fn path(
+ db: &RootDatabase,
+ module: hir::Module,
+ item_name: Option<String>,
+ edition: Edition,
+) -> String {
let crate_name =
db.crate_graph()[module.krate().into()].display_name.as_ref().map(|it| it.to_string());
let module_path = module
.path_to_root(db)
.into_iter()
.rev()
- .flat_map(|it| it.name(db).map(|name| name.display(db).to_string()));
+ .flat_map(|it| it.name(db).map(|name| name.display(db, edition).to_string()));
crate_name.into_iter().chain(module_path).chain(item_name).join("::")
}
@@ -405,39 +419,42 @@ pub(super) fn definition(
notable_traits: &[(Trait, Vec<(Option<Type>, Name)>)],
macro_arm: Option<u32>,
config: &HoverConfig,
+ edition: Edition,
) -> Markup {
- let mod_path = definition_mod_path(db, &def);
+ let mod_path = definition_mod_path(db, &def, edition);
let label = match def {
Definition::Trait(trait_) => {
- trait_.display_limited(db, config.max_trait_assoc_items_count).to_string()
+ trait_.display_limited(db, config.max_trait_assoc_items_count, edition).to_string()
}
Definition::Adt(adt @ (Adt::Struct(_) | Adt::Union(_))) => {
- adt.display_limited(db, config.max_fields_count).to_string()
+ adt.display_limited(db, config.max_fields_count, edition).to_string()
}
Definition::Variant(variant) => {
- variant.display_limited(db, config.max_fields_count).to_string()
+ variant.display_limited(db, config.max_fields_count, edition).to_string()
}
Definition::Adt(adt @ Adt::Enum(_)) => {
- adt.display_limited(db, config.max_enum_variants_count).to_string()
+ adt.display_limited(db, config.max_enum_variants_count, edition).to_string()
}
Definition::SelfType(impl_def) => {
let self_ty = &impl_def.self_ty(db);
match self_ty.as_adt() {
- Some(adt) => adt.display_limited(db, config.max_fields_count).to_string(),
- None => self_ty.display(db).to_string(),
+ Some(adt) => adt.display_limited(db, config.max_fields_count, edition).to_string(),
+ None => self_ty.display(db, edition).to_string(),
}
}
Definition::Macro(it) => {
- let mut label = it.display(db).to_string();
+ let mut label = it.display(db, edition).to_string();
if let Some(macro_arm) = macro_arm {
format_to!(label, " // matched arm #{}", macro_arm);
}
label
}
- Definition::Function(fn_) => fn_.display_with_container_bounds(db, true).to_string(),
- _ => def.label(db),
+ Definition::Function(fn_) => {
+ fn_.display_with_container_bounds(db, true, edition).to_string()
+ }
+ _ => def.label(db, edition),
};
- let docs = def.docs(db, famous_defs);
+ let docs = def.docs(db, famous_defs, edition);
let value = (|| match def {
Definition::Variant(it) => {
if !it.parent_enum(db).is_data_carrying(db) {
@@ -452,7 +469,7 @@ pub(super) fn definition(
}
}
Definition::Const(it) => {
- let body = it.render_eval(db);
+ let body = it.render_eval(db, edition);
match body {
Ok(it) => Some(it),
Err(_) => {
@@ -510,7 +527,7 @@ pub(super) fn definition(
};
let mut desc = String::new();
- if let Some(notable_traits) = render_notable_trait_comment(db, notable_traits) {
+ if let Some(notable_traits) = render_notable_trait_comment(db, notable_traits, edition) {
desc.push_str(&notable_traits);
desc.push('\n');
}
@@ -527,7 +544,11 @@ pub(super) fn definition(
markup(docs.map(Into::into), desc, mod_path)
}
-pub(super) fn literal(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> Option<Markup> {
+pub(super) fn literal(
+ sema: &Semantics<'_, RootDatabase>,
+ token: SyntaxToken,
+ edition: Edition,
+) -> Option<Markup> {
let lit = token.parent().and_then(ast::Literal::cast)?;
let ty = if let Some(p) = lit.syntax().parent().and_then(ast::Pat::cast) {
sema.type_of_pat(&p)?
@@ -574,7 +595,7 @@ pub(super) fn literal(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) ->
_ => return None
}
};
- let ty = ty.display(sema.db);
+ let ty = ty.display(sema.db, edition);
let mut s = format!("```rust\n{ty}\n```\n___\n\n");
match value {
@@ -593,6 +614,7 @@ pub(super) fn literal(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) ->
fn render_notable_trait_comment(
db: &RootDatabase,
notable_traits: &[(Trait, Vec<(Option<Type>, Name)>)],
+ edition: Edition,
) -> Option<String> {
let mut desc = String::new();
let mut needs_impl_header = true;
@@ -602,17 +624,17 @@ fn render_notable_trait_comment(
} else {
", "
});
- format_to!(desc, "{}", trait_.name(db).display(db),);
+ format_to!(desc, "{}", trait_.name(db).display(db, edition));
if !assoc_types.is_empty() {
desc.push('<');
format_to!(
desc,
"{}",
assoc_types.iter().format_with(", ", |(ty, name), f| {
- f(&name.display(db))?;
+ f(&name.display(db, edition))?;
f(&" = ")?;
match ty {
- Some(ty) => f(&ty.display(db)),
+ Some(ty) => f(&ty.display(db, edition)),
None => f(&"?"),
}
})
@@ -627,8 +649,9 @@ fn type_info(
sema: &Semantics<'_, RootDatabase>,
config: &HoverConfig,
ty: TypeInfo,
+ edition: Edition,
) -> Option<HoverResult> {
- if let Some(res) = closure_ty(sema, config, &ty) {
+ if let Some(res) = closure_ty(sema, config, &ty, edition) {
return Some(res);
};
let db = sema.db;
@@ -654,17 +677,17 @@ fn type_info(
} else {
", "
});
- format_to!(desc, "{}", trait_.name(db).display(db),);
+ format_to!(desc, "{}", trait_.name(db).display(db, edition));
if !assoc_types.is_empty() {
desc.push('<');
format_to!(
desc,
"{}",
assoc_types.into_iter().format_with(", ", |(ty, name), f| {
- f(&name.display(db))?;
+ f(&name.display(db, edition))?;
f(&" = ")?;
match ty {
- Some(ty) => f(&ty.display(db)),
+ Some(ty) => f(&ty.display(db, edition)),
None => f(&"?"),
}
})
@@ -678,8 +701,8 @@ fn type_info(
desc
};
- let original = original.display(db).to_string();
- let adjusted = adjusted_ty.display(db).to_string();
+ let original = original.display(db, edition).to_string();
+ let adjusted = adjusted_ty.display(db, edition).to_string();
let static_text_diff_len = "Coerced to: ".len() - "Type: ".len();
format!(
"```text\nType: {:>apad$}\nCoerced to: {:>opad$}\n{notable}```\n",
@@ -690,14 +713,15 @@ fn type_info(
)
.into()
} else {
- let mut desc = match render_notable_trait_comment(db, &notable_traits(db, &original)) {
- Some(desc) => desc + "\n",
- None => String::new(),
- };
- format_to!(desc, "{}", original.display(db));
+ let mut desc =
+ match render_notable_trait_comment(db, &notable_traits(db, &original), edition) {
+ Some(desc) => desc + "\n",
+ None => String::new(),
+ };
+ format_to!(desc, "{}", original.display(db, edition));
Markup::fenced_block(&desc)
};
- if let Some(actions) = HoverAction::goto_type_from_targets(db, targets) {
+ if let Some(actions) = HoverAction::goto_type_from_targets(db, targets, edition) {
res.actions.push(actions);
}
Some(res)
@@ -707,6 +731,7 @@ fn closure_ty(
sema: &Semantics<'_, RootDatabase>,
config: &HoverConfig,
TypeInfo { original, adjusted }: &TypeInfo,
+ edition: Edition,
) -> Option<HoverResult> {
let c = original.as_closure()?;
let mut captures_rendered = c.captured_items(sema.db)
@@ -739,12 +764,12 @@ fn closure_ty(
walk_and_push_ty(sema.db, adjusted_ty, &mut push_new_def);
format!(
"\nCoerced to: {}",
- adjusted_ty.display(sema.db).with_closure_style(hir::ClosureStyle::ImplFn)
+ adjusted_ty.display(sema.db, edition).with_closure_style(hir::ClosureStyle::ImplFn)
)
} else {
String::new()
};
- let mut markup = format!("```rust\n{}", c.display_with_id(sema.db),);
+ let mut markup = format!("```rust\n{}", c.display_with_id(sema.db, edition));
if let Some(layout) =
render_memory_layout(config.memory_layout, || original.layout(sema.db), |_| None, |_| None)
@@ -757,23 +782,23 @@ fn closure_ty(
format_to!(
markup,
"\n{}\n```{adjusted}\n\n## Captures\n{}",
- c.display_with_impl(sema.db),
+ c.display_with_impl(sema.db, edition),
captures_rendered,
);
let mut res = HoverResult::default();
- if let Some(actions) = HoverAction::goto_type_from_targets(sema.db, targets) {
+ if let Some(actions) = HoverAction::goto_type_from_targets(sema.db, targets, edition) {
res.actions.push(actions);
}
res.markup = markup.into();
Some(res)
}
-fn definition_mod_path(db: &RootDatabase, def: &Definition) -> Option<String> {
+fn definition_mod_path(db: &RootDatabase, def: &Definition, edition: Edition) -> Option<String> {
if matches!(def, Definition::GenericParam(_) | Definition::Local(_) | Definition::Label(_)) {
return None;
}
- def.module(db).map(|module| path(db, module, definition_owner_name(db, def)))
+ def.module(db).map(|module| path(db, module, definition_owner_name(db, def, edition), edition))
}
fn markup(docs: Option<String>, desc: String, mod_path: Option<String>) -> Markup {
@@ -792,12 +817,16 @@ fn markup(docs: Option<String>, desc: String, mod_path: Option<String>) -> Marku
buf.into()
}
-fn find_std_module(famous_defs: &FamousDefs<'_, '_>, name: &str) -> Option<hir::Module> {
+fn find_std_module(
+ famous_defs: &FamousDefs<'_, '_>,
+ name: &str,
+ edition: Edition,
+) -> Option<hir::Module> {
let db = famous_defs.0.db;
let std_crate = famous_defs.std()?;
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)
+ module.name(db).map_or(false, |module| module.display(db, edition).to_string() == name)
})
}
@@ -888,6 +917,7 @@ fn keyword_hints(
sema: &Semantics<'_, RootDatabase>,
token: &SyntaxToken,
parent: syntax::SyntaxNode,
+ edition: Edition,
) -> KeywordHint {
match token.kind() {
T![await] | T![loop] | T![match] | T![unsafe] | T![as] | T![try] | T![if] | T![else] => {
@@ -905,12 +935,12 @@ fn keyword_hints(
walk_and_push_ty(sema.db, &ty.original, &mut push_new_def);
let ty = ty.adjusted();
- let description = format!("{}: {}", token.text(), ty.display(sema.db));
+ let description = format!("{}: {}", token.text(), ty.display(sema.db, edition));
KeywordHint {
description,
keyword_mod,
- actions: HoverAction::goto_type_from_targets(sema.db, targets)
+ actions: HoverAction::goto_type_from_targets(sema.db, targets, edition)
.into_iter()
.collect(),
}
diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs
index 516e32ef91..9585bdbe4c 100644
--- a/crates/ide/src/hover/tests.rs
+++ b/crates/ide/src/hover/tests.rs
@@ -8465,7 +8465,7 @@ impl Iterator for S {
file_id: FileId(
1,
),
- full_range: 7800..8008,
+ full_range: 7800..8042,
focus_range: 7865..7871,
name: "Future",
kind: Trait,
@@ -8479,8 +8479,8 @@ impl Iterator for S {
file_id: FileId(
1,
),
- full_range: 8638..9104,
- focus_range: 8682..8690,
+ full_range: 8672..9171,
+ focus_range: 8749..8757,
name: "Iterator",
kind: Trait,
container_name: "iterator",
@@ -8602,3 +8602,103 @@ fn test() {
"#]],
);
}
+
+#[test]
+fn issue_17871() {
+ check(
+ r#"
+trait T {
+ fn f<A>();
+}
+
+struct S {}
+impl T for S {
+ fn f<A>() {}
+}
+
+fn main() {
+ let x$0 = S::f::<i32>;
+}
+"#,
+ expect![[r#"
+ *x*
+
+ ```rust
+ // size = 0, align = 1
+ let x: fn f<S, i32>()
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn raw_keyword_different_editions() {
+ check(
+ r#"
+//- /lib1.rs crate:with_edition_2015 edition:2015
+pub fn dyn() {}
+
+//- /lib2.rs crate:with_edition_2018 edition:2018 deps:with_edition_2015 new_source_root:local
+fn foo() {
+ with_edition_2015::r#dyn$0();
+}
+ "#,
+ expect![[r#"
+ *r#dyn*
+
+ ```rust
+ with_edition_2015
+ ```
+
+ ```rust
+ pub fn r#dyn()
+ ```
+ "#]],
+ );
+
+ check(
+ r#"
+//- /lib1.rs crate:with_edition_2018 edition:2018
+pub fn r#dyn() {}
+
+//- /lib2.rs crate:with_edition_2015 edition:2015 deps:with_edition_2018 new_source_root:local
+fn foo() {
+ with_edition_2018::dyn$0();
+}
+ "#,
+ expect![[r#"
+ *dyn*
+
+ ```rust
+ with_edition_2018
+ ```
+
+ ```rust
+ pub fn dyn()
+ ```
+ "#]],
+ );
+
+ check(
+ r#"
+//- /lib1.rs crate:escaping_needlessly edition:2015
+pub fn r#dyn() {}
+
+//- /lib2.rs crate:dependent edition:2015 deps:escaping_needlessly new_source_root:local
+fn foo() {
+ escaping_needlessly::dyn$0();
+}
+ "#,
+ expect![[r#"
+ *dyn*
+
+ ```rust
+ escaping_needlessly
+ ```
+
+ ```rust
+ pub fn dyn()
+ ```
+ "#]],
+ );
+}
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index 0a8d272757..6a5d5e26a4 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -11,7 +11,7 @@ use hir::{
use ide_db::{famous_defs::FamousDefs, FileRange, RootDatabase};
use itertools::Itertools;
use smallvec::{smallvec, SmallVec};
-use span::EditionedFileId;
+use span::{Edition, EditionedFileId};
use stdx::never;
use syntax::{
ast::{self, AstNode},
@@ -372,6 +372,7 @@ fn label_of_ty(
famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
ty: &hir::Type,
+ edition: Edition,
) -> Option<InlayHintLabel> {
fn rec(
sema: &Semantics<'_, RootDatabase>,
@@ -380,6 +381,7 @@ fn label_of_ty(
ty: &hir::Type,
label_builder: &mut InlayHintLabelBuilder<'_>,
config: &InlayHintsConfig,
+ edition: Edition,
) -> Result<(), HirDisplayError> {
let iter_item_type = hint_iterator(sema, famous_defs, ty);
match iter_item_type {
@@ -410,12 +412,12 @@ fn label_of_ty(
label_builder.write_str(LABEL_ITEM)?;
label_builder.end_location_link();
label_builder.write_str(LABEL_MIDDLE2)?;
- rec(sema, famous_defs, max_length, &ty, label_builder, config)?;
+ rec(sema, famous_defs, max_length, &ty, label_builder, config, edition)?;
label_builder.write_str(LABEL_END)?;
Ok(())
}
None => ty
- .display_truncated(sema.db, max_length)
+ .display_truncated(sema.db, max_length, edition)
.with_closure_style(config.closure_style)
.write_to(label_builder),
}
@@ -427,7 +429,7 @@ fn label_of_ty(
location: None,
result: InlayHintLabel::default(),
};
- let _ = rec(sema, famous_defs, config.max_length, ty, &mut label_builder, config);
+ let _ = rec(sema, famous_defs, config.max_length, ty, &mut label_builder, config, edition);
let r = label_builder.finish();
Some(r)
}
@@ -569,7 +571,7 @@ fn hints(
match node {
ast::Expr(expr) => {
chaining::hints(hints, famous_defs, config, file_id, &expr);
- adjustment::hints(hints, sema, config, &expr);
+ adjustment::hints(hints, sema, config, file_id, &expr);
match expr {
ast::Expr::CallExpr(it) => param_name::hints(hints, sema, config, ast::Expr::from(it)),
ast::Expr::MethodCallExpr(it) => {
@@ -600,7 +602,7 @@ fn hints(
// FIXME: record impl lifetimes so they aren't being reused in assoc item lifetime inlay hints
ast::Item::Impl(_) => None,
ast::Item::Fn(it) => {
- implicit_drop::hints(hints, sema, config, &it);
+ implicit_drop::hints(hints, sema, config, file_id, &it);
fn_lifetime_fn::hints(hints, config, it)
},
// static type elisions
diff --git a/crates/ide/src/inlay_hints/adjustment.rs b/crates/ide/src/inlay_hints/adjustment.rs
index 7932d8efbc..756198d0c0 100644
--- a/crates/ide/src/inlay_hints/adjustment.rs
+++ b/crates/ide/src/inlay_hints/adjustment.rs
@@ -10,6 +10,7 @@ use hir::{
};
use ide_db::RootDatabase;
+use span::EditionedFileId;
use stdx::never;
use syntax::{
ast::{self, make, AstNode},
@@ -25,6 +26,7 @@ pub(super) fn hints(
acc: &mut Vec<InlayHint>,
sema: &Semantics<'_, RootDatabase>,
config: &InlayHintsConfig,
+ file_id: EditionedFileId,
expr: &ast::Expr,
) -> Option<()> {
if config.adjustment_hints_hide_outside_unsafe && !sema.is_inside_unsafe(expr) {
@@ -141,8 +143,8 @@ pub(super) fn hints(
if postfix { format!(".{}", text.trim_end()) } else { text.to_owned() },
Some(InlayTooltip::Markdown(format!(
"`{}` → `{}` ({coercion} coercion)",
- source.display(sema.db),
- target.display(sema.db),
+ source.display(sema.db, file_id.edition()),
+ target.display(sema.db, file_id.edition()),
))),
None,
);
diff --git a/crates/ide/src/inlay_hints/bind_pat.rs b/crates/ide/src/inlay_hints/bind_pat.rs
index 7310852b8e..82b0a6ffcf 100644
--- a/crates/ide/src/inlay_hints/bind_pat.rs
+++ b/crates/ide/src/inlay_hints/bind_pat.rs
@@ -22,7 +22,7 @@ pub(super) fn hints(
acc: &mut Vec<InlayHint>,
famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
- _file_id: EditionedFileId,
+ file_id: EditionedFileId,
pat: &ast::IdentPat,
) -> Option<()> {
if !config.type_hints {
@@ -67,7 +67,7 @@ pub(super) fn hints(
return None;
}
- let mut label = label_of_ty(famous_defs, config, &ty)?;
+ let mut label = label_of_ty(famous_defs, config, &ty, file_id.edition())?;
if config.hide_named_constructor_hints
&& is_named_constructor(sema, pat, &label.to_string()).is_some()
@@ -342,7 +342,7 @@ fn f<'a>() {
let x = S::<'static>;
//^ S<'static>
let y = S::<'_>;
- //^ S
+ //^ S<'_>
let z = S::<'a>;
//^ S<'a>
diff --git a/crates/ide/src/inlay_hints/chaining.rs b/crates/ide/src/inlay_hints/chaining.rs
index 4e15213b8b..35f4d46e18 100644
--- a/crates/ide/src/inlay_hints/chaining.rs
+++ b/crates/ide/src/inlay_hints/chaining.rs
@@ -14,7 +14,7 @@ pub(super) fn hints(
acc: &mut Vec<InlayHint>,
famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
- _file_id: EditionedFileId,
+ file_id: EditionedFileId,
expr: &ast::Expr,
) -> Option<()> {
if !config.chaining_hints {
@@ -58,7 +58,7 @@ pub(super) fn hints(
}
}
}
- let label = label_of_ty(famous_defs, config, &ty)?;
+ let label = label_of_ty(famous_defs, config, &ty, file_id.edition())?;
acc.push(InlayHint {
range: expr.syntax().text_range(),
kind: InlayKind::Chaining,
diff --git a/crates/ide/src/inlay_hints/closing_brace.rs b/crates/ide/src/inlay_hints/closing_brace.rs
index 8f2777f392..d78fd64bdf 100644
--- a/crates/ide/src/inlay_hints/closing_brace.rs
+++ b/crates/ide/src/inlay_hints/closing_brace.rs
@@ -36,8 +36,12 @@ pub(super) fn hints(
let ty = imp.self_ty(sema.db);
let trait_ = imp.trait_(sema.db);
let hint_text = match trait_ {
- Some(tr) => format!("impl {} for {}", tr.name(sema.db).display(sema.db), ty.display_truncated(sema.db, config.max_length)),
- None => format!("impl {}", ty.display_truncated(sema.db, config.max_length)),
+ Some(tr) => format!(
+ "impl {} for {}",
+ tr.name(sema.db).display(sema.db, file_id.edition()),
+ ty.display_truncated(sema.db, config.max_length, file_id.edition(),
+ )),
+ None => format!("impl {}", ty.display_truncated(sema.db, config.max_length, file_id.edition())),
};
(hint_text, None)
},
diff --git a/crates/ide/src/inlay_hints/closure_ret.rs b/crates/ide/src/inlay_hints/closure_ret.rs
index f6bd7ca064..325c204069 100644
--- a/crates/ide/src/inlay_hints/closure_ret.rs
+++ b/crates/ide/src/inlay_hints/closure_ret.rs
@@ -14,7 +14,7 @@ pub(super) fn hints(
acc: &mut Vec<InlayHint>,
famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
- _file_id: EditionedFileId,
+ file_id: EditionedFileId,
closure: ast::ClosureExpr,
) -> Option<()> {
if config.closure_return_type_hints == ClosureReturnTypeHints::Never {
@@ -43,7 +43,7 @@ pub(super) fn hints(
return None;
}
- let mut label = label_of_ty(famous_defs, config, &ty)?;
+ let mut label = label_of_ty(famous_defs, config, &ty, file_id.edition())?;
if arrow.is_none() {
label.prepend_str(" -> ");
diff --git a/crates/ide/src/inlay_hints/implicit_drop.rs b/crates/ide/src/inlay_hints/implicit_drop.rs
index 7f901db28d..b4695a2b35 100644
--- a/crates/ide/src/inlay_hints/implicit_drop.rs
+++ b/crates/ide/src/inlay_hints/implicit_drop.rs
@@ -12,6 +12,7 @@ use hir::{
};
use ide_db::{FileRange, RootDatabase};
+use span::EditionedFileId;
use syntax::{
ast::{self, AstNode},
match_ast, ToSmolStr,
@@ -23,6 +24,7 @@ pub(super) fn hints(
acc: &mut Vec<InlayHint>,
sema: &Semantics<'_, RootDatabase>,
config: &InlayHintsConfig,
+ file_id: EditionedFileId,
def: &ast::Fn,
) -> Option<()> {
if !config.implicit_drop_hints {
@@ -100,7 +102,7 @@ pub(super) fn hints(
})
});
let binding = &hir.bindings[*binding];
- let name = binding.name.display_no_db().to_smolstr();
+ let name = binding.name.display_no_db(file_id.edition()).to_smolstr();
if name.starts_with("<ra@") {
continue; // Ignore desugared variables
}
diff --git a/crates/ide/src/inlay_hints/param_name.rs b/crates/ide/src/inlay_hints/param_name.rs
index 70d790efad..0f3142ef3f 100644
--- a/crates/ide/src/inlay_hints/param_name.rs
+++ b/crates/ide/src/inlay_hints/param_name.rs
@@ -121,7 +121,9 @@ fn should_hide_param_name_hint(
}
let fn_name = match callable.kind() {
- hir::CallableKind::Function(it) => Some(it.name(sema.db).display_no_db().to_smolstr()),
+ hir::CallableKind::Function(it) => {
+ Some(it.name(sema.db).unescaped().display_no_db().to_smolstr())
+ }
_ => None,
};
let fn_name = fn_name.as_deref();
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index eff4bc3d37..ba0aaae19c 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -104,7 +104,9 @@ pub use crate::{
rename::RenameError,
runnables::{Runnable, RunnableKind, TestId},
signature_help::SignatureHelp,
- static_index::{StaticIndex, StaticIndexedFile, TokenId, TokenStaticData},
+ static_index::{
+ StaticIndex, StaticIndexedFile, TokenId, TokenStaticData, VendoredLibrariesConfig,
+ },
syntax_highlighting::{
tags::{Highlight, HlMod, HlMods, HlOperator, HlPunct, HlTag},
HighlightConfig, HlRange,
diff --git a/crates/ide/src/markup.rs b/crates/ide/src/markup.rs
index 4a4e29fa33..750d125426 100644
--- a/crates/ide/src/markup.rs
+++ b/crates/ide/src/markup.rs
@@ -5,7 +5,7 @@
//! what is used by LSP, so let's keep it simple.
use std::fmt;
-#[derive(Default, Debug)]
+#[derive(Clone, Default, Debug, Hash, PartialEq, Eq)]
pub struct Markup {
text: String,
}
diff --git a/crates/ide/src/moniker.rs b/crates/ide/src/moniker.rs
index 1b64bc9260..4be1b57098 100644
--- a/crates/ide/src/moniker.rs
+++ b/crates/ide/src/moniker.rs
@@ -3,7 +3,7 @@
use core::fmt;
-use hir::{Adt, AsAssocItem, AssocItemContainer, Crate, DescendPreference, MacroKind, Semantics};
+use hir::{Adt, AsAssocItem, AssocItemContainer, Crate, MacroKind, Semantics};
use ide_db::{
base_db::{CrateOrigin, LangCrateOrigin},
defs::{Definition, IdentClass},
@@ -154,7 +154,7 @@ pub(crate) fn moniker(
});
}
let navs = sema
- .descend_into_macros(DescendPreference::None, original_token.clone())
+ .descend_into_macros_exact(original_token.clone())
.into_iter()
.filter_map(|token| {
IdentClass::classify_token(sema, &token).map(IdentClass::definitions_no_ops).map(|it| {
@@ -249,10 +249,11 @@ pub(crate) fn def_to_moniker(
let module = def.module(db)?;
let krate = module.krate();
+ let edition = krate.edition(db);
let mut description = vec![];
description.extend(module.path_to_root(db).into_iter().filter_map(|x| {
Some(MonikerDescriptor {
- name: x.name(db)?.display(db).to_string(),
+ name: x.name(db)?.display(db, edition).to_string(),
desc: def_to_kind(db, x.into()).into(),
})
}));
@@ -265,7 +266,7 @@ pub(crate) fn def_to_moniker(
// Because different traits can have functions with the same name,
// we have to include the trait name as part of the moniker for uniqueness.
description.push(MonikerDescriptor {
- name: trait_.name(db).display(db).to_string(),
+ name: trait_.name(db).display(db, edition).to_string(),
desc: def_to_kind(db, trait_.into()).into(),
});
}
@@ -274,14 +275,14 @@ pub(crate) fn def_to_moniker(
// we add both the struct name and the trait name to the path
if let Some(adt) = impl_.self_ty(db).as_adt() {
description.push(MonikerDescriptor {
- name: adt.name(db).display(db).to_string(),
+ name: adt.name(db).display(db, edition).to_string(),
desc: def_to_kind(db, adt.into()).into(),
});
}
if let Some(trait_) = impl_.trait_(db) {
description.push(MonikerDescriptor {
- name: trait_.name(db).display(db).to_string(),
+ name: trait_.name(db).display(db, edition).to_string(),
desc: def_to_kind(db, trait_.into()).into(),
});
}
@@ -291,7 +292,7 @@ pub(crate) fn def_to_moniker(
if let Definition::Field(it) = def {
description.push(MonikerDescriptor {
- name: it.parent_def(db).name(db).display(db).to_string(),
+ name: it.parent_def(db).name(db).display(db, edition).to_string(),
desc: def_to_kind(db, it.parent_def(db).into()).into(),
});
}
@@ -303,7 +304,7 @@ pub(crate) fn def_to_moniker(
let parent_name = parent.name(db);
if let Some(name) = parent_name {
description.push(MonikerDescriptor {
- name: name.display(db).to_string(),
+ name: name.display(db, edition).to_string(),
desc: def_to_kind(db, parent).into(),
});
}
@@ -326,53 +327,53 @@ pub(crate) fn def_to_moniker(
return None;
}
- MonikerDescriptor { name: local.name(db).display(db).to_string(), desc }
+ MonikerDescriptor { name: local.name(db).display(db, edition).to_string(), desc }
}
Definition::Macro(m) => {
- MonikerDescriptor { name: m.name(db).display(db).to_string(), desc }
+ MonikerDescriptor { name: m.name(db).display(db, edition).to_string(), desc }
}
Definition::Function(f) => {
- MonikerDescriptor { name: f.name(db).display(db).to_string(), desc }
+ MonikerDescriptor { name: f.name(db).display(db, edition).to_string(), desc }
}
Definition::Variant(v) => {
- MonikerDescriptor { name: v.name(db).display(db).to_string(), desc }
+ MonikerDescriptor { name: v.name(db).display(db, edition).to_string(), desc }
}
Definition::Const(c) => {
- MonikerDescriptor { name: c.name(db)?.display(db).to_string(), desc }
+ MonikerDescriptor { name: c.name(db)?.display(db, edition).to_string(), desc }
}
Definition::Trait(trait_) => {
- MonikerDescriptor { name: trait_.name(db).display(db).to_string(), desc }
+ MonikerDescriptor { name: trait_.name(db).display(db, edition).to_string(), desc }
}
Definition::TraitAlias(ta) => {
- MonikerDescriptor { name: ta.name(db).display(db).to_string(), desc }
+ MonikerDescriptor { name: ta.name(db).display(db, edition).to_string(), desc }
}
Definition::TypeAlias(ta) => {
- MonikerDescriptor { name: ta.name(db).display(db).to_string(), desc }
+ MonikerDescriptor { name: ta.name(db).display(db, edition).to_string(), desc }
}
Definition::Module(m) => {
- MonikerDescriptor { name: m.name(db)?.display(db).to_string(), desc }
+ MonikerDescriptor { name: m.name(db)?.display(db, edition).to_string(), desc }
}
Definition::BuiltinType(b) => {
- MonikerDescriptor { name: b.name().display(db).to_string(), desc }
+ MonikerDescriptor { name: b.name().display(db, edition).to_string(), desc }
}
Definition::SelfType(imp) => MonikerDescriptor {
- name: imp.self_ty(db).as_adt()?.name(db).display(db).to_string(),
+ name: imp.self_ty(db).as_adt()?.name(db).display(db, edition).to_string(),
desc,
},
Definition::Field(it) => {
- MonikerDescriptor { name: it.name(db).display(db).to_string(), desc }
+ MonikerDescriptor { name: it.name(db).display(db, edition).to_string(), desc }
}
Definition::TupleField(it) => {
- MonikerDescriptor { name: it.name().display(db).to_string(), desc }
+ MonikerDescriptor { name: it.name().display(db, edition).to_string(), desc }
}
Definition::Adt(adt) => {
- MonikerDescriptor { name: adt.name(db).display(db).to_string(), desc }
+ MonikerDescriptor { name: adt.name(db).display(db, edition).to_string(), desc }
}
Definition::Static(s) => {
- MonikerDescriptor { name: s.name(db).display(db).to_string(), desc }
+ MonikerDescriptor { name: s.name(db).display(db, edition).to_string(), desc }
}
Definition::ExternCrateDecl(m) => {
- MonikerDescriptor { name: m.name(db).display(db).to_string(), desc }
+ MonikerDescriptor { name: m.name(db).display(db, edition).to_string(), desc }
}
};
diff --git a/crates/ide/src/navigation_target.rs b/crates/ide/src/navigation_target.rs
index 066141d36f..9ace9fda62 100644
--- a/crates/ide/src/navigation_target.rs
+++ b/crates/ide/src/navigation_target.rs
@@ -5,14 +5,15 @@ use std::fmt;
use arrayvec::ArrayVec;
use either::Either;
use hir::{
- db::ExpandDatabase, symbols::FileSymbol, AssocItem, FieldSource, HasContainer, HasSource,
- HirDisplay, HirFileId, InFile, LocalSource, ModuleSource,
+ db::ExpandDatabase, symbols::FileSymbol, AssocItem, FieldSource, HasContainer, HasCrate,
+ HasSource, HirDisplay, HirFileId, HirFileIdExt, InFile, LocalSource, ModuleSource,
};
use ide_db::{
defs::Definition,
documentation::{Documentation, HasDocs},
FileId, FileRange, RootDatabase, SymbolKind,
};
+use span::Edition;
use stdx::never;
use syntax::{
ast::{self, HasName},
@@ -97,7 +98,9 @@ impl NavigationTarget {
db: &RootDatabase,
module: hir::Module,
) -> UpmappingResult<NavigationTarget> {
- let name = module.name(db).map(|it| it.display_no_db().to_smolstr()).unwrap_or_default();
+ let edition = module.krate().edition(db);
+ let name =
+ module.name(db).map(|it| it.display_no_db(edition).to_smolstr()).unwrap_or_default();
match module.declaration_source(db) {
Some(InFile { value, file_id }) => {
orig_range_with_focus(db, file_id, value.syntax(), value.name()).map(
@@ -110,7 +113,7 @@ impl NavigationTarget {
SymbolKind::Module,
);
res.docs = module.docs(db);
- res.description = Some(module.display(db).to_string());
+ res.description = Some(module.display(db, edition).to_string());
res
},
)
@@ -175,6 +178,8 @@ impl NavigationTarget {
impl TryToNav for FileSymbol {
fn try_to_nav(&self, db: &RootDatabase) -> Option<UpmappingResult<NavigationTarget>> {
+ let edition =
+ self.def.module(db).map(|it| it.krate().edition(db)).unwrap_or(Edition::CURRENT);
Some(
orig_range_with_focus_r(
db,
@@ -185,27 +190,26 @@ impl TryToNav for FileSymbol {
.map(|(FileRange { file_id, range: full_range }, focus_range)| {
NavigationTarget {
file_id,
- name: self
- .is_alias
- .then(|| self.def.name(db))
- .flatten()
- .map_or_else(|| self.name.clone(), |it| it.display_no_db().to_smolstr()),
+ name: self.is_alias.then(|| self.def.name(db)).flatten().map_or_else(
+ || self.name.clone(),
+ |it| it.display_no_db(edition).to_smolstr(),
+ ),
alias: self.is_alias.then(|| self.name.clone()),
kind: Some(hir::ModuleDefId::from(self.def).into()),
full_range,
focus_range,
container_name: self.container_name.clone(),
description: match self.def {
- hir::ModuleDef::Module(it) => Some(it.display(db).to_string()),
- hir::ModuleDef::Function(it) => Some(it.display(db).to_string()),
- hir::ModuleDef::Adt(it) => Some(it.display(db).to_string()),
- hir::ModuleDef::Variant(it) => Some(it.display(db).to_string()),
- hir::ModuleDef::Const(it) => Some(it.display(db).to_string()),
- hir::ModuleDef::Static(it) => Some(it.display(db).to_string()),
- hir::ModuleDef::Trait(it) => Some(it.display(db).to_string()),
- hir::ModuleDef::TraitAlias(it) => Some(it.display(db).to_string()),
- hir::ModuleDef::TypeAlias(it) => Some(it.display(db).to_string()),
- hir::ModuleDef::Macro(it) => Some(it.display(db).to_string()),
+ hir::ModuleDef::Module(it) => Some(it.display(db, edition).to_string()),
+ hir::ModuleDef::Function(it) => Some(it.display(db, edition).to_string()),
+ hir::ModuleDef::Adt(it) => Some(it.display(db, edition).to_string()),
+ hir::ModuleDef::Variant(it) => Some(it.display(db, edition).to_string()),
+ hir::ModuleDef::Const(it) => Some(it.display(db, edition).to_string()),
+ hir::ModuleDef::Static(it) => Some(it.display(db, edition).to_string()),
+ hir::ModuleDef::Trait(it) => Some(it.display(db, edition).to_string()),
+ hir::ModuleDef::TraitAlias(it) => Some(it.display(db, edition).to_string()),
+ hir::ModuleDef::TypeAlias(it) => Some(it.display(db, edition).to_string()),
+ hir::ModuleDef::Macro(it) => Some(it.display(db, edition).to_string()),
hir::ModuleDef::BuiltinType(_) => None,
},
docs: None,
@@ -271,11 +275,13 @@ pub(crate) trait ToNavFromAst: Sized {
}
}
-fn container_name(db: &RootDatabase, t: impl HasContainer) -> Option<SmolStr> {
+fn container_name(db: &RootDatabase, t: impl HasContainer, edition: Edition) -> Option<SmolStr> {
match t.container(db) {
- hir::ItemContainer::Trait(it) => Some(it.name(db).display_no_db().to_smolstr()),
+ hir::ItemContainer::Trait(it) => Some(it.name(db).display_no_db(edition).to_smolstr()),
// FIXME: Handle owners of blocks correctly here
- hir::ItemContainer::Module(it) => it.name(db).map(|name| name.display_no_db().to_smolstr()),
+ hir::ItemContainer::Module(it) => {
+ it.name(db).map(|name| name.display_no_db(edition).to_smolstr())
+ }
_ => None,
}
}
@@ -283,32 +289,32 @@ fn container_name(db: &RootDatabase, t: impl HasContainer) -> Option<SmolStr> {
impl ToNavFromAst for hir::Function {
const KIND: SymbolKind = SymbolKind::Function;
fn container_name(self, db: &RootDatabase) -> Option<SmolStr> {
- container_name(db, self)
+ container_name(db, self, self.krate(db).edition(db))
}
}
impl ToNavFromAst for hir::Const {
const KIND: SymbolKind = SymbolKind::Const;
fn container_name(self, db: &RootDatabase) -> Option<SmolStr> {
- container_name(db, self)
+ container_name(db, self, self.krate(db).edition(db))
}
}
impl ToNavFromAst for hir::Static {
const KIND: SymbolKind = SymbolKind::Static;
fn container_name(self, db: &RootDatabase) -> Option<SmolStr> {
- container_name(db, self)
+ container_name(db, self, self.krate(db).edition(db))
}
}
impl ToNavFromAst for hir::Struct {
const KIND: SymbolKind = SymbolKind::Struct;
fn container_name(self, db: &RootDatabase) -> Option<SmolStr> {
- container_name(db, self)
+ container_name(db, self, self.krate(db).edition(db))
}
}
impl ToNavFromAst for hir::Enum {
const KIND: SymbolKind = SymbolKind::Enum;
fn container_name(self, db: &RootDatabase) -> Option<SmolStr> {
- container_name(db, self)
+ container_name(db, self, self.krate(db).edition(db))
}
}
impl ToNavFromAst for hir::Variant {
@@ -317,25 +323,25 @@ impl ToNavFromAst for hir::Variant {
impl ToNavFromAst for hir::Union {
const KIND: SymbolKind = SymbolKind::Union;
fn container_name(self, db: &RootDatabase) -> Option<SmolStr> {
- container_name(db, self)
+ container_name(db, self, self.krate(db).edition(db))
}
}
impl ToNavFromAst for hir::TypeAlias {
const KIND: SymbolKind = SymbolKind::TypeAlias;
fn container_name(self, db: &RootDatabase) -> Option<SmolStr> {
- container_name(db, self)
+ container_name(db, self, self.krate(db).edition(db))
}
}
impl ToNavFromAst for hir::Trait {
const KIND: SymbolKind = SymbolKind::Trait;
fn container_name(self, db: &RootDatabase) -> Option<SmolStr> {
- container_name(db, self)
+ container_name(db, self, self.krate(db).edition(db))
}
}
impl ToNavFromAst for hir::TraitAlias {
const KIND: SymbolKind = SymbolKind::TraitAlias;
fn container_name(self, db: &RootDatabase) -> Option<SmolStr> {
- container_name(db, self)
+ container_name(db, self, self.krate(db).edition(db))
}
}
@@ -346,6 +352,7 @@ where
{
fn try_to_nav(&self, db: &RootDatabase) -> Option<UpmappingResult<NavigationTarget>> {
let src = self.source(db)?;
+ let edition = src.file_id.original_file(db).edition();
Some(
NavigationTarget::from_named(
db,
@@ -354,7 +361,7 @@ where
)
.map(|mut res| {
res.docs = self.docs(db);
- res.description = Some(self.display(db).to_string());
+ res.description = Some(self.display(db, edition).to_string());
res.container_name = self.container_name(db);
res
}),
@@ -365,8 +372,10 @@ where
impl ToNav for hir::Module {
fn to_nav(&self, db: &RootDatabase) -> UpmappingResult<NavigationTarget> {
let InFile { file_id, value } = self.definition_source(db);
+ let edition = self.krate(db).edition(db);
- let name = self.name(db).map(|it| it.display_no_db().to_smolstr()).unwrap_or_default();
+ let name =
+ self.name(db).map(|it| it.display_no_db(edition).to_smolstr()).unwrap_or_default();
let (syntax, focus) = match &value {
ModuleSource::SourceFile(node) => (node.syntax(), None),
ModuleSource::Module(node) => (node.syntax(), node.name()),
@@ -418,6 +427,7 @@ impl TryToNav for hir::ExternCrateDecl {
let focus = value
.rename()
.map_or_else(|| value.name_ref().map(Either::Left), |it| it.name().map(Either::Right));
+ let edition = self.module(db).krate().edition(db);
Some(orig_range_with_focus(db, file_id, value.syntax(), focus).map(
|(FileRange { file_id, range: full_range }, focus_range)| {
@@ -425,7 +435,7 @@ impl TryToNav for hir::ExternCrateDecl {
file_id,
self.alias_or_name(db)
.unwrap_or_else(|| self.name(db))
- .display_no_db()
+ .display_no_db(edition)
.to_smolstr(),
focus_range,
full_range,
@@ -433,8 +443,8 @@ impl TryToNav for hir::ExternCrateDecl {
);
res.docs = self.docs(db);
- res.description = Some(self.display(db).to_string());
- res.container_name = container_name(db, *self);
+ res.description = Some(self.display(db, edition).to_string());
+ res.container_name = container_name(db, *self, edition);
res
},
))
@@ -444,13 +454,14 @@ impl TryToNav for hir::ExternCrateDecl {
impl TryToNav for hir::Field {
fn try_to_nav(&self, db: &RootDatabase) -> Option<UpmappingResult<NavigationTarget>> {
let src = self.source(db)?;
+ let edition = self.parent_def(db).module(db).krate().edition(db);
let field_source = match &src.value {
FieldSource::Named(it) => {
NavigationTarget::from_named(db, src.with_value(it), SymbolKind::Field).map(
|mut res| {
res.docs = self.docs(db);
- res.description = Some(self.display(db).to_string());
+ res.description = Some(self.display(db, edition).to_string());
res
},
)
@@ -531,10 +542,11 @@ impl ToNav for LocalSource {
Either::Left(bind_pat) => (bind_pat.syntax(), bind_pat.name()),
Either::Right(it) => (it.syntax(), it.name()),
};
+ let edition = self.local.parent(db).module(db).krate().edition(db);
orig_range_with_focus(db, file_id, node, name).map(
|(FileRange { file_id, range: full_range }, focus_range)| {
- let name = local.name(db).display_no_db().to_smolstr();
+ let name = local.name(db).display_no_db(edition).to_smolstr();
let kind = if local.is_self(db) {
SymbolKind::SelfParam
} else if local.is_param(db) {
@@ -567,7 +579,8 @@ impl ToNav for hir::Local {
impl TryToNav for hir::Label {
fn try_to_nav(&self, db: &RootDatabase) -> Option<UpmappingResult<NavigationTarget>> {
let InFile { file_id, value } = self.source(db)?;
- let name = self.name(db).display_no_db().to_smolstr();
+ // Labels can't be keywords, so no escaping needed.
+ let name = self.name(db).display_no_db(Edition::Edition2015).to_smolstr();
Some(orig_range_with_focus(db, file_id, value.syntax(), value.lifetime()).map(
|(FileRange { file_id, range: full_range }, focus_range)| NavigationTarget {
@@ -588,7 +601,8 @@ impl TryToNav for hir::Label {
impl TryToNav for hir::TypeParam {
fn try_to_nav(&self, db: &RootDatabase) -> Option<UpmappingResult<NavigationTarget>> {
let InFile { file_id, value } = self.merge().source(db)?;
- let name = self.name(db).display_no_db().to_smolstr();
+ let edition = self.module(db).krate().edition(db);
+ let name = self.name(db).display_no_db(edition).to_smolstr();
let value = match value {
Either::Left(ast::TypeOrConstParam::Type(x)) => Either::Left(x),
@@ -630,7 +644,8 @@ impl TryToNav for hir::TypeOrConstParam {
impl TryToNav for hir::LifetimeParam {
fn try_to_nav(&self, db: &RootDatabase) -> Option<UpmappingResult<NavigationTarget>> {
let InFile { file_id, value } = self.source(db)?;
- let name = self.name(db).display_no_db().to_smolstr();
+ // Lifetimes cannot be keywords, so not escaping needed.
+ let name = self.name(db).display_no_db(Edition::Edition2015).to_smolstr();
Some(orig_range(db, file_id, value.syntax()).map(
|(FileRange { file_id, range: full_range }, focus_range)| NavigationTarget {
@@ -651,7 +666,8 @@ impl TryToNav for hir::LifetimeParam {
impl TryToNav for hir::ConstParam {
fn try_to_nav(&self, db: &RootDatabase) -> Option<UpmappingResult<NavigationTarget>> {
let InFile { file_id, value } = self.merge().source(db)?;
- let name = self.name(db).display_no_db().to_smolstr();
+ let edition = self.module(db).krate().edition(db);
+ let name = self.name(db).display_no_db(edition).to_smolstr();
let value = match value {
Either::Left(ast::TypeOrConstParam::Const(x)) => x,
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index 64b82b31c7..64d717f88d 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -9,7 +9,7 @@
//! at the index that the match starts at and its tree parent is
//! resolved to the search element definition, we get a reference.
-use hir::{DescendPreference, PathResolution, Semantics};
+use hir::{PathResolution, Semantics};
use ide_db::{
defs::{Definition, NameClass, NameRefClass},
search::{ReferenceCategory, SearchScope, UsageSearchResult},
@@ -17,6 +17,7 @@ use ide_db::{
};
use itertools::Itertools;
use nohash_hasher::IntMap;
+use span::Edition;
use syntax::{
ast::{self, HasName},
match_ast, AstNode,
@@ -148,7 +149,7 @@ pub(crate) fn find_defs<'a>(
}
Some(
- sema.descend_into_macros(DescendPreference::SameText, token)
+ sema.descend_into_macros_exact(token)
.into_iter()
.filter_map(|it| ast::NameLike::cast(it.parent()?))
.filter_map(move |name_like| {
@@ -305,7 +306,9 @@ fn handle_control_flow_keywords(
FilePosition { file_id, offset }: FilePosition,
) -> Option<ReferenceSearchResult> {
let file = sema.parse_guess_edition(file_id);
- let token = file.syntax().token_at_offset(offset).find(|t| t.kind().is_keyword())?;
+ let edition =
+ sema.attach_first_edition(file_id).map(|it| it.edition()).unwrap_or(Edition::CURRENT);
+ let token = file.syntax().token_at_offset(offset).find(|t| t.kind().is_keyword(edition))?;
let references = match token.kind() {
T![fn] | T![return] | T![try] => highlight_related::highlight_exit_points(sema, token),
@@ -2507,4 +2510,244 @@ fn main() {
"#]],
)
}
+
+ // The following are tests for short_associated_function_fast_search() in crates/ide-db/src/search.rs, because find all references
+ // use `FindUsages` and I found it easy to test it here.
+
+ #[test]
+ fn goto_ref_on_short_associated_function() {
+ cov_mark::check!(short_associated_function_fast_search);
+ check(
+ r#"
+struct Foo;
+impl Foo {
+ fn new$0() {}
+}
+
+fn bar() {
+ Foo::new();
+}
+fn baz() {
+ Foo::new;
+}
+ "#,
+ expect![[r#"
+ new Function FileId(0) 27..38 30..33
+
+ FileId(0) 62..65
+ FileId(0) 91..94
+ "#]],
+ );
+ }
+
+ #[test]
+ fn goto_ref_on_short_associated_function_with_aliases() {
+ cov_mark::check!(short_associated_function_fast_search);
+ cov_mark::check!(container_use_rename);
+ cov_mark::check!(container_type_alias);
+ check(
+ r#"
+//- /lib.rs
+mod a;
+mod b;
+
+struct Foo;
+impl Foo {
+ fn new$0() {}
+}
+
+fn bar() {
+ b::c::Baz::new();
+}
+
+//- /a.rs
+use crate::Foo as Bar;
+
+fn baz() { Bar::new(); }
+fn quux() { <super::b::Other as super::b::Trait>::Assoc::new(); }
+
+//- /b.rs
+pub(crate) mod c;
+
+pub(crate) struct Other;
+pub(crate) trait Trait {
+ type Assoc;
+}
+impl Trait for Other {
+ type Assoc = super::Foo;
+}
+
+//- /b/c.rs
+type Itself<T> = T;
+pub(in super::super) type Baz = Itself<crate::Foo>;
+ "#,
+ expect![[r#"
+ new Function FileId(0) 42..53 45..48
+
+ FileId(0) 83..86
+ FileId(1) 40..43
+ FileId(1) 106..109
+ "#]],
+ );
+ }
+
+ #[test]
+ fn goto_ref_on_short_associated_function_self_works() {
+ cov_mark::check!(short_associated_function_fast_search);
+ cov_mark::check!(self_type_alias);
+ check(
+ r#"
+//- /lib.rs
+mod module;
+
+struct Foo;
+impl Foo {
+ fn new$0() {}
+ fn bar() { Self::new(); }
+}
+trait Trait {
+ type Assoc;
+ fn baz();
+}
+impl Trait for Foo {
+ type Assoc = Self;
+ fn baz() { Self::new(); }
+}
+
+//- /module.rs
+impl super::Foo {
+ fn quux() { Self::new(); }
+}
+fn foo() { <super::Foo as super::Trait>::Assoc::new(); }
+ "#,
+ expect![[r#"
+ new Function FileId(0) 40..51 43..46
+
+ FileId(0) 73..76
+ FileId(0) 195..198
+ FileId(1) 40..43
+ FileId(1) 99..102
+ "#]],
+ );
+ }
+
+ #[test]
+ fn goto_ref_on_short_associated_function_overlapping_self_ranges() {
+ check(
+ r#"
+struct Foo;
+impl Foo {
+ fn new$0() {}
+ fn bar() {
+ Self::new();
+ impl Foo {
+ fn baz() { Self::new(); }
+ }
+ }
+}
+ "#,
+ expect![[r#"
+ new Function FileId(0) 27..38 30..33
+
+ FileId(0) 68..71
+ FileId(0) 123..126
+ "#]],
+ );
+ }
+
+ #[test]
+ fn goto_ref_on_short_associated_function_no_direct_self_but_path_contains_self() {
+ cov_mark::check!(short_associated_function_fast_search);
+ check(
+ r#"
+struct Foo;
+impl Foo {
+ fn new$0() {}
+}
+trait Trait {
+ type Assoc;
+}
+impl<A, B> Trait for (A, B) {
+ type Assoc = B;
+}
+impl Foo {
+ fn bar() {
+ <((), Foo) as Trait>::Assoc::new();
+ <((), Self) as Trait>::Assoc::new();
+ }
+}
+ "#,
+ expect![[r#"
+ new Function FileId(0) 27..38 30..33
+
+ FileId(0) 188..191
+ FileId(0) 233..236
+ "#]],
+ );
+ }
+
+ // Checks that we can circumvent our fast path logic using complicated type level functions.
+ // This mainly exists as a documentation. I don't believe it is fixable.
+ // Usages search is not 100% accurate anyway; we miss macros.
+ #[test]
+ fn goto_ref_on_short_associated_function_complicated_type_magic_can_confuse_our_logic() {
+ cov_mark::check!(short_associated_function_fast_search);
+ cov_mark::check!(same_name_different_def_type_alias);
+ check(
+ r#"
+struct Foo;
+impl Foo {
+ fn new$0() {}
+}
+
+struct ChoiceA;
+struct ChoiceB;
+trait Choice {
+ type Choose<A, B>;
+}
+impl Choice for ChoiceA {
+ type Choose<A, B> = A;
+}
+impl Choice for ChoiceB {
+ type Choose<A, B> = B;
+}
+type Choose<A, C> = <C as Choice>::Choose<A, Foo>;
+
+fn bar() {
+ Choose::<(), ChoiceB>::new();
+}
+ "#,
+ expect![[r#"
+ new Function FileId(0) 27..38 30..33
+
+ (no references)
+ "#]],
+ );
+ }
+
+ #[test]
+ fn goto_ref_on_short_associated_function_same_path_mention_alias_and_self() {
+ cov_mark::check!(short_associated_function_fast_search);
+ check(
+ r#"
+struct Foo;
+impl Foo {
+ fn new$0() {}
+}
+
+type IgnoreFirst<A, B> = B;
+
+impl Foo {
+ fn bar() {
+ <IgnoreFirst<Foo, Self>>::new();
+ }
+}
+ "#,
+ expect![[r#"
+ new Function FileId(0) 27..38 30..33
+
+ FileId(0) 131..134
+ "#]],
+ );
+ }
}
diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs
index 9581474ca7..42b7472c64 100644
--- a/crates/ide/src/rename.rs
+++ b/crates/ide/src/rename.rs
@@ -12,12 +12,8 @@ use ide_db::{
FileId, FileRange, RootDatabase,
};
use itertools::Itertools;
-use span::Edition;
use stdx::{always, never};
-use syntax::{
- ast, utils::is_raw_identifier, AstNode, SmolStr, SyntaxKind, SyntaxNode, TextRange, TextSize,
- ToSmolStr,
-};
+use syntax::{ast, AstNode, SyntaxKind, SyntaxNode, TextRange, TextSize};
use text_edit::TextEdit;
@@ -102,7 +98,7 @@ pub(crate) fn rename(
// FIXME: This can use the `ide_db::rename_reference` (or def.rename) method once we can
// properly find "direct" usages/references.
.map(|(.., def)| {
- match IdentifierKind::classify(Edition::CURRENT_FIXME, new_name)? {
+ match IdentifierKind::classify(new_name)? {
IdentifierKind::Ident => (),
IdentifierKind::Lifetime => {
bail!("Cannot alias reference to a lifetime identifier")
@@ -124,7 +120,10 @@ pub(crate) fn rename(
let mut source_change = SourceChange::default();
source_change.extend(usages.references.get_mut(&file_id).iter().map(|refs| {
- (position.file_id, source_edit_from_references(refs, def, new_name))
+ (
+ position.file_id,
+ source_edit_from_references(refs, def, new_name, file_id.edition()),
+ )
}));
Ok(source_change)
@@ -162,11 +161,7 @@ pub(crate) fn will_rename_file(
let sema = Semantics::new(db);
let module = sema.file_to_module_def(file_id)?;
let def = Definition::Module(module);
- let mut change = if is_raw_identifier(new_name_stem) {
- def.rename(&sema, &SmolStr::from_iter(["r#", new_name_stem])).ok()?
- } else {
- def.rename(&sema, new_name_stem).ok()?
- };
+ let mut change = def.rename(&sema, new_name_stem).ok()?;
change.file_system_edits.clear();
Some(change)
}
@@ -270,7 +265,7 @@ fn find_definitions(
// if the name differs from the definitions name it has to be an alias
if def
.name(sema.db)
- .map_or(false, |it| it.display_no_db().to_smolstr() != name_ref.text().as_str())
+ .map_or(false, |it| !it.eq_ident(name_ref.text().as_str()))
{
Err(format_err!("Renaming aliases is currently unsupported"))
} else {
@@ -377,7 +372,7 @@ fn rename_to_self(
let usages = def.usages(sema).all();
let mut source_change = SourceChange::default();
source_change.extend(usages.iter().map(|(file_id, references)| {
- (file_id.into(), source_edit_from_references(references, def, "self"))
+ (file_id.into(), source_edit_from_references(references, def, "self", file_id.edition()))
}));
source_change.insert_source_edit(
file_id.original_file(sema.db),
@@ -398,7 +393,7 @@ fn rename_self_to_param(
return Ok(SourceChange::default());
}
- let identifier_kind = IdentifierKind::classify(Edition::CURRENT_FIXME, new_name)?;
+ let identifier_kind = IdentifierKind::classify(new_name)?;
let InFile { file_id, value: self_param } =
sema.source(self_param).ok_or_else(|| format_err!("cannot find function source"))?;
@@ -413,7 +408,7 @@ fn rename_self_to_param(
let mut source_change = SourceChange::default();
source_change.insert_source_edit(file_id.original_file(sema.db), edit);
source_change.extend(usages.iter().map(|(file_id, references)| {
- (file_id.into(), source_edit_from_references(references, def, new_name))
+ (file_id.into(), source_edit_from_references(references, def, new_name, file_id.edition()))
}));
Ok(source_change)
}
@@ -634,9 +629,9 @@ impl Foo {
#[test]
fn test_rename_to_invalid_identifier3() {
check(
- "let",
+ "super",
r#"fn main() { let i$0 = 1; }"#,
- "error: Invalid name `let`: not an identifier",
+ "error: Invalid name `super`: not an identifier",
);
}
@@ -685,11 +680,7 @@ impl Foo {
#[test]
fn test_rename_mod_invalid_raw_ident() {
- check(
- "r#self",
- r#"mod foo$0 {}"#,
- "error: Invalid name: `self` cannot be a raw identifier",
- );
+ check("r#self", r#"mod foo$0 {}"#, "error: Invalid name `self`: not an identifier");
}
#[test]
@@ -1544,6 +1535,228 @@ pub fn baz() {}
}
#[test]
+ fn test_rename_each_usage_gets_appropriate_rawness() {
+ check_expect(
+ "dyn",
+ r#"
+//- /a.rs crate:a edition:2015
+pub fn foo() {}
+
+//- /b.rs crate:b edition:2018 deps:a new_source_root:local
+fn bar() {
+ a::foo$0();
+}
+ "#,
+ expect![[r#"
+ source_file_edits: [
+ (
+ FileId(
+ 0,
+ ),
+ [
+ Indel {
+ insert: "dyn",
+ delete: 7..10,
+ },
+ ],
+ ),
+ (
+ FileId(
+ 1,
+ ),
+ [
+ Indel {
+ insert: "r#dyn",
+ delete: 18..21,
+ },
+ ],
+ ),
+ ]
+ file_system_edits: []
+ "#]],
+ );
+
+ check_expect(
+ "dyn",
+ r#"
+//- /a.rs crate:a edition:2018
+pub fn foo() {}
+
+//- /b.rs crate:b edition:2015 deps:a new_source_root:local
+fn bar() {
+ a::foo$0();
+}
+ "#,
+ expect![[r#"
+ source_file_edits: [
+ (
+ FileId(
+ 0,
+ ),
+ [
+ Indel {
+ insert: "r#dyn",
+ delete: 7..10,
+ },
+ ],
+ ),
+ (
+ FileId(
+ 1,
+ ),
+ [
+ Indel {
+ insert: "dyn",
+ delete: 18..21,
+ },
+ ],
+ ),
+ ]
+ file_system_edits: []
+ "#]],
+ );
+
+ check_expect(
+ "r#dyn",
+ r#"
+//- /a.rs crate:a edition:2018
+pub fn foo$0() {}
+
+//- /b.rs crate:b edition:2015 deps:a new_source_root:local
+fn bar() {
+ a::foo();
+}
+ "#,
+ expect![[r#"
+ source_file_edits: [
+ (
+ FileId(
+ 0,
+ ),
+ [
+ Indel {
+ insert: "r#dyn",
+ delete: 7..10,
+ },
+ ],
+ ),
+ (
+ FileId(
+ 1,
+ ),
+ [
+ Indel {
+ insert: "dyn",
+ delete: 18..21,
+ },
+ ],
+ ),
+ ]
+ file_system_edits: []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn rename_raw_identifier() {
+ check_expect(
+ "abc",
+ r#"
+//- /a.rs crate:a edition:2015
+pub fn dyn() {}
+
+fn foo() {
+ dyn$0();
+}
+
+//- /b.rs crate:b edition:2018 deps:a new_source_root:local
+fn bar() {
+ a::r#dyn();
+}
+ "#,
+ expect![[r#"
+ source_file_edits: [
+ (
+ FileId(
+ 0,
+ ),
+ [
+ Indel {
+ insert: "abc",
+ delete: 7..10,
+ },
+ Indel {
+ insert: "abc",
+ delete: 32..35,
+ },
+ ],
+ ),
+ (
+ FileId(
+ 1,
+ ),
+ [
+ Indel {
+ insert: "abc",
+ delete: 18..23,
+ },
+ ],
+ ),
+ ]
+ file_system_edits: []
+ "#]],
+ );
+
+ check_expect(
+ "abc",
+ r#"
+//- /a.rs crate:a edition:2018
+pub fn r#dyn() {}
+
+fn foo() {
+ r#dyn$0();
+}
+
+//- /b.rs crate:b edition:2015 deps:a new_source_root:local
+fn bar() {
+ a::dyn();
+}
+ "#,
+ expect![[r#"
+ source_file_edits: [
+ (
+ FileId(
+ 0,
+ ),
+ [
+ Indel {
+ insert: "abc",
+ delete: 7..12,
+ },
+ Indel {
+ insert: "abc",
+ delete: 34..39,
+ },
+ ],
+ ),
+ (
+ FileId(
+ 1,
+ ),
+ [
+ Indel {
+ insert: "abc",
+ delete: 18..21,
+ },
+ ],
+ ),
+ ]
+ file_system_edits: []
+ "#]],
+ );
+ }
+
+ #[test]
fn test_enum_variant_from_module_1() {
cov_mark::check!(rename_non_local);
check(
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs
index 5d4b8b3643..38dc522789 100644
--- a/crates/ide/src/runnables.rs
+++ b/crates/ide/src/runnables.rs
@@ -3,7 +3,8 @@ use std::fmt;
use ast::HasName;
use cfg::{CfgAtom, CfgExpr};
use hir::{
- db::HirDatabase, sym, AsAssocItem, AttrsWithOwner, HasAttrs, HasSource, HirFileIdExt, Semantics,
+ db::HirDatabase, sym, AsAssocItem, AttrsWithOwner, HasAttrs, HasCrate, HasSource, HirFileIdExt,
+ Semantics,
};
use ide_assists::utils::{has_test_related_attribute, test_related_attribute_syn};
use ide_db::{
@@ -14,7 +15,7 @@ use ide_db::{
FilePosition, FxHashMap, FxHashSet, RootDatabase, SymbolKind,
};
use itertools::Itertools;
-use span::TextSize;
+use span::{Edition, TextSize};
use stdx::{always, format_to};
use syntax::{
ast::{self, AstNode},
@@ -321,6 +322,7 @@ pub(crate) fn runnable_fn(
sema: &Semantics<'_, RootDatabase>,
def: hir::Function,
) -> Option<Runnable> {
+ let edition = def.krate(sema.db).edition(sema.db);
let under_cfg_test = has_cfg_test(def.module(sema.db).attrs(sema.db));
let kind = if !under_cfg_test && def.is_main(sema.db) {
RunnableKind::Bin
@@ -328,11 +330,11 @@ pub(crate) fn runnable_fn(
let test_id = || {
let canonical_path = {
let def: hir::ModuleDef = def.into();
- def.canonical_path(sema.db)
+ def.canonical_path(sema.db, edition)
};
canonical_path
.map(TestId::Path)
- .unwrap_or(TestId::Name(def.name(sema.db).display_no_db().to_smolstr()))
+ .unwrap_or(TestId::Name(def.name(sema.db).display_no_db(edition).to_smolstr()))
};
if def.is_test(sema.db) {
@@ -367,8 +369,11 @@ pub(crate) fn runnable_mod(
.path_to_root(sema.db)
.into_iter()
.rev()
- .filter_map(|it| it.name(sema.db))
- .map(|it| it.display(sema.db).to_string())
+ .filter_map(|module| {
+ module.name(sema.db).map(|mod_name| {
+ mod_name.display(sema.db, module.krate().edition(sema.db)).to_string()
+ })
+ })
.join("::");
let attrs = def.attrs(sema.db);
@@ -381,6 +386,7 @@ pub(crate) fn runnable_impl(
sema: &Semantics<'_, RootDatabase>,
def: &hir::Impl,
) -> Option<Runnable> {
+ let edition = def.module(sema.db).krate().edition(sema.db);
let attrs = def.attrs(sema.db);
if !has_runnable_doc_test(&attrs) {
return None;
@@ -389,13 +395,13 @@ pub(crate) fn runnable_impl(
let nav = def.try_to_nav(sema.db)?.call_site();
let ty = def.self_ty(sema.db);
let adt_name = ty.as_adt()?.name(sema.db);
- let mut ty_args = ty.generic_parameters(sema.db).peekable();
+ let mut ty_args = ty.generic_parameters(sema.db, edition).peekable();
let params = if ty_args.peek().is_some() {
format!("<{}>", ty_args.format_with(",", |ty, cb| cb(&ty)))
} else {
String::new()
};
- let mut test_id = format!("{}{params}", adt_name.display(sema.db));
+ let mut test_id = format!("{}{params}", adt_name.display(sema.db, edition));
test_id.retain(|c| c != ' ');
let test_id = TestId::Path(test_id);
@@ -419,8 +425,11 @@ fn runnable_mod_outline_definition(
.path_to_root(sema.db)
.into_iter()
.rev()
- .filter_map(|it| it.name(sema.db))
- .map(|it| it.display(sema.db).to_string())
+ .filter_map(|module| {
+ module.name(sema.db).map(|mod_name| {
+ mod_name.display(sema.db, module.krate().edition(sema.db)).to_string()
+ })
+ })
.join("::");
let attrs = def.attrs(sema.db);
@@ -452,6 +461,7 @@ fn module_def_doctest(db: &RootDatabase, def: Definition) -> Option<Runnable> {
Definition::SelfType(it) => it.attrs(db),
_ => return None,
};
+ let edition = def.krate(db).map(|it| it.edition(db)).unwrap_or(Edition::CURRENT);
if !has_runnable_doc_test(&attrs) {
return None;
}
@@ -460,29 +470,29 @@ fn module_def_doctest(db: &RootDatabase, def: Definition) -> Option<Runnable> {
let mut path = String::new();
def.canonical_module_path(db)?
.flat_map(|it| it.name(db))
- .for_each(|name| format_to!(path, "{}::", name.display(db)));
+ .for_each(|name| format_to!(path, "{}::", name.display(db, edition)));
// This probably belongs to canonical_path?
if let Some(assoc_item) = def.as_assoc_item(db) {
if let Some(ty) = assoc_item.implementing_ty(db) {
if let Some(adt) = ty.as_adt() {
let name = adt.name(db);
- let mut ty_args = ty.generic_parameters(db).peekable();
- format_to!(path, "{}", name.display(db));
+ let mut ty_args = ty.generic_parameters(db, edition).peekable();
+ format_to!(path, "{}", name.display(db, edition));
if ty_args.peek().is_some() {
format_to!(path, "<{}>", ty_args.format_with(",", |ty, cb| cb(&ty)));
}
- format_to!(path, "::{}", def_name.display(db));
+ format_to!(path, "::{}", def_name.display(db, edition));
path.retain(|c| c != ' ');
return Some(path);
}
}
}
- format_to!(path, "{}", def_name.display(db));
+ format_to!(path, "{}", def_name.display(db, edition));
Some(path)
})();
- let test_id =
- path.map_or_else(|| TestId::Name(def_name.display_no_db().to_smolstr()), TestId::Path);
+ let test_id = path
+ .map_or_else(|| TestId::Name(def_name.display_no_db(edition).to_smolstr()), TestId::Path);
let mut nav = match def {
Definition::Module(def) => NavigationTarget::from_module_to_decl(db, def),
diff --git a/crates/ide/src/signature_help.rs b/crates/ide/src/signature_help.rs
index b6c9e2f636..516f64959c 100644
--- a/crates/ide/src/signature_help.rs
+++ b/crates/ide/src/signature_help.rs
@@ -4,15 +4,13 @@
use std::collections::BTreeSet;
use either::Either;
-use hir::{
- AssocItem, DescendPreference, GenericParam, HirDisplay, ModuleDef, PathResolution, Semantics,
- Trait,
-};
+use hir::{AssocItem, GenericParam, HirDisplay, ModuleDef, PathResolution, Semantics, Trait};
use ide_db::{
active_parameter::{callable_for_node, generic_def_for_node},
documentation::{Documentation, HasDocs},
FilePosition, FxIndexMap,
};
+use span::Edition;
use stdx::format_to;
use syntax::{
algo,
@@ -81,7 +79,9 @@ pub(crate) fn signature_help(
// if the cursor is sandwiched between two space tokens and the call is unclosed
// this prevents us from leaving the CallExpression
.and_then(|tok| algo::skip_trivia_token(tok, Direction::Prev))?;
- let token = sema.descend_into_macros_single(DescendPreference::None, token);
+ let token = sema.descend_into_macros_single_exact(token);
+ let edition =
+ sema.attach_first_edition(file_id).map(|it| it.edition()).unwrap_or(Edition::CURRENT);
for node in token.parent_ancestors() {
match_ast! {
@@ -91,49 +91,49 @@ pub(crate) fn signature_help(
if cursor_outside {
continue;
}
- return signature_help_for_call(&sema, arg_list, token);
+ return signature_help_for_call(&sema, arg_list, token, edition);
},
ast::GenericArgList(garg_list) => {
let cursor_outside = garg_list.r_angle_token().as_ref() == Some(&token);
if cursor_outside {
continue;
}
- return signature_help_for_generics(&sema, garg_list, token);
+ return signature_help_for_generics(&sema, garg_list, token, edition);
},
ast::RecordExpr(record) => {
let cursor_outside = record.record_expr_field_list().and_then(|list| list.r_curly_token()).as_ref() == Some(&token);
if cursor_outside {
continue;
}
- return signature_help_for_record_lit(&sema, record, token);
+ return signature_help_for_record_lit(&sema, record, token, edition);
},
ast::RecordPat(record) => {
let cursor_outside = record.record_pat_field_list().and_then(|list| list.r_curly_token()).as_ref() == Some(&token);
if cursor_outside {
continue;
}
- return signature_help_for_record_pat(&sema, record, token);
+ return signature_help_for_record_pat(&sema, record, token, edition);
},
ast::TupleStructPat(tuple_pat) => {
let cursor_outside = tuple_pat.r_paren_token().as_ref() == Some(&token);
if cursor_outside {
continue;
}
- return signature_help_for_tuple_struct_pat(&sema, tuple_pat, token);
+ return signature_help_for_tuple_struct_pat(&sema, tuple_pat, token, edition);
},
ast::TuplePat(tuple_pat) => {
let cursor_outside = tuple_pat.r_paren_token().as_ref() == Some(&token);
if cursor_outside {
continue;
}
- return signature_help_for_tuple_pat(&sema, tuple_pat, token);
+ return signature_help_for_tuple_pat(&sema, tuple_pat, token, edition);
},
ast::TupleExpr(tuple_expr) => {
let cursor_outside = tuple_expr.r_paren_token().as_ref() == Some(&token);
if cursor_outside {
continue;
}
- return signature_help_for_tuple_expr(&sema, tuple_expr, token);
+ return signature_help_for_tuple_expr(&sema, tuple_expr, token, edition);
},
_ => (),
}
@@ -157,6 +157,7 @@ fn signature_help_for_call(
sema: &Semantics<'_, RootDatabase>,
arg_list: ast::ArgList,
token: SyntaxToken,
+ edition: Edition,
) -> Option<SignatureHelp> {
// Find the calling expression and its NameRef
let mut nodes = arg_list.syntax().ancestors().skip(1);
@@ -181,7 +182,7 @@ fn signature_help_for_call(
match callable.kind() {
hir::CallableKind::Function(func) => {
res.doc = func.docs(db);
- format_to!(res.signature, "fn {}", func.name(db).display(db));
+ format_to!(res.signature, "fn {}", func.name(db).display(db, edition));
fn_params = Some(match callable.receiver_param(db) {
Some(_self) => func.params_without_self(db),
None => func.assoc_fn_params(db),
@@ -189,15 +190,15 @@ fn signature_help_for_call(
}
hir::CallableKind::TupleStruct(strukt) => {
res.doc = strukt.docs(db);
- format_to!(res.signature, "struct {}", strukt.name(db).display(db));
+ format_to!(res.signature, "struct {}", strukt.name(db).display(db, edition));
}
hir::CallableKind::TupleEnumVariant(variant) => {
res.doc = variant.docs(db);
format_to!(
res.signature,
"enum {}::{}",
- variant.parent_enum(db).name(db).display(db),
- variant.name(db).display(db)
+ variant.parent_enum(db).name(db).display(db, edition),
+ variant.name(db).display(db, edition)
);
}
hir::CallableKind::Closure(closure) => {
@@ -210,7 +211,7 @@ fn signature_help_for_call(
Some(adt) => format_to!(
res.signature,
"<{} as {fn_trait}>::{}",
- adt.name(db).display(db),
+ adt.name(db).display(db, edition),
fn_trait.function_name()
),
None => format_to!(res.signature, "impl {fn_trait}"),
@@ -220,7 +221,7 @@ fn signature_help_for_call(
res.signature.push('(');
{
if let Some((self_param, _)) = callable.receiver_param(db) {
- format_to!(res.signature, "{}", self_param.display(db))
+ format_to!(res.signature, "{}", self_param.display(db, edition))
}
let mut buf = String::new();
for (idx, p) in callable.params().into_iter().enumerate() {
@@ -240,8 +241,10 @@ fn signature_help_for_call(
// This is overly conservative: we do not substitute known type vars
// (see FIXME in tests::impl_trait) and falling back on any unknowns.
match (p.ty().contains_unknown(), fn_params.as_deref()) {
- (true, Some(fn_params)) => format_to!(buf, "{}", fn_params[idx].ty().display(db)),
- _ => format_to!(buf, "{}", p.ty().display(db)),
+ (true, Some(fn_params)) => {
+ format_to!(buf, "{}", fn_params[idx].ty().display(db, edition))
+ }
+ _ => format_to!(buf, "{}", p.ty().display(db, edition)),
}
res.push_call_param(&buf);
}
@@ -250,7 +253,7 @@ fn signature_help_for_call(
let mut render = |ret_type: hir::Type| {
if !ret_type.is_unit() {
- format_to!(res.signature, " -> {}", ret_type.display(db));
+ format_to!(res.signature, " -> {}", ret_type.display(db, edition));
}
};
match callable.kind() {
@@ -270,6 +273,7 @@ fn signature_help_for_generics(
sema: &Semantics<'_, RootDatabase>,
arg_list: ast::GenericArgList,
token: SyntaxToken,
+ edition: Edition,
) -> Option<SignatureHelp> {
let (generics_def, mut active_parameter, first_arg_is_non_lifetime, variant) =
generic_def_for_node(sema, &arg_list, &token)?;
@@ -284,11 +288,11 @@ fn signature_help_for_generics(
match generics_def {
hir::GenericDef::Function(it) => {
res.doc = it.docs(db);
- format_to!(res.signature, "fn {}", it.name(db).display(db));
+ format_to!(res.signature, "fn {}", it.name(db).display(db, edition));
}
hir::GenericDef::Adt(hir::Adt::Enum(it)) => {
res.doc = it.docs(db);
- format_to!(res.signature, "enum {}", it.name(db).display(db));
+ format_to!(res.signature, "enum {}", it.name(db).display(db, edition));
if let Some(variant) = variant {
// In paths, generics of an enum can be specified *after* one of its variants.
// eg. `None::<u8>`
@@ -298,23 +302,23 @@ fn signature_help_for_generics(
}
hir::GenericDef::Adt(hir::Adt::Struct(it)) => {
res.doc = it.docs(db);
- format_to!(res.signature, "struct {}", it.name(db).display(db));
+ format_to!(res.signature, "struct {}", it.name(db).display(db, edition));
}
hir::GenericDef::Adt(hir::Adt::Union(it)) => {
res.doc = it.docs(db);
- format_to!(res.signature, "union {}", it.name(db).display(db));
+ format_to!(res.signature, "union {}", it.name(db).display(db, edition));
}
hir::GenericDef::Trait(it) => {
res.doc = it.docs(db);
- format_to!(res.signature, "trait {}", it.name(db).display(db));
+ format_to!(res.signature, "trait {}", it.name(db).display(db, edition));
}
hir::GenericDef::TraitAlias(it) => {
res.doc = it.docs(db);
- format_to!(res.signature, "trait {}", it.name(db).display(db));
+ format_to!(res.signature, "trait {}", it.name(db).display(db, edition));
}
hir::GenericDef::TypeAlias(it) => {
res.doc = it.docs(db);
- format_to!(res.signature, "type {}", it.name(db).display(db));
+ format_to!(res.signature, "type {}", it.name(db).display(db, edition));
}
// These don't have generic args that can be specified
hir::GenericDef::Impl(_) | hir::GenericDef::Const(_) => return None,
@@ -339,11 +343,11 @@ fn signature_help_for_generics(
}
buf.clear();
- format_to!(buf, "{}", param.display(db));
+ format_to!(buf, "{}", param.display(db, edition));
res.push_generic_param(&buf);
}
if let hir::GenericDef::Trait(tr) = generics_def {
- add_assoc_type_bindings(db, &mut res, tr, arg_list);
+ add_assoc_type_bindings(db, &mut res, tr, arg_list, edition);
}
res.signature.push('>');
@@ -355,6 +359,7 @@ fn add_assoc_type_bindings(
res: &mut SignatureHelp,
tr: Trait,
args: ast::GenericArgList,
+ edition: Edition,
) {
if args.syntax().ancestors().find_map(ast::TypeBound::cast).is_none() {
// Assoc type bindings are only valid in type bound position.
@@ -378,7 +383,7 @@ fn add_assoc_type_bindings(
for item in tr.items_with_supertraits(db) {
if let AssocItem::TypeAlias(ty) = item {
- let name = ty.name(db).display_no_db().to_smolstr();
+ let name = ty.name(db).display_no_db(edition).to_smolstr();
if !present_bindings.contains(&*name) {
buf.clear();
format_to!(buf, "{} = …", name);
@@ -392,6 +397,7 @@ fn signature_help_for_record_lit(
sema: &Semantics<'_, RootDatabase>,
record: ast::RecordExpr,
token: SyntaxToken,
+ edition: Edition,
) -> Option<SignatureHelp> {
signature_help_for_record_(
sema,
@@ -403,6 +409,7 @@ fn signature_help_for_record_lit(
.filter_map(|field| sema.resolve_record_field(&field))
.map(|(field, _, ty)| (field, ty)),
token,
+ edition,
)
}
@@ -410,6 +417,7 @@ fn signature_help_for_record_pat(
sema: &Semantics<'_, RootDatabase>,
record: ast::RecordPat,
token: SyntaxToken,
+ edition: Edition,
) -> Option<SignatureHelp> {
signature_help_for_record_(
sema,
@@ -420,6 +428,7 @@ fn signature_help_for_record_pat(
.fields()
.filter_map(|field| sema.resolve_record_pat_field(&field)),
token,
+ edition,
)
}
@@ -427,6 +436,7 @@ fn signature_help_for_tuple_struct_pat(
sema: &Semantics<'_, RootDatabase>,
pat: ast::TupleStructPat,
token: SyntaxToken,
+ edition: Edition,
) -> Option<SignatureHelp> {
let path = pat.path()?;
let path_res = sema.resolve_path(&path)?;
@@ -445,8 +455,8 @@ fn signature_help_for_tuple_struct_pat(
format_to!(
res.signature,
"enum {}::{} (",
- en.name(db).display(db),
- variant.name(db).display(db)
+ en.name(db).display(db, edition),
+ variant.name(db).display(db, edition)
);
variant.fields(db)
} else {
@@ -459,7 +469,7 @@ fn signature_help_for_tuple_struct_pat(
match adt {
hir::Adt::Struct(it) => {
res.doc = it.docs(db);
- format_to!(res.signature, "struct {} (", it.name(db).display(db));
+ format_to!(res.signature, "struct {} (", it.name(db).display(db, edition));
it.fields(db)
}
_ => return None,
@@ -472,6 +482,7 @@ fn signature_help_for_tuple_struct_pat(
token,
pat.fields(),
fields.into_iter().map(|it| it.ty(db)),
+ edition,
))
}
@@ -479,6 +490,7 @@ fn signature_help_for_tuple_pat(
sema: &Semantics<'_, RootDatabase>,
pat: ast::TuplePat,
token: SyntaxToken,
+ edition: Edition,
) -> Option<SignatureHelp> {
let db = sema.db;
let field_pats = pat.fields();
@@ -498,6 +510,7 @@ fn signature_help_for_tuple_pat(
token,
field_pats,
fields.into_iter(),
+ edition,
))
}
@@ -505,6 +518,7 @@ fn signature_help_for_tuple_expr(
sema: &Semantics<'_, RootDatabase>,
expr: ast::TupleExpr,
token: SyntaxToken,
+ edition: Edition,
) -> Option<SignatureHelp> {
let active_parameter = Some(
expr.syntax()
@@ -526,7 +540,7 @@ fn signature_help_for_tuple_expr(
let fields = expr.original.tuple_fields(db);
let mut buf = String::new();
for ty in fields {
- format_to!(buf, "{}", ty.display_truncated(db, Some(20)));
+ format_to!(buf, "{}", ty.display_truncated(db, Some(20), edition));
res.push_call_param(&buf);
buf.clear();
}
@@ -540,6 +554,7 @@ fn signature_help_for_record_(
path: &ast::Path,
fields2: impl Iterator<Item = (hir::Field, hir::Type)>,
token: SyntaxToken,
+ edition: Edition,
) -> Option<SignatureHelp> {
let active_parameter = field_list_children
.filter_map(NodeOrToken::into_token)
@@ -566,8 +581,8 @@ fn signature_help_for_record_(
format_to!(
res.signature,
"enum {}::{} {{ ",
- en.name(db).display(db),
- variant.name(db).display(db)
+ en.name(db).display(db, edition),
+ variant.name(db).display(db, edition)
);
} else {
let adt = match path_res {
@@ -580,12 +595,12 @@ fn signature_help_for_record_(
hir::Adt::Struct(it) => {
fields = it.fields(db);
res.doc = it.docs(db);
- format_to!(res.signature, "struct {} {{ ", it.name(db).display(db));
+ format_to!(res.signature, "struct {} {{ ", it.name(db).display(db, edition));
}
hir::Adt::Union(it) => {
fields = it.fields(db);
res.doc = it.docs(db);
- format_to!(res.signature, "union {} {{ ", it.name(db).display(db));
+ format_to!(res.signature, "union {} {{ ", it.name(db).display(db, edition));
}
_ => return None,
}
@@ -596,7 +611,12 @@ fn signature_help_for_record_(
let mut buf = String::new();
for (field, ty) in fields2 {
let name = field.name(db);
- format_to!(buf, "{}: {}", name.display(db), ty.display_truncated(db, Some(20)));
+ format_to!(
+ buf,
+ "{}: {}",
+ name.display(db, edition),
+ ty.display_truncated(db, Some(20), edition)
+ );
res.push_record_field(&buf);
buf.clear();
@@ -606,7 +626,12 @@ fn signature_help_for_record_(
}
for (name, field) in fields {
let Some(field) = field else { continue };
- format_to!(buf, "{}: {}", name.display(db), field.ty(db).display_truncated(db, Some(20)));
+ format_to!(
+ buf,
+ "{}: {}",
+ name.display(db, edition),
+ field.ty(db).display_truncated(db, Some(20), edition)
+ );
res.push_record_field(&buf);
buf.clear();
}
@@ -621,6 +646,7 @@ fn signature_help_for_tuple_pat_ish(
token: SyntaxToken,
mut field_pats: AstChildren<ast::Pat>,
fields: impl ExactSizeIterator<Item = hir::Type>,
+ edition: Edition,
) -> SignatureHelp {
let rest_pat = field_pats.find(|it| matches!(it, ast::Pat::RestPat(_)));
let is_left_of_rest_pat =
@@ -647,7 +673,7 @@ fn signature_help_for_tuple_pat_ish(
let mut buf = String::new();
for ty in fields {
- format_to!(buf, "{}", ty.display_truncated(db, Some(20)));
+ format_to!(buf, "{}", ty.display_truncated(db, Some(20), edition));
res.push_call_param(&buf);
buf.clear();
}
diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs
index eaccee08e8..1cbe8c62a8 100644
--- a/crates/ide/src/static_index.rs
+++ b/crates/ide/src/static_index.rs
@@ -10,6 +10,7 @@ use ide_db::{
helpers::get_definition,
FileId, FileRange, FxHashMap, FxHashSet, RootDatabase,
};
+use span::Edition;
use syntax::{AstNode, SyntaxKind::*, SyntaxNode, TextRange, T};
use crate::inlay_hints::InlayFieldsToResolve;
@@ -116,7 +117,16 @@ fn documentation_for_definition(
_ => None,
};
- def.docs(sema.db, famous_defs.as_ref())
+ def.docs(
+ sema.db,
+ famous_defs.as_ref(),
+ def.krate(sema.db).map(|it| it.edition(sema.db)).unwrap_or(Edition::CURRENT),
+ )
+}
+
+pub enum VendoredLibrariesConfig<'a> {
+ Included { workspace_root: &'a VfsPath },
+ Excluded,
}
impl StaticIndex<'_> {
@@ -161,6 +171,8 @@ impl StaticIndex<'_> {
// hovers
let sema = hir::Semantics::new(self.db);
let tokens_or_nodes = sema.parse_guess_edition(file_id).syntax().clone();
+ let edition =
+ sema.attach_first_edition(file_id).map(|it| it.edition()).unwrap_or(Edition::CURRENT);
let tokens = tokens_or_nodes.descendants_with_tokens().filter_map(|it| match it {
syntax::NodeOrToken::Node(_) => None,
syntax::NodeOrToken::Token(it) => Some(it),
@@ -201,17 +213,20 @@ impl StaticIndex<'_> {
&node,
None,
&hover_config,
+ edition,
)),
definition: def.try_to_nav(self.db).map(UpmappingResult::call_site).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)),
- display_name: def.name(self.db).map(|name| name.display(self.db).to_string()),
+ display_name: def
+ .name(self.db)
+ .map(|name| name.display(self.db, edition).to_string()),
enclosing_moniker: current_crate
.zip(def.enclosing_definition(self.db))
.and_then(|(cc, enclosing_def)| def_to_moniker(self.db, enclosing_def, cc)),
- signature: Some(def.label(self.db)),
+ signature: Some(def.label(self.db, edition)),
kind: def_to_kind(self.db, def),
});
self.def_map.insert(def, it);
@@ -230,15 +245,22 @@ impl StaticIndex<'_> {
self.files.push(result);
}
- pub fn compute<'a>(analysis: &'a Analysis, workspace_root: &VfsPath) -> StaticIndex<'a> {
+ pub fn compute<'a>(
+ analysis: &'a Analysis,
+ vendored_libs_config: VendoredLibrariesConfig<'_>,
+ ) -> StaticIndex<'a> {
let db = &*analysis.db;
let work = all_modules(db).into_iter().filter(|module| {
let file_id = module.definition_source_file_id(db).original_file(db);
let source_root = db.file_source_root(file_id.into());
let source_root = db.source_root(source_root);
- let is_vendored = source_root
- .path_for_file(&file_id.into())
- .is_some_and(|module_path| module_path.starts_with(workspace_root));
+ let is_vendored = match vendored_libs_config {
+ VendoredLibrariesConfig::Included { workspace_root } => source_root
+ .path_for_file(&file_id.into())
+ .is_some_and(|module_path| module_path.starts_with(workspace_root)),
+ VendoredLibrariesConfig::Excluded => false,
+ };
+
!source_root.is_library || is_vendored
});
let mut this = StaticIndex {
@@ -268,10 +290,11 @@ mod tests {
use ide_db::{base_db::VfsPath, FileRange, FxHashSet};
use syntax::TextSize;
- fn check_all_ranges(ra_fixture: &str) {
+ use super::VendoredLibrariesConfig;
+
+ fn check_all_ranges(ra_fixture: &str, vendored_libs_config: VendoredLibrariesConfig<'_>) {
let (analysis, ranges) = fixture::annotations_without_marker(ra_fixture);
- let s =
- StaticIndex::compute(&analysis, &VfsPath::new_virtual_path("/workspace".to_owned()));
+ let s = StaticIndex::compute(&analysis, vendored_libs_config);
let mut range_set: FxHashSet<_> = ranges.iter().map(|it| it.0).collect();
for f in s.files {
for (range, _) in f.tokens {
@@ -288,10 +311,9 @@ mod tests {
}
#[track_caller]
- fn check_definitions(ra_fixture: &str) {
+ fn check_definitions(ra_fixture: &str, vendored_libs_config: VendoredLibrariesConfig<'_>) {
let (analysis, ranges) = fixture::annotations_without_marker(ra_fixture);
- let s =
- StaticIndex::compute(&analysis, &VfsPath::new_virtual_path("/workspace".to_owned()));
+ let s = StaticIndex::compute(&analysis, vendored_libs_config);
let mut range_set: FxHashSet<_> = ranges.iter().map(|it| it.0).collect();
for (_, t) in s.tokens.iter() {
if let Some(t) = t.definition {
@@ -319,6 +341,9 @@ struct Foo;
enum E { X(Foo) }
//^ ^ ^^^
"#,
+ VendoredLibrariesConfig::Included {
+ workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),
+ },
);
check_definitions(
r#"
@@ -327,6 +352,9 @@ struct Foo;
enum E { X(Foo) }
//^ ^
"#,
+ VendoredLibrariesConfig::Included {
+ workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),
+ },
);
}
@@ -349,6 +377,9 @@ pub func() {
}
"#,
+ VendoredLibrariesConfig::Included {
+ workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),
+ },
);
}
@@ -367,10 +398,31 @@ struct ExternalLibrary(i32);
struct VendoredLibrary(i32);
//^^^^^^^^^^^^^^^ ^^^
"#,
+ VendoredLibrariesConfig::Included {
+ workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),
+ },
);
}
#[test]
+ fn vendored_crate_excluded() {
+ check_all_ranges(
+ r#"
+//- /workspace/main.rs crate:main deps:external,vendored
+struct Main(i32);
+ //^^^^ ^^^
+
+//- /external/lib.rs new_source_root:library crate:[email protected],https://a.b/foo.git library
+struct ExternalLibrary(i32);
+
+//- /workspace/vendored/lib.rs new_source_root:library crate:[email protected],https://a.b/bar.git library
+struct VendoredLibrary(i32);
+"#,
+ VendoredLibrariesConfig::Excluded,
+ )
+ }
+
+ #[test]
fn derives() {
check_all_ranges(
r#"
@@ -384,6 +436,9 @@ pub macro Copy {}
struct Hello(i32);
//^^^^^ ^^^
"#,
+ VendoredLibrariesConfig::Included {
+ workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),
+ },
);
}
}
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index 2318592005..927fdaa178 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -13,7 +13,9 @@ mod html;
#[cfg(test)]
mod tests;
-use hir::{DescendPreference, Name, Semantics};
+use std::ops::ControlFlow;
+
+use hir::{InRealFile, Name, Semantics};
use ide_db::{FxHashMap, RootDatabase, SymbolKind};
use span::EditionedFileId;
use syntax::{
@@ -399,19 +401,55 @@ fn traverse(
// Attempt to descend tokens into macro-calls.
let res = match element {
NodeOrToken::Token(token) if token.kind() != COMMENT => {
- let token = if token.kind() == STRING {
- // for strings, try to prefer a string that has not been lost in a token
- // tree
- // FIXME: This should be done for everything, but check perf first
- sema.descend_into_macros(DescendPreference::SameKind, token)
- .into_iter()
- .max_by_key(|it| {
- it.parent().map_or(false, |it| it.kind() != TOKEN_TREE)
- })
- .unwrap()
- } else {
- sema.descend_into_macros_single(DescendPreference::SameKind, token)
- };
+ let kind = token.kind();
+ let text = token.text();
+ let ident_kind = kind.is_any_identifier();
+
+ let mut t = None;
+ let mut r = 0;
+ sema.descend_into_macros_breakable(
+ InRealFile::new(file_id, token.clone()),
+ |tok| {
+ let tok = tok.value;
+ let tok_kind = tok.kind();
+
+ let exact_same_kind = tok_kind == kind;
+ let both_idents =
+ exact_same_kind || (tok_kind.is_any_identifier() && ident_kind);
+ let same_text = tok.text() == text;
+ // anything that mapped into a token tree has likely no semantic information
+ let no_tt_parent =
+ tok.parent().map_or(false, |it| it.kind() != TOKEN_TREE);
+ let my_rank = (both_idents as usize)
+ | ((exact_same_kind as usize) << 1)
+ | ((same_text as usize) << 2)
+ | ((no_tt_parent as usize) << 3);
+
+ if my_rank > 0b1110 {
+ // a rank of 0b1110 means that we have found a maximally interesting
+ // token so stop early.
+ t = Some(tok);
+ return ControlFlow::Break(());
+ }
+
+ // r = r.max(my_rank);
+ // t = Some(t.take_if(|_| r < my_rank).unwrap_or(tok));
+ match &mut t {
+ Some(prev) if r < my_rank => {
+ *prev = tok;
+ r = my_rank;
+ }
+ Some(_) => (),
+ None => {
+ r = my_rank;
+ t = Some(tok)
+ }
+ }
+ ControlFlow::Continue(())
+ },
+ );
+
+ let token = t.unwrap_or(token);
match token.parent().and_then(ast::NameLike::cast) {
// Remap the token into the wrapping single token nodes
Some(parent) => match (token.kind(), parent.syntax().kind()) {
@@ -501,7 +539,9 @@ fn traverse(
config.syntactic_name_ref_highlighting,
name_like,
),
- NodeOrToken::Token(token) => highlight::token(sema, token).zip(Some(None)),
+ NodeOrToken::Token(token) => {
+ highlight::token(sema, token, file_id.edition()).zip(Some(None))
+ }
};
if let Some((mut highlight, binding_hash)) = element {
if is_unlinked && highlight.tag == HlTag::UnresolvedReference {
diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs
index 291073f877..eeba9cf35c 100644
--- a/crates/ide/src/syntax_highlighting/highlight.rs
+++ b/crates/ide/src/syntax_highlighting/highlight.rs
@@ -6,6 +6,7 @@ use ide_db::{
defs::{Definition, IdentClass, NameClass, NameRefClass},
FxHashMap, RootDatabase, SymbolKind,
};
+use span::Edition;
use stdx::hash_once;
use syntax::{
ast, match_ast, AstNode, AstToken, NodeOrToken,
@@ -18,7 +19,11 @@ use crate::{
Highlight, HlMod, HlTag,
};
-pub(super) fn token(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> Option<Highlight> {
+pub(super) fn token(
+ sema: &Semantics<'_, RootDatabase>,
+ token: SyntaxToken,
+ edition: Edition,
+) -> Option<Highlight> {
if let Some(comment) = ast::Comment::cast(token.clone()) {
let h = HlTag::Comment;
return Some(match comment.kind().doc {
@@ -41,7 +46,7 @@ pub(super) fn token(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> O
HlTag::None.into()
}
p if p.is_punct() => punctuation(sema, token, p),
- k if k.is_keyword() => keyword(sema, token, k)?,
+ k if k.is_keyword(edition) => keyword(sema, token, k)?,
_ => return None,
};
Some(highlight)
diff --git a/crates/ide/src/syntax_highlighting/macro_.rs b/crates/ide/src/syntax_highlighting/macro_.rs
index 1099d9c23b..b441b4cc90 100644
--- a/crates/ide/src/syntax_highlighting/macro_.rs
+++ b/crates/ide/src/syntax_highlighting/macro_.rs
@@ -117,7 +117,7 @@ fn update_macro_state(state: &mut MacroMatcherParseState, tok: &SyntaxToken) {
fn is_metavariable(token: &SyntaxToken) -> Option<TextRange> {
match token.kind() {
- kind if kind == SyntaxKind::IDENT || kind.is_keyword() => {
+ kind if kind.is_any_identifier() => {
if let Some(_dollar) = token.prev_token().filter(|t| t.kind() == T![$]) {
return Some(token.text_range());
}
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html b/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html
index 17411fefbd..196552020a 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html
@@ -100,7 +100,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
<span class="brace">}</span><span class="semicolon">;</span>
<span class="brace">}</span>
-<span class="macro default_library library">include</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="macro default_library library macro">concat</span><span class="macro_bang macro">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"foo/"</span><span class="string_literal 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="macro default_library library">include</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="macro default_library library macro">concat</span><span class="macro_bang 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">struct</span> <span class="struct declaration">S</span><span class="angle">&lt;</span><span class="type_param declaration">T</span><span class="angle">&gt;</span><span class="parenthesis">(</span><span class="type_param">T</span><span class="parenthesis">)</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>
diff --git a/crates/ide/src/view_item_tree.rs b/crates/ide/src/view_item_tree.rs
index dae79998dc..a6352b99d4 100644
--- a/crates/ide/src/view_item_tree.rs
+++ b/crates/ide/src/view_item_tree.rs
@@ -16,5 +16,5 @@ pub(crate) fn view_item_tree(db: &RootDatabase, file_id: FileId) -> String {
let file_id = sema
.attach_first_edition(file_id)
.unwrap_or_else(|| EditionedFileId::current_edition(file_id));
- db.file_item_tree(file_id.into()).pretty_print(db)
+ db.file_item_tree(file_id.into()).pretty_print(db, file_id.edition())
}
diff --git a/crates/ide/src/view_memory_layout.rs b/crates/ide/src/view_memory_layout.rs
index df3f2f18b4..830c39e21e 100644
--- a/crates/ide/src/view_memory_layout.rs
+++ b/crates/ide/src/view_memory_layout.rs
@@ -6,6 +6,7 @@ use ide_db::{
helpers::{get_definition, pick_best_token},
RootDatabase,
};
+use span::Edition;
use syntax::{AstNode, SyntaxKind};
use crate::FilePosition;
@@ -85,6 +86,10 @@ pub(crate) fn view_memory_layout(
) -> Option<RecursiveMemoryLayout> {
let sema = Semantics::new(db);
let file = sema.parse_guess_edition(position.file_id);
+ let edition = sema
+ .attach_first_edition(position.file_id)
+ .map(|it| it.edition())
+ .unwrap_or(Edition::CURRENT);
let token =
pick_best_token(file.syntax().token_at_offset(position.offset), |kind| match kind {
SyntaxKind::IDENT => 3,
@@ -111,6 +116,7 @@ pub(crate) fn view_memory_layout(
ty: &Type,
layout: &Layout,
parent_idx: usize,
+ edition: Edition,
) {
let mut fields = ty
.fields(db)
@@ -141,7 +147,7 @@ pub(crate) fn view_memory_layout(
if let Ok(child_layout) = child_ty.layout(db) {
nodes.push(MemoryLayoutNode {
item_name: field.name(db),
- typename: child_ty.display(db).to_string(),
+ typename: child_ty.display(db, edition).to_string(),
size: child_layout.size(),
alignment: child_layout.align(),
offset: match *field {
@@ -157,7 +163,7 @@ pub(crate) fn view_memory_layout(
item_name: field.name(db)
+ format!("(no layout data: {:?})", child_ty.layout(db).unwrap_err())
.as_ref(),
- typename: child_ty.display(db).to_string(),
+ typename: child_ty.display(db, edition).to_string(),
size: 0,
offset: 0,
alignment: 0,
@@ -170,7 +176,7 @@ pub(crate) fn view_memory_layout(
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);
+ read_layout(nodes, db, child_ty, &child_layout, children_start + i, edition);
}
}
}
@@ -188,7 +194,7 @@ pub(crate) fn view_memory_layout(
def => def.name(db).map(|n| n.as_str().to_owned()).unwrap_or("[ROOT]".to_owned()),
};
- let typename = ty.display(db).to_string();
+ let typename = ty.display(db, edition).to_string();
let mut nodes = vec![MemoryLayoutNode {
item_name,
@@ -200,7 +206,7 @@ pub(crate) fn view_memory_layout(
children_start: -1,
children_len: 0,
}];
- read_layout(&mut nodes, db, &ty, &layout, 0);
+ read_layout(&mut nodes, db, &ty, &layout, 0, edition);
RecursiveMemoryLayout { nodes }
})
diff --git a/crates/intern/src/symbol/symbols.rs b/crates/intern/src/symbol/symbols.rs
index 2feca32ff8..7eb8e4a5e2 100644
--- a/crates/intern/src/symbol/symbols.rs
+++ b/crates/intern/src/symbol/symbols.rs
@@ -239,6 +239,7 @@ define_symbols! {
fundamental,
future_trait,
future,
+ future_output,
Future,
ge,
get_context,
@@ -273,6 +274,7 @@ define_symbols! {
iter_mut,
iter,
Iterator,
+ iterator,
keyword,
lang,
le,
diff --git a/crates/parser/src/grammar/generic_args.rs b/crates/parser/src/grammar/generic_args.rs
index 249be2a333..c62c8a9d3f 100644
--- a/crates/parser/src/grammar/generic_args.rs
+++ b/crates/parser/src/grammar/generic_args.rs
@@ -102,13 +102,18 @@ fn generic_arg(p: &mut Parser<'_>) -> bool {
IDENT if p.nth_at(1, T!['(']) => {
let m = p.start();
name_ref(p);
- params::param_list_fn_trait(p);
- if p.at(T![:]) && !p.at(T![::]) {
- // test associated_return_type_bounds
- // fn foo<T: Foo<foo(): Send, bar(i32): Send, baz(i32, i32): Send>>() {}
+ if p.nth_at(1, T![..]) {
+ let rtn = p.start();
+ p.bump(T!['(']);
+ p.bump(T![..]);
+ p.expect(T![')']);
+ rtn.complete(p, RETURN_TYPE_SYNTAX);
+ // test return_type_syntax_assoc_type_bound
+ // fn foo<T: Trait<method(..): Send>>() {}
generic_params::bounds(p);
m.complete(p, ASSOC_TYPE_ARG);
} else {
+ params::param_list_fn_trait(p);
// test bare_dyn_types_with_paren_as_generic_args
// type A = S<Fn(i32)>;
// type A = S<Fn(i32) + Send>;
diff --git a/crates/parser/src/grammar/generic_params.rs b/crates/parser/src/grammar/generic_params.rs
index cf80a535ac..e0fa753fa7 100644
--- a/crates/parser/src/grammar/generic_params.rs
+++ b/crates/parser/src/grammar/generic_params.rs
@@ -119,8 +119,7 @@ fn lifetime_bounds(p: &mut Parser<'_>) {
// test type_param_bounds
// struct S<T: 'a + ?Sized + (Copy) + ~const Drop>;
pub(super) fn bounds(p: &mut Parser<'_>) {
- assert!(p.at(T![:]));
- p.bump(T![:]);
+ p.expect(T![:]);
bounds_without_colon(p);
}
diff --git a/crates/parser/src/grammar/paths.rs b/crates/parser/src/grammar/paths.rs
index 01b8f9e918..09db921803 100644
--- a/crates/parser/src/grammar/paths.rs
+++ b/crates/parser/src/grammar/paths.rs
@@ -140,11 +140,24 @@ fn opt_path_type_args(p: &mut Parser<'_>, mode: Mode) {
if p.at(T![::]) && p.nth_at(2, T!['(']) {
p.bump(T![::]);
}
- // test path_fn_trait_args
- // type F = Box<Fn(i32) -> ()>;
if p.at(T!['(']) {
- params::param_list_fn_trait(p);
- opt_ret_type(p);
+ if p.nth_at(1, T![..]) {
+ // test return_type_syntax_in_path
+ // fn foo<T>()
+ // where
+ // T::method(..): Send,
+ // {}
+ let rtn = p.start();
+ p.bump(T!['(']);
+ p.bump(T![..]);
+ p.expect(T![')']);
+ rtn.complete(p, RETURN_TYPE_SYNTAX);
+ } else {
+ // test path_fn_trait_args
+ // type F = Box<Fn(i32) -> ()>;
+ params::param_list_fn_trait(p);
+ opt_ret_type(p);
+ }
} else {
generic_args::opt_generic_arg_list(p, false);
}
diff --git a/crates/parser/src/lexed_str.rs b/crates/parser/src/lexed_str.rs
index 13fc61074d..ff924830ae 100644
--- a/crates/parser/src/lexed_str.rs
+++ b/crates/parser/src/lexed_str.rs
@@ -178,19 +178,8 @@ impl<'a> Converter<'a> {
rustc_lexer::TokenKind::Whitespace => WHITESPACE,
rustc_lexer::TokenKind::Ident if token_text == "_" => UNDERSCORE,
- rustc_lexer::TokenKind::Ident
- if ["async", "await", "dyn", "try"].contains(&token_text)
- && !self.edition.at_least_2018() =>
- {
- IDENT
- }
- rustc_lexer::TokenKind::Ident
- if token_text == "gen" && !self.edition.at_least_2024() =>
- {
- IDENT
- }
rustc_lexer::TokenKind::Ident => {
- SyntaxKind::from_keyword(token_text).unwrap_or(IDENT)
+ SyntaxKind::from_keyword(token_text, self.edition).unwrap_or(IDENT)
}
rustc_lexer::TokenKind::InvalidPrefix | rustc_lexer::TokenKind::InvalidIdent => {
err = "Ident contains invalid characters";
diff --git a/crates/parser/src/shortcuts.rs b/crates/parser/src/shortcuts.rs
index 1cf81e79b0..7adedba7c4 100644
--- a/crates/parser/src/shortcuts.rs
+++ b/crates/parser/src/shortcuts.rs
@@ -35,12 +35,10 @@ impl LexedStr<'_> {
was_joint = false
} else if kind == SyntaxKind::IDENT {
let token_text = self.text(i);
- let contextual_kw = if !edition.at_least_2018() && token_text == "dyn" {
- SyntaxKind::DYN_KW
- } else {
- SyntaxKind::from_contextual_keyword(token_text).unwrap_or(SyntaxKind::IDENT)
- };
- res.push_ident(contextual_kw);
+ res.push_ident(
+ SyntaxKind::from_contextual_keyword(token_text, edition)
+ .unwrap_or(SyntaxKind::IDENT),
+ )
} else {
if was_joint {
res.was_joint();
diff --git a/crates/parser/src/syntax_kind.rs b/crates/parser/src/syntax_kind.rs
index 3ca6bd4cb1..6a8cca9ccc 100644
--- a/crates/parser/src/syntax_kind.rs
+++ b/crates/parser/src/syntax_kind.rs
@@ -3,6 +3,8 @@
mod generated;
+use crate::Edition;
+
#[allow(unreachable_pub)]
pub use self::generated::SyntaxKind;
@@ -26,4 +28,11 @@ impl SyntaxKind {
pub fn is_trivia(self) -> bool {
matches!(self, SyntaxKind::WHITESPACE | SyntaxKind::COMMENT)
}
+
+ /// Returns true if this is an identifier or a keyword.
+ #[inline]
+ pub fn is_any_identifier(self) -> bool {
+ // Assuming no edition removed keywords...
+ self == SyntaxKind::IDENT || self.is_keyword(Edition::LATEST)
+ }
}
diff --git a/crates/parser/src/syntax_kind/generated.rs b/crates/parser/src/syntax_kind/generated.rs
index 7bddf88740..00f212487a 100644
--- a/crates/parser/src/syntax_kind/generated.rs
+++ b/crates/parser/src/syntax_kind/generated.rs
@@ -1,6 +1,7 @@
//! Generated by `cargo codegen grammar`, do not edit by hand.
#![allow(bad_style, missing_docs, unreachable_pub)]
+use crate::Edition;
#[doc = r" The kind of syntax node, e.g. `IDENT`, `USE_KW`, or `STRUCT`."]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[repr(u16)]
@@ -64,8 +65,6 @@ pub enum SyntaxKind {
SELF_TYPE_KW,
ABSTRACT_KW,
AS_KW,
- ASYNC_KW,
- AWAIT_KW,
BECOME_KW,
BOX_KW,
BREAK_KW,
@@ -73,7 +72,6 @@ pub enum SyntaxKind {
CONTINUE_KW,
CRATE_KW,
DO_KW,
- DYN_KW,
ELSE_KW,
ENUM_KW,
EXTERN_KW,
@@ -81,7 +79,6 @@ pub enum SyntaxKind {
FINAL_KW,
FN_KW,
FOR_KW,
- GEN_KW,
IF_KW,
IMPL_KW,
IN_KW,
@@ -103,7 +100,6 @@ pub enum SyntaxKind {
SUPER_KW,
TRAIT_KW,
TRUE_KW,
- TRY_KW,
TYPE_KW,
TYPEOF_KW,
UNSAFE_KW,
@@ -114,13 +110,18 @@ pub enum SyntaxKind {
WHILE_KW,
YIELD_KW,
ASM_KW,
+ ASYNC_KW,
AUTO_KW,
+ AWAIT_KW,
BUILTIN_KW,
DEFAULT_KW,
+ DYN_KW,
FORMAT_ARGS_KW,
+ GEN_KW,
MACRO_RULES_KW,
OFFSET_OF_KW,
RAW_KW,
+ TRY_KW,
UNION_KW,
YEET_KW,
BYTE,
@@ -252,6 +253,7 @@ pub enum SyntaxKind {
RENAME,
REST_PAT,
RETURN_EXPR,
+ RETURN_TYPE_SYNTAX,
RET_TYPE,
SELF_PARAM,
SLICE_PAT,
@@ -296,14 +298,14 @@ pub enum SyntaxKind {
}
use self::SyntaxKind::*;
impl SyntaxKind {
- pub fn is_keyword(self) -> bool {
+ #[doc = r" Checks whether this syntax kind is a strict keyword for the given edition."]
+ #[doc = r" Strict keywords are identifiers that are always considered keywords."]
+ pub fn is_strict_keyword(self, edition: Edition) -> bool {
matches!(
self,
SELF_TYPE_KW
| ABSTRACT_KW
| AS_KW
- | ASYNC_KW
- | AWAIT_KW
| BECOME_KW
| BOX_KW
| BREAK_KW
@@ -311,7 +313,6 @@ impl SyntaxKind {
| CONTINUE_KW
| CRATE_KW
| DO_KW
- | DYN_KW
| ELSE_KW
| ENUM_KW
| EXTERN_KW
@@ -319,7 +320,6 @@ impl SyntaxKind {
| FINAL_KW
| FN_KW
| FOR_KW
- | GEN_KW
| IF_KW
| IMPL_KW
| IN_KW
@@ -341,7 +341,6 @@ impl SyntaxKind {
| SUPER_KW
| TRAIT_KW
| TRUE_KW
- | TRY_KW
| TYPE_KW
| TYPEOF_KW
| UNSAFE_KW
@@ -351,17 +350,103 @@ impl SyntaxKind {
| WHERE_KW
| WHILE_KW
| YIELD_KW
- | ASM_KW
- | AUTO_KW
- | BUILTIN_KW
- | DEFAULT_KW
- | FORMAT_ARGS_KW
- | MACRO_RULES_KW
- | OFFSET_OF_KW
- | RAW_KW
- | UNION_KW
- | YEET_KW
- )
+ ) || match self {
+ ASYNC_KW if Edition::Edition2018 <= edition => true,
+ AWAIT_KW if Edition::Edition2018 <= edition => true,
+ DYN_KW if Edition::Edition2018 <= edition => true,
+ GEN_KW if Edition::Edition2024 <= edition => true,
+ TRY_KW if Edition::Edition2018 <= edition => true,
+ _ => false,
+ }
+ }
+ #[doc = r" Checks whether this syntax kind is a weak keyword for the given edition."]
+ #[doc = r" Weak keywords are identifiers that are considered keywords only in certain contexts."]
+ pub fn is_contextual_keyword(self, edition: Edition) -> bool {
+ match self {
+ ASM_KW => true,
+ AUTO_KW => true,
+ BUILTIN_KW => true,
+ DEFAULT_KW => true,
+ DYN_KW if edition < Edition::Edition2018 => true,
+ FORMAT_ARGS_KW => true,
+ MACRO_RULES_KW => true,
+ OFFSET_OF_KW => true,
+ RAW_KW => true,
+ UNION_KW => true,
+ YEET_KW => true,
+ _ => false,
+ }
+ }
+ #[doc = r" Checks whether this syntax kind is a strict or weak keyword for the given edition."]
+ pub fn is_keyword(self, edition: Edition) -> bool {
+ matches!(
+ self,
+ SELF_TYPE_KW
+ | ABSTRACT_KW
+ | AS_KW
+ | BECOME_KW
+ | BOX_KW
+ | BREAK_KW
+ | CONST_KW
+ | CONTINUE_KW
+ | CRATE_KW
+ | DO_KW
+ | ELSE_KW
+ | ENUM_KW
+ | EXTERN_KW
+ | FALSE_KW
+ | FINAL_KW
+ | FN_KW
+ | FOR_KW
+ | IF_KW
+ | IMPL_KW
+ | IN_KW
+ | LET_KW
+ | LOOP_KW
+ | MACRO_KW
+ | MATCH_KW
+ | MOD_KW
+ | MOVE_KW
+ | MUT_KW
+ | OVERRIDE_KW
+ | PRIV_KW
+ | PUB_KW
+ | REF_KW
+ | RETURN_KW
+ | SELF_KW
+ | STATIC_KW
+ | STRUCT_KW
+ | SUPER_KW
+ | TRAIT_KW
+ | TRUE_KW
+ | TYPE_KW
+ | TYPEOF_KW
+ | UNSAFE_KW
+ | UNSIZED_KW
+ | USE_KW
+ | VIRTUAL_KW
+ | WHERE_KW
+ | WHILE_KW
+ | YIELD_KW
+ ) || match self {
+ ASYNC_KW if Edition::Edition2018 <= edition => true,
+ AWAIT_KW if Edition::Edition2018 <= edition => true,
+ DYN_KW if Edition::Edition2018 <= edition => true,
+ GEN_KW if Edition::Edition2024 <= edition => true,
+ TRY_KW if Edition::Edition2018 <= edition => true,
+ ASM_KW => true,
+ AUTO_KW => true,
+ BUILTIN_KW => true,
+ DEFAULT_KW => true,
+ DYN_KW if edition < Edition::Edition2018 => true,
+ FORMAT_ARGS_KW => true,
+ MACRO_RULES_KW => true,
+ OFFSET_OF_KW => true,
+ RAW_KW => true,
+ UNION_KW => true,
+ YEET_KW => true,
+ _ => false,
+ }
}
pub fn is_punct(self) -> bool {
matches!(
@@ -434,13 +519,11 @@ impl SyntaxKind {
| STRING
)
}
- pub fn from_keyword(ident: &str) -> Option<SyntaxKind> {
+ pub fn from_keyword(ident: &str, edition: Edition) -> Option<SyntaxKind> {
let kw = match ident {
"Self" => SELF_TYPE_KW,
"abstract" => ABSTRACT_KW,
"as" => AS_KW,
- "async" => ASYNC_KW,
- "await" => AWAIT_KW,
"become" => BECOME_KW,
"box" => BOX_KW,
"break" => BREAK_KW,
@@ -448,7 +531,6 @@ impl SyntaxKind {
"continue" => CONTINUE_KW,
"crate" => CRATE_KW,
"do" => DO_KW,
- "dyn" => DYN_KW,
"else" => ELSE_KW,
"enum" => ENUM_KW,
"extern" => EXTERN_KW,
@@ -456,7 +538,6 @@ impl SyntaxKind {
"final" => FINAL_KW,
"fn" => FN_KW,
"for" => FOR_KW,
- "gen" => GEN_KW,
"if" => IF_KW,
"impl" => IMPL_KW,
"in" => IN_KW,
@@ -478,7 +559,6 @@ impl SyntaxKind {
"super" => SUPER_KW,
"trait" => TRAIT_KW,
"true" => TRUE_KW,
- "try" => TRY_KW,
"type" => TYPE_KW,
"typeof" => TYPEOF_KW,
"unsafe" => UNSAFE_KW,
@@ -488,16 +568,22 @@ impl SyntaxKind {
"where" => WHERE_KW,
"while" => WHILE_KW,
"yield" => YIELD_KW,
+ "async" if Edition::Edition2018 <= edition => ASYNC_KW,
+ "await" if Edition::Edition2018 <= edition => AWAIT_KW,
+ "dyn" if Edition::Edition2018 <= edition => DYN_KW,
+ "gen" if Edition::Edition2024 <= edition => GEN_KW,
+ "try" if Edition::Edition2018 <= edition => TRY_KW,
_ => return None,
};
Some(kw)
}
- pub fn from_contextual_keyword(ident: &str) -> Option<SyntaxKind> {
+ pub fn from_contextual_keyword(ident: &str, edition: Edition) -> Option<SyntaxKind> {
let kw = match ident {
"asm" => ASM_KW,
"auto" => AUTO_KW,
"builtin" => BUILTIN_KW,
"default" => DEFAULT_KW,
+ "dyn" if edition < Edition::Edition2018 => DYN_KW,
"format_args" => FORMAT_ARGS_KW,
"macro_rules" => MACRO_RULES_KW,
"offset_of" => OFFSET_OF_KW,
@@ -544,4 +630,4 @@ impl SyntaxKind {
}
}
#[macro_export]
-macro_rules ! T { [$] => { $ crate :: SyntaxKind :: DOLLAR } ; [;] => { $ crate :: SyntaxKind :: SEMICOLON } ; [,] => { $ crate :: SyntaxKind :: COMMA } ; ['('] => { $ crate :: SyntaxKind :: L_PAREN } ; [')'] => { $ crate :: SyntaxKind :: R_PAREN } ; ['{'] => { $ crate :: SyntaxKind :: L_CURLY } ; ['}'] => { $ crate :: SyntaxKind :: R_CURLY } ; ['['] => { $ crate :: SyntaxKind :: L_BRACK } ; [']'] => { $ crate :: SyntaxKind :: R_BRACK } ; [<] => { $ crate :: SyntaxKind :: L_ANGLE } ; [>] => { $ crate :: SyntaxKind :: R_ANGLE } ; [@] => { $ crate :: SyntaxKind :: AT } ; [#] => { $ crate :: SyntaxKind :: POUND } ; [~] => { $ crate :: SyntaxKind :: TILDE } ; [?] => { $ crate :: SyntaxKind :: QUESTION } ; [&] => { $ crate :: SyntaxKind :: AMP } ; [|] => { $ crate :: SyntaxKind :: PIPE } ; [+] => { $ crate :: SyntaxKind :: PLUS } ; [*] => { $ crate :: SyntaxKind :: STAR } ; [/] => { $ crate :: SyntaxKind :: SLASH } ; [^] => { $ crate :: SyntaxKind :: CARET } ; [%] => { $ crate :: SyntaxKind :: PERCENT } ; [_] => { $ crate :: SyntaxKind :: UNDERSCORE } ; [.] => { $ crate :: SyntaxKind :: DOT } ; [..] => { $ crate :: SyntaxKind :: DOT2 } ; [...] => { $ crate :: SyntaxKind :: DOT3 } ; [..=] => { $ crate :: SyntaxKind :: DOT2EQ } ; [:] => { $ crate :: SyntaxKind :: COLON } ; [::] => { $ crate :: SyntaxKind :: COLON2 } ; [=] => { $ crate :: SyntaxKind :: EQ } ; [==] => { $ crate :: SyntaxKind :: EQ2 } ; [=>] => { $ crate :: SyntaxKind :: FAT_ARROW } ; [!] => { $ crate :: SyntaxKind :: BANG } ; [!=] => { $ crate :: SyntaxKind :: NEQ } ; [-] => { $ crate :: SyntaxKind :: MINUS } ; [->] => { $ crate :: SyntaxKind :: THIN_ARROW } ; [<=] => { $ crate :: SyntaxKind :: LTEQ } ; [>=] => { $ crate :: SyntaxKind :: GTEQ } ; [+=] => { $ crate :: SyntaxKind :: PLUSEQ } ; [-=] => { $ crate :: SyntaxKind :: MINUSEQ } ; [|=] => { $ crate :: SyntaxKind :: PIPEEQ } ; [&=] => { $ crate :: SyntaxKind :: AMPEQ } ; [^=] => { $ crate :: SyntaxKind :: CARETEQ } ; [/=] => { $ crate :: SyntaxKind :: SLASHEQ } ; [*=] => { $ crate :: SyntaxKind :: STAREQ } ; [%=] => { $ crate :: SyntaxKind :: PERCENTEQ } ; [&&] => { $ crate :: SyntaxKind :: AMP2 } ; [||] => { $ crate :: SyntaxKind :: PIPE2 } ; [<<] => { $ crate :: SyntaxKind :: SHL } ; [>>] => { $ crate :: SyntaxKind :: SHR } ; [<<=] => { $ crate :: SyntaxKind :: SHLEQ } ; [>>=] => { $ crate :: SyntaxKind :: SHREQ } ; [Self] => { $ crate :: SyntaxKind :: SELF_TYPE_KW } ; [abstract] => { $ crate :: SyntaxKind :: ABSTRACT_KW } ; [as] => { $ crate :: SyntaxKind :: AS_KW } ; [async] => { $ crate :: SyntaxKind :: ASYNC_KW } ; [await] => { $ crate :: SyntaxKind :: AWAIT_KW } ; [become] => { $ crate :: SyntaxKind :: BECOME_KW } ; [box] => { $ crate :: SyntaxKind :: BOX_KW } ; [break] => { $ crate :: SyntaxKind :: BREAK_KW } ; [const] => { $ crate :: SyntaxKind :: CONST_KW } ; [continue] => { $ crate :: SyntaxKind :: CONTINUE_KW } ; [crate] => { $ crate :: SyntaxKind :: CRATE_KW } ; [do] => { $ crate :: SyntaxKind :: DO_KW } ; [dyn] => { $ crate :: SyntaxKind :: DYN_KW } ; [else] => { $ crate :: SyntaxKind :: ELSE_KW } ; [enum] => { $ crate :: SyntaxKind :: ENUM_KW } ; [extern] => { $ crate :: SyntaxKind :: EXTERN_KW } ; [false] => { $ crate :: SyntaxKind :: FALSE_KW } ; [final] => { $ crate :: SyntaxKind :: FINAL_KW } ; [fn] => { $ crate :: SyntaxKind :: FN_KW } ; [for] => { $ crate :: SyntaxKind :: FOR_KW } ; [gen] => { $ crate :: SyntaxKind :: GEN_KW } ; [if] => { $ crate :: SyntaxKind :: IF_KW } ; [impl] => { $ crate :: SyntaxKind :: IMPL_KW } ; [in] => { $ crate :: SyntaxKind :: IN_KW } ; [let] => { $ crate :: SyntaxKind :: LET_KW } ; [loop] => { $ crate :: SyntaxKind :: LOOP_KW } ; [macro] => { $ crate :: SyntaxKind :: MACRO_KW } ; [match] => { $ crate :: SyntaxKind :: MATCH_KW } ; [mod] => { $ crate :: SyntaxKind :: MOD_KW } ; [move] => { $ crate :: SyntaxKind :: MOVE_KW } ; [mut] => { $ crate :: SyntaxKind :: MUT_KW } ; [override] => { $ crate :: SyntaxKind :: OVERRIDE_KW } ; [priv] => { $ crate :: SyntaxKind :: PRIV_KW } ; [pub] => { $ crate :: SyntaxKind :: PUB_KW } ; [ref] => { $ crate :: SyntaxKind :: REF_KW } ; [return] => { $ crate :: SyntaxKind :: RETURN_KW } ; [self] => { $ crate :: SyntaxKind :: SELF_KW } ; [static] => { $ crate :: SyntaxKind :: STATIC_KW } ; [struct] => { $ crate :: SyntaxKind :: STRUCT_KW } ; [super] => { $ crate :: SyntaxKind :: SUPER_KW } ; [trait] => { $ crate :: SyntaxKind :: TRAIT_KW } ; [true] => { $ crate :: SyntaxKind :: TRUE_KW } ; [try] => { $ crate :: SyntaxKind :: TRY_KW } ; [type] => { $ crate :: SyntaxKind :: TYPE_KW } ; [typeof] => { $ crate :: SyntaxKind :: TYPEOF_KW } ; [unsafe] => { $ crate :: SyntaxKind :: UNSAFE_KW } ; [unsized] => { $ crate :: SyntaxKind :: UNSIZED_KW } ; [use] => { $ crate :: SyntaxKind :: USE_KW } ; [virtual] => { $ crate :: SyntaxKind :: VIRTUAL_KW } ; [where] => { $ crate :: SyntaxKind :: WHERE_KW } ; [while] => { $ crate :: SyntaxKind :: WHILE_KW } ; [yield] => { $ crate :: SyntaxKind :: YIELD_KW } ; [asm] => { $ crate :: SyntaxKind :: ASM_KW } ; [auto] => { $ crate :: SyntaxKind :: AUTO_KW } ; [builtin] => { $ crate :: SyntaxKind :: BUILTIN_KW } ; [default] => { $ crate :: SyntaxKind :: DEFAULT_KW } ; [format_args] => { $ crate :: SyntaxKind :: FORMAT_ARGS_KW } ; [macro_rules] => { $ crate :: SyntaxKind :: MACRO_RULES_KW } ; [offset_of] => { $ crate :: SyntaxKind :: OFFSET_OF_KW } ; [raw] => { $ crate :: SyntaxKind :: RAW_KW } ; [union] => { $ crate :: SyntaxKind :: UNION_KW } ; [yeet] => { $ crate :: SyntaxKind :: YEET_KW } ; [lifetime_ident] => { $ crate :: SyntaxKind :: LIFETIME_IDENT } ; [int_number] => { $ crate :: SyntaxKind :: INT_NUMBER } ; [ident] => { $ crate :: SyntaxKind :: IDENT } ; [string] => { $ crate :: SyntaxKind :: STRING } ; [shebang] => { $ crate :: SyntaxKind :: SHEBANG } ; }
+macro_rules ! T { [$] => { $ crate :: SyntaxKind :: DOLLAR } ; [;] => { $ crate :: SyntaxKind :: SEMICOLON } ; [,] => { $ crate :: SyntaxKind :: COMMA } ; ['('] => { $ crate :: SyntaxKind :: L_PAREN } ; [')'] => { $ crate :: SyntaxKind :: R_PAREN } ; ['{'] => { $ crate :: SyntaxKind :: L_CURLY } ; ['}'] => { $ crate :: SyntaxKind :: R_CURLY } ; ['['] => { $ crate :: SyntaxKind :: L_BRACK } ; [']'] => { $ crate :: SyntaxKind :: R_BRACK } ; [<] => { $ crate :: SyntaxKind :: L_ANGLE } ; [>] => { $ crate :: SyntaxKind :: R_ANGLE } ; [@] => { $ crate :: SyntaxKind :: AT } ; [#] => { $ crate :: SyntaxKind :: POUND } ; [~] => { $ crate :: SyntaxKind :: TILDE } ; [?] => { $ crate :: SyntaxKind :: QUESTION } ; [&] => { $ crate :: SyntaxKind :: AMP } ; [|] => { $ crate :: SyntaxKind :: PIPE } ; [+] => { $ crate :: SyntaxKind :: PLUS } ; [*] => { $ crate :: SyntaxKind :: STAR } ; [/] => { $ crate :: SyntaxKind :: SLASH } ; [^] => { $ crate :: SyntaxKind :: CARET } ; [%] => { $ crate :: SyntaxKind :: PERCENT } ; [_] => { $ crate :: SyntaxKind :: UNDERSCORE } ; [.] => { $ crate :: SyntaxKind :: DOT } ; [..] => { $ crate :: SyntaxKind :: DOT2 } ; [...] => { $ crate :: SyntaxKind :: DOT3 } ; [..=] => { $ crate :: SyntaxKind :: DOT2EQ } ; [:] => { $ crate :: SyntaxKind :: COLON } ; [::] => { $ crate :: SyntaxKind :: COLON2 } ; [=] => { $ crate :: SyntaxKind :: EQ } ; [==] => { $ crate :: SyntaxKind :: EQ2 } ; [=>] => { $ crate :: SyntaxKind :: FAT_ARROW } ; [!] => { $ crate :: SyntaxKind :: BANG } ; [!=] => { $ crate :: SyntaxKind :: NEQ } ; [-] => { $ crate :: SyntaxKind :: MINUS } ; [->] => { $ crate :: SyntaxKind :: THIN_ARROW } ; [<=] => { $ crate :: SyntaxKind :: LTEQ } ; [>=] => { $ crate :: SyntaxKind :: GTEQ } ; [+=] => { $ crate :: SyntaxKind :: PLUSEQ } ; [-=] => { $ crate :: SyntaxKind :: MINUSEQ } ; [|=] => { $ crate :: SyntaxKind :: PIPEEQ } ; [&=] => { $ crate :: SyntaxKind :: AMPEQ } ; [^=] => { $ crate :: SyntaxKind :: CARETEQ } ; [/=] => { $ crate :: SyntaxKind :: SLASHEQ } ; [*=] => { $ crate :: SyntaxKind :: STAREQ } ; [%=] => { $ crate :: SyntaxKind :: PERCENTEQ } ; [&&] => { $ crate :: SyntaxKind :: AMP2 } ; [||] => { $ crate :: SyntaxKind :: PIPE2 } ; [<<] => { $ crate :: SyntaxKind :: SHL } ; [>>] => { $ crate :: SyntaxKind :: SHR } ; [<<=] => { $ crate :: SyntaxKind :: SHLEQ } ; [>>=] => { $ crate :: SyntaxKind :: SHREQ } ; [Self] => { $ crate :: SyntaxKind :: SELF_TYPE_KW } ; [abstract] => { $ crate :: SyntaxKind :: ABSTRACT_KW } ; [as] => { $ crate :: SyntaxKind :: AS_KW } ; [become] => { $ crate :: SyntaxKind :: BECOME_KW } ; [box] => { $ crate :: SyntaxKind :: BOX_KW } ; [break] => { $ crate :: SyntaxKind :: BREAK_KW } ; [const] => { $ crate :: SyntaxKind :: CONST_KW } ; [continue] => { $ crate :: SyntaxKind :: CONTINUE_KW } ; [crate] => { $ crate :: SyntaxKind :: CRATE_KW } ; [do] => { $ crate :: SyntaxKind :: DO_KW } ; [else] => { $ crate :: SyntaxKind :: ELSE_KW } ; [enum] => { $ crate :: SyntaxKind :: ENUM_KW } ; [extern] => { $ crate :: SyntaxKind :: EXTERN_KW } ; [false] => { $ crate :: SyntaxKind :: FALSE_KW } ; [final] => { $ crate :: SyntaxKind :: FINAL_KW } ; [fn] => { $ crate :: SyntaxKind :: FN_KW } ; [for] => { $ crate :: SyntaxKind :: FOR_KW } ; [if] => { $ crate :: SyntaxKind :: IF_KW } ; [impl] => { $ crate :: SyntaxKind :: IMPL_KW } ; [in] => { $ crate :: SyntaxKind :: IN_KW } ; [let] => { $ crate :: SyntaxKind :: LET_KW } ; [loop] => { $ crate :: SyntaxKind :: LOOP_KW } ; [macro] => { $ crate :: SyntaxKind :: MACRO_KW } ; [match] => { $ crate :: SyntaxKind :: MATCH_KW } ; [mod] => { $ crate :: SyntaxKind :: MOD_KW } ; [move] => { $ crate :: SyntaxKind :: MOVE_KW } ; [mut] => { $ crate :: SyntaxKind :: MUT_KW } ; [override] => { $ crate :: SyntaxKind :: OVERRIDE_KW } ; [priv] => { $ crate :: SyntaxKind :: PRIV_KW } ; [pub] => { $ crate :: SyntaxKind :: PUB_KW } ; [ref] => { $ crate :: SyntaxKind :: REF_KW } ; [return] => { $ crate :: SyntaxKind :: RETURN_KW } ; [self] => { $ crate :: SyntaxKind :: SELF_KW } ; [static] => { $ crate :: SyntaxKind :: STATIC_KW } ; [struct] => { $ crate :: SyntaxKind :: STRUCT_KW } ; [super] => { $ crate :: SyntaxKind :: SUPER_KW } ; [trait] => { $ crate :: SyntaxKind :: TRAIT_KW } ; [true] => { $ crate :: SyntaxKind :: TRUE_KW } ; [type] => { $ crate :: SyntaxKind :: TYPE_KW } ; [typeof] => { $ crate :: SyntaxKind :: TYPEOF_KW } ; [unsafe] => { $ crate :: SyntaxKind :: UNSAFE_KW } ; [unsized] => { $ crate :: SyntaxKind :: UNSIZED_KW } ; [use] => { $ crate :: SyntaxKind :: USE_KW } ; [virtual] => { $ crate :: SyntaxKind :: VIRTUAL_KW } ; [where] => { $ crate :: SyntaxKind :: WHERE_KW } ; [while] => { $ crate :: SyntaxKind :: WHILE_KW } ; [yield] => { $ crate :: SyntaxKind :: YIELD_KW } ; [asm] => { $ crate :: SyntaxKind :: ASM_KW } ; [auto] => { $ crate :: SyntaxKind :: AUTO_KW } ; [builtin] => { $ crate :: SyntaxKind :: BUILTIN_KW } ; [default] => { $ crate :: SyntaxKind :: DEFAULT_KW } ; [dyn] => { $ crate :: SyntaxKind :: DYN_KW } ; [format_args] => { $ crate :: SyntaxKind :: FORMAT_ARGS_KW } ; [macro_rules] => { $ crate :: SyntaxKind :: MACRO_RULES_KW } ; [offset_of] => { $ crate :: SyntaxKind :: OFFSET_OF_KW } ; [raw] => { $ crate :: SyntaxKind :: RAW_KW } ; [union] => { $ crate :: SyntaxKind :: UNION_KW } ; [yeet] => { $ crate :: SyntaxKind :: YEET_KW } ; [async] => { $ crate :: SyntaxKind :: ASYNC_KW } ; [await] => { $ crate :: SyntaxKind :: AWAIT_KW } ; [dyn] => { $ crate :: SyntaxKind :: DYN_KW } ; [gen] => { $ crate :: SyntaxKind :: GEN_KW } ; [try] => { $ crate :: SyntaxKind :: TRY_KW } ; [lifetime_ident] => { $ crate :: SyntaxKind :: LIFETIME_IDENT } ; [int_number] => { $ crate :: SyntaxKind :: INT_NUMBER } ; [ident] => { $ crate :: SyntaxKind :: IDENT } ; [string] => { $ crate :: SyntaxKind :: STRING } ; [shebang] => { $ crate :: SyntaxKind :: SHEBANG } ; }
diff --git a/crates/parser/test_data/generated/runner.rs b/crates/parser/test_data/generated/runner.rs
index 1907f03b44..9ce5a2ae74 100644
--- a/crates/parser/test_data/generated/runner.rs
+++ b/crates/parser/test_data/generated/runner.rs
@@ -37,10 +37,6 @@ mod ok {
#[test]
fn assoc_type_eq() { run_and_expect_no_errors("test_data/parser/inline/ok/assoc_type_eq.rs"); }
#[test]
- fn associated_return_type_bounds() {
- run_and_expect_no_errors("test_data/parser/inline/ok/associated_return_type_bounds.rs");
- }
- #[test]
fn associated_type_bounds() {
run_and_expect_no_errors("test_data/parser/inline/ok/associated_type_bounds.rs");
}
@@ -519,6 +515,16 @@ mod ok {
#[test]
fn return_expr() { run_and_expect_no_errors("test_data/parser/inline/ok/return_expr.rs"); }
#[test]
+ fn return_type_syntax_assoc_type_bound() {
+ run_and_expect_no_errors(
+ "test_data/parser/inline/ok/return_type_syntax_assoc_type_bound.rs",
+ );
+ }
+ #[test]
+ fn return_type_syntax_in_path() {
+ run_and_expect_no_errors("test_data/parser/inline/ok/return_type_syntax_in_path.rs");
+ }
+ #[test]
fn self_param() { run_and_expect_no_errors("test_data/parser/inline/ok/self_param.rs"); }
#[test]
fn self_param_outer_attr() {
diff --git a/crates/parser/test_data/parser/inline/ok/associated_return_type_bounds.rs b/crates/parser/test_data/parser/inline/ok/associated_return_type_bounds.rs
deleted file mode 100644
index 42029ac592..0000000000
--- a/crates/parser/test_data/parser/inline/ok/associated_return_type_bounds.rs
+++ /dev/null
@@ -1 +0,0 @@
-fn foo<T: Foo<foo(): Send, bar(i32): Send, baz(i32, i32): Send>>() {}
diff --git a/crates/parser/test_data/parser/inline/ok/return_type_syntax_assoc_type_bound.rast b/crates/parser/test_data/parser/inline/ok/return_type_syntax_assoc_type_bound.rast
new file mode 100644
index 0000000000..30e0e73bbd
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/ok/return_type_syntax_assoc_type_bound.rast
@@ -0,0 +1,49 @@
+SOURCE_FILE
+ FN
+ FN_KW "fn"
+ WHITESPACE " "
+ NAME
+ IDENT "foo"
+ GENERIC_PARAM_LIST
+ L_ANGLE "<"
+ TYPE_PARAM
+ NAME
+ IDENT "T"
+ COLON ":"
+ WHITESPACE " "
+ TYPE_BOUND_LIST
+ TYPE_BOUND
+ PATH_TYPE
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "Trait"
+ GENERIC_ARG_LIST
+ L_ANGLE "<"
+ ASSOC_TYPE_ARG
+ NAME_REF
+ IDENT "method"
+ RETURN_TYPE_SYNTAX
+ L_PAREN "("
+ DOT2 ".."
+ R_PAREN ")"
+ COLON ":"
+ WHITESPACE " "
+ TYPE_BOUND_LIST
+ TYPE_BOUND
+ PATH_TYPE
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "Send"
+ R_ANGLE ">"
+ R_ANGLE ">"
+ PARAM_LIST
+ L_PAREN "("
+ R_PAREN ")"
+ WHITESPACE " "
+ BLOCK_EXPR
+ STMT_LIST
+ L_CURLY "{"
+ R_CURLY "}"
+ WHITESPACE "\n"
diff --git a/crates/parser/test_data/parser/inline/ok/return_type_syntax_assoc_type_bound.rs b/crates/parser/test_data/parser/inline/ok/return_type_syntax_assoc_type_bound.rs
new file mode 100644
index 0000000000..8a4cf4c3a0
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/ok/return_type_syntax_assoc_type_bound.rs
@@ -0,0 +1 @@
+fn foo<T: Trait<method(..): Send>>() {}
diff --git a/crates/parser/test_data/parser/inline/ok/return_type_syntax_in_path.rast b/crates/parser/test_data/parser/inline/ok/return_type_syntax_in_path.rast
new file mode 100644
index 0000000000..501dccd79d
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/ok/return_type_syntax_in_path.rast
@@ -0,0 +1,50 @@
+SOURCE_FILE
+ FN
+ FN_KW "fn"
+ WHITESPACE " "
+ NAME
+ IDENT "foo"
+ GENERIC_PARAM_LIST
+ L_ANGLE "<"
+ TYPE_PARAM
+ NAME
+ IDENT "T"
+ R_ANGLE ">"
+ PARAM_LIST
+ L_PAREN "("
+ R_PAREN ")"
+ WHITESPACE "\n"
+ WHERE_CLAUSE
+ WHERE_KW "where"
+ WHITESPACE "\n "
+ WHERE_PRED
+ PATH_TYPE
+ PATH
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "T"
+ COLON2 "::"
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "method"
+ RETURN_TYPE_SYNTAX
+ L_PAREN "("
+ DOT2 ".."
+ R_PAREN ")"
+ COLON ":"
+ WHITESPACE " "
+ TYPE_BOUND_LIST
+ TYPE_BOUND
+ PATH_TYPE
+ PATH
+ PATH_SEGMENT
+ NAME_REF
+ IDENT "Send"
+ COMMA ","
+ WHITESPACE "\n"
+ BLOCK_EXPR
+ STMT_LIST
+ L_CURLY "{"
+ R_CURLY "}"
+ WHITESPACE "\n"
diff --git a/crates/parser/test_data/parser/inline/ok/return_type_syntax_in_path.rs b/crates/parser/test_data/parser/inline/ok/return_type_syntax_in_path.rs
new file mode 100644
index 0000000000..a9b63fb01c
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/ok/return_type_syntax_in_path.rs
@@ -0,0 +1,4 @@
+fn foo<T>()
+where
+ T::method(..): Send,
+{}
diff --git a/crates/proc-macro-api/src/lib.rs b/crates/proc-macro-api/src/lib.rs
index 54c1475b8b..d50a3cdbf7 100644
--- a/crates/proc-macro-api/src/lib.rs
+++ b/crates/proc-macro-api/src/lib.rs
@@ -154,7 +154,8 @@ impl ProcMacro {
mixed_site: Span,
) -> Result<Result<tt::Subtree<Span>, PanicMessage>, ServerError> {
let version = self.process.version();
- let current_dir = env.get("CARGO_MANIFEST_DIR");
+ let current_dir =
+ env.get("CARGO_RUSTC_CURRENT_DIR").or_else(|| env.get("CARGO_MANIFEST_DIR"));
let mut span_data_table = SpanDataIndexMap::default();
let def_site = span_data_table.insert_full(def_site).0;
diff --git a/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs b/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs
index 552d99f51b..d508c19dd7 100644
--- a/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs
+++ b/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs
@@ -142,7 +142,13 @@ impl server::TokenStream for RaSpanServer {
stream.is_empty()
}
fn from_str(&mut self, src: &str) -> Self::TokenStream {
- Self::TokenStream::from_str(src, self.call_site).expect("cannot parse string")
+ Self::TokenStream::from_str(src, self.call_site).unwrap_or_else(|e| {
+ Self::TokenStream::from_str(
+ &format!("compile_error!(\"failed to parse str to token stream: {e}\")"),
+ self.call_site,
+ )
+ .unwrap()
+ })
}
fn to_string(&mut self, stream: &Self::TokenStream) -> String {
stream.to_string()
@@ -501,12 +507,17 @@ mod tests {
close: span,
kind: tt::DelimiterKind::Brace,
},
- token_trees: Box::new([]),
+ token_trees: Box::new([tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal {
+ kind: tt::LitKind::Str,
+ symbol: Symbol::intern("string"),
+ suffix: None,
+ span,
+ }))]),
}),
],
};
- assert_eq!(s.to_string(), "struct T {}");
+ assert_eq!(s.to_string(), "struct T {\"string\"}");
}
#[test]
diff --git a/crates/proc-macro-srv/src/server_impl/token_id.rs b/crates/proc-macro-srv/src/server_impl/token_id.rs
index 7720c6d83c..e478b1c853 100644
--- a/crates/proc-macro-srv/src/server_impl/token_id.rs
+++ b/crates/proc-macro-srv/src/server_impl/token_id.rs
@@ -131,7 +131,13 @@ impl server::TokenStream for TokenIdServer {
stream.is_empty()
}
fn from_str(&mut self, src: &str) -> Self::TokenStream {
- Self::TokenStream::from_str(src, self.call_site).expect("cannot parse string")
+ Self::TokenStream::from_str(src, self.call_site).unwrap_or_else(|e| {
+ Self::TokenStream::from_str(
+ &format!("compile_error!(\"failed to parse str to token stream: {e}\")"),
+ self.call_site,
+ )
+ .unwrap()
+ })
}
fn to_string(&mut self, stream: &Self::TokenStream) -> String {
stream.to_string()
diff --git a/crates/proc-macro-srv/src/server_impl/token_stream.rs b/crates/proc-macro-srv/src/server_impl/token_stream.rs
index 4d8d496418..dbcb5a3143 100644
--- a/crates/proc-macro-srv/src/server_impl/token_stream.rs
+++ b/crates/proc-macro-srv/src/server_impl/token_stream.rs
@@ -131,7 +131,7 @@ pub(super) mod token_stream {
call_site,
src,
)
- .ok_or("lexing error")?;
+ .ok_or_else(|| format!("lexing error: {src}"))?;
Ok(TokenStream::with_subtree(subtree))
}
diff --git a/crates/project-model/src/build_dependencies.rs b/crates/project-model/src/build_dependencies.rs
index e7a4b8f39f..dc71b13eee 100644
--- a/crates/project-model/src/build_dependencies.rs
+++ b/crates/project-model/src/build_dependencies.rs
@@ -19,8 +19,8 @@ use serde::Deserialize;
use toolchain::Tool;
use crate::{
- utf8_stdout, CargoConfig, CargoFeatures, CargoWorkspace, InvocationLocation,
- InvocationStrategy, ManifestPath, Package, Sysroot, TargetKind,
+ utf8_stdout, CargoConfig, CargoFeatures, CargoWorkspace, InvocationStrategy, ManifestPath,
+ Package, Sysroot, TargetKind,
};
/// Output of the build script and proc-macro building steps for a workspace.
@@ -63,10 +63,7 @@ impl WorkspaceBuildScripts {
progress: &dyn Fn(String),
sysroot: &Sysroot,
) -> io::Result<WorkspaceBuildScripts> {
- let current_dir = match &config.invocation_location {
- InvocationLocation::Root(root) if config.run_build_script_command.is_some() => root,
- _ => workspace.workspace_root(),
- };
+ let current_dir = workspace.workspace_root();
let allowed_features = workspace.workspace_features();
let cmd = Self::build_command(
@@ -85,25 +82,16 @@ impl WorkspaceBuildScripts {
config: &CargoConfig,
workspaces: &[&CargoWorkspace],
progress: &dyn Fn(String),
- workspace_root: &AbsPathBuf,
+ working_directory: &AbsPathBuf,
) -> io::Result<Vec<WorkspaceBuildScripts>> {
assert_eq!(config.invocation_strategy, InvocationStrategy::Once);
- let current_dir = match &config.invocation_location {
- InvocationLocation::Root(root) => root,
- InvocationLocation::Workspace => {
- return Err(io::Error::new(
- io::ErrorKind::Other,
- "Cannot run build scripts from workspace with invocation strategy `once`",
- ))
- }
- };
let cmd = Self::build_command(
config,
&Default::default(),
// This is not gonna be used anyways, so just construct a dummy here
- &ManifestPath::try_from(workspace_root.clone()).unwrap(),
- current_dir,
+ &ManifestPath::try_from(working_directory.clone()).unwrap(),
+ working_directory,
&Sysroot::empty(),
)?;
// NB: Cargo.toml could have been modified between `cargo metadata` and
diff --git a/crates/project-model/src/cargo_workspace.rs b/crates/project-model/src/cargo_workspace.rs
index 38eeedec62..7cc21bcf13 100644
--- a/crates/project-model/src/cargo_workspace.rs
+++ b/crates/project-model/src/cargo_workspace.rs
@@ -13,7 +13,7 @@ use serde_json::from_value;
use span::Edition;
use toolchain::Tool;
-use crate::{utf8_stdout, InvocationLocation, ManifestPath, Sysroot};
+use crate::{utf8_stdout, ManifestPath, Sysroot};
use crate::{CfgOverrides, InvocationStrategy};
/// [`CargoWorkspace`] represents the logical structure of, well, a Cargo
@@ -98,7 +98,6 @@ pub struct CargoConfig {
/// Extra env vars to set when invoking the cargo command
pub extra_env: FxHashMap<String, String>,
pub invocation_strategy: InvocationStrategy,
- pub invocation_location: InvocationLocation,
/// Optional path to use instead of `target` when building
pub target_dir: Option<Utf8PathBuf>,
}
@@ -242,6 +241,10 @@ impl TargetKind {
pub fn is_executable(self) -> bool {
matches!(self, TargetKind::Bin | TargetKind::Example)
}
+
+ pub fn is_proc_macro(self) -> bool {
+ matches!(self, TargetKind::Lib { is_proc_macro: true })
+ }
}
// Deserialize helper for the cargo metadata
@@ -252,6 +255,9 @@ struct PackageMetadata {
}
impl CargoWorkspace {
+ /// Fetches the metadata for the given `cargo_toml` manifest.
+ /// A successful result may contain another metadata error if the initial fetching failed but
+ /// the `--no-deps` retry succeeded.
pub fn fetch_metadata(
cargo_toml: &ManifestPath,
current_dir: &AbsPath,
@@ -259,7 +265,19 @@ impl CargoWorkspace {
sysroot: &Sysroot,
locked: bool,
progress: &dyn Fn(String),
- ) -> anyhow::Result<cargo_metadata::Metadata> {
+ ) -> anyhow::Result<(cargo_metadata::Metadata, Option<anyhow::Error>)> {
+ Self::fetch_metadata_(cargo_toml, current_dir, config, sysroot, locked, false, progress)
+ }
+
+ fn fetch_metadata_(
+ cargo_toml: &ManifestPath,
+ current_dir: &AbsPath,
+ config: &CargoConfig,
+ sysroot: &Sysroot,
+ locked: bool,
+ no_deps: bool,
+ progress: &dyn Fn(String),
+ ) -> anyhow::Result<(cargo_metadata::Metadata, Option<anyhow::Error>)> {
let targets = find_list_of_build_targets(config, cargo_toml, sysroot);
let cargo = sysroot.tool(Tool::Cargo);
@@ -314,6 +332,9 @@ impl CargoWorkspace {
if locked {
other_options.push("--locked".to_owned());
}
+ if no_deps {
+ other_options.push("--no-deps".to_owned());
+ }
meta.other_options(other_options);
// FIXME: Fetching metadata is a slow process, as it might require
@@ -321,19 +342,42 @@ impl CargoWorkspace {
// unclear whether cargo itself supports it.
progress("metadata".to_owned());
- (|| -> Result<cargo_metadata::Metadata, cargo_metadata::Error> {
+ (|| -> anyhow::Result<(_, _)> {
let output = meta.cargo_command().output()?;
if !output.status.success() {
- return Err(cargo_metadata::Error::CargoMetadata {
+ let error = cargo_metadata::Error::CargoMetadata {
stderr: String::from_utf8(output.stderr)?,
- });
+ }
+ .into();
+ if !no_deps {
+ // If we failed to fetch metadata with deps, try again without them.
+ // This makes r-a still work partially when offline.
+ if let Ok((metadata, _)) = Self::fetch_metadata_(
+ cargo_toml,
+ current_dir,
+ config,
+ sysroot,
+ locked,
+ true,
+ progress,
+ ) {
+ return Ok((metadata, Some(error)));
+ }
+ }
+ return Err(error);
}
let stdout = from_utf8(&output.stdout)?
.lines()
.find(|line| line.starts_with('{'))
.ok_or(cargo_metadata::Error::NoJson)?;
- cargo_metadata::MetadataCommand::parse(stdout)
+ Ok((cargo_metadata::MetadataCommand::parse(stdout)?, None))
})()
+ .map(|(metadata, error)| {
+ (
+ metadata,
+ error.map(|e| e.context(format!("Failed to run `{:?}`", meta.cargo_command()))),
+ )
+ })
.with_context(|| format!("Failed to run `{:?}`", meta.cargo_command()))
}
@@ -431,8 +475,7 @@ impl CargoWorkspace {
pkg_data.targets.push(tgt);
}
}
- let resolve = meta.resolve.expect("metadata executed with deps");
- for mut node in resolve.nodes {
+ for mut node in meta.resolve.map_or_else(Vec::new, |it| it.nodes) {
let &source = pkg_by_id.get(&node.id).unwrap();
node.deps.sort_by(|a, b| a.pkg.cmp(&b.pkg));
let dependencies = node
diff --git a/crates/project-model/src/env.rs b/crates/project-model/src/env.rs
index ac7246acc5..ff9d2035f6 100644
--- a/crates/project-model/src/env.rs
+++ b/crates/project-model/src/env.rs
@@ -3,7 +3,7 @@ use base_db::Env;
use rustc_hash::FxHashMap;
use toolchain::Tool;
-use crate::{utf8_stdout, ManifestPath, PackageData, Sysroot, TargetKind};
+use crate::{utf8_stdout, CargoWorkspace, ManifestPath, PackageData, Sysroot, TargetKind};
/// Recreates the compile-time environment variables that Cargo sets.
///
@@ -50,13 +50,23 @@ pub(crate) fn inject_cargo_env(env: &mut Env) {
env.set("CARGO", Tool::Cargo.path().to_string());
}
-pub(crate) fn inject_rustc_tool_env(env: &mut Env, cargo_name: &str, kind: TargetKind) {
+pub(crate) fn inject_rustc_tool_env(
+ env: &mut Env,
+ cargo: &CargoWorkspace,
+ cargo_name: &str,
+ kind: TargetKind,
+) {
_ = kind;
// FIXME
// if kind.is_executable() {
// env.set("CARGO_BIN_NAME", cargo_name);
// }
env.set("CARGO_CRATE_NAME", cargo_name.replace('-', "_"));
+ // NOTE: Technically we should set this for all crates, but that will worsen the deduplication
+ // logic so for now just keeping it proc-macros ought to be fine.
+ if kind.is_proc_macro() {
+ env.set("CARGO_RUSTC_CURRENT_DIR", cargo.manifest_path().parent().to_string());
+ }
}
pub(crate) fn cargo_config_env(
diff --git a/crates/project-model/src/lib.rs b/crates/project-model/src/lib.rs
index 4fa70508bb..b8ac55ed0d 100644
--- a/crates/project-model/src/lib.rs
+++ b/crates/project-model/src/lib.rs
@@ -186,20 +186,13 @@ fn utf8_stdout(mut cmd: Command) -> anyhow::Result<String> {
Ok(stdout.trim().to_owned())
}
-#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum InvocationStrategy {
Once,
#[default]
PerWorkspace,
}
-#[derive(Clone, Debug, Default, PartialEq, Eq)]
-pub enum InvocationLocation {
- Root(AbsPathBuf),
- #[default]
- Workspace,
-}
-
/// A set of cfg-overrides per crate.
#[derive(Default, Debug, Clone, Eq, PartialEq)]
pub struct CfgOverrides {
diff --git a/crates/project-model/src/project_json.rs b/crates/project-model/src/project_json.rs
index 7dea0c3839..a09c7a77ab 100644
--- a/crates/project-model/src/project_json.rs
+++ b/crates/project-model/src/project_json.rs
@@ -52,7 +52,7 @@
use base_db::{CrateDisplayName, CrateName};
use cfg::CfgAtom;
use paths::{AbsPath, AbsPathBuf, Utf8PathBuf};
-use rustc_hash::FxHashMap;
+use rustc_hash::{FxHashMap, FxHashSet};
use serde::{de, Deserialize, Serialize};
use span::Edition;
@@ -122,6 +122,25 @@ impl ProjectJson {
None => None,
};
+ let cfg = crate_data
+ .cfg_groups
+ .iter()
+ .flat_map(|cfg_extend| {
+ let cfg_group = data.cfg_groups.get(cfg_extend);
+ match cfg_group {
+ Some(cfg_group) => cfg_group.0.iter().cloned(),
+ None => {
+ tracing::error!(
+ "Unknown cfg group `{cfg_extend}` in crate `{}`",
+ crate_data.display_name.as_deref().unwrap_or("<unknown>"),
+ );
+ [].iter().cloned()
+ }
+ }
+ })
+ .chain(crate_data.cfg.0)
+ .collect();
+
Crate {
display_name: crate_data
.display_name
@@ -131,7 +150,7 @@ impl ProjectJson {
edition: crate_data.edition.into(),
version: crate_data.version.as_ref().map(ToString::to_string),
deps: crate_data.deps,
- cfg: crate_data.cfg,
+ cfg,
target: crate_data.target,
env: crate_data.env,
proc_macro_dylib_path: crate_data
@@ -306,11 +325,17 @@ pub enum RunnableKind {
pub struct ProjectJsonData {
sysroot: Option<Utf8PathBuf>,
sysroot_src: Option<Utf8PathBuf>,
+ #[serde(default)]
+ cfg_groups: FxHashMap<String, CfgList>,
crates: Vec<CrateData>,
#[serde(default)]
runnables: Vec<RunnableData>,
}
+#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, Default)]
+#[serde(transparent)]
+struct CfgList(#[serde(with = "cfg_")] Vec<CfgAtom>);
+
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
struct CrateData {
display_name: Option<String>,
@@ -320,8 +345,9 @@ struct CrateData {
version: Option<semver::Version>,
deps: Vec<Dep>,
#[serde(default)]
- #[serde(with = "cfg_")]
- cfg: Vec<CfgAtom>,
+ cfg_groups: FxHashSet<String>,
+ #[serde(default)]
+ cfg: CfgList,
target: Option<String>,
#[serde(default)]
env: FxHashMap<String, String>,
diff --git a/crates/project-model/src/sysroot.rs b/crates/project-model/src/sysroot.rs
index 419fac3f41..19f4c35b5a 100644
--- a/crates/project-model/src/sysroot.rs
+++ b/crates/project-model/src/sysroot.rs
@@ -325,7 +325,7 @@ impl Sysroot {
"nightly".to_owned(),
);
- let mut res = match CargoWorkspace::fetch_metadata(
+ let (mut res, _) = match CargoWorkspace::fetch_metadata(
&library_manifest,
sysroot_src_dir,
&cargo_config,
@@ -372,18 +372,19 @@ impl Sysroot {
.flatten()
};
- let resolve = res.resolve.as_mut().expect("metadata executed with deps");
- resolve.nodes.retain_mut(|node| {
- // Replace `rustc-std-workspace` crate with the actual one in the dependency list
- node.deps.iter_mut().for_each(|dep| {
- let real_pkg = patches.clone().find(|((_, fake_id), _)| *fake_id == dep.pkg);
- if let Some((_, real)) = real_pkg {
- dep.pkg = real;
- }
+ if let Some(resolve) = res.resolve.as_mut() {
+ resolve.nodes.retain_mut(|node| {
+ // Replace `rustc-std-workspace` crate with the actual one in the dependency list
+ node.deps.iter_mut().for_each(|dep| {
+ let real_pkg = patches.clone().find(|((_, fake_id), _)| *fake_id == dep.pkg);
+ if let Some((_, real)) = real_pkg {
+ dep.pkg = real;
+ }
+ });
+ // Remove this node if it's a fake one
+ !patches.clone().any(|((_, fake), _)| fake == node.id)
});
- // Remove this node if it's a fake one
- !patches.clone().any(|((_, fake), _)| fake == node.id)
- });
+ }
// Remove the fake ones from the package list
patches.map(|((idx, _), _)| idx).sorted().rev().for_each(|idx| {
res.packages.remove(idx);
diff --git a/crates/project-model/src/tests.rs b/crates/project-model/src/tests.rs
index e3bc81e196..f540bb94c1 100644
--- a/crates/project-model/src/tests.rs
+++ b/crates/project-model/src/tests.rs
@@ -34,6 +34,7 @@ fn load_cargo_with_overrides(
build_scripts: WorkspaceBuildScripts::default(),
rustc: Err(None),
cargo_config_extra_env: Default::default(),
+ error: None,
},
cfg_overrides,
sysroot: Sysroot::empty(),
@@ -58,6 +59,7 @@ fn load_cargo_with_fake_sysroot(
build_scripts: WorkspaceBuildScripts::default(),
rustc: Err(None),
cargo_config_extra_env: Default::default(),
+ error: None,
},
sysroot: get_fake_sysroot(),
rustc_cfg: Vec::new(),
@@ -234,6 +236,12 @@ fn rust_project_hello_world_project_model() {
}
#[test]
+fn rust_project_cfg_groups() {
+ let (crate_graph, _proc_macros) = load_rust_project("cfg-groups.json");
+ check_crate_graph(crate_graph, expect_file!["../test_data/output/rust_project_cfg_groups.txt"]);
+}
+
+#[test]
fn rust_project_is_proc_macro_has_proc_macro_dep() {
let (crate_graph, _proc_macros) = load_rust_project("is-proc-macro-project.json");
// Since the project only defines one crate (outside the sysroot crates),
@@ -294,6 +302,7 @@ fn smoke_test_real_sysroot_cargo() {
build_scripts: WorkspaceBuildScripts::default(),
rustc: Err(None),
cargo_config_extra_env: Default::default(),
+ error: None,
},
sysroot,
rustc_cfg: Vec::new(),
diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs
index 5620dfade2..7834238ace 100644
--- a/crates/project-model/src/workspace.rs
+++ b/crates/project-model/src/workspace.rs
@@ -69,6 +69,8 @@ pub enum ProjectWorkspaceKind {
Cargo {
/// The workspace as returned by `cargo metadata`.
cargo: CargoWorkspace,
+ /// Additional `cargo metadata` error. (only populated if retried fetching via `--no-deps` succeeded).
+ error: Option<Arc<anyhow::Error>>,
/// The build script results for the workspace.
build_scripts: WorkspaceBuildScripts,
/// The rustc workspace loaded for this workspace. An `Err(None)` means loading has been
@@ -93,7 +95,7 @@ pub enum ProjectWorkspaceKind {
/// The file in question.
file: ManifestPath,
/// Is this file a cargo script file?
- cargo: Option<(CargoWorkspace, WorkspaceBuildScripts)>,
+ cargo: Option<(CargoWorkspace, WorkspaceBuildScripts, Option<Arc<anyhow::Error>>)>,
/// Environment variables set in the `.cargo/config` file.
cargo_config_extra_env: FxHashMap<String, String>,
},
@@ -106,6 +108,7 @@ impl fmt::Debug for ProjectWorkspace {
match kind {
ProjectWorkspaceKind::Cargo {
cargo,
+ error: _,
build_scripts: _,
rustc,
cargo_config_extra_env,
@@ -256,7 +259,7 @@ impl ProjectWorkspace {
false,
progress,
) {
- Ok(meta) => {
+ Ok((meta, _error)) => {
let workspace = CargoWorkspace::new(meta, cargo_toml.clone());
let buildscripts = WorkspaceBuildScripts::rustc_crates(
&workspace,
@@ -301,7 +304,7 @@ impl ProjectWorkspace {
tracing::error!(%e, "failed fetching data layout for {cargo_toml:?} workspace");
}
- let meta = CargoWorkspace::fetch_metadata(
+ let (meta, error) = CargoWorkspace::fetch_metadata(
cargo_toml,
cargo_toml.parent(),
config,
@@ -324,6 +327,7 @@ impl ProjectWorkspace {
build_scripts: WorkspaceBuildScripts::default(),
rustc,
cargo_config_extra_env,
+ error: error.map(Arc::new),
},
sysroot,
rustc_cfg,
@@ -404,10 +408,11 @@ impl ProjectWorkspace {
let cargo_script =
CargoWorkspace::fetch_metadata(detached_file, dir, config, &sysroot, false, &|_| ())
.ok()
- .map(|ws| {
+ .map(|(ws, error)| {
(
CargoWorkspace::new(ws, detached_file.clone()),
WorkspaceBuildScripts::default(),
+ error.map(Arc::new),
)
});
@@ -440,15 +445,14 @@ impl ProjectWorkspace {
progress: &dyn Fn(String),
) -> anyhow::Result<WorkspaceBuildScripts> {
match &self.kind {
- ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _)), .. }
- | ProjectWorkspaceKind::Cargo { cargo, .. } => {
+ ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _, None)), .. }
+ | ProjectWorkspaceKind::Cargo { cargo, error: None, .. } => {
WorkspaceBuildScripts::run_for_workspace(config, cargo, progress, &self.sysroot)
.with_context(|| {
format!("Failed to run build scripts for {}", cargo.workspace_root())
})
}
- ProjectWorkspaceKind::DetachedFile { cargo: None, .. }
- | ProjectWorkspaceKind::Json { .. } => Ok(WorkspaceBuildScripts::default()),
+ _ => Ok(WorkspaceBuildScripts::default()),
}
}
@@ -458,7 +462,7 @@ impl ProjectWorkspace {
workspaces: &[ProjectWorkspace],
config: &CargoConfig,
progress: &dyn Fn(String),
- workspace_root: &AbsPathBuf,
+ working_directory: &AbsPathBuf,
) -> Vec<anyhow::Result<WorkspaceBuildScripts>> {
if matches!(config.invocation_strategy, InvocationStrategy::PerWorkspace)
|| config.run_build_script_command.is_none()
@@ -473,13 +477,16 @@ impl ProjectWorkspace {
_ => None,
})
.collect();
- let outputs =
- &mut match WorkspaceBuildScripts::run_once(config, &cargo_ws, progress, workspace_root)
- {
- Ok(it) => Ok(it.into_iter()),
- // io::Error is not Clone?
- Err(e) => Err(sync::Arc::new(e)),
- };
+ let outputs = &mut match WorkspaceBuildScripts::run_once(
+ config,
+ &cargo_ws,
+ progress,
+ working_directory,
+ ) {
+ Ok(it) => Ok(it.into_iter()),
+ // io::Error is not Clone?
+ Err(e) => Err(sync::Arc::new(e)),
+ };
workspaces
.iter()
@@ -498,7 +505,7 @@ impl ProjectWorkspace {
pub fn set_build_scripts(&mut self, bs: WorkspaceBuildScripts) {
match &mut self.kind {
ProjectWorkspaceKind::Cargo { build_scripts, .. }
- | ProjectWorkspaceKind::DetachedFile { cargo: Some((_, build_scripts)), .. } => {
+ | ProjectWorkspaceKind::DetachedFile { cargo: Some((_, build_scripts, _)), .. } => {
*build_scripts = bs
}
_ => assert_eq!(bs, WorkspaceBuildScripts::default()),
@@ -589,6 +596,7 @@ impl ProjectWorkspace {
rustc,
build_scripts,
cargo_config_extra_env: _,
+ error: _,
} => {
cargo
.packages()
@@ -644,7 +652,7 @@ impl ProjectWorkspace {
include: vec![file.to_path_buf()],
exclude: Vec::new(),
})
- .chain(cargo_script.iter().flat_map(|(cargo, build_scripts)| {
+ .chain(cargo_script.iter().flat_map(|(cargo, build_scripts, _)| {
cargo.packages().map(|pkg| {
let is_local = cargo[pkg].is_local;
let pkg_root = cargo[pkg].manifest.parent().to_path_buf();
@@ -699,7 +707,7 @@ impl ProjectWorkspace {
}
ProjectWorkspaceKind::DetachedFile { cargo: cargo_script, .. } => {
sysroot_package_len
- + cargo_script.as_ref().map_or(1, |(cargo, _)| cargo.packages().len())
+ + cargo_script.as_ref().map_or(1, |(cargo, _, _)| cargo.packages().len())
}
}
}
@@ -729,6 +737,7 @@ impl ProjectWorkspace {
rustc,
build_scripts,
cargo_config_extra_env: _,
+ error: _,
} => (
cargo_to_crate_graph(
load,
@@ -742,7 +751,7 @@ impl ProjectWorkspace {
sysroot,
),
ProjectWorkspaceKind::DetachedFile { file, cargo: cargo_script, .. } => (
- if let Some((cargo, build_scripts)) = cargo_script {
+ if let Some((cargo, build_scripts, _)) = cargo_script {
cargo_to_crate_graph(
&mut |path| load(path),
None,
@@ -791,12 +800,14 @@ impl ProjectWorkspace {
rustc,
cargo_config_extra_env,
build_scripts: _,
+ error: _,
},
ProjectWorkspaceKind::Cargo {
cargo: o_cargo,
rustc: o_rustc,
cargo_config_extra_env: o_cargo_config_extra_env,
build_scripts: _,
+ error: _,
},
) => {
cargo == o_cargo
@@ -809,12 +820,12 @@ impl ProjectWorkspace {
(
ProjectWorkspaceKind::DetachedFile {
file,
- cargo: Some((cargo_script, _)),
+ cargo: Some((cargo_script, _, _)),
cargo_config_extra_env,
},
ProjectWorkspaceKind::DetachedFile {
file: o_file,
- cargo: Some((o_cargo_script, _)),
+ cargo: Some((o_cargo_script, _, _)),
cargo_config_extra_env: o_cargo_config_extra_env,
},
) => {
@@ -1016,6 +1027,7 @@ fn cargo_to_crate_graph(
let crate_id = add_target_crate_root(
crate_graph,
proc_macros,
+ cargo,
pkg_data,
build_data,
cfg_options.clone(),
@@ -1235,6 +1247,7 @@ fn handle_rustc_crates(
let crate_id = add_target_crate_root(
crate_graph,
proc_macros,
+ rustc_workspace,
&rustc_workspace[pkg],
build_scripts.get_output(pkg),
cfg_options.clone(),
@@ -1294,6 +1307,7 @@ fn handle_rustc_crates(
fn add_target_crate_root(
crate_graph: &mut CrateGraph,
proc_macros: &mut ProcMacroPaths,
+ cargo: &CargoWorkspace,
pkg: &PackageData,
build_data: Option<&BuildScriptOutput>,
cfg_options: CfgOptions,
@@ -1327,7 +1341,7 @@ fn add_target_crate_root(
let mut env = Env::default();
inject_cargo_package_env(&mut env, pkg);
inject_cargo_env(&mut env);
- inject_rustc_tool_env(&mut env, cargo_name, kind);
+ inject_rustc_tool_env(&mut env, cargo, cargo_name, kind);
if let Some(envs) = build_data.map(|it| &it.envs) {
for (k, v) in envs {
diff --git a/crates/project-model/test_data/cfg-groups.json b/crates/project-model/test_data/cfg-groups.json
new file mode 100644
index 0000000000..29f83afd9d
--- /dev/null
+++ b/crates/project-model/test_data/cfg-groups.json
@@ -0,0 +1,26 @@
+{
+ "sysroot_src": null,
+ "cfg_groups": {
+ "group1": ["group1_cfg=\"some_config\"", "group1_other_cfg=\"other_config\""],
+ "group2": ["group2_cfg=\"yet_another_config\""]
+ },
+ "crates": [
+ {
+ "display_name": "hello_world",
+ "root_module": "$ROOT$src/lib.rs",
+ "edition": "2018",
+ "cfg_groups": ["group1", "group2"],
+ "deps": [],
+ "is_workspace_member": true
+ },
+ {
+ "display_name": "other_crate",
+ "root_module": "$ROOT$src/lib.rs",
+ "edition": "2018",
+ "cfg_groups": ["group2"],
+ "cfg": ["group2_cfg=\"fourth_config\"", "unrelated_cfg"],
+ "deps": [],
+ "is_workspace_member": true
+ }
+ ]
+}
diff --git a/crates/project-model/test_data/output/rust_project_cfg_groups.txt b/crates/project-model/test_data/output/rust_project_cfg_groups.txt
new file mode 100644
index 0000000000..8261e5a2d9
--- /dev/null
+++ b/crates/project-model/test_data/output/rust_project_cfg_groups.txt
@@ -0,0 +1,545 @@
+{
+ 0: CrateData {
+ root_file_id: FileId(
+ 1,
+ ),
+ edition: Edition2021,
+ version: None,
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "alloc",
+ ),
+ canonical_name: "alloc",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "miri",
+ ],
+ ),
+ potential_cfg_options: None,
+ env: Env {
+ entries: {},
+ },
+ dependencies: [
+ Dependency {
+ crate_id: Idx::<CrateData>(1),
+ name: CrateName(
+ "core",
+ ),
+ prelude: true,
+ sysroot: false,
+ },
+ ],
+ origin: Lang(
+ Alloc,
+ ),
+ is_proc_macro: false,
+ },
+ 1: CrateData {
+ root_file_id: FileId(
+ 2,
+ ),
+ edition: Edition2021,
+ version: None,
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "core",
+ ),
+ canonical_name: "core",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "miri",
+ ],
+ ),
+ potential_cfg_options: None,
+ env: Env {
+ entries: {},
+ },
+ dependencies: [],
+ origin: Lang(
+ Core,
+ ),
+ is_proc_macro: false,
+ },
+ 2: CrateData {
+ root_file_id: FileId(
+ 3,
+ ),
+ edition: Edition2021,
+ version: None,
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "panic_abort",
+ ),
+ canonical_name: "panic_abort",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "miri",
+ ],
+ ),
+ potential_cfg_options: None,
+ env: Env {
+ entries: {},
+ },
+ dependencies: [],
+ origin: Lang(
+ Other,
+ ),
+ is_proc_macro: false,
+ },
+ 3: CrateData {
+ root_file_id: FileId(
+ 4,
+ ),
+ edition: Edition2021,
+ version: None,
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "panic_unwind",
+ ),
+ canonical_name: "panic_unwind",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "miri",
+ ],
+ ),
+ potential_cfg_options: None,
+ env: Env {
+ entries: {},
+ },
+ dependencies: [],
+ origin: Lang(
+ Other,
+ ),
+ is_proc_macro: false,
+ },
+ 4: CrateData {
+ root_file_id: FileId(
+ 5,
+ ),
+ edition: Edition2021,
+ version: None,
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "proc_macro",
+ ),
+ canonical_name: "proc_macro",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "miri",
+ ],
+ ),
+ potential_cfg_options: None,
+ env: Env {
+ entries: {},
+ },
+ dependencies: [
+ Dependency {
+ crate_id: Idx::<CrateData>(6),
+ name: CrateName(
+ "std",
+ ),
+ prelude: true,
+ sysroot: false,
+ },
+ Dependency {
+ crate_id: Idx::<CrateData>(1),
+ name: CrateName(
+ "core",
+ ),
+ prelude: true,
+ sysroot: false,
+ },
+ ],
+ origin: Lang(
+ ProcMacro,
+ ),
+ is_proc_macro: false,
+ },
+ 5: CrateData {
+ root_file_id: FileId(
+ 6,
+ ),
+ edition: Edition2021,
+ version: None,
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "profiler_builtins",
+ ),
+ canonical_name: "profiler_builtins",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "miri",
+ ],
+ ),
+ potential_cfg_options: None,
+ env: Env {
+ entries: {},
+ },
+ dependencies: [],
+ origin: Lang(
+ Other,
+ ),
+ is_proc_macro: false,
+ },
+ 6: CrateData {
+ root_file_id: FileId(
+ 7,
+ ),
+ edition: Edition2021,
+ version: None,
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "std",
+ ),
+ canonical_name: "std",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "miri",
+ ],
+ ),
+ potential_cfg_options: None,
+ env: Env {
+ entries: {},
+ },
+ dependencies: [
+ Dependency {
+ crate_id: Idx::<CrateData>(0),
+ name: CrateName(
+ "alloc",
+ ),
+ prelude: true,
+ sysroot: false,
+ },
+ Dependency {
+ crate_id: Idx::<CrateData>(3),
+ name: CrateName(
+ "panic_unwind",
+ ),
+ prelude: true,
+ sysroot: false,
+ },
+ Dependency {
+ crate_id: Idx::<CrateData>(2),
+ name: CrateName(
+ "panic_abort",
+ ),
+ prelude: true,
+ sysroot: false,
+ },
+ Dependency {
+ crate_id: Idx::<CrateData>(1),
+ name: CrateName(
+ "core",
+ ),
+ prelude: true,
+ sysroot: false,
+ },
+ Dependency {
+ crate_id: Idx::<CrateData>(5),
+ name: CrateName(
+ "profiler_builtins",
+ ),
+ prelude: true,
+ sysroot: false,
+ },
+ Dependency {
+ crate_id: Idx::<CrateData>(9),
+ name: CrateName(
+ "unwind",
+ ),
+ prelude: true,
+ sysroot: false,
+ },
+ Dependency {
+ crate_id: Idx::<CrateData>(7),
+ name: CrateName(
+ "std_detect",
+ ),
+ prelude: true,
+ sysroot: false,
+ },
+ Dependency {
+ crate_id: Idx::<CrateData>(8),
+ name: CrateName(
+ "test",
+ ),
+ prelude: true,
+ sysroot: false,
+ },
+ ],
+ origin: Lang(
+ Std,
+ ),
+ is_proc_macro: false,
+ },
+ 7: CrateData {
+ root_file_id: FileId(
+ 8,
+ ),
+ edition: Edition2021,
+ version: None,
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "std_detect",
+ ),
+ canonical_name: "std_detect",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "miri",
+ ],
+ ),
+ potential_cfg_options: None,
+ env: Env {
+ entries: {},
+ },
+ dependencies: [],
+ origin: Lang(
+ Other,
+ ),
+ is_proc_macro: false,
+ },
+ 8: CrateData {
+ root_file_id: FileId(
+ 9,
+ ),
+ edition: Edition2021,
+ version: None,
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "test",
+ ),
+ canonical_name: "test",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "miri",
+ ],
+ ),
+ potential_cfg_options: None,
+ env: Env {
+ entries: {},
+ },
+ dependencies: [],
+ origin: Lang(
+ Test,
+ ),
+ is_proc_macro: false,
+ },
+ 9: CrateData {
+ root_file_id: FileId(
+ 10,
+ ),
+ edition: Edition2021,
+ version: None,
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "unwind",
+ ),
+ canonical_name: "unwind",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [
+ "debug_assertions",
+ "miri",
+ ],
+ ),
+ potential_cfg_options: None,
+ env: Env {
+ entries: {},
+ },
+ dependencies: [],
+ origin: Lang(
+ Other,
+ ),
+ is_proc_macro: false,
+ },
+ 10: CrateData {
+ root_file_id: FileId(
+ 11,
+ ),
+ edition: Edition2018,
+ version: None,
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "hello_world",
+ ),
+ canonical_name: "hello_world",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [
+ "group1_cfg=some_config",
+ "group1_other_cfg=other_config",
+ "group2_cfg=yet_another_config",
+ "rust_analyzer",
+ ],
+ ),
+ potential_cfg_options: None,
+ env: Env {
+ entries: {},
+ },
+ dependencies: [
+ Dependency {
+ crate_id: Idx::<CrateData>(1),
+ name: CrateName(
+ "core",
+ ),
+ prelude: true,
+ sysroot: true,
+ },
+ Dependency {
+ crate_id: Idx::<CrateData>(0),
+ name: CrateName(
+ "alloc",
+ ),
+ prelude: false,
+ sysroot: true,
+ },
+ Dependency {
+ crate_id: Idx::<CrateData>(6),
+ name: CrateName(
+ "std",
+ ),
+ prelude: true,
+ sysroot: true,
+ },
+ Dependency {
+ crate_id: Idx::<CrateData>(8),
+ name: CrateName(
+ "test",
+ ),
+ prelude: false,
+ sysroot: true,
+ },
+ Dependency {
+ crate_id: Idx::<CrateData>(4),
+ name: CrateName(
+ "proc_macro",
+ ),
+ prelude: false,
+ sysroot: true,
+ },
+ ],
+ origin: Local {
+ repo: None,
+ name: Some(
+ "hello_world",
+ ),
+ },
+ is_proc_macro: false,
+ },
+ 11: CrateData {
+ root_file_id: FileId(
+ 12,
+ ),
+ edition: Edition2018,
+ version: None,
+ display_name: Some(
+ CrateDisplayName {
+ crate_name: CrateName(
+ "other_crate",
+ ),
+ canonical_name: "other_crate",
+ },
+ ),
+ cfg_options: CfgOptions(
+ [
+ "group2_cfg=fourth_config",
+ "group2_cfg=yet_another_config",
+ "rust_analyzer",
+ "unrelated_cfg",
+ ],
+ ),
+ potential_cfg_options: None,
+ env: Env {
+ entries: {},
+ },
+ dependencies: [
+ Dependency {
+ crate_id: Idx::<CrateData>(1),
+ name: CrateName(
+ "core",
+ ),
+ prelude: true,
+ sysroot: true,
+ },
+ Dependency {
+ crate_id: Idx::<CrateData>(0),
+ name: CrateName(
+ "alloc",
+ ),
+ prelude: false,
+ sysroot: true,
+ },
+ Dependency {
+ crate_id: Idx::<CrateData>(6),
+ name: CrateName(
+ "std",
+ ),
+ prelude: true,
+ sysroot: true,
+ },
+ Dependency {
+ crate_id: Idx::<CrateData>(8),
+ name: CrateName(
+ "test",
+ ),
+ prelude: false,
+ sysroot: true,
+ },
+ Dependency {
+ crate_id: Idx::<CrateData>(4),
+ name: CrateName(
+ "proc_macro",
+ ),
+ prelude: false,
+ sysroot: true,
+ },
+ ],
+ origin: Local {
+ repo: None,
+ name: Some(
+ "other_crate",
+ ),
+ },
+ is_proc_macro: false,
+ },
+} \ No newline at end of file
diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs
index 42953d3b83..21b481c1fa 100644
--- a/crates/rust-analyzer/src/bin/main.rs
+++ b/crates/rust-analyzer/src/bin/main.rs
@@ -227,7 +227,7 @@ fn run_server() -> anyhow::Result<()> {
.filter(|workspaces| !workspaces.is_empty())
.unwrap_or_else(|| vec![root_path.clone()]);
let mut config =
- Config::new(root_path, capabilities, workspace_roots, visual_studio_code_version, None);
+ Config::new(root_path, capabilities, workspace_roots, visual_studio_code_version);
if let Some(json) = initialization_options {
let mut change = ConfigChange::default();
change.change_client_config(json);
diff --git a/crates/rust-analyzer/src/cli.rs b/crates/rust-analyzer/src/cli.rs
index 0bd6677b66..5eb6ff664f 100644
--- a/crates/rust-analyzer/src/cli.rs
+++ b/crates/rust-analyzer/src/cli.rs
@@ -21,7 +21,7 @@ use std::io::Read;
use anyhow::Result;
use hir::{Module, Name};
use hir_ty::db::HirDatabase;
-use ide::AnalysisHost;
+use ide::{AnalysisHost, Edition};
use itertools::Itertools;
use vfs::Vfs;
@@ -85,6 +85,6 @@ fn full_name_of_item(db: &dyn HirDatabase, module: Module, name: Name) -> String
.rev()
.filter_map(|it| it.name(db))
.chain(Some(name))
- .map(|it| it.display(db.upcast()).to_string())
+ .map(|it| it.display(db.upcast(), Edition::LATEST).to_string())
.join("::")
}
diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs
index 06f4ba815d..44e56645ba 100644
--- a/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -17,7 +17,7 @@ use hir_def::{
};
use hir_ty::{Interner, Substitution, TyExt, TypeFlags};
use ide::{
- Analysis, AnalysisHost, AnnotationConfig, DiagnosticsConfig, InlayFieldsToResolve,
+ Analysis, AnalysisHost, AnnotationConfig, DiagnosticsConfig, Edition, InlayFieldsToResolve,
InlayHintsConfig, LineCol, RootDatabase,
};
use ide_db::{
@@ -309,7 +309,7 @@ impl flags::AnalysisStats {
let mut fail = 0;
for &c in consts {
all += 1;
- let Err(error) = c.render_eval(db) else {
+ let Err(error) = c.render_eval(db, Edition::LATEST) else {
continue;
};
if verbosity.is_spammy() {
@@ -442,6 +442,7 @@ impl flags::AnalysisStats {
prefer_prelude: true,
prefer_absolute: false,
},
+ Edition::LATEST,
)
.unwrap();
syntax_hit_found |= trim(&original_text) == trim(&generated);
@@ -563,7 +564,7 @@ impl flags::AnalysisStats {
.rev()
.filter_map(|it| it.name(db))
.chain(Some(body.name(db).unwrap_or_else(Name::missing)))
- .map(|it| it.display(db).to_string())
+ .map(|it| it.display(db, Edition::LATEST).to_string())
.join("::");
println!("Mir body for {full_name} failed due {e:?}");
}
@@ -628,12 +629,14 @@ impl flags::AnalysisStats {
.filter_map(|it| it.name(db))
.rev()
.chain(Some(body_id.name(db).unwrap_or_else(Name::missing)))
- .map(|it| it.display(db).to_string()),
+ .map(|it| it.display(db, Edition::LATEST).to_string()),
)
.join("::")
};
if let Some(only_name) = self.only.as_deref() {
- if name.display(db).to_string() != only_name && full_name() != only_name {
+ if name.display(db, Edition::LATEST).to_string() != only_name
+ && full_name() != only_name
+ {
continue;
}
}
@@ -687,7 +690,10 @@ impl flags::AnalysisStats {
end.col,
));
} else {
- bar.println(format!("{}: Unknown type", name.display(db)));
+ bar.println(format!(
+ "{}: Unknown type",
+ name.display(db, Edition::LATEST)
+ ));
}
}
true
@@ -708,17 +714,20 @@ impl flags::AnalysisStats {
start.col,
end.line + 1,
end.col,
- ty.display(db)
+ ty.display(db, Edition::LATEST)
));
} else {
- bar.println(format!("unknown location: {}", ty.display(db)));
+ bar.println(format!(
+ "unknown location: {}",
+ ty.display(db, Edition::LATEST)
+ ));
}
}
if unknown_or_partial && self.output == Some(OutputFormat::Csv) {
println!(
r#"{},type,"{}""#,
location_csv_expr(db, vfs, &sm(), expr_id),
- ty.display(db)
+ ty.display(db, Edition::LATEST)
);
}
if let Some(mismatch) = inference_result.type_mismatch_for_expr(expr_id) {
@@ -733,15 +742,15 @@ impl flags::AnalysisStats {
start.col,
end.line + 1,
end.col,
- mismatch.expected.display(db),
- mismatch.actual.display(db)
+ mismatch.expected.display(db, Edition::LATEST),
+ mismatch.actual.display(db, Edition::LATEST)
));
} else {
bar.println(format!(
"{}: Expected {}, got {}",
- name.display(db),
- mismatch.expected.display(db),
- mismatch.actual.display(db)
+ name.display(db, Edition::LATEST),
+ mismatch.expected.display(db, Edition::LATEST),
+ mismatch.actual.display(db, Edition::LATEST)
));
}
}
@@ -749,8 +758,8 @@ impl flags::AnalysisStats {
println!(
r#"{},mismatch,"{}","{}""#,
location_csv_expr(db, vfs, &sm(), expr_id),
- mismatch.expected.display(db),
- mismatch.actual.display(db)
+ mismatch.expected.display(db, Edition::LATEST),
+ mismatch.actual.display(db, Edition::LATEST)
);
}
}
@@ -785,7 +794,10 @@ impl flags::AnalysisStats {
end.col,
));
} else {
- bar.println(format!("{}: Unknown type", name.display(db)));
+ bar.println(format!(
+ "{}: Unknown type",
+ name.display(db, Edition::LATEST)
+ ));
}
}
true
@@ -806,17 +818,20 @@ impl flags::AnalysisStats {
start.col,
end.line + 1,
end.col,
- ty.display(db)
+ ty.display(db, Edition::LATEST)
));
} else {
- bar.println(format!("unknown location: {}", ty.display(db)));
+ bar.println(format!(
+ "unknown location: {}",
+ ty.display(db, Edition::LATEST)
+ ));
}
}
if unknown_or_partial && self.output == Some(OutputFormat::Csv) {
println!(
r#"{},type,"{}""#,
location_csv_pat(db, vfs, &sm(), pat_id),
- ty.display(db)
+ ty.display(db, Edition::LATEST)
);
}
if let Some(mismatch) = inference_result.type_mismatch_for_pat(pat_id) {
@@ -830,15 +845,15 @@ impl flags::AnalysisStats {
start.col,
end.line + 1,
end.col,
- mismatch.expected.display(db),
- mismatch.actual.display(db)
+ mismatch.expected.display(db, Edition::LATEST),
+ mismatch.actual.display(db, Edition::LATEST)
));
} else {
bar.println(format!(
"{}: Expected {}, got {}",
- name.display(db),
- mismatch.expected.display(db),
- mismatch.actual.display(db)
+ name.display(db, Edition::LATEST),
+ mismatch.expected.display(db, Edition::LATEST),
+ mismatch.actual.display(db, Edition::LATEST)
));
}
}
@@ -846,8 +861,8 @@ impl flags::AnalysisStats {
println!(
r#"{},mismatch,"{}","{}""#,
location_csv_pat(db, vfs, &sm(), pat_id),
- mismatch.expected.display(db),
- mismatch.actual.display(db)
+ mismatch.expected.display(db, Edition::LATEST),
+ mismatch.actual.display(db, Edition::LATEST)
);
}
}
@@ -923,12 +938,16 @@ impl flags::AnalysisStats {
.filter_map(|it| it.name(db))
.rev()
.chain(Some(body_id.name(db).unwrap_or_else(Name::missing)))
- .map(|it| it.display(db).to_string()),
+ .map(|it| it.display(db, Edition::LATEST).to_string()),
)
.join("::")
};
if let Some(only_name) = self.only.as_deref() {
- if body_id.name(db).unwrap_or_else(Name::missing).display(db).to_string()
+ if body_id
+ .name(db)
+ .unwrap_or_else(Name::missing)
+ .display(db, Edition::LATEST)
+ .to_string()
!= only_name
&& full_name() != only_name
{
diff --git a/crates/rust-analyzer/src/cli/flags.rs b/crates/rust-analyzer/src/cli/flags.rs
index 2a3e74c680..16d90de661 100644
--- a/crates/rust-analyzer/src/cli/flags.rs
+++ b/crates/rust-analyzer/src/cli/flags.rs
@@ -138,6 +138,9 @@ xflags::xflags! {
cmd lsif {
required path: PathBuf
+
+ /// Exclude code from vendored libraries from the resulting index.
+ optional --exclude-vendored-libraries
}
cmd scip {
@@ -148,6 +151,9 @@ xflags::xflags! {
/// A path to an json configuration file that can be used to customize cargo behavior.
optional --config-path config_path: PathBuf
+
+ /// Exclude code from vendored libraries from the resulting index.
+ optional --exclude-vendored-libraries
}
}
}
@@ -259,6 +265,8 @@ pub struct Search {
#[derive(Debug)]
pub struct Lsif {
pub path: PathBuf,
+
+ pub exclude_vendored_libraries: bool,
}
#[derive(Debug)]
@@ -267,6 +275,7 @@ pub struct Scip {
pub output: Option<PathBuf>,
pub config_path: Option<PathBuf>,
+ pub exclude_vendored_libraries: bool,
}
impl RustAnalyzer {
diff --git a/crates/rust-analyzer/src/cli/lsif.rs b/crates/rust-analyzer/src/cli/lsif.rs
index 016f0edc2d..89fe712ced 100644
--- a/crates/rust-analyzer/src/cli/lsif.rs
+++ b/crates/rust-analyzer/src/cli/lsif.rs
@@ -5,7 +5,7 @@ use std::time::Instant;
use ide::{
Analysis, AnalysisHost, FileId, FileRange, MonikerKind, PackageInformation, RootDatabase,
- StaticIndex, StaticIndexedFile, TokenId, TokenStaticData,
+ StaticIndex, StaticIndexedFile, TokenId, TokenStaticData, VendoredLibrariesConfig,
};
use ide_db::{line_index::WideEncoding, LineIndexDatabase};
use load_cargo::{load_workspace, LoadCargoConfig, ProcMacroServerChoice};
@@ -296,7 +296,13 @@ impl flags::Lsif {
let db = host.raw_database();
let analysis = host.analysis();
- let si = StaticIndex::compute(&analysis, &path.clone().into());
+ let vendored_libs_config = if self.exclude_vendored_libraries {
+ VendoredLibrariesConfig::Excluded
+ } else {
+ VendoredLibrariesConfig::Included { workspace_root: &path.clone().into() }
+ };
+
+ let si = StaticIndex::compute(&analysis, vendored_libs_config);
let mut lsif = LsifManager::new(&analysis, db, &vfs);
lsif.add_vertex(lsif::Vertex::MetaData(lsif::MetaData {
diff --git a/crates/rust-analyzer/src/cli/scip.rs b/crates/rust-analyzer/src/cli/scip.rs
index 2fc0ef8d4d..ceb8534fdf 100644
--- a/crates/rust-analyzer/src/cli/scip.rs
+++ b/crates/rust-analyzer/src/cli/scip.rs
@@ -4,7 +4,7 @@ use std::{path::PathBuf, time::Instant};
use ide::{
AnalysisHost, LineCol, MonikerDescriptorKind, MonikerResult, StaticIndex, StaticIndexedFile,
- SymbolInformationKind, TextRange, TokenId,
+ SymbolInformationKind, TextRange, TokenId, VendoredLibrariesConfig,
};
use ide_db::LineIndexDatabase;
use load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice};
@@ -37,7 +37,6 @@ impl flags::Scip {
lsp_types::ClientCapabilities::default(),
vec![],
None,
- None,
);
if let Some(p) = self.config_path {
@@ -63,7 +62,13 @@ impl flags::Scip {
let db = host.raw_database();
let analysis = host.analysis();
- let si = StaticIndex::compute(&analysis, &root.clone().into());
+ let vendored_libs_config = if self.exclude_vendored_libraries {
+ VendoredLibrariesConfig::Excluded
+ } else {
+ VendoredLibrariesConfig::Included { workspace_root: &root.clone().into() }
+ };
+
+ let si = StaticIndex::compute(&analysis, vendored_libs_config);
let metadata = scip_types::Metadata {
version: scip_types::ProtocolVersion::UnspecifiedProtocolVersion.into(),
@@ -137,7 +142,9 @@ impl flags::Scip {
let mut symbol_roles = Default::default();
if let Some(def) = token.definition {
- if def.range == text_range {
+ // if the the range of the def and the range of the token are the same, this must be the definition.
+ // they also must be in the same file. See https://github.com/rust-lang/rust-analyzer/pull/17988
+ if def.file_id == file_id && def.range == text_range {
symbol_roles |= scip_types::SymbolRole::Definition as i32;
}
@@ -352,8 +359,12 @@ mod test {
let (host, position) = position(ra_fixture);
let analysis = host.analysis();
- let si =
- StaticIndex::compute(&analysis, &VfsPath::new_virtual_path("/workspace".to_owned()));
+ let si = StaticIndex::compute(
+ &analysis,
+ VendoredLibrariesConfig::Included {
+ workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),
+ },
+ );
let FilePosition { file_id, offset } = position;
@@ -617,8 +628,12 @@ pub mod example_mod {
host.raw_database_mut().apply_change(change_fixture.change);
let analysis = host.analysis();
- let si =
- StaticIndex::compute(&analysis, &VfsPath::new_virtual_path("/workspace".to_owned()));
+ let si = StaticIndex::compute(
+ &analysis,
+ VendoredLibrariesConfig::Included {
+ workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),
+ },
+ );
let file = si.files.first().unwrap();
let (_, token_id) = file.tokens.first().unwrap();
diff --git a/crates/rust-analyzer/src/command.rs b/crates/rust-analyzer/src/command.rs
index f1009eb466..b19a1b8d16 100644
--- a/crates/rust-analyzer/src/command.rs
+++ b/crates/rust-analyzer/src/command.rs
@@ -148,7 +148,6 @@ impl<T: ParseFromLine> CommandHandle<T> {
}
pub(crate) fn join(mut self) -> io::Result<()> {
- let _ = self.child.0.kill();
let exit_status = self.child.0.wait()?;
let (read_at_least_one_message, error) = self.thread.join()?;
if read_at_least_one_message || exit_status.success() {
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 02f5d75136..2889af844b 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -3,10 +3,13 @@
//! Of particular interest is the `feature_flags` hash map: while other fields
//! configure the server itself, feature flags are passed into analysis, and
//! tweak things like automatic insertion of `()` in completions.
-use std::{fmt, iter, ops::Not, sync::OnceLock};
+use std::{
+ env, fmt, iter,
+ ops::Not,
+ sync::{LazyLock, OnceLock},
+};
use cfg::{CfgAtom, CfgDiff};
-use dirs::config_dir;
use hir::Symbol;
use ide::{
AssistConfig, CallableSnippets, CompletionConfig, DiagnosticsConfig, ExprFillDefaultMode,
@@ -80,16 +83,11 @@ config_data! {
pub(crate) cargo_autoreload: bool = true,
/// Run build scripts (`build.rs`) for more precise code analysis.
cargo_buildScripts_enable: bool = true,
- /// Specifies the working directory for running build scripts.
- /// - "workspace": run build scripts for a workspace in the workspace's root directory.
- /// This is incompatible with `#rust-analyzer.cargo.buildScripts.invocationStrategy#` set to `once`.
- /// - "root": run build scripts in the project's root directory.
- /// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`
- /// is set.
- cargo_buildScripts_invocationLocation: InvocationLocation = InvocationLocation::Workspace,
/// Specifies the invocation strategy to use when running the build scripts command.
- /// If `per_workspace` is set, the command will be executed for each workspace.
- /// If `once` is set, the command will be executed once.
+ /// If `per_workspace` is set, the command will be executed for each Rust workspace with the
+ /// workspace as the working directory.
+ /// If `once` is set, the command will be executed once with the opened project as the
+ /// working directory.
/// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`
/// is set.
cargo_buildScripts_invocationStrategy: InvocationStrategy = InvocationStrategy::PerWorkspace,
@@ -101,8 +99,7 @@ config_data! {
/// If there are multiple linked projects/workspaces, this command is invoked for
/// each of them, with the working directory being the workspace root
/// (i.e., the folder containing the `Cargo.toml`). This can be overwritten
- /// by changing `#rust-analyzer.cargo.buildScripts.invocationStrategy#` and
- /// `#rust-analyzer.cargo.buildScripts.invocationLocation#`.
+ /// by changing `#rust-analyzer.cargo.buildScripts.invocationStrategy#`.
///
/// By default, a cargo invocation will be constructed for the configured
/// targets and features, with the following base command line:
@@ -182,14 +179,6 @@ config_data! {
///
/// For example for `cargo check`: `dead_code`, `unused_imports`, `unused_variables`,...
check_ignore: FxHashSet<String> = FxHashSet::default(),
- /// Specifies the working directory for running checks.
- /// - "workspace": run checks for workspaces in the corresponding workspaces' root directories.
- // FIXME: Ideally we would support this in some way
- /// This falls back to "root" if `#rust-analyzer.check.invocationStrategy#` is set to `once`.
- /// - "root": run checks in the project's root directory.
- /// This config only has an effect when `#rust-analyzer.check.overrideCommand#`
- /// is set.
- check_invocationLocation | checkOnSave_invocationLocation: InvocationLocation = InvocationLocation::Workspace,
/// Specifies the invocation strategy to use when running the check command.
/// If `per_workspace` is set, the command will be executed for each workspace.
/// If `once` is set, the command will be executed once.
@@ -212,8 +201,7 @@ config_data! {
/// If there are multiple linked projects/workspaces, this command is invoked for
/// each of them, with the working directory being the workspace root
/// (i.e., the folder containing the `Cargo.toml`). This can be overwritten
- /// by changing `#rust-analyzer.check.invocationStrategy#` and
- /// `#rust-analyzer.check.invocationLocation#`.
+ /// by changing `#rust-analyzer.check.invocationStrategy#`.
///
/// If `$saved_file` is part of the command, rust-analyzer will pass
/// the absolute path of the saved file to the provided command. This is
@@ -309,18 +297,6 @@ config_data! {
/// This option does not take effect until rust-analyzer is restarted.
rustc_source: Option<String> = None,
- /// Additional arguments to `rustfmt`.
- rustfmt_extraArgs: Vec<String> = vec![],
- /// Advanced option, fully override the command rust-analyzer uses for
- /// formatting. This should be the equivalent of `rustfmt` here, and
- /// not that of `cargo fmt`. The file contents will be passed on the
- /// standard input and the formatted result will be read from the
- /// standard output.
- rustfmt_overrideCommand: Option<Vec<String>> = None,
- /// Enables the use of rustfmt's unstable range formatting command for the
- /// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only
- /// available on a nightly build.
- rustfmt_rangeFormatting_enable: bool = false,
/// Enables automatic discovery of projects using [`DiscoverWorkspaceConfig::command`].
///
@@ -452,6 +428,25 @@ config_data! {
}
config_data! {
+ workspace: struct WorkspaceDefaultConfigData <- WorkspaceConfigInput -> {
+
+ /// Additional arguments to `rustfmt`.
+ rustfmt_extraArgs: Vec<String> = vec![],
+ /// Advanced option, fully override the command rust-analyzer uses for
+ /// formatting. This should be the equivalent of `rustfmt` here, and
+ /// not that of `cargo fmt`. The file contents will be passed on the
+ /// standard input and the formatted result will be read from the
+ /// standard output.
+ rustfmt_overrideCommand: Option<Vec<String>> = None,
+ /// Enables the use of rustfmt's unstable range formatting command for the
+ /// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only
+ /// available on a nightly build.
+ rustfmt_rangeFormatting_enable: bool = false,
+
+ }
+}
+
+config_data! {
/// Configs that only make sense when they are set by a client. As such they can only be defined
/// by setting them using client's settings (e.g `settings.json` on VS Code).
client: struct ClientDefaultConfigData <- ClientConfigInput -> {
@@ -750,10 +745,9 @@ pub enum RatomlFileKind {
}
#[derive(Debug, Clone)]
-// FIXME @alibektas : Seems like a clippy warning of this sort should tell that combining different ConfigInputs into one enum was not a good idea.
#[allow(clippy::large_enum_variant)]
enum RatomlFile {
- Workspace(GlobalLocalConfigInput),
+ Workspace(WorkspaceLocalConfigInput),
Crate(LocalConfigInput),
}
@@ -772,18 +766,8 @@ pub struct Config {
/// by receiving a `lsp_types::notification::DidChangeConfiguration`.
client_config: (FullConfigInput, ConfigErrors),
- /// Path to the root configuration file. This can be seen as a generic way to define what would be `$XDG_CONFIG_HOME/rust-analyzer/rust-analyzer.toml` in Linux.
- /// If not specified by init of a `Config` object this value defaults to :
- ///
- /// |Platform | Value | Example |
- /// | ------- | ------------------------------------- | ---------------------------------------- |
- /// | Linux | `$XDG_CONFIG_HOME` or `$HOME`/.config | /home/alice/.config |
- /// | macOS | `$HOME`/Library/Application Support | /Users/Alice/Library/Application Support |
- /// | Windows | `{FOLDERID_RoamingAppData}` | C:\Users\Alice\AppData\Roaming |
- user_config_path: VfsPath,
-
/// Config node whose values apply to **every** Rust project.
- user_config: Option<(GlobalLocalConfigInput, ConfigErrors)>,
+ user_config: Option<(GlobalWorkspaceLocalConfigInput, ConfigErrors)>,
ratoml_file: FxHashMap<SourceRootId, (RatomlFile, ConfigErrors)>,
@@ -809,8 +793,25 @@ impl std::ops::Deref for Config {
}
impl Config {
- pub fn user_config_path(&self) -> &VfsPath {
- &self.user_config_path
+ /// Path to the root configuration file. This can be seen as a generic way to define what would be `$XDG_CONFIG_HOME/rust-analyzer/rust-analyzer.toml` in Linux.
+ /// This path is equal to:
+ ///
+ /// |Platform | Value | Example |
+ /// | ------- | ------------------------------------- | ---------------------------------------- |
+ /// | Linux | `$XDG_CONFIG_HOME` or `$HOME`/.config | /home/alice/.config |
+ /// | macOS | `$HOME`/Library/Application Support | /Users/Alice/Library/Application Support |
+ /// | Windows | `{FOLDERID_RoamingAppData}` | C:\Users\Alice\AppData\Roaming |
+ pub fn user_config_path() -> Option<&'static AbsPath> {
+ static USER_CONFIG_PATH: LazyLock<Option<AbsPathBuf>> = LazyLock::new(|| {
+ let user_config_path = if let Some(path) = env::var_os("__TEST_RA_USER_CONFIG_DIR") {
+ std::path::PathBuf::from(path)
+ } else {
+ dirs::config_dir()?.join("rust-analyzer")
+ }
+ .join("rust-analyzer.toml");
+ Some(AbsPathBuf::assert_utf8(user_config_path))
+ });
+ USER_CONFIG_PATH.as_deref()
}
pub fn same_source_root_parent_map(
@@ -833,13 +834,13 @@ impl Config {
if let Ok(table) = toml::from_str(&change) {
let mut toml_errors = vec![];
validate_toml_table(
- GlobalLocalConfigInput::FIELDS,
+ GlobalWorkspaceLocalConfigInput::FIELDS,
&table,
&mut String::new(),
&mut toml_errors,
);
config.user_config = Some((
- GlobalLocalConfigInput::from_toml(table, &mut toml_errors),
+ GlobalWorkspaceLocalConfigInput::from_toml(table, &mut toml_errors),
ConfigErrors(
toml_errors
.into_iter()
@@ -968,7 +969,7 @@ impl Config {
match toml::from_str(&text) {
Ok(table) => {
validate_toml_table(
- GlobalLocalConfigInput::FIELDS,
+ WorkspaceLocalConfigInput::FIELDS,
&table,
&mut String::new(),
&mut toml_errors,
@@ -977,7 +978,7 @@ impl Config {
source_root_id,
(
RatomlFile::Workspace(
- GlobalLocalConfigInput::from_toml(
+ WorkspaceLocalConfigInput::from_toml(
table,
&mut toml_errors,
),
@@ -1015,7 +1016,7 @@ impl Config {
config.source_root_parent_map = source_root_map;
}
- if config.check_command(None).is_empty() {
+ if config.check_command().is_empty() {
config.validation_errors.0.push(Arc::new(ConfigErrorInner::Json {
config_key: "/check/command".to_owned(),
error: serde_json::Error::custom("expected a non-empty string"),
@@ -1330,24 +1331,8 @@ impl Config {
caps: lsp_types::ClientCapabilities,
workspace_roots: Vec<AbsPathBuf>,
visual_studio_code_version: Option<Version>,
- user_config_path: Option<Utf8PathBuf>,
) -> Self {
static DEFAULT_CONFIG_DATA: OnceLock<&'static DefaultConfigData> = OnceLock::new();
- let user_config_path = if let Some(user_config_path) = user_config_path {
- user_config_path.join("rust-analyzer").join("rust-analyzer.toml")
- } else {
- let p = config_dir()
- .expect("A config dir is expected to existed on all platforms ra supports.")
- .join("rust-analyzer")
- .join("rust-analyzer.toml");
- Utf8PathBuf::from_path_buf(p).expect("Config dir expected to be abs.")
- };
-
- // A user config cannot be a virtual path as rust-analyzer cannot support watching changes in virtual paths.
- // See `GlobalState::process_changes` to get more info.
- // FIXME @alibektas : Temporary solution. I don't think this is right as at some point we may allow users to specify
- // custom USER_CONFIG_PATHs which may also be relative.
- let user_config_path = VfsPath::from(AbsPathBuf::assert(user_config_path));
Config {
caps: ClientCapabilities::new(caps),
@@ -1360,7 +1345,6 @@ impl Config {
default_config: DEFAULT_CONFIG_DATA.get_or_init(|| Box::leak(Box::default())),
source_root_parent_map: Arc::new(FxHashMap::default()),
user_config: None,
- user_config_path,
detached_files: Default::default(),
validation_errors: Default::default(),
ratoml_file: Default::default(),
@@ -1459,11 +1443,11 @@ impl Config {
pub fn diagnostics(&self, source_root: Option<SourceRootId>) -> DiagnosticsConfig {
DiagnosticsConfig {
- enabled: *self.diagnostics_enable(source_root),
+ enabled: *self.diagnostics_enable(),
proc_attr_macros_enabled: self.expand_proc_attr_macros(),
proc_macros_enabled: *self.procMacro_enable(),
- disable_experimental: !self.diagnostics_experimental_enable(source_root),
- disabled: self.diagnostics_disabled(source_root).clone(),
+ disable_experimental: !self.diagnostics_experimental_enable(),
+ disabled: self.diagnostics_disabled().clone(),
expr_fill_default: match self.assist_expressionFillDefault(source_root) {
ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo,
ExprFillDefaultDef::Default => ExprFillDefaultMode::Default,
@@ -1473,7 +1457,7 @@ impl Config {
prefer_no_std: self.imports_preferNoStd(source_root).to_owned(),
prefer_prelude: self.imports_preferPrelude(source_root).to_owned(),
prefer_absolute: self.imports_prefixExternPrelude(source_root).to_owned(),
- style_lints: self.diagnostics_styleLints_enable(source_root).to_owned(),
+ style_lints: self.diagnostics_styleLints_enable().to_owned(),
term_search_fuel: self.assist_termSearch_fuel(source_root).to_owned() as u64,
term_search_borrowck: self.assist_termSearch_borrowcheck(source_root).to_owned(),
}
@@ -1666,11 +1650,11 @@ impl Config {
}
pub fn has_linked_projects(&self) -> bool {
- !self.linkedProjects(None).is_empty()
+ !self.linkedProjects().is_empty()
}
pub fn linked_manifests(&self) -> impl Iterator<Item = &Utf8Path> + '_ {
- self.linkedProjects(None).iter().filter_map(|it| match it {
+ self.linkedProjects().iter().filter_map(|it| match it {
ManifestOrProjectJson::Manifest(p) => Some(&**p),
// despite having a buildfile, using this variant as a manifest
// will fail.
@@ -1680,20 +1664,20 @@ impl Config {
}
pub fn has_linked_project_jsons(&self) -> bool {
- self.linkedProjects(None)
+ self.linkedProjects()
.iter()
.any(|it| matches!(it, ManifestOrProjectJson::ProjectJson { .. }))
}
pub fn discover_workspace_config(&self) -> Option<&DiscoverWorkspaceConfig> {
- self.workspace_discoverConfig(None).as_ref()
+ self.workspace_discoverConfig().as_ref()
}
pub fn linked_or_discovered_projects(&self) -> Vec<LinkedProject> {
- match self.linkedProjects(None).as_slice() {
+ match self.linkedProjects().as_slice() {
[] => {
let exclude_dirs: Vec<_> =
- self.files_excludeDirs(None).iter().map(|p| self.root_path.join(p)).collect();
+ self.files_excludeDirs().iter().map(|p| self.root_path.join(p)).collect();
self.discovered_projects
.iter()
.filter(|project| {
@@ -1728,48 +1712,48 @@ impl Config {
}
pub fn prefill_caches(&self) -> bool {
- self.cachePriming_enable(None).to_owned()
+ self.cachePriming_enable().to_owned()
}
pub fn publish_diagnostics(&self) -> bool {
- self.diagnostics_enable(None).to_owned()
+ self.diagnostics_enable().to_owned()
}
pub fn diagnostics_map(&self) -> DiagnosticsMapConfig {
DiagnosticsMapConfig {
- remap_prefix: self.diagnostics_remapPrefix(None).clone(),
- warnings_as_info: self.diagnostics_warningsAsInfo(None).clone(),
- warnings_as_hint: self.diagnostics_warningsAsHint(None).clone(),
- check_ignore: self.check_ignore(None).clone(),
+ remap_prefix: self.diagnostics_remapPrefix().clone(),
+ warnings_as_info: self.diagnostics_warningsAsInfo().clone(),
+ warnings_as_hint: self.diagnostics_warningsAsHint().clone(),
+ check_ignore: self.check_ignore().clone(),
}
}
pub fn extra_args(&self) -> &Vec<String> {
- self.cargo_extraArgs(None)
+ self.cargo_extraArgs()
}
pub fn extra_env(&self) -> &FxHashMap<String, String> {
- self.cargo_extraEnv(None)
+ self.cargo_extraEnv()
}
pub fn check_extra_args(&self) -> Vec<String> {
let mut extra_args = self.extra_args().clone();
- extra_args.extend_from_slice(self.check_extraArgs(None));
+ extra_args.extend_from_slice(self.check_extraArgs());
extra_args
}
pub fn check_extra_env(&self) -> FxHashMap<String, String> {
- let mut extra_env = self.cargo_extraEnv(None).clone();
- extra_env.extend(self.check_extraEnv(None).clone());
+ let mut extra_env = self.cargo_extraEnv().clone();
+ extra_env.extend(self.check_extraEnv().clone());
extra_env
}
pub fn lru_parse_query_capacity(&self) -> Option<u16> {
- self.lru_capacity(None).to_owned()
+ self.lru_capacity().to_owned()
}
pub fn lru_query_capacities_config(&self) -> Option<&FxHashMap<Box<str>, u16>> {
- self.lru_query_capacities(None).is_empty().not().then(|| self.lru_query_capacities(None))
+ self.lru_query_capacities().is_empty().not().then(|| self.lru_query_capacities())
}
pub fn proc_macro_srv(&self) -> Option<AbsPathBuf> {
@@ -1778,7 +1762,7 @@ impl Config {
}
pub fn ignored_proc_macros(&self) -> &FxHashMap<Box<str>, Box<[Box<str>]>> {
- self.procMacro_ignored(None)
+ self.procMacro_ignored()
}
pub fn expand_proc_macros(&self) -> bool {
@@ -1793,11 +1777,7 @@ impl Config {
}
_ => FilesWatcher::Server,
},
- exclude: self
- .files_excludeDirs(None)
- .iter()
- .map(|it| self.root_path.join(it))
- .collect(),
+ exclude: self.files_excludeDirs().iter().map(|it| self.root_path.join(it)).collect(),
}
}
@@ -1808,22 +1788,22 @@ impl Config {
}
pub fn cargo_autoreload_config(&self) -> bool {
- self.cargo_autoreload(None).to_owned()
+ self.cargo_autoreload().to_owned()
}
pub fn run_build_scripts(&self) -> bool {
- self.cargo_buildScripts_enable(None).to_owned() || self.procMacro_enable().to_owned()
+ self.cargo_buildScripts_enable().to_owned() || self.procMacro_enable().to_owned()
}
pub fn cargo(&self) -> CargoConfig {
- let rustc_source = self.rustc_source(None).as_ref().map(|rustc_src| {
+ let rustc_source = self.rustc_source().as_ref().map(|rustc_src| {
if rustc_src == "discover" {
RustLibSource::Discover
} else {
RustLibSource::Path(self.root_path.join(rustc_src))
}
});
- let sysroot = self.cargo_sysroot(None).as_ref().map(|sysroot| {
+ let sysroot = self.cargo_sysroot().as_ref().map(|sysroot| {
if sysroot == "discover" {
RustLibSource::Discover
} else {
@@ -1831,24 +1811,24 @@ impl Config {
}
});
let sysroot_src =
- self.cargo_sysrootSrc(None).as_ref().map(|sysroot| self.root_path.join(sysroot));
+ self.cargo_sysrootSrc().as_ref().map(|sysroot| self.root_path.join(sysroot));
CargoConfig {
- all_targets: *self.cargo_allTargets(None),
- features: match &self.cargo_features(None) {
+ all_targets: *self.cargo_allTargets(),
+ features: match &self.cargo_features() {
CargoFeaturesDef::All => CargoFeatures::All,
CargoFeaturesDef::Selected(features) => CargoFeatures::Selected {
features: features.clone(),
- no_default_features: self.cargo_noDefaultFeatures(None).to_owned(),
+ no_default_features: self.cargo_noDefaultFeatures().to_owned(),
},
},
- target: self.cargo_target(None).clone(),
+ target: self.cargo_target().clone(),
sysroot,
sysroot_src,
rustc_source,
cfg_overrides: project_model::CfgOverrides {
global: CfgDiff::new(
- self.cargo_cfgs(None)
+ self.cargo_cfgs()
.iter()
.map(|(key, val)| match val {
Some(val) => CfgAtom::KeyValue {
@@ -1863,20 +1843,14 @@ impl Config {
.unwrap(),
selective: Default::default(),
},
- wrap_rustc_in_build_scripts: *self.cargo_buildScripts_useRustcWrapper(None),
- invocation_strategy: match self.cargo_buildScripts_invocationStrategy(None) {
+ wrap_rustc_in_build_scripts: *self.cargo_buildScripts_useRustcWrapper(),
+ invocation_strategy: match self.cargo_buildScripts_invocationStrategy() {
InvocationStrategy::Once => project_model::InvocationStrategy::Once,
InvocationStrategy::PerWorkspace => project_model::InvocationStrategy::PerWorkspace,
},
- invocation_location: match self.cargo_buildScripts_invocationLocation(None) {
- InvocationLocation::Root => {
- project_model::InvocationLocation::Root(self.root_path.clone())
- }
- InvocationLocation::Workspace => project_model::InvocationLocation::Workspace,
- },
- run_build_script_command: self.cargo_buildScripts_overrideCommand(None).clone(),
- extra_args: self.cargo_extraArgs(None).clone(),
- extra_env: self.cargo_extraEnv(None).clone(),
+ run_build_script_command: self.cargo_buildScripts_overrideCommand().clone(),
+ extra_args: self.cargo_extraArgs().clone(),
+ extra_env: self.cargo_extraEnv().clone(),
target_dir: self.target_dir_from_config(),
}
}
@@ -1896,27 +1870,28 @@ impl Config {
}
pub fn flycheck_workspace(&self) -> bool {
- *self.check_workspace(None)
+ *self.check_workspace()
}
pub(crate) fn cargo_test_options(&self) -> CargoOptions {
CargoOptions {
- target_triples: self.cargo_target(None).clone().into_iter().collect(),
+ target_triples: self.cargo_target().clone().into_iter().collect(),
all_targets: false,
- no_default_features: *self.cargo_noDefaultFeatures(None),
- all_features: matches!(self.cargo_features(None), CargoFeaturesDef::All),
- features: match self.cargo_features(None).clone() {
+ no_default_features: *self.cargo_noDefaultFeatures(),
+ all_features: matches!(self.cargo_features(), CargoFeaturesDef::All),
+ features: match self.cargo_features().clone() {
CargoFeaturesDef::All => vec![],
CargoFeaturesDef::Selected(it) => it,
},
extra_args: self.extra_args().clone(),
+ extra_test_bin_args: self.runnables_extraTestBinaryArgs().clone(),
extra_env: self.extra_env().clone(),
target_dir: self.target_dir_from_config(),
}
}
pub(crate) fn flycheck(&self) -> FlycheckConfig {
- match &self.check_overrideCommand(None) {
+ match &self.check_overrideCommand() {
Some(args) if !args.is_empty() => {
let mut args = args.clone();
let command = args.remove(0);
@@ -1924,52 +1899,43 @@ impl Config {
command,
args,
extra_env: self.check_extra_env(),
- invocation_strategy: match self.check_invocationStrategy(None) {
+ invocation_strategy: match self.check_invocationStrategy() {
InvocationStrategy::Once => crate::flycheck::InvocationStrategy::Once,
InvocationStrategy::PerWorkspace => {
crate::flycheck::InvocationStrategy::PerWorkspace
}
},
- invocation_location: match self.check_invocationLocation(None) {
- InvocationLocation::Root => {
- crate::flycheck::InvocationLocation::Root(self.root_path.clone())
- }
- InvocationLocation::Workspace => {
- crate::flycheck::InvocationLocation::Workspace
- }
- },
}
}
Some(_) | None => FlycheckConfig::CargoCommand {
- command: self.check_command(None).clone(),
+ command: self.check_command().clone(),
options: CargoOptions {
target_triples: self
- .check_targets(None)
+ .check_targets()
.clone()
.and_then(|targets| match &targets.0[..] {
[] => None,
targets => Some(targets.into()),
})
- .unwrap_or_else(|| self.cargo_target(None).clone().into_iter().collect()),
- all_targets: self
- .check_allTargets(None)
- .unwrap_or(*self.cargo_allTargets(None)),
+ .unwrap_or_else(|| self.cargo_target().clone().into_iter().collect()),
+ all_targets: self.check_allTargets().unwrap_or(*self.cargo_allTargets()),
no_default_features: self
- .check_noDefaultFeatures(None)
- .unwrap_or(*self.cargo_noDefaultFeatures(None)),
+ .check_noDefaultFeatures()
+ .unwrap_or(*self.cargo_noDefaultFeatures()),
all_features: matches!(
- self.check_features(None).as_ref().unwrap_or(self.cargo_features(None)),
+ self.check_features().as_ref().unwrap_or(self.cargo_features()),
CargoFeaturesDef::All
),
features: match self
- .check_features(None)
+ .check_features()
.clone()
- .unwrap_or_else(|| self.cargo_features(None).clone())
+ .unwrap_or_else(|| self.cargo_features().clone())
{
CargoFeaturesDef::All => vec![],
CargoFeaturesDef::Selected(it) => it,
},
extra_args: self.check_extra_args(),
+ extra_test_bin_args: self.runnables_extraTestBinaryArgs().clone(),
extra_env: self.check_extra_env(),
target_dir: self.target_dir_from_config(),
},
@@ -1979,7 +1945,7 @@ impl Config {
}
fn target_dir_from_config(&self) -> Option<Utf8PathBuf> {
- self.cargo_targetDir(None).as_ref().and_then(|target_dir| match target_dir {
+ self.cargo_targetDir().as_ref().and_then(|target_dir| match target_dir {
TargetDirectory::UseSubdirectory(true) => {
Some(Utf8PathBuf::from("target/rust-analyzer"))
}
@@ -1990,18 +1956,18 @@ impl Config {
}
pub fn check_on_save(&self) -> bool {
- *self.checkOnSave(None)
+ *self.checkOnSave()
}
pub fn script_rebuild_on_save(&self) -> bool {
- *self.cargo_buildScripts_rebuildOnSave(None)
+ *self.cargo_buildScripts_rebuildOnSave()
}
pub fn runnables(&self) -> RunnablesConfig {
RunnablesConfig {
- override_cargo: self.runnables_command(None).clone(),
- cargo_extra_args: self.runnables_extraArgs(None).clone(),
- extra_test_binary_args: self.runnables_extraTestBinaryArgs(None).clone(),
+ override_cargo: self.runnables_command().clone(),
+ cargo_extra_args: self.runnables_extraArgs().clone(),
+ extra_test_binary_args: self.runnables_extraTestBinaryArgs().clone(),
}
}
@@ -2072,7 +2038,7 @@ impl Config {
}
pub fn prime_caches_num_threads(&self) -> usize {
- match self.cachePriming_numThreads(None) {
+ match self.cachePriming_numThreads() {
NumThreads::Concrete(0) | NumThreads::Physical => num_cpus::get_physical(),
&NumThreads::Concrete(n) => n,
NumThreads::Logical => num_cpus::get(),
@@ -2350,13 +2316,6 @@ struct CheckOnSaveTargets(#[serde(with = "single_or_array")] Vec<String>);
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
-enum InvocationLocation {
- Root,
- Workspace,
-}
-
-#[derive(Serialize, Deserialize, Debug, Clone)]
-#[serde(rename_all = "snake_case")]
enum LifetimeElisionDef {
SkipTrivial,
#[serde(with = "true_or_always")]
@@ -2578,7 +2537,7 @@ macro_rules! _impl_for_config_data {
)*
}
};
- (global, $(
+ (workspace, $(
$(#[doc=$doc:literal])*
$vis:vis $field:ident : $ty:ty = $default:expr,
)*
@@ -2587,18 +2546,42 @@ macro_rules! _impl_for_config_data {
$(
$($doc)*
#[allow(non_snake_case)]
- $vis fn $field(&self, source_root : Option<SourceRootId>) -> &$ty {
+ $vis fn $field(&self, source_root: Option<SourceRootId>) -> &$ty {
let mut source_root = source_root.as_ref();
while let Some(sr) = source_root {
if let Some((RatomlFile::Workspace(config), _)) = self.ratoml_file.get(&sr) {
- if let Some(v) = config.global.$field.as_ref() {
+ if let Some(v) = config.workspace.$field.as_ref() {
return &v;
}
}
-
source_root = self.source_root_parent_map.get(&sr);
}
+ if let Some(v) = self.client_config.0.workspace.$field.as_ref() {
+ return &v;
+ }
+
+ if let Some((user_config, _)) = self.user_config.as_ref() {
+ if let Some(v) = user_config.workspace.$field.as_ref() {
+ return &v;
+ }
+ }
+
+ &self.default_config.workspace.$field
+ }
+ )*
+ }
+ };
+ (global, $(
+ $(#[doc=$doc:literal])*
+ $vis:vis $field:ident : $ty:ty = $default:expr,
+ )*
+ ) => {
+ impl Config {
+ $(
+ $($doc)*
+ #[allow(non_snake_case)]
+ $vis fn $field(&self) -> &$ty {
if let Some(v) = self.client_config.0.global.$field.as_ref() {
return &v;
}
@@ -2736,6 +2719,7 @@ use _config_data as config_data;
#[derive(Default, Debug, Clone)]
struct DefaultConfigData {
global: GlobalDefaultConfigData,
+ workspace: WorkspaceDefaultConfigData,
local: LocalDefaultConfigData,
client: ClientDefaultConfigData,
}
@@ -2746,6 +2730,7 @@ struct DefaultConfigData {
#[derive(Debug, Clone, Default)]
struct FullConfigInput {
global: GlobalConfigInput,
+ workspace: WorkspaceConfigInput,
local: LocalConfigInput,
client: ClientConfigInput,
}
@@ -2759,6 +2744,7 @@ impl FullConfigInput {
global: GlobalConfigInput::from_json(&mut json, error_sink),
local: LocalConfigInput::from_json(&mut json, error_sink),
client: ClientConfigInput::from_json(&mut json, error_sink),
+ workspace: WorkspaceConfigInput::from_json(&mut json, error_sink),
}
}
@@ -2767,6 +2753,7 @@ impl FullConfigInput {
GlobalConfigInput::schema_fields(&mut fields);
LocalConfigInput::schema_fields(&mut fields);
ClientConfigInput::schema_fields(&mut fields);
+ WorkspaceConfigInput::schema_fields(&mut fields);
fields.sort_by_key(|&(x, ..)| x);
fields
.iter()
@@ -2789,21 +2776,45 @@ impl FullConfigInput {
/// some rust-analyzer.toml file or JSON blob. An empty rust-analyzer.toml corresponds to
/// all fields being None.
#[derive(Debug, Clone, Default)]
-struct GlobalLocalConfigInput {
+struct GlobalWorkspaceLocalConfigInput {
global: GlobalConfigInput,
local: LocalConfigInput,
+ workspace: WorkspaceConfigInput,
}
-impl GlobalLocalConfigInput {
+impl GlobalWorkspaceLocalConfigInput {
const FIELDS: &'static [&'static [&'static str]] =
&[GlobalConfigInput::FIELDS, LocalConfigInput::FIELDS];
fn from_toml(
toml: toml::Table,
error_sink: &mut Vec<(String, toml::de::Error)>,
- ) -> GlobalLocalConfigInput {
- GlobalLocalConfigInput {
+ ) -> GlobalWorkspaceLocalConfigInput {
+ GlobalWorkspaceLocalConfigInput {
global: GlobalConfigInput::from_toml(&toml, error_sink),
local: LocalConfigInput::from_toml(&toml, error_sink),
+ workspace: WorkspaceConfigInput::from_toml(&toml, error_sink),
+ }
+ }
+}
+
+/// Workspace and local config levels, all fields `Option<T>`, to describe fields that are actually set by
+/// some rust-analyzer.toml file or JSON blob. An empty rust-analyzer.toml corresponds to
+/// all fields being None.
+#[derive(Debug, Clone, Default)]
+#[allow(dead_code)]
+struct WorkspaceLocalConfigInput {
+ workspace: WorkspaceConfigInput,
+ local: LocalConfigInput,
+}
+
+impl WorkspaceLocalConfigInput {
+ #[allow(dead_code)]
+ const FIELDS: &'static [&'static [&'static str]] =
+ &[WorkspaceConfigInput::FIELDS, LocalConfigInput::FIELDS];
+ fn from_toml(toml: toml::Table, error_sink: &mut Vec<(String, toml::de::Error)>) -> Self {
+ Self {
+ workspace: WorkspaceConfigInput::from_toml(&toml, error_sink),
+ local: LocalConfigInput::from_toml(&toml, error_sink),
}
}
}
@@ -3192,16 +3203,8 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
"type": "string",
"enum": ["per_workspace", "once"],
"enumDescriptions": [
- "The command will be executed for each workspace.",
- "The command will be executed once."
- ],
- },
- "InvocationLocation" => set! {
- "type": "string",
- "enum": ["workspace", "root"],
- "enumDescriptions": [
- "The command will be executed in the corresponding workspace root.",
- "The command will be executed in the project root."
+ "The command will be executed for each Rust workspace with the workspace as the working directory.",
+ "The command will be executed once with the opened project as the working directory."
],
},
"Option<CheckOnSaveTargets>" => set! {
@@ -3461,7 +3464,7 @@ mod tests {
#[test]
fn proc_macro_srv_null() {
let mut config =
- Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None, None);
+ Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None);
let mut change = ConfigChange::default();
change.change_client_config(serde_json::json!({
@@ -3476,7 +3479,7 @@ mod tests {
#[test]
fn proc_macro_srv_abs() {
let mut config =
- Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None, None);
+ Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None);
let mut change = ConfigChange::default();
change.change_client_config(serde_json::json!({
"procMacro" : {
@@ -3490,7 +3493,7 @@ mod tests {
#[test]
fn proc_macro_srv_rel() {
let mut config =
- Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None, None);
+ Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None);
let mut change = ConfigChange::default();
@@ -3510,7 +3513,7 @@ mod tests {
#[test]
fn cargo_target_dir_unset() {
let mut config =
- Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None, None);
+ Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None);
let mut change = ConfigChange::default();
@@ -3519,7 +3522,7 @@ mod tests {
}));
(config, _, _) = config.apply_change(change);
- assert_eq!(config.cargo_targetDir(None), &None);
+ assert_eq!(config.cargo_targetDir(), &None);
assert!(
matches!(config.flycheck(), FlycheckConfig::CargoCommand { options, .. } if options.target_dir.is_none())
);
@@ -3528,7 +3531,7 @@ mod tests {
#[test]
fn cargo_target_dir_subdir() {
let mut config =
- Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None, None);
+ Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None);
let mut change = ConfigChange::default();
change.change_client_config(serde_json::json!({
@@ -3537,7 +3540,7 @@ mod tests {
(config, _, _) = config.apply_change(change);
- assert_eq!(config.cargo_targetDir(None), &Some(TargetDirectory::UseSubdirectory(true)));
+ assert_eq!(config.cargo_targetDir(), &Some(TargetDirectory::UseSubdirectory(true)));
assert!(
matches!(config.flycheck(), FlycheckConfig::CargoCommand { options, .. } if options.target_dir == Some(Utf8PathBuf::from("target/rust-analyzer")))
);
@@ -3546,7 +3549,7 @@ mod tests {
#[test]
fn cargo_target_dir_relative_dir() {
let mut config =
- Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None, None);
+ Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None);
let mut change = ConfigChange::default();
change.change_client_config(serde_json::json!({
@@ -3556,7 +3559,7 @@ mod tests {
(config, _, _) = config.apply_change(change);
assert_eq!(
- config.cargo_targetDir(None),
+ config.cargo_targetDir(),
&Some(TargetDirectory::Directory(Utf8PathBuf::from("other_folder")))
);
assert!(
@@ -3567,7 +3570,7 @@ mod tests {
#[test]
fn toml_unknown_key() {
let config =
- Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None, None);
+ Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None);
let mut change = ConfigChange::default();
diff --git a/crates/rust-analyzer/src/diagnostics.rs b/crates/rust-analyzer/src/diagnostics.rs
index 034c49c3d5..5f2871ac99 100644
--- a/crates/rust-analyzer/src/diagnostics.rs
+++ b/crates/rust-analyzer/src/diagnostics.rs
@@ -75,7 +75,7 @@ impl DiagnosticCollection {
flycheck_id: usize,
file_id: FileId,
diagnostic: lsp_types::Diagnostic,
- fix: Option<Fix>,
+ fix: Option<Box<Fix>>,
) {
let diagnostics = self.check.entry(flycheck_id).or_default().entry(file_id).or_default();
for existing_diagnostic in diagnostics.iter() {
@@ -84,8 +84,10 @@ impl DiagnosticCollection {
}
}
- let check_fixes = Arc::make_mut(&mut self.check_fixes);
- check_fixes.entry(flycheck_id).or_default().entry(file_id).or_default().extend(fix);
+ if let Some(fix) = fix {
+ let check_fixes = Arc::make_mut(&mut self.check_fixes);
+ check_fixes.entry(flycheck_id).or_default().entry(file_id).or_default().push(*fix);
+ }
diagnostics.push(diagnostic);
self.changes.insert(file_id);
}
diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs
index 208a70bc02..c3ab7f3ae7 100644
--- a/crates/rust-analyzer/src/diagnostics/to_proto.rs
+++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs
@@ -170,7 +170,7 @@ fn resolve_path(
struct SubDiagnostic {
related: lsp_types::DiagnosticRelatedInformation,
- suggested_fix: Option<Fix>,
+ suggested_fix: Option<Box<Fix>>,
}
enum MappedRustChildDiagnostic {
@@ -241,7 +241,7 @@ fn map_rust_child_diagnostic(
location: location(config, workspace_root, spans[0], snap),
message: message.clone(),
},
- suggested_fix: Some(Fix {
+ suggested_fix: Some(Box::new(Fix {
ranges: spans
.iter()
.map(|&span| location(config, workspace_root, span, snap).range)
@@ -260,7 +260,7 @@ fn map_rust_child_diagnostic(
data: None,
command: None,
},
- }),
+ })),
})
}
}
@@ -269,7 +269,7 @@ fn map_rust_child_diagnostic(
pub(crate) struct MappedRustDiagnostic {
pub(crate) url: lsp_types::Url,
pub(crate) diagnostic: lsp_types::Diagnostic,
- pub(crate) fix: Option<Fix>,
+ pub(crate) fix: Option<Box<Fix>>,
}
/// Converts a Rust root diagnostic to LSP form
@@ -548,7 +548,6 @@ mod tests {
ClientCapabilities::default(),
Vec::new(),
None,
- None,
),
);
let snap = state.snapshot();
diff --git a/crates/rust-analyzer/src/flycheck.rs b/crates/rust-analyzer/src/flycheck.rs
index 8f2e7d1ca2..b035d779a7 100644
--- a/crates/rust-analyzer/src/flycheck.rs
+++ b/crates/rust-analyzer/src/flycheck.rs
@@ -15,20 +15,13 @@ use toolchain::Tool;
use crate::command::{CommandHandle, ParseFromLine};
-#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(crate) enum InvocationStrategy {
Once,
#[default]
PerWorkspace,
}
-#[derive(Clone, Debug, Default, PartialEq, Eq)]
-pub(crate) enum InvocationLocation {
- Root(AbsPathBuf),
- #[default]
- Workspace,
-}
-
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct CargoOptions {
pub(crate) target_triples: Vec<String>,
@@ -37,10 +30,19 @@ pub(crate) struct CargoOptions {
pub(crate) all_features: bool,
pub(crate) features: Vec<String>,
pub(crate) extra_args: Vec<String>,
+ pub(crate) extra_test_bin_args: Vec<String>,
pub(crate) extra_env: FxHashMap<String, String>,
pub(crate) target_dir: Option<Utf8PathBuf>,
}
+#[derive(Clone)]
+pub(crate) enum Target {
+ Bin(String),
+ Example(String),
+ Benchmark(String),
+ Test(String),
+}
+
impl CargoOptions {
pub(crate) fn apply_on_command(&self, cmd: &mut Command) {
for target in &self.target_triples {
@@ -79,7 +81,6 @@ pub(crate) enum FlycheckConfig {
args: Vec<String>,
extra_env: FxHashMap<String, String>,
invocation_strategy: InvocationStrategy,
- invocation_location: InvocationLocation,
},
}
@@ -127,13 +128,13 @@ impl FlycheckHandle {
/// Schedule a re-start of the cargo check worker to do a workspace wide check.
pub(crate) fn restart_workspace(&self, saved_file: Option<AbsPathBuf>) {
- self.sender.send(StateChange::Restart { package: None, saved_file }).unwrap();
+ self.sender.send(StateChange::Restart { package: None, saved_file, target: None }).unwrap();
}
/// Schedule a re-start of the cargo check worker to do a package wide check.
- pub(crate) fn restart_for_package(&self, package: String) {
+ pub(crate) fn restart_for_package(&self, package: String, target: Option<Target>) {
self.sender
- .send(StateChange::Restart { package: Some(package), saved_file: None })
+ .send(StateChange::Restart { package: Some(package), saved_file: None, target })
.unwrap();
}
@@ -191,7 +192,7 @@ pub(crate) enum Progress {
}
enum StateChange {
- Restart { package: Option<String>, saved_file: Option<AbsPathBuf> },
+ Restart { package: Option<String>, saved_file: Option<AbsPathBuf>, target: Option<Target> },
Cancel,
}
@@ -218,6 +219,7 @@ struct FlycheckActor {
status: FlycheckStatus,
}
+#[allow(clippy::large_enum_variant)]
enum Event {
RequestStateChange(StateChange),
CheckEvent(Option<CargoCheckMessage>),
@@ -278,7 +280,7 @@ impl FlycheckActor {
tracing::debug!(flycheck_id = self.id, "flycheck cancelled");
self.cancel_check_process();
}
- Event::RequestStateChange(StateChange::Restart { package, saved_file }) => {
+ Event::RequestStateChange(StateChange::Restart { package, saved_file, target }) => {
// Cancel the previously spawned process
self.cancel_check_process();
while let Ok(restart) = inbox.recv_timeout(Duration::from_millis(50)) {
@@ -288,11 +290,12 @@ impl FlycheckActor {
}
}
- let command =
- match self.check_command(package.as_deref(), saved_file.as_deref()) {
- Some(c) => c,
- None => continue,
- };
+ let Some(command) =
+ self.check_command(package.as_deref(), saved_file.as_deref(), target)
+ else {
+ continue;
+ };
+
let formatted_command = format!("{command:?}");
tracing::debug!(?command, "will restart flycheck");
@@ -388,6 +391,7 @@ impl FlycheckActor {
&self,
package: Option<&str>,
saved_file: Option<&AbsPath>,
+ target: Option<Target>,
) -> Option<Command> {
match &self.config {
FlycheckConfig::CargoCommand { command, options, ansi_color_output } => {
@@ -403,6 +407,15 @@ impl FlycheckActor {
None => cmd.arg("--workspace"),
};
+ if let Some(tgt) = target {
+ match tgt {
+ Target::Bin(tgt) => cmd.arg("--bin").arg(tgt),
+ Target::Example(tgt) => cmd.arg("--example").arg(tgt),
+ Target::Test(tgt) => cmd.arg("--test").arg(tgt),
+ Target::Benchmark(tgt) => cmd.arg("--bench").arg(tgt),
+ };
+ }
+
cmd.arg(if *ansi_color_output {
"--message-format=json-diagnostic-rendered-ansi"
} else {
@@ -423,30 +436,17 @@ impl FlycheckActor {
cmd.args(&options.extra_args);
Some(cmd)
}
- FlycheckConfig::CustomCommand {
- command,
- args,
- extra_env,
- invocation_strategy,
- invocation_location,
- } => {
+ FlycheckConfig::CustomCommand { command, args, extra_env, invocation_strategy } => {
let mut cmd = Command::new(command);
cmd.envs(extra_env);
- match invocation_location {
- InvocationLocation::Workspace => {
- match invocation_strategy {
- InvocationStrategy::Once => {
- cmd.current_dir(&self.root);
- }
- InvocationStrategy::PerWorkspace => {
- // FIXME: cmd.current_dir(&affected_workspace);
- cmd.current_dir(&self.root);
- }
- }
+ match invocation_strategy {
+ InvocationStrategy::Once => {
+ cmd.current_dir(&self.root);
}
- InvocationLocation::Root(root) => {
- cmd.current_dir(root);
+ InvocationStrategy::PerWorkspace => {
+ // FIXME: cmd.current_dir(&affected_workspace);
+ cmd.current_dir(&self.root);
}
}
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index 7a7ec1d77e..9d0082c370 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -380,7 +380,7 @@ impl GlobalState {
|| !self.config.same_source_root_parent_map(&self.local_roots_parent_map)
{
let config_change = {
- let user_config_path = self.config.user_config_path();
+ let user_config_path = Config::user_config_path();
let mut change = ConfigChange::default();
let db = self.analysis_host.raw_database();
@@ -399,7 +399,7 @@ impl GlobalState {
.collect_vec();
for (file_id, (_change_kind, vfs_path)) in modified_ratoml_files {
- if vfs_path == *user_config_path {
+ if vfs_path.as_path() == user_config_path {
change.change_user_config(Some(db.file_text(file_id)));
continue;
}
@@ -667,7 +667,7 @@ impl GlobalStateSnapshot {
for workspace in self.workspaces.iter() {
match &workspace.kind {
ProjectWorkspaceKind::Cargo { cargo, .. }
- | ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _)), .. } => {
+ | ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _, _)), .. } => {
let Some(target_idx) = cargo.target_by_root(path) else {
continue;
};
@@ -696,6 +696,7 @@ impl GlobalStateSnapshot {
};
return Some(TargetSpec::ProjectJson(ProjectJsonTargetSpec {
+ crate_id,
label: build.label,
target_kind: build.target_kind,
shell_runnables: project.runnables().to_owned(),
diff --git a/crates/rust-analyzer/src/handlers/dispatch.rs b/crates/rust-analyzer/src/handlers/dispatch.rs
index a105ec6382..f03de8ce0f 100644
--- a/crates/rust-analyzer/src/handlers/dispatch.rs
+++ b/crates/rust-analyzer/src/handlers/dispatch.rs
@@ -139,16 +139,26 @@ impl RequestDispatcher<'_> {
self.on_with_thread_intent::<true, ALLOW_RETRYING, R>(ThreadIntent::Worker, f)
}
- /// Dispatches a latency-sensitive request onto the thread pool.
+ /// Dispatches a latency-sensitive request onto the thread pool. When the VFS is marked not
+ /// ready this will return a default constructed [`R::Result`].
pub(crate) fn on_latency_sensitive<const ALLOW_RETRYING: bool, R>(
&mut self,
f: fn(GlobalStateSnapshot, R::Params) -> anyhow::Result<R::Result>,
) -> &mut Self
where
- R: lsp_types::request::Request + 'static,
- R::Params: DeserializeOwned + panic::UnwindSafe + Send + fmt::Debug,
- R::Result: Serialize,
+ R: lsp_types::request::Request<
+ Params: DeserializeOwned + panic::UnwindSafe + Send + fmt::Debug,
+ Result: Serialize + Default,
+ > + 'static,
{
+ if !self.global_state.vfs_done {
+ if let Some(lsp_server::Request { id, .. }) =
+ self.req.take_if(|it| it.method == R::METHOD)
+ {
+ self.global_state.respond(lsp_server::Response::new_ok(id, R::Result::default()));
+ }
+ return self;
+ }
self.on_with_thread_intent::<true, ALLOW_RETRYING, R>(ThreadIntent::LatencySensitive, f)
}
diff --git a/crates/rust-analyzer/src/handlers/notification.rs b/crates/rust-analyzer/src/handlers/notification.rs
index de5d1f2313..38b88ff2d0 100644
--- a/crates/rust-analyzer/src/handlers/notification.rs
+++ b/crates/rust-analyzer/src/handlers/notification.rs
@@ -10,16 +10,19 @@ use lsp_types::{
DidOpenTextDocumentParams, DidSaveTextDocumentParams, WorkDoneProgressCancelParams,
};
use paths::Utf8PathBuf;
+use stdx::TupleExt;
use triomphe::Arc;
use vfs::{AbsPathBuf, ChangeKind, VfsPath};
use crate::{
config::{Config, ConfigChange},
+ flycheck::Target,
global_state::{FetchWorkspaceRequest, GlobalState},
lsp::{from_proto, utils::apply_document_changes},
lsp_ext::{self, RunFlycheckParams},
mem_docs::DocumentData,
reload,
+ target_spec::TargetSpec,
};
pub(crate) fn handle_cancel(state: &mut GlobalState, params: CancelParams) -> anyhow::Result<()> {
@@ -287,17 +290,41 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool {
let world = state.snapshot();
let mut updated = false;
let task = move || -> std::result::Result<(), ide::Cancelled> {
- // Trigger flychecks for all workspaces that depend on the saved file
- // Crates containing or depending on the saved file
- let crate_ids: Vec<_> = world
- .analysis
- .crates_for(file_id)?
- .into_iter()
- .flat_map(|id| world.analysis.transitive_rev_deps(id))
- .flatten()
- .unique()
- .collect();
+ // Is the target binary? If so we let flycheck run only for the workspace that contains the crate.
+ let target = TargetSpec::for_file(&world, file_id)?.and_then(|it| {
+ let tgt_kind = it.target_kind();
+ let (tgt_name, crate_id) = match it {
+ TargetSpec::Cargo(c) => (c.target, c.crate_id),
+ TargetSpec::ProjectJson(p) => (p.label, p.crate_id),
+ };
+
+ let tgt = match tgt_kind {
+ project_model::TargetKind::Bin => Target::Bin(tgt_name),
+ project_model::TargetKind::Example => Target::Example(tgt_name),
+ project_model::TargetKind::Test => Target::Test(tgt_name),
+ project_model::TargetKind::Bench => Target::Benchmark(tgt_name),
+ _ => return None,
+ };
+
+ Some((tgt, crate_id))
+ });
+ let crate_ids = match target {
+ // Trigger flychecks for the only crate which the target belongs to
+ Some((_, krate)) => vec![krate],
+ None => {
+ // Trigger flychecks for all workspaces that depend on the saved file
+ // Crates containing or depending on the saved file
+ world
+ .analysis
+ .crates_for(file_id)?
+ .into_iter()
+ .flat_map(|id| world.analysis.transitive_rev_deps(id))
+ .flatten()
+ .unique()
+ .collect::<Vec<_>>()
+ }
+ };
let crate_root_paths: Vec<_> = crate_ids
.iter()
.filter_map(|&crate_id| {
@@ -317,7 +344,7 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool {
let package = match &ws.kind {
project_model::ProjectWorkspaceKind::Cargo { cargo, .. }
| project_model::ProjectWorkspaceKind::DetachedFile {
- cargo: Some((cargo, _)),
+ cargo: Some((cargo, _, _)),
..
} => cargo.packages().find_map(|pkg| {
let has_target_with_root = cargo[pkg]
@@ -346,8 +373,11 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool {
for (id, package) in workspace_ids.clone() {
if id == flycheck.id() {
updated = true;
- match package.filter(|_| !world.config.flycheck_workspace()) {
- Some(package) => flycheck.restart_for_package(package),
+ match package
+ .filter(|_| !world.config.flycheck_workspace() || target.is_some())
+ {
+ Some(package) => flycheck
+ .restart_for_package(package, target.clone().map(TupleExt::head)),
None => flycheck.restart_workspace(saved_file.clone()),
}
continue;
diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs
index 34325ac7a9..1ad5ff0c8c 100644
--- a/crates/rust-analyzer/src/handlers/request.rs
+++ b/crates/rust-analyzer/src/handlers/request.rs
@@ -799,7 +799,7 @@ pub(crate) fn handle_parent_module(
.iter()
.filter_map(|ws| match &ws.kind {
ProjectWorkspaceKind::Cargo { cargo, .. }
- | ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _)), .. } => {
+ | ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _, _)), .. } => {
cargo.parent_manifests(&manifest_path)
}
_ => None,
@@ -1839,7 +1839,7 @@ pub(crate) fn handle_open_docs(
let ws_and_sysroot = snap.workspaces.iter().find_map(|ws| match &ws.kind {
ProjectWorkspaceKind::Cargo { cargo, .. }
- | ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _)), .. } => {
+ | ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _, _)), .. } => {
Some((cargo, &ws.sysroot))
}
ProjectWorkspaceKind::Json { .. } => None,
@@ -2113,9 +2113,9 @@ fn run_rustfmt(
let edition = editions.iter().copied().max();
let line_index = snap.file_line_index(file_id)?;
- let sr = snap.analysis.source_root_id(file_id)?;
+ let source_root_id = snap.analysis.source_root_id(file_id).ok();
- let mut command = match snap.config.rustfmt(Some(sr)) {
+ let mut command = match snap.config.rustfmt(source_root_id) {
RustfmtConfig::Rustfmt { extra_args, enable_range_formatting } => {
// FIXME: Set RUSTUP_TOOLCHAIN
let mut cmd = process::Command::new(toolchain::Tool::Rustfmt.path());
@@ -2303,7 +2303,7 @@ pub(crate) fn internal_testing_fetch_config(
.transpose()?;
serde_json::to_value(match &*params.config {
"local" => state.config.assist(source_root).assist_emit_must_use,
- "global" => matches!(
+ "workspace" => matches!(
state.config.rustfmt(source_root),
RustfmtConfig::Rustfmt { enable_range_formatting: true, .. }
),
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 1d4ee71e5c..616d6b4935 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -689,7 +689,7 @@ impl GlobalState {
self.fetch_workspaces_queue
.op_completed(Some((workspaces, force_reload_crate_graph)));
if let Err(e) = self.fetch_workspace_error() {
- error!("FetchWorkspaceError:\n{e}");
+ error!("FetchWorkspaceError: {e}");
}
self.wants_to_switch = Some("fetched workspace".to_owned());
(Progress::End, None)
@@ -729,7 +729,7 @@ impl GlobalState {
BuildDataProgress::End(build_data_result) => {
self.fetch_build_data_queue.op_completed(build_data_result);
if let Err(e) = self.fetch_build_data_error() {
- error!("FetchBuildDataError:\n{e}");
+ error!("FetchBuildDataError: {e}");
}
if self.wants_to_switch.is_none() {
diff --git a/crates/rust-analyzer/src/op_queue.rs b/crates/rust-analyzer/src/op_queue.rs
index 99f9e9829c..eab9733872 100644
--- a/crates/rust-analyzer/src/op_queue.rs
+++ b/crates/rust-analyzer/src/op_queue.rs
@@ -3,6 +3,26 @@
pub(crate) type Cause = String;
+/// A single-item queue that allows callers to request an operation to
+/// be performed later.
+///
+/// ```
+/// let queue = OpQueue::default();
+///
+/// // Request work to be done.
+/// queue.request_op("user pushed a button", ());
+///
+/// // In a later iteration of the server loop, we start the work.
+/// if let Some((_cause, ())) = queue.should_start_op() {
+/// dbg!("Some slow operation here");
+/// }
+///
+/// // In an even later iteration of the server loop, we can see that the work
+/// // was completed.
+/// if !queue.op_in_progress() {
+/// dbg!("Work has been done!");
+/// }
+/// ```
#[derive(Debug)]
pub(crate) struct OpQueue<Args = (), Output = ()> {
op_requested: Option<(Cause, Args)>,
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index dee34b1b39..68366136ed 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -61,6 +61,10 @@ pub(crate) enum ProcMacroProgress {
}
impl GlobalState {
+ /// Is the server quiescent?
+ ///
+ /// This indicates that we've fully loaded the projects and
+ /// are ready to do semantic work.
pub(crate) fn is_quiescent(&self) -> bool {
self.vfs_done
&& self.last_reported_status.is_some()
@@ -71,6 +75,15 @@ impl GlobalState {
&& self.vfs_progress_config_version >= self.vfs_config_version
}
+ /// Is the server ready to respond to analysis dependent LSP requests?
+ ///
+ /// Unlike `is_quiescent`, this returns false when we're indexing
+ /// the project, because we're holding the salsa lock and cannot
+ /// respond to LSP requests that depend on salsa data.
+ fn is_fully_ready(&self) -> bool {
+ self.is_quiescent() && !self.prime_caches_queue.op_in_progress()
+ }
+
pub(crate) fn update_configuration(&mut self, config: Config) {
let _p = tracing::info_span!("GlobalState::update_configuration").entered();
let old_config = mem::replace(&mut self.config, Arc::new(config));
@@ -102,13 +115,15 @@ impl GlobalState {
}
pub(crate) fn current_status(&self) -> lsp_ext::ServerStatusParams {
- let quiescent = self.is_quiescent();
- let mut status =
- lsp_ext::ServerStatusParams { health: lsp_ext::Health::Ok, quiescent, message: None };
+ let mut status = lsp_ext::ServerStatusParams {
+ health: lsp_ext::Health::Ok,
+ quiescent: self.is_fully_ready(),
+ message: None,
+ };
let mut message = String::new();
- if !self.config.cargo_autoreload(None)
- && quiescent
+ if !self.config.cargo_autoreload()
+ && self.is_quiescent()
&& self.fetch_workspaces_queue.op_requested()
&& self.config.discover_workspace_config().is_none()
{
@@ -165,6 +180,19 @@ impl GlobalState {
self.proc_macro_clients.iter().map(Some).chain(iter::repeat_with(|| None));
for (ws, proc_macro_client) in self.workspaces.iter().zip(proc_macro_clients) {
+ if let ProjectWorkspaceKind::Cargo { error: Some(error), .. }
+ | ProjectWorkspaceKind::DetachedFile {
+ cargo: Some((_, _, Some(error))), ..
+ } = &ws.kind
+ {
+ status.health |= lsp_ext::Health::Warning;
+ format_to!(
+ message,
+ "Failed to read Cargo metadata with dependencies for `{}`: {:#}\n\n",
+ ws.manifest_or_root(),
+ error
+ );
+ }
if let Some(err) = ws.sysroot.error() {
status.health |= lsp_ext::Health::Warning;
format_to!(
@@ -537,8 +565,25 @@ impl GlobalState {
.collect()
};
+ // Also explicitly watch any build files configured in JSON project files.
+ for ws in self.workspaces.iter() {
+ if let ProjectWorkspaceKind::Json(project_json) = &ws.kind {
+ for (_, krate) in project_json.crates() {
+ let Some(build) = &krate.build else {
+ continue;
+ };
+ watchers.push(lsp_types::FileSystemWatcher {
+ glob_pattern: lsp_types::GlobPattern::String(
+ build.build_file.to_string(),
+ ),
+ kind: None,
+ });
+ }
+ }
+ }
+
watchers.extend(
- iter::once(self.config.user_config_path().as_path())
+ iter::once(Config::user_config_path())
.chain(self.workspaces.iter().map(|ws| ws.manifest().map(ManifestPath::as_ref)))
.flatten()
.map(|glob_pattern| lsp_types::FileSystemWatcher {
@@ -752,18 +797,22 @@ impl GlobalState {
FlycheckConfig::CargoCommand { .. } => {
crate::flycheck::InvocationStrategy::PerWorkspace
}
- FlycheckConfig::CustomCommand { invocation_strategy, .. } => invocation_strategy,
+ FlycheckConfig::CustomCommand { ref invocation_strategy, .. } => {
+ invocation_strategy.clone()
+ }
};
self.flycheck = match invocation_strategy {
- crate::flycheck::InvocationStrategy::Once => vec![FlycheckHandle::spawn(
- 0,
- sender,
- config,
- None,
- self.config.root_path().clone(),
- None,
- )],
+ crate::flycheck::InvocationStrategy::Once => {
+ vec![FlycheckHandle::spawn(
+ 0,
+ sender,
+ config,
+ None,
+ self.config.root_path().clone(),
+ None,
+ )]
+ }
crate::flycheck::InvocationStrategy::PerWorkspace => {
self.workspaces
.iter()
@@ -774,7 +823,7 @@ impl GlobalState {
match &ws.kind {
ProjectWorkspaceKind::Cargo { cargo, .. }
| ProjectWorkspaceKind::DetachedFile {
- cargo: Some((cargo, _)),
+ cargo: Some((cargo, _, _)),
..
} => (cargo.workspace_root(), Some(cargo.manifest_path())),
ProjectWorkspaceKind::Json(project) => {
diff --git a/crates/rust-analyzer/src/target_spec.rs b/crates/rust-analyzer/src/target_spec.rs
index 965fd415e9..954e13cbf2 100644
--- a/crates/rust-analyzer/src/target_spec.rs
+++ b/crates/rust-analyzer/src/target_spec.rs
@@ -62,6 +62,7 @@ pub(crate) struct CargoTargetSpec {
#[derive(Clone, Debug)]
pub(crate) struct ProjectJsonTargetSpec {
+ pub(crate) crate_id: CrateId,
pub(crate) label: String,
pub(crate) target_kind: TargetKind,
pub(crate) shell_runnables: Vec<Runnable>,
diff --git a/crates/rust-analyzer/src/test_runner.rs b/crates/rust-analyzer/src/test_runner.rs
index 293cff4743..5e43a3c60d 100644
--- a/crates/rust-analyzer/src/test_runner.rs
+++ b/crates/rust-analyzer/src/test_runner.rs
@@ -102,6 +102,11 @@ impl CargoTestHandle {
}
cmd.args(["-Z", "unstable-options"]);
cmd.arg("--format=json");
+
+ for extra_arg in options.extra_test_bin_args {
+ cmd.arg(extra_arg);
+ }
+
Ok(Self { _handle: CommandHandle::spawn(cmd, sender)? })
}
}
diff --git a/crates/rust-analyzer/tests/crate_graph.rs b/crates/rust-analyzer/tests/crate_graph.rs
index b8a82fd6a7..04b6713b8d 100644
--- a/crates/rust-analyzer/tests/crate_graph.rs
+++ b/crates/rust-analyzer/tests/crate_graph.rs
@@ -20,6 +20,7 @@ fn load_cargo_with_fake_sysroot(file: &str) -> ProjectWorkspace {
build_scripts: WorkspaceBuildScripts::default(),
rustc: Err(None),
cargo_config_extra_env: Default::default(),
+ error: None,
},
sysroot: get_fake_sysroot(),
rustc_cfg: Vec::new(),
@@ -93,7 +94,7 @@ fn test_deduplicate_origin_dev() {
}
}
- assert!(crates_named_p2.len() == 1);
+ assert_eq!(crates_named_p2.len(), 1);
let p2 = crates_named_p2[0];
assert!(p2.origin.is_local());
}
@@ -119,7 +120,7 @@ fn test_deduplicate_origin_dev_rev() {
}
}
- assert!(crates_named_p2.len() == 1);
+ assert_eq!(crates_named_p2.len(), 1);
let p2 = crates_named_p2[0];
assert!(p2.origin.is_local());
}
diff --git a/crates/rust-analyzer/tests/slow-tests/ratoml.rs b/crates/rust-analyzer/tests/slow-tests/ratoml.rs
index c06ba9eee1..295d1d4e8e 100644
--- a/crates/rust-analyzer/tests/slow-tests/ratoml.rs
+++ b/crates/rust-analyzer/tests/slow-tests/ratoml.rs
@@ -8,6 +8,7 @@ use lsp_types::{
};
use paths::Utf8PathBuf;
+use rust_analyzer::config::Config;
use rust_analyzer::lsp::ext::{InternalTestingFetchConfig, InternalTestingFetchConfigParams};
use serde_json::json;
use test_utils::skip_slow_tests;
@@ -17,14 +18,13 @@ enum QueryType {
/// A query whose config key is a part of the global configs, so that
/// testing for changes to this config means testing if global changes
/// take affect.
- Global,
+ Workspace,
}
struct RatomlTest {
urls: Vec<Url>,
server: Server,
tmp_path: Utf8PathBuf,
- user_config_dir: Utf8PathBuf,
}
impl RatomlTest {
@@ -41,11 +41,7 @@ impl RatomlTest {
let full_fixture = fixtures.join("\n");
- let user_cnf_dir = TestDir::new();
- let user_config_dir = user_cnf_dir.path().to_owned();
-
- let mut project =
- Project::with_fixture(&full_fixture).tmp_dir(tmp_dir).user_config_dir(user_cnf_dir);
+ let mut project = Project::with_fixture(&full_fixture).tmp_dir(tmp_dir);
for root in roots {
project = project.root(root);
@@ -57,7 +53,7 @@ impl RatomlTest {
let server = project.server().wait_until_workspace_is_loaded();
- let mut case = Self { urls: vec![], server, tmp_path, user_config_dir };
+ let mut case = Self { urls: vec![], server, tmp_path };
let urls = fixtures.iter().map(|fixture| case.fixture_path(fixture)).collect::<Vec<_>>();
case.urls = urls;
case
@@ -81,7 +77,7 @@ impl RatomlTest {
let mut spl = spl.into_iter();
if let Some(first) = spl.next() {
if first == "$$CONFIG_DIR$$" {
- path = self.user_config_dir.clone();
+ path = Config::user_config_path().unwrap().to_path_buf().into();
} else {
path = path.join(first);
}
@@ -165,7 +161,7 @@ impl RatomlTest {
fn query(&self, query: QueryType, source_file_idx: usize) -> bool {
let config = match query {
QueryType::Local => "local".to_owned(),
- QueryType::Global => "global".to_owned(),
+ QueryType::Workspace => "workspace".to_owned(),
};
let res = self.server.send_request::<InternalTestingFetchConfig>(
InternalTestingFetchConfigParams {
@@ -823,10 +819,8 @@ fn ratoml_multiple_ratoml_in_single_source_root() {
// assert!(!server.query(QueryType::AssistEmitMustUse, 5));
// }
-/// Having a ratoml file at the root of a project enables
-/// configuring global level configurations as well.
#[test]
-fn ratoml_in_root_is_global() {
+fn ratoml_in_root_is_workspace() {
if skip_slow_tests() {
return;
}
@@ -854,7 +848,7 @@ fn main() {
None,
);
- assert!(server.query(QueryType::Global, 2));
+ assert!(server.query(QueryType::Workspace, 2));
}
#[test]
@@ -886,9 +880,9 @@ fn main() {
None,
);
- assert!(server.query(QueryType::Global, 2));
+ assert!(server.query(QueryType::Workspace, 2));
server.edit(1, "rustfmt.rangeFormatting.enable = false".to_owned());
- assert!(!server.query(QueryType::Global, 2));
+ assert!(!server.query(QueryType::Workspace, 2));
}
#[test]
@@ -920,7 +914,7 @@ fn main() {
None,
);
- assert!(server.query(QueryType::Global, 2));
+ assert!(server.query(QueryType::Workspace, 2));
server.delete(1);
- assert!(!server.query(QueryType::Global, 2));
+ assert!(!server.query(QueryType::Workspace, 2));
}
diff --git a/crates/rust-analyzer/tests/slow-tests/support.rs b/crates/rust-analyzer/tests/slow-tests/support.rs
index 081ee5fa3e..06ce984681 100644
--- a/crates/rust-analyzer/tests/slow-tests/support.rs
+++ b/crates/rust-analyzer/tests/slow-tests/support.rs
@@ -8,6 +8,7 @@ use std::{
use crossbeam_channel::{after, select, Receiver};
use lsp_server::{Connection, Message, Notification, Request};
use lsp_types::{notification::Exit, request::Shutdown, TextDocumentIdentifier, Url};
+use parking_lot::{Mutex, MutexGuard};
use paths::{Utf8Path, Utf8PathBuf};
use rust_analyzer::{
config::{Config, ConfigChange, ConfigErrors},
@@ -27,7 +28,6 @@ pub(crate) struct Project<'a> {
roots: Vec<Utf8PathBuf>,
config: serde_json::Value,
root_dir_contains_symlink: bool,
- user_config_path: Option<Utf8PathBuf>,
}
impl Project<'_> {
@@ -51,15 +51,9 @@ impl Project<'_> {
}
}),
root_dir_contains_symlink: false,
- user_config_path: None,
}
}
- pub(crate) fn user_config_dir(mut self, config_path_dir: TestDir) -> Self {
- self.user_config_path = Some(config_path_dir.path().to_owned());
- self
- }
-
pub(crate) fn tmp_dir(mut self, tmp_dir: TestDir) -> Self {
self.tmp_dir = Some(tmp_dir);
self
@@ -91,6 +85,7 @@ impl Project<'_> {
}
pub(crate) fn server(self) -> Server {
+ static CONFIG_DIR_LOCK: Mutex<()> = Mutex::new(());
let tmp_dir = self.tmp_dir.unwrap_or_else(|| {
if self.root_dir_contains_symlink {
TestDir::new_symlink()
@@ -122,9 +117,13 @@ impl Project<'_> {
assert!(mini_core.is_none());
assert!(toolchain.is_none());
+ let mut config_dir_guard = None;
for entry in fixture {
if let Some(pth) = entry.path.strip_prefix("/$$CONFIG_DIR$$") {
- let path = self.user_config_path.clone().unwrap().join(&pth['/'.len_utf8()..]);
+ if config_dir_guard.is_none() {
+ config_dir_guard = Some(CONFIG_DIR_LOCK.lock());
+ }
+ let path = Config::user_config_path().unwrap().join(&pth['/'.len_utf8()..]);
fs::create_dir_all(path.parent().unwrap()).unwrap();
fs::write(path.as_path(), entry.text.as_bytes()).unwrap();
} else {
@@ -201,7 +200,6 @@ impl Project<'_> {
},
roots,
None,
- self.user_config_path,
);
let mut change = ConfigChange::default();
@@ -213,7 +211,7 @@ impl Project<'_> {
config.rediscover_workspaces();
- Server::new(tmp_dir.keep(), config)
+ Server::new(config_dir_guard, tmp_dir.keep(), config)
}
}
@@ -228,10 +226,15 @@ pub(crate) struct Server {
client: Connection,
/// XXX: remove the tempdir last
dir: TestDir,
+ _config_dir_guard: Option<MutexGuard<'static, ()>>,
}
impl Server {
- fn new(dir: TestDir, config: Config) -> Server {
+ fn new(
+ config_dir_guard: Option<MutexGuard<'static, ()>>,
+ dir: TestDir,
+ config: Config,
+ ) -> Server {
let (connection, client) = Connection::memory();
let _thread = stdx::thread::Builder::new(stdx::thread::ThreadIntent::Worker)
@@ -239,7 +242,14 @@ impl Server {
.spawn(move || main_loop(config, connection).unwrap())
.expect("failed to spawn a thread");
- Server { req_id: Cell::new(1), dir, messages: Default::default(), client, _thread }
+ Server {
+ req_id: Cell::new(1),
+ dir,
+ messages: Default::default(),
+ client,
+ _thread,
+ _config_dir_guard: config_dir_guard,
+ }
}
pub(crate) fn doc_id(&self, rel_path: &str) -> TextDocumentIdentifier {
diff --git a/crates/salsa/LICENSE-APACHE b/crates/salsa/LICENSE-APACHE
index 16fe87b06e..1b5ec8b78e 100644
--- a/crates/salsa/LICENSE-APACHE
+++ b/crates/salsa/LICENSE-APACHE
@@ -174,28 +174,3 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
-
-APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
-Copyright [yyyy] [name of copyright owner]
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
diff --git a/crates/syntax-bridge/src/lib.rs b/crates/syntax-bridge/src/lib.rs
index b0afd245c5..56e43e82ed 100644
--- a/crates/syntax-bridge/src/lib.rs
+++ b/crates/syntax-bridge/src/lib.rs
@@ -301,13 +301,11 @@ where
};
}
let leaf: tt::Leaf<_> = match kind {
- T![true] | T![false] => make_ident!(),
- IDENT => {
+ k if k.is_any_identifier() => {
let text = token.to_text(conv);
tt::Ident::new(&text, conv.span_for(abs_range)).into()
}
UNDERSCORE => make_ident!(),
- k if k.is_keyword() => make_ident!(),
k if k.is_literal() => {
let text = token.to_text(conv);
let span = conv.span_for(abs_range);
diff --git a/crates/syntax-bridge/src/to_parser_input.rs b/crates/syntax-bridge/src/to_parser_input.rs
index 2c54899268..14216e3093 100644
--- a/crates/syntax-bridge/src/to_parser_input.rs
+++ b/crates/syntax-bridge/src/to_parser_input.rs
@@ -64,14 +64,12 @@ pub fn to_parser_input<S: Copy + fmt::Debug>(
"_" => res.push(T![_]),
i if i.starts_with('\'') => res.push(LIFETIME_IDENT),
_ if ident.is_raw.yes() => res.push(IDENT),
- "gen" if !edition.at_least_2024() => res.push(IDENT),
- "dyn" if !edition.at_least_2018() => res.push_ident(DYN_KW),
- "async" | "await" | "try" if !edition.at_least_2018() => res.push(IDENT),
- text => match SyntaxKind::from_keyword(text) {
+ text => match SyntaxKind::from_keyword(text, edition) {
Some(kind) => res.push(kind),
None => {
- let contextual_keyword = SyntaxKind::from_contextual_keyword(text)
- .unwrap_or(SyntaxKind::IDENT);
+ let contextual_keyword =
+ SyntaxKind::from_contextual_keyword(text, edition)
+ .unwrap_or(SyntaxKind::IDENT);
res.push_ident(contextual_keyword);
}
},
diff --git a/crates/syntax/Cargo.toml b/crates/syntax/Cargo.toml
index 994c21469f..fcb9b0ea35 100644
--- a/crates/syntax/Cargo.toml
+++ b/crates/syntax/Cargo.toml
@@ -16,9 +16,8 @@ doctest = false
cov-mark = "2.0.0-pre.1"
either.workspace = true
itertools.workspace = true
-rowan = "0.15.15"
+rowan = "=0.15.15"
rustc-hash.workspace = true
-once_cell = "1.17.0"
indexmap.workspace = true
smol_str.workspace = true
triomphe.workspace = true
diff --git a/crates/syntax/rust.ungram b/crates/syntax/rust.ungram
index c23bcd6914..43375ce6ae 100644
--- a/crates/syntax/rust.ungram
+++ b/crates/syntax/rust.ungram
@@ -9,8 +9,6 @@
// // -- comment
// Name = -- non-terminal definition
// 'ident' -- keyword or punct token (terminal)
-// '?ident' -- contextual keyword (terminal)
-// too)
// '#ident' -- generic token (terminal)
// '@ident' -- literal token (terminal)
// A B -- sequence
@@ -40,8 +38,12 @@ PathSegment =
'::'? NameRef
| NameRef GenericArgList?
| NameRef ParamList RetType?
+| NameRef ReturnTypeSyntax
| '<' Type ('as' PathType)? '>'
+ReturnTypeSyntax =
+ '(' '..' ')'
+
//*************************//
// Generics //
@@ -61,7 +63,7 @@ TypeArg =
AssocTypeArg =
NameRef
- (GenericArgList | ParamList RetType?)?
+ (GenericArgList | ParamList RetType? | ReturnTypeSyntax)?
(':' TypeBoundList | ('=' Type | ConstArg))
LifetimeArg =
@@ -152,7 +154,7 @@ Item =
MacroRules =
Attr* Visibility?
- '?macro_rules' '!' Name
+ 'macro_rules' '!' Name
TokenTree
MacroDef =
@@ -188,7 +190,7 @@ UseTreeList =
Fn =
Attr* Visibility?
- '?default'? 'const'? 'async'? 'unsafe'? Abi?
+ 'default'? 'const'? 'async'? 'gen'? 'unsafe'? Abi?
'fn' Name GenericParamList? ParamList RetType? WhereClause?
(body:BlockExpr | ';')
@@ -220,7 +222,7 @@ RetType =
TypeAlias =
Attr* Visibility?
- '?default'?
+ 'default'?
'type' Name GenericParamList? (':' TypeBoundList?)? WhereClause?
('=' Type)? ';'
@@ -263,7 +265,7 @@ Variant =
Union =
Attr* Visibility?
- '?union' Name GenericParamList? WhereClause?
+ 'union' Name GenericParamList? WhereClause?
RecordFieldList
// A Data Type.
@@ -276,7 +278,7 @@ Adt =
Const =
Attr* Visibility?
- '?default'?
+ 'default'?
'const' (Name | '_') ':' Type
('=' body:Expr)? ';'
@@ -287,7 +289,7 @@ Static =
Trait =
Attr* Visibility?
- 'unsafe'? '?auto'?
+ 'unsafe'? 'auto'?
'trait' Name GenericParamList?
(':' TypeBoundList?)? WhereClause? AssocItemList
@@ -306,7 +308,7 @@ AssocItem =
Impl =
Attr* Visibility?
- '?default'? 'unsafe'?
+ 'default'? 'unsafe'?
'impl' GenericParamList? ('const'? '!'? trait:Type 'for')? self_ty:Type WhereClause?
AssocItemList
@@ -387,13 +389,13 @@ Expr =
| UnderscoreExpr
OffsetOfExpr =
- Attr* '?builtin' '#' '?offset_of' '(' Type ',' fields:(NameRef ('.' NameRef)* ) ')'
+ Attr* 'builtin' '#' 'offset_of' '(' Type ',' fields:(NameRef ('.' NameRef)* ) ')'
AsmExpr =
- Attr* '?builtin' '#' '?asm' '(' Expr ')'
+ Attr* 'builtin' '#' 'asm' '(' Expr ')'
FormatArgsExpr =
- Attr* '?builtin' '#' '?format_args' '('
+ Attr* 'builtin' '#' 'format_args' '('
template:Expr
(',' args:(FormatArgsArg (',' FormatArgsArg)* ','?)? )?
')'
@@ -425,7 +427,7 @@ StmtList =
'}'
RefExpr =
- Attr* '&' (('?raw' 'const'?)| ('?raw'? 'mut') ) Expr
+ Attr* '&' (('raw' 'const'?)| ('raw'? 'mut') ) Expr
TryExpr =
Attr* Expr '?'
@@ -550,7 +552,7 @@ YieldExpr =
Attr* 'yield' Expr?
YeetExpr =
- Attr* 'do' '?yeet' Expr?
+ Attr* 'do' 'yeet' Expr?
LetExpr =
Attr* 'let' Pat '=' Expr
diff --git a/crates/syntax/src/ast/edit.rs b/crates/syntax/src/ast/edit.rs
index 5bc6b780e4..de40d638be 100644
--- a/crates/syntax/src/ast/edit.rs
+++ b/crates/syntax/src/ast/edit.rs
@@ -8,7 +8,7 @@ use crate::{
ted, AstToken, NodeOrToken, SyntaxElement, SyntaxNode, SyntaxToken,
};
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IndentLevel(pub u8);
impl From<u8> for IndentLevel {
diff --git a/crates/syntax/src/ast/generated/nodes.rs b/crates/syntax/src/ast/generated/nodes.rs
index 01886d119d..c9b39e9922 100644
--- a/crates/syntax/src/ast/generated/nodes.rs
+++ b/crates/syntax/src/ast/generated/nodes.rs
@@ -114,6 +114,8 @@ impl AssocTypeArg {
#[inline]
pub fn ret_type(&self) -> Option<RetType> { support::child(&self.syntax) }
#[inline]
+ pub fn return_type_syntax(&self) -> Option<ReturnTypeSyntax> { support::child(&self.syntax) }
+ #[inline]
pub fn ty(&self) -> Option<Type> { support::child(&self.syntax) }
#[inline]
pub fn eq_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![=]) }
@@ -484,6 +486,8 @@ impl Fn {
#[inline]
pub fn fn_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![fn]) }
#[inline]
+ pub fn gen_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![gen]) }
+ #[inline]
pub fn unsafe_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![unsafe]) }
}
@@ -1221,6 +1225,8 @@ impl PathSegment {
#[inline]
pub fn ret_type(&self) -> Option<RetType> { support::child(&self.syntax) }
#[inline]
+ pub fn return_type_syntax(&self) -> Option<ReturnTypeSyntax> { support::child(&self.syntax) }
+ #[inline]
pub fn ty(&self) -> Option<Type> { support::child(&self.syntax) }
#[inline]
pub fn coloncolon_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![::]) }
@@ -1486,6 +1492,19 @@ impl ReturnExpr {
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ReturnTypeSyntax {
+ pub(crate) syntax: SyntaxNode,
+}
+impl ReturnTypeSyntax {
+ #[inline]
+ pub fn l_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['(']) }
+ #[inline]
+ pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) }
+ #[inline]
+ pub fn dotdot_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![..]) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SelfParam {
pub(crate) syntax: SyntaxNode,
}
@@ -3697,6 +3716,20 @@ impl AstNode for ReturnExpr {
#[inline]
fn syntax(&self) -> &SyntaxNode { &self.syntax }
}
+impl AstNode for ReturnTypeSyntax {
+ #[inline]
+ fn can_cast(kind: SyntaxKind) -> bool { kind == RETURN_TYPE_SYNTAX }
+ #[inline]
+ fn cast(syntax: SyntaxNode) -> Option<Self> {
+ if Self::can_cast(syntax.kind()) {
+ Some(Self { syntax })
+ } else {
+ None
+ }
+ }
+ #[inline]
+ fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
impl AstNode for SelfParam {
#[inline]
fn can_cast(kind: SyntaxKind) -> bool { kind == SELF_PARAM }
@@ -6609,6 +6642,11 @@ impl std::fmt::Display for ReturnExpr {
std::fmt::Display::fmt(self.syntax(), f)
}
}
+impl std::fmt::Display for ReturnTypeSyntax {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ std::fmt::Display::fmt(self.syntax(), f)
+ }
+}
impl std::fmt::Display for SelfParam {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f)
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index 0228d9dd71..abf1a1f382 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -117,7 +117,7 @@ pub fn name_ref(name_ref: &str) -> ast::NameRef {
ast_from_text(&format!("fn f() {{ {raw_escape}{name_ref}; }}"))
}
fn raw_ident_esc(ident: &str) -> &'static str {
- if is_raw_identifier(ident) {
+ if is_raw_identifier(ident, Edition::CURRENT) {
"r#"
} else {
""
@@ -1035,6 +1035,7 @@ pub fn fn_(
is_async: bool,
is_const: bool,
is_unsafe: bool,
+ is_gen: bool,
) -> ast::Fn {
let type_params = match type_params {
Some(type_params) => format!("{type_params}"),
@@ -1056,9 +1057,10 @@ pub fn fn_(
let async_literal = if is_async { "async " } else { "" };
let const_literal = if is_const { "const " } else { "" };
let unsafe_literal = if is_unsafe { "unsafe " } else { "" };
+ let gen_literal = if is_gen { "gen " } else { "" };
ast_from_text(&format!(
- "{visibility}{async_literal}{const_literal}{unsafe_literal}fn {fn_name}{type_params}{params} {ret_type}{where_clause}{body}",
+ "{visibility}{const_literal}{async_literal}{gen_literal}{unsafe_literal}fn {fn_name}{type_params}{params} {ret_type}{where_clause}{body}",
))
}
pub fn struct_(
@@ -1152,12 +1154,13 @@ pub fn token(kind: SyntaxKind) -> SyntaxToken {
}
pub mod tokens {
- use once_cell::sync::Lazy;
+ use std::sync::LazyLock;
+
use parser::Edition;
use crate::{ast, AstNode, Parse, SourceFile, SyntaxKind::*, SyntaxToken};
- pub(super) static SOURCE_FILE: Lazy<Parse<SourceFile>> = Lazy::new(|| {
+ pub(super) static SOURCE_FILE: LazyLock<Parse<SourceFile>> = LazyLock::new(|| {
SourceFile::parse(
"const C: <()>::Item = ( true && true , true || true , 1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p, &p , &mut p, { let _ @ [] })\n;\n\nimpl A for B where: {}", Edition::CURRENT,
)
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs
index 5447906206..693bfe330b 100644
--- a/crates/syntax/src/ast/node_ext.rs
+++ b/crates/syntax/src/ast/node_ext.rs
@@ -17,7 +17,7 @@ use crate::{
ted, NodeOrToken, SmolStr, SyntaxElement, SyntaxToken, TokenText, T,
};
-use super::{RangeItem, RangeOp};
+use super::{GenericParam, RangeItem, RangeOp};
impl ast::Lifetime {
pub fn text(&self) -> TokenText<'_> {
@@ -822,6 +822,15 @@ pub enum TypeOrConstParam {
Const(ast::ConstParam),
}
+impl From<TypeOrConstParam> for GenericParam {
+ fn from(value: TypeOrConstParam) -> Self {
+ match value {
+ TypeOrConstParam::Type(it) => GenericParam::TypeParam(it),
+ TypeOrConstParam::Const(it) => GenericParam::ConstParam(it),
+ }
+ }
+}
+
impl TypeOrConstParam {
pub fn name(&self) -> Option<ast::Name> {
match self {
diff --git a/crates/syntax/src/hacks.rs b/crates/syntax/src/hacks.rs
index 36615d11d8..9e63448ce9 100644
--- a/crates/syntax/src/hacks.rs
+++ b/crates/syntax/src/hacks.rs
@@ -6,9 +6,9 @@ use parser::Edition;
use crate::{ast, AstNode};
-pub fn parse_expr_from_str(s: &str) -> Option<ast::Expr> {
+pub fn parse_expr_from_str(s: &str, edition: Edition) -> Option<ast::Expr> {
let s = s.trim();
- let file = ast::SourceFile::parse(&format!("const _: () = {s};"), Edition::CURRENT);
+ let file = ast::SourceFile::parse(&format!("const _: () = {s};"), edition);
let expr = file.syntax_node().descendants().find_map(ast::Expr::cast)?;
if expr.syntax().text() != s {
return None;
diff --git a/crates/syntax/src/utils.rs b/crates/syntax/src/utils.rs
index a38f8b2b55..d1f60f0b71 100644
--- a/crates/syntax/src/utils.rs
+++ b/crates/syntax/src/utils.rs
@@ -2,7 +2,8 @@
use crate::SyntaxKind;
-pub fn is_raw_identifier(name: &str) -> bool {
- let is_keyword = SyntaxKind::from_keyword(name).is_some();
+#[inline]
+pub fn is_raw_identifier(name: &str, edition: parser::Edition) -> bool {
+ let is_keyword = SyntaxKind::from_keyword(name, edition).is_some();
is_keyword && !matches!(name, "self" | "crate" | "super" | "Self")
}
diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs
index 2d615c34a3..3be4469bee 100644
--- a/crates/test-utils/src/minicore.rs
+++ b/crates/test-utils/src/minicore.rs
@@ -63,6 +63,7 @@
//! unsize: sized
//! todo: panic
//! unimplemented: panic
+//! column:
#![rustc_coherence_is_core]
@@ -1195,6 +1196,7 @@ pub mod future {
#[doc(notable_trait)]
#[lang = "future_trait"]
pub trait Future {
+ #[lang = "future_output"]
type Output;
#[lang = "poll"]
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
@@ -1292,6 +1294,7 @@ pub mod iter {
mod traits {
mod iterator {
#[doc(notable_trait)]
+ #[lang = "iterator"]
pub trait Iterator {
type Item;
#[lang = "next"]
@@ -1615,6 +1618,14 @@ pub mod error {
}
// endregion:error
+// region:column
+#[rustc_builtin_macro]
+#[macro_export]
+macro_rules! column {
+ () => {};
+}
+// endregion:column
+
pub mod prelude {
pub mod v1 {
pub use crate::{
diff --git a/crates/tt/src/lib.rs b/crates/tt/src/lib.rs
index 7b72f9ff10..8d915d0a51 100644
--- a/crates/tt/src/lib.rs
+++ b/crates/tt/src/lib.rs
@@ -603,7 +603,7 @@ pub fn pretty<S>(tkns: &[TokenTree<S>]) -> String {
TokenTree::Leaf(Leaf::Ident(ident)) => {
format!("{}{}", ident.is_raw.as_str(), ident.sym)
}
- TokenTree::Leaf(Leaf::Literal(literal)) => literal.symbol.as_str().to_owned(),
+ TokenTree::Leaf(Leaf::Literal(literal)) => format!("{literal}"),
TokenTree::Leaf(Leaf::Punct(punct)) => format!("{}", punct.char),
TokenTree::Subtree(subtree) => {
let content = pretty(&subtree.token_trees);
diff --git a/crates/vfs/src/vfs_path.rs b/crates/vfs/src/vfs_path.rs
index 92a49e0793..3c8e37413f 100644
--- a/crates/vfs/src/vfs_path.rs
+++ b/crates/vfs/src/vfs_path.rs
@@ -313,6 +313,20 @@ impl fmt::Debug for VfsPathRepr {
}
}
+impl PartialEq<AbsPath> for VfsPath {
+ fn eq(&self, other: &AbsPath) -> bool {
+ match &self.0 {
+ VfsPathRepr::PathBuf(lhs) => lhs == other,
+ VfsPathRepr::VirtualPath(_) => false,
+ }
+ }
+}
+impl PartialEq<VfsPath> for AbsPath {
+ fn eq(&self, other: &VfsPath) -> bool {
+ other == self
+ }
+}
+
/// `/`-separated virtual path.
///
/// This is used to describe files that do not reside on the file system.
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index 2be338dd4d..e4a8c6493a 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -45,22 +45,14 @@ Automatically refresh project info via `cargo metadata` on
--
Run build scripts (`build.rs`) for more precise code analysis.
--
-[[rust-analyzer.cargo.buildScripts.invocationLocation]]rust-analyzer.cargo.buildScripts.invocationLocation (default: `"workspace"`)::
-+
---
-Specifies the working directory for running build scripts.
-- "workspace": run build scripts for a workspace in the workspace's root directory.
- This is incompatible with `#rust-analyzer.cargo.buildScripts.invocationStrategy#` set to `once`.
-- "root": run build scripts in the project's root directory.
-This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`
-is set.
---
[[rust-analyzer.cargo.buildScripts.invocationStrategy]]rust-analyzer.cargo.buildScripts.invocationStrategy (default: `"per_workspace"`)::
+
--
Specifies the invocation strategy to use when running the build scripts command.
-If `per_workspace` is set, the command will be executed for each workspace.
-If `once` is set, the command will be executed once.
+If `per_workspace` is set, the command will be executed for each Rust workspace with the
+workspace as the working directory.
+If `once` is set, the command will be executed once with the opened project as the
+working directory.
This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`
is set.
--
@@ -75,8 +67,7 @@ option.
If there are multiple linked projects/workspaces, this command is invoked for
each of them, with the working directory being the workspace root
(i.e., the folder containing the `Cargo.toml`). This can be overwritten
-by changing `#rust-analyzer.cargo.buildScripts.invocationStrategy#` and
-`#rust-analyzer.cargo.buildScripts.invocationLocation#`.
+by changing `#rust-analyzer.cargo.buildScripts.invocationStrategy#`.
By default, a cargo invocation will be constructed for the configured
targets and features, with the following base command line:
@@ -209,16 +200,6 @@ List of `cargo check` (or other command specified in `check.command`) diagnostic
For example for `cargo check`: `dead_code`, `unused_imports`, `unused_variables`,...
--
-[[rust-analyzer.check.invocationLocation]]rust-analyzer.check.invocationLocation (default: `"workspace"`)::
-+
---
-Specifies the working directory for running checks.
-- "workspace": run checks for workspaces in the corresponding workspaces' root directories.
- This falls back to "root" if `#rust-analyzer.check.invocationStrategy#` is set to `once`.
-- "root": run checks in the project's root directory.
-This config only has an effect when `#rust-analyzer.check.overrideCommand#`
-is set.
---
[[rust-analyzer.check.invocationStrategy]]rust-analyzer.check.invocationStrategy (default: `"per_workspace"`)::
+
--
@@ -250,8 +231,7 @@ Cargo, you might also want to change
If there are multiple linked projects/workspaces, this command is invoked for
each of them, with the working directory being the workspace root
(i.e., the folder containing the `Cargo.toml`). This can be overwritten
-by changing `#rust-analyzer.check.invocationStrategy#` and
-`#rust-analyzer.check.invocationLocation#`.
+by changing `#rust-analyzer.check.invocationStrategy#`.
If `$saved_file` is part of the command, rust-analyzer will pass
the absolute path of the saved file to the provided command. This is
diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc
index 703ec66921..246ebdab2c 100644
--- a/docs/user/manual.adoc
+++ b/docs/user/manual.adoc
@@ -705,6 +705,12 @@ interface JsonProject {
/// several different "sysroots" in one graph of
/// crates.
sysroot_src?: string;
+ /// List of groups of common cfg values, to allow
+ /// sharing them between crates.
+ ///
+ /// Maps from group name to its cfgs. Cfg follow
+ /// the same format as `Crate.cfg`.
+ cfg_groups?: { [key: string]: string[]; };
/// The set of crates comprising the current
/// project. Must include all transitive
/// dependencies as well as sysroot crate (libstd,
@@ -754,6 +760,12 @@ interface Crate {
include_dirs: string[],
exclude_dirs: string[],
},
+ /// List of cfg groups this crate inherits.
+ ///
+ /// All cfg in these groups will be concatenated to
+ /// `cfg`. It is impossible to replace a value from
+ /// the groups.
+ cfg_groups?: string[];
/// The set of cfgs activated for a given crate, like
/// `["unix", "feature=\"foo\"", "feature=\"bar\""]`.
cfg: string[];
diff --git a/editors/code/package.json b/editors/code/package.json
index bf9c4a366d..98e8bbf02a 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -330,14 +330,6 @@
"default": false,
"type": "boolean"
},
- "rust-analyzer.discoverProjectRunner": {
- "markdownDescription": "Sets the extension responsible for determining which extension the rust-analyzer extension uses to generate `rust-project.json` files. This should should only be used\n if a build system like Buck or Bazel is also in use.",
- "default": null,
- "type": [
- "null",
- "string"
- ]
- },
"rust-analyzer.showUnlinkedFileNotification": {
"markdownDescription": "Whether to show a notification for unlinked files asking the user to add the corresponding Cargo.toml to the linked projects setting.",
"default": true,
@@ -674,26 +666,8 @@
{
"title": "cargo",
"properties": {
- "rust-analyzer.cargo.buildScripts.invocationLocation": {
- "markdownDescription": "Specifies the working directory for running build scripts.\n- \"workspace\": run build scripts for a workspace in the workspace's root directory.\n This is incompatible with `#rust-analyzer.cargo.buildScripts.invocationStrategy#` set to `once`.\n- \"root\": run build scripts in the project's root directory.\nThis config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`\nis set.",
- "default": "workspace",
- "type": "string",
- "enum": [
- "workspace",
- "root"
- ],
- "enumDescriptions": [
- "The command will be executed in the corresponding workspace root.",
- "The command will be executed in the project root."
- ]
- }
- }
- },
- {
- "title": "cargo",
- "properties": {
"rust-analyzer.cargo.buildScripts.invocationStrategy": {
- "markdownDescription": "Specifies the invocation strategy to use when running the build scripts command.\nIf `per_workspace` is set, the command will be executed for each workspace.\nIf `once` is set, the command will be executed once.\nThis config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`\nis set.",
+ "markdownDescription": "Specifies the invocation strategy to use when running the build scripts command.\nIf `per_workspace` is set, the command will be executed for each Rust workspace with the\nworkspace as the working directory.\nIf `once` is set, the command will be executed once with the opened project as the\nworking directory.\nThis config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`\nis set.",
"default": "per_workspace",
"type": "string",
"enum": [
@@ -701,8 +675,8 @@
"once"
],
"enumDescriptions": [
- "The command will be executed for each workspace.",
- "The command will be executed once."
+ "The command will be executed for each Rust workspace with the workspace as the working directory.",
+ "The command will be executed once with the opened project as the working directory."
]
}
}
@@ -711,7 +685,7 @@
"title": "cargo",
"properties": {
"rust-analyzer.cargo.buildScripts.overrideCommand": {
- "markdownDescription": "Override the command rust-analyzer uses to run build scripts and\nbuild procedural macros. The command is required to output json\nand should therefore include `--message-format=json` or a similar\noption.\n\nIf there are multiple linked projects/workspaces, this command is invoked for\neach of them, with the working directory being the workspace root\n(i.e., the folder containing the `Cargo.toml`). This can be overwritten\nby changing `#rust-analyzer.cargo.buildScripts.invocationStrategy#` and\n`#rust-analyzer.cargo.buildScripts.invocationLocation#`.\n\nBy default, a cargo invocation will be constructed for the configured\ntargets and features, with the following base command line:\n\n```bash\ncargo check --quiet --workspace --message-format=json --all-targets --keep-going\n```\n.",
+ "markdownDescription": "Override the command rust-analyzer uses to run build scripts and\nbuild procedural macros. The command is required to output json\nand should therefore include `--message-format=json` or a similar\noption.\n\nIf there are multiple linked projects/workspaces, this command is invoked for\neach of them, with the working directory being the workspace root\n(i.e., the folder containing the `Cargo.toml`). This can be overwritten\nby changing `#rust-analyzer.cargo.buildScripts.invocationStrategy#`.\n\nBy default, a cargo invocation will be constructed for the configured\ntargets and features, with the following base command line:\n\n```bash\ncargo check --quiet --workspace --message-format=json --all-targets --keep-going\n```\n.",
"default": null,
"type": [
"null",
@@ -976,24 +950,6 @@
{
"title": "check",
"properties": {
- "rust-analyzer.check.invocationLocation": {
- "markdownDescription": "Specifies the working directory for running checks.\n- \"workspace\": run checks for workspaces in the corresponding workspaces' root directories.\n This falls back to \"root\" if `#rust-analyzer.check.invocationStrategy#` is set to `once`.\n- \"root\": run checks in the project's root directory.\nThis config only has an effect when `#rust-analyzer.check.overrideCommand#`\nis set.",
- "default": "workspace",
- "type": "string",
- "enum": [
- "workspace",
- "root"
- ],
- "enumDescriptions": [
- "The command will be executed in the corresponding workspace root.",
- "The command will be executed in the project root."
- ]
- }
- }
- },
- {
- "title": "check",
- "properties": {
"rust-analyzer.check.invocationStrategy": {
"markdownDescription": "Specifies the invocation strategy to use when running the check command.\nIf `per_workspace` is set, the command will be executed for each workspace.\nIf `once` is set, the command will be executed once.\nThis config only has an effect when `#rust-analyzer.check.overrideCommand#`\nis set.",
"default": "per_workspace",
@@ -1003,8 +959,8 @@
"once"
],
"enumDescriptions": [
- "The command will be executed for each workspace.",
- "The command will be executed once."
+ "The command will be executed for each Rust workspace with the workspace as the working directory.",
+ "The command will be executed once with the opened project as the working directory."
]
}
}
@@ -1026,7 +982,7 @@
"title": "check",
"properties": {
"rust-analyzer.check.overrideCommand": {
- "markdownDescription": "Override the command rust-analyzer uses instead of `cargo check` for\ndiagnostics on save. The command is required to output json and\nshould therefore include `--message-format=json` or a similar option\n(if your client supports the `colorDiagnosticOutput` experimental\ncapability, you can use `--message-format=json-diagnostic-rendered-ansi`).\n\nIf you're changing this because you're using some tool wrapping\nCargo, you might also want to change\n`#rust-analyzer.cargo.buildScripts.overrideCommand#`.\n\nIf there are multiple linked projects/workspaces, this command is invoked for\neach of them, with the working directory being the workspace root\n(i.e., the folder containing the `Cargo.toml`). This can be overwritten\nby changing `#rust-analyzer.check.invocationStrategy#` and\n`#rust-analyzer.check.invocationLocation#`.\n\nIf `$saved_file` is part of the command, rust-analyzer will pass\nthe absolute path of the saved file to the provided command. This is\nintended to be used with non-Cargo build systems.\nNote that `$saved_file` is experimental and may be removed in the future.\n\nAn example command would be:\n\n```bash\ncargo check --workspace --message-format=json --all-targets\n```\n.",
+ "markdownDescription": "Override the command rust-analyzer uses instead of `cargo check` for\ndiagnostics on save. The command is required to output json and\nshould therefore include `--message-format=json` or a similar option\n(if your client supports the `colorDiagnosticOutput` experimental\ncapability, you can use `--message-format=json-diagnostic-rendered-ansi`).\n\nIf you're changing this because you're using some tool wrapping\nCargo, you might also want to change\n`#rust-analyzer.cargo.buildScripts.overrideCommand#`.\n\nIf there are multiple linked projects/workspaces, this command is invoked for\neach of them, with the working directory being the workspace root\n(i.e., the folder containing the `Cargo.toml`). This can be overwritten\nby changing `#rust-analyzer.check.invocationStrategy#`.\n\nIf `$saved_file` is part of the command, rust-analyzer will pass\nthe absolute path of the saved file to the provided command. This is\nintended to be used with non-Cargo build systems.\nNote that `$saved_file` is experimental and may be removed in the future.\n\nAn example command would be:\n\n```bash\ncargo check --workspace --message-format=json --all-targets\n```\n.",
"default": null,
"type": [
"null",
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index dc0165df71..1e3dc60809 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -252,10 +252,6 @@ export class Config {
await this.cfg.update("checkOnSave", !(value || false), target || null, overrideInLanguage);
}
- get discoverProjectRunner(): string | undefined {
- return this.get<string | undefined>("discoverProjectRunner");
- }
-
get problemMatcher(): string[] {
return this.get<string[]>("runnables.problemMatcher") || [];
}
diff --git a/rust-version b/rust-version
index d4f1703d85..5f21a43f3b 100644
--- a/rust-version
+++ b/rust-version
@@ -1 +1 @@
-80eb5a8e910e5185d47cdefe3732d839c78a5e7e
+6cf068db566de080dfa7ed24a216ea3aed2b98ce
diff --git a/xtask/src/codegen/grammar.rs b/xtask/src/codegen/grammar.rs
index 0352539754..39e06f9642 100644
--- a/xtask/src/codegen/grammar.rs
+++ b/xtask/src/codegen/grammar.rs
@@ -396,24 +396,66 @@ fn generate_syntax_kinds(grammar: KindsSrc) -> String {
let punctuation =
grammar.punct.iter().map(|(_token, name)| format_ident!("{}", name)).collect::<Vec<_>>();
- let x = |&name| match name {
+ let fmt_kw_as_variant = |&name| match name {
"Self" => format_ident!("SELF_TYPE_KW"),
name => format_ident!("{}_KW", to_upper_snake_case(name)),
};
- let full_keywords_values = grammar.keywords;
- let full_keywords = full_keywords_values.iter().map(x);
+ let strict_keywords = grammar.keywords;
+ let strict_keywords_variants =
+ strict_keywords.iter().map(fmt_kw_as_variant).collect::<Vec<_>>();
+ let strict_keywords_tokens = strict_keywords.iter().map(|it| format_ident!("{it}"));
- let contextual_keywords_values = &grammar.contextual_keywords;
- let contextual_keywords = contextual_keywords_values.iter().map(x);
+ let edition_dependent_keywords_variants_match_arm = grammar
+ .edition_dependent_keywords
+ .iter()
+ .map(|(kw, ed)| {
+ let kw = fmt_kw_as_variant(kw);
+ quote! { #kw if #ed <= edition }
+ })
+ .collect::<Vec<_>>();
+ let edition_dependent_keywords_str_match_arm = grammar
+ .edition_dependent_keywords
+ .iter()
+ .map(|(kw, ed)| {
+ quote! { #kw if #ed <= edition }
+ })
+ .collect::<Vec<_>>();
+ let edition_dependent_keywords_variants = grammar
+ .edition_dependent_keywords
+ .iter()
+ .map(|(kw, _)| fmt_kw_as_variant(kw))
+ .collect::<Vec<_>>();
+ let edition_dependent_keywords_tokens =
+ grammar.edition_dependent_keywords.iter().map(|(it, _)| format_ident!("{it}"));
+
+ let contextual_keywords = grammar.contextual_keywords;
+ let contextual_keywords_variants =
+ contextual_keywords.iter().map(fmt_kw_as_variant).collect::<Vec<_>>();
+ let contextual_keywords_tokens = contextual_keywords.iter().map(|it| format_ident!("{it}"));
+ let contextual_keywords_str_match_arm = grammar.contextual_keywords.iter().map(|kw| {
+ match grammar.edition_dependent_keywords.iter().find(|(ed_kw, _)| ed_kw == kw) {
+ Some((_, ed)) => quote! { #kw if edition < #ed },
+ None => quote! { #kw },
+ }
+ });
+ let contextual_keywords_variants_match_arm = grammar
+ .contextual_keywords
+ .iter()
+ .map(|kw_s| {
+ let kw = fmt_kw_as_variant(kw_s);
+ match grammar.edition_dependent_keywords.iter().find(|(ed_kw, _)| ed_kw == kw_s) {
+ Some((_, ed)) => quote! { #kw if edition < #ed },
+ None => quote! { #kw },
+ }
+ })
+ .collect::<Vec<_>>();
- let all_keywords_values = grammar
- .keywords
+ let non_strict_keyword_variants = contextual_keywords_variants
.iter()
- .chain(grammar.contextual_keywords.iter())
- .copied()
+ .chain(edition_dependent_keywords_variants.iter())
+ .sorted()
+ .dedup()
.collect::<Vec<_>>();
- let all_keywords_idents = all_keywords_values.iter().map(|kw| format_ident!("{}", kw));
- let all_keywords = all_keywords_values.iter().map(x).collect::<Vec<_>>();
let literals =
grammar.literals.iter().map(|name| format_ident!("{}", name)).collect::<Vec<_>>();
@@ -424,6 +466,8 @@ fn generate_syntax_kinds(grammar: KindsSrc) -> String {
let ast = quote! {
#![allow(bad_style, missing_docs, unreachable_pub)]
+ use crate::Edition;
+
/// The kind of syntax node, e.g. `IDENT`, `USE_KW`, or `STRUCT`.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[repr(u16)]
@@ -435,7 +479,8 @@ fn generate_syntax_kinds(grammar: KindsSrc) -> String {
#[doc(hidden)]
EOF,
#(#punctuation,)*
- #(#all_keywords,)*
+ #(#strict_keywords_variants,)*
+ #(#non_strict_keyword_variants,)*
#(#literals,)*
#(#tokens,)*
#(#nodes,)*
@@ -447,31 +492,55 @@ fn generate_syntax_kinds(grammar: KindsSrc) -> String {
use self::SyntaxKind::*;
impl SyntaxKind {
- pub fn is_keyword(self) -> bool {
- matches!(self, #(#all_keywords)|*)
+ /// Checks whether this syntax kind is a strict keyword for the given edition.
+ /// Strict keywords are identifiers that are always considered keywords.
+ pub fn is_strict_keyword(self, edition: Edition) -> bool {
+ matches!(self, #(#strict_keywords_variants)|*)
+ || match self {
+ #(#edition_dependent_keywords_variants_match_arm => true,)*
+ _ => false,
+ }
}
- pub fn is_punct(self) -> bool {
+ /// Checks whether this syntax kind is a weak keyword for the given edition.
+ /// Weak keywords are identifiers that are considered keywords only in certain contexts.
+ pub fn is_contextual_keyword(self, edition: Edition) -> bool {
+ match self {
+ #(#contextual_keywords_variants_match_arm => true,)*
+ _ => false,
+ }
+ }
- matches!(self, #(#punctuation)|*)
+ /// Checks whether this syntax kind is a strict or weak keyword for the given edition.
+ pub fn is_keyword(self, edition: Edition) -> bool {
+ matches!(self, #(#strict_keywords_variants)|*)
+ || match self {
+ #(#edition_dependent_keywords_variants_match_arm => true,)*
+ #(#contextual_keywords_variants_match_arm => true,)*
+ _ => false,
+ }
+ }
+ pub fn is_punct(self) -> bool {
+ matches!(self, #(#punctuation)|*)
}
pub fn is_literal(self) -> bool {
matches!(self, #(#literals)|*)
}
- pub fn from_keyword(ident: &str) -> Option<SyntaxKind> {
+ pub fn from_keyword(ident: &str, edition: Edition) -> Option<SyntaxKind> {
let kw = match ident {
- #(#full_keywords_values => #full_keywords,)*
+ #(#strict_keywords => #strict_keywords_variants,)*
+ #(#edition_dependent_keywords_str_match_arm => #edition_dependent_keywords_variants,)*
_ => return None,
};
Some(kw)
}
- pub fn from_contextual_keyword(ident: &str) -> Option<SyntaxKind> {
+ pub fn from_contextual_keyword(ident: &str, edition: Edition) -> Option<SyntaxKind> {
let kw = match ident {
- #(#contextual_keywords_values => #contextual_keywords,)*
+ #(#contextual_keywords_str_match_arm => #contextual_keywords_variants,)*
_ => return None,
};
Some(kw)
@@ -489,7 +558,9 @@ fn generate_syntax_kinds(grammar: KindsSrc) -> String {
#[macro_export]
macro_rules! T {
#([#punctuation_values] => { $crate::SyntaxKind::#punctuation };)*
- #([#all_keywords_idents] => { $crate::SyntaxKind::#all_keywords };)*
+ #([#strict_keywords_tokens] => { $crate::SyntaxKind::#strict_keywords_variants };)*
+ #([#contextual_keywords_tokens] => { $crate::SyntaxKind::#contextual_keywords_variants };)*
+ #([#edition_dependent_keywords_tokens] => { $crate::SyntaxKind::#edition_dependent_keywords_variants };)*
[lifetime_ident] => { $crate::SyntaxKind::LIFETIME_IDENT };
[int_number] => { $crate::SyntaxKind::INT_NUMBER };
[ident] => { $crate::SyntaxKind::IDENT };
diff --git a/xtask/src/codegen/grammar/ast_src.rs b/xtask/src/codegen/grammar/ast_src.rs
index 3444f89908..34151bd958 100644
--- a/xtask/src/codegen/grammar/ast_src.rs
+++ b/xtask/src/codegen/grammar/ast_src.rs
@@ -1,5 +1,7 @@
//! Defines input for code generation process.
+use quote::ToTokens;
+
use crate::codegen::grammar::to_upper_snake_case;
#[derive(Copy, Clone, Debug)]
@@ -10,6 +12,35 @@ pub(crate) struct KindsSrc {
pub(crate) literals: &'static [&'static str],
pub(crate) tokens: &'static [&'static str],
pub(crate) nodes: &'static [&'static str],
+ pub(crate) edition_dependent_keywords: &'static [(&'static str, Edition)],
+}
+
+#[allow(dead_code)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub(super) enum Edition {
+ Edition2015,
+ Edition2018,
+ Edition2021,
+ Edition2024,
+}
+
+impl ToTokens for Edition {
+ fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
+ match self {
+ Edition::Edition2015 => {
+ tokens.extend(quote::quote! { Edition::Edition2015 });
+ }
+ Edition::Edition2018 => {
+ tokens.extend(quote::quote! { Edition::Edition2018 });
+ }
+ Edition::Edition2021 => {
+ tokens.extend(quote::quote! { Edition::Edition2021 });
+ }
+ Edition::Edition2024 => {
+ tokens.extend(quote::quote! { Edition::Edition2024 });
+ }
+ }
+ }
}
/// The punctuations of the language.
@@ -75,17 +106,32 @@ const EOF: &str = "EOF";
const RESERVED: &[&str] = &[
"abstract", "become", "box", "do", "final", "macro", "override", "priv", "typeof", "unsized",
- "virtual", "yield", "try",
+ "virtual", "yield",
+];
+// keywords that are keywords only in specific parse contexts
+#[doc(alias = "WEAK_KEYWORDS")]
+const CONTEXTUAL_KEYWORDS: &[&str] =
+ &["macro_rules", "union", "default", "raw", "dyn", "auto", "yeet"];
+// keywords we use for special macro expansions
+const CONTEXTUAL_BUILTIN_KEYWORDS: &[&str] = &["builtin", "offset_of", "format_args", "asm"];
+// keywords that are keywords depending on the edition
+const EDITION_DEPENDENT_KEYWORDS: &[(&str, Edition)] = &[
+ ("try", Edition::Edition2018),
+ ("dyn", Edition::Edition2018),
+ ("async", Edition::Edition2018),
+ ("await", Edition::Edition2018),
+ ("gen", Edition::Edition2024),
];
-const CONTEXTUAL_RESERVED: &[&str] = &[];
pub(crate) fn generate_kind_src(
nodes: &[AstNodeSrc],
enums: &[AstEnumSrc],
grammar: &ungrammar::Grammar,
) -> KindsSrc {
+ let mut contextual_keywords: Vec<&_> =
+ CONTEXTUAL_KEYWORDS.iter().chain(CONTEXTUAL_BUILTIN_KEYWORDS).copied().collect();
+
let mut keywords: Vec<&_> = Vec::new();
- let mut contextual_keywords: Vec<&_> = Vec::new();
let mut tokens: Vec<&_> = TOKENS.to_vec();
let mut literals: Vec<&_> = Vec::new();
let mut used_puncts = vec![false; PUNCT.len()];
@@ -103,9 +149,7 @@ pub(crate) fn generate_kind_src(
("#", token) if !token.is_empty() => {
tokens.push(String::leak(to_upper_snake_case(token)));
}
- ("?", kw) if !kw.is_empty() => {
- contextual_keywords.push(String::leak(kw.to_owned()));
- }
+ _ if contextual_keywords.contains(&name) => {}
_ if name.chars().all(char::is_alphabetic) => {
keywords.push(String::leak(name.to_owned()));
}
@@ -124,9 +168,14 @@ pub(crate) fn generate_kind_src(
keywords.extend(RESERVED.iter().copied());
keywords.sort();
keywords.dedup();
- contextual_keywords.extend(CONTEXTUAL_RESERVED.iter().copied());
contextual_keywords.sort();
contextual_keywords.dedup();
+ let mut edition_dependent_keywords: Vec<(&_, _)> = EDITION_DEPENDENT_KEYWORDS.to_vec();
+ edition_dependent_keywords.sort();
+ edition_dependent_keywords.dedup();
+
+ keywords.retain(|&it| !contextual_keywords.contains(&it));
+ keywords.retain(|&it| !edition_dependent_keywords.iter().any(|&(kw, _)| kw == it));
// we leak things here for simplicity, that way we don't have to deal with lifetimes
// The execution is a one shot job so thats fine
@@ -142,12 +191,21 @@ pub(crate) fn generate_kind_src(
nodes.sort();
let keywords = Vec::leak(keywords);
let contextual_keywords = Vec::leak(contextual_keywords);
+ let edition_dependent_keywords = Vec::leak(edition_dependent_keywords);
let literals = Vec::leak(literals);
literals.sort();
let tokens = Vec::leak(tokens);
tokens.sort();
- KindsSrc { punct: PUNCT, nodes, keywords, contextual_keywords, literals, tokens }
+ KindsSrc {
+ punct: PUNCT,
+ nodes,
+ keywords,
+ contextual_keywords,
+ edition_dependent_keywords,
+ literals,
+ tokens,
+ }
}
#[derive(Default, Debug)]