From 932d85b52946d917deab2c23ead552f7f713b828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauren=C8=9Biu=20Nicola?= Date: Wed, 3 Jan 2024 11:35:07 +0200 Subject: [PATCH 1/2] Merge commit '426d2842c1f0e5cc5e34bb37c7ac3ee0945f9746' into sync-from-ra2 --- .cargo/config.toml | 2 +- .github/workflows/ci.yaml | 1 - Cargo.lock | 87 +- Cargo.toml | 25 +- crates/base-db/Cargo.toml | 7 +- crates/base-db/src/change.rs | 18 +- crates/base-db/src/input.rs | 64 +- crates/base-db/src/lib.rs | 30 +- crates/cfg/Cargo.toml | 5 +- crates/cfg/src/tests.rs | 10 +- crates/flycheck/Cargo.toml | 9 +- crates/hir-def/Cargo.toml | 11 +- crates/hir-def/src/attr.rs | 21 +- crates/hir-def/src/attr/tests.rs | 10 +- crates/hir-def/src/body/lower.rs | 78 +- crates/hir-def/src/body/scope.rs | 3 +- crates/hir-def/src/body/tests.rs | 3 +- crates/hir-def/src/data.rs | 4 +- crates/hir-def/src/db.rs | 110 +- crates/hir-def/src/expander.rs | 72 +- crates/hir-def/src/find_path.rs | 2 +- crates/hir-def/src/import_map.rs | 3 +- crates/hir-def/src/item_scope.rs | 10 +- crates/hir-def/src/item_tree.rs | 19 +- crates/hir-def/src/item_tree/lower.rs | 8 +- crates/hir-def/src/item_tree/pretty.rs | 4 +- crates/hir-def/src/item_tree/tests.rs | 2 +- crates/hir-def/src/lib.rs | 189 +--- crates/hir-def/src/lower.rs | 2 +- .../macro_expansion_tests/builtin_fn_macro.rs | 42 +- .../hir-def/src/macro_expansion_tests/mbe.rs | 14 +- .../macro_expansion_tests/mbe/meta_syntax.rs | 4 +- .../macro_expansion_tests/mbe/metavar_expr.rs | 78 +- .../hir-def/src/macro_expansion_tests/mod.rs | 26 +- crates/hir-def/src/nameres.rs | 14 +- crates/hir-def/src/nameres/attr_resolution.rs | 3 +- crates/hir-def/src/nameres/collector.rs | 116 ++- crates/hir-def/src/nameres/proc_macro.rs | 10 +- crates/hir-def/src/nameres/tests.rs | 3 +- .../hir-def/src/nameres/tests/incremental.rs | 7 +- crates/hir-def/src/nameres/tests/macros.rs | 8 +- crates/hir-def/src/resolver.rs | 14 +- crates/hir-def/src/visibility.rs | 2 +- crates/hir-expand/Cargo.toml | 6 +- crates/hir-expand/src/ast_id_map.rs | 27 +- crates/hir-expand/src/attrs.rs | 22 +- crates/hir-expand/src/builtin_attr_macro.rs | 27 +- crates/hir-expand/src/builtin_derive_macro.rs | 69 +- crates/hir-expand/src/builtin_fn_macro.rs | 168 +-- crates/hir-expand/src/change.rs | 42 + crates/hir-expand/src/db.rs | 191 +++- crates/hir-expand/src/eager.rs | 26 +- crates/hir-expand/src/files.rs | 7 +- crates/hir-expand/src/fixup.rs | 77 +- crates/hir-expand/src/hygiene.rs | 43 +- crates/hir-expand/src/lib.rs | 222 ++-- crates/hir-expand/src/mod_path.rs | 5 +- crates/hir-expand/src/name.rs | 5 + crates/hir-expand/src/proc_macro.rs | 58 +- crates/hir-expand/src/quote.rs | 35 +- crates/hir-expand/src/span.rs | 124 --- crates/hir-expand/src/span_map.rs | 65 ++ crates/hir-ty/Cargo.toml | 8 +- crates/hir-ty/src/consteval/tests.rs | 3 +- crates/hir-ty/src/infer.rs | 10 + crates/hir-ty/src/infer/expr.rs | 19 + crates/hir-ty/src/infer/path.rs | 3 + crates/hir-ty/src/layout/tests.rs | 2 +- crates/hir-ty/src/mir/eval/tests.rs | 3 +- crates/hir-ty/src/tests.rs | 3 +- crates/hir-ty/src/tests/incremental.rs | 3 +- crates/hir-ty/src/tests/patterns.rs | 34 + crates/hir/Cargo.toml | 7 +- crates/hir/src/attrs.rs | 2 +- crates/hir/src/db.rs | 2 +- crates/hir/src/diagnostics.rs | 9 +- crates/hir/src/lib.rs | 33 +- crates/hir/src/semantics.rs | 24 +- crates/hir/src/source_analyzer.rs | 73 +- crates/hir/src/symbols.rs | 16 +- crates/ide-assists/Cargo.toml | 4 + .../ide-assists/src/handlers/auto_import.rs | 7 +- .../ide-assists/src/handlers/bool_to_enum.rs | 227 +++-- .../src/handlers/extract_variable.rs | 229 +++-- .../src/handlers/generate_delegate_trait.rs | 963 +++++++++++++++--- .../src/handlers/generate_enum_variant.rs | 2 +- .../src/handlers/generate_function.rs | 300 +++--- .../ide-assists/src/handlers/inline_call.rs | 134 ++- .../src/handlers/introduce_named_generic.rs | 40 +- .../src/handlers/promote_local_to_const.rs | 89 +- .../src/handlers/remove_unused_imports.rs | 184 +++- .../replace_is_method_with_if_let_method.rs | 34 +- crates/ide-assists/src/tests.rs | 53 +- crates/ide-assists/src/tests/generated.rs | 2 +- crates/ide-assists/src/utils.rs | 18 + crates/ide-assists/src/utils/suggest_name.rs | 56 +- crates/ide-completion/Cargo.toml | 4 + .../src/completions/attribute.rs | 5 + .../src/completions/attribute/macro_use.rs | 35 + crates/ide-completion/src/completions/dot.rs | 31 +- crates/ide-completion/src/context.rs | 3 +- crates/ide-completion/src/context/analysis.rs | 43 +- crates/ide-completion/src/lib.rs | 2 + crates/ide-completion/src/render.rs | 6 +- crates/ide-completion/src/render/function.rs | 15 +- crates/ide-completion/src/tests.rs | 3 +- crates/ide-completion/src/tests/attribute.rs | 79 ++ crates/ide-db/Cargo.toml | 9 +- crates/ide-db/src/apply_change.rs | 8 +- crates/ide-db/src/documentation.rs | 16 +- crates/ide-db/src/imports/insert_use/tests.rs | 2 +- crates/ide-db/src/lib.rs | 2 + crates/ide-db/src/path_transform.rs | 28 + crates/ide-db/src/rename.rs | 7 +- crates/ide-db/src/symbol_index.rs | 8 +- .../test_symbol_index_collection.txt | 142 ++- crates/ide-db/src/traits.rs | 3 +- crates/ide-diagnostics/Cargo.toml | 4 + .../trait_impl_redundant_assoc_item.rs | 20 + .../src/handlers/unresolved_assoc_item.rs | 52 + .../src/handlers/unresolved_method.rs | 204 +++- crates/ide-diagnostics/src/lib.rs | 4 +- crates/ide-diagnostics/src/tests.rs | 5 +- crates/ide-ssr/Cargo.toml | 4 + crates/ide-ssr/src/tests.rs | 4 +- crates/ide/Cargo.toml | 6 +- crates/ide/src/annotations.rs | 331 +++--- crates/ide/src/doc_links.rs | 2 +- crates/ide/src/fixture.rs | 2 +- crates/ide/src/goto_definition.rs | 71 +- crates/ide/src/goto_implementation.rs | 15 +- crates/ide/src/lib.rs | 5 +- crates/ide/src/markdown_remove.rs | 17 +- crates/ide/src/shuffle_crate_graph.rs | 5 +- crates/ide/src/signature_help.rs | 3 +- crates/ide/src/ssr.rs | 5 +- crates/ide/src/status.rs | 55 +- crates/intern/Cargo.toml | 5 +- crates/limit/Cargo.toml | 3 + crates/load-cargo/Cargo.toml | 8 +- crates/load-cargo/src/lib.rs | 50 +- crates/mbe/Cargo.toml | 6 +- crates/mbe/src/benchmark.rs | 20 +- crates/mbe/src/expander.rs | 19 +- crates/mbe/src/expander/matcher.rs | 59 +- crates/mbe/src/expander/transcriber.rs | 110 +- crates/mbe/src/lib.rs | 46 +- crates/mbe/src/parser.rs | 70 +- crates/mbe/src/syntax_bridge.rs | 184 ++-- crates/mbe/src/syntax_bridge/tests.rs | 4 +- crates/mbe/src/tt_iter.rs | 15 +- crates/parser/Cargo.toml | 3 + crates/paths/Cargo.toml | 3 + crates/proc-macro-api/Cargo.toml | 4 + crates/proc-macro-api/src/lib.rs | 35 +- crates/proc-macro-api/src/msg.rs | 69 +- crates/proc-macro-api/src/msg/flat.rs | 61 +- crates/proc-macro-api/src/process.rs | 36 +- crates/proc-macro-srv-cli/Cargo.toml | 3 + crates/proc-macro-srv-cli/src/main.rs | 14 +- crates/proc-macro-srv/Cargo.toml | 9 +- .../proc-macro-srv/proc-macro-test/Cargo.toml | 19 + .../proc-macro-test/build.rs | 3 + .../proc-macro-test/imp/.gitignore | 0 .../proc-macro-test/imp/Cargo.toml | 7 +- .../proc-macro-test/imp/src/lib.rs | 25 + .../proc-macro-test/src/lib.rs | 0 crates/proc-macro-srv/src/dylib.rs | 22 +- crates/proc-macro-srv/src/lib.rs | 194 +++- crates/proc-macro-srv/src/proc_macros.rs | 48 +- crates/proc-macro-srv/src/server.rs | 399 +------- .../src/server/rust_analyzer_span.rs | 411 ++++++++ crates/proc-macro-srv/src/server/token_id.rs | 380 +++++++ .../proc-macro-srv/src/server/token_stream.rs | 111 +- crates/proc-macro-srv/src/tests/mod.rs | 92 +- crates/proc-macro-srv/src/tests/utils.rs | 87 +- crates/proc-macro-test/Cargo.toml | 20 - crates/profile/Cargo.toml | 3 + crates/project-model/Cargo.toml | 7 +- crates/project-model/src/cargo_workspace.rs | 1 + crates/project-model/src/project_json.rs | 3 + crates/project-model/src/workspace.rs | 38 +- .../cargo_hello_world_project_model.txt | 10 +- ...project_model_with_selective_overrides.txt | 10 +- ..._project_model_with_wildcard_overrides.txt | 10 +- ...rust_project_hello_world_project_model.txt | 22 +- crates/rust-analyzer/Cargo.toml | 8 +- crates/rust-analyzer/src/bin/main.rs | 17 +- crates/rust-analyzer/src/caps.rs | 2 + crates/rust-analyzer/src/cargo_target_spec.rs | 4 +- crates/rust-analyzer/src/cli/rustc_tests.rs | 4 +- crates/rust-analyzer/src/cli/scip.rs | 2 +- crates/rust-analyzer/src/config.rs | 46 +- crates/rust-analyzer/src/global_state.rs | 3 +- .../src/handlers/notification.rs | 7 + crates/rust-analyzer/src/handlers/request.rs | 16 +- .../src/integrated_benchmarks.rs | 3 +- crates/rust-analyzer/src/lsp/ext.rs | 2 +- crates/rust-analyzer/src/lsp/to_proto.rs | 2 +- crates/rust-analyzer/src/lsp/utils.rs | 35 +- crates/rust-analyzer/src/reload.rs | 28 +- crates/rust-analyzer/tests/slow-tests/main.rs | 2 +- crates/rustc-dependencies/Cargo.toml | 3 + crates/sourcegen/Cargo.toml | 4 + crates/span/Cargo.toml | 21 + .../{base-db/src/span.rs => span/src/lib.rs} | 88 +- .../{mbe/src/token_map.rs => span/src/map.rs} | 65 +- crates/stdx/Cargo.toml | 3 + crates/syntax/Cargo.toml | 5 +- crates/syntax/fuzz/Cargo.toml | 3 + crates/syntax/src/ast/edit_in_place.rs | 47 +- crates/syntax/src/ast/make.rs | 64 +- crates/syntax/src/ast/node_ext.rs | 44 +- crates/test-fixture/Cargo.toml | 21 + .../fixture.rs => test-fixture/src/lib.rs} | 138 +-- crates/test-utils/Cargo.toml | 5 +- crates/test-utils/src/minicore.rs | 4 + crates/text-edit/Cargo.toml | 3 + crates/toolchain/Cargo.toml | 3 + crates/tt/Cargo.toml | 6 + crates/tt/src/lib.rs | 73 +- crates/vfs-notify/Cargo.toml | 3 + crates/vfs/Cargo.toml | 5 +- docs/dev/architecture.md | 51 +- docs/dev/lsp-extensions.md | 2 +- docs/user/generated_config.adoc | 16 +- editors/code/package-lock.json | 10 +- editors/code/package.json | 26 +- editors/code/src/debug.ts | 6 +- editors/code/src/run.ts | 2 +- lib/la-arena/Cargo.toml | 3 + lib/line-index/Cargo.toml | 3 + lib/lsp-server/Cargo.toml | 9 +- lib/lsp-server/examples/goto_def.rs | 10 +- lib/lsp-server/src/error.rs | 17 +- lib/lsp-server/src/lib.rs | 49 +- lib/lsp-server/src/msg.rs | 4 +- lib/lsp-server/src/stdio.rs | 3 +- xtask/Cargo.toml | 3 + xtask/src/release/changelog.rs | 52 +- 240 files changed, 6941 insertions(+), 3102 deletions(-) create mode 100644 crates/hir-expand/src/change.rs delete mode 100644 crates/hir-expand/src/span.rs create mode 100644 crates/hir-expand/src/span_map.rs create mode 100644 crates/ide-completion/src/completions/attribute/macro_use.rs create mode 100644 crates/ide-diagnostics/src/handlers/unresolved_assoc_item.rs create mode 100644 crates/proc-macro-srv/proc-macro-test/Cargo.toml rename crates/{ => proc-macro-srv}/proc-macro-test/build.rs (97%) rename crates/{ => proc-macro-srv}/proc-macro-test/imp/.gitignore (100%) rename crates/{ => proc-macro-srv}/proc-macro-test/imp/Cargo.toml (91%) rename crates/{ => proc-macro-srv}/proc-macro-test/imp/src/lib.rs (80%) rename crates/{ => proc-macro-srv}/proc-macro-test/src/lib.rs (100%) create mode 100644 crates/proc-macro-srv/src/server/rust_analyzer_span.rs create mode 100644 crates/proc-macro-srv/src/server/token_id.rs delete mode 100644 crates/proc-macro-test/Cargo.toml create mode 100644 crates/span/Cargo.toml rename crates/{base-db/src/span.rs => span/src/lib.rs} (72%) rename crates/{mbe/src/token_map.rs => span/src/map.rs} (59%) create mode 100644 crates/test-fixture/Cargo.toml rename crates/{base-db/src/fixture.rs => test-fixture/src/lib.rs} (87%) diff --git a/.cargo/config.toml b/.cargo/config.toml index c9ad7803951..c3cfda85517 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -2,7 +2,7 @@ xtask = "run --package xtask --bin xtask --" tq = "test -- -q" qt = "tq" -lint = "clippy --all-targets -- -Aclippy::collapsible_if -Aclippy::needless_pass_by_value -Aclippy::nonminimal_bool -Aclippy::redundant_pattern_matching --cap-lints warn" +lint = "clippy --all-targets -- --cap-lints warn" [target.x86_64-pc-windows-msvc] linker = "rust-lld" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1f2a7796d11..be830415f9c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -38,7 +38,6 @@ jobs: - 'crates/proc-macro-api/**' - 'crates/proc-macro-srv/**' - 'crates/proc-macro-srv-cli/**' - - 'crates/proc-macro-test/**' rust: needs: changes diff --git a/Cargo.lock b/Cargo.lock index 227d1db0ec7..c7d110eafb6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,11 +74,11 @@ dependencies = [ "profile", "rust-analyzer-salsa", "rustc-hash", + "semver", + "span", "stdx", "syntax", - "test-utils", "triomphe", - "tt", "vfs", ] @@ -516,8 +516,10 @@ dependencies = [ "rustc-dependencies", "rustc-hash", "smallvec", + "span", "stdx", "syntax", + "test-fixture", "test-utils", "tracing", "triomphe", @@ -542,6 +544,7 @@ dependencies = [ "profile", "rustc-hash", "smallvec", + "span", "stdx", "syntax", "tracing", @@ -581,6 +584,7 @@ dependencies = [ "smallvec", "stdx", "syntax", + "test-fixture", "test-utils", "tracing", "tracing-subscriber", @@ -624,6 +628,7 @@ dependencies = [ "smallvec", "stdx", "syntax", + "test-fixture", "test-utils", "text-edit", "toolchain", @@ -647,6 +652,7 @@ dependencies = [ "sourcegen", "stdx", "syntax", + "test-fixture", "test-utils", "text-edit", ] @@ -666,6 +672,7 @@ dependencies = [ "smallvec", "stdx", "syntax", + "test-fixture", "test-utils", "text-edit", ] @@ -694,8 +701,10 @@ dependencies = [ "rayon", "rustc-hash", "sourcegen", + "span", "stdx", "syntax", + "test-fixture", "test-utils", "text-edit", "tracing", @@ -720,6 +729,7 @@ dependencies = [ "sourcegen", "stdx", "syntax", + "test-fixture", "test-utils", "text-edit", ] @@ -737,6 +747,7 @@ dependencies = [ "parser", "stdx", "syntax", + "test-fixture", "test-utils", "text-edit", "triomphe", @@ -903,11 +914,13 @@ version = "0.0.0" dependencies = [ "anyhow", "crossbeam-channel", + "hir-expand", "ide", "ide-db", "itertools", "proc-macro-api", "project-model", + "span", "tracing", "tt", "vfs", @@ -932,19 +945,7 @@ checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] name = "lsp-server" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b52dccdf3302eefab8c8a1273047f0a3c3dca4b527c8458d00c09484c8371928" -dependencies = [ - "crossbeam-channel", - "log", - "serde", - "serde_json", -] - -[[package]] -name = "lsp-server" -version = "0.7.5" +version = "0.7.6" dependencies = [ "crossbeam-channel", "ctrlc", @@ -955,10 +956,22 @@ dependencies = [ ] [[package]] -name = "lsp-types" -version = "0.94.0" +name = "lsp-server" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b63735a13a1f9cd4f4835223d828ed9c2e35c8c5e61837774399f558b6a1237" +checksum = "248f65b78f6db5d8e1b1604b4098a28b43d21a8eb1deeca22b1c421b276c7095" +dependencies = [ + "crossbeam-channel", + "log", + "serde", + "serde_json", +] + +[[package]] +name = "lsp-types" +version = "0.95.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "158c1911354ef73e8fe42da6b10c0484cb65c7f1007f28022e847706c1ab6984" dependencies = [ "bitflags 1.3.2", "serde", @@ -975,6 +988,7 @@ dependencies = [ "parser", "rustc-hash", "smallvec", + "span", "stdx", "syntax", "test-utils", @@ -1251,6 +1265,7 @@ dependencies = [ "serde", "serde_json", "snap", + "span", "stdx", "text-size", "tracing", @@ -1262,6 +1277,7 @@ dependencies = [ name = "proc-macro-srv" version = "0.0.0" dependencies = [ + "base-db", "expect-test", "libloading", "mbe", @@ -1270,6 +1286,7 @@ dependencies = [ "paths", "proc-macro-api", "proc-macro-test", + "span", "stdx", "tt", ] @@ -1287,14 +1304,9 @@ name = "proc-macro-test" version = "0.0.0" dependencies = [ "cargo_metadata", - "proc-macro-test-impl", "toolchain", ] -[[package]] -name = "proc-macro-test-impl" -version = "0.0.0" - [[package]] name = "proc-macro2" version = "1.0.69" @@ -1514,7 +1526,7 @@ dependencies = [ "ide-ssr", "itertools", "load-cargo", - "lsp-server 0.7.4", + "lsp-server 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", "lsp-types", "mbe", "mimalloc", @@ -1535,6 +1547,7 @@ dependencies = [ "sourcegen", "stdx", "syntax", + "test-fixture", "test-utils", "tikv-jemallocator", "toolchain", @@ -1726,6 +1739,17 @@ dependencies = [ "xshell", ] +[[package]] +name = "span" +version = "0.0.0" +dependencies = [ + "la-arena 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rust-analyzer-salsa", + "stdx", + "syntax", + "vfs", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -1796,6 +1820,20 @@ dependencies = [ "ungrammar", ] +[[package]] +name = "test-fixture" +version = "0.0.0" +dependencies = [ + "base-db", + "cfg", + "hir-expand", + "rustc-hash", + "span", + "stdx", + "test-utils", + "tt", +] + [[package]] name = "test-utils" version = "0.0.0" @@ -1998,6 +2036,7 @@ name = "tt" version = "0.0.0" dependencies = [ "smol_str", + "span", "stdx", "text-size", ] diff --git a/Cargo.toml b/Cargo.toml index 1213979c390..7054020086e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,10 @@ [workspace] members = ["xtask/", "lib/*", "crates/*"] -exclude = ["crates/proc-macro-test/imp"] +exclude = ["crates/proc-macro-srv/proc-macro-test/"] resolver = "2" [workspace.package] -rust-version = "1.70" +rust-version = "1.74" edition = "2021" license = "MIT OR Apache-2.0" authors = ["rust-analyzer team"] @@ -70,10 +70,9 @@ proc-macro-srv = { path = "./crates/proc-macro-srv", version = "0.0.0" } proc-macro-srv-cli = { path = "./crates/proc-macro-srv-cli", version = "0.0.0" } profile = { path = "./crates/profile", version = "0.0.0" } project-model = { path = "./crates/project-model", version = "0.0.0" } -sourcegen = { path = "./crates/sourcegen", version = "0.0.0" } +span = { path = "./crates/span", version = "0.0.0" } stdx = { path = "./crates/stdx", version = "0.0.0" } syntax = { path = "./crates/syntax", version = "0.0.0" } -test-utils = { path = "./crates/test-utils", version = "0.0.0" } text-edit = { path = "./crates/text-edit", version = "0.0.0" } toolchain = { path = "./crates/toolchain", version = "0.0.0" } tt = { path = "./crates/tt", version = "0.0.0" } @@ -82,19 +81,25 @@ vfs = { path = "./crates/vfs", version = "0.0.0" } rustc-dependencies = { path = "./crates/rustc-dependencies", version = "0.0.0" } # local crates that aren't published to crates.io. These should not have versions. -proc-macro-test = { path = "./crates/proc-macro-test" } +sourcegen = { path = "./crates/sourcegen" } +test-fixture = { path = "./crates/test-fixture" } +test-utils = { path = "./crates/test-utils" } # In-tree crates that are published separately and follow semver. See lib/README.md line-index = { version = "0.1.1" } la-arena = { version = "0.3.1" } -lsp-server = { version = "0.7.4" } +lsp-server = { version = "0.7.6" } # non-local crates anyhow = "1.0.75" +arrayvec = "0.7.4" bitflags = "2.4.1" cargo_metadata = "0.18.1" +command-group = "2.0.1" +crossbeam-channel = "0.5.8" dissimilar = "1.0.7" either = "1.9.0" +expect-test = "1.4.0" hashbrown = { version = "0.14", features = [ "inline-more", ], default-features = false } @@ -105,6 +110,7 @@ nohash-hasher = "0.2.0" rayon = "1.8.0" rust-analyzer-salsa = "0.17.0-pre.4" rustc-hash = "1.1.0" +semver = "1.0.14" serde = { version = "1.0.192", features = ["derive"] } serde_json = "1.0.108" smallvec = { version = "1.10.0", features = [ @@ -124,5 +130,12 @@ tracing-subscriber = { version = "0.3.18", default-features = false, features = triomphe = { version = "0.1.10", default-features = false, features = ["std"] } xshell = "0.2.5" + # We need to freeze the version of the crate, as the raw-api feature is considered unstable dashmap = { version = "=5.5.3", features = ["raw-api"] } + +[workspace.lints.clippy] +collapsible_if = "allow" +needless_pass_by_value = "allow" +nonminimal_bool = "allow" +redundant_pattern_matching = "allow" \ No newline at end of file diff --git a/crates/base-db/Cargo.toml b/crates/base-db/Cargo.toml index 393ffe155ba..1aa43175f90 100644 --- a/crates/base-db/Cargo.toml +++ b/crates/base-db/Cargo.toml @@ -16,12 +16,15 @@ la-arena.workspace = true rust-analyzer-salsa.workspace = true rustc-hash.workspace = true triomphe.workspace = true +semver.workspace = true # local deps cfg.workspace = true profile.workspace = true stdx.workspace = true syntax.workspace = true -test-utils.workspace = true -tt.workspace = true vfs.workspace = true +span.workspace = true + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/base-db/src/change.rs b/crates/base-db/src/change.rs index 6a3b36b2312..4332e572e20 100644 --- a/crates/base-db/src/change.rs +++ b/crates/base-db/src/change.rs @@ -7,18 +7,17 @@ use triomphe::Arc; use vfs::FileId; -use crate::{CrateGraph, ProcMacros, SourceDatabaseExt, SourceRoot, SourceRootId}; +use crate::{CrateGraph, SourceDatabaseExt, SourceRoot, SourceRootId}; /// Encapsulate a bunch of raw `.set` calls on the database. #[derive(Default)] -pub struct Change { +pub struct FileChange { pub roots: Option>, pub files_changed: Vec<(FileId, Option>)>, pub crate_graph: Option, - pub proc_macros: Option, } -impl fmt::Debug for Change { +impl fmt::Debug for FileChange { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { let mut d = fmt.debug_struct("Change"); if let Some(roots) = &self.roots { @@ -34,9 +33,9 @@ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { } } -impl Change { +impl FileChange { pub fn new() -> Self { - Change::default() + FileChange::default() } pub fn set_roots(&mut self, roots: Vec) { @@ -51,10 +50,6 @@ pub fn set_crate_graph(&mut self, graph: CrateGraph) { self.crate_graph = Some(graph); } - pub fn set_proc_macros(&mut self, proc_macros: ProcMacros) { - self.proc_macros = Some(proc_macros); - } - pub fn apply(self, db: &mut dyn SourceDatabaseExt) { let _p = profile::span("RootDatabase::apply_change"); if let Some(roots) = self.roots { @@ -79,9 +74,6 @@ pub fn apply(self, db: &mut dyn SourceDatabaseExt) { if let Some(crate_graph) = self.crate_graph { db.set_crate_graph_with_durability(Arc::new(crate_graph), Durability::HIGH); } - if let Some(proc_macros) = self.proc_macros { - db.set_proc_macros_with_durability(Arc::new(proc_macros), Durability::HIGH); - } } } diff --git a/crates/base-db/src/input.rs b/crates/base-db/src/input.rs index c2472363aac..e45a81238ac 100644 --- a/crates/base-db/src/input.rs +++ b/crates/base-db/src/input.rs @@ -6,22 +6,19 @@ //! actual IO. See `vfs` and `project_model` in the `rust-analyzer` crate for how //! actual IO is done and lowered to input. -use std::{fmt, mem, ops, panic::RefUnwindSafe, str::FromStr, sync}; +use std::{fmt, mem, ops, str::FromStr}; use cfg::CfgOptions; use la_arena::{Arena, Idx}; use rustc_hash::{FxHashMap, FxHashSet}; +use semver::Version; use syntax::SmolStr; use triomphe::Arc; use vfs::{file_set::FileSet, AbsPathBuf, AnchoredPath, FileId, VfsPath}; -use crate::span::SpanData; - // Map from crate id to the name of the crate and path of the proc-macro. If the value is `None`, // then the crate for the proc-macro hasn't been build yet as the build data is missing. pub type ProcMacroPaths = FxHashMap, AbsPathBuf), String>>; -pub type ProcMacros = FxHashMap; - /// Files are grouped into source roots. A source root is a directory on the /// file systems which is watched for changes. Typically it corresponds to a /// Rust crate. Source roots *might* be nested: in this case, a file belongs to @@ -242,49 +239,8 @@ pub fn from_canonical_name(canonical_name: String) -> CrateDisplayName { CrateDisplayName { crate_name, canonical_name } } } - -// FIXME: These should not be defined in here? Why does base db know about proc-macros -// ProcMacroKind is used in [`fixture`], but that module probably shouldn't be in this crate either. - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub struct ProcMacroId(pub u32); - -#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)] -pub enum ProcMacroKind { - CustomDerive, - FuncLike, - Attr, -} - -pub trait ProcMacroExpander: fmt::Debug + Send + Sync + RefUnwindSafe { - fn expand( - &self, - subtree: &tt::Subtree, - attrs: Option<&tt::Subtree>, - env: &Env, - def_site: SpanData, - call_site: SpanData, - mixed_site: SpanData, - ) -> Result, ProcMacroExpansionError>; -} - -#[derive(Debug)] -pub enum ProcMacroExpansionError { - Panic(String), - /// Things like "proc macro server was killed by OOM". - System(String), -} - -pub type ProcMacroLoadResult = Result, String>; pub type TargetLayoutLoadResult = Result, Arc>; -#[derive(Debug, Clone)] -pub struct ProcMacro { - pub name: SmolStr, - pub kind: ProcMacroKind, - pub expander: sync::Arc, -} - #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum ReleaseChannel { Stable, @@ -303,7 +259,7 @@ pub fn as_str(self) -> &'static str { pub fn from_str(str: &str) -> Option { Some(match str { - "" => ReleaseChannel::Stable, + "" | "stable" => ReleaseChannel::Stable, "nightly" => ReleaseChannel::Nightly, _ if str.starts_with("beta") => ReleaseChannel::Beta, _ => return None, @@ -334,7 +290,7 @@ pub struct CrateData { // things. This info does need to be somewhat present though as to prevent deduplication from // happening across different workspaces with different layouts. pub target_layout: TargetLayoutLoadResult, - pub channel: Option, + pub toolchain: Option, } impl CrateData { @@ -391,6 +347,10 @@ pub fn eq_ignoring_origin_and_deps(&self, other: &CrateData, ignore_dev_deps: bo slf_deps.eq(other_deps) } + + pub fn channel(&self) -> Option { + self.toolchain.as_ref().and_then(|v| ReleaseChannel::from_str(&v.pre)) + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -398,10 +358,12 @@ pub enum Edition { Edition2015, Edition2018, Edition2021, + Edition2024, } impl Edition { pub const CURRENT: Edition = Edition::Edition2021; + pub const DEFAULT: Edition = Edition::Edition2015; } #[derive(Default, Debug, Clone, PartialEq, Eq)] @@ -472,7 +434,7 @@ pub fn add_crate_root( is_proc_macro: bool, origin: CrateOrigin, target_layout: Result, Arc>, - channel: Option, + toolchain: Option, ) -> CrateId { let data = CrateData { root_file_id, @@ -486,7 +448,7 @@ pub fn add_crate_root( origin, target_layout, is_proc_macro, - channel, + toolchain, }; self.arena.alloc(data) } @@ -784,6 +746,7 @@ fn from_str(s: &str) -> Result { "2015" => Edition::Edition2015, "2018" => Edition::Edition2018, "2021" => Edition::Edition2021, + "2024" => Edition::Edition2024, _ => return Err(ParseEditionError { invalid_input: s.to_string() }), }; Ok(res) @@ -796,6 +759,7 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { Edition::Edition2015 => "2015", Edition::Edition2018 => "2018", Edition::Edition2021 => "2021", + Edition::Edition2024 => "2024", }) } } diff --git a/crates/base-db/src/lib.rs b/crates/base-db/src/lib.rs index 57e7934367b..a0a55df5f99 100644 --- a/crates/base-db/src/lib.rs +++ b/crates/base-db/src/lib.rs @@ -4,27 +4,27 @@ mod input; mod change; -pub mod fixture; -pub mod span; use std::panic; use rustc_hash::FxHashSet; -use syntax::{ast, Parse, SourceFile, TextRange, TextSize}; +use syntax::{ast, Parse, SourceFile}; use triomphe::Arc; pub use crate::{ - change::Change, + change::FileChange, input::{ CrateData, CrateDisplayName, CrateGraph, CrateId, CrateName, CrateOrigin, Dependency, - DependencyKind, Edition, Env, LangCrateOrigin, ProcMacro, ProcMacroExpander, - ProcMacroExpansionError, ProcMacroId, ProcMacroKind, ProcMacroLoadResult, ProcMacroPaths, - ProcMacros, ReleaseChannel, SourceRoot, SourceRootId, TargetLayoutLoadResult, + DependencyKind, Edition, Env, LangCrateOrigin, ProcMacroPaths, ReleaseChannel, SourceRoot, + SourceRootId, TargetLayoutLoadResult, }, }; pub use salsa::{self, Cancelled}; +pub use span::{FilePosition, FileRange}; pub use vfs::{file_set::FileSet, AnchoredPath, AnchoredPathBuf, FileId, VfsPath}; +pub use semver::{BuildMetadata, Prerelease, Version, VersionReq}; + #[macro_export] macro_rules! impl_intern_key { ($name:ident) => { @@ -43,18 +43,6 @@ pub trait Upcast { fn upcast(&self) -> &T; } -#[derive(Clone, Copy, Debug)] -pub struct FilePosition { - pub file_id: FileId, - pub offset: TextSize, -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] -pub struct FileRange { - pub file_id: FileId, - pub range: TextRange, -} - pub const DEFAULT_PARSE_LRU_CAP: usize = 128; pub trait FileLoader { @@ -74,10 +62,6 @@ pub trait SourceDatabase: FileLoader + std::fmt::Debug { /// The crate graph. #[salsa::input] fn crate_graph(&self) -> Arc; - - /// The proc macros. - #[salsa::input] - fn proc_macros(&self) -> Arc; } fn parse(db: &dyn SourceDatabase, file_id: FileId) -> Parse { diff --git a/crates/cfg/Cargo.toml b/crates/cfg/Cargo.toml index 4324584df39..fbda065b10f 100644 --- a/crates/cfg/Cargo.toml +++ b/crates/cfg/Cargo.toml @@ -12,7 +12,7 @@ rust-version.workspace = true doctest = false [dependencies] -rustc-hash = "1.1.0" +rustc-hash.workspace = true # locals deps tt.workspace = true @@ -29,3 +29,6 @@ derive_arbitrary = "1.3.2" # local deps mbe.workspace = true syntax.workspace = true + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/cfg/src/tests.rs b/crates/cfg/src/tests.rs index c7ac1af934a..62fb429a63f 100644 --- a/crates/cfg/src/tests.rs +++ b/crates/cfg/src/tests.rs @@ -1,6 +1,6 @@ use arbitrary::{Arbitrary, Unstructured}; use expect_test::{expect, Expect}; -use mbe::{syntax_node_to_token_tree, DummyTestSpanMap}; +use mbe::{syntax_node_to_token_tree, DummyTestSpanMap, DUMMY}; use syntax::{ast, AstNode}; use crate::{CfgAtom, CfgExpr, CfgOptions, DnfExpr}; @@ -8,7 +8,7 @@ fn assert_parse_result(input: &str, expected: CfgExpr) { let source_file = ast::SourceFile::parse(input).ok().unwrap(); let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); - let tt = syntax_node_to_token_tree(tt.syntax(), DummyTestSpanMap); + let tt = syntax_node_to_token_tree(tt.syntax(), DummyTestSpanMap, DUMMY); let cfg = CfgExpr::parse(&tt); assert_eq!(cfg, expected); } @@ -16,7 +16,7 @@ fn assert_parse_result(input: &str, expected: CfgExpr) { fn check_dnf(input: &str, expect: Expect) { let source_file = ast::SourceFile::parse(input).ok().unwrap(); let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); - let tt = syntax_node_to_token_tree(tt.syntax(), DummyTestSpanMap); + let tt = syntax_node_to_token_tree(tt.syntax(), DummyTestSpanMap, DUMMY); let cfg = CfgExpr::parse(&tt); let actual = format!("#![cfg({})]", DnfExpr::new(cfg)); expect.assert_eq(&actual); @@ -25,7 +25,7 @@ fn check_dnf(input: &str, expect: Expect) { fn check_why_inactive(input: &str, opts: &CfgOptions, expect: Expect) { let source_file = ast::SourceFile::parse(input).ok().unwrap(); let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); - let tt = syntax_node_to_token_tree(tt.syntax(), DummyTestSpanMap); + let tt = syntax_node_to_token_tree(tt.syntax(), DummyTestSpanMap, DUMMY); let cfg = CfgExpr::parse(&tt); let dnf = DnfExpr::new(cfg); let why_inactive = dnf.why_inactive(opts).unwrap().to_string(); @@ -36,7 +36,7 @@ fn check_why_inactive(input: &str, opts: &CfgOptions, expect: Expect) { fn check_enable_hints(input: &str, opts: &CfgOptions, expected_hints: &[&str]) { let source_file = ast::SourceFile::parse(input).ok().unwrap(); let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); - let tt = syntax_node_to_token_tree(tt.syntax(), DummyTestSpanMap); + let tt = syntax_node_to_token_tree(tt.syntax(), DummyTestSpanMap, DUMMY); let cfg = CfgExpr::parse(&tt); let dnf = DnfExpr::new(cfg); let hints = dnf.compute_enable_hints(opts).map(|diff| diff.to_string()).collect::>(); diff --git a/crates/flycheck/Cargo.toml b/crates/flycheck/Cargo.toml index 4322d2d966a..b8c10da1b6e 100644 --- a/crates/flycheck/Cargo.toml +++ b/crates/flycheck/Cargo.toml @@ -13,14 +13,17 @@ doctest = false [dependencies] cargo_metadata.workspace = true -crossbeam-channel = "0.5.8" +crossbeam-channel.workspace = true tracing.workspace = true -rustc-hash = "1.1.0" +rustc-hash.workspace = true serde_json.workspace = true serde.workspace = true -command-group = "2.0.1" +command-group.workspace = true # local deps paths.workspace = true stdx.workspace = true toolchain.workspace = true + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/hir-def/Cargo.toml b/crates/hir-def/Cargo.toml index 2d174517605..5933d30040f 100644 --- a/crates/hir-def/Cargo.toml +++ b/crates/hir-def/Cargo.toml @@ -12,7 +12,7 @@ rust-version.workspace = true doctest = false [dependencies] -arrayvec = "0.7.2" +arrayvec.workspace = true bitflags.workspace = true cov-mark = "2.0.0-pre.1" dashmap.workspace = true @@ -23,7 +23,7 @@ indexmap.workspace = true itertools.workspace = true la-arena.workspace = true once_cell = "1.17.0" -rustc-hash = "1.1.0" +rustc-hash.workspace = true tracing.workspace = true smallvec.workspace = true hashbrown.workspace = true @@ -42,13 +42,18 @@ mbe.workspace = true cfg.workspace = true tt.workspace = true limit.workspace = true +span.workspace = true [dev-dependencies] -expect-test = "1.4.0" +expect-test.workspace = true # local deps test-utils.workspace = true +test-fixture.workspace = true [features] in-rust-tree = ["rustc-dependencies/in-rust-tree"] + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/hir-def/src/attr.rs b/crates/hir-def/src/attr.rs index 942b28fc145..26f76afb1f0 100644 --- a/crates/hir-def/src/attr.rs +++ b/crates/hir-def/src/attr.rs @@ -637,9 +637,12 @@ pub fn find_string_value_in_tt(self, key: &'attr str) -> Option<&SmolStr> { } } -fn any_has_attrs( - db: &dyn DefDatabase, - id: impl Lookup>, +fn any_has_attrs<'db>( + db: &(dyn DefDatabase + 'db), + id: impl Lookup< + Database<'db> = dyn DefDatabase + 'db, + Data = impl HasSource, + >, ) -> InFile { id.lookup(db).source(db).map(ast::AnyHasAttrs::new) } @@ -650,17 +653,17 @@ fn attrs_from_item_tree(db: &dyn DefDatabase, id: ItemTreeId tree.raw_attrs(mod_item.into()).clone() } -fn attrs_from_item_tree_loc( - db: &dyn DefDatabase, - lookup: impl Lookup>, +fn attrs_from_item_tree_loc<'db, N: ItemTreeNode>( + db: &(dyn DefDatabase + 'db), + lookup: impl Lookup = dyn DefDatabase + 'db, Data = ItemLoc>, ) -> RawAttrs { let id = lookup.lookup(db).id; attrs_from_item_tree(db, id) } -fn attrs_from_item_tree_assoc( - db: &dyn DefDatabase, - lookup: impl Lookup>, +fn attrs_from_item_tree_assoc<'db, N: ItemTreeNode>( + db: &(dyn DefDatabase + 'db), + lookup: impl Lookup = dyn DefDatabase + 'db, Data = AssocItemLoc>, ) -> RawAttrs { let id = lookup.lookup(db).id; attrs_from_item_tree(db, id) diff --git a/crates/hir-def/src/attr/tests.rs b/crates/hir-def/src/attr/tests.rs index 0f98a4ec93c..1a63e96bfa9 100644 --- a/crates/hir-def/src/attr/tests.rs +++ b/crates/hir-def/src/attr/tests.rs @@ -1,19 +1,23 @@ //! This module contains tests for doc-expression parsing. //! Currently, it tests `#[doc(hidden)]` and `#[doc(alias)]`. +use triomphe::Arc; + use base_db::FileId; -use hir_expand::span::{RealSpanMap, SpanMapRef}; +use hir_expand::span_map::{RealSpanMap, SpanMap}; use mbe::syntax_node_to_token_tree; -use syntax::{ast, AstNode}; +use syntax::{ast, AstNode, TextRange}; use crate::attr::{DocAtom, DocExpr}; fn assert_parse_result(input: &str, expected: DocExpr) { let source_file = ast::SourceFile::parse(input).ok().unwrap(); let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); + let map = SpanMap::RealSpanMap(Arc::new(RealSpanMap::absolute(FileId::from_raw(0)))); let tt = syntax_node_to_token_tree( tt.syntax(), - SpanMapRef::RealSpanMap(&RealSpanMap::absolute(FileId::from_raw(0))), + map.as_ref(), + map.span_for_range(TextRange::empty(0.into())), ); let cfg = DocExpr::parse(&tt); assert_eq!(cfg, expected); diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs index c6a90932015..a45ec844aba 100644 --- a/crates/hir-def/src/body/lower.rs +++ b/crates/hir-def/src/body/lower.rs @@ -8,7 +8,7 @@ use hir_expand::{ ast_id_map::AstIdMap, name::{name, AsName, Name}, - AstId, ExpandError, InFile, + ExpandError, InFile, }; use intern::Interned; use profile::Count; @@ -66,7 +66,7 @@ pub(super) fn lower( krate, def_map: expander.module.def_map(db), source_map: BodySourceMap::default(), - ast_id_map: db.ast_id_map(expander.current_file_id), + ast_id_map: db.ast_id_map(expander.current_file_id()), body: Body { exprs: Default::default(), pats: Default::default(), @@ -408,7 +408,7 @@ fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option { ast::Expr::ParenExpr(e) => { let inner = self.collect_expr_opt(e.expr()); // make the paren expr point to the inner expression as well - let src = self.expander.to_source(syntax_ptr); + let src = self.expander.in_file(syntax_ptr); self.source_map.expr_map.insert(src, inner); inner } @@ -441,7 +441,7 @@ fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option { Some(e) => self.collect_expr(e), None => self.missing_expr(), }; - let src = self.expander.to_source(AstPtr::new(&field)); + let src = self.expander.in_file(AstPtr::new(&field)); self.source_map.field_map_back.insert(expr, src); Some(RecordLitField { name, expr }) }) @@ -644,7 +644,7 @@ fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option { Some(id) => { // Make the macro-call point to its expanded expression so we can query // semantics on syntax pointers to the macro - let src = self.expander.to_source(syntax_ptr); + let src = self.expander.in_file(syntax_ptr); self.source_map.expr_map.insert(src, id); id } @@ -957,22 +957,31 @@ fn collect_macro_call( T: ast::AstNode, { // File containing the macro call. Expansion errors will be attached here. - let outer_file = self.expander.current_file_id; + let outer_file = self.expander.current_file_id(); - let macro_call_ptr = self.expander.to_source(AstPtr::new(&mcall)); + let macro_call_ptr = self.expander.in_file(syntax_ptr); let module = self.expander.module.local_id; - let res = self.expander.enter_expand(self.db, mcall, |path| { - self.def_map - .resolve_path( - self.db, - module, - &path, - crate::item_scope::BuiltinShadowMode::Other, - Some(MacroSubNs::Bang), - ) - .0 - .take_macros() - }); + + let res = match self.def_map.modules[module] + .scope + .macro_invocations + .get(&InFile::new(outer_file, self.ast_id_map.ast_id_for_ptr(syntax_ptr))) + { + // fast path, macro call is in a block module + Some(&call) => Ok(self.expander.enter_expand_id(self.db, call)), + None => self.expander.enter_expand(self.db, mcall, |path| { + self.def_map + .resolve_path( + self.db, + module, + &path, + crate::item_scope::BuiltinShadowMode::Other, + Some(MacroSubNs::Bang), + ) + .0 + .take_macros() + }), + }; let res = match res { Ok(res) => res, @@ -986,7 +995,6 @@ fn collect_macro_call( return collector(self, None); } }; - if record_diagnostics { match &res.err { Some(ExpandError::UnresolvedProcMacro(krate)) => { @@ -1013,10 +1021,10 @@ fn collect_macro_call( Some((mark, expansion)) => { // Keep collecting even with expansion errors so we can provide completions and // other services in incomplete macro expressions. - self.source_map.expansions.insert(macro_call_ptr, self.expander.current_file_id); + self.source_map.expansions.insert(macro_call_ptr, self.expander.current_file_id()); let prev_ast_id_map = mem::replace( &mut self.ast_id_map, - self.db.ast_id_map(self.expander.current_file_id), + self.db.ast_id_map(self.expander.current_file_id()), ); if record_diagnostics { @@ -1066,7 +1074,7 @@ fn collect_macro_as_stmt( Some(tail) => { // Make the macro-call point to its expanded expression so we can query // semantics on syntax pointers to the macro - let src = self.expander.to_source(syntax_ptr); + let src = self.expander.in_file(syntax_ptr); self.source_map.expr_map.insert(src, tail); Some(tail) } @@ -1140,7 +1148,7 @@ fn collect_block_( let block_id = if block_has_items { let file_local_id = self.ast_id_map.ast_id(&block); - let ast_id = AstId::new(self.expander.current_file_id, file_local_id); + let ast_id = self.expander.in_file(file_local_id); Some(self.db.intern_block(BlockLoc { ast_id, module: self.expander.module })) } else { None @@ -1333,7 +1341,7 @@ fn collect_pat(&mut self, pat: ast::Pat, binding_list: &mut BindingList) -> PatI let ast_pat = f.pat()?; let pat = self.collect_pat(ast_pat, binding_list); let name = f.field_name()?.as_name(); - let src = self.expander.to_source(AstPtr::new(&f)); + let src = self.expander.in_file(AstPtr::new(&f)); self.source_map.pat_field_map_back.insert(pat, src); Some(RecordFieldPat { name, pat }) }) @@ -1391,7 +1399,7 @@ fn collect_pat(&mut self, pat: ast::Pat, binding_list: &mut BindingList) -> PatI ast::Pat::MacroPat(mac) => match mac.macro_call() { Some(call) => { let macro_ptr = AstPtr::new(&call); - let src = self.expander.to_source(AstPtr::new(&Either::Left(pat))); + let src = self.expander.in_file(AstPtr::new(&Either::Left(pat))); let pat = self.collect_macro_call(call, macro_ptr, true, |this, expanded_pat| { this.collect_pat_opt(expanded_pat, binding_list) @@ -1472,10 +1480,7 @@ fn check_cfg(&mut self, owner: &dyn ast::HasAttrs) -> Option<()> { } self.source_map.diagnostics.push(BodyDiagnostic::InactiveCode { - node: InFile::new( - self.expander.current_file_id, - SyntaxNodePtr::new(owner.syntax()), - ), + node: self.expander.in_file(SyntaxNodePtr::new(owner.syntax())), cfg, opts: self.expander.cfg_options().clone(), }); @@ -1514,10 +1519,7 @@ fn resolve_label( } else { Err(BodyDiagnostic::UnreachableLabel { name, - node: InFile::new( - self.expander.current_file_id, - AstPtr::new(&lifetime), - ), + node: self.expander.in_file(AstPtr::new(&lifetime)), }) }; } @@ -1526,7 +1528,7 @@ fn resolve_label( Err(BodyDiagnostic::UndeclaredLabel { name, - node: InFile::new(self.expander.current_file_id, AstPtr::new(&lifetime)), + node: self.expander.in_file(AstPtr::new(&lifetime)), }) } @@ -1990,7 +1992,7 @@ fn pat_literal_to_hir(lit: &ast::LiteralPat) -> Option<(Literal, ast::Literal)> impl ExprCollector<'_> { fn alloc_expr(&mut self, expr: Expr, ptr: ExprPtr) -> ExprId { - let src = self.expander.to_source(ptr); + let src = self.expander.in_file(ptr); let id = self.body.exprs.alloc(expr); self.source_map.expr_map_back.insert(id, src.clone()); self.source_map.expr_map.insert(src, id); @@ -2018,7 +2020,7 @@ fn alloc_binding(&mut self, name: Name, mode: BindingAnnotation) -> BindingId { } fn alloc_pat(&mut self, pat: Pat, ptr: PatPtr) -> PatId { - let src = self.expander.to_source(ptr); + let src = self.expander.in_file(ptr); let id = self.body.pats.alloc(pat); self.source_map.pat_map_back.insert(id, src.clone()); self.source_map.pat_map.insert(src, id); @@ -2033,7 +2035,7 @@ fn missing_pat(&mut self) -> PatId { } fn alloc_label(&mut self, label: Label, ptr: LabelPtr) -> LabelId { - let src = self.expander.to_source(ptr); + let src = self.expander.in_file(ptr); let id = self.body.labels.alloc(label); self.source_map.label_map_back.insert(id, src.clone()); self.source_map.label_map.insert(src, id); diff --git a/crates/hir-def/src/body/scope.rs b/crates/hir-def/src/body/scope.rs index baca293e290..ab623250d40 100644 --- a/crates/hir-def/src/body/scope.rs +++ b/crates/hir-def/src/body/scope.rs @@ -267,9 +267,10 @@ fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope #[cfg(test)] mod tests { - use base_db::{fixture::WithFixture, FileId, SourceDatabase}; + use base_db::{FileId, SourceDatabase}; use hir_expand::{name::AsName, InFile}; use syntax::{algo::find_node_at_offset, ast, AstNode}; + use test_fixture::WithFixture; use test_utils::{assert_eq_text, extract_offset}; use crate::{db::DefDatabase, test_db::TestDB, FunctionId, ModuleDefId}; diff --git a/crates/hir-def/src/body/tests.rs b/crates/hir-def/src/body/tests.rs index 2b432dfbb92..a76ddffb411 100644 --- a/crates/hir-def/src/body/tests.rs +++ b/crates/hir-def/src/body/tests.rs @@ -1,7 +1,8 @@ mod block; -use base_db::{fixture::WithFixture, SourceDatabase}; +use base_db::SourceDatabase; use expect_test::{expect, Expect}; +use test_fixture::WithFixture; use crate::{test_db::TestDB, ModuleDefId}; diff --git a/crates/hir-def/src/data.rs b/crates/hir-def/src/data.rs index 635d13f24ad..9c183c9332b 100644 --- a/crates/hir-def/src/data.rs +++ b/crates/hir-def/src/data.rs @@ -16,7 +16,7 @@ db::DefDatabase, expander::{Expander, Mark}, item_tree::{self, AssocItem, FnFlags, ItemTree, ItemTreeId, MacroCall, ModItem, TreeId}, - macro_call_as_call_id, macro_id_to_def_id, + macro_call_as_call_id, nameres::{ attr_resolution::ResolvedAttr, diagnostics::DefDiagnostic, @@ -720,7 +720,7 @@ fn collect_item( ) .0 .take_macros() - .map(|it| macro_id_to_def_id(self.db, it)) + .map(|it| self.db.macro_def(it)) }; match macro_call_as_call_id( self.db.upcast(), diff --git a/crates/hir-def/src/db.rs b/crates/hir-def/src/db.rs index 31c1a713031..d5831022f28 100644 --- a/crates/hir-def/src/db.rs +++ b/crates/hir-def/src/db.rs @@ -1,7 +1,7 @@ //! Defines database & queries for name resolution. use base_db::{salsa, CrateId, SourceDatabase, Upcast}; use either::Either; -use hir_expand::{db::ExpandDatabase, HirFileId}; +use hir_expand::{db::ExpandDatabase, HirFileId, MacroDefId}; use intern::Interned; use la_arena::ArenaMap; use syntax::{ast, AstPtr}; @@ -24,9 +24,10 @@ AttrDefId, BlockId, BlockLoc, ConstBlockId, ConstBlockLoc, ConstId, ConstLoc, DefWithBodyId, EnumId, EnumLoc, ExternBlockId, ExternBlockLoc, ExternCrateId, ExternCrateLoc, FunctionId, FunctionLoc, GenericDefId, ImplId, ImplLoc, InTypeConstId, InTypeConstLoc, LocalEnumVariantId, - LocalFieldId, Macro2Id, Macro2Loc, MacroRulesId, MacroRulesLoc, ProcMacroId, ProcMacroLoc, - StaticId, StaticLoc, StructId, StructLoc, TraitAliasId, TraitAliasLoc, TraitId, TraitLoc, - TypeAliasId, TypeAliasLoc, UnionId, UnionLoc, UseId, UseLoc, VariantId, + LocalFieldId, Macro2Id, Macro2Loc, MacroId, MacroRulesId, MacroRulesLoc, MacroRulesLocFlags, + ProcMacroId, ProcMacroLoc, StaticId, StaticLoc, StructId, StructLoc, TraitAliasId, + TraitAliasLoc, TraitId, TraitLoc, TypeAliasId, TypeAliasLoc, UnionId, UnionLoc, UseId, UseLoc, + VariantId, }; #[salsa::query_group(InternDatabaseStorage)] @@ -110,6 +111,8 @@ pub trait DefDatabase: InternDatabase + ExpandDatabase + Upcast Arc; + fn macro_def(&self, m: MacroId) -> MacroDefId; + // region:data #[salsa::invoke(StructData::struct_data_query)] @@ -239,12 +242,6 @@ fn fields_attrs_source_map( #[salsa::invoke(LangItems::crate_lang_items_query)] fn crate_lang_items(&self, krate: CrateId) -> Arc; - #[salsa::transparent] - fn crate_limits(&self, crate_id: CrateId) -> CrateLimits; - - #[salsa::transparent] - fn recursion_limit(&self, crate_id: CrateId) -> u32; - fn crate_supports_no_std(&self, crate_id: CrateId) -> bool; } @@ -253,24 +250,6 @@ fn crate_def_map_wait(db: &dyn DefDatabase, krate: CrateId) -> Arc { db.crate_def_map_query(krate) } -pub struct CrateLimits { - /// The maximum depth for potentially infinitely-recursive compile-time operations like macro expansion or auto-dereference. - pub recursion_limit: u32, -} - -fn crate_limits(db: &dyn DefDatabase, crate_id: CrateId) -> CrateLimits { - let def_map = db.crate_def_map(crate_id); - - CrateLimits { - // 128 is the default in rustc. - recursion_limit: def_map.recursion_limit().unwrap_or(128), - } -} - -fn recursion_limit(db: &dyn DefDatabase, crate_id: CrateId) -> u32 { - db.crate_limits(crate_id).recursion_limit -} - fn crate_supports_no_std(db: &dyn DefDatabase, crate_id: CrateId) -> bool { let file = db.crate_graph()[crate_id].root_file_id; let item_tree = db.file_item_tree(file.into()); @@ -305,3 +284,78 @@ fn crate_supports_no_std(db: &dyn DefDatabase, crate_id: CrateId) -> bool { false } + +fn macro_def(db: &dyn DefDatabase, id: MacroId) -> MacroDefId { + use hir_expand::InFile; + + use crate::{Lookup, MacroDefKind, MacroExpander}; + + let kind = |expander, file_id, m| { + let in_file = InFile::new(file_id, m); + match expander { + MacroExpander::Declarative => MacroDefKind::Declarative(in_file), + MacroExpander::BuiltIn(it) => MacroDefKind::BuiltIn(it, in_file), + MacroExpander::BuiltInAttr(it) => MacroDefKind::BuiltInAttr(it, in_file), + MacroExpander::BuiltInDerive(it) => MacroDefKind::BuiltInDerive(it, in_file), + MacroExpander::BuiltInEager(it) => MacroDefKind::BuiltInEager(it, in_file), + } + }; + + match id { + MacroId::Macro2Id(it) => { + let loc: Macro2Loc = it.lookup(db); + + let item_tree = loc.id.item_tree(db); + let makro = &item_tree[loc.id.value]; + MacroDefId { + krate: loc.container.krate, + kind: kind(loc.expander, loc.id.file_id(), makro.ast_id.upcast()), + local_inner: false, + allow_internal_unsafe: loc.allow_internal_unsafe, + span: db + .span_map(loc.id.file_id()) + .span_for_range(db.ast_id_map(loc.id.file_id()).get(makro.ast_id).text_range()), + edition: loc.edition, + } + } + + MacroId::MacroRulesId(it) => { + let loc: MacroRulesLoc = it.lookup(db); + + let item_tree = loc.id.item_tree(db); + let makro = &item_tree[loc.id.value]; + MacroDefId { + krate: loc.container.krate, + kind: kind(loc.expander, loc.id.file_id(), makro.ast_id.upcast()), + local_inner: loc.flags.contains(MacroRulesLocFlags::LOCAL_INNER), + allow_internal_unsafe: loc + .flags + .contains(MacroRulesLocFlags::ALLOW_INTERNAL_UNSAFE), + span: db + .span_map(loc.id.file_id()) + .span_for_range(db.ast_id_map(loc.id.file_id()).get(makro.ast_id).text_range()), + edition: loc.edition, + } + } + MacroId::ProcMacroId(it) => { + let loc = it.lookup(db); + + let item_tree = loc.id.item_tree(db); + let makro = &item_tree[loc.id.value]; + MacroDefId { + krate: loc.container.krate, + kind: MacroDefKind::ProcMacro( + loc.expander, + loc.kind, + InFile::new(loc.id.file_id(), makro.ast_id), + ), + local_inner: false, + allow_internal_unsafe: false, + span: db + .span_map(loc.id.file_id()) + .span_for_range(db.ast_id_map(loc.id.file_id()).get(makro.ast_id).text_range()), + edition: loc.edition, + } + } + } +} diff --git a/crates/hir-def/src/expander.rs b/crates/hir-def/src/expander.rs index 398f116d831..b83feeedc34 100644 --- a/crates/hir-def/src/expander.rs +++ b/crates/hir-def/src/expander.rs @@ -4,15 +4,15 @@ use cfg::CfgOptions; use drop_bomb::DropBomb; use hir_expand::{ - attrs::RawAttrs, mod_path::ModPath, span::SpanMap, ExpandError, ExpandResult, HirFileId, + attrs::RawAttrs, mod_path::ModPath, span_map::SpanMap, ExpandError, ExpandResult, HirFileId, InFile, MacroCallId, }; use limit::Limit; -use syntax::{ast, Parse, SyntaxNode}; +use syntax::{ast, Parse}; use crate::{ - attr::Attrs, db::DefDatabase, lower::LowerCtx, macro_id_to_def_id, path::Path, AsMacroCall, - MacroId, ModuleId, UnresolvedMacro, + attr::Attrs, db::DefDatabase, lower::LowerCtx, path::Path, AsMacroCall, MacroId, ModuleId, + UnresolvedMacro, }; #[derive(Debug)] @@ -20,7 +20,7 @@ pub struct Expander { cfg_options: CfgOptions, span_map: SpanMap, krate: CrateId, - pub(crate) current_file_id: HirFileId, + current_file_id: HirFileId, pub(crate) module: ModuleId, /// `recursion_depth == usize::MAX` indicates that the recursion limit has been reached. recursion_depth: u32, @@ -29,12 +29,13 @@ pub struct Expander { impl Expander { pub fn new(db: &dyn DefDatabase, current_file_id: HirFileId, module: ModuleId) -> Expander { - let recursion_limit = db.recursion_limit(module.krate); - #[cfg(not(test))] - let recursion_limit = Limit::new(recursion_limit as usize); - // Without this, `body::tests::your_stack_belongs_to_me` stack-overflows in debug - #[cfg(test)] - let recursion_limit = Limit::new(std::cmp::min(32, recursion_limit as usize)); + let recursion_limit = module.def_map(db).recursion_limit() as usize; + let recursion_limit = Limit::new(if cfg!(test) { + // Without this, `body::tests::your_stack_belongs_to_me` stack-overflows in debug + std::cmp::min(32, recursion_limit) + } else { + recursion_limit + }); Expander { current_file_id, module, @@ -56,9 +57,9 @@ pub fn enter_expand( let mut unresolved_macro_err = None; let result = self.within_limit(db, |this| { - let macro_call = InFile::new(this.current_file_id, ¯o_call); + let macro_call = this.in_file(¯o_call); match macro_call.as_call_id_with_errors(db.upcast(), this.module.krate(), |path| { - resolver(path).map(|it| macro_id_to_def_id(db, it)) + resolver(path).map(|it| db.macro_def(it)) }) { Ok(call_id) => call_id, Err(resolve_err) => { @@ -83,17 +84,6 @@ pub fn enter_expand_id( self.within_limit(db, |_this| ExpandResult::ok(Some(call_id))) } - fn enter_expand_inner( - db: &dyn DefDatabase, - call_id: MacroCallId, - error: Option, - ) -> ExpandResult>>> { - let macro_file = call_id.as_macro_file(); - let ExpandResult { value, err } = db.parse_macro_expansion(macro_file); - - ExpandResult { value: Some(InFile::new(macro_file.into(), value.0)), err: error.or(err) } - } - pub fn exit(&mut self, mut mark: Mark) { self.span_map = mark.span_map; self.current_file_id = mark.file_id; @@ -113,7 +103,7 @@ pub fn ctx<'a>(&self, db: &'a dyn DefDatabase) -> LowerCtx<'a> { LowerCtx::new(db, self.span_map.clone(), self.current_file_id) } - pub(crate) fn to_source(&self, value: T) -> InFile { + pub(crate) fn in_file(&self, value: T) -> InFile { InFile { file_id: self.current_file_id, value } } @@ -164,26 +154,34 @@ fn within_limit( return ExpandResult { value: None, err }; }; - let res = Self::enter_expand_inner(db, call_id, err); - match res.err { - // If proc-macro is disabled or unresolved, we want to expand to a missing expression - // instead of an empty tree which might end up in an empty block. - Some(ExpandError::UnresolvedProcMacro(_)) => res.map(|_| None), - _ => res.map(|value| { - value.and_then(|InFile { file_id, value }| { - let parse = value.cast::()?; + let macro_file = call_id.as_macro_file(); + let res = db.parse_macro_expansion(macro_file); + + let err = err.or(res.err); + ExpandResult { + value: match err { + // If proc-macro is disabled or unresolved, we want to expand to a missing expression + // instead of an empty tree which might end up in an empty block. + Some(ExpandError::UnresolvedProcMacro(_)) => None, + _ => (|| { + let parse = res.value.0.cast::()?; self.recursion_depth += 1; - let old_span_map = std::mem::replace(&mut self.span_map, db.span_map(file_id)); - let old_file_id = std::mem::replace(&mut self.current_file_id, file_id); + let old_span_map = std::mem::replace( + &mut self.span_map, + SpanMap::ExpansionSpanMap(res.value.1), + ); + let old_file_id = + std::mem::replace(&mut self.current_file_id, macro_file.into()); let mark = Mark { file_id: old_file_id, span_map: old_span_map, bomb: DropBomb::new("expansion mark dropped"), }; Some((mark, parse)) - }) - }), + })(), + }, + err, } } } diff --git a/crates/hir-def/src/find_path.rs b/crates/hir-def/src/find_path.rs index 13af0b0218e..4737b48703d 100644 --- a/crates/hir-def/src/find_path.rs +++ b/crates/hir-def/src/find_path.rs @@ -585,9 +585,9 @@ fn find_local_import_locations( #[cfg(test)] mod tests { - use base_db::fixture::WithFixture; use hir_expand::db::ExpandDatabase; use syntax::ast::AstNode; + use test_fixture::WithFixture; use crate::test_db::TestDB; diff --git a/crates/hir-def/src/import_map.rs b/crates/hir-def/src/import_map.rs index 26d333f9a0b..aea7229bd64 100644 --- a/crates/hir-def/src/import_map.rs +++ b/crates/hir-def/src/import_map.rs @@ -469,8 +469,9 @@ pub fn search_dependencies( #[cfg(test)] mod tests { - use base_db::{fixture::WithFixture, SourceDatabase, Upcast}; + use base_db::{SourceDatabase, Upcast}; use expect_test::{expect, Expect}; + use test_fixture::WithFixture; use crate::{db::DefDatabase, test_db::TestDB, ItemContainerId, Lookup}; diff --git a/crates/hir-def/src/item_scope.rs b/crates/hir-def/src/item_scope.rs index ce83cb435e2..4902f24e2e3 100644 --- a/crates/hir-def/src/item_scope.rs +++ b/crates/hir-def/src/item_scope.rs @@ -102,8 +102,10 @@ pub struct ItemScope { // FIXME: Macro shadowing in one module is not properly handled. Non-item place macros will // be all resolved to the last one defined if shadowing happens. legacy_macros: FxHashMap>, - /// The derive macro invocations in this scope. + /// The attribute macro invocations in this scope. attr_macros: FxHashMap, MacroCallId>, + /// The macro invocations in this scope. + pub macro_invocations: FxHashMap, MacroCallId>, /// The derive macro invocations in this scope, keyed by the owner item over the actual derive attributes /// paired with the derive macro invocations for the specific attribute. derive_macros: FxHashMap, SmallVec<[DeriveMacroInvocation; 1]>>, @@ -345,6 +347,10 @@ pub(crate) fn add_attr_macro_invoc(&mut self, item: AstId, call: Macr self.attr_macros.insert(item, call); } + pub(crate) fn add_macro_invoc(&mut self, call: AstId, call_id: MacroCallId) { + self.macro_invocations.insert(call, call_id); + } + pub(crate) fn attr_macro_invocs( &self, ) -> impl Iterator, MacroCallId)> + '_ { @@ -692,6 +698,7 @@ pub(crate) fn shrink_to_fit(&mut self) { use_imports_values, use_imports_types, use_imports_macros, + macro_invocations, } = self; types.shrink_to_fit(); values.shrink_to_fit(); @@ -709,6 +716,7 @@ pub(crate) fn shrink_to_fit(&mut self) { derive_macros.shrink_to_fit(); extern_crate_decls.shrink_to_fit(); use_decls.shrink_to_fit(); + macro_invocations.shrink_to_fit(); } } diff --git a/crates/hir-def/src/item_tree.rs b/crates/hir-def/src/item_tree.rs index 3d2cddffa3b..20e4e44339e 100644 --- a/crates/hir-def/src/item_tree.rs +++ b/crates/hir-def/src/item_tree.rs @@ -29,6 +29,9 @@ //! //! In general, any item in the `ItemTree` stores its `AstId`, which allows mapping it back to its //! surface syntax. +//! +//! Note that we cannot store [`span::Span`]s inside of this, as typing in an item invalidates its +//! encompassing span! mod lower; mod pretty; @@ -42,7 +45,7 @@ }; use ast::{AstNode, HasName, StructKind}; -use base_db::{span::SyntaxContextId, CrateId}; +use base_db::CrateId; use either::Either; use hir_expand::{ ast_id_map::{AstIdNode, FileAstId}, @@ -55,6 +58,7 @@ use profile::Count; use rustc_hash::FxHashMap; use smallvec::SmallVec; +use span::Span; use stdx::never; use syntax::{ast, match_ast, SyntaxKind}; use triomphe::Arc; @@ -280,7 +284,7 @@ struct ItemTreeData { mods: Arena, macro_calls: Arena, macro_rules: Arena, - macro_defs: Arena, + macro_defs: Arena, vis: ItemVisibilities, } @@ -513,7 +517,7 @@ fn index(&self, index: Idx<$typ>) -> &Self::Output { Mod in mods -> ast::Module, MacroCall in macro_calls -> ast::MacroCall, MacroRules in macro_rules -> ast::MacroRules, - MacroDef in macro_defs -> ast::MacroDef, + Macro2 in macro_defs -> ast::MacroDef, } macro_rules! impl_index { @@ -746,7 +750,8 @@ pub struct MacroCall { pub path: Interned, pub ast_id: FileAstId, pub expand_to: ExpandTo, - pub call_site: SyntaxContextId, + // FIXME: We need to move this out. It invalidates the item tree when typing inside the macro call. + pub call_site: Span, } #[derive(Debug, Clone, Eq, PartialEq)] @@ -758,7 +763,7 @@ pub struct MacroRules { /// "Macros 2.0" macro definition. #[derive(Debug, Clone, Eq, PartialEq)] -pub struct MacroDef { +pub struct Macro2 { pub name: Name, pub visibility: RawVisibilityId, pub ast_id: FileAstId, @@ -917,7 +922,7 @@ pub fn as_assoc_item(&self) -> Option { | ModItem::Impl(_) | ModItem::Mod(_) | ModItem::MacroRules(_) - | ModItem::MacroDef(_) => None, + | ModItem::Macro2(_) => None, ModItem::MacroCall(call) => Some(AssocItem::MacroCall(*call)), ModItem::Const(konst) => Some(AssocItem::Const(*konst)), ModItem::TypeAlias(alias) => Some(AssocItem::TypeAlias(*alias)), @@ -943,7 +948,7 @@ pub fn ast_id(&self, tree: &ItemTree) -> FileAstId { ModItem::Mod(it) => tree[it.index()].ast_id().upcast(), ModItem::MacroCall(it) => tree[it.index()].ast_id().upcast(), ModItem::MacroRules(it) => tree[it.index()].ast_id().upcast(), - ModItem::MacroDef(it) => tree[it.index()].ast_id().upcast(), + ModItem::Macro2(it) => tree[it.index()].ast_id().upcast(), } } } diff --git a/crates/hir-def/src/item_tree/lower.rs b/crates/hir-def/src/item_tree/lower.rs index 83a2790ce8f..8e2fafe81b5 100644 --- a/crates/hir-def/src/item_tree/lower.rs +++ b/crates/hir-def/src/item_tree/lower.rs @@ -2,7 +2,7 @@ use std::collections::hash_map::Entry; -use hir_expand::{ast_id_map::AstIdMap, span::SpanMapRef, HirFileId}; +use hir_expand::{ast_id_map::AstIdMap, span_map::SpanMapRef, HirFileId}; use syntax::ast::{self, HasModuleItem, HasTypeBounds}; use crate::{ @@ -549,7 +549,7 @@ fn lower_macro_call(&mut self, m: &ast::MacroCall) -> Option Option Option> { + fn lower_macro_def(&mut self, m: &ast::MacroDef) -> Option> { let name = m.name().map(|it| it.as_name())?; let ast_id = self.source_ast_id_map.ast_id(m); let visibility = self.lower_visibility(m); - let res = MacroDef { name, ast_id, visibility }; + let res = Macro2 { name, ast_id, visibility }; Some(id(self.data().macro_defs.alloc(res))) } diff --git a/crates/hir-def/src/item_tree/pretty.rs b/crates/hir-def/src/item_tree/pretty.rs index 244111d202c..6d92fce0727 100644 --- a/crates/hir-def/src/item_tree/pretty.rs +++ b/crates/hir-def/src/item_tree/pretty.rs @@ -464,8 +464,8 @@ fn print_mod_item(&mut self, item: ModItem) { let MacroRules { name, ast_id: _ } = &self.tree[it]; wln!(self, "macro_rules! {} {{ ... }}", name.display(self.db.upcast())); } - ModItem::MacroDef(it) => { - let MacroDef { name, visibility, ast_id: _ } = &self.tree[it]; + ModItem::Macro2(it) => { + let Macro2 { name, visibility, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); wln!(self, "macro {} {{ ... }}", name.display(self.db.upcast())); } diff --git a/crates/hir-def/src/item_tree/tests.rs b/crates/hir-def/src/item_tree/tests.rs index 96c65b941c1..f97ae0d8e43 100644 --- a/crates/hir-def/src/item_tree/tests.rs +++ b/crates/hir-def/src/item_tree/tests.rs @@ -1,5 +1,5 @@ -use base_db::fixture::WithFixture; use expect_test::{expect, Expect}; +use test_fixture::WithFixture; use crate::{db::DefDatabase, test_db::TestDB}; diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs index b5333861cc8..22ba3aab4e9 100644 --- a/crates/hir-def/src/lib.rs +++ b/crates/hir-def/src/lib.rs @@ -63,7 +63,7 @@ macro_rules! eprintln { panic::{RefUnwindSafe, UnwindSafe}, }; -use base_db::{impl_intern_key, salsa, span::SyntaxContextId, CrateId, ProcMacroKind}; +use base_db::{impl_intern_key, salsa, CrateId, Edition}; use hir_expand::{ ast_id_map::{AstIdNode, FileAstId}, attrs::{Attr, AttrId, AttrInput}, @@ -72,24 +72,27 @@ macro_rules! eprintln { builtin_fn_macro::{BuiltinFnLikeExpander, EagerExpander}, db::ExpandDatabase, eager::expand_eager_macro_input, + impl_intern_lookup, name::Name, - proc_macro::ProcMacroExpander, + proc_macro::{CustomProcMacroExpander, ProcMacroKind}, AstId, ExpandError, ExpandResult, ExpandTo, HirFileId, InFile, MacroCallId, MacroCallKind, MacroDefId, MacroDefKind, }; use item_tree::ExternBlock; use la_arena::Idx; use nameres::DefMap; +use span::Span; use stdx::impl_from; use syntax::{ast, AstNode}; -pub use hir_expand::tt; +pub use hir_expand::{tt, Intern, Lookup}; use crate::{ builtin_type::BuiltinType, data::adt::VariantData, + db::DefDatabase, item_tree::{ - Const, Enum, ExternCrate, Function, Impl, ItemTreeId, ItemTreeNode, MacroDef, MacroRules, + Const, Enum, ExternCrate, Function, Impl, ItemTreeId, ItemTreeNode, Macro2, MacroRules, Static, Struct, Trait, TraitAlias, TypeAlias, Union, Use, }, }; @@ -101,7 +104,7 @@ pub struct CrateRootModuleId { } impl CrateRootModuleId { - pub fn def_map(&self, db: &dyn db::DefDatabase) -> Arc { + pub fn def_map(&self, db: &dyn DefDatabase) -> Arc { db.crate_def_map(self.krate) } @@ -163,7 +166,7 @@ pub struct ModuleId { } impl ModuleId { - pub fn def_map(self, db: &dyn db::DefDatabase) -> Arc { + pub fn def_map(self, db: &dyn DefDatabase) -> Arc { match self.block { Some(block) => db.block_def_map(block), None => db.crate_def_map(self.krate), @@ -174,7 +177,7 @@ pub fn krate(self) -> CrateId { self.krate } - pub fn name(self, db: &dyn db::DefDatabase) -> Option { + pub fn name(self, db: &dyn DefDatabase) -> Option { let def_map = self.def_map(db); let parent = def_map[self.local_id].parent?; def_map[parent].children.iter().find_map(|(name, module_id)| { @@ -186,7 +189,7 @@ pub fn name(self, db: &dyn db::DefDatabase) -> Option { }) } - pub fn containing_module(self, db: &dyn db::DefDatabase) -> Option { + pub fn containing_module(self, db: &dyn DefDatabase) -> Option { self.def_map(db).containing_module(self.local_id) } @@ -263,20 +266,7 @@ fn hash(&self, state: &mut H) { macro_rules! impl_intern { ($id:ident, $loc:ident, $intern:ident, $lookup:ident) => { impl_intern_key!($id); - - impl Intern for $loc { - type ID = $id; - fn intern(self, db: &dyn db::DefDatabase) -> $id { - db.$intern(self) - } - } - - impl Lookup for $id { - type Data = $loc; - fn lookup(&self, db: &dyn db::DefDatabase) -> $loc { - db.$lookup(*self) - } - } + impl_intern_lookup!(DefDatabase, $id, $loc, $intern, $lookup); }; } @@ -376,9 +366,10 @@ pub enum MacroExpander { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Macro2Loc { pub container: ModuleId, - pub id: ItemTreeId, + pub id: ItemTreeId, pub expander: MacroExpander, pub allow_internal_unsafe: bool, + pub edition: Edition, } impl_intern!(Macro2Id, Macro2Loc, intern_macro2, lookup_intern_macro2); @@ -389,19 +380,28 @@ pub struct MacroRulesLoc { pub container: ModuleId, pub id: ItemTreeId, pub expander: MacroExpander, - pub allow_internal_unsafe: bool, - pub local_inner: bool, + pub flags: MacroRulesLocFlags, + pub edition: Edition, } impl_intern!(MacroRulesId, MacroRulesLoc, intern_macro_rules, lookup_intern_macro_rules); +bitflags::bitflags! { + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub struct MacroRulesLocFlags: u8 { + const ALLOW_INTERNAL_UNSAFE = 1 << 0; + const LOCAL_INNER = 1 << 1; + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] pub struct ProcMacroId(salsa::InternId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct ProcMacroLoc { pub container: CrateRootModuleId, pub id: ItemTreeId, - pub expander: ProcMacroExpander, + pub expander: CustomProcMacroExpander, pub kind: ProcMacroKind, + pub edition: Edition, } impl_intern!(ProcMacroId, ProcMacroLoc, intern_proc_macro, lookup_intern_proc_macro); @@ -510,7 +510,7 @@ pub enum MacroId { impl_from!(Macro2Id, MacroRulesId, ProcMacroId for MacroId); impl MacroId { - pub fn is_attribute(self, db: &dyn db::DefDatabase) -> bool { + pub fn is_attribute(self, db: &dyn DefDatabase) -> bool { matches!(self, MacroId::ProcMacroId(it) if it.lookup(db).kind == ProcMacroKind::Attr) } } @@ -722,7 +722,7 @@ fn eq(&self, other: &Self) -> bool { } impl InTypeConstId { - pub fn source(&self, db: &dyn db::DefDatabase) -> ast::ConstArg { + pub fn source(&self, db: &dyn DefDatabase) -> ast::ConstArg { let src = self.lookup(db).id; let file_id = src.file_id; let root = &db.parse_or_expand(file_id); @@ -742,7 +742,7 @@ pub enum GeneralConstId { impl_from!(ConstId, ConstBlockId, InTypeConstId for GeneralConstId); impl GeneralConstId { - pub fn generic_def(self, db: &dyn db::DefDatabase) -> Option { + pub fn generic_def(self, db: &dyn DefDatabase) -> Option { match self { GeneralConstId::ConstId(it) => Some(it.into()), GeneralConstId::ConstBlockId(it) => it.lookup(db).parent.as_generic_def_id(), @@ -750,7 +750,7 @@ pub fn generic_def(self, db: &dyn db::DefDatabase) -> Option { } } - pub fn name(self, db: &dyn db::DefDatabase) -> String { + pub fn name(self, db: &dyn DefDatabase) -> String { match self { GeneralConstId::ConstId(const_id) => db .const_data(const_id) @@ -933,7 +933,7 @@ pub enum VariantId { impl_from!(EnumVariantId, StructId, UnionId for VariantId); impl VariantId { - pub fn variant_data(self, db: &dyn db::DefDatabase) -> Arc { + pub fn variant_data(self, db: &dyn DefDatabase) -> Arc { match self { VariantId::StructId(it) => db.struct_data(it).variant_data.clone(), VariantId::UnionId(it) => db.union_data(it).variant_data.clone(), @@ -943,7 +943,7 @@ pub fn variant_data(self, db: &dyn db::DefDatabase) -> Arc { } } - pub fn file_id(self, db: &dyn db::DefDatabase) -> HirFileId { + pub fn file_id(self, db: &dyn DefDatabase) -> HirFileId { match self { VariantId::EnumVariantId(it) => it.parent.lookup(db).id.file_id(), VariantId::StructId(it) => it.lookup(db).id.file_id(), @@ -960,22 +960,12 @@ pub fn adt_id(self) -> AdtId { } } -trait Intern { - type ID; - fn intern(self, db: &dyn db::DefDatabase) -> Self::ID; -} - -pub trait Lookup { - type Data; - fn lookup(&self, db: &dyn db::DefDatabase) -> Self::Data; -} - pub trait HasModule { - fn module(&self, db: &dyn db::DefDatabase) -> ModuleId; + fn module(&self, db: &dyn DefDatabase) -> ModuleId; } impl HasModule for ItemContainerId { - fn module(&self, db: &dyn db::DefDatabase) -> ModuleId { + fn module(&self, db: &dyn DefDatabase) -> ModuleId { match *self { ItemContainerId::ModuleId(it) => it, ItemContainerId::ImplId(it) => it.lookup(db).container, @@ -986,13 +976,13 @@ fn module(&self, db: &dyn db::DefDatabase) -> ModuleId { } impl HasModule for AssocItemLoc { - fn module(&self, db: &dyn db::DefDatabase) -> ModuleId { + fn module(&self, db: &dyn DefDatabase) -> ModuleId { self.container.module(db) } } impl HasModule for AdtId { - fn module(&self, db: &dyn db::DefDatabase) -> ModuleId { + fn module(&self, db: &dyn DefDatabase) -> ModuleId { match self { AdtId::StructId(it) => it.lookup(db).container, AdtId::UnionId(it) => it.lookup(db).container, @@ -1002,13 +992,13 @@ fn module(&self, db: &dyn db::DefDatabase) -> ModuleId { } impl HasModule for ExternCrateId { - fn module(&self, db: &dyn db::DefDatabase) -> ModuleId { + fn module(&self, db: &dyn DefDatabase) -> ModuleId { self.lookup(db).container } } impl HasModule for VariantId { - fn module(&self, db: &dyn db::DefDatabase) -> ModuleId { + fn module(&self, db: &dyn DefDatabase) -> ModuleId { match self { VariantId::EnumVariantId(it) => it.parent.lookup(db).container, VariantId::StructId(it) => it.lookup(db).container, @@ -1018,7 +1008,7 @@ fn module(&self, db: &dyn db::DefDatabase) -> ModuleId { } impl HasModule for MacroId { - fn module(&self, db: &dyn db::DefDatabase) -> ModuleId { + fn module(&self, db: &dyn DefDatabase) -> ModuleId { match self { MacroId::MacroRulesId(it) => it.lookup(db).container, MacroId::Macro2Id(it) => it.lookup(db).container, @@ -1028,7 +1018,7 @@ fn module(&self, db: &dyn db::DefDatabase) -> ModuleId { } impl HasModule for TypeOwnerId { - fn module(&self, db: &dyn db::DefDatabase) -> ModuleId { + fn module(&self, db: &dyn DefDatabase) -> ModuleId { match self { TypeOwnerId::FunctionId(it) => it.lookup(db).module(db), TypeOwnerId::StaticId(it) => it.lookup(db).module(db), @@ -1045,7 +1035,7 @@ fn module(&self, db: &dyn db::DefDatabase) -> ModuleId { } impl HasModule for DefWithBodyId { - fn module(&self, db: &dyn db::DefDatabase) -> ModuleId { + fn module(&self, db: &dyn DefDatabase) -> ModuleId { match self { DefWithBodyId::FunctionId(it) => it.lookup(db).module(db), DefWithBodyId::StaticId(it) => it.lookup(db).module(db), @@ -1057,7 +1047,7 @@ fn module(&self, db: &dyn db::DefDatabase) -> ModuleId { } impl HasModule for GenericDefId { - fn module(&self, db: &dyn db::DefDatabase) -> ModuleId { + fn module(&self, db: &dyn DefDatabase) -> ModuleId { match self { GenericDefId::FunctionId(it) => it.lookup(db).module(db), GenericDefId::AdtId(it) => it.module(db), @@ -1072,13 +1062,13 @@ fn module(&self, db: &dyn db::DefDatabase) -> ModuleId { } impl HasModule for TypeAliasId { - fn module(&self, db: &dyn db::DefDatabase) -> ModuleId { + fn module(&self, db: &dyn DefDatabase) -> ModuleId { self.lookup(db).module(db) } } impl HasModule for TraitId { - fn module(&self, db: &dyn db::DefDatabase) -> ModuleId { + fn module(&self, db: &dyn DefDatabase) -> ModuleId { self.lookup(db).container } } @@ -1087,7 +1077,7 @@ impl ModuleDefId { /// Returns the module containing `self` (or `self`, if `self` is itself a module). /// /// Returns `None` if `self` refers to a primitive type. - pub fn module(&self, db: &dyn db::DefDatabase) -> Option { + pub fn module(&self, db: &dyn DefDatabase) -> Option { Some(match self { ModuleDefId::ModuleId(id) => *id, ModuleDefId::FunctionId(id) => id.lookup(db).module(db), @@ -1105,7 +1095,7 @@ pub fn module(&self, db: &dyn db::DefDatabase) -> Option { } impl AttrDefId { - pub fn krate(&self, db: &dyn db::DefDatabase) -> CrateId { + pub fn krate(&self, db: &dyn DefDatabase) -> CrateId { match self { AttrDefId::ModuleId(it) => it.krate, AttrDefId::FieldId(it) => it.parent.module(db).krate, @@ -1171,7 +1161,7 @@ fn as_call_id_with_errors( return Ok(ExpandResult::only_err(ExpandError::other("malformed macro invocation"))); }; - let call_site = span_map.span_for_range(self.value.syntax().text_range()).ctx; + let call_site = span_map.span_for_range(self.value.syntax().text_range()); macro_call_as_call_id_with_eager( db, @@ -1201,7 +1191,7 @@ fn new(file_id: HirFileId, ast_id: FileAstId, path: path::ModPath) -> AstIdWi fn macro_call_as_call_id( db: &dyn ExpandDatabase, call: &AstIdWithPath, - call_site: SyntaxContextId, + call_site: Span, expand_to: ExpandTo, krate: CrateId, resolver: impl Fn(path::ModPath) -> Option + Copy, @@ -1213,7 +1203,7 @@ fn macro_call_as_call_id( fn macro_call_as_call_id_with_eager( db: &dyn ExpandDatabase, call: &AstIdWithPath, - call_site: SyntaxContextId, + call_site: Span, expand_to: ExpandTo, krate: CrateId, resolver: impl FnOnce(path::ModPath) -> Option, @@ -1243,83 +1233,12 @@ fn macro_call_as_call_id_with_eager( Ok(res) } -pub fn macro_id_to_def_id(db: &dyn db::DefDatabase, id: MacroId) -> MacroDefId { - match id { - MacroId::Macro2Id(it) => { - let loc = it.lookup(db); - - let item_tree = loc.id.item_tree(db); - let makro = &item_tree[loc.id.value]; - let in_file = |m: FileAstId| InFile::new(loc.id.file_id(), m.upcast()); - MacroDefId { - krate: loc.container.krate, - kind: match loc.expander { - MacroExpander::Declarative => MacroDefKind::Declarative(in_file(makro.ast_id)), - MacroExpander::BuiltIn(it) => MacroDefKind::BuiltIn(it, in_file(makro.ast_id)), - MacroExpander::BuiltInAttr(it) => { - MacroDefKind::BuiltInAttr(it, in_file(makro.ast_id)) - } - MacroExpander::BuiltInDerive(it) => { - MacroDefKind::BuiltInDerive(it, in_file(makro.ast_id)) - } - MacroExpander::BuiltInEager(it) => { - MacroDefKind::BuiltInEager(it, in_file(makro.ast_id)) - } - }, - local_inner: false, - allow_internal_unsafe: loc.allow_internal_unsafe, - } - } - MacroId::MacroRulesId(it) => { - let loc = it.lookup(db); - - let item_tree = loc.id.item_tree(db); - let makro = &item_tree[loc.id.value]; - let in_file = |m: FileAstId| InFile::new(loc.id.file_id(), m.upcast()); - MacroDefId { - krate: loc.container.krate, - kind: match loc.expander { - MacroExpander::Declarative => MacroDefKind::Declarative(in_file(makro.ast_id)), - MacroExpander::BuiltIn(it) => MacroDefKind::BuiltIn(it, in_file(makro.ast_id)), - MacroExpander::BuiltInAttr(it) => { - MacroDefKind::BuiltInAttr(it, in_file(makro.ast_id)) - } - MacroExpander::BuiltInDerive(it) => { - MacroDefKind::BuiltInDerive(it, in_file(makro.ast_id)) - } - MacroExpander::BuiltInEager(it) => { - MacroDefKind::BuiltInEager(it, in_file(makro.ast_id)) - } - }, - local_inner: loc.local_inner, - allow_internal_unsafe: loc.allow_internal_unsafe, - } - } - MacroId::ProcMacroId(it) => { - let loc = it.lookup(db); - - let item_tree = loc.id.item_tree(db); - let makro = &item_tree[loc.id.value]; - MacroDefId { - krate: loc.container.krate, - kind: MacroDefKind::ProcMacro( - loc.expander, - loc.kind, - InFile::new(loc.id.file_id(), makro.ast_id), - ), - local_inner: false, - allow_internal_unsafe: false, - } - } - } -} - fn derive_macro_as_call_id( - db: &dyn db::DefDatabase, + db: &dyn DefDatabase, item_attr: &AstIdWithPath, derive_attr_index: AttrId, derive_pos: u32, - call_site: SyntaxContextId, + call_site: Span, krate: CrateId, resolver: impl Fn(path::ModPath) -> Option<(MacroId, MacroDefId)>, ) -> Result<(MacroId, MacroDefId, MacroCallId), UnresolvedMacro> { @@ -1340,7 +1259,7 @@ fn derive_macro_as_call_id( } fn attr_macro_as_call_id( - db: &dyn db::DefDatabase, + db: &dyn DefDatabase, item_attr: &AstIdWithPath, macro_attr: &Attr, krate: CrateId, @@ -1349,7 +1268,7 @@ fn attr_macro_as_call_id( let arg = match macro_attr.input.as_deref() { Some(AttrInput::TokenTree(tt)) => { let mut tt = tt.as_ref().clone(); - tt.delimiter = tt::Delimiter::DUMMY_INVISIBLE; + tt.delimiter = tt::Delimiter::invisible_spanned(macro_attr.span); Some(tt) } @@ -1364,7 +1283,7 @@ fn attr_macro_as_call_id( attr_args: arg.map(Arc::new), invoc_attr_index: macro_attr.id, }, - macro_attr.ctxt, + macro_attr.span, ) } diff --git a/crates/hir-def/src/lower.rs b/crates/hir-def/src/lower.rs index a3505b65fe7..395b69d284f 100644 --- a/crates/hir-def/src/lower.rs +++ b/crates/hir-def/src/lower.rs @@ -3,7 +3,7 @@ use hir_expand::{ ast_id_map::{AstIdMap, AstIdNode}, - span::{SpanMap, SpanMapRef}, + span_map::{SpanMap, SpanMapRef}, AstId, HirFileId, InFile, }; use syntax::ast; diff --git a/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs b/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs index 514219ee715..d4798f4507d 100644 --- a/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs +++ b/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs @@ -163,31 +163,43 @@ macro_rules! file {() => {}} fn test_assert_expand() { check( r#" -#[rustc_builtin_macro] -macro_rules! assert { - ($cond:expr) => ({ /* compiler built-in */ }); - ($cond:expr, $($args:tt)*) => ({ /* compiler built-in */ }) -} - +//- minicore: assert fn main() { assert!(true, "{} {:?}", arg1(a, b, c), arg2); } "#, - expect![[r##" -#[rustc_builtin_macro] -macro_rules! assert { - ($cond:expr) => ({ /* compiler built-in */ }); - ($cond:expr, $($args:tt)*) => ({ /* compiler built-in */ }) -} - + expect![[r#" fn main() { { if !(true ) { - $crate::panic!("{} {:?}", arg1(a, b, c), arg2); + $crate::panic::panic_2021!("{} {:?}", arg1(a, b, c), arg2); } }; } -"##]], +"#]], + ); +} + +// FIXME: This is the wrong expansion, see FIXME on `builtin_fn_macro::use_panic_2021` +#[test] +fn test_assert_expand_2015() { + check( + r#" +//- minicore: assert +//- /main.rs edition:2015 +fn main() { + assert!(true, "{} {:?}", arg1(a, b, c), arg2); +} +"#, + expect![[r#" +fn main() { + { + if !(true ) { + $crate::panic::panic_2021!("{} {:?}", arg1(a, b, c), arg2); + } + }; +} +"#]], ); } diff --git a/crates/hir-def/src/macro_expansion_tests/mbe.rs b/crates/hir-def/src/macro_expansion_tests/mbe.rs index 9bf2a50d57c..f2046bfbce4 100644 --- a/crates/hir-def/src/macro_expansion_tests/mbe.rs +++ b/crates/hir-def/src/macro_expansion_tests/mbe.rs @@ -1218,8 +1218,10 @@ macro_rules! m { macro_rules! m { ($(#[$m:meta])+) => ( $(#[$m])+ fn bar() {} ) } -#[doc = " Single Line Doc 1"] -#[doc = "\n MultiLines Doc\n "] fn bar() {} +#[doc = r" Single Line Doc 1"] +#[doc = r" + MultiLines Doc + "] fn bar() {} "##]], ); } @@ -1260,8 +1262,10 @@ macro_rules! m { macro_rules! m { ($(#[$ m:meta])+) => ( $(#[$m])+ fn bar() {} ) } -#[doc = " 錦瑟無端五十弦,一弦一柱思華年。"] -#[doc = "\n 莊生曉夢迷蝴蝶,望帝春心託杜鵑。\n "] fn bar() {} +#[doc = r" 錦瑟無端五十弦,一弦一柱思華年。"] +#[doc = r" + 莊生曉夢迷蝴蝶,望帝春心託杜鵑。 + "] fn bar() {} "##]], ); } @@ -1281,7 +1285,7 @@ macro_rules! m { macro_rules! m { ($(#[$m:meta])+) => ( $(#[$m])+ fn bar() {} ) } -#[doc = " \\ \" \'"] fn bar() {} +#[doc = r#" \ " '"#] fn bar() {} "##]], ); } diff --git a/crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs b/crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs index 7e7b4004421..e875950e4e5 100644 --- a/crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs +++ b/crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs @@ -18,7 +18,7 @@ macro_rules! m { ($($false:ident)*) => ($false); (double_dollar) => ($$); ($) => (m!($);); - ($($t:tt)*) => ($( ${ignore(t)} ${index()} )-*); + ($($t:tt)*) => ($( ${ignore($t)} ${index()} )-*); } m!($); "#, @@ -33,7 +33,7 @@ macro_rules! m { ($($false:ident)*) => ($false); (double_dollar) => ($$); ($) => (m!($);); - ($($t:tt)*) => ($( ${ignore(t)} ${index()} )-*); + ($($t:tt)*) => ($( ${ignore($t)} ${index()} )-*); } m!($); "#]], diff --git a/crates/hir-def/src/macro_expansion_tests/mbe/metavar_expr.rs b/crates/hir-def/src/macro_expansion_tests/mbe/metavar_expr.rs index 967b5ad36ba..6560d0ec466 100644 --- a/crates/hir-def/src/macro_expansion_tests/mbe/metavar_expr.rs +++ b/crates/hir-def/src/macro_expansion_tests/mbe/metavar_expr.rs @@ -77,13 +77,13 @@ fn test_metavar_exprs() { check( r#" macro_rules! m { - ( $( $t:tt )* ) => ( $( ${ignore(t)} -${index()} )-* ); + ( $( $t:tt )* ) => ( $( ${ignore($t)} -${index()} )-* ); } const _: i32 = m!(a b c); "#, expect![[r#" macro_rules! m { - ( $( $t:tt )* ) => ( $( ${ignore(t)} -${index()} )-* ); + ( $( $t:tt )* ) => ( $( ${ignore($t)} -${index()} )-* ); } const _: i32 = -0--1--2; "#]], @@ -96,7 +96,7 @@ fn count_basic() { r#" macro_rules! m { ($($t:ident),*) => { - ${count(t)} + ${count($t)} } } @@ -109,7 +109,7 @@ fn test() { expect![[r#" macro_rules! m { ($($t:ident),*) => { - ${count(t)} + ${count($t)} } } @@ -130,9 +130,9 @@ macro_rules! foo { ($( $( $($t:ident)* ),* );*) => { $( { - let depth_none = ${count(t)}; - let depth_zero = ${count(t, 0)}; - let depth_one = ${count(t, 1)}; + let depth_none = ${count($t)}; + let depth_zero = ${count($t, 0)}; + let depth_one = ${count($t, 1)}; } )* } @@ -150,9 +150,9 @@ macro_rules! foo { ($( $( $($t:ident)* ),* );*) => { $( { - let depth_none = ${count(t)}; - let depth_zero = ${count(t, 0)}; - let depth_one = ${count(t, 1)}; + let depth_none = ${count($t)}; + let depth_zero = ${count($t, 0)}; + let depth_one = ${count($t, 1)}; } )* } @@ -160,11 +160,11 @@ macro_rules! foo { fn bar() { { - let depth_none = 6; + let depth_none = 3; let depth_zero = 3; let depth_one = 6; } { - let depth_none = 3; + let depth_none = 1; let depth_zero = 1; let depth_one = 3; } @@ -178,12 +178,12 @@ fn count_depth_out_of_bounds() { check( r#" macro_rules! foo { - ($($t:ident)*) => { ${count(t, 1)} }; - ($( $( $l:literal )* );*) => { $(${count(l, 1)};)* } + ($($t:ident)*) => { ${count($t, 1)} }; + ($( $( $l:literal )* );*) => { $(${count($l, 1)};)* } } macro_rules! bar { - ($($t:ident)*) => { ${count(t, 1024)} }; - ($( $( $l:literal )* );*) => { $(${count(l, 8192)};)* } + ($($t:ident)*) => { ${count($t, 1024)} }; + ($( $( $l:literal )* );*) => { $(${count($l, 8192)};)* } } fn test() { @@ -195,19 +195,21 @@ fn test() { "#, expect![[r#" macro_rules! foo { - ($($t:ident)*) => { ${count(t, 1)} }; - ($( $( $l:literal )* );*) => { $(${count(l, 1)};)* } + ($($t:ident)*) => { ${count($t, 1)} }; + ($( $( $l:literal )* );*) => { $(${count($l, 1)};)* } } macro_rules! bar { - ($($t:ident)*) => { ${count(t, 1024)} }; - ($( $( $l:literal )* );*) => { $(${count(l, 8192)};)* } + ($($t:ident)*) => { ${count($t, 1024)} }; + ($( $( $l:literal )* );*) => { $(${count($l, 8192)};)* } } fn test() { - /* error: ${count} out of bounds */; - /* error: ${count} out of bounds */; - /* error: ${count} out of bounds */; - /* error: ${count} out of bounds */; + 2; + 2; + 1;; + 2; + 2; + 1;; } "#]], ); @@ -218,8 +220,8 @@ fn misplaced_count() { check( r#" macro_rules! foo { - ($($t:ident)*) => { $(${count(t)})* }; - ($l:literal) => { ${count(l)} } + ($($t:ident)*) => { $(${count($t)})* }; + ($l:literal) => { ${count($l)} } } fn test() { @@ -229,13 +231,13 @@ fn test() { "#, expect![[r#" macro_rules! foo { - ($($t:ident)*) => { $(${count(t)})* }; - ($l:literal) => { ${count(l)} } + ($($t:ident)*) => { $(${count($t)})* }; + ($l:literal) => { ${count($l)} } } fn test() { - /* error: ${count} misplaced */; - /* error: ${count} misplaced */; + 1 1 1; + 1; } "#]], ); @@ -246,13 +248,13 @@ fn malformed_count() { check( r#" macro_rules! too_many_args { - ($($t:ident)*) => { ${count(t, 1, leftover)} } + ($($t:ident)*) => { ${count($t, 1, leftover)} } } macro_rules! depth_suffixed { - ($($t:ident)*) => { ${count(t, 0usize)} } + ($($t:ident)*) => { ${count($t, 0usize)} } } macro_rules! depth_too_large { - ($($t:ident)*) => { ${count(t, 18446744073709551616)} } + ($($t:ident)*) => { ${count($t, 18446744073709551616)} } } fn test() { @@ -263,13 +265,13 @@ fn test() { "#, expect![[r#" macro_rules! too_many_args { - ($($t:ident)*) => { ${count(t, 1, leftover)} } + ($($t:ident)*) => { ${count($t, 1, leftover)} } } macro_rules! depth_suffixed { - ($($t:ident)*) => { ${count(t, 0usize)} } + ($($t:ident)*) => { ${count($t, 0usize)} } } macro_rules! depth_too_large { - ($($t:ident)*) => { ${count(t, 18446744073709551616)} } + ($($t:ident)*) => { ${count($t, 18446744073709551616)} } } fn test() { @@ -288,7 +290,7 @@ fn count_interaction_with_empty_binding() { r#" macro_rules! m { ($($t:ident),*) => { - ${count(t, 100)} + ${count($t, 100)} } } @@ -299,7 +301,7 @@ fn test() { expect![[r#" macro_rules! m { ($($t:ident),*) => { - ${count(t, 100)} + ${count($t, 100)} } } diff --git a/crates/hir-def/src/macro_expansion_tests/mod.rs b/crates/hir-def/src/macro_expansion_tests/mod.rs index be2a503d82b..ee806361237 100644 --- a/crates/hir-def/src/macro_expansion_tests/mod.rs +++ b/crates/hir-def/src/macro_expansion_tests/mod.rs @@ -16,9 +16,15 @@ use std::{iter, ops::Range, sync}; -use base_db::{fixture::WithFixture, span::SpanData, ProcMacro, SourceDatabase}; +use base_db::SourceDatabase; use expect_test::Expect; -use hir_expand::{db::ExpandDatabase, span::SpanMapRef, InFile, MacroFileId, MacroFileIdExt}; +use hir_expand::{ + db::ExpandDatabase, + proc_macro::{ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacroKind}, + span_map::SpanMapRef, + InFile, MacroFileId, MacroFileIdExt, +}; +use span::Span; use stdx::format_to; use syntax::{ ast::{self, edit::IndentLevel}, @@ -26,10 +32,10 @@ SyntaxKind::{COMMENT, EOF, IDENT, LIFETIME_IDENT}, SyntaxNode, T, }; +use test_fixture::WithFixture; use crate::{ db::DefDatabase, - macro_id_to_def_id, nameres::{DefMap, MacroSubNs, ModuleSource}, resolver::HasResolver, src::HasSource, @@ -50,7 +56,7 @@ pub fn identity_when_valid(_attr: TokenStream, item: TokenStream) -> TokenStream .into(), ProcMacro { name: "identity_when_valid".into(), - kind: base_db::ProcMacroKind::Attr, + kind: ProcMacroKind::Attr, expander: sync::Arc::new(IdentityWhenValidProcMacroExpander), }, )]; @@ -90,7 +96,7 @@ pub fn identity_when_valid(_attr: TokenStream, item: TokenStream) -> TokenStream .as_call_id_with_errors(&db, krate, |path| { resolver .resolve_path_as_macro(&db, &path, Some(MacroSubNs::Bang)) - .map(|(it, _)| macro_id_to_def_id(&db, it)) + .map(|(it, _)| db.macro_def(it)) }) .unwrap(); let macro_call_id = res.value.unwrap(); @@ -307,16 +313,16 @@ fn pretty_print_macro_expansion( // compile errors. #[derive(Debug)] struct IdentityWhenValidProcMacroExpander; -impl base_db::ProcMacroExpander for IdentityWhenValidProcMacroExpander { +impl ProcMacroExpander for IdentityWhenValidProcMacroExpander { fn expand( &self, subtree: &Subtree, _: Option<&Subtree>, _: &base_db::Env, - _: SpanData, - _: SpanData, - _: SpanData, - ) -> Result { + _: Span, + _: Span, + _: Span, + ) -> Result { let (parse, _) = ::mbe::token_tree_to_syntax_node(subtree, ::mbe::TopEntryPoint::MacroItems); if parse.errors().is_empty() { diff --git a/crates/hir-def/src/nameres.rs b/crates/hir-def/src/nameres.rs index 9a9fa0e02b0..52a981fd19e 100644 --- a/crates/hir-def/src/nameres.rs +++ b/crates/hir-def/src/nameres.rs @@ -59,8 +59,11 @@ use std::{cmp::Ord, ops::Deref}; -use base_db::{CrateId, Edition, FileId, ProcMacroKind}; -use hir_expand::{ast_id_map::FileAstId, name::Name, HirFileId, InFile, MacroCallId, MacroDefId}; +use base_db::{CrateId, Edition, FileId}; +use hir_expand::{ + ast_id_map::FileAstId, name::Name, proc_macro::ProcMacroKind, HirFileId, InFile, MacroCallId, + MacroDefId, +}; use itertools::Itertools; use la_arena::Arena; use profile::Count; @@ -97,7 +100,7 @@ pub struct DefMap { /// contains this block. block: Option, /// The modules and their data declared in this crate. - modules: Arena, + pub modules: Arena, krate: CrateId, /// The prelude module for this crate. This either comes from an import /// marked with the `prelude_import` attribute, or (in the normal case) from @@ -623,8 +626,9 @@ pub fn diagnostics(&self) -> &[DefDiagnostic] { self.diagnostics.as_slice() } - pub fn recursion_limit(&self) -> Option { - self.data.recursion_limit + pub fn recursion_limit(&self) -> u32 { + // 128 is the default in rustc + self.data.recursion_limit.unwrap_or(128) } } diff --git a/crates/hir-def/src/nameres/attr_resolution.rs b/crates/hir-def/src/nameres/attr_resolution.rs index a7abf445918..6288b8366bf 100644 --- a/crates/hir-def/src/nameres/attr_resolution.rs +++ b/crates/hir-def/src/nameres/attr_resolution.rs @@ -8,7 +8,6 @@ attr_macro_as_call_id, db::DefDatabase, item_scope::BuiltinShadowMode, - macro_id_to_def_id, nameres::path_resolution::ResolveMode, path::{ModPath, PathKind}, AstIdWithPath, LocalModuleId, UnresolvedMacro, @@ -63,7 +62,7 @@ pub(crate) fn resolve_attr_macro( &ast_id, attr, self.krate, - macro_id_to_def_id(db, def), + db.macro_def(def), ))) } diff --git a/crates/hir-def/src/nameres/collector.rs b/crates/hir-def/src/nameres/collector.rs index b3a10a3869a..3763bfcbcfa 100644 --- a/crates/hir-def/src/nameres/collector.rs +++ b/crates/hir-def/src/nameres/collector.rs @@ -5,7 +5,7 @@ use std::{cmp::Ordering, iter, mem}; -use base_db::{span::SyntaxContextId, CrateId, Dependency, Edition, FileId}; +use base_db::{CrateId, Dependency, Edition, FileId}; use cfg::{CfgExpr, CfgOptions}; use either::Either; use hir_expand::{ @@ -15,7 +15,7 @@ builtin_derive_macro::find_builtin_derive, builtin_fn_macro::find_builtin_macro, name::{name, AsName, Name}, - proc_macro::ProcMacroExpander, + proc_macro::CustomProcMacroExpander, ExpandResult, ExpandTo, HirFileId, InFile, MacroCallId, MacroCallKind, MacroCallLoc, MacroDefId, MacroDefKind, }; @@ -23,6 +23,7 @@ use la_arena::Idx; use limit::Limit; use rustc_hash::{FxHashMap, FxHashSet}; +use span::{Span, SyntaxContextId}; use stdx::always; use syntax::{ast, SmolStr}; use triomphe::Arc; @@ -35,9 +36,9 @@ item_scope::{ImportId, ImportOrExternCrate, ImportType, PerNsGlobImports}, item_tree::{ self, ExternCrate, Fields, FileItemTreeId, ImportKind, ItemTree, ItemTreeId, ItemTreeNode, - MacroCall, MacroDef, MacroRules, Mod, ModItem, ModKind, TreeId, + Macro2, MacroCall, MacroRules, Mod, ModItem, ModKind, TreeId, }, - macro_call_as_call_id, macro_call_as_call_id_with_eager, macro_id_to_def_id, + macro_call_as_call_id, macro_call_as_call_id_with_eager, nameres::{ diagnostics::DefDiagnostic, mod_resolution::ModDir, @@ -53,8 +54,9 @@ AdtId, AstId, AstIdWithPath, ConstLoc, CrateRootModuleId, EnumLoc, EnumVariantId, ExternBlockLoc, ExternCrateId, ExternCrateLoc, FunctionId, FunctionLoc, ImplLoc, Intern, ItemContainerId, LocalModuleId, Lookup, Macro2Id, Macro2Loc, MacroExpander, MacroId, - MacroRulesId, MacroRulesLoc, ModuleDefId, ModuleId, ProcMacroId, ProcMacroLoc, StaticLoc, - StructLoc, TraitAliasLoc, TraitLoc, TypeAliasLoc, UnionLoc, UnresolvedMacro, UseId, UseLoc, + MacroRulesId, MacroRulesLoc, MacroRulesLocFlags, ModuleDefId, ModuleId, ProcMacroId, + ProcMacroLoc, StaticLoc, StructLoc, TraitAliasLoc, TraitLoc, TypeAliasLoc, UnionLoc, + UnresolvedMacro, UseId, UseLoc, }; static GLOB_RECURSION_LIMIT: Limit = Limit::new(100); @@ -86,16 +88,21 @@ pub(super) fn collect_defs(db: &dyn DefDatabase, def_map: DefMap, tree_id: TreeI // FIXME: a hacky way to create a Name from string. let name = tt::Ident { text: it.name.clone(), - span: tt::SpanData { + span: Span { range: syntax::TextRange::empty(syntax::TextSize::new(0)), - anchor: base_db::span::SpanAnchor { + anchor: span::SpanAnchor { file_id: FileId::BOGUS, - ast_id: base_db::span::ROOT_ERASED_FILE_AST_ID, + ast_id: span::ROOT_ERASED_FILE_AST_ID, }, ctx: SyntaxContextId::ROOT, }, }; - (name.as_name(), ProcMacroExpander::new(base_db::ProcMacroId(idx as u32))) + ( + name.as_name(), + CustomProcMacroExpander::new(hir_expand::proc_macro::ProcMacroId( + idx as u32, + )), + ) }) .collect()) } @@ -222,13 +229,13 @@ enum MacroDirectiveKind { FnLike { ast_id: AstIdWithPath, expand_to: ExpandTo, - call_site: SyntaxContextId, + call_site: Span, }, Derive { ast_id: AstIdWithPath, derive_attr: AttrId, derive_pos: usize, - call_site: SyntaxContextId, + call_site: Span, }, Attr { ast_id: AstIdWithPath, @@ -253,7 +260,7 @@ struct DefCollector<'a> { /// built by the build system, and is the list of proc. macros we can actually expand. It is /// empty when proc. macro support is disabled (in which case we still do name resolution for /// them). - proc_macros: Result, Box>, + proc_macros: Result, Box>, is_proc_macro: bool, from_glob_import: PerNsGlobImports, /// If we fail to resolve an attribute on a `ModItem`, we fall back to ignoring the attribute. @@ -545,6 +552,8 @@ fn inject_prelude(&mut self) { Edition::Edition2015 => name![rust_2015], Edition::Edition2018 => name![rust_2018], Edition::Edition2021 => name![rust_2021], + // FIXME: update this when rust_2024 exists + Edition::Edition2024 => name![rust_2021], }; let path_kind = match self.def_map.data.edition { @@ -603,18 +612,21 @@ fn export_proc_macro( let (expander, kind) = match self.proc_macros.as_ref().map(|it| it.iter().find(|(n, _)| n == &def.name)) { Ok(Some(&(_, expander))) => (expander, kind), - _ => (ProcMacroExpander::dummy(), kind), + _ => (CustomProcMacroExpander::dummy(), kind), }; - let proc_macro_id = - ProcMacroLoc { container: self.def_map.crate_root(), id, expander, kind } - .intern(self.db); + let proc_macro_id = ProcMacroLoc { + container: self.def_map.crate_root(), + id, + expander, + kind, + edition: self.def_map.data.edition, + } + .intern(self.db); self.define_proc_macro(def.name.clone(), proc_macro_id); let crate_data = Arc::get_mut(&mut self.def_map.data).unwrap(); if let ProcMacroKind::CustomDerive { helpers } = def.kind { - crate_data - .exported_derives - .insert(macro_id_to_def_id(self.db, proc_macro_id.into()), helpers); + crate_data.exported_derives.insert(self.db.macro_def(proc_macro_id.into()), helpers); } crate_data.fn_proc_macro_mapping.insert(fn_id, proc_macro_id); } @@ -1125,10 +1137,7 @@ fn resolve_macros(&mut self) -> ReachedFixedPoint { BuiltinShadowMode::Module, Some(subns), ); - resolved_res - .resolved_def - .take_macros() - .map(|it| (it, macro_id_to_def_id(self.db, it))) + resolved_res.resolved_def.take_macros().map(|it| (it, self.db.macro_def(it))) }; let resolver_def_id = |path| resolver(path).map(|(_, it)| it); @@ -1143,6 +1152,9 @@ fn resolve_macros(&mut self) -> ReachedFixedPoint { resolver_def_id, ); if let Ok(Some(call_id)) = call_id { + self.def_map.modules[directive.module_id] + .scope + .add_macro_invoc(ast_id.ast_id, call_id); push_resolved(directive, call_id); res = ReachedFixedPoint::No; @@ -1299,14 +1311,13 @@ fn resolve_macros(&mut self) -> ReachedFixedPoint { // Not resolved to a derive helper or the derive attribute, so try to treat as a normal attribute. let call_id = attr_macro_as_call_id(self.db, file_ast_id, attr, self.def_map.krate, def); - let loc: MacroCallLoc = self.db.lookup_intern_macro_call(call_id); // If proc attribute macro expansion is disabled, skip expanding it here if !self.db.expand_proc_attr_macros() { self.def_map.diagnostics.push(DefDiagnostic::unresolved_proc_macro( directive.module_id, - loc.kind, - loc.def.krate, + self.db.lookup_intern_macro_call(call_id).kind, + def.krate, )); return recollect_without(self); } @@ -1314,14 +1325,14 @@ fn resolve_macros(&mut self) -> ReachedFixedPoint { // Skip #[test]/#[bench] expansion, which would merely result in more memory usage // due to duplicating functions into macro expansions if matches!( - loc.def.kind, + def.kind, MacroDefKind::BuiltInAttr(expander, _) if expander.is_test() || expander.is_bench() ) { return recollect_without(self); } - if let MacroDefKind::ProcMacro(exp, ..) = loc.def.kind { + if let MacroDefKind::ProcMacro(exp, ..) = def.kind { if exp.is_dummy() { // If there's no expander for the proc macro (e.g. // because proc macros are disabled, or building the @@ -1329,8 +1340,8 @@ fn resolve_macros(&mut self) -> ReachedFixedPoint { // expansion like we would if it was disabled self.def_map.diagnostics.push(DefDiagnostic::unresolved_proc_macro( directive.module_id, - loc.kind, - loc.def.krate, + self.db.lookup_intern_macro_call(call_id).kind, + def.krate, )); return recollect_without(self); @@ -1436,10 +1447,7 @@ fn finish(mut self) -> DefMap { BuiltinShadowMode::Module, Some(MacroSubNs::Bang), ); - resolved_res - .resolved_def - .take_macros() - .map(|it| macro_id_to_def_id(self.db, it)) + resolved_res.resolved_def.take_macros().map(|it| self.db.macro_def(it)) }, ); if let Err(UnresolvedMacro { path }) = macro_call_as_call_id { @@ -1645,7 +1653,7 @@ fn collect(&mut self, items: &[ModItem], container: ItemContainerId) { ), ModItem::MacroCall(mac) => self.collect_macro_call(&self.item_tree[mac], container), ModItem::MacroRules(id) => self.collect_macro_rules(id, module), - ModItem::MacroDef(id) => self.collect_macro_def(id, module), + ModItem::Macro2(id) => self.collect_macro_def(id, module), ModItem::Impl(imp) => { let impl_id = ImplLoc { container: module, id: ItemTreeId::new(self.tree_id, imp) } @@ -2090,11 +2098,11 @@ fn collect_macro_rules(&mut self, id: FileItemTreeId, module: Module // FIXME: a hacky way to create a Name from string. name = tt::Ident { text: it.clone(), - span: tt::SpanData { + span: Span { range: syntax::TextRange::empty(syntax::TextSize::new(0)), - anchor: base_db::span::SpanAnchor { + anchor: span::SpanAnchor { file_id: FileId::BOGUS, - ast_id: base_db::span::ROOT_ERASED_FILE_AST_ID, + ast_id: span::ROOT_ERASED_FILE_AST_ID, }, ctx: SyntaxContextId::ROOT, }, @@ -2136,12 +2144,16 @@ fn collect_macro_rules(&mut self, id: FileItemTreeId, module: Module }; let allow_internal_unsafe = attrs.by_key("allow_internal_unsafe").exists(); + let mut flags = MacroRulesLocFlags::empty(); + flags.set(MacroRulesLocFlags::LOCAL_INNER, local_inner); + flags.set(MacroRulesLocFlags::ALLOW_INTERNAL_UNSAFE, allow_internal_unsafe); + let macro_id = MacroRulesLoc { container: module, id: ItemTreeId::new(self.tree_id, id), - local_inner, - allow_internal_unsafe, + flags, expander, + edition: self.def_collector.def_map.data.edition, } .intern(self.def_collector.db); self.def_collector.define_macro_rules( @@ -2152,7 +2164,7 @@ fn collect_macro_rules(&mut self, id: FileItemTreeId, module: Module ); } - fn collect_macro_def(&mut self, id: FileItemTreeId, module: ModuleId) { + fn collect_macro_def(&mut self, id: FileItemTreeId, module: ModuleId) { let krate = self.def_collector.def_map.krate; let mac = &self.item_tree[id]; let ast_id = InFile::new(self.file_id(), mac.ast_id.upcast()); @@ -2207,6 +2219,7 @@ fn collect_macro_def(&mut self, id: FileItemTreeId, module: ModuleId) id: ItemTreeId::new(self.tree_id, id), expander, allow_internal_unsafe, + edition: self.def_collector.def_map.data.edition, } .intern(self.def_collector.db); self.def_collector.define_macro_def( @@ -2220,7 +2233,7 @@ fn collect_macro_def(&mut self, id: FileItemTreeId, module: ModuleId) Arc::get_mut(&mut self.def_collector.def_map.data) .unwrap() .exported_derives - .insert(macro_id_to_def_id(self.def_collector.db, macro_id.into()), helpers); + .insert(self.def_collector.db.macro_def(macro_id.into()), helpers); } } } @@ -2259,7 +2272,7 @@ fn collect_macro_call( Some(MacroSubNs::Bang), ) }) - .map(|it| macro_id_to_def_id(self.def_collector.db, it)) + .map(|it| self.def_collector.db.macro_def(it)) }) }, |path| { @@ -2271,7 +2284,7 @@ fn collect_macro_call( BuiltinShadowMode::Module, Some(MacroSubNs::Bang), ); - resolved_res.resolved_def.take_macros().map(|it| macro_id_to_def_id(db, it)) + resolved_res.resolved_def.take_macros().map(|it| db.macro_def(it)) }, ) { // FIXME: if there were errors, this mightve been in the eager expansion from an @@ -2279,10 +2292,13 @@ fn collect_macro_call( if res.err.is_none() { // Legacy macros need to be expanded immediately, so that any macros they produce // are in scope. - if let Some(val) = res.value { + if let Some(call_id) = res.value { + self.def_collector.def_map.modules[self.module_id] + .scope + .add_macro_invoc(ast_id.ast_id, call_id); self.def_collector.collect_macro_expansion( self.module_id, - val, + call_id, self.macro_depth + 1, container, ); @@ -2296,7 +2312,7 @@ fn collect_macro_call( self.def_collector.unresolved_macros.push(MacroDirective { module_id: self.module_id, depth: self.macro_depth + 1, - kind: MacroDirectiveKind::FnLike { ast_id, expand_to: expand_to, call_site }, + kind: MacroDirectiveKind::FnLike { ast_id, expand_to, call_site }, container, }); } @@ -2363,8 +2379,10 @@ fn file_id(&self) -> HirFileId { #[cfg(test)] mod tests { + use base_db::SourceDatabase; + use test_fixture::WithFixture; + use crate::{db::DefDatabase, test_db::TestDB}; - use base_db::{fixture::WithFixture, SourceDatabase}; use super::*; diff --git a/crates/hir-def/src/nameres/proc_macro.rs b/crates/hir-def/src/nameres/proc_macro.rs index 751b7beaac1..c126fdac1c6 100644 --- a/crates/hir-def/src/nameres/proc_macro.rs +++ b/crates/hir-def/src/nameres/proc_macro.rs @@ -19,11 +19,13 @@ pub enum ProcMacroKind { } impl ProcMacroKind { - pub(super) fn to_basedb_kind(&self) -> base_db::ProcMacroKind { + pub(super) fn to_basedb_kind(&self) -> hir_expand::proc_macro::ProcMacroKind { match self { - ProcMacroKind::CustomDerive { .. } => base_db::ProcMacroKind::CustomDerive, - ProcMacroKind::FnLike => base_db::ProcMacroKind::FuncLike, - ProcMacroKind::Attr => base_db::ProcMacroKind::Attr, + ProcMacroKind::CustomDerive { .. } => { + hir_expand::proc_macro::ProcMacroKind::CustomDerive + } + ProcMacroKind::FnLike => hir_expand::proc_macro::ProcMacroKind::FuncLike, + ProcMacroKind::Attr => hir_expand::proc_macro::ProcMacroKind::Attr, } } } diff --git a/crates/hir-def/src/nameres/tests.rs b/crates/hir-def/src/nameres/tests.rs index b2ffbbe4c5d..17e82dc16c4 100644 --- a/crates/hir-def/src/nameres/tests.rs +++ b/crates/hir-def/src/nameres/tests.rs @@ -4,8 +4,9 @@ mod mod_resolution; mod primitives; -use base_db::{fixture::WithFixture, SourceDatabase}; +use base_db::SourceDatabase; use expect_test::{expect, Expect}; +use test_fixture::WithFixture; use triomphe::Arc; use crate::{db::DefDatabase, nameres::DefMap, test_db::TestDB}; diff --git a/crates/hir-def/src/nameres/tests/incremental.rs b/crates/hir-def/src/nameres/tests/incremental.rs index 78cb78e833e..6efced02718 100644 --- a/crates/hir-def/src/nameres/tests/incremental.rs +++ b/crates/hir-def/src/nameres/tests/incremental.rs @@ -1,11 +1,8 @@ use base_db::{SourceDatabase, SourceDatabaseExt}; +use test_fixture::WithFixture; use triomphe::Arc; -use crate::{ - db::DefDatabase, - nameres::tests::{TestDB, WithFixture}, - AdtId, ModuleDefId, -}; +use crate::{db::DefDatabase, nameres::tests::TestDB, AdtId, ModuleDefId}; fn check_def_map_is_not_recomputed(ra_fixture_initial: &str, ra_fixture_change: &str) { let (mut db, pos) = TestDB::with_position(ra_fixture_initial); diff --git a/crates/hir-def/src/nameres/tests/macros.rs b/crates/hir-def/src/nameres/tests/macros.rs index e64fa0b46f1..48fe43450a7 100644 --- a/crates/hir-def/src/nameres/tests/macros.rs +++ b/crates/hir-def/src/nameres/tests/macros.rs @@ -1,6 +1,12 @@ -use super::*; +use expect_test::expect; +use test_fixture::WithFixture; + use itertools::Itertools; +use crate::nameres::tests::check; + +use super::*; + #[test] fn macro_rules_are_globally_visible() { check( diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs index 2ac1516ec07..301391516d6 100644 --- a/crates/hir-def/src/resolver.rs +++ b/crates/hir-def/src/resolver.rs @@ -2,7 +2,10 @@ use std::{fmt, hash::BuildHasherDefault}; use base_db::CrateId; -use hir_expand::name::{name, Name}; +use hir_expand::{ + name::{name, Name}, + MacroDefId, +}; use indexmap::IndexMap; use intern::Interned; use rustc_hash::FxHashSet; @@ -406,6 +409,15 @@ pub fn resolve_path_as_macro( .take_macros_import() } + pub fn resolve_path_as_macro_def( + &self, + db: &dyn DefDatabase, + path: &ModPath, + expected_macro_kind: Option, + ) -> Option { + self.resolve_path_as_macro(db, path, expected_macro_kind).map(|(it, _)| db.macro_def(it)) + } + /// Returns a set of names available in the current scope. /// /// Note that this is a somewhat fuzzy concept -- internally, the compiler diff --git a/crates/hir-def/src/visibility.rs b/crates/hir-def/src/visibility.rs index f5803653c73..49688c5ee9c 100644 --- a/crates/hir-def/src/visibility.rs +++ b/crates/hir-def/src/visibility.rs @@ -2,7 +2,7 @@ use std::iter; -use hir_expand::{span::SpanMapRef, InFile}; +use hir_expand::{span_map::SpanMapRef, InFile}; use la_arena::ArenaMap; use syntax::ast; use triomphe::Arc; diff --git a/crates/hir-expand/Cargo.toml b/crates/hir-expand/Cargo.toml index 361bbec4318..506a188a211 100644 --- a/crates/hir-expand/Cargo.toml +++ b/crates/hir-expand/Cargo.toml @@ -15,7 +15,7 @@ doctest = false cov-mark = "2.0.0-pre.1" tracing.workspace = true either.workspace = true -rustc-hash = "1.1.0" +rustc-hash.workspace = true la-arena.workspace = true itertools.workspace = true hashbrown.workspace = true @@ -32,6 +32,10 @@ profile.workspace = true tt.workspace = true mbe.workspace = true limit.workspace = true +span.workspace = true [dev-dependencies] expect-test = "1.4.0" + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/hir-expand/src/ast_id_map.rs b/crates/hir-expand/src/ast_id_map.rs index be0b72f9dfa..d0d229e1319 100644 --- a/crates/hir-expand/src/ast_id_map.rs +++ b/crates/hir-expand/src/ast_id_map.rs @@ -5,6 +5,8 @@ //! item as an ID. That way, id's don't change unless the set of items itself //! changes. +// FIXME: Consider moving this into the span crate + use std::{ any::type_name, fmt, @@ -17,9 +19,9 @@ use rustc_hash::FxHasher; use syntax::{ast, AstNode, AstPtr, SyntaxNode, SyntaxNodePtr}; -use crate::db; +use crate::db::ExpandDatabase; -pub use base_db::span::ErasedFileAstId; +pub use span::ErasedFileAstId; /// `AstId` points to an AST node in any file. /// @@ -27,13 +29,13 @@ pub type AstId = crate::InFile>; impl AstId { - pub fn to_node(&self, db: &dyn db::ExpandDatabase) -> N { + pub fn to_node(&self, db: &dyn ExpandDatabase) -> N { self.to_ptr(db).to_node(&db.parse_or_expand(self.file_id)) } - pub fn to_in_file_node(&self, db: &dyn db::ExpandDatabase) -> crate::InFile { + pub fn to_in_file_node(&self, db: &dyn ExpandDatabase) -> crate::InFile { crate::InFile::new(self.file_id, self.to_ptr(db).to_node(&db.parse_or_expand(self.file_id))) } - pub fn to_ptr(&self, db: &dyn db::ExpandDatabase) -> AstPtr { + pub fn to_ptr(&self, db: &dyn ExpandDatabase) -> AstPtr { db.ast_id_map(self.file_id).get(self.value) } } @@ -41,7 +43,7 @@ pub fn to_ptr(&self, db: &dyn db::ExpandDatabase) -> AstPtr { pub type ErasedAstId = crate::InFile; impl ErasedAstId { - pub fn to_ptr(&self, db: &dyn db::ExpandDatabase) -> SyntaxNodePtr { + pub fn to_ptr(&self, db: &dyn ExpandDatabase) -> SyntaxNodePtr { db.ast_id_map(self.file_id).get_erased(self.value) } } @@ -197,6 +199,19 @@ pub fn ast_id(&self, item: &N) -> FileAstId { FileAstId { raw, covariant: PhantomData } } + pub fn ast_id_for_ptr(&self, ptr: AstPtr) -> FileAstId { + let ptr = ptr.syntax_node_ptr(); + let hash = hash_ptr(&ptr); + match self.map.raw_entry().from_hash(hash, |&idx| self.arena[idx] == ptr) { + Some((&raw, &())) => FileAstId { raw, covariant: PhantomData }, + None => panic!( + "Can't find {:?} in AstIdMap:\n{:?}", + ptr, + self.arena.iter().map(|(_id, i)| i).collect::>(), + ), + } + } + pub fn get(&self, id: FileAstId) -> AstPtr { AstPtr::try_from_raw(self.arena[id.raw].clone()).unwrap() } diff --git a/crates/hir-expand/src/attrs.rs b/crates/hir-expand/src/attrs.rs index b8fc30c9118..bd0f81881ee 100644 --- a/crates/hir-expand/src/attrs.rs +++ b/crates/hir-expand/src/attrs.rs @@ -1,19 +1,20 @@ //! A higher level attributes based on TokenTree, with also some shortcuts. use std::{fmt, ops}; -use base_db::{span::SyntaxContextId, CrateId}; +use base_db::CrateId; use cfg::CfgExpr; use either::Either; use intern::Interned; use mbe::{syntax_node_to_token_tree, DelimiterKind, Punct}; use smallvec::{smallvec, SmallVec}; +use span::Span; use syntax::{ast, match_ast, AstNode, AstToken, SmolStr, SyntaxNode}; use triomphe::Arc; use crate::{ db::ExpandDatabase, mod_path::ModPath, - span::SpanMapRef, + span_map::SpanMapRef, tt::{self, Subtree}, InFile, }; @@ -52,7 +53,7 @@ pub fn new( id, input: Some(Interned::new(AttrInput::Literal(SmolStr::new(doc)))), path: Interned::new(ModPath::from(crate::name!(doc))), - ctxt: span_map.span_for_range(comment.syntax().text_range()).ctx, + span: span_map.span_for_range(comment.syntax().text_range()), }), }); let entries: Arc<[Attr]> = Arc::from_iter(entries); @@ -119,7 +120,7 @@ pub fn filter(self, db: &dyn ExpandDatabase, krate: CrateId) -> RawAttrs { let attrs = parts.enumerate().take(1 << AttrId::CFG_ATTR_BITS).filter_map(|(idx, attr)| { let tree = Subtree { - delimiter: tt::Delimiter::dummy_invisible(), + delimiter: tt::Delimiter::invisible_spanned(attr.first()?.first_span()), token_trees: attr.to_vec(), }; Attr::from_tt(db, &tree, index.with_cfg_attr(idx)) @@ -176,7 +177,7 @@ pub struct Attr { pub id: AttrId, pub path: Interned, pub input: Option>, - pub ctxt: SyntaxContextId, + pub span: Span, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -205,6 +206,7 @@ fn from_src( id: AttrId, ) -> Option { let path = Interned::new(ModPath::from_src(db, ast.path()?, span_map)?); + let span = span_map.span_for_range(ast.syntax().text_range()); let input = if let Some(ast::Expr::Literal(lit)) = ast.expr() { let value = match lit.kind() { ast::LiteralKind::String(string) => string.value()?.into(), @@ -212,12 +214,12 @@ fn from_src( }; Some(Interned::new(AttrInput::Literal(value))) } else if let Some(tt) = ast.token_tree() { - let tree = syntax_node_to_token_tree(tt.syntax(), span_map); + let tree = syntax_node_to_token_tree(tt.syntax(), span_map, span); Some(Interned::new(AttrInput::TokenTree(Box::new(tree)))) } else { None }; - Some(Attr { id, path, input, ctxt: span_map.span_for_range(ast.syntax().text_range()).ctx }) + Some(Attr { id, path, input, span }) } fn from_tt(db: &dyn ExpandDatabase, tt: &tt::Subtree, id: AttrId) -> Option { @@ -265,7 +267,7 @@ pub fn token_tree_value(&self) -> Option<&Subtree> { pub fn parse_path_comma_token_tree<'a>( &'a self, db: &'a dyn ExpandDatabase, - ) -> Option + 'a> { + ) -> Option + 'a> { let args = self.token_tree_value()?; if args.delimiter.kind != DelimiterKind::Parenthesis { @@ -281,7 +283,7 @@ pub fn parse_path_comma_token_tree<'a>( // FIXME: This is necessarily a hack. It'd be nice if we could avoid allocation // here or maybe just parse a mod path from a token tree directly let subtree = tt::Subtree { - delimiter: tt::Delimiter::dummy_invisible(), + delimiter: tt::Delimiter::invisible_spanned(tts.first()?.first_span()), token_trees: tts.to_vec(), }; let (parse, span_map) = @@ -293,7 +295,7 @@ pub fn parse_path_comma_token_tree<'a>( return None; } let path = meta.path()?; - let call_site = span_map.span_at(path.syntax().text_range().start()).ctx; + let call_site = span_map.span_at(path.syntax().text_range().start()); Some(( ModPath::from_src(db, path, SpanMapRef::ExpansionSpanMap(&span_map))?, call_site, diff --git a/crates/hir-expand/src/builtin_attr_macro.rs b/crates/hir-expand/src/builtin_attr_macro.rs index de58a495fef..55157abe671 100644 --- a/crates/hir-expand/src/builtin_attr_macro.rs +++ b/crates/hir-expand/src/builtin_attr_macro.rs @@ -1,12 +1,7 @@ //! Builtin attributes. +use span::{MacroCallId, Span}; -use base_db::{ - span::{SyntaxContextId, ROOT_ERASED_FILE_AST_ID}, - FileId, -}; -use syntax::{TextRange, TextSize}; - -use crate::{db::ExpandDatabase, name, tt, ExpandResult, MacroCallId, MacroCallKind}; +use crate::{db::ExpandDatabase, name, tt, ExpandResult, MacroCallKind}; macro_rules! register_builtin { ($expand_fn:ident: $(($name:ident, $variant:ident) => $expand:ident),* ) => { @@ -106,7 +101,12 @@ fn derive_attr_expand( MacroCallKind::Attr { attr_args: Some(attr_args), .. } if loc.def.is_attribute_derive() => { attr_args } - _ => return ExpandResult::ok(tt::Subtree::empty(tt::DelimSpan::DUMMY)), + _ => { + return ExpandResult::ok(tt::Subtree::empty(tt::DelimSpan { + open: loc.call_site, + close: loc.call_site, + })) + } }; pseudo_derive_attr_expansion(tt, derives, loc.call_site) } @@ -114,20 +114,13 @@ fn derive_attr_expand( pub fn pseudo_derive_attr_expansion( tt: &tt::Subtree, args: &tt::Subtree, - call_site: SyntaxContextId, + call_site: Span, ) -> ExpandResult { let mk_leaf = |char| { tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char, spacing: tt::Spacing::Alone, - span: tt::SpanData { - range: TextRange::empty(TextSize::new(0)), - anchor: base_db::span::SpanAnchor { - file_id: FileId::BOGUS, - ast_id: ROOT_ERASED_FILE_AST_ID, - }, - ctx: call_site, - }, + span: call_site, })) }; diff --git a/crates/hir-expand/src/builtin_derive_macro.rs b/crates/hir-expand/src/builtin_derive_macro.rs index 410aa4d289e..8f240ef0732 100644 --- a/crates/hir-expand/src/builtin_derive_macro.rs +++ b/crates/hir-expand/src/builtin_derive_macro.rs @@ -1,20 +1,21 @@ //! Builtin derives. -use base_db::{span::SpanData, CrateOrigin, LangCrateOrigin}; +use base_db::{CrateOrigin, LangCrateOrigin}; use itertools::izip; use rustc_hash::FxHashSet; +use span::{MacroCallId, Span}; use stdx::never; use tracing::debug; use crate::{ hygiene::span_with_def_site_ctxt, name::{AsName, Name}, - span::SpanMapRef, + span_map::SpanMapRef, tt, }; use syntax::ast::{self, AstNode, FieldList, HasAttrs, HasGenericParams, HasName, HasTypeBounds}; -use crate::{db::ExpandDatabase, name, quote, ExpandError, ExpandResult, MacroCallId}; +use crate::{db::ExpandDatabase, name, quote, ExpandError, ExpandResult}; macro_rules! register_builtin { ( $($trait:ident => $expand:ident),* ) => { @@ -35,7 +36,7 @@ pub fn expand( $( BuiltinDeriveExpander::$trait => $expand, )* }; - let span = db.lookup_intern_macro_call(id).span(db); + let span = db.lookup_intern_macro_call(id).call_site; let span = span_with_def_site_ctxt(db, span, id); expander(db, id, span, tt, token_map) } @@ -73,16 +74,16 @@ enum VariantShape { Unit, } -fn tuple_field_iterator(span: SpanData, n: usize) -> impl Iterator { +fn tuple_field_iterator(span: Span, n: usize) -> impl Iterator { (0..n).map(move |it| tt::Ident::new(format!("f{it}"), span)) } impl VariantShape { - fn as_pattern(&self, path: tt::Subtree, span: SpanData) -> tt::Subtree { + fn as_pattern(&self, path: tt::Subtree, span: Span) -> tt::Subtree { self.as_pattern_map(path, span, |it| quote!(span => #it)) } - fn field_names(&self, span: SpanData) -> Vec { + fn field_names(&self, span: Span) -> Vec { match self { VariantShape::Struct(s) => s.clone(), VariantShape::Tuple(n) => tuple_field_iterator(span, *n).collect(), @@ -93,7 +94,7 @@ fn field_names(&self, span: SpanData) -> Vec { fn as_pattern_map( &self, path: tt::Subtree, - span: SpanData, + span: Span, field_map: impl Fn(&tt::Ident) -> tt::Subtree, ) -> tt::Subtree { match self { @@ -143,11 +144,11 @@ enum AdtShape { } impl AdtShape { - fn as_pattern(&self, span: SpanData, name: &tt::Ident) -> Vec { + fn as_pattern(&self, span: Span, name: &tt::Ident) -> Vec { self.as_pattern_map(name, |it| quote!(span =>#it), span) } - fn field_names(&self, span: SpanData) -> Vec> { + fn field_names(&self, span: Span) -> Vec> { match self { AdtShape::Struct(s) => { vec![s.field_names(span)] @@ -166,7 +167,7 @@ fn as_pattern_map( &self, name: &tt::Ident, field_map: impl Fn(&tt::Ident) -> tt::Subtree, - span: SpanData, + span: Span, ) -> Vec { match self { AdtShape::Struct(s) => { @@ -199,7 +200,7 @@ struct BasicAdtInfo { fn parse_adt( tm: SpanMapRef<'_>, adt: &ast::Adt, - call_site: SpanData, + call_site: Span, ) -> Result { let (name, generic_param_list, shape) = match adt { ast::Adt::Struct(it) => ( @@ -245,7 +246,7 @@ fn parse_adt( match this { Some(it) => { param_type_set.insert(it.as_name()); - mbe::syntax_node_to_token_tree(it.syntax(), tm) + mbe::syntax_node_to_token_tree(it.syntax(), tm, call_site) } None => { tt::Subtree::empty(::tt::DelimSpan { open: call_site, close: call_site }) @@ -253,15 +254,15 @@ fn parse_adt( } }; let bounds = match ¶m { - ast::TypeOrConstParam::Type(it) => { - it.type_bound_list().map(|it| mbe::syntax_node_to_token_tree(it.syntax(), tm)) - } + ast::TypeOrConstParam::Type(it) => it + .type_bound_list() + .map(|it| mbe::syntax_node_to_token_tree(it.syntax(), tm, call_site)), ast::TypeOrConstParam::Const(_) => None, }; let ty = if let ast::TypeOrConstParam::Const(param) = param { let ty = param .ty() - .map(|ty| mbe::syntax_node_to_token_tree(ty.syntax(), tm)) + .map(|ty| mbe::syntax_node_to_token_tree(ty.syntax(), tm, call_site)) .unwrap_or_else(|| { tt::Subtree::empty(::tt::DelimSpan { open: call_site, close: call_site }) }); @@ -297,7 +298,7 @@ fn parse_adt( let name = p.path()?.qualifier()?.as_single_name_ref()?.as_name(); param_type_set.contains(&name).then_some(p) }) - .map(|it| mbe::syntax_node_to_token_tree(it.syntax(), tm)) + .map(|it| mbe::syntax_node_to_token_tree(it.syntax(), tm, call_site)) .collect(); let name_token = name_to_token(tm, name)?; Ok(BasicAdtInfo { name: name_token, shape, param_types, associated_types }) @@ -349,7 +350,7 @@ fn name_to_token( /// therefore does not get bound by the derived trait. fn expand_simple_derive( // FIXME: use - invoc_span: SpanData, + invoc_span: Span, tt: &ast::Adt, tm: SpanMapRef<'_>, trait_path: tt::Subtree, @@ -397,7 +398,7 @@ impl < ##params > #trait_path for #name < ##args > where ##where_block { #trait_ ExpandResult::ok(expanded) } -fn find_builtin_crate(db: &dyn ExpandDatabase, id: MacroCallId, span: SpanData) -> tt::TokenTree { +fn find_builtin_crate(db: &dyn ExpandDatabase, id: MacroCallId, span: Span) -> tt::TokenTree { // FIXME: make hygiene works for builtin derive macro // such that $crate can be used here. let cg = db.crate_graph(); @@ -416,7 +417,7 @@ fn find_builtin_crate(db: &dyn ExpandDatabase, id: MacroCallId, span: SpanData) fn copy_expand( db: &dyn ExpandDatabase, id: MacroCallId, - span: SpanData, + span: Span, tt: &ast::Adt, tm: SpanMapRef<'_>, ) -> ExpandResult { @@ -427,7 +428,7 @@ fn copy_expand( fn clone_expand( db: &dyn ExpandDatabase, id: MacroCallId, - span: SpanData, + span: Span, tt: &ast::Adt, tm: SpanMapRef<'_>, ) -> ExpandResult { @@ -470,13 +471,13 @@ fn clone(&self) -> Self { } /// This function exists since `quote! {span => => }` doesn't work. -fn fat_arrow(span: SpanData) -> tt::Subtree { +fn fat_arrow(span: Span) -> tt::Subtree { let eq = tt::Punct { char: '=', spacing: ::tt::Spacing::Joint, span }; quote! {span => #eq> } } /// This function exists since `quote! {span => && }` doesn't work. -fn and_and(span: SpanData) -> tt::Subtree { +fn and_and(span: Span) -> tt::Subtree { let and = tt::Punct { char: '&', spacing: ::tt::Spacing::Joint, span }; quote! {span => #and& } } @@ -484,7 +485,7 @@ fn and_and(span: SpanData) -> tt::Subtree { fn default_expand( db: &dyn ExpandDatabase, id: MacroCallId, - span: SpanData, + span: Span, tt: &ast::Adt, tm: SpanMapRef<'_>, ) -> ExpandResult { @@ -529,7 +530,7 @@ fn default() -> Self { fn debug_expand( db: &dyn ExpandDatabase, id: MacroCallId, - span: SpanData, + span: Span, tt: &ast::Adt, tm: SpanMapRef<'_>, ) -> ExpandResult { @@ -607,7 +608,7 @@ fn fmt(&self, f: &mut #krate::fmt::Formatter) -> #krate::fmt::Result { fn hash_expand( db: &dyn ExpandDatabase, id: MacroCallId, - span: SpanData, + span: Span, tt: &ast::Adt, tm: SpanMapRef<'_>, ) -> ExpandResult { @@ -660,7 +661,7 @@ fn hash(&self, ra_expand_state: &mut H) { fn eq_expand( db: &dyn ExpandDatabase, id: MacroCallId, - span: SpanData, + span: Span, tt: &ast::Adt, tm: SpanMapRef<'_>, ) -> ExpandResult { @@ -671,7 +672,7 @@ fn eq_expand( fn partial_eq_expand( db: &dyn ExpandDatabase, id: MacroCallId, - span: SpanData, + span: Span, tt: &ast::Adt, tm: SpanMapRef<'_>, ) -> ExpandResult { @@ -725,7 +726,7 @@ fn eq(&self, other: &Self) -> bool { fn self_and_other_patterns( adt: &BasicAdtInfo, name: &tt::Ident, - span: SpanData, + span: Span, ) -> (Vec, Vec) { let self_patterns = adt.shape.as_pattern_map( name, @@ -749,7 +750,7 @@ fn self_and_other_patterns( fn ord_expand( db: &dyn ExpandDatabase, id: MacroCallId, - span: SpanData, + span: Span, tt: &ast::Adt, tm: SpanMapRef<'_>, ) -> ExpandResult { @@ -760,7 +761,7 @@ fn compare( left: tt::Subtree, right: tt::Subtree, rest: tt::Subtree, - span: SpanData, + span: Span, ) -> tt::Subtree { let fat_arrow1 = fat_arrow(span); let fat_arrow2 = fat_arrow(span); @@ -813,7 +814,7 @@ fn cmp(&self, other: &Self) -> #krate::cmp::Ordering { fn partial_ord_expand( db: &dyn ExpandDatabase, id: MacroCallId, - span: SpanData, + span: Span, tt: &ast::Adt, tm: SpanMapRef<'_>, ) -> ExpandResult { @@ -824,7 +825,7 @@ fn compare( left: tt::Subtree, right: tt::Subtree, rest: tt::Subtree, - span: SpanData, + span: Span, ) -> tt::Subtree { let fat_arrow1 = fat_arrow(span); let fat_arrow2 = fat_arrow(span); diff --git a/crates/hir-expand/src/builtin_fn_macro.rs b/crates/hir-expand/src/builtin_fn_macro.rs index c8f04bfee54..f99a8917623 100644 --- a/crates/hir-expand/src/builtin_fn_macro.rs +++ b/crates/hir-expand/src/builtin_fn_macro.rs @@ -1,13 +1,11 @@ //! Builtin macro -use base_db::{ - span::{SpanAnchor, SpanData, SyntaxContextId, ROOT_ERASED_FILE_AST_ID}, - AnchoredPath, Edition, FileId, -}; +use base_db::{AnchoredPath, Edition, FileId}; use cfg::CfgExpr; use either::Either; use itertools::Itertools; use mbe::{parse_exprs_with_sep, parse_to_token_tree}; +use span::{Span, SpanAnchor, SyntaxContextId, ROOT_ERASED_FILE_AST_ID}; use syntax::{ ast::{self, AstToken}, SmolStr, @@ -15,10 +13,11 @@ use crate::{ db::ExpandDatabase, - hygiene::span_with_def_site_ctxt, - name, quote, + hygiene::{span_with_call_site_ctxt, span_with_def_site_ctxt}, + name::{self, known}, + quote, tt::{self, DelimSpan}, - ExpandError, ExpandResult, HirFileIdExt, MacroCallId, MacroCallLoc, + ExpandError, ExpandResult, HirFileIdExt, MacroCallId, }; macro_rules! register_builtin { @@ -44,7 +43,7 @@ pub fn expand( $( BuiltinFnLikeExpander::$kind => $expand, )* }; - let span = db.lookup_intern_macro_call(id).span(db); + let span = db.lookup_intern_macro_call(id).call_site; let span = span_with_def_site_ctxt(db, span, id); expander(db, id, tt, span) } @@ -61,7 +60,7 @@ pub fn expand( $( EagerExpander::$e_kind => $e_expand, )* }; - let span = db.lookup_intern_macro_call(id).span(db); + let span = db.lookup_intern_macro_call(id).call_site; let span = span_with_def_site_ctxt(db, span, id); expander(db, id, tt, span) } @@ -109,6 +108,7 @@ pub fn find_builtin_macro( (format_args, FormatArgs) => format_args_expand, (const_format_args, ConstFormatArgs) => format_args_expand, (format_args_nl, FormatArgsNl) => format_args_nl_expand, + (quote, Quote) => quote_expand, EAGER: (compile_error, CompileError) => compile_error_expand, @@ -122,7 +122,7 @@ pub fn find_builtin_macro( (option_env, OptionEnv) => option_env_expand } -fn mk_pound(span: SpanData) -> tt::Subtree { +fn mk_pound(span: Span) -> tt::Subtree { crate::quote::IntoTt::to_subtree( vec![crate::tt::Leaf::Punct(crate::tt::Punct { char: '#', @@ -138,7 +138,7 @@ fn module_path_expand( _db: &dyn ExpandDatabase, _id: MacroCallId, _tt: &tt::Subtree, - span: SpanData, + span: Span, ) -> ExpandResult { // Just return a dummy result. ExpandResult::ok(quote! {span => @@ -150,13 +150,13 @@ fn line_expand( _db: &dyn ExpandDatabase, _id: MacroCallId, _tt: &tt::Subtree, - span: SpanData, + span: Span, ) -> ExpandResult { // dummy implementation for type-checking purposes // Note that `line!` and `column!` will never be implemented properly, as they are by definition // not incremental ExpandResult::ok(tt::Subtree { - delimiter: tt::Delimiter::dummy_invisible(), + delimiter: tt::Delimiter::invisible_spanned(span), token_trees: vec![tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { text: "0u32".into(), span, @@ -168,7 +168,7 @@ fn log_syntax_expand( _db: &dyn ExpandDatabase, _id: MacroCallId, _tt: &tt::Subtree, - span: SpanData, + span: Span, ) -> ExpandResult { ExpandResult::ok(quote! {span =>}) } @@ -177,7 +177,7 @@ fn trace_macros_expand( _db: &dyn ExpandDatabase, _id: MacroCallId, _tt: &tt::Subtree, - span: SpanData, + span: Span, ) -> ExpandResult { ExpandResult::ok(quote! {span =>}) } @@ -186,7 +186,7 @@ fn stringify_expand( _db: &dyn ExpandDatabase, _id: MacroCallId, tt: &tt::Subtree, - span: SpanData, + span: Span, ) -> ExpandResult { let pretty = ::tt::pretty(&tt.token_trees); @@ -198,32 +198,38 @@ fn stringify_expand( } fn assert_expand( - _db: &dyn ExpandDatabase, - _id: MacroCallId, + db: &dyn ExpandDatabase, + id: MacroCallId, tt: &tt::Subtree, - span: SpanData, + span: Span, ) -> ExpandResult { - let args = parse_exprs_with_sep(tt, ','); + let call_site_span = span_with_call_site_ctxt(db, span, id); + let args = parse_exprs_with_sep(tt, ',', call_site_span); let dollar_crate = tt::Ident { text: SmolStr::new_inline("$crate"), span }; let expanded = match &*args { [cond, panic_args @ ..] => { let comma = tt::Subtree { - delimiter: tt::Delimiter::dummy_invisible(), + delimiter: tt::Delimiter::invisible_spanned(call_site_span), token_trees: vec![tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char: ',', spacing: tt::Spacing::Alone, - span, + span: call_site_span, }))], }; let cond = cond.clone(); let panic_args = itertools::Itertools::intersperse(panic_args.iter().cloned(), comma); - quote! {span =>{ + let mac = if use_panic_2021(db, span) { + quote! {call_site_span => #dollar_crate::panic::panic_2021!(##panic_args) } + } else { + quote! {call_site_span => #dollar_crate::panic!(##panic_args) } + }; + quote! {call_site_span =>{ if !(#cond) { - #dollar_crate::panic!(##panic_args); + #mac; } }} } - [] => quote! {span =>{}}, + [] => quote! {call_site_span =>{}}, }; ExpandResult::ok(expanded) @@ -233,7 +239,7 @@ fn file_expand( _db: &dyn ExpandDatabase, _id: MacroCallId, _tt: &tt::Subtree, - span: SpanData, + span: Span, ) -> ExpandResult { // FIXME: RA purposefully lacks knowledge of absolute file names // so just return "". @@ -250,7 +256,7 @@ fn format_args_expand( db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, - span: SpanData, + span: Span, ) -> ExpandResult { format_args_expand_general(db, id, tt, "", span) } @@ -259,7 +265,7 @@ fn format_args_nl_expand( db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, - span: SpanData, + span: Span, ) -> ExpandResult { format_args_expand_general(db, id, tt, "\\n", span) } @@ -270,7 +276,7 @@ fn format_args_expand_general( tt: &tt::Subtree, // FIXME: Make use of this so that mir interpretation works properly _end_string: &str, - span: SpanData, + span: Span, ) -> ExpandResult { let pound = mk_pound(span); let mut tt = tt.clone(); @@ -284,7 +290,7 @@ fn asm_expand( _db: &dyn ExpandDatabase, _id: MacroCallId, tt: &tt::Subtree, - span: SpanData, + span: Span, ) -> ExpandResult { // We expand all assembly snippets to `format_args!` invocations to get format syntax // highlighting for them. @@ -314,7 +320,7 @@ fn global_asm_expand( _db: &dyn ExpandDatabase, _id: MacroCallId, _tt: &tt::Subtree, - span: SpanData, + span: Span, ) -> ExpandResult { // Expand to nothing (at item-level) ExpandResult::ok(quote! {span =>}) @@ -324,7 +330,7 @@ fn cfg_expand( db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, - span: SpanData, + span: Span, ) -> ExpandResult { let loc = db.lookup_intern_macro_call(id); let expr = CfgExpr::parse(tt); @@ -337,19 +343,25 @@ fn panic_expand( db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, - span: SpanData, + span: Span, ) -> ExpandResult { - let loc: MacroCallLoc = db.lookup_intern_macro_call(id); let dollar_crate = tt::Ident { text: SmolStr::new_inline("$crate"), span }; + let call_site_span = span_with_call_site_ctxt(db, span, id); + + let mac = + if use_panic_2021(db, call_site_span) { known::panic_2021 } else { known::panic_2015 }; + // Expand to a macro call `$crate::panic::panic_{edition}` - let mut call = if db.crate_graph()[loc.krate].edition >= Edition::Edition2021 { - quote!(span =>#dollar_crate::panic::panic_2021!) - } else { - quote!(span =>#dollar_crate::panic::panic_2015!) - }; + let mut call = quote!(call_site_span =>#dollar_crate::panic::#mac!); // Pass the original arguments - call.token_trees.push(tt::TokenTree::Subtree(tt.clone())); + let mut subtree = tt.clone(); + subtree.delimiter = tt::Delimiter { + open: call_site_span, + close: call_site_span, + kind: tt::DelimiterKind::Parenthesis, + }; + call.token_trees.push(tt::TokenTree::Subtree(subtree)); ExpandResult::ok(call) } @@ -357,22 +369,52 @@ fn unreachable_expand( db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, - span: SpanData, + span: Span, ) -> ExpandResult { - let loc: MacroCallLoc = db.lookup_intern_macro_call(id); - // Expand to a macro call `$crate::panic::unreachable_{edition}` let dollar_crate = tt::Ident { text: SmolStr::new_inline("$crate"), span }; - let mut call = if db.crate_graph()[loc.krate].edition >= Edition::Edition2021 { - quote!(span =>#dollar_crate::panic::unreachable_2021!) + let call_site_span = span_with_call_site_ctxt(db, span, id); + + let mac = if use_panic_2021(db, call_site_span) { + known::unreachable_2021 } else { - quote!(span =>#dollar_crate::panic::unreachable_2015!) + known::unreachable_2015 }; + // Expand to a macro call `$crate::panic::panic_{edition}` + let mut call = quote!(call_site_span =>#dollar_crate::panic::#mac!); + // Pass the original arguments - call.token_trees.push(tt::TokenTree::Subtree(tt.clone())); + let mut subtree = tt.clone(); + subtree.delimiter = tt::Delimiter { + open: call_site_span, + close: call_site_span, + kind: tt::DelimiterKind::Parenthesis, + }; + call.token_trees.push(tt::TokenTree::Subtree(subtree)); ExpandResult::ok(call) } +fn use_panic_2021(db: &dyn ExpandDatabase, span: Span) -> bool { + // To determine the edition, we check the first span up the expansion + // stack that does not have #[allow_internal_unstable(edition_panic)]. + // (To avoid using the edition of e.g. the assert!() or debug_assert!() definition.) + loop { + let Some(expn) = db.lookup_intern_syntax_context(span.ctx).outer_expn else { + break false; + }; + let expn = db.lookup_intern_macro_call(expn); + // FIXME: Record allow_internal_unstable in the macro def (not been done yet because it + // would consume quite a bit extra memory for all call locs...) + // if let Some(features) = expn.def.allow_internal_unstable { + // if features.iter().any(|&f| f == sym::edition_panic) { + // span = expn.call_site; + // continue; + // } + // } + break expn.def.edition >= Edition::Edition2021; + } +} + fn unquote_str(lit: &tt::Literal) -> Option { let lit = ast::make::tokens::literal(&lit.to_string()); let token = ast::String::cast(lit)?; @@ -395,7 +437,7 @@ fn compile_error_expand( _db: &dyn ExpandDatabase, _id: MacroCallId, tt: &tt::Subtree, - span: SpanData, + span: Span, ) -> ExpandResult { let err = match &*tt.token_trees { [tt::TokenTree::Leaf(tt::Leaf::Literal(it))] => match unquote_str(it) { @@ -412,7 +454,7 @@ fn concat_expand( _db: &dyn ExpandDatabase, _arg_id: MacroCallId, tt: &tt::Subtree, - span: SpanData, + span: Span, ) -> ExpandResult { let mut err = None; let mut text = String::new(); @@ -459,7 +501,7 @@ fn concat_bytes_expand( _db: &dyn ExpandDatabase, _arg_id: MacroCallId, tt: &tt::Subtree, - span: SpanData, + span: Span, ) -> ExpandResult { let mut bytes = Vec::new(); let mut err = None; @@ -543,7 +585,7 @@ fn concat_idents_expand( _db: &dyn ExpandDatabase, _arg_id: MacroCallId, tt: &tt::Subtree, - span: SpanData, + span: Span, ) -> ExpandResult { let mut err = None; let mut ident = String::new(); @@ -596,7 +638,7 @@ fn include_expand( db: &dyn ExpandDatabase, arg_id: MacroCallId, tt: &tt::Subtree, - span: SpanData, + span: Span, ) -> ExpandResult { let file_id = match include_input_to_file_id(db, arg_id, tt) { Ok(it) => it, @@ -629,11 +671,11 @@ fn include_bytes_expand( _db: &dyn ExpandDatabase, _arg_id: MacroCallId, _tt: &tt::Subtree, - span: SpanData, + span: Span, ) -> ExpandResult { // FIXME: actually read the file here if the user asked for macro expansion let res = tt::Subtree { - delimiter: tt::Delimiter::dummy_invisible(), + delimiter: tt::Delimiter::invisible_spanned(span), token_trees: vec![tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { text: r#"b"""#.into(), span, @@ -646,7 +688,7 @@ fn include_str_expand( db: &dyn ExpandDatabase, arg_id: MacroCallId, tt: &tt::Subtree, - span: SpanData, + span: Span, ) -> ExpandResult { let path = match parse_string(tt) { Ok(it) => it, @@ -681,7 +723,7 @@ fn env_expand( db: &dyn ExpandDatabase, arg_id: MacroCallId, tt: &tt::Subtree, - span: SpanData, + span: Span, ) -> ExpandResult { let key = match parse_string(tt) { Ok(it) => it, @@ -713,7 +755,7 @@ fn option_env_expand( db: &dyn ExpandDatabase, arg_id: MacroCallId, tt: &tt::Subtree, - span: SpanData, + span: Span, ) -> ExpandResult { let key = match parse_string(tt) { Ok(it) => it, @@ -729,3 +771,15 @@ fn option_env_expand( ExpandResult::ok(expanded) } + +fn quote_expand( + _db: &dyn ExpandDatabase, + _arg_id: MacroCallId, + _tt: &tt::Subtree, + span: Span, +) -> ExpandResult { + ExpandResult::new( + tt::Subtree::empty(tt::DelimSpan { open: span, close: span }), + ExpandError::other("quote! is not implemented"), + ) +} diff --git a/crates/hir-expand/src/change.rs b/crates/hir-expand/src/change.rs new file mode 100644 index 00000000000..67b7df198e9 --- /dev/null +++ b/crates/hir-expand/src/change.rs @@ -0,0 +1,42 @@ +//! Defines a unit of change that can applied to the database to get the next +//! state. Changes are transactional. +use base_db::{salsa::Durability, CrateGraph, FileChange, SourceDatabaseExt, SourceRoot}; +use span::FileId; +use triomphe::Arc; + +use crate::{db::ExpandDatabase, proc_macro::ProcMacros}; + +#[derive(Debug, Default)] +pub struct Change { + pub source_change: FileChange, + pub proc_macros: Option, +} + +impl Change { + pub fn new() -> Self { + Self::default() + } + + pub fn apply(self, db: &mut (impl ExpandDatabase + SourceDatabaseExt)) { + self.source_change.apply(db); + if let Some(proc_macros) = self.proc_macros { + db.set_proc_macros_with_durability(Arc::new(proc_macros), Durability::HIGH); + } + } + + pub fn change_file(&mut self, file_id: FileId, new_text: Option>) { + self.source_change.change_file(file_id, new_text) + } + + pub fn set_crate_graph(&mut self, graph: CrateGraph) { + self.source_change.set_crate_graph(graph) + } + + pub fn set_proc_macros(&mut self, proc_macros: ProcMacros) { + self.proc_macros = Some(proc_macros); + } + + pub fn set_roots(&mut self, roots: Vec) { + self.source_change.set_roots(roots) + } +} diff --git a/crates/hir-expand/src/db.rs b/crates/hir-expand/src/db.rs index 935669d49b5..f7a26e436de 100644 --- a/crates/hir-expand/src/db.rs +++ b/crates/hir-expand/src/db.rs @@ -1,14 +1,16 @@ //! Defines database & queries for macro expansion. +use std::sync::OnceLock; + use base_db::{ salsa::{self, debug::DebugQueryTable}, - span::SyntaxContextId, - CrateId, Edition, FileId, SourceDatabase, + CrateId, Edition, FileId, SourceDatabase, VersionReq, }; use either::Either; use limit::Limit; use mbe::{syntax_node_to_token_tree, ValueResult}; use rustc_hash::FxHashSet; +use span::{Span, SyntaxContextId}; use syntax::{ ast::{self, HasAttrs}, AstNode, Parse, SyntaxError, SyntaxNode, SyntaxToken, T, @@ -21,11 +23,16 @@ builtin_attr_macro::pseudo_derive_attr_expansion, builtin_fn_macro::EagerExpander, fixup::{self, reverse_fixups, SyntaxFixupUndoInfo}, - hygiene::{apply_mark, SyntaxContextData, Transparency}, - span::{RealSpanMap, SpanMap, SpanMapRef}, - tt, AstId, BuiltinAttrExpander, BuiltinDeriveExpander, BuiltinFnLikeExpander, EagerCallInfo, - ExpandError, ExpandResult, ExpandTo, ExpansionSpanMap, HirFileId, HirFileIdRepr, MacroCallId, - MacroCallKind, MacroCallLoc, MacroDefId, MacroDefKind, MacroFileId, ProcMacroExpander, + hygiene::{ + apply_mark, span_with_call_site_ctxt, span_with_def_site_ctxt, span_with_mixed_site_ctxt, + SyntaxContextData, Transparency, + }, + proc_macro::ProcMacros, + span_map::{RealSpanMap, SpanMap, SpanMapRef}, + tt, AstId, BuiltinAttrExpander, BuiltinDeriveExpander, BuiltinFnLikeExpander, + CustomProcMacroExpander, EagerCallInfo, ExpandError, ExpandResult, ExpandTo, ExpansionSpanMap, + HirFileId, HirFileIdRepr, MacroCallId, MacroCallKind, MacroCallLoc, MacroDefId, MacroDefKind, + MacroFileId, }; /// Total limit on the number of tokens produced by any macro invocation. @@ -39,10 +46,13 @@ #[derive(Debug, Clone, Eq, PartialEq)] /// Old-style `macro_rules` or the new macros 2.0 pub struct DeclarativeMacroExpander { - pub mac: mbe::DeclarativeMacro, + pub mac: mbe::DeclarativeMacro, pub transparency: Transparency, } +// FIXME: Remove this once we drop support for 1.76 +static REQUIREMENT: OnceLock = OnceLock::new(); + impl DeclarativeMacroExpander { pub fn expand( &self, @@ -50,25 +60,61 @@ pub fn expand( tt: tt::Subtree, call_id: MacroCallId, ) -> ExpandResult { + let loc = db.lookup_intern_macro_call(call_id); + let toolchain = &db.crate_graph()[loc.def.krate].toolchain; + let new_meta_vars = toolchain.as_ref().map_or(false, |version| { + REQUIREMENT.get_or_init(|| VersionReq::parse(">=1.76").unwrap()).matches( + &base_db::Version { + pre: base_db::Prerelease::EMPTY, + build: base_db::BuildMetadata::EMPTY, + major: version.major, + minor: version.minor, + patch: version.patch, + }, + ) + }); match self.mac.err() { Some(e) => ExpandResult::new( - tt::Subtree::empty(tt::DelimSpan::DUMMY), + tt::Subtree::empty(tt::DelimSpan { open: loc.call_site, close: loc.call_site }), ExpandError::other(format!("invalid macro definition: {e}")), ), None => self .mac - .expand(&tt, |s| s.ctx = apply_mark(db, s.ctx, call_id, self.transparency)) + .expand( + &tt, + |s| s.ctx = apply_mark(db, s.ctx, call_id, self.transparency), + new_meta_vars, + loc.call_site, + ) .map_err(Into::into), } } - pub fn expand_unhygienic(&self, tt: tt::Subtree) -> ExpandResult { + pub fn expand_unhygienic( + &self, + db: &dyn ExpandDatabase, + tt: tt::Subtree, + krate: CrateId, + call_site: Span, + ) -> ExpandResult { + let toolchain = &db.crate_graph()[krate].toolchain; + let new_meta_vars = toolchain.as_ref().map_or(false, |version| { + REQUIREMENT.get_or_init(|| VersionReq::parse(">=1.76").unwrap()).matches( + &base_db::Version { + pre: base_db::Prerelease::EMPTY, + build: base_db::BuildMetadata::EMPTY, + major: version.major, + minor: version.minor, + patch: version.patch, + }, + ) + }); match self.mac.err() { Some(e) => ExpandResult::new( - tt::Subtree::empty(tt::DelimSpan::DUMMY), + tt::Subtree::empty(tt::DelimSpan { open: call_site, close: call_site }), ExpandError::other(format!("invalid macro definition: {e}")), ), - None => self.mac.expand(&tt, |_| ()).map_err(Into::into), + None => self.mac.expand(&tt, |_| (), new_meta_vars, call_site).map_err(Into::into), } } } @@ -86,11 +132,15 @@ pub enum TokenExpander { /// `derive(Copy)` and such. BuiltInDerive(BuiltinDeriveExpander), /// The thing we love the most here in rust-analyzer -- procedural macros. - ProcMacro(ProcMacroExpander), + ProcMacro(CustomProcMacroExpander), } #[salsa::query_group(ExpandDatabaseStorage)] pub trait ExpandDatabase: SourceDatabase { + /// The proc macros. + #[salsa::input] + fn proc_macros(&self) -> Arc; + fn ast_id_map(&self, file_id: HirFileId) -> Arc; /// Main public API -- parses a hir file, not caring whether it's a real @@ -164,7 +214,20 @@ pub fn span_map(db: &dyn ExpandDatabase, file_id: HirFileId) -> SpanMap { } pub fn real_span_map(db: &dyn ExpandDatabase, file_id: FileId) -> Arc { - Arc::new(RealSpanMap::from_file(db, file_id)) + use syntax::ast::HasModuleItem; + let mut pairs = vec![(syntax::TextSize::new(0), span::ROOT_ERASED_FILE_AST_ID)]; + let ast_id_map = db.ast_id_map(file_id.into()); + let tree = db.parse(file_id).tree(); + pairs.extend( + tree.items() + .map(|item| (item.syntax().text_range().start(), ast_id_map.ast_id(&item).erase())), + ); + + Arc::new(RealSpanMap::from_file( + file_id, + pairs.into_boxed_slice(), + tree.syntax().text_range().end(), + )) } /// This expands the given macro call, but with different arguments. This is @@ -184,12 +247,13 @@ pub fn expand_speculative( // Build the subtree and token mapping for the speculative args let (mut tt, undo_info) = match loc.kind { - MacroCallKind::FnLike { .. } => { - (mbe::syntax_node_to_token_tree(speculative_args, span_map), SyntaxFixupUndoInfo::NONE) - } + MacroCallKind::FnLike { .. } => ( + mbe::syntax_node_to_token_tree(speculative_args, span_map, loc.call_site), + SyntaxFixupUndoInfo::NONE, + ), MacroCallKind::Derive { .. } | MacroCallKind::Attr { .. } => { let censor = censor_for_macro_input(&loc, speculative_args); - let mut fixups = fixup::fixup_syntax(span_map, speculative_args); + let mut fixups = fixup::fixup_syntax(span_map, speculative_args, loc.call_site); fixups.append.retain(|it, _| match it { syntax::NodeOrToken::Node(it) => !censor.contains(it), syntax::NodeOrToken::Token(_) => true, @@ -201,6 +265,7 @@ pub fn expand_speculative( span_map, fixups.append, fixups.remove, + loc.call_site, ), fixups.undo_info, ) @@ -222,8 +287,9 @@ pub fn expand_speculative( }?; match attr.token_tree() { Some(token_tree) => { - let mut tree = syntax_node_to_token_tree(token_tree.syntax(), span_map); - tree.delimiter = tt::Delimiter::DUMMY_INVISIBLE; + let mut tree = + syntax_node_to_token_tree(token_tree.syntax(), span_map, loc.call_site); + tree.delimiter = tt::Delimiter::invisible_spanned(loc.call_site); Some(tree) } @@ -237,17 +303,16 @@ pub fn expand_speculative( // Otherwise the expand query will fetch the non speculative attribute args and pass those instead. let mut speculative_expansion = match loc.def.kind { MacroDefKind::ProcMacro(expander, ..) => { - tt.delimiter = tt::Delimiter::DUMMY_INVISIBLE; - let call_site = loc.span(db); + tt.delimiter = tt::Delimiter::invisible_spanned(loc.call_site); expander.expand( db, loc.def.krate, loc.krate, &tt, attr_arg.as_ref(), - call_site, - call_site, - call_site, + span_with_def_site_ctxt(db, loc.def.span, actual_macro_call), + span_with_call_site_ctxt(db, loc.def.span, actual_macro_call), + span_with_mixed_site_ctxt(db, loc.def.span, actual_macro_call), ) } MacroDefKind::BuiltInAttr(BuiltinAttrExpander::Derive, _) => { @@ -258,9 +323,12 @@ pub fn expand_speculative( let adt = ast::Adt::cast(speculative_args.clone()).unwrap(); expander.expand(db, actual_macro_call, &adt, span_map) } - MacroDefKind::Declarative(it) => { - db.decl_macro_expander(loc.krate, it).expand_unhygienic(tt) - } + MacroDefKind::Declarative(it) => db.decl_macro_expander(loc.krate, it).expand_unhygienic( + db, + tt, + loc.def.krate, + loc.call_site, + ), MacroDefKind::BuiltIn(it, _) => it.expand(db, actual_macro_call, &tt).map_err(Into::into), MacroDefKind::BuiltInEager(it, _) => { it.expand(db, actual_macro_call, &tt).map_err(Into::into) @@ -410,12 +478,13 @@ fn macro_arg( MacroCallKind::Attr { ast_id, .. } => ast_id.to_ptr(db).to_node(&root).syntax().clone(), }; let (mut tt, undo_info) = match loc.kind { - MacroCallKind::FnLike { .. } => { - (mbe::syntax_node_to_token_tree(&syntax, map.as_ref()), SyntaxFixupUndoInfo::NONE) - } + MacroCallKind::FnLike { .. } => ( + mbe::syntax_node_to_token_tree(&syntax, map.as_ref(), loc.call_site), + SyntaxFixupUndoInfo::NONE, + ), MacroCallKind::Derive { .. } | MacroCallKind::Attr { .. } => { let censor = censor_for_macro_input(&loc, &syntax); - let mut fixups = fixup::fixup_syntax(map.as_ref(), &syntax); + let mut fixups = fixup::fixup_syntax(map.as_ref(), &syntax, loc.call_site); fixups.append.retain(|it, _| match it { syntax::NodeOrToken::Node(it) => !censor.contains(it), syntax::NodeOrToken::Token(_) => true, @@ -427,6 +496,7 @@ fn macro_arg( map.as_ref(), fixups.append.clone(), fixups.remove.clone(), + loc.call_site, ); reverse_fixups(&mut tt, &fixups.undo_info); } @@ -436,6 +506,7 @@ fn macro_arg( map, fixups.append, fixups.remove, + loc.call_site, ), fixups.undo_info, ) @@ -444,7 +515,7 @@ fn macro_arg( if loc.def.is_proc_macro() { // proc macros expect their inputs without parentheses, MBEs expect it with them included - tt.delimiter = tt::Delimiter::DUMMY_INVISIBLE; + tt.delimiter.kind = tt::DelimiterKind::Invisible; } if matches!(loc.def.kind, MacroDefKind::BuiltInEager(..)) { @@ -506,7 +577,8 @@ fn decl_macro_expander( def_crate: CrateId, id: AstId, ) -> Arc { - let is_2021 = db.crate_graph()[def_crate].edition >= Edition::Edition2021; + let crate_data = &db.crate_graph()[def_crate]; + let is_2021 = crate_data.edition >= Edition::Edition2021; let (root, map) = parse_with_map(db, id.file_id); let root = root.syntax_node(); @@ -530,13 +602,29 @@ fn decl_macro_expander( _ => None, } }; + let toolchain = crate_data.toolchain.as_ref(); + let new_meta_vars = toolchain.as_ref().map_or(false, |version| { + REQUIREMENT.get_or_init(|| VersionReq::parse(">=1.76").unwrap()).matches( + &base_db::Version { + pre: base_db::Prerelease::EMPTY, + build: base_db::BuildMetadata::EMPTY, + major: version.major, + minor: version.minor, + patch: version.patch, + }, + ) + }); let (mac, transparency) = match id.to_ptr(db).to_node(&root) { ast::Macro::MacroRules(macro_rules) => ( match macro_rules.token_tree() { Some(arg) => { - let tt = mbe::syntax_node_to_token_tree(arg.syntax(), map.as_ref()); - let mac = mbe::DeclarativeMacro::parse_macro_rules(&tt, is_2021); + let tt = mbe::syntax_node_to_token_tree( + arg.syntax(), + map.as_ref(), + map.span_for_range(macro_rules.macro_rules_token().unwrap().text_range()), + ); + let mac = mbe::DeclarativeMacro::parse_macro_rules(&tt, is_2021, new_meta_vars); mac } None => mbe::DeclarativeMacro::from_err( @@ -549,8 +637,12 @@ fn decl_macro_expander( ast::Macro::MacroDef(macro_def) => ( match macro_def.body() { Some(arg) => { - let tt = mbe::syntax_node_to_token_tree(arg.syntax(), map.as_ref()); - let mac = mbe::DeclarativeMacro::parse_macro2(&tt, is_2021); + let tt = mbe::syntax_node_to_token_tree( + arg.syntax(), + map.as_ref(), + map.span_for_range(macro_def.macro_token().unwrap().text_range()), + ); + let mac = mbe::DeclarativeMacro::parse_macro2(&tt, is_2021, new_meta_vars); mac } None => mbe::DeclarativeMacro::from_err( @@ -601,7 +693,7 @@ fn macro_expand( let Some((macro_arg, undo_info)) = value else { return ExpandResult { value: Arc::new(tt::Subtree { - delimiter: tt::Delimiter::DUMMY_INVISIBLE, + delimiter: tt::Delimiter::invisible_spanned(loc.call_site), token_trees: Vec::new(), }), // FIXME: We should make sure to enforce an invariant that invalid macro @@ -660,7 +752,7 @@ fn macro_expand( // Skip checking token tree limit for include! macro call if !loc.def.is_include() { // Set a hard limit for the expanded tt - if let Err(value) = check_tt_count(&tt) { + if let Err(value) = check_tt_count(&tt, loc.call_site) { return value; } } @@ -673,7 +765,7 @@ fn expand_proc_macro(db: &dyn ExpandDatabase, id: MacroCallId) -> ExpandResult ExpandResult None, }; - let call_site = loc.span(db); let ExpandResult { value: mut tt, err } = expander.expand( db, loc.def.krate, loc.krate, ¯o_arg, attr_arg, - // FIXME - call_site, - call_site, - // FIXME - call_site, + span_with_def_site_ctxt(db, loc.def.span, id), + span_with_call_site_ctxt(db, loc.def.span, id), + span_with_mixed_site_ctxt(db, loc.def.span, id), ); // Set a hard limit for the expanded tt - if let Err(value) = check_tt_count(&tt) { + if let Err(value) = check_tt_count(&tt, loc.call_site) { return value; } @@ -730,12 +819,12 @@ fn token_tree_to_syntax_node( mbe::token_tree_to_syntax_node(tt, entry_point) } -fn check_tt_count(tt: &tt::Subtree) -> Result<(), ExpandResult>> { +fn check_tt_count(tt: &tt::Subtree, call_site: Span) -> Result<(), ExpandResult>> { let count = tt.count(); if TOKEN_LIMIT.check(count).is_err() { Err(ExpandResult { value: Arc::new(tt::Subtree { - delimiter: tt::Delimiter::DUMMY_INVISIBLE, + delimiter: tt::Delimiter::invisible_spanned(call_site), token_trees: vec![], }), err: Some(ExpandError::other(format!( diff --git a/crates/hir-expand/src/eager.rs b/crates/hir-expand/src/eager.rs index 8d55240aef5..da85c2ec7ac 100644 --- a/crates/hir-expand/src/eager.rs +++ b/crates/hir-expand/src/eager.rs @@ -18,7 +18,8 @@ //! //! //! See the full discussion : -use base_db::{span::SyntaxContextId, CrateId}; +use base_db::CrateId; +use span::Span; use syntax::{ted, Parse, SyntaxElement, SyntaxNode, TextSize, WalkEvent}; use triomphe::Arc; @@ -26,9 +27,9 @@ ast::{self, AstNode}, db::ExpandDatabase, mod_path::ModPath, - span::SpanMapRef, - EagerCallInfo, ExpandError, ExpandResult, ExpandTo, ExpansionSpanMap, InFile, MacroCallId, - MacroCallKind, MacroCallLoc, MacroDefId, MacroDefKind, + span_map::SpanMapRef, + EagerCallInfo, ExpandError, ExpandResult, ExpandTo, ExpansionSpanMap, InFile, Intern, + MacroCallId, MacroCallKind, MacroCallLoc, MacroDefId, MacroDefKind, }; pub fn expand_eager_macro_input( @@ -36,7 +37,7 @@ pub fn expand_eager_macro_input( krate: CrateId, macro_call: InFile, def: MacroDefId, - call_site: SyntaxContextId, + call_site: Span, resolver: &dyn Fn(ModPath) -> Option, ) -> ExpandResult> { let ast_map = db.ast_id_map(macro_call.file_id); @@ -48,13 +49,14 @@ pub fn expand_eager_macro_input( // When `lazy_expand` is called, its *parent* file must already exist. // Here we store an eager macro id for the argument expanded subtree // for that purpose. - let arg_id = db.intern_macro_call(MacroCallLoc { + let arg_id = MacroCallLoc { def, krate, eager: None, kind: MacroCallKind::FnLike { ast_id: call_id, expand_to: ExpandTo::Expr }, call_site, - }); + } + .intern(db); let ExpandResult { value: (arg_exp, arg_exp_map), err: parse_err } = db.parse_macro_expansion(arg_id.as_macro_file()); @@ -81,9 +83,9 @@ pub fn expand_eager_macro_input( return ExpandResult { value: None, err }; }; - let mut subtree = mbe::syntax_node_to_token_tree(&expanded_eager_input, arg_map); + let mut subtree = mbe::syntax_node_to_token_tree(&expanded_eager_input, arg_map, call_site); - subtree.delimiter = crate::tt::Delimiter::DUMMY_INVISIBLE; + subtree.delimiter.kind = crate::tt::DelimiterKind::Invisible; let loc = MacroCallLoc { def, @@ -93,7 +95,7 @@ pub fn expand_eager_macro_input( call_site, }; - ExpandResult { value: Some(db.intern_macro_call(loc)), err } + ExpandResult { value: Some(loc.intern(db)), err } } fn lazy_expand( @@ -101,7 +103,7 @@ fn lazy_expand( def: &MacroDefId, macro_call: InFile, krate: CrateId, - call_site: SyntaxContextId, + call_site: Span, ) -> ExpandResult<(InFile>, Arc)> { let ast_id = db.ast_id_map(macro_call.file_id).ast_id(¯o_call.value); @@ -121,7 +123,7 @@ fn eager_macro_recur( mut offset: TextSize, curr: InFile, krate: CrateId, - call_site: SyntaxContextId, + call_site: Span, macro_resolver: &dyn Fn(ModPath) -> Option, ) -> ExpandResult> { let original = curr.value.clone_for_update(); diff --git a/crates/hir-expand/src/files.rs b/crates/hir-expand/src/files.rs index 89f0685d5b6..d0a1bef11c3 100644 --- a/crates/hir-expand/src/files.rs +++ b/crates/hir-expand/src/files.rs @@ -1,11 +1,8 @@ //! Things to wrap other things in file ids. use std::iter; -use base_db::{ - span::{HirFileId, HirFileIdRepr, MacroFileId, SyntaxContextId}, - FileId, FileRange, -}; use either::Either; +use span::{FileId, FileRange, HirFileId, HirFileIdRepr, MacroFileId, SyntaxContextId}; use syntax::{AstNode, SyntaxNode, SyntaxToken, TextRange, TextSize}; use crate::{db, ExpansionInfo, MacroFileIdExt}; @@ -345,7 +342,7 @@ pub fn original_node_file_range_opt( } impl InFile { - pub fn original_ast_node(self, db: &dyn db::ExpandDatabase) -> Option> { + pub fn original_ast_node_rooted(self, db: &dyn db::ExpandDatabase) -> Option> { // This kind of upmapping can only be achieved in attribute expanded files, // as we don't have node inputs otherwise and therefore can't find an `N` node in the input let file_id = match self.file_id.repr() { diff --git a/crates/hir-expand/src/fixup.rs b/crates/hir-expand/src/fixup.rs index 346cd39a767..d241d94b8c4 100644 --- a/crates/hir-expand/src/fixup.rs +++ b/crates/hir-expand/src/fixup.rs @@ -1,23 +1,19 @@ //! To make attribute macros work reliably when typing, we need to take care to //! fix up syntax errors in the code we're passing to them. -use base_db::{ - span::{ErasedFileAstId, SpanAnchor, SpanData}, - FileId, -}; -use la_arena::RawIdx; use rustc_hash::{FxHashMap, FxHashSet}; use smallvec::SmallVec; +use span::{ErasedFileAstId, Span, SpanAnchor, SpanData, FIXUP_ERASED_FILE_AST_ID_MARKER}; use stdx::never; use syntax::{ ast::{self, AstNode, HasLoopBody}, match_ast, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, TextSize, }; use triomphe::Arc; -use tt::{Spacing, Span}; +use tt::Spacing; use crate::{ - span::SpanMapRef, + span_map::SpanMapRef, tt::{Ident, Leaf, Punct, Subtree}, }; @@ -42,28 +38,30 @@ impl SyntaxFixupUndoInfo { pub(crate) const NONE: Self = SyntaxFixupUndoInfo { original: None }; } -// censoring -> just don't convert the node -// replacement -> censor + append -// append -> insert a fake node, here we need to assemble some dummy span that we can figure out how -// to remove later -const FIXUP_DUMMY_FILE: FileId = FileId::from_raw(FileId::MAX_FILE_ID); -const FIXUP_DUMMY_AST_ID: ErasedFileAstId = ErasedFileAstId::from_raw(RawIdx::from_u32(!0)); +// We mark spans with `FIXUP_DUMMY_AST_ID` to indicate that they are fake. +const FIXUP_DUMMY_AST_ID: ErasedFileAstId = FIXUP_ERASED_FILE_AST_ID_MARKER; const FIXUP_DUMMY_RANGE: TextRange = TextRange::empty(TextSize::new(0)); +// If the fake span has this range end, that means that the range start is an index into the +// `original` list in `SyntaxFixupUndoInfo`. const FIXUP_DUMMY_RANGE_END: TextSize = TextSize::new(!0); -pub(crate) fn fixup_syntax(span_map: SpanMapRef<'_>, node: &SyntaxNode) -> SyntaxFixups { +pub(crate) fn fixup_syntax( + span_map: SpanMapRef<'_>, + node: &SyntaxNode, + call_site: Span, +) -> SyntaxFixups { let mut append = FxHashMap::::default(); let mut remove = FxHashSet::::default(); let mut preorder = node.preorder(); let mut original = Vec::new(); let dummy_range = FIXUP_DUMMY_RANGE; - // we use a file id of `FileId(!0)` to signal a fake node, and the text range's start offset as - // the index into the replacement vec but only if the end points to !0 - let dummy_anchor = SpanAnchor { file_id: FIXUP_DUMMY_FILE, ast_id: FIXUP_DUMMY_AST_ID }; - let fake_span = |range| SpanData { - range: dummy_range, - anchor: dummy_anchor, - ctx: span_map.span_for_range(range).ctx, + let fake_span = |range| { + let span = span_map.span_for_range(range); + SpanData { + range: dummy_range, + anchor: SpanAnchor { ast_id: FIXUP_DUMMY_AST_ID, ..span.anchor }, + ctx: span.ctx, + } }; while let Some(event) = preorder.next() { let syntax::WalkEvent::Enter(node) = event else { continue }; @@ -72,15 +70,16 @@ pub(crate) fn fixup_syntax(span_map: SpanMapRef<'_>, node: &SyntaxNode) -> Synta if can_handle_error(&node) && has_error_to_handle(&node) { remove.insert(node.clone().into()); // the node contains an error node, we have to completely replace it by something valid - let original_tree = mbe::syntax_node_to_token_tree(&node, span_map); + let original_tree = mbe::syntax_node_to_token_tree(&node, span_map, call_site); let idx = original.len() as u32; original.push(original_tree); + let span = span_map.span_for_range(node_range); let replacement = Leaf::Ident(Ident { text: "__ra_fixup".into(), span: SpanData { range: TextRange::new(TextSize::new(idx), FIXUP_DUMMY_RANGE_END), - anchor: dummy_anchor, - ctx: span_map.span_for_range(node_range).ctx, + anchor: SpanAnchor { ast_id: FIXUP_DUMMY_AST_ID, ..span.anchor }, + ctx: span.ctx, }, }); append.insert(node.clone().into(), vec![replacement]); @@ -301,9 +300,10 @@ fn has_error_to_handle(node: &SyntaxNode) -> bool { pub(crate) fn reverse_fixups(tt: &mut Subtree, undo_info: &SyntaxFixupUndoInfo) { let Some(undo_info) = undo_info.original.as_deref() else { return }; let undo_info = &**undo_info; + #[allow(deprecated)] if never!( - tt.delimiter.close.anchor.file_id == FIXUP_DUMMY_FILE - || tt.delimiter.open.anchor.file_id == FIXUP_DUMMY_FILE + tt.delimiter.close.anchor.ast_id == FIXUP_DUMMY_AST_ID + || tt.delimiter.open.anchor.ast_id == FIXUP_DUMMY_AST_ID ) { tt.delimiter.close = SpanData::DUMMY; tt.delimiter.open = SpanData::DUMMY; @@ -319,7 +319,7 @@ fn reverse_fixups_(tt: &mut Subtree, undo_info: &[Subtree]) { .filter(|tt| match tt { tt::TokenTree::Leaf(leaf) => { let span = leaf.span(); - let is_real_leaf = span.anchor.file_id != FIXUP_DUMMY_FILE; + let is_real_leaf = span.anchor.ast_id != FIXUP_DUMMY_AST_ID; let is_replaced_node = span.range.end() == FIXUP_DUMMY_RANGE_END; is_real_leaf || is_replaced_node } @@ -327,8 +327,8 @@ fn reverse_fixups_(tt: &mut Subtree, undo_info: &[Subtree]) { }) .flat_map(|tt| match tt { tt::TokenTree::Subtree(mut tt) => { - if tt.delimiter.close.anchor.file_id == FIXUP_DUMMY_FILE - || tt.delimiter.open.anchor.file_id == FIXUP_DUMMY_FILE + if tt.delimiter.close.anchor.ast_id == FIXUP_DUMMY_AST_ID + || tt.delimiter.open.anchor.ast_id == FIXUP_DUMMY_AST_ID { // Even though fixup never creates subtrees with fixup spans, the old proc-macro server // might copy them if the proc-macro asks for it, so we need to filter those out @@ -339,7 +339,7 @@ fn reverse_fixups_(tt: &mut Subtree, undo_info: &[Subtree]) { SmallVec::from_const([tt.into()]) } tt::TokenTree::Leaf(leaf) => { - if leaf.span().anchor.file_id == FIXUP_DUMMY_FILE { + if leaf.span().anchor.ast_id == FIXUP_DUMMY_AST_ID { // we have a fake node here, we need to replace it again with the original let original = undo_info[u32::from(leaf.span().range.start()) as usize].clone(); if original.delimiter.kind == tt::DelimiterKind::Invisible { @@ -360,11 +360,12 @@ fn reverse_fixups_(tt: &mut Subtree, undo_info: &[Subtree]) { mod tests { use base_db::FileId; use expect_test::{expect, Expect}; + use syntax::TextRange; use triomphe::Arc; use crate::{ fixup::reverse_fixups, - span::{RealSpanMap, SpanMap}, + span_map::{RealSpanMap, SpanMap}, tt, }; @@ -397,12 +398,17 @@ fn check_tt_eq(a: &tt::TokenTree, b: &tt::TokenTree) -> bool { fn check(ra_fixture: &str, mut expect: Expect) { let parsed = syntax::SourceFile::parse(ra_fixture); let span_map = SpanMap::RealSpanMap(Arc::new(RealSpanMap::absolute(FileId::from_raw(0)))); - let fixups = super::fixup_syntax(span_map.as_ref(), &parsed.syntax_node()); + let fixups = super::fixup_syntax( + span_map.as_ref(), + &parsed.syntax_node(), + span_map.span_for_range(TextRange::empty(0.into())), + ); let mut tt = mbe::syntax_node_to_token_tree_modified( &parsed.syntax_node(), span_map.as_ref(), fixups.append, fixups.remove, + span_map.span_for_range(TextRange::empty(0.into())), ); let actual = format!("{tt}\n"); @@ -422,8 +428,11 @@ fn check(ra_fixture: &str, mut expect: Expect) { // the fixed-up + reversed version should be equivalent to the original input // modulo token IDs and `Punct`s' spacing. - let original_as_tt = - mbe::syntax_node_to_token_tree(&parsed.syntax_node(), span_map.as_ref()); + let original_as_tt = mbe::syntax_node_to_token_tree( + &parsed.syntax_node(), + span_map.as_ref(), + span_map.span_for_range(TextRange::empty(0.into())), + ); assert!( check_subtree_eq(&tt, &original_as_tt), "different token tree:\n{tt:?}\n\n{original_as_tt:?}" diff --git a/crates/hir-expand/src/hygiene.rs b/crates/hir-expand/src/hygiene.rs index 7b03709aced..57921543c4b 100644 --- a/crates/hir-expand/src/hygiene.rs +++ b/crates/hir-expand/src/hygiene.rs @@ -2,9 +2,12 @@ //! //! Specifically, `ast` + `Hygiene` allows you to create a `Name`. Note that, at //! this moment, this is horribly incomplete and handles only `$crate`. + +// FIXME: Consider moving this into the span crate. + use std::iter; -use base_db::span::{MacroCallId, SpanData, SyntaxContextId}; +use span::{MacroCallId, Span, SyntaxContextId}; use crate::db::ExpandDatabase; @@ -78,37 +81,29 @@ pub enum Transparency { Opaque, } -pub fn span_with_def_site_ctxt( - db: &dyn ExpandDatabase, - span: SpanData, - expn_id: MacroCallId, -) -> SpanData { +pub fn span_with_def_site_ctxt(db: &dyn ExpandDatabase, span: Span, expn_id: MacroCallId) -> Span { span_with_ctxt_from_mark(db, span, expn_id, Transparency::Opaque) } -pub fn span_with_call_site_ctxt( - db: &dyn ExpandDatabase, - span: SpanData, - expn_id: MacroCallId, -) -> SpanData { +pub fn span_with_call_site_ctxt(db: &dyn ExpandDatabase, span: Span, expn_id: MacroCallId) -> Span { span_with_ctxt_from_mark(db, span, expn_id, Transparency::Transparent) } pub fn span_with_mixed_site_ctxt( db: &dyn ExpandDatabase, - span: SpanData, + span: Span, expn_id: MacroCallId, -) -> SpanData { +) -> Span { span_with_ctxt_from_mark(db, span, expn_id, Transparency::SemiTransparent) } fn span_with_ctxt_from_mark( db: &dyn ExpandDatabase, - span: SpanData, + span: Span, expn_id: MacroCallId, transparency: Transparency, -) -> SpanData { - SpanData { ctx: apply_mark(db, SyntaxContextId::ROOT, expn_id, transparency), ..span } +) -> Span { + Span { ctx: apply_mark(db, SyntaxContextId::ROOT, expn_id, transparency), ..span } } pub(super) fn apply_mark( @@ -121,7 +116,7 @@ pub(super) fn apply_mark( return apply_mark_internal(db, ctxt, Some(call_id), transparency); } - let call_site_ctxt = db.lookup_intern_macro_call(call_id).call_site; + let call_site_ctxt = db.lookup_intern_macro_call(call_id).call_site.ctx; let mut call_site_ctxt = if transparency == Transparency::SemiTransparent { call_site_ctxt.normalize_to_macros_2_0(db) } else { @@ -154,15 +149,16 @@ fn apply_mark_internal( transparency: Transparency, ) -> SyntaxContextId { let syntax_context_data = db.lookup_intern_syntax_context(ctxt); - let mut opaque = syntax_context_data.opaque; - let mut opaque_and_semitransparent = syntax_context_data.opaque_and_semitransparent; + let mut opaque = handle_self_ref(ctxt, syntax_context_data.opaque); + let mut opaque_and_semitransparent = + handle_self_ref(ctxt, syntax_context_data.opaque_and_semitransparent); if transparency >= Transparency::Opaque { let parent = opaque; + // Unlike rustc, with salsa we can't prefetch the to be allocated ID to create cycles with + // salsa when interning, so we use a sentinel value that effectively means the current + // syntax context. let new_opaque = SyntaxContextId::SELF_REF; - // But we can't just grab the to be allocated ID either as that would not deduplicate - // things! - // So we need a new salsa store type here ... opaque = db.intern_syntax_context(SyntaxContextData { outer_expn: call_id, outer_transparency: transparency, @@ -174,6 +170,9 @@ fn apply_mark_internal( if transparency >= Transparency::SemiTransparent { let parent = opaque_and_semitransparent; + // Unlike rustc, with salsa we can't prefetch the to be allocated ID to create cycles with + // salsa when interning, so we use a sentinel value that effectively means the current + // syntax context. let new_opaque_and_semitransparent = SyntaxContextId::SELF_REF; opaque_and_semitransparent = db.intern_syntax_context(SyntaxContextData { outer_expn: call_id, diff --git a/crates/hir-expand/src/lib.rs b/crates/hir-expand/src/lib.rs index d7819b315c4..b5197d4c25d 100644 --- a/crates/hir-expand/src/lib.rs +++ b/crates/hir-expand/src/lib.rs @@ -6,20 +6,21 @@ #![warn(rust_2018_idioms, unused_lifetimes)] -pub mod db; pub mod ast_id_map; -pub mod name; -pub mod hygiene; +pub mod attrs; pub mod builtin_attr_macro; pub mod builtin_derive_macro; pub mod builtin_fn_macro; +pub mod db; +pub mod eager; +pub mod files; +pub mod change; +pub mod hygiene; +pub mod mod_path; +pub mod name; pub mod proc_macro; pub mod quote; -pub mod eager; -pub mod mod_path; -pub mod attrs; -pub mod span; -pub mod files; +pub mod span_map; mod fixup; use attrs::collect_attrs; @@ -27,11 +28,9 @@ use std::{fmt, hash::Hash}; -use base_db::{ - span::{HirFileIdRepr, SpanData, SyntaxContextId}, - CrateId, FileId, FileRange, ProcMacroKind, -}; +use base_db::{CrateId, Edition, FileId}; use either::Either; +use span::{FileRange, HirFileIdRepr, Span, SyntaxContextId}; use syntax::{ ast::{self, AstNode}, SyntaxNode, SyntaxToken, TextRange, TextSize, @@ -42,35 +41,86 @@ builtin_attr_macro::BuiltinAttrExpander, builtin_derive_macro::BuiltinDeriveExpander, builtin_fn_macro::{BuiltinFnLikeExpander, EagerExpander}, - db::TokenExpander, + db::{ExpandDatabase, TokenExpander}, fixup::SyntaxFixupUndoInfo, + hygiene::SyntaxContextData, mod_path::ModPath, - proc_macro::ProcMacroExpander, - span::{ExpansionSpanMap, SpanMap}, + proc_macro::{CustomProcMacroExpander, ProcMacroKind}, + span_map::{ExpansionSpanMap, SpanMap}, }; pub use crate::ast_id_map::{AstId, ErasedAstId, ErasedFileAstId}; pub use crate::files::{InFile, InMacroFile, InRealFile}; -pub use base_db::span::{HirFileId, MacroCallId, MacroFileId}; pub use mbe::ValueResult; +pub use span::{HirFileId, MacroCallId, MacroFileId}; -pub type DeclarativeMacro = ::mbe::DeclarativeMacro; +pub type DeclarativeMacro = ::mbe::DeclarativeMacro; pub mod tt { - pub use base_db::span::SpanData; - pub use tt::{DelimiterKind, Spacing, Span, SpanAnchor}; + pub use span::Span; + pub use tt::{DelimiterKind, Spacing}; - pub type Delimiter = ::tt::Delimiter; - pub type DelimSpan = ::tt::DelimSpan; - pub type Subtree = ::tt::Subtree; - pub type Leaf = ::tt::Leaf; - pub type Literal = ::tt::Literal; - pub type Punct = ::tt::Punct; - pub type Ident = ::tt::Ident; - pub type TokenTree = ::tt::TokenTree; + pub type Delimiter = ::tt::Delimiter; + pub type DelimSpan = ::tt::DelimSpan; + pub type Subtree = ::tt::Subtree; + pub type Leaf = ::tt::Leaf; + pub type Literal = ::tt::Literal; + pub type Punct = ::tt::Punct; + pub type Ident = ::tt::Ident; + pub type TokenTree = ::tt::TokenTree; } +#[macro_export] +macro_rules! impl_intern_lookup { + ($db:ident, $id:ident, $loc:ident, $intern:ident, $lookup:ident) => { + impl $crate::Intern for $loc { + type Database<'db> = dyn $db + 'db; + type ID = $id; + fn intern<'db>(self, db: &Self::Database<'db>) -> $id { + db.$intern(self) + } + } + + impl $crate::Lookup for $id { + type Database<'db> = dyn $db + 'db; + type Data = $loc; + fn lookup<'db>(&self, db: &Self::Database<'db>) -> $loc { + db.$lookup(*self) + } + } + }; +} + +// ideally these would be defined in base-db, but the orphan rule doesn't let us +pub trait Intern { + type Database<'db>: ?Sized; + type ID; + fn intern<'db>(self, db: &Self::Database<'db>) -> Self::ID; +} + +pub trait Lookup { + type Database<'db>: ?Sized; + type Data; + fn lookup<'db>(&self, db: &Self::Database<'db>) -> Self::Data; +} + +impl_intern_lookup!( + ExpandDatabase, + MacroCallId, + MacroCallLoc, + intern_macro_call, + lookup_intern_macro_call +); + +impl_intern_lookup!( + ExpandDatabase, + SyntaxContextId, + SyntaxContextData, + intern_syntax_context, + lookup_intern_syntax_context +); + pub type ExpandResult = ValueResult; #[derive(Debug, PartialEq, Eq, Clone, Hash)] @@ -117,18 +167,20 @@ pub struct MacroCallLoc { pub krate: CrateId, /// Some if this is a macro call for an eager macro. Note that this is `None` /// for the eager input macro file. + // FIXME: This seems bad to save in an interned structure eager: Option>, pub kind: MacroCallKind, - pub call_site: SyntaxContextId, + pub call_site: Span, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct MacroDefId { pub krate: CrateId, + pub edition: Edition, pub kind: MacroDefKind, pub local_inner: bool, pub allow_internal_unsafe: bool, - // pub def_site: SyntaxContextId, + pub span: Span, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -138,7 +190,7 @@ pub enum MacroDefKind { BuiltInAttr(BuiltinAttrExpander, AstId), BuiltInDerive(BuiltinDeriveExpander, AstId), BuiltInEager(EagerExpander, AstId), - ProcMacro(ProcMacroExpander, ProcMacroKind, AstId), + ProcMacro(CustomProcMacroExpander, ProcMacroKind, AstId), } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -179,40 +231,39 @@ pub enum MacroCallKind { pub trait HirFileIdExt { /// Returns the original file of this macro call hierarchy. - fn original_file(self, db: &dyn db::ExpandDatabase) -> FileId; + fn original_file(self, db: &dyn ExpandDatabase) -> FileId; /// Returns the original file of this macro call hierarchy while going into the included file if /// one of the calls comes from an `include!``. - fn original_file_respecting_includes(self, db: &dyn db::ExpandDatabase) -> FileId; + fn original_file_respecting_includes(self, db: &dyn ExpandDatabase) -> FileId; /// If this is a macro call, returns the syntax node of the very first macro call this file resides in. - fn original_call_node(self, db: &dyn db::ExpandDatabase) -> Option>; + fn original_call_node(self, db: &dyn ExpandDatabase) -> Option>; /// Return expansion information if it is a macro-expansion file - fn expansion_info(self, db: &dyn db::ExpandDatabase) -> Option; + fn expansion_info(self, db: &dyn ExpandDatabase) -> Option; - fn as_builtin_derive_attr_node(&self, db: &dyn db::ExpandDatabase) - -> Option>; + fn as_builtin_derive_attr_node(&self, db: &dyn ExpandDatabase) -> Option>; } impl HirFileIdExt for HirFileId { - fn original_file(self, db: &dyn db::ExpandDatabase) -> FileId { + fn original_file(self, db: &dyn ExpandDatabase) -> FileId { let mut file_id = self; loop { match file_id.repr() { HirFileIdRepr::FileId(id) => break id, HirFileIdRepr::MacroFile(MacroFileId { macro_call_id }) => { - file_id = db.lookup_intern_macro_call(macro_call_id).kind.file_id(); + file_id = macro_call_id.lookup(db).kind.file_id(); } } } } - fn original_file_respecting_includes(mut self, db: &dyn db::ExpandDatabase) -> FileId { + fn original_file_respecting_includes(mut self, db: &dyn ExpandDatabase) -> FileId { loop { match self.repr() { - base_db::span::HirFileIdRepr::FileId(id) => break id, - base_db::span::HirFileIdRepr::MacroFile(file) => { + HirFileIdRepr::FileId(id) => break id, + HirFileIdRepr::MacroFile(file) => { let loc = db.lookup_intern_macro_call(file.macro_call_id); if loc.def.is_include() { if let Some(eager) = &loc.eager { @@ -231,7 +282,7 @@ fn original_file_respecting_includes(mut self, db: &dyn db::ExpandDatabase) -> F } } - fn original_call_node(self, db: &dyn db::ExpandDatabase) -> Option> { + fn original_call_node(self, db: &dyn ExpandDatabase) -> Option> { let mut call = db.lookup_intern_macro_call(self.macro_file()?.macro_call_id).to_node(db); loop { match call.file_id.repr() { @@ -246,14 +297,11 @@ fn original_call_node(self, db: &dyn db::ExpandDatabase) -> Option Option { + fn expansion_info(self, db: &dyn ExpandDatabase) -> Option { Some(ExpansionInfo::new(db, self.macro_file()?)) } - fn as_builtin_derive_attr_node( - &self, - db: &dyn db::ExpandDatabase, - ) -> Option> { + fn as_builtin_derive_attr_node(&self, db: &dyn ExpandDatabase) -> Option> { let macro_file = self.macro_file()?; let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id); let attr = match loc.def.kind { @@ -265,32 +313,32 @@ fn as_builtin_derive_attr_node( } pub trait MacroFileIdExt { - fn expansion_level(self, db: &dyn db::ExpandDatabase) -> u32; + fn expansion_level(self, db: &dyn ExpandDatabase) -> u32; /// If this is a macro call, returns the syntax node of the call. - fn call_node(self, db: &dyn db::ExpandDatabase) -> InFile; + fn call_node(self, db: &dyn ExpandDatabase) -> InFile; - fn expansion_info(self, db: &dyn db::ExpandDatabase) -> ExpansionInfo; + fn expansion_info(self, db: &dyn ExpandDatabase) -> ExpansionInfo; - fn is_builtin_derive(&self, db: &dyn db::ExpandDatabase) -> bool; - fn is_custom_derive(&self, db: &dyn db::ExpandDatabase) -> bool; + fn is_builtin_derive(&self, db: &dyn ExpandDatabase) -> bool; + fn is_custom_derive(&self, db: &dyn ExpandDatabase) -> bool; /// Return whether this file is an include macro - fn is_include_macro(&self, db: &dyn db::ExpandDatabase) -> bool; + fn is_include_macro(&self, db: &dyn ExpandDatabase) -> bool; - fn is_eager(&self, db: &dyn db::ExpandDatabase) -> bool; + fn is_eager(&self, db: &dyn ExpandDatabase) -> bool; /// Return whether this file is an attr macro - fn is_attr_macro(&self, db: &dyn db::ExpandDatabase) -> bool; + fn is_attr_macro(&self, db: &dyn ExpandDatabase) -> bool; /// Return whether this file is the pseudo expansion of the derive attribute. /// See [`crate::builtin_attr_macro::derive_attr_expand`]. - fn is_derive_attr_pseudo_expansion(&self, db: &dyn db::ExpandDatabase) -> bool; + fn is_derive_attr_pseudo_expansion(&self, db: &dyn ExpandDatabase) -> bool; } impl MacroFileIdExt for MacroFileId { - fn call_node(self, db: &dyn db::ExpandDatabase) -> InFile { + fn call_node(self, db: &dyn ExpandDatabase) -> InFile { db.lookup_intern_macro_call(self.macro_call_id).to_node(db) } - fn expansion_level(self, db: &dyn db::ExpandDatabase) -> u32 { + fn expansion_level(self, db: &dyn ExpandDatabase) -> u32 { let mut level = 0; let mut macro_file = self; loop { @@ -305,39 +353,39 @@ fn expansion_level(self, db: &dyn db::ExpandDatabase) -> u32 { } /// Return expansion information if it is a macro-expansion file - fn expansion_info(self, db: &dyn db::ExpandDatabase) -> ExpansionInfo { + fn expansion_info(self, db: &dyn ExpandDatabase) -> ExpansionInfo { ExpansionInfo::new(db, self) } - fn is_custom_derive(&self, db: &dyn db::ExpandDatabase) -> bool { + fn is_custom_derive(&self, db: &dyn ExpandDatabase) -> bool { matches!( db.lookup_intern_macro_call(self.macro_call_id).def.kind, MacroDefKind::ProcMacro(_, ProcMacroKind::CustomDerive, _) ) } - fn is_builtin_derive(&self, db: &dyn db::ExpandDatabase) -> bool { + fn is_builtin_derive(&self, db: &dyn ExpandDatabase) -> bool { matches!( db.lookup_intern_macro_call(self.macro_call_id).def.kind, MacroDefKind::BuiltInDerive(..) ) } - fn is_include_macro(&self, db: &dyn db::ExpandDatabase) -> bool { + fn is_include_macro(&self, db: &dyn ExpandDatabase) -> bool { db.lookup_intern_macro_call(self.macro_call_id).def.is_include() } - fn is_eager(&self, db: &dyn db::ExpandDatabase) -> bool { + fn is_eager(&self, db: &dyn ExpandDatabase) -> bool { let loc: MacroCallLoc = db.lookup_intern_macro_call(self.macro_call_id); matches!(loc.def.kind, MacroDefKind::BuiltInEager(..)) } - fn is_attr_macro(&self, db: &dyn db::ExpandDatabase) -> bool { + fn is_attr_macro(&self, db: &dyn ExpandDatabase) -> bool { let loc: MacroCallLoc = db.lookup_intern_macro_call(self.macro_call_id); matches!(loc.kind, MacroCallKind::Attr { .. }) } - fn is_derive_attr_pseudo_expansion(&self, db: &dyn db::ExpandDatabase) -> bool { + fn is_derive_attr_pseudo_expansion(&self, db: &dyn ExpandDatabase) -> bool { let loc: MacroCallLoc = db.lookup_intern_macro_call(self.macro_call_id); loc.def.is_attribute_derive() } @@ -346,15 +394,15 @@ fn is_derive_attr_pseudo_expansion(&self, db: &dyn db::ExpandDatabase) -> bool { impl MacroDefId { pub fn as_lazy_macro( self, - db: &dyn db::ExpandDatabase, + db: &dyn ExpandDatabase, krate: CrateId, kind: MacroCallKind, - call_site: SyntaxContextId, + call_site: Span, ) -> MacroCallId { - db.intern_macro_call(MacroCallLoc { def: self, krate, eager: None, kind, call_site }) + MacroCallLoc { def: self, krate, eager: None, kind, call_site }.intern(db) } - pub fn definition_range(&self, db: &dyn db::ExpandDatabase) -> InFile { + pub fn definition_range(&self, db: &dyn ExpandDatabase) -> InFile { match self.kind { MacroDefKind::Declarative(id) | MacroDefKind::BuiltIn(_, id) @@ -419,19 +467,7 @@ pub fn is_include(&self) -> bool { } impl MacroCallLoc { - pub fn span(&self, db: &dyn db::ExpandDatabase) -> SpanData { - let ast_id = self.kind.erased_ast_id(); - let file_id = self.kind.file_id(); - let range = db.ast_id_map(file_id).get_erased(ast_id).text_range(); - match file_id.repr() { - HirFileIdRepr::FileId(file_id) => db.real_span_map(file_id).span_for_range(range), - HirFileIdRepr::MacroFile(m) => { - db.parse_macro_expansion(m).value.1.span_at(range.start()) - } - } - } - - pub fn to_node(&self, db: &dyn db::ExpandDatabase) -> InFile { + pub fn to_node(&self, db: &dyn ExpandDatabase) -> InFile { match self.kind { MacroCallKind::FnLike { ast_id, .. } => { ast_id.with_value(ast_id.to_node(db).syntax().clone()) @@ -498,7 +534,7 @@ pub fn file_id(&self) -> HirFileId { } } - fn erased_ast_id(&self) -> ErasedFileAstId { + pub fn erased_ast_id(&self) -> ErasedFileAstId { match *self { MacroCallKind::FnLike { ast_id: InFile { value, .. }, .. } => value.erase(), MacroCallKind::Derive { ast_id: InFile { value, .. }, .. } => value.erase(), @@ -509,7 +545,7 @@ fn erased_ast_id(&self) -> ErasedFileAstId { /// Returns the original file range that best describes the location of this macro call. /// /// Unlike `MacroCallKind::original_call_range`, this also spans the item of attributes and derives. - pub fn original_call_range_with_body(self, db: &dyn db::ExpandDatabase) -> FileRange { + pub fn original_call_range_with_body(self, db: &dyn ExpandDatabase) -> FileRange { let mut kind = self; let file_id = loop { match kind.file_id().repr() { @@ -534,7 +570,7 @@ pub fn original_call_range_with_body(self, db: &dyn db::ExpandDatabase) -> FileR /// Here we try to roughly match what rustc does to improve diagnostics: fn-like macros /// get the whole `ast::MacroCall`, attribute macros get the attribute's range, and derives /// get only the specific derive that is being referred to. - pub fn original_call_range(self, db: &dyn db::ExpandDatabase) -> FileRange { + pub fn original_call_range(self, db: &dyn ExpandDatabase) -> FileRange { let mut kind = self; let file_id = loop { match kind.file_id().repr() { @@ -573,7 +609,7 @@ pub fn original_call_range(self, db: &dyn db::ExpandDatabase) -> FileRange { FileRange { range, file_id } } - fn arg(&self, db: &dyn db::ExpandDatabase) -> InFile> { + fn arg(&self, db: &dyn ExpandDatabase) -> InFile> { match self { MacroCallKind::FnLike { ast_id, .. } => { ast_id.to_in_file_node(db).map(|it| Some(it.token_tree()?.syntax().clone())) @@ -617,7 +653,7 @@ pub fn call_node(&self) -> Option> { /// Maps the passed in file range down into a macro expansion if it is the input to a macro call. pub fn map_range_down<'a>( &'a self, - span: SpanData, + span: Span, ) -> Option + 'a>> { let tokens = self .exp_map @@ -630,7 +666,7 @@ pub fn map_range_down<'a>( /// Looks up the span at the given offset. pub fn span_for_offset( &self, - db: &dyn db::ExpandDatabase, + db: &dyn ExpandDatabase, offset: TextSize, ) -> (FileRange, SyntaxContextId) { debug_assert!(self.expanded.value.text_range().contains(offset)); @@ -646,12 +682,12 @@ pub fn span_for_offset( /// Maps up the text range out of the expansion hierarchy back into the original file its from. pub fn map_node_range_up( &self, - db: &dyn db::ExpandDatabase, + db: &dyn ExpandDatabase, range: TextRange, ) -> Option<(FileRange, SyntaxContextId)> { debug_assert!(self.expanded.value.text_range().contains_range(range)); let mut spans = self.exp_map.spans_for_range(range); - let SpanData { range, anchor, ctx } = spans.next()?; + let Span { range, anchor, ctx } = spans.next()?; let mut start = range.start(); let mut end = range.end(); @@ -676,7 +712,7 @@ pub fn map_node_range_up( /// Maps up the text range out of the expansion into is macro call. pub fn map_range_up_once( &self, - db: &dyn db::ExpandDatabase, + db: &dyn ExpandDatabase, token: TextRange, ) -> InFile> { debug_assert!(self.expanded.value.text_range().contains_range(token)); @@ -705,7 +741,7 @@ pub fn map_range_up_once( } } - pub fn new(db: &dyn db::ExpandDatabase, macro_file: MacroFileId) -> ExpansionInfo { + pub fn new(db: &dyn ExpandDatabase, macro_file: MacroFileId) -> ExpansionInfo { let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id); let arg_tt = loc.kind.arg(db); @@ -718,7 +754,7 @@ pub fn new(db: &dyn db::ExpandDatabase, macro_file: MacroFileId) -> ExpansionInf let (macro_arg, _) = db.macro_arg(macro_file.macro_call_id).value.unwrap_or_else(|| { ( Arc::new(tt::Subtree { - delimiter: tt::Delimiter::DUMMY_INVISIBLE, + delimiter: tt::Delimiter::invisible_spanned(loc.call_site), token_trees: Vec::new(), }), SyntaxFixupUndoInfo::NONE, diff --git a/crates/hir-expand/src/mod_path.rs b/crates/hir-expand/src/mod_path.rs index 9534b5039f6..30b8c189f52 100644 --- a/crates/hir-expand/src/mod_path.rs +++ b/crates/hir-expand/src/mod_path.rs @@ -9,10 +9,11 @@ db::ExpandDatabase, hygiene::{marks_rev, SyntaxContextExt, Transparency}, name::{known, AsName, Name}, - span::SpanMapRef, + span_map::SpanMapRef, }; -use base_db::{span::SyntaxContextId, CrateId}; +use base_db::CrateId; use smallvec::SmallVec; +use span::SyntaxContextId; use syntax::{ast, AstNode}; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] diff --git a/crates/hir-expand/src/name.rs b/crates/hir-expand/src/name.rs index a321f94cd75..3d8d01e2556 100644 --- a/crates/hir-expand/src/name.rs +++ b/crates/hir-expand/src/name.rs @@ -318,6 +318,10 @@ macro_rules! known_names { new_lower_hex, new_upper_hex, from_usize, + panic_2015, + panic_2021, + unreachable_2015, + unreachable_2021, // Components of known path (type name) Iterator, IntoIterator, @@ -384,6 +388,7 @@ macro_rules! known_names { log_syntax, module_path, option_env, + quote, std_panic, stringify, trace_macros, diff --git a/crates/hir-expand/src/proc_macro.rs b/crates/hir-expand/src/proc_macro.rs index de577796831..25c78fade82 100644 --- a/crates/hir-expand/src/proc_macro.rs +++ b/crates/hir-expand/src/proc_macro.rs @@ -1,18 +1,64 @@ //! Proc Macro Expander stub -use base_db::{span::SpanData, CrateId, ProcMacroExpansionError, ProcMacroId, ProcMacroKind}; +use core::fmt; +use std::{panic::RefUnwindSafe, sync}; + +use base_db::{CrateId, Env}; +use rustc_hash::FxHashMap; +use span::Span; use stdx::never; +use syntax::SmolStr; use crate::{db::ExpandDatabase, tt, ExpandError, ExpandResult}; +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct ProcMacroId(pub u32); + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)] +pub enum ProcMacroKind { + CustomDerive, + FuncLike, + Attr, +} + +pub trait ProcMacroExpander: fmt::Debug + Send + Sync + RefUnwindSafe { + fn expand( + &self, + subtree: &tt::Subtree, + attrs: Option<&tt::Subtree>, + env: &Env, + def_site: Span, + call_site: Span, + mixed_site: Span, + ) -> Result; +} + +#[derive(Debug)] +pub enum ProcMacroExpansionError { + Panic(String), + /// Things like "proc macro server was killed by OOM". + System(String), +} + +pub type ProcMacroLoadResult = Result, String>; + +pub type ProcMacros = FxHashMap; + +#[derive(Debug, Clone)] +pub struct ProcMacro { + pub name: SmolStr, + pub kind: ProcMacroKind, + pub expander: sync::Arc, +} + #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] -pub struct ProcMacroExpander { +pub struct CustomProcMacroExpander { proc_macro_id: ProcMacroId, } const DUMMY_ID: u32 = !0; -impl ProcMacroExpander { +impl CustomProcMacroExpander { pub fn new(proc_macro_id: ProcMacroId) -> Self { assert_ne!(proc_macro_id.0, DUMMY_ID); Self { proc_macro_id } @@ -33,9 +79,9 @@ pub fn expand( calling_crate: CrateId, tt: &tt::Subtree, attr_arg: Option<&tt::Subtree>, - def_site: SpanData, - call_site: SpanData, - mixed_site: SpanData, + def_site: Span, + call_site: Span, + mixed_site: Span, ) -> ExpandResult { match self.proc_macro_id { ProcMacroId(DUMMY_ID) => ExpandResult::new( diff --git a/crates/hir-expand/src/quote.rs b/crates/hir-expand/src/quote.rs index acbde26c8dd..9bdd75f9d22 100644 --- a/crates/hir-expand/src/quote.rs +++ b/crates/hir-expand/src/quote.rs @@ -1,6 +1,8 @@ //! A simplified version of quote-crate like quasi quote macro -use base_db::span::SpanData; +use span::Span; + +use crate::name::Name; // A helper macro quote macro // FIXME: @@ -130,12 +132,12 @@ macro_rules! quote { } pub(crate) trait IntoTt { - fn to_subtree(self, span: SpanData) -> crate::tt::Subtree; + fn to_subtree(self, span: Span) -> crate::tt::Subtree; fn to_tokens(self) -> Vec; } impl IntoTt for Vec { - fn to_subtree(self, span: SpanData) -> crate::tt::Subtree { + fn to_subtree(self, span: Span) -> crate::tt::Subtree { crate::tt::Subtree { delimiter: crate::tt::Delimiter::invisible_spanned(span), token_trees: self, @@ -148,7 +150,7 @@ fn to_tokens(self) -> Vec { } impl IntoTt for crate::tt::Subtree { - fn to_subtree(self, _: SpanData) -> crate::tt::Subtree { + fn to_subtree(self, _: Span) -> crate::tt::Subtree { self } @@ -158,39 +160,39 @@ fn to_tokens(self) -> Vec { } pub(crate) trait ToTokenTree { - fn to_token(self, span: SpanData) -> crate::tt::TokenTree; + fn to_token(self, span: Span) -> crate::tt::TokenTree; } impl ToTokenTree for crate::tt::TokenTree { - fn to_token(self, _: SpanData) -> crate::tt::TokenTree { + fn to_token(self, _: Span) -> crate::tt::TokenTree { self } } impl ToTokenTree for &crate::tt::TokenTree { - fn to_token(self, _: SpanData) -> crate::tt::TokenTree { + fn to_token(self, _: Span) -> crate::tt::TokenTree { self.clone() } } impl ToTokenTree for crate::tt::Subtree { - fn to_token(self, _: SpanData) -> crate::tt::TokenTree { + fn to_token(self, _: Span) -> crate::tt::TokenTree { self.into() } } macro_rules! impl_to_to_tokentrees { - ($($span:ident: $ty:ty => $this:ident $im:block);*) => { + ($($span:ident: $ty:ty => $this:ident $im:block;)*) => { $( impl ToTokenTree for $ty { - fn to_token($this, $span: SpanData) -> crate::tt::TokenTree { + fn to_token($this, $span: Span) -> crate::tt::TokenTree { let leaf: crate::tt::Leaf = $im.into(); leaf.into() } } impl ToTokenTree for &$ty { - fn to_token($this, $span: SpanData) -> crate::tt::TokenTree { + fn to_token($this, $span: Span) -> crate::tt::TokenTree { let leaf: crate::tt::Leaf = $im.clone().into(); leaf.into() } @@ -209,20 +211,19 @@ fn to_token($this, $span: SpanData) -> crate::tt::TokenTree { _span: crate::tt::Ident => self { self }; _span: crate::tt::Punct => self { self }; span: &str => self { crate::tt::Literal{text: format!("\"{}\"", self.escape_default()).into(), span}}; - span: String => self { crate::tt::Literal{text: format!("\"{}\"", self.escape_default()).into(), span}} + span: String => self { crate::tt::Literal{text: format!("\"{}\"", self.escape_default()).into(), span}}; + span: Name => self { crate::tt::Ident{text: self.to_smol_str(), span}}; } #[cfg(test)] mod tests { use crate::tt; - use base_db::{ - span::{SpanAnchor, SyntaxContextId, ROOT_ERASED_FILE_AST_ID}, - FileId, - }; + use base_db::FileId; use expect_test::expect; + use span::{SpanAnchor, SyntaxContextId, ROOT_ERASED_FILE_AST_ID}; use syntax::{TextRange, TextSize}; - const DUMMY: tt::SpanData = tt::SpanData { + const DUMMY: tt::Span = tt::Span { range: TextRange::empty(TextSize::new(0)), anchor: SpanAnchor { file_id: FileId::BOGUS, ast_id: ROOT_ERASED_FILE_AST_ID }, ctx: SyntaxContextId::ROOT, diff --git a/crates/hir-expand/src/span.rs b/crates/hir-expand/src/span.rs deleted file mode 100644 index fe476a40feb..00000000000 --- a/crates/hir-expand/src/span.rs +++ /dev/null @@ -1,124 +0,0 @@ -//! Spanmaps allow turning absolute ranges into relative ranges for incrementality purposes as well -//! as associating spans with text ranges in a particular file. -use base_db::{ - span::{ErasedFileAstId, SpanAnchor, SpanData, SyntaxContextId, ROOT_ERASED_FILE_AST_ID}, - FileId, -}; -use syntax::{ast::HasModuleItem, AstNode, TextRange, TextSize}; -use triomphe::Arc; - -use crate::db::ExpandDatabase; - -pub type ExpansionSpanMap = mbe::SpanMap; - -/// Spanmap for a macro file or a real file -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum SpanMap { - /// Spanmap for a macro file - ExpansionSpanMap(Arc), - /// Spanmap for a real file - RealSpanMap(Arc), -} - -#[derive(Copy, Clone)] -pub enum SpanMapRef<'a> { - /// Spanmap for a macro file - ExpansionSpanMap(&'a ExpansionSpanMap), - /// Spanmap for a real file - RealSpanMap(&'a RealSpanMap), -} - -impl mbe::SpanMapper for SpanMap { - fn span_for(&self, range: TextRange) -> SpanData { - self.span_for_range(range) - } -} -impl mbe::SpanMapper for SpanMapRef<'_> { - fn span_for(&self, range: TextRange) -> SpanData { - self.span_for_range(range) - } -} -impl mbe::SpanMapper for RealSpanMap { - fn span_for(&self, range: TextRange) -> SpanData { - self.span_for_range(range) - } -} - -impl SpanMap { - pub fn span_for_range(&self, range: TextRange) -> SpanData { - match self { - Self::ExpansionSpanMap(span_map) => span_map.span_at(range.start()), - Self::RealSpanMap(span_map) => span_map.span_for_range(range), - } - } - - pub fn as_ref(&self) -> SpanMapRef<'_> { - match self { - Self::ExpansionSpanMap(span_map) => SpanMapRef::ExpansionSpanMap(span_map), - Self::RealSpanMap(span_map) => SpanMapRef::RealSpanMap(span_map), - } - } -} - -impl SpanMapRef<'_> { - pub fn span_for_range(self, range: TextRange) -> SpanData { - match self { - Self::ExpansionSpanMap(span_map) => span_map.span_at(range.start()), - Self::RealSpanMap(span_map) => span_map.span_for_range(range), - } - } -} - -#[derive(PartialEq, Eq, Hash, Debug)] -pub struct RealSpanMap { - file_id: FileId, - /// Invariant: Sorted vec over TextSize - // FIXME: SortedVec<(TextSize, ErasedFileAstId)>? - pairs: Box<[(TextSize, ErasedFileAstId)]>, - end: TextSize, -} - -impl RealSpanMap { - /// Creates a real file span map that returns absolute ranges (relative ranges to the root ast id). - pub fn absolute(file_id: FileId) -> Self { - RealSpanMap { - file_id, - pairs: Box::from([(TextSize::new(0), ROOT_ERASED_FILE_AST_ID)]), - end: TextSize::new(!0), - } - } - - pub fn from_file(db: &dyn ExpandDatabase, file_id: FileId) -> Self { - let mut pairs = vec![(TextSize::new(0), ROOT_ERASED_FILE_AST_ID)]; - let ast_id_map = db.ast_id_map(file_id.into()); - let tree = db.parse(file_id).tree(); - pairs - .extend(tree.items().map(|item| { - (item.syntax().text_range().start(), ast_id_map.ast_id(&item).erase()) - })); - RealSpanMap { - file_id, - pairs: pairs.into_boxed_slice(), - end: tree.syntax().text_range().end(), - } - } - - pub fn span_for_range(&self, range: TextRange) -> SpanData { - assert!( - range.end() <= self.end, - "range {range:?} goes beyond the end of the file {:?}", - self.end - ); - let start = range.start(); - let idx = self - .pairs - .binary_search_by(|&(it, _)| it.cmp(&start).then(std::cmp::Ordering::Less)) - .unwrap_err(); - let (offset, ast_id) = self.pairs[idx - 1]; - SpanData { - range: range - offset, - anchor: SpanAnchor { file_id: self.file_id, ast_id }, - ctx: SyntaxContextId::ROOT, - } - } -} diff --git a/crates/hir-expand/src/span_map.rs b/crates/hir-expand/src/span_map.rs new file mode 100644 index 00000000000..4ec6e657f9e --- /dev/null +++ b/crates/hir-expand/src/span_map.rs @@ -0,0 +1,65 @@ +//! Span maps for real files and macro expansions. +use span::Span; +use syntax::TextRange; +use triomphe::Arc; + +pub use span::RealSpanMap; + +pub type ExpansionSpanMap = span::SpanMap; + +/// Spanmap for a macro file or a real file +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum SpanMap { + /// Spanmap for a macro file + ExpansionSpanMap(Arc), + /// Spanmap for a real file + RealSpanMap(Arc), +} + +#[derive(Copy, Clone)] +pub enum SpanMapRef<'a> { + /// Spanmap for a macro file + ExpansionSpanMap(&'a ExpansionSpanMap), + /// Spanmap for a real file + RealSpanMap(&'a RealSpanMap), +} + +impl mbe::SpanMapper for SpanMap { + fn span_for(&self, range: TextRange) -> Span { + self.span_for_range(range) + } +} +impl mbe::SpanMapper for SpanMapRef<'_> { + fn span_for(&self, range: TextRange) -> Span { + self.span_for_range(range) + } +} + +impl SpanMap { + pub fn span_for_range(&self, range: TextRange) -> Span { + match self { + // FIXME: Is it correct for us to only take the span at the start? This feels somewhat + // wrong. The context will be right, but the range could be considered wrong. See + // https://github.com/rust-lang/rust/issues/23480, we probably want to fetch the span at + // the start and end, then merge them like rustc does in `Span::to + Self::ExpansionSpanMap(span_map) => span_map.span_at(range.start()), + Self::RealSpanMap(span_map) => span_map.span_for_range(range), + } + } + + pub fn as_ref(&self) -> SpanMapRef<'_> { + match self { + Self::ExpansionSpanMap(span_map) => SpanMapRef::ExpansionSpanMap(span_map), + Self::RealSpanMap(span_map) => SpanMapRef::RealSpanMap(span_map), + } + } +} + +impl SpanMapRef<'_> { + pub fn span_for_range(self, range: TextRange) -> Span { + match self { + Self::ExpansionSpanMap(span_map) => span_map.span_at(range.start()), + Self::RealSpanMap(span_map) => span_map.span_for_range(range), + } + } +} diff --git a/crates/hir-ty/Cargo.toml b/crates/hir-ty/Cargo.toml index bbcb76a43ff..1873e7bfe6a 100644 --- a/crates/hir-ty/Cargo.toml +++ b/crates/hir-ty/Cargo.toml @@ -14,14 +14,14 @@ doctest = false [dependencies] cov-mark = "2.0.0-pre.1" itertools.workspace = true -arrayvec = "0.7.2" +arrayvec.workspace = true bitflags.workspace = true smallvec.workspace = true ena = "0.14.0" either.workspace = true oorandom = "11.1.3" tracing.workspace = true -rustc-hash = "1.1.0" +rustc-hash.workspace = true scoped-tls = "1.0.0" chalk-solve = { version = "0.95.0", default-features = false } chalk-ir = "0.95.0" @@ -54,6 +54,10 @@ project-model = { path = "../project-model" } # local deps test-utils.workspace = true +test-fixture.workspace = true [features] in-rust-tree = ["rustc-dependencies/in-rust-tree"] + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/hir-ty/src/consteval/tests.rs b/crates/hir-ty/src/consteval/tests.rs index b395e7f4a81..ac82208708a 100644 --- a/crates/hir-ty/src/consteval/tests.rs +++ b/crates/hir-ty/src/consteval/tests.rs @@ -1,6 +1,7 @@ -use base_db::{fixture::WithFixture, FileId}; +use base_db::FileId; use chalk_ir::Substitution; use hir_def::db::DefDatabase; +use test_fixture::WithFixture; use test_utils::skip_slow_tests; use crate::{ diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 6f724e45874..8053300ad22 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -217,6 +217,10 @@ pub enum InferenceDiagnostic { name: Name, /// Contains the type the field resolves to field_with_same_name: Option, + assoc_func_with_same_name: Option, + }, + UnresolvedAssocItem { + id: ExprOrPatId, }, // FIXME: This should be emitted in body lowering BreakOutsideOfLoop { @@ -1200,6 +1204,12 @@ fn resolve_variant_on_alias( path: &ModPath, ) -> (Ty, Option) { 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()) + } + _ => ty, + }; match remaining { None => { let variant = ty.as_adt().and_then(|(adt_id, _)| match adt_id { diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index a5e77a12d8c..84954ca7e90 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -1575,11 +1575,30 @@ fn infer_method_call( } None => None, }; + + let assoc_func_with_same_name = method_resolution::iterate_method_candidates( + &canonicalized_receiver.value, + self.db, + self.table.trait_env.clone(), + self.get_traits_in_scope().as_ref().left_or_else(|&it| it), + VisibleFromModule::Filter(self.resolver.module()), + Some(method_name), + method_resolution::LookupMode::Path, + |_ty, item, visible| { + if visible { + Some(item) + } else { + None + } + }, + ); + self.result.diagnostics.push(InferenceDiagnostic::UnresolvedMethodCall { expr: tgt_expr, receiver: receiver_ty.clone(), name: method_name.clone(), field_with_same_name: field_with_same_name_exists, + assoc_func_with_same_name, }); ( receiver_ty, diff --git a/crates/hir-ty/src/infer/path.rs b/crates/hir-ty/src/infer/path.rs index 49fb78f67a6..e61a070265a 100644 --- a/crates/hir-ty/src/infer/path.rs +++ b/crates/hir-ty/src/infer/path.rs @@ -340,6 +340,9 @@ fn resolve_ty_assoc_item( }, ); let res = res.or(not_visible); + if res.is_none() { + self.push_diagnostic(InferenceDiagnostic::UnresolvedAssocItem { id }); + } let (item, visible) = res?; let (def, container) = match item { diff --git a/crates/hir-ty/src/layout/tests.rs b/crates/hir-ty/src/layout/tests.rs index 5e3a86c80e3..9937113685c 100644 --- a/crates/hir-ty/src/layout/tests.rs +++ b/crates/hir-ty/src/layout/tests.rs @@ -1,9 +1,9 @@ use std::collections::HashMap; -use base_db::fixture::WithFixture; use chalk_ir::{AdtId, TyKind}; use either::Either; use hir_def::db::DefDatabase; +use test_fixture::WithFixture; use triomphe::Arc; use crate::{ diff --git a/crates/hir-ty/src/mir/eval/tests.rs b/crates/hir-ty/src/mir/eval/tests.rs index ff30dc6dade..b0f929279a5 100644 --- a/crates/hir-ty/src/mir/eval/tests.rs +++ b/crates/hir-ty/src/mir/eval/tests.rs @@ -1,6 +1,7 @@ -use base_db::{fixture::WithFixture, FileId}; +use base_db::FileId; use hir_def::db::DefDatabase; use syntax::{TextRange, TextSize}; +use test_fixture::WithFixture; use crate::{db::HirDatabase, test_db::TestDB, Interner, Substitution}; diff --git a/crates/hir-ty/src/tests.rs b/crates/hir-ty/src/tests.rs index 1446e83fa88..c8cc61cc21b 100644 --- a/crates/hir-ty/src/tests.rs +++ b/crates/hir-ty/src/tests.rs @@ -12,7 +12,7 @@ use std::{collections::HashMap, env}; -use base_db::{fixture::WithFixture, FileRange, SourceDatabaseExt}; +use base_db::{FileRange, SourceDatabaseExt}; use expect_test::Expect; use hir_def::{ body::{Body, BodySourceMap, SyntheticSyntax}, @@ -30,6 +30,7 @@ ast::{self, AstNode, HasName}, SyntaxNode, }; +use test_fixture::WithFixture; use tracing_subscriber::{layer::SubscriberExt, Registry}; use tracing_tree::HierarchicalLayer; use triomphe::Arc; diff --git a/crates/hir-ty/src/tests/incremental.rs b/crates/hir-ty/src/tests/incremental.rs index 28e84e480d7..82d934009f3 100644 --- a/crates/hir-ty/src/tests/incremental.rs +++ b/crates/hir-ty/src/tests/incremental.rs @@ -1,4 +1,5 @@ -use base_db::{fixture::WithFixture, SourceDatabaseExt}; +use base_db::SourceDatabaseExt; +use test_fixture::WithFixture; use triomphe::Arc; use crate::{db::HirDatabase, test_db::TestDB}; diff --git a/crates/hir-ty/src/tests/patterns.rs b/crates/hir-ty/src/tests/patterns.rs index 7234af2d683..548f782f4f2 100644 --- a/crates/hir-ty/src/tests/patterns.rs +++ b/crates/hir-ty/src/tests/patterns.rs @@ -1154,6 +1154,40 @@ fn main() { ); } +#[test] +fn generic_alias_with_qualified_path() { + check_types( + r#" +type Wrap = T; + +struct S; + +trait Schematic { + type Props; +} + +impl Schematic for S { + type Props = X; +} + +enum X { + A { cool: u32, stuff: u32 }, + B, +} + +fn main() { + let wrapped = Wrap::<::Props>::A { + cool: 100, + stuff: 100, + }; + + if let Wrap::<::Props>::A { cool, ..} = &wrapped {} + //^^^^ &u32 +} +"#, + ); +} + #[test] fn type_mismatch_pat_const_reference() { check_no_mismatches( diff --git a/crates/hir/Cargo.toml b/crates/hir/Cargo.toml index 4c1dfbc294e..e4e4bcea610 100644 --- a/crates/hir/Cargo.toml +++ b/crates/hir/Cargo.toml @@ -12,9 +12,9 @@ rust-version.workspace = true doctest = false [dependencies] -rustc-hash = "1.1.0" +rustc-hash.workspace = true either.workspace = true -arrayvec = "0.7.2" +arrayvec.workspace = true itertools.workspace = true smallvec.workspace = true triomphe.workspace = true @@ -33,3 +33,6 @@ tt.workspace = true [features] in-rust-tree = [] + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/hir/src/attrs.rs b/crates/hir/src/attrs.rs index 18585335318..d60d20f5b7e 100644 --- a/crates/hir/src/attrs.rs +++ b/crates/hir/src/attrs.rs @@ -11,7 +11,7 @@ }; use hir_expand::{ name::Name, - span::{RealSpanMap, SpanMapRef}, + span_map::{RealSpanMap, SpanMapRef}, }; use hir_ty::db::HirDatabase; use syntax::{ast, AstNode}; diff --git a/crates/hir/src/db.rs b/crates/hir/src/db.rs index d98e3decd21..7204868464b 100644 --- a/crates/hir/src/db.rs +++ b/crates/hir/src/db.rs @@ -24,6 +24,6 @@ pub use hir_expand::db::{ AstIdMapQuery, DeclMacroExpanderQuery, ExpandDatabase, ExpandDatabaseStorage, ExpandProcMacroQuery, InternMacroCallQuery, InternSyntaxContextQuery, MacroArgQuery, - ParseMacroExpansionErrorQuery, ParseMacroExpansionQuery, RealSpanMapQuery, + ParseMacroExpansionErrorQuery, ParseMacroExpansionQuery, ProcMacrosQuery, RealSpanMapQuery, }; pub use hir_ty::db::*; diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index 1cb36f9b021..bf29a53913d 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -8,7 +8,7 @@ use base_db::CrateId; use cfg::{CfgExpr, CfgOptions}; use either::Either; -use hir_def::path::ModPath; +use hir_def::{path::ModPath, AssocItemId}; use hir_expand::{name::Name, HirFileId, InFile}; use syntax::{ast, AstPtr, SyntaxError, SyntaxNodePtr, TextRange}; @@ -62,6 +62,7 @@ fn from(d: $diag) -> AnyDiagnostic { UndeclaredLabel, UnimplementedBuiltinMacro, UnreachableLabel, + UnresolvedAssocItem, UnresolvedExternCrate, UnresolvedField, UnresolvedImport, @@ -215,6 +216,12 @@ pub struct UnresolvedMethodCall { pub receiver: Type, pub name: Name, pub field_with_same_name: Option, + pub assoc_func_with_same_name: Option, +} + +#[derive(Debug)] +pub struct UnresolvedAssocItem { + pub expr_or_pat: InFile>>>, } #[derive(Debug)] diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index e0230fa3761..09b56e13824 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -37,7 +37,7 @@ use std::{iter, mem::discriminant, ops::ControlFlow}; use arrayvec::ArrayVec; -use base_db::{CrateDisplayName, CrateId, CrateOrigin, Edition, FileId, ProcMacroKind}; +use base_db::{CrateDisplayName, CrateId, CrateOrigin, Edition, FileId}; use either::Either; use hir_def::{ body::{BodyDiagnostic, SyntheticSyntax}, @@ -47,7 +47,6 @@ item_tree::ItemTreeNode, lang_item::LangItemTarget, layout::{self, ReprOptions, TargetDataLayout}, - macro_id_to_def_id, nameres::{self, diagnostics::DefDiagnostic}, path::ImportAlias, per_ns::PerNs, @@ -59,7 +58,7 @@ Lookup, MacroExpander, MacroId, ModuleId, StaticId, StructId, TraitAliasId, TraitId, TypeAliasId, TypeOrConstParamId, TypeParamId, UnionId, }; -use hir_expand::{attrs::collect_attrs, name::name, MacroCallKind}; +use hir_expand::{attrs::collect_attrs, name::name, proc_macro::ProcMacroKind, MacroCallKind}; use hir_ty::{ all_super_traits, autoderef, check_orphan_rules, consteval::{try_const_usize, unknown_const_as_generic, ConstEvalError, ConstExt}, @@ -125,8 +124,10 @@ }, hir_expand::{ attrs::{Attr, AttrId}, + change::Change, hygiene::{marks_rev, SyntaxContextExt}, name::{known, Name}, + proc_macro::ProcMacros, tt, ExpandResult, HirFileId, HirFileIdExt, InFile, InMacroFile, InRealFile, MacroFileId, MacroFileIdExt, }, @@ -146,7 +147,7 @@ hir_def::path::Path, hir_expand::{ name::AsName, - span::{ExpansionSpanMap, RealSpanMap, SpanMap, SpanMapRef}, + span_map::{ExpansionSpanMap, RealSpanMap, SpanMap, SpanMapRef}, }, }; @@ -808,7 +809,7 @@ pub fn find_use_path_prefixed( } fn emit_macro_def_diagnostics(db: &dyn HirDatabase, acc: &mut Vec, m: Macro) { - let id = macro_id_to_def_id(db.upcast(), m.id); + let id = db.macro_def(m.id); if let hir_expand::db::TokenExpander::DeclarativeMacro(expander) = db.macro_expander(id) { if let Some(e) = expander.mac.err() { let Some(ast) = id.ast_id().left() else { @@ -1679,6 +1680,7 @@ pub fn diagnostics(self, db: &dyn HirDatabase, acc: &mut Vec) { receiver, name, field_with_same_name, + assoc_func_with_same_name, } => { let expr = expr_syntax(*expr); @@ -1690,10 +1692,18 @@ pub fn diagnostics(self, db: &dyn HirDatabase, acc: &mut Vec) { field_with_same_name: field_with_same_name .clone() .map(|ty| Type::new(db, DefWithBodyId::from(self), ty)), + assoc_func_with_same_name: assoc_func_with_same_name.clone(), } .into(), ) } + &hir_ty::InferenceDiagnostic::UnresolvedAssocItem { id } => { + let expr_or_pat = match id { + ExprOrPatId::ExprId(expr) => expr_syntax(expr).map(AstPtr::wrap_left), + ExprOrPatId::PatId(pat) => pat_syntax(pat).map(AstPtr::wrap_right), + }; + acc.push(UnresolvedAssocItem { expr_or_pat }.into()) + } &hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { expr, is_break, @@ -2784,9 +2794,13 @@ fn as_assoc_item(self, db: &dyn HirDatabase) -> Option { } } -fn as_assoc_item(db: &dyn HirDatabase, ctor: CTOR, id: ID) -> Option +fn as_assoc_item<'db, ID, DEF, CTOR, AST>( + db: &(dyn HirDatabase + 'db), + ctor: CTOR, + id: ID, +) -> Option where - ID: Lookup>, + ID: Lookup = dyn DefDatabase + 'db, Data = AssocItemLoc>, DEF: From, CTOR: FnOnce(DEF) -> AssocItem, AST: ItemTreeNode, @@ -3520,7 +3534,7 @@ pub fn as_builtin_derive_path(self, db: &dyn HirDatabase) -> Option { let module_id = self.id.lookup(db.upcast()).container; @@ -4652,6 +4666,9 @@ pub fn params( pub fn return_type(&self) -> Type { self.ty.derived(self.sig.ret().clone()) } + pub fn sig(&self) -> &CallableSig { + &self.sig + } } fn closure_source(db: &dyn HirDatabase, closure: ClosureId) -> Option { diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index a03ff220745..fdc604a006f 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -13,7 +13,6 @@ use hir_def::{ hir::Expr, lower::LowerCtx, - macro_id_to_def_id, nameres::MacroSubNs, resolver::{self, HasResolver, Resolver, TypeNs}, type_ref::Mutability, @@ -40,8 +39,8 @@ source_analyzer::{resolve_hir_path, SourceAnalyzer}, Access, Adjust, Adjustment, AutoBorrow, BindingMode, BuiltinAttr, Callable, ConstParam, Crate, DeriveHelper, Field, Function, HasSource, HirFileId, Impl, InFile, Label, LifetimeParam, Local, - Macro, Module, ModuleDef, Name, OverloadedDeref, Path, ScopeDef, ToolModule, Trait, Type, - TypeAlias, TypeParam, VariantDef, + Macro, Module, ModuleDef, Name, OverloadedDeref, Path, ScopeDef, Struct, ToolModule, Trait, + Type, TypeAlias, TypeParam, VariantDef, }; pub enum DescendPreference { @@ -229,6 +228,14 @@ pub fn to_module_def(&self, file: FileId) -> Option { pub fn to_module_defs(&self, file: FileId) -> impl Iterator { self.imp.to_module_def(file) } + + pub fn to_struct_def(&self, s: &ast::Struct) -> Option { + self.imp.to_def(s).map(Struct::from) + } + + pub fn to_impl_def(&self, i: &ast::Impl) -> Option { + self.imp.to_def(i).map(Impl::from) + } } impl<'db> SemanticsImpl<'db> { @@ -341,9 +348,7 @@ pub fn speculative_expand( let macro_call = InFile::new(file_id, actual_macro_call); let krate = resolver.krate(); let macro_call_id = macro_call.as_call_id(self.db.upcast(), krate, |path| { - resolver - .resolve_path_as_macro(self.db.upcast(), &path, Some(MacroSubNs::Bang)) - .map(|(it, _)| macro_id_to_def_id(self.db.upcast(), it)) + resolver.resolve_path_as_macro_def(self.db.upcast(), &path, Some(MacroSubNs::Bang)) })?; hir_expand::db::expand_speculative( self.db.upcast(), @@ -512,8 +517,7 @@ fn resolve_offset_in_format_args( } /// Descend the token into its macro call if it is part of one, returning the tokens in the - /// expansion that it is associated with. If `offset` points into the token's range, it will - /// be considered for the mapping in case of inline format args. + /// expansion that it is associated with. pub fn descend_into_macros( &self, mode: DescendPreference, @@ -674,7 +678,7 @@ fn descend_into_macros_impl( _ => 0, }; // FIXME: here, the attribute's text range is used to strip away all - // entries from the start of the attribute "list" up the the invoking + // entries from the start of the attribute "list" up the invoking // attribute. But in // ``` // mod foo { @@ -850,7 +854,7 @@ pub fn original_range_opt(&self, node: &SyntaxNode) -> Option { /// Attempts to map the node out of macro expanded files. /// This only work for attribute expansions, as other ones do not have nodes as input. pub fn original_ast_node(&self, node: N) -> Option { - self.wrap_node_infile(node).original_ast_node(self.db.upcast()).map( + self.wrap_node_infile(node).original_ast_node_rooted(self.db.upcast()).map( |InRealFile { file_id, value }| { self.cache(find_root(value.syntax()), file_id.into()); value diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs index d05118bbc28..54b4d81012f 100644 --- a/crates/hir/src/source_analyzer.rs +++ b/crates/hir/src/source_analyzer.rs @@ -16,7 +16,6 @@ hir::{BindingId, ExprId, Pat, PatId}, lang_item::LangItem, lower::LowerCtx, - macro_id_to_def_id, nameres::MacroSubNs, path::{ModPath, Path, PathKind}, resolver::{resolver_for_scope, Resolver, TypeNs, ValueNs}, @@ -771,9 +770,7 @@ pub(crate) fn expand( ) -> Option { let krate = self.resolver.krate(); let macro_call_id = macro_call.as_call_id(db.upcast(), krate, |path| { - self.resolver - .resolve_path_as_macro(db.upcast(), &path, Some(MacroSubNs::Bang)) - .map(|(it, _)| macro_id_to_def_id(db.upcast(), it)) + self.resolver.resolve_path_as_macro_def(db.upcast(), &path, Some(MacroSubNs::Bang)) })?; // why the 64? Some(macro_call_id.as_macro_file()).filter(|it| it.expansion_level(db.upcast()) < 64) @@ -1163,9 +1160,40 @@ fn resolve_hir_path_qualifier( resolver: &Resolver, path: &Path, ) -> Option { - resolver - .resolve_path_in_type_ns_fully(db.upcast(), &path) - .map(|ty| match ty { + (|| { + let (ty, unresolved) = match path.type_anchor() { + Some(type_ref) => { + let (_, res) = + TyLoweringContext::new_maybe_unowned(db, resolver, resolver.type_owner()) + .lower_ty_ext(type_ref); + res.map(|ty_ns| (ty_ns, path.segments().first())) + } + None => { + let (ty, remaining_idx, _) = resolver.resolve_path_in_type_ns(db.upcast(), path)?; + match remaining_idx { + Some(remaining_idx) => { + if remaining_idx + 1 == path.segments().len() { + Some((ty, path.segments().last())) + } else { + None + } + } + None => Some((ty, None)), + } + } + }?; + + // If we are in a TypeNs for a Trait, and we have an unresolved name, try to resolve it as a type + // within the trait's associated types. + if let (Some(unresolved), &TypeNs::TraitId(trait_id)) = (&unresolved, &ty) { + if let Some(type_alias_id) = + db.trait_data(trait_id).associated_type_by_name(unresolved.name) + { + return Some(PathResolution::Def(ModuleDefId::from(type_alias_id).into())); + } + } + + let res = match ty { TypeNs::SelfType(it) => PathResolution::SelfType(it.into()), TypeNs::GenericParam(id) => PathResolution::TypeParam(id.into()), TypeNs::AdtSelfType(it) | TypeNs::AdtId(it) => { @@ -1176,11 +1204,28 @@ fn resolve_hir_path_qualifier( TypeNs::BuiltinType(it) => PathResolution::Def(BuiltinType::from(it).into()), TypeNs::TraitId(it) => PathResolution::Def(Trait::from(it).into()), TypeNs::TraitAliasId(it) => PathResolution::Def(TraitAlias::from(it).into()), - }) - .or_else(|| { - resolver - .resolve_module_path_in_items(db.upcast(), path.mod_path()?) - .take_types() - .map(|it| PathResolution::Def(it.into())) - }) + }; + match unresolved { + Some(unresolved) => resolver + .generic_def() + .and_then(|def| { + hir_ty::associated_type_shorthand_candidates( + db, + def, + res.in_type_ns()?, + |name, id| (name == unresolved.name).then_some(id), + ) + }) + .map(TypeAlias::from) + .map(Into::into) + .map(PathResolution::Def), + None => Some(res), + } + })() + .or_else(|| { + resolver + .resolve_module_path_in_items(db.upcast(), path.mod_path()?) + .take_types() + .map(|it| PathResolution::Def(it.into())) + }) } diff --git a/crates/hir/src/symbols.rs b/crates/hir/src/symbols.rs index a2a30edeb03..4da0dfba675 100644 --- a/crates/hir/src/symbols.rs +++ b/crates/hir/src/symbols.rs @@ -2,13 +2,14 @@ use base_db::FileRange; use hir_def::{ + db::DefDatabase, item_scope::ItemInNs, src::{HasChildSource, HasSource}, AdtId, AssocItemId, DefWithBodyId, HasModule, ImplId, Lookup, MacroId, ModuleDefId, ModuleId, TraitId, }; use hir_expand::{HirFileId, InFile}; -use hir_ty::db::HirDatabase; +use hir_ty::{db::HirDatabase, display::HirDisplay}; use syntax::{ast::HasName, AstNode, AstPtr, SmolStr, SyntaxNode, SyntaxNodePtr}; use crate::{Module, ModuleDef, Semantics}; @@ -230,9 +231,12 @@ fn collect_from_body(&mut self, body_id: impl Into) { fn collect_from_impl(&mut self, impl_id: ImplId) { let impl_data = self.db.impl_data(impl_id); - for &assoc_item_id in &impl_data.items { - self.push_assoc_item(assoc_item_id) - } + let impl_name = Some(SmolStr::new(impl_data.self_ty.display(self.db).to_string())); + self.with_container_name(impl_name, |s| { + for &assoc_item_id in &impl_data.items { + s.push_assoc_item(assoc_item_id) + } + }) } fn collect_from_trait(&mut self, trait_id: TraitId) { @@ -274,9 +278,9 @@ fn push_assoc_item(&mut self, assoc_item_id: AssocItemId) { } } - fn push_decl(&mut self, id: L, is_assoc: bool) + fn push_decl<'db, L>(&mut self, id: L, is_assoc: bool) where - L: Lookup + Into, + L: Lookup = dyn DefDatabase + 'db> + Into, ::Data: HasSource, <::Data as HasSource>::Value: HasName, { diff --git a/crates/ide-assists/Cargo.toml b/crates/ide-assists/Cargo.toml index a622ec1a953..4d4bac5fb96 100644 --- a/crates/ide-assists/Cargo.toml +++ b/crates/ide-assists/Cargo.toml @@ -31,7 +31,11 @@ expect-test = "1.4.0" # local deps test-utils.workspace = true +test-fixture.workspace = true sourcegen.workspace = true [features] in-rust-tree = [] + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/ide-assists/src/handlers/auto_import.rs b/crates/ide-assists/src/handlers/auto_import.rs index f508c42c53e..1f785b5d0a8 100644 --- a/crates/ide-assists/src/handlers/auto_import.rs +++ b/crates/ide-assists/src/handlers/auto_import.rs @@ -281,11 +281,8 @@ mod tests { use super::*; use hir::Semantics; - use ide_db::{ - assists::AssistResolveStrategy, - base_db::{fixture::WithFixture, FileRange}, - RootDatabase, - }; + use ide_db::{assists::AssistResolveStrategy, base_db::FileRange, RootDatabase}; + use test_fixture::WithFixture; use crate::tests::{ check_assist, check_assist_by_label, check_assist_not_applicable, check_assist_target, diff --git a/crates/ide-assists/src/handlers/bool_to_enum.rs b/crates/ide-assists/src/handlers/bool_to_enum.rs index 0f2d1057c0a..b7b00e7ed06 100644 --- a/crates/ide-assists/src/handlers/bool_to_enum.rs +++ b/crates/ide-assists/src/handlers/bool_to_enum.rs @@ -16,11 +16,14 @@ edit_in_place::{AttrsOwnerEdit, Indent}, make, HasName, }, - ted, AstNode, NodeOrToken, SyntaxKind, SyntaxNode, T, + AstNode, NodeOrToken, SyntaxKind, SyntaxNode, T, }; use text_edit::TextRange; -use crate::assist_context::{AssistContext, Assists}; +use crate::{ + assist_context::{AssistContext, Assists}, + utils, +}; // Assist: bool_to_enum // @@ -73,7 +76,7 @@ pub(crate) fn bool_to_enum(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option let usages = definition.usages(&ctx.sema).all(); add_enum_def(edit, ctx, &usages, target_node, &target_module); - replace_usages(edit, ctx, &usages, definition, &target_module); + replace_usages(edit, ctx, usages, definition, &target_module); }, ) } @@ -169,8 +172,8 @@ fn replace_bool_expr(edit: &mut SourceChangeBuilder, expr: ast::Expr) { /// Converts an expression of type `bool` to one of the new enum type. fn bool_expr_to_enum_expr(expr: ast::Expr) -> ast::Expr { - let true_expr = make::expr_path(make::path_from_text("Bool::True")).clone_for_update(); - let false_expr = make::expr_path(make::path_from_text("Bool::False")).clone_for_update(); + let true_expr = make::expr_path(make::path_from_text("Bool::True")); + let false_expr = make::expr_path(make::path_from_text("Bool::False")); if let ast::Expr::Literal(literal) = &expr { match literal.kind() { @@ -184,7 +187,6 @@ fn bool_expr_to_enum_expr(expr: ast::Expr) -> ast::Expr { make::tail_only_block_expr(true_expr), Some(ast::ElseBranch::Block(make::tail_only_block_expr(false_expr))), ) - .clone_for_update() } } @@ -192,21 +194,19 @@ fn bool_expr_to_enum_expr(expr: ast::Expr) -> ast::Expr { fn replace_usages( edit: &mut SourceChangeBuilder, ctx: &AssistContext<'_>, - usages: &UsageSearchResult, + usages: UsageSearchResult, target_definition: Definition, target_module: &hir::Module, ) { - for (file_id, references) in usages.iter() { - edit.edit_file(*file_id); + for (file_id, references) in usages { + edit.edit_file(file_id); - let refs_with_imports = - augment_references_with_imports(edit, ctx, references, target_module); + let refs_with_imports = augment_references_with_imports(ctx, references, target_module); refs_with_imports.into_iter().rev().for_each( - |FileReferenceWithImport { range, old_name, new_name, import_data }| { + |FileReferenceWithImport { range, name, import_data }| { // replace the usages in patterns and expressions - if let Some(ident_pat) = old_name.syntax().ancestors().find_map(ast::IdentPat::cast) - { + if let Some(ident_pat) = name.syntax().ancestors().find_map(ast::IdentPat::cast) { cov_mark::hit!(replaces_record_pat_shorthand); let definition = ctx.sema.to_def(&ident_pat).map(Definition::Local); @@ -214,36 +214,35 @@ fn replace_usages( replace_usages( edit, ctx, - &def.usages(&ctx.sema).all(), + def.usages(&ctx.sema).all(), target_definition, target_module, ) } - } else if let Some(initializer) = find_assignment_usage(&new_name) { + } else if let Some(initializer) = find_assignment_usage(&name) { cov_mark::hit!(replaces_assignment); replace_bool_expr(edit, initializer); - } else if let Some((prefix_expr, inner_expr)) = find_negated_usage(&new_name) { + } else if let Some((prefix_expr, inner_expr)) = find_negated_usage(&name) { cov_mark::hit!(replaces_negation); edit.replace( prefix_expr.syntax().text_range(), format!("{} == Bool::False", inner_expr), ); - } else if let Some((record_field, initializer)) = old_name + } else if let Some((record_field, initializer)) = name .as_name_ref() .and_then(ast::RecordExprField::for_field_name) .and_then(|record_field| ctx.sema.resolve_record_field(&record_field)) .and_then(|(got_field, _, _)| { - find_record_expr_usage(&new_name, got_field, target_definition) + find_record_expr_usage(&name, got_field, target_definition) }) { cov_mark::hit!(replaces_record_expr); - let record_field = edit.make_mut(record_field); let enum_expr = bool_expr_to_enum_expr(initializer); - record_field.replace_expr(enum_expr); - } else if let Some(pat) = find_record_pat_field_usage(&old_name) { + utils::replace_record_field_expr(ctx, edit, record_field, enum_expr); + } else if let Some(pat) = find_record_pat_field_usage(&name) { match pat { ast::Pat::IdentPat(ident_pat) => { cov_mark::hit!(replaces_record_pat); @@ -253,7 +252,7 @@ fn replace_usages( replace_usages( edit, ctx, - &def.usages(&ctx.sema).all(), + def.usages(&ctx.sema).all(), target_definition, target_module, ) @@ -270,40 +269,44 @@ fn replace_usages( } _ => (), } - } else if let Some((ty_annotation, initializer)) = find_assoc_const_usage(&new_name) - { + } else if let Some((ty_annotation, initializer)) = find_assoc_const_usage(&name) { edit.replace(ty_annotation.syntax().text_range(), "Bool"); replace_bool_expr(edit, initializer); - } else if let Some(receiver) = find_method_call_expr_usage(&new_name) { + } else if let Some(receiver) = find_method_call_expr_usage(&name) { edit.replace( receiver.syntax().text_range(), format!("({} == Bool::True)", receiver), ); - } else if new_name.syntax().ancestors().find_map(ast::UseTree::cast).is_none() { + } else if name.syntax().ancestors().find_map(ast::UseTree::cast).is_none() { // for any other usage in an expression, replace it with a check that it is the true variant - if let Some((record_field, expr)) = new_name - .as_name_ref() - .and_then(ast::RecordExprField::for_field_name) - .and_then(|record_field| { - record_field.expr().map(|expr| (record_field, expr)) - }) + if let Some((record_field, expr)) = + name.as_name_ref().and_then(ast::RecordExprField::for_field_name).and_then( + |record_field| record_field.expr().map(|expr| (record_field, expr)), + ) { - record_field.replace_expr( + utils::replace_record_field_expr( + ctx, + edit, + record_field, make::expr_bin_op( expr, ast::BinaryOp::CmpOp(ast::CmpOp::Eq { negated: false }), make::expr_path(make::path_from_text("Bool::True")), - ) - .clone_for_update(), + ), ); } else { - edit.replace(range, format!("{} == Bool::True", new_name.text())); + edit.replace(range, format!("{} == Bool::True", name.text())); } } // add imports across modules where needed if let Some((import_scope, path)) = import_data { - insert_use(&import_scope, path, &ctx.config.insert_use); + let scope = match import_scope.clone() { + ImportScope::File(it) => ImportScope::File(edit.make_mut(it)), + ImportScope::Module(it) => ImportScope::Module(edit.make_mut(it)), + ImportScope::Block(it) => ImportScope::Block(edit.make_mut(it)), + }; + insert_use(&scope, path, &ctx.config.insert_use); } }, ) @@ -312,37 +315,31 @@ fn replace_usages( struct FileReferenceWithImport { range: TextRange, - old_name: ast::NameLike, - new_name: ast::NameLike, + name: ast::NameLike, import_data: Option<(ImportScope, ast::Path)>, } fn augment_references_with_imports( - edit: &mut SourceChangeBuilder, ctx: &AssistContext<'_>, - references: &[FileReference], + references: Vec, target_module: &hir::Module, ) -> Vec { let mut visited_modules = FxHashSet::default(); references - .iter() + .into_iter() .filter_map(|FileReference { range, name, .. }| { let name = name.clone().into_name_like()?; - ctx.sema.scope(name.syntax()).map(|scope| (*range, name, scope.module())) + ctx.sema.scope(name.syntax()).map(|scope| (range, name, scope.module())) }) .map(|(range, name, ref_module)| { - let old_name = name.clone(); - let new_name = edit.make_mut(name.clone()); - // if the referenced module is not the same as the target one and has not been seen before, add an import let import_data = if ref_module.nearest_non_block_module(ctx.db()) != *target_module && !visited_modules.contains(&ref_module) { visited_modules.insert(ref_module); - let import_scope = - ImportScope::find_insert_use_container(new_name.syntax(), &ctx.sema); + let import_scope = ImportScope::find_insert_use_container(name.syntax(), &ctx.sema); let path = ref_module .find_use_path_prefixed( ctx.sema.db, @@ -360,7 +357,7 @@ fn augment_references_with_imports( None }; - FileReferenceWithImport { range, old_name, new_name, import_data } + FileReferenceWithImport { range, name, import_data } }) .collect() } @@ -405,13 +402,12 @@ fn find_record_expr_usage( let record_field = ast::RecordExprField::for_field_name(name_ref)?; let initializer = record_field.expr()?; - if let Definition::Field(expected_field) = target_definition { - if got_field != expected_field { - return None; + match target_definition { + Definition::Field(expected_field) if got_field == expected_field => { + Some((record_field, initializer)) } + _ => None, } - - Some((record_field, initializer)) } fn find_record_pat_field_usage(name: &ast::NameLike) -> Option { @@ -466,12 +462,9 @@ fn add_enum_def( let indent = IndentLevel::from_node(&insert_before); enum_def.reindent_to(indent); - ted::insert_all( - ted::Position::before(&edit.make_syntax_mut(insert_before)), - vec![ - enum_def.syntax().clone().into(), - make::tokens::whitespace(&format!("\n\n{indent}")).into(), - ], + edit.insert( + insert_before.text_range().start(), + format!("{}\n\n{indent}", enum_def.syntax().text()), ); } @@ -800,6 +793,78 @@ fn main() { ) } + #[test] + fn local_var_init_struct_usage() { + check_assist( + bool_to_enum, + r#" +struct Foo { + foo: bool, +} + +fn main() { + let $0foo = true; + let s = Foo { foo }; +} +"#, + r#" +struct Foo { + foo: bool, +} + +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +fn main() { + let foo = Bool::True; + let s = Foo { foo: foo == Bool::True }; +} +"#, + ) + } + + #[test] + fn local_var_init_struct_usage_in_macro() { + check_assist( + bool_to_enum, + r#" +struct Struct { + boolean: bool, +} + +macro_rules! identity { + ($body:expr) => { + $body + } +} + +fn new() -> Struct { + let $0boolean = true; + identity![Struct { boolean }] +} +"#, + r#" +struct Struct { + boolean: bool, +} + +macro_rules! identity { + ($body:expr) => { + $body + } +} + +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +fn new() -> Struct { + let boolean = Bool::True; + identity![Struct { boolean: boolean == Bool::True }] +} +"#, + ) + } + #[test] fn field_struct_basic() { cov_mark::check!(replaces_record_expr); @@ -1321,6 +1386,46 @@ fn main() { ) } + #[test] + fn field_in_macro() { + check_assist( + bool_to_enum, + r#" +struct Struct { + $0boolean: bool, +} + +fn boolean(x: Struct) { + let Struct { boolean } = x; +} + +macro_rules! identity { ($body:expr) => { $body } } + +fn new() -> Struct { + identity!(Struct { boolean: true }) +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Bool { True, False } + +struct Struct { + boolean: Bool, +} + +fn boolean(x: Struct) { + let Struct { boolean } = x; +} + +macro_rules! identity { ($body:expr) => { $body } } + +fn new() -> Struct { + identity!(Struct { boolean: Bool::True }) +} +"#, + ) + } + #[test] fn field_non_bool() { cov_mark::check!(not_applicable_non_bool_field); diff --git a/crates/ide-assists/src/handlers/extract_variable.rs b/crates/ide-assists/src/handlers/extract_variable.rs index e7c884dcb70..874b81d3b63 100644 --- a/crates/ide-assists/src/handlers/extract_variable.rs +++ b/crates/ide-assists/src/handlers/extract_variable.rs @@ -1,12 +1,8 @@ use hir::TypeInfo; -use stdx::format_to; use syntax::{ - ast::{self, AstNode}, - NodeOrToken, - SyntaxKind::{ - BLOCK_EXPR, BREAK_EXPR, CLOSURE_EXPR, COMMENT, LOOP_EXPR, MATCH_ARM, MATCH_GUARD, - PATH_EXPR, RETURN_EXPR, - }, + ast::{self, edit::IndentLevel, edit_in_place::Indent, make, AstNode, HasName}, + ted, NodeOrToken, + SyntaxKind::{BLOCK_EXPR, BREAK_EXPR, COMMENT, LOOP_EXPR, MATCH_GUARD, PATH_EXPR, RETURN_EXPR}, SyntaxNode, }; @@ -66,98 +62,140 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op .as_ref() .map_or(false, |it| matches!(it, ast::Expr::FieldExpr(_) | ast::Expr::MethodCallExpr(_))); - let reference_modifier = match ty.filter(|_| needs_adjust) { - Some(receiver_type) if receiver_type.is_mutable_reference() => "&mut ", - Some(receiver_type) if receiver_type.is_reference() => "&", - _ => "", - }; - - let var_modifier = match parent { - Some(ast::Expr::RefExpr(expr)) if expr.mut_token().is_some() => "mut ", - _ => "", - }; - let anchor = Anchor::from(&to_extract)?; - let indent = anchor.syntax().prev_sibling_or_token()?.as_token()?.clone(); let target = to_extract.syntax().text_range(); acc.add( AssistId("extract_variable", AssistKind::RefactorExtract), "Extract into variable", target, move |edit| { - let field_shorthand = - match to_extract.syntax().parent().and_then(ast::RecordExprField::cast) { - Some(field) => field.name_ref(), - None => None, - }; + let field_shorthand = to_extract + .syntax() + .parent() + .and_then(ast::RecordExprField::cast) + .filter(|field| field.name_ref().is_some()); - let mut buf = String::new(); + let (var_name, expr_replace) = match field_shorthand { + Some(field) => (field.to_string(), field.syntax().clone()), + None => ( + suggest_name::for_variable(&to_extract, &ctx.sema), + to_extract.syntax().clone(), + ), + }; - let var_name = match &field_shorthand { - Some(it) => it.to_string(), - None => suggest_name::for_variable(&to_extract, &ctx.sema), + let ident_pat = match parent { + Some(ast::Expr::RefExpr(expr)) if expr.mut_token().is_some() => { + make::ident_pat(false, true, make::name(&var_name)) + } + _ => make::ident_pat(false, false, make::name(&var_name)), }; - let expr_range = match &field_shorthand { - Some(it) => it.syntax().text_range().cover(to_extract.syntax().text_range()), - None => to_extract.syntax().text_range(), + + let to_extract = match ty.as_ref().filter(|_| needs_adjust) { + Some(receiver_type) if receiver_type.is_mutable_reference() => { + make::expr_ref(to_extract, true) + } + Some(receiver_type) if receiver_type.is_reference() => { + make::expr_ref(to_extract, false) + } + _ => to_extract, }; + 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(); + let name_expr = make::expr_path(make::ext::ident_path(&var_name)).clone_for_update(); + match anchor { - Anchor::Before(_) | Anchor::Replace(_) => { - format_to!(buf, "let {var_modifier}{var_name} = {reference_modifier}") - } - Anchor::WrapInBlock(_) => { - format_to!(buf, "{{ let {var_name} = {reference_modifier}") - } - }; - format_to!(buf, "{to_extract}"); + Anchor::Before(place) => { + let prev_ws = place.prev_sibling_or_token().and_then(|it| it.into_token()); + let indent_to = IndentLevel::from_node(&place); + let insert_place = edit.make_syntax_mut(place); - if let Anchor::Replace(stmt) = anchor { - cov_mark::hit!(test_extract_var_expr_stmt); - if stmt.semicolon_token().is_none() { - buf.push(';'); - } - match ctx.config.snippet_cap { - Some(cap) => { - let snip = buf.replace( - &format!("let {var_modifier}{var_name}"), - &format!("let {var_modifier}$0{var_name}"), - ); - edit.replace_snippet(cap, expr_range, snip) - } - None => edit.replace(expr_range, buf), - } - return; - } + // Adjust ws to insert depending on if this is all inline or on separate lines + let trailing_ws = if prev_ws.is_some_and(|it| it.text().starts_with("\n")) { + format!("\n{indent_to}") + } else { + format!(" ") + }; - buf.push(';'); - - // We want to maintain the indent level, - // but we do not want to duplicate possible - // extra newlines in the indent block - let text = indent.text(); - if text.starts_with('\n') { - buf.push('\n'); - buf.push_str(text.trim_start_matches('\n')); - } else { - buf.push_str(text); - } - - edit.replace(expr_range, var_name.clone()); - let offset = anchor.syntax().text_range().start(); - match ctx.config.snippet_cap { - Some(cap) => { - let snip = buf.replace( - &format!("let {var_modifier}{var_name}"), - &format!("let {var_modifier}$0{var_name}"), + ted::insert_all_raw( + ted::Position::before(insert_place), + vec![ + let_stmt.syntax().clone().into(), + make::tokens::whitespace(&trailing_ws).into(), + ], ); - edit.insert_snippet(cap, offset, snip) - } - None => edit.insert(offset, buf), - } - if let Anchor::WrapInBlock(_) = anchor { - edit.insert(anchor.syntax().text_range().end(), " }"); + ted::replace(expr_replace, name_expr.syntax()); + + if let Some(cap) = ctx.config.snippet_cap { + if let Some(ast::Pat::IdentPat(ident_pat)) = let_stmt.pat() { + if let Some(name) = ident_pat.name() { + edit.add_tabstop_before(cap, name); + } + } + } + } + Anchor::Replace(stmt) => { + cov_mark::hit!(test_extract_var_expr_stmt); + + let stmt_replace = edit.make_mut(stmt); + ted::replace(stmt_replace.syntax(), let_stmt.syntax()); + + if let Some(cap) = ctx.config.snippet_cap { + if let Some(ast::Pat::IdentPat(ident_pat)) = let_stmt.pat() { + if let Some(name) = ident_pat.name() { + edit.add_tabstop_before(cap, name); + } + } + } + } + Anchor::WrapInBlock(to_wrap) => { + let indent_to = to_wrap.indent_level(); + + let block = if to_wrap.syntax() == &expr_replace { + // Since `expr_replace` is the same that needs to be wrapped in a block, + // we can just directly replace it with a block + let block = + make::block_expr([let_stmt.into()], Some(name_expr)).clone_for_update(); + ted::replace(expr_replace, block.syntax()); + + block + } else { + // `expr_replace` is a descendant of `to_wrap`, so both steps need to be + // handled seperately, otherwise we wrap the wrong expression + let to_wrap = edit.make_mut(to_wrap); + + // Replace the target expr first so that we don't need to find where + // `expr_replace` is in the wrapped `to_wrap` + ted::replace(expr_replace, name_expr.syntax()); + + // Wrap `to_wrap` in a block + let block = make::block_expr([let_stmt.into()], Some(to_wrap.clone())) + .clone_for_update(); + ted::replace(to_wrap.syntax(), block.syntax()); + + block + }; + + if let Some(cap) = ctx.config.snippet_cap { + // Adding a tabstop to `name` requires finding the let stmt again, since + // the existing `let_stmt` is not actually added to the tree + let pat = block.statements().find_map(|stmt| { + let ast::Stmt::LetStmt(let_stmt) = stmt else { return None }; + let_stmt.pat() + }); + + if let Some(ast::Pat::IdentPat(ident_pat)) = pat { + if let Some(name) = ident_pat.name() { + edit.add_tabstop_before(cap, name); + } + } + } + + // fixup indentation of block + block.indent(indent_to); + } } }, ) @@ -181,7 +219,7 @@ fn valid_target_expr(node: SyntaxNode) -> Option { enum Anchor { Before(SyntaxNode), Replace(ast::ExprStmt), - WrapInBlock(SyntaxNode), + WrapInBlock(ast::Expr), } impl Anchor { @@ -204,16 +242,16 @@ fn from(to_extract: &ast::Expr) -> Option { } if let Some(parent) = node.parent() { - if parent.kind() == CLOSURE_EXPR { + if let Some(parent) = ast::ClosureExpr::cast(parent.clone()) { cov_mark::hit!(test_extract_var_in_closure_no_block); - return Some(Anchor::WrapInBlock(node)); + return parent.body().map(Anchor::WrapInBlock); } - if parent.kind() == MATCH_ARM { + if let Some(parent) = ast::MatchArm::cast(parent) { if node.kind() == MATCH_GUARD { cov_mark::hit!(test_extract_var_in_match_guard); } else { cov_mark::hit!(test_extract_var_in_match_arm_no_block); - return Some(Anchor::WrapInBlock(node)); + return parent.expr().map(Anchor::WrapInBlock); } } } @@ -229,13 +267,6 @@ fn from(to_extract: &ast::Expr) -> Option { None }) } - - fn syntax(&self) -> &SyntaxNode { - match self { - Anchor::Before(it) | Anchor::WrapInBlock(it) => it, - Anchor::Replace(stmt) => stmt.syntax(), - } - } } #[cfg(test)] @@ -502,7 +533,10 @@ fn main() { fn main() { let x = true; let tuple = match x { - true => { let $0var_name = 2 + 2; (var_name, true) } + true => { + let $0var_name = 2 + 2; + (var_name, true) + } _ => (0, false) }; } @@ -579,7 +613,10 @@ fn main() { "#, r#" fn main() { - let lambda = |x: u32| { let $0var_name = x * 2; var_name }; + let lambda = |x: u32| { + let $0var_name = x * 2; + var_name + }; } "#, ); diff --git a/crates/ide-assists/src/handlers/generate_delegate_trait.rs b/crates/ide-assists/src/handlers/generate_delegate_trait.rs index f4fa6a74c6b..0d34502add9 100644 --- a/crates/ide-assists/src/handlers/generate_delegate_trait.rs +++ b/crates/ide-assists/src/handlers/generate_delegate_trait.rs @@ -2,22 +2,25 @@ use crate::{ assist_context::{AssistContext, Assists}, - utils::convert_param_list_to_arg_list, + utils::{convert_param_list_to_arg_list, suggest_name}, }; use either::Either; use hir::{db::HirDatabase, HasVisibility}; use ide_db::{ assists::{AssistId, GroupLabel}, path_transform::PathTransform, + FxHashMap, FxHashSet, }; +use itertools::Itertools; use syntax::{ ast::{ self, edit::{self, AstNodeEdit}, - make, AssocItem, HasGenericParams, HasName, HasVisibility as astHasVisibility, Path, + make, AssocItem, GenericArgList, GenericParamList, HasGenericParams, HasName, + HasTypeBounds, HasVisibility as astHasVisibility, Path, }, ted::{self, Position}, - AstNode, NodeOrToken, SyntaxKind, + AstNode, NodeOrToken, SmolStr, SyntaxKind, }; // Assist: generate_delegate_trait @@ -77,7 +80,7 @@ // } // // fn method_(&mut self) -> bool { -// ::method_( &mut self.a ) +// ::method_(&mut self.a) // } // } // ``` @@ -98,6 +101,7 @@ pub(crate) fn generate_delegate_trait(acc: &mut Assists, ctx: &AssistContext<'_> } /// A utility object that represents a struct's field. +#[derive(Debug)] struct Field { name: String, ty: ast::Type, @@ -111,44 +115,33 @@ pub(crate) fn new( f: Either, ) -> Option { let db = ctx.sema.db; - let name: String; - let range: syntax::TextRange; - let ty: ast::Type; let module = ctx.sema.to_module_def(ctx.file_id())?; - match f { + let (name, range, ty) = match f { Either::Left(f) => { - name = f.name()?.to_string(); - ty = f.ty()?; - range = f.syntax().text_range(); + let name = f.name()?.to_string(); + (name, f.syntax().text_range(), f.ty()?) } Either::Right((f, l)) => { - name = l.fields().position(|it| it == f)?.to_string(); - ty = f.ty()?; - range = f.syntax().text_range(); + let name = l.fields().position(|it| it == f)?.to_string(); + (name, f.syntax().text_range(), f.ty()?) } }; let hir_ty = ctx.sema.resolve_type(&ty)?; let type_impls = hir::Impl::all_for_type(db, hir_ty.clone()); let mut impls = Vec::with_capacity(type_impls.len()); - let type_param = hir_ty.as_type_param(db); - if let Some(tp) = type_param { + if let Some(tp) = hir_ty.as_type_param(db) { for tb in tp.trait_bounds(db) { - impls.push(Delegee::Bound(BoundCase(tb))); + impls.push(Delegee::Bound(tb)); } }; for imp in type_impls { - match imp.trait_(db) { - Some(tr) => { - if tr.is_visible_from(db, module) { - impls.push(Delegee::Impls(ImplCase(tr, imp))) - } - } - None => (), + if let Some(tr) = imp.trait_(db).filter(|tr| tr.is_visible_from(db, module)) { + impls.push(Delegee::Impls(tr, imp)) } } @@ -161,19 +154,17 @@ pub(crate) fn new( /// actually implements the trait and the second way is when the field /// has a bound type parameter. We handle these cases in different ways /// hence the enum. +#[derive(Debug)] enum Delegee { - Bound(BoundCase), - Impls(ImplCase), + Bound(hir::Trait), + Impls(hir::Trait, hir::Impl), } -struct BoundCase(hir::Trait); -struct ImplCase(hir::Trait, hir::Impl); - impl Delegee { fn signature(&self, db: &dyn HirDatabase) -> String { let mut s = String::new(); - let (Delegee::Bound(BoundCase(it)) | Delegee::Impls(ImplCase(it, _))) = self; + 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) { @@ -200,25 +191,33 @@ pub(crate) fn new(s: ast::Struct) -> Option { pub(crate) fn delegate(&self, field: Field, acc: &mut Assists, ctx: &AssistContext<'_>) { let db = ctx.db(); + for delegee in &field.impls { + let trait_ = match delegee { + Delegee::Bound(b) => b, + Delegee::Impls(i, _) => i, + }; + + // Skip trait that has `Self` type, which cannot be delegated + // + // See [`test_self_ty`] + if has_self_type(*trait_, ctx).is_some() { + continue; + } + // FIXME : We can omit already implemented impl_traits // But we don't know what the &[hir::Type] argument should look like. - - // let trait_ = match delegee { - // Delegee::Bound(b) => b.0, - // Delegee::Impls(i) => i.1, - // }; - // if self.hir_ty.impls_trait(db, trait_, &[]) { // continue; // } let signature = delegee.signature(db); + let Some(delegate) = generate_impl(ctx, self, &field.ty, &field.name, delegee) else { continue; }; acc.add_group( - &GroupLabel("Delegate trait impl for field...".to_owned()), + &GroupLabel(format!("Generate delegate impls for field `{}`", field.name)), AssistId("generate_delegate_trait", ide_db::assists::AssistKind::Generate), format!("Generate delegate impl `{}` for `{}`", signature, field.name), field.range, @@ -241,46 +240,40 @@ fn generate_impl( delegee: &Delegee, ) -> Option { let delegate: ast::Impl; - let source: ast::Impl; - let genpar: Option; let db = ctx.db(); - let base_path = make::path_from_text(&field_ty.to_string().as_str()); - let s_path = make::ext::ident_path(&strukt.name.to_string()); + let ast_strukt = &strukt.strukt; + let strukt_ty = make::ty_path(make::ext::ident_path(&strukt.name.to_string())); match delegee { Delegee::Bound(delegee) => { - let in_file = ctx.sema.source(delegee.0.to_owned())?; - let source: ast::Trait = in_file.value; + let bound_def = ctx.sema.source(delegee.to_owned())?.value; + let bound_params = bound_def.generic_param_list(); + let strukt_params = ast_strukt.generic_param_list(); delegate = make::impl_trait( - delegee.0.is_unsafe(db), - None, - None, - strukt.strukt.generic_param_list(), - None, - delegee.0.is_auto(db), - make::ty(&delegee.0.name(db).to_smol_str()), - make::ty_path(s_path), - source.where_clause(), - strukt.strukt.where_clause(), + delegee.is_unsafe(db), + bound_params.clone(), + bound_params.map(|params| params.to_generic_args()), + strukt_params.clone(), + strukt_params.map(|params| params.to_generic_args()), + delegee.is_auto(db), + make::ty(&delegee.name(db).to_smol_str()), + strukt_ty, + bound_def.where_clause(), + ast_strukt.where_clause(), None, ) .clone_for_update(); - genpar = source.generic_param_list(); - let delegate_assoc_items = delegate.get_or_create_assoc_item_list(); - let gen_args: String = - genpar.map_or_else(String::new, |params| params.to_generic_args().to_string()); - // Goto link : https://doc.rust-lang.org/reference/paths.html#qualified-paths let qualified_path_type = make::path_from_text(&format!( - "<{} as {}{}>", - base_path.to_string(), - delegee.0.name(db).to_smol_str(), - gen_args.to_string() + "<{} as {}>", + field_ty.to_string(), + delegate.trait_()?.to_string() )); - match source.assoc_item_list() { + let delegate_assoc_items = delegate.get_or_create_assoc_item_list(); + match bound_def.assoc_item_list() { Some(ai) => { ai.assoc_items() .filter(|item| matches!(item, AssocItem::MacroCall(_)).not()) @@ -295,66 +288,394 @@ fn generate_impl( None => {} }; - let target = ctx.sema.scope(strukt.strukt.syntax())?; - let source = ctx.sema.scope(source.syntax())?; - - let transform = - PathTransform::trait_impl(&target, &source, delegee.0, delegate.clone()); + let target_scope = ctx.sema.scope(strukt.strukt.syntax())?; + let source_scope = ctx.sema.scope(bound_def.syntax())?; + let transform = PathTransform::generic_transformation(&target_scope, &source_scope); transform.apply(&delegate.syntax()); } - Delegee::Impls(delegee) => { - let in_file = ctx.sema.source(delegee.1.to_owned())?; - source = in_file.value; + Delegee::Impls(trait_, old_impl) => { + let old_impl = ctx.sema.source(old_impl.to_owned())?.value; + + // `old_trait_args` contains names of generic args for trait in `old_impl` + let old_trait_args = old_impl + .trait_()? + .generic_arg_list() + .map(|l| l.generic_args().map(|arg| arg.to_string())) + .map_or_else(|| FxHashSet::default(), |it| it.collect()); + + let old_impl_params = old_impl.generic_param_list(); + + // Resolve conflicts with generic parameters in strukt. + // These generics parameters will also be used in `field_ty` and `where_clauses`, + // so we should substitude arguments in them as well. + let (renamed_strukt_params, field_ty, ty_where_clause) = if let Some(strukt_params) = + resolve_conflicts_for_strukt(ast_strukt, old_impl_params.as_ref()) + { + let strukt_args = strukt_params.to_generic_args(); + let field_ty = + subst_name_in_strukt(ctx, ast_strukt, field_ty, strukt_args.clone())?; + let wc = ast_strukt + .where_clause() + .and_then(|wc| Some(subst_name_in_strukt(ctx, ast_strukt, &wc, strukt_args)?)); + (Some(strukt_params), field_ty, wc) + } else { + (None, field_ty.clone_for_update(), None) + }; + + // Some generics used in `field_ty` may be instantiated, so they are no longer + // `generics`. We should remove them from generics params, and use the rest params. + let trait_gen_params = + remove_instantiated_params(&old_impl.self_ty()?, old_impl_params, &old_trait_args); + + // Generate generic args that applied to current impl, this step will also remove unused params + let args_for_impl = + get_args_for_impl(&old_impl, &field_ty, &trait_gen_params, &old_trait_args); + + let mut trait_gen_args = old_impl.trait_()?.generic_arg_list(); + if let Some(arg_list) = &mut trait_gen_args { + *arg_list = arg_list.clone_for_update(); + transform_impl(ctx, ast_strukt, &old_impl, &args_for_impl, &arg_list.syntax())?; + } + + let mut type_gen_args = + renamed_strukt_params.clone().map(|params| params.to_generic_args()); + if let Some(type_args) = &mut type_gen_args { + *type_args = type_args.clone_for_update(); + transform_impl(ctx, ast_strukt, &old_impl, &args_for_impl, &type_args.syntax())?; + } + + let path_type = make::ty(&trait_.name(db).to_smol_str()).clone_for_update(); + transform_impl(ctx, ast_strukt, &old_impl, &args_for_impl, &path_type.syntax())?; + delegate = make::impl_trait( - delegee.0.is_unsafe(db), - source.generic_param_list(), - None, - None, - None, - delegee.0.is_auto(db), - make::ty(&delegee.0.name(db).to_smol_str()), - make::ty_path(s_path), - source.where_clause(), - strukt.strukt.where_clause(), + trait_.is_unsafe(db), + trait_gen_params, + trait_gen_args, + renamed_strukt_params, + type_gen_args, + trait_.is_auto(db), + path_type, + strukt_ty, + old_impl.where_clause().map(|wc| wc.clone_for_update()), + ty_where_clause, None, ) .clone_for_update(); - genpar = source.generic_param_list(); - let delegate_assoc_items = delegate.get_or_create_assoc_item_list(); - let gen_args: String = - genpar.map_or_else(String::new, |params| params.to_generic_args().to_string()); // Goto link : https://doc.rust-lang.org/reference/paths.html#qualified-paths let qualified_path_type = make::path_from_text(&format!( - "<{} as {}{}>", - base_path.to_string().as_str(), - delegee.0.name(db).to_smol_str(), - gen_args.to_string().as_str() + "<{} as {}>", + field_ty.to_string(), + delegate.trait_()?.to_string() )); - source + let delegate_assoc_items = delegate.get_or_create_assoc_item_list(); + for item in old_impl .get_or_create_assoc_item_list() .assoc_items() .filter(|item| matches!(item, AssocItem::MacroCall(_)).not()) - .for_each(|item| { - let assoc = process_assoc_item(item, qualified_path_type.clone(), &field_name); - if let Some(assoc) = assoc { - delegate_assoc_items.add_item(assoc); - } - }); + { + let assoc = process_assoc_item( + transform_assoc_item(ctx, ast_strukt, &old_impl, &args_for_impl, item)?, + qualified_path_type.clone(), + &field_name, + )?; - let target = ctx.sema.scope(strukt.strukt.syntax())?; - let source = ctx.sema.scope(source.syntax())?; + delegate_assoc_items.add_item(assoc); + } - let transform = - PathTransform::trait_impl(&target, &source, delegee.0, delegate.clone()); - transform.apply(&delegate.syntax()); + // Remove unused where clauses + if let Some(wc) = delegate.where_clause() { + remove_useless_where_clauses(&delegate, wc)?; + } } } Some(delegate) } +fn transform_assoc_item( + ctx: &AssistContext<'_>, + strukt: &ast::Struct, + old_impl: &ast::Impl, + args: &Option, + item: AssocItem, +) -> Option { + let source_scope = ctx.sema.scope(&item.syntax()).unwrap(); + let target_scope = ctx.sema.scope(&strukt.syntax())?; + let hir_old_impl = ctx.sema.to_impl_def(old_impl)?; + let item = item.clone_for_update(); + let transform = args.as_ref().map_or_else( + || PathTransform::generic_transformation(&target_scope, &source_scope), + |args| { + PathTransform::impl_transformation( + &target_scope, + &source_scope, + hir_old_impl, + args.clone(), + ) + }, + ); + transform.apply(&item.syntax()); + Some(item) +} + +fn transform_impl( + ctx: &AssistContext<'_>, + strukt: &ast::Struct, + old_impl: &ast::Impl, + args: &Option, + syntax: &syntax::SyntaxNode, +) -> Option<()> { + let source_scope = ctx.sema.scope(&old_impl.self_ty()?.syntax())?; + let target_scope = ctx.sema.scope(&strukt.syntax())?; + let hir_old_impl = ctx.sema.to_impl_def(old_impl)?; + + let transform = args.as_ref().map_or_else( + || PathTransform::generic_transformation(&target_scope, &source_scope), + |args| { + PathTransform::impl_transformation( + &target_scope, + &source_scope, + hir_old_impl, + args.clone(), + ) + }, + ); + + transform.apply(&syntax); + Some(()) +} + +fn remove_instantiated_params( + self_ty: &ast::Type, + old_impl_params: Option, + old_trait_args: &FxHashSet, +) -> Option { + match self_ty { + ast::Type::PathType(path_type) => { + old_impl_params.and_then(|gpl| { + // Remove generic parameters in field_ty (which is instantiated). + let new_gpl = gpl.clone_for_update(); + + path_type + .path()? + .segments() + .filter_map(|seg| seg.generic_arg_list()) + .flat_map(|it| it.generic_args()) + // However, if the param is also used in the trait arguments, it shouldn't be removed. + .filter(|arg| !old_trait_args.contains(&arg.to_string())) + .for_each(|arg| { + new_gpl.remove_generic_arg(&arg); + }); + (new_gpl.generic_params().count() > 0).then_some(new_gpl) + }) + } + _ => old_impl_params, + } +} + +fn remove_useless_where_clauses(delegate: &ast::Impl, wc: ast::WhereClause) -> Option<()> { + let trait_args = + delegate.trait_()?.generic_arg_list().map(|trait_args| trait_args.generic_args()); + let strukt_args = + delegate.self_ty()?.generic_arg_list().map(|strukt_args| strukt_args.generic_args()); + let used_generic_names = match (trait_args, strukt_args) { + (None, None) => None, + (None, Some(y)) => Some(y.map(|arg| arg.to_string()).collect::>()), + (Some(x), None) => Some(x.map(|arg| arg.to_string()).collect::>()), + (Some(x), Some(y)) => Some(x.chain(y).map(|arg| arg.to_string()).collect::>()), + }; + + // Keep clauses that have generic clauses after substitution, and remove the rest + if let Some(used_generic_names) = used_generic_names { + wc.predicates() + .filter(|pred| { + pred.syntax() + .descendants_with_tokens() + .filter_map(|e| e.into_token()) + .find(|e| { + e.kind() == SyntaxKind::IDENT && used_generic_names.contains(&e.to_string()) + }) + .is_none() + }) + .for_each(|pred| { + wc.remove_predicate(pred); + }); + } else { + wc.predicates().for_each(|pred| wc.remove_predicate(pred)); + } + + if wc.predicates().count() == 0 { + // Remove useless whitespaces + wc.syntax() + .siblings_with_tokens(syntax::Direction::Prev) + .skip(1) + .take_while(|node_or_tok| node_or_tok.kind() == SyntaxKind::WHITESPACE) + .for_each(|ws| ted::remove(ws)); + wc.syntax() + .siblings_with_tokens(syntax::Direction::Next) + .skip(1) + .take_while(|node_or_tok| node_or_tok.kind() == SyntaxKind::WHITESPACE) + .for_each(|ws| ted::remove(ws)); + ted::insert( + ted::Position::after(wc.syntax()), + NodeOrToken::Token(make::token(SyntaxKind::WHITESPACE)), + ); + // Remove where clause + ted::remove(wc.syntax()); + } + + Some(()) +} + +fn get_args_for_impl( + old_impl: &ast::Impl, + field_ty: &ast::Type, + trait_params: &Option, + old_trait_args: &FxHashSet, +) -> Option { + // Generate generic args that should be apply to current impl + // + // For exmaple, if we have `impl Trait for B`, and `b: B` in `S`, + // then the generic `A` should be renamed to `T`. While the last two generic args + // doesn't change, it renames . So we apply `` as generic arguments + // to impl. + let old_impl_params = old_impl.generic_param_list(); + let self_ty = old_impl.self_ty(); + + if let (Some(old_impl_gpl), Some(self_ty)) = (old_impl_params, self_ty) { + // Make pair of the arguments of `field_ty` and `old_strukt_args` to + // get the list for substitution + let mut arg_substs = FxHashMap::default(); + + match field_ty { + field_ty @ ast::Type::PathType(_) => { + let field_args = field_ty.generic_arg_list(); + if let (Some(field_args), Some(old_impl_args)) = + (field_args, self_ty.generic_arg_list()) + { + field_args.generic_args().zip(old_impl_args.generic_args()).for_each( + |(field_arg, impl_arg)| { + arg_substs.entry(impl_arg.to_string()).or_insert(field_arg); + }, + ) + } + } + _ => {} + } + + let args = old_impl_gpl + .to_generic_args() + .generic_args() + .map(|old_arg| { + arg_substs.get(&old_arg.to_string()).map_or_else( + || old_arg.clone(), + |replace_with| { + // The old_arg will be replaced, so it becomes redundant + let old_arg_name = old_arg.to_string(); + if old_trait_args.contains(&old_arg_name) { + // However, we should check type bounds and where clauses on old_arg, + // if it has type bound, we should keep the type bound. + // match trait_params.and_then(|params| params.remove_generic_arg(&old_arg)) { + // Some(ast::GenericParam::TypeParam(ty)) => { + // ty.type_bound_list().and_then(|bounds| ) + // } + // _ => {} + // } + if let Some(params) = trait_params { + params.remove_generic_arg(&old_arg); + } + } + replace_with.clone() + }, + ) + }) + .collect_vec(); + args.is_empty().not().then(|| make::generic_arg_list(args.into_iter())) + } else { + None + } +} + +fn subst_name_in_strukt( + ctx: &AssistContext<'_>, + strukt: &ast::Struct, + item: &N, + args: GenericArgList, +) -> Option +where + N: ast::AstNode, +{ + let hir_strukt = ctx.sema.to_struct_def(strukt)?; + let hir_adt = hir::Adt::from(hir_strukt); + + let item = item.clone_for_update(); + let item_scope = ctx.sema.scope(item.syntax())?; + let transform = PathTransform::adt_transformation(&item_scope, &item_scope, hir_adt, args); + transform.apply(&item.syntax()); + Some(item) +} + +fn has_self_type(trait_: hir::Trait, ctx: &AssistContext<'_>) -> Option<()> { + let trait_source = ctx.sema.source(trait_)?.value; + trait_source + .syntax() + .descendants_with_tokens() + .filter_map(|e| e.into_token()) + .find(|e| e.kind() == SyntaxKind::SELF_TYPE_KW) + .map(|_| ()) +} + +fn resolve_conflicts_for_strukt( + strukt: &ast::Struct, + old_impl_params: Option<&ast::GenericParamList>, +) -> Option { + match (strukt.generic_param_list(), old_impl_params) { + (Some(old_strukt_params), Some(old_impl_params)) => { + let params = make::generic_param_list(std::iter::empty()).clone_for_update(); + + for old_strukt_param in old_strukt_params.generic_params() { + // Get old name from `strukt`` + let mut name = SmolStr::from(match &old_strukt_param { + ast::GenericParam::ConstParam(c) => c.name()?.to_string(), + ast::GenericParam::LifetimeParam(l) => { + l.lifetime()?.lifetime_ident_token()?.to_string() + } + ast::GenericParam::TypeParam(t) => t.name()?.to_string(), + }); + + // The new name cannot be conflicted with generics in trait, and the renamed names. + name = suggest_name::for_unique_generic_name(&name, old_impl_params); + name = suggest_name::for_unique_generic_name(&name, ¶ms); + match old_strukt_param { + ast::GenericParam::ConstParam(c) => { + if let Some(const_ty) = c.ty() { + let const_param = make::const_param(make::name(&name), const_ty); + params.add_generic_param(ast::GenericParam::ConstParam( + const_param.clone_for_update(), + )); + } + } + p @ ast::GenericParam::LifetimeParam(_) => { + params.add_generic_param(p.clone_for_update()); + } + ast::GenericParam::TypeParam(t) => { + let type_bounds = t.type_bound_list(); + let type_param = make::type_param(make::name(&name), type_bounds); + params.add_generic_param(ast::GenericParam::TypeParam( + type_param.clone_for_update(), + )); + } + } + } + Some(params) + } + (Some(old_strukt_gpl), None) => Some(old_strukt_gpl), + _ => None, + } +} + fn process_assoc_item( item: syntax::ast::AssocItem, qual_path_ty: ast::Path, @@ -381,10 +702,14 @@ fn const_assoc_item(item: syntax::ast::Const, qual_path_ty: ast::Path) -> Option // >::ConstName; // FIXME : We can't rely on `make::path_qualified` for now but it would be nice to replace the following with it. // make::path_qualified(qual_path_ty, path_expr_segment.as_single_segment().unwrap()); - let qualpath = qualpath(qual_path_ty, path_expr_segment); - let inner = - make::item_const(item.visibility(), item.name()?, item.ty()?, make::expr_path(qualpath)) - .clone_for_update(); + let qualified_path = qualified_path(qual_path_ty, path_expr_segment); + let inner = make::item_const( + item.visibility(), + item.name()?, + item.ty()?, + make::expr_path(qualified_path), + ) + .clone_for_update(); Some(AssocItem::Const(inner)) } @@ -395,7 +720,7 @@ fn func_assoc_item( base_name: &str, ) -> Option { let path_expr_segment = make::path_from_text(item.name()?.to_string().as_str()); - let qualpath = qualpath(qual_path_ty, path_expr_segment); + let qualified_path = qualified_path(qual_path_ty, path_expr_segment); let call = match item.param_list() { // Methods and funcs should be handled separately. @@ -413,31 +738,33 @@ fn func_assoc_item( let param_count = l.params().count(); let args = convert_param_list_to_arg_list(l).clone_for_update(); - + let pos_after_l_paren = Position::after(args.l_paren_token()?); if param_count > 0 { // Add SelfParam and a TOKEN::COMMA - ted::insert_all( - Position::after(args.l_paren_token()?), + ted::insert_all_raw( + pos_after_l_paren, vec![ NodeOrToken::Node(tail_expr_self.syntax().clone_for_update()), - NodeOrToken::Token(make::token(SyntaxKind::WHITESPACE)), NodeOrToken::Token(make::token(SyntaxKind::COMMA)), + NodeOrToken::Token(make::token(SyntaxKind::WHITESPACE)), ], ); } else { // Add SelfParam only - ted::insert( - Position::after(args.l_paren_token()?), + ted::insert_raw( + pos_after_l_paren, NodeOrToken::Node(tail_expr_self.syntax().clone_for_update()), ); } - make::expr_call(make::expr_path(qualpath), args) + make::expr_call(make::expr_path(qualified_path), args) + } + None => { + make::expr_call(make::expr_path(qualified_path), convert_param_list_to_arg_list(l)) } - None => make::expr_call(make::expr_path(qualpath), convert_param_list_to_arg_list(l)), }, None => make::expr_call( - make::expr_path(qualpath), + make::expr_path(qualified_path), convert_param_list_to_arg_list(make::param_list(None, Vec::new())), ), } @@ -463,8 +790,8 @@ fn func_assoc_item( fn ty_assoc_item(item: syntax::ast::TypeAlias, qual_path_ty: Path) -> Option { let path_expr_segment = make::path_from_text(item.name()?.to_string().as_str()); - let qualpath = qualpath(qual_path_ty, path_expr_segment); - let ty = make::ty_path(qualpath); + let qualified_path = qualified_path(qual_path_ty, path_expr_segment); + let ty = make::ty_path(qualified_path); let ident = item.name()?.to_string(); let alias = make::ty_alias( @@ -479,7 +806,7 @@ fn ty_assoc_item(item: syntax::ast::TypeAlias, qual_path_ty: Path) -> Option ast::Path { +fn qualified_path(qual_path_ty: ast::Path, path_expr_seg: ast::Path) -> ast::Path { make::path_from_text(&format!("{}::{}", qual_path_ty.to_string(), path_expr_seg.to_string())) } @@ -510,6 +837,29 @@ impl Trait for Base {} ); } + #[test] + fn test_self_ty() { + // trait whith `Self` type cannot be delegated + // + // See the function `fn f() -> Self`. + // It should be `fn f() -> Base` in `Base`, and `fn f() -> S` in `S` + check_assist_not_applicable( + generate_delegate_trait, + r#" +struct Base(()); +struct S(B$0ase); +trait Trait { + fn f() -> Self; +} +impl Trait for Base { + fn f() -> Base { + Base(()) + } +} +"#, + ); + } + #[test] fn test_struct_struct_basic() { check_assist( @@ -628,7 +978,7 @@ unsafe fn a_func() { } unsafe fn a_method(&self) { - ::a_method( &self.base ) + ::a_method(&self.base) } } @@ -672,6 +1022,245 @@ impl AnotherTrait for S ); } + #[test] + fn test_fields_with_generics() { + check_assist( + generate_delegate_trait, + r#" +struct B { + a: T +} + +trait Trait { + fn f(&self, a: T) -> T; +} + +impl Trait for B { + fn f(&self, a: T1) -> T1 { a } +} + +struct A {} +struct S { + b :$0 B, +} +"#, + r#" +struct B { + a: T +} + +trait Trait { + fn f(&self, a: T) -> T; +} + +impl Trait for B { + fn f(&self, a: T1) -> T1 { a } +} + +struct A {} +struct S { + b : B, +} + +impl Trait for S { + fn f(&self, a: T1) -> T1 { + as Trait>::f(&self.b, a) + } +} +"#, + ); + } + + #[test] + fn test_generics_with_conflict_names() { + check_assist( + generate_delegate_trait, + r#" +struct B { + a: T +} + +trait Trait { + fn f(&self, a: T) -> T; +} + +impl Trait for B { + fn f(&self, a: T) -> T { a } +} + +struct S { + b : $0B, +} +"#, + r#" +struct B { + a: T +} + +trait Trait { + fn f(&self, a: T) -> T; +} + +impl Trait for B { + fn f(&self, a: T) -> T { a } +} + +struct S { + b : B, +} + +impl Trait for S { + fn f(&self, a: T) -> T { + as Trait>::f(&self.b, a) + } +} +"#, + ); + } + + #[test] + fn test_lifetime_with_conflict_names() { + check_assist( + generate_delegate_trait, + r#" +struct B<'a, T> { + a: &'a T +} + +trait Trait { + fn f(&self, a: T) -> T; +} + +impl<'a, T, T0> Trait for B<'a, T0> { + fn f(&self, a: T) -> T { a } +} + +struct S<'a, T> { + b : $0B<'a, T>, +} +"#, + r#" +struct B<'a, T> { + a: &'a T +} + +trait Trait { + fn f(&self, a: T) -> T; +} + +impl<'a, T, T0> Trait for B<'a, T0> { + fn f(&self, a: T) -> T { a } +} + +struct S<'a, T> { + b : B<'a, T>, +} + +impl<'a, T, T1> Trait for S<'a, T1> { + fn f(&self, a: T) -> T { + as Trait>::f(&self.b, a) + } +} +"#, + ); + } + + #[test] + fn test_multiple_generics() { + check_assist( + generate_delegate_trait, + r#" +struct B { + a: T1, + b: T2 +} + +trait Trait { + fn f(&self, a: T) -> T; +} + +impl Trait for B { + fn f(&self, a: T) -> T { a } +} + +struct S { + b :$0 B, +} +"#, + r#" +struct B { + a: T1, + b: T2 +} + +trait Trait { + fn f(&self, a: T) -> T; +} + +impl Trait for B { + fn f(&self, a: T) -> T { a } +} + +struct S { + b : B, +} + +impl Trait for S { + fn f(&self, a: i32) -> i32 { + as Trait>::f(&self.b, a) + } +} +"#, + ); + } + + #[test] + fn test_generics_multiplex() { + check_assist( + generate_delegate_trait, + r#" +struct B { + a: T +} + +trait Trait { + fn f(&self, a: T) -> T; +} + +impl Trait for B { + fn f(&self, a: T) -> T { a } +} + +struct S { + b : $0B, +} +"#, + r#" +struct B { + a: T +} + +trait Trait { + fn f(&self, a: T) -> T; +} + +impl Trait for B { + fn f(&self, a: T) -> T { a } +} + +struct S { + b : B, +} + +impl Trait for S { + fn f(&self, a: T0) -> T0 { + as Trait>::f(&self.b, a) + } +} +"#, + ); + } + #[test] fn test_complex_without_where() { check_assist( @@ -719,7 +1308,7 @@ fn assoc_fn(p: ()) { } fn assoc_method(&self, p: ()) { - >::assoc_method( &self.field , p) + >::assoc_method(&self.field, p) } } @@ -789,7 +1378,7 @@ fn assoc_fn(p: ()) { } fn assoc_method(&self, p: ()) { - >::assoc_method( &self.field , p) + >::assoc_method(&self.field, p) } } @@ -875,7 +1464,7 @@ fn assoc_fn(p: ()) { } fn assoc_method(&self, p: ()) { - >::assoc_method( &self.field , p) + >::assoc_method(&self.field, p) } } @@ -923,6 +1512,132 @@ impl AnotherTrait for S ); } + #[test] + fn test_type_bound_with_generics_1() { + check_assist( + generate_delegate_trait, + r#" +trait AnotherTrait {} +struct B +where + T1: AnotherTrait +{ + a: T, + b: T1 +} + +trait Trait { + fn f(&self, a: T) -> T; +} + +impl Trait for B { + fn f(&self, a: T) -> T { a } +} + +struct S +where + T1: AnotherTrait +{ + b : $0B, +}"#, + r#" +trait AnotherTrait {} +struct B +where + T1: AnotherTrait +{ + a: T, + b: T1 +} + +trait Trait { + fn f(&self, a: T) -> T; +} + +impl Trait for B { + fn f(&self, a: T) -> T { a } +} + +struct S +where + T1: AnotherTrait +{ + b : B, +} + +impl Trait for S +where + T10: AnotherTrait +{ + fn f(&self, a: T) -> T { + as Trait>::f(&self.b, a) + } +}"#, + ); + } + + #[test] + fn test_type_bound_with_generics_2() { + check_assist( + generate_delegate_trait, + r#" +trait AnotherTrait {} +struct B +where + T1: AnotherTrait +{ + b: T1 +} + +trait Trait { + fn f(&self, a: T1) -> T1; +} + +impl Trait for B { + fn f(&self, a: T) -> T { a } +} + +struct S +where + T: AnotherTrait +{ + b : $0B, +}"#, + r#" +trait AnotherTrait {} +struct B +where + T1: AnotherTrait +{ + b: T1 +} + +trait Trait { + fn f(&self, a: T1) -> T1; +} + +impl Trait for B { + fn f(&self, a: T) -> T { a } +} + +struct S +where + T: AnotherTrait +{ + b : B, +} + +impl Trait for S +where + T0: AnotherTrait +{ + fn f(&self, a: T) -> T { + as Trait>::f(&self.b, a) + } +}"#, + ); + } + #[test] fn test_docstring_example() { check_assist( @@ -975,7 +1690,7 @@ fn fn_(arg: u32) -> u32 { } fn method_(&mut self) -> bool { - ::method_( &mut self.a ) + ::method_(&mut self.a) } } "#, @@ -1043,7 +1758,7 @@ fn fn_(arg: u32) -> u32 { } fn method_(&mut self) -> bool { - ::method_( &mut self.a ) + ::method_(&mut self.a) } }"#, ) diff --git a/crates/ide-assists/src/handlers/generate_enum_variant.rs b/crates/ide-assists/src/handlers/generate_enum_variant.rs index 1a1e992e28a..2aaf9d0679d 100644 --- a/crates/ide-assists/src/handlers/generate_enum_variant.rs +++ b/crates/ide-assists/src/handlers/generate_enum_variant.rs @@ -114,7 +114,7 @@ fn add_variant_to_accumulator( parent: PathParent, ) -> Option<()> { let db = ctx.db(); - let InRealFile { file_id, value: enum_node } = adt.source(db)?.original_ast_node(db)?; + let InRealFile { file_id, value: enum_node } = adt.source(db)?.original_ast_node_rooted(db)?; acc.add( AssistId("generate_enum_variant", AssistKind::Generate), diff --git a/crates/ide-assists/src/handlers/generate_function.rs b/crates/ide-assists/src/handlers/generate_function.rs index a113c817f7e..5bb200e84a4 100644 --- a/crates/ide-assists/src/handlers/generate_function.rs +++ b/crates/ide-assists/src/handlers/generate_function.rs @@ -8,20 +8,21 @@ famous_defs::FamousDefs, helpers::is_editable_crate, path_transform::PathTransform, + source_change::SourceChangeBuilder, FxHashMap, FxHashSet, RootDatabase, SnippetCap, }; +use itertools::Itertools; use stdx::to_lower_snake_case; use syntax::{ ast::{ - self, - edit::{AstNodeEdit, IndentLevel}, - make, AstNode, CallExpr, HasArgList, HasGenericParams, HasModuleItem, HasTypeBounds, + self, edit::IndentLevel, edit_in_place::Indent, make, AstNode, CallExpr, HasArgList, + HasGenericParams, HasModuleItem, HasTypeBounds, }, - SyntaxKind, SyntaxNode, TextRange, TextSize, + ted, SyntaxKind, SyntaxNode, TextRange, T, }; use crate::{ - utils::{convert_reference_type, find_struct_impl, render_snippet, Cursor}, + utils::{convert_reference_type, find_struct_impl}, AssistContext, AssistId, AssistKind, Assists, }; @@ -65,7 +66,7 @@ fn gen_fn(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { } let fn_name = &*name_ref.text(); - let TargetInfo { target_module, adt_name, target, file, insert_offset } = + let TargetInfo { target_module, adt_name, target, file } = fn_target_info(ctx, path, &call, fn_name)?; if let Some(m) = target_module { @@ -77,16 +78,7 @@ fn gen_fn(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let function_builder = FunctionBuilder::from_call(ctx, &call, fn_name, target_module, target)?; let text_range = call.syntax().text_range(); let label = format!("Generate {} function", function_builder.fn_name); - add_func_to_accumulator( - acc, - ctx, - text_range, - function_builder, - insert_offset, - file, - adt_name, - label, - ) + add_func_to_accumulator(acc, ctx, text_range, function_builder, file, adt_name, label) } struct TargetInfo { @@ -94,7 +86,6 @@ struct TargetInfo { adt_name: Option, target: GeneratedFunctionTarget, file: FileId, - insert_offset: TextSize, } impl TargetInfo { @@ -103,9 +94,8 @@ fn new( adt_name: Option, target: GeneratedFunctionTarget, file: FileId, - insert_offset: TextSize, ) -> Self { - Self { target_module, adt_name, target, file, insert_offset } + Self { target_module, adt_name, target, file } } } @@ -156,7 +146,7 @@ fn gen_method(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { } let (impl_, file) = get_adt_source(ctx, &adt, fn_name.text().as_str())?; - let (target, insert_offset) = get_method_target(ctx, &impl_, &adt)?; + let target = get_method_target(ctx, &impl_, &adt)?; let function_builder = FunctionBuilder::from_method_call( ctx, @@ -169,16 +159,7 @@ fn gen_method(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let text_range = call.syntax().text_range(); let adt_name = if impl_.is_none() { Some(adt.name(ctx.sema.db)) } else { None }; let label = format!("Generate {} method", function_builder.fn_name); - add_func_to_accumulator( - acc, - ctx, - text_range, - function_builder, - insert_offset, - file, - adt_name, - label, - ) + add_func_to_accumulator(acc, ctx, text_range, function_builder, file, adt_name, label) } fn add_func_to_accumulator( @@ -186,23 +167,28 @@ fn add_func_to_accumulator( ctx: &AssistContext<'_>, text_range: TextRange, function_builder: FunctionBuilder, - insert_offset: TextSize, file: FileId, adt_name: Option, label: String, ) -> Option<()> { - acc.add(AssistId("generate_function", AssistKind::Generate), label, text_range, |builder| { - let indent = IndentLevel::from_node(function_builder.target.syntax()); - let function_template = function_builder.render(adt_name.is_some()); - let mut func = function_template.to_string(ctx.config.snippet_cap); + acc.add(AssistId("generate_function", AssistKind::Generate), label, text_range, |edit| { + edit.edit_file(file); + + let target = function_builder.target.clone(); + let function_template = function_builder.render(); + let func = function_template.to_ast(ctx.config.snippet_cap, edit); + if let Some(name) = adt_name { + let name = make::ty_path(make::ext::ident_path(&format!("{}", name.display(ctx.db())))); + // FIXME: adt may have generic params. - func = format!("\n{indent}impl {} {{\n{func}\n{indent}}}", name.display(ctx.db())); - } - builder.edit_file(file); - match ctx.config.snippet_cap { - Some(cap) => builder.insert_snippet(cap, insert_offset, func), - None => builder.insert(insert_offset, func), + let impl_ = make::impl_(None, None, name, None, None).clone_for_update(); + + func.indent(IndentLevel(1)); + impl_.get_or_create_assoc_item_list().add_item(func.into()); + target.insert_impl_at(edit, impl_); + } else { + target.insert_fn_at(edit, func); } }) } @@ -220,36 +206,33 @@ fn get_adt_source( } struct FunctionTemplate { - leading_ws: String, fn_def: ast::Fn, ret_type: Option, should_focus_return_type: bool, - trailing_ws: String, tail_expr: ast::Expr, } impl FunctionTemplate { - fn to_string(&self, cap: Option) -> String { - let Self { leading_ws, fn_def, ret_type, should_focus_return_type, trailing_ws, tail_expr } = - self; + fn to_ast(&self, cap: Option, edit: &mut SourceChangeBuilder) -> ast::Fn { + let Self { fn_def, ret_type, should_focus_return_type, tail_expr } = self; - let f = match cap { - Some(cap) => { - let cursor = if *should_focus_return_type { - // Focus the return type if there is one - match ret_type { - Some(ret_type) => ret_type.syntax(), - None => tail_expr.syntax(), + if let Some(cap) = cap { + if *should_focus_return_type { + // Focus the return type if there is one + match ret_type { + Some(ret_type) => { + edit.add_placeholder_snippet(cap, ret_type.clone()); } - } else { - tail_expr.syntax() - }; - render_snippet(cap, fn_def.syntax(), Cursor::Replace(cursor)) + None => { + edit.add_placeholder_snippet(cap, tail_expr.clone()); + } + } + } else { + edit.add_placeholder_snippet(cap, tail_expr.clone()); } - None => fn_def.to_string(), - }; + } - format!("{leading_ws}{f}{trailing_ws}") + fn_def.clone() } } @@ -356,7 +339,7 @@ fn from_method_call( }) } - fn render(self, is_method: bool) -> FunctionTemplate { + fn render(self) -> FunctionTemplate { let placeholder_expr = make::ext::expr_todo(); let fn_body = make::block_expr(vec![], Some(placeholder_expr)); let visibility = match self.visibility { @@ -364,7 +347,7 @@ fn render(self, is_method: bool) -> FunctionTemplate { Visibility::Crate => Some(make::visibility_pub_crate()), Visibility::Pub => Some(make::visibility_pub()), }; - let mut fn_def = make::fn_( + let fn_def = make::fn_( visibility, self.fn_name, self.generic_param_list, @@ -375,34 +358,10 @@ fn render(self, is_method: bool) -> FunctionTemplate { self.is_async, false, // FIXME : const and unsafe are not handled yet. false, - ); - let leading_ws; - let trailing_ws; - - match self.target { - GeneratedFunctionTarget::BehindItem(it) => { - let mut indent = IndentLevel::from_node(&it); - if is_method { - indent = indent + 1; - leading_ws = format!("{indent}"); - } else { - leading_ws = format!("\n\n{indent}"); - } - - fn_def = fn_def.indent(indent); - trailing_ws = String::new(); - } - GeneratedFunctionTarget::InEmptyItemList(it) => { - let indent = IndentLevel::from_node(&it); - let leading_indent = indent + 1; - leading_ws = format!("\n{leading_indent}"); - fn_def = fn_def.indent(leading_indent); - trailing_ws = format!("\n{indent}"); - } - }; + ) + .clone_for_update(); FunctionTemplate { - leading_ws, ret_type: fn_def.ret_type(), // PANIC: we guarantee we always create a function body with a tail expr tail_expr: fn_def @@ -412,7 +371,6 @@ fn render(self, is_method: bool) -> FunctionTemplate { .expect("function body should have a tail expression"), should_focus_return_type: self.should_focus_return_type, fn_def, - trailing_ws, } } } @@ -456,40 +414,37 @@ fn get_fn_target_info( target_module: Option, call: CallExpr, ) -> Option { - let (target, file, insert_offset) = get_fn_target(ctx, target_module, call)?; - Some(TargetInfo::new(target_module, None, target, file, insert_offset)) + let (target, file) = get_fn_target(ctx, target_module, call)?; + Some(TargetInfo::new(target_module, None, target, file)) } fn get_fn_target( ctx: &AssistContext<'_>, target_module: Option, call: CallExpr, -) -> Option<(GeneratedFunctionTarget, FileId, TextSize)> { +) -> Option<(GeneratedFunctionTarget, FileId)> { let mut file = ctx.file_id(); let target = match target_module { Some(target_module) => { - let module_source = target_module.definition_source(ctx.db()); - let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, &module_source)?; + let (in_file, target) = next_space_for_fn_in_module(ctx.db(), target_module); file = in_file; target } None => next_space_for_fn_after_call_site(ast::CallableExpr::Call(call))?, }; - Some((target.clone(), file, get_insert_offset(&target))) + Some((target.clone(), file)) } fn get_method_target( ctx: &AssistContext<'_>, impl_: &Option, adt: &Adt, -) -> Option<(GeneratedFunctionTarget, TextSize)> { +) -> Option { let target = match impl_ { - Some(impl_) => next_space_for_fn_in_impl(impl_)?, - None => { - GeneratedFunctionTarget::BehindItem(adt.source(ctx.sema.db)?.syntax().value.clone()) - } + Some(impl_) => GeneratedFunctionTarget::InImpl(impl_.clone()), + None => GeneratedFunctionTarget::AfterItem(adt.source(ctx.sema.db)?.syntax().value.clone()), }; - Some((target.clone(), get_insert_offset(&target))) + Some(target) } fn assoc_fn_target_info( @@ -505,36 +460,120 @@ fn assoc_fn_target_info( return None; } let (impl_, file) = get_adt_source(ctx, &adt, fn_name)?; - let (target, insert_offset) = get_method_target(ctx, &impl_, &adt)?; + let target = get_method_target(ctx, &impl_, &adt)?; let adt_name = if impl_.is_none() { Some(adt.name(ctx.sema.db)) } else { None }; - Some(TargetInfo::new(target_module, adt_name, target, file, insert_offset)) -} - -fn get_insert_offset(target: &GeneratedFunctionTarget) -> TextSize { - match target { - GeneratedFunctionTarget::BehindItem(it) => it.text_range().end(), - GeneratedFunctionTarget::InEmptyItemList(it) => it.text_range().start() + TextSize::of('{'), - } + Some(TargetInfo::new(target_module, adt_name, target, file)) } #[derive(Clone)] enum GeneratedFunctionTarget { - BehindItem(SyntaxNode), + AfterItem(SyntaxNode), InEmptyItemList(SyntaxNode), + InImpl(ast::Impl), } impl GeneratedFunctionTarget { fn syntax(&self) -> &SyntaxNode { match self { - GeneratedFunctionTarget::BehindItem(it) => it, + GeneratedFunctionTarget::AfterItem(it) => it, GeneratedFunctionTarget::InEmptyItemList(it) => it, + GeneratedFunctionTarget::InImpl(it) => it.syntax(), } } fn parent(&self) -> SyntaxNode { match self { - GeneratedFunctionTarget::BehindItem(it) => it.parent().expect("item without parent"), + GeneratedFunctionTarget::AfterItem(it) => it.parent().expect("item without parent"), GeneratedFunctionTarget::InEmptyItemList(it) => it.clone(), + GeneratedFunctionTarget::InImpl(it) => it.syntax().clone(), + } + } + + fn insert_impl_at(&self, edit: &mut SourceChangeBuilder, impl_: ast::Impl) { + match self { + GeneratedFunctionTarget::AfterItem(item) => { + let item = edit.make_syntax_mut(item.clone()); + let position = if item.parent().is_some() { + ted::Position::after(&item) + } else { + ted::Position::first_child_of(&item) + }; + + let indent = IndentLevel::from_node(&item); + let leading_ws = make::tokens::whitespace(&format!("\n{indent}")); + impl_.indent(indent); + + ted::insert_all(position, vec![leading_ws.into(), impl_.syntax().clone().into()]); + } + GeneratedFunctionTarget::InEmptyItemList(item_list) => { + let item_list = edit.make_syntax_mut(item_list.clone()); + let insert_after = + item_list.children_with_tokens().find_or_first(|child| child.kind() == T!['{']); + let position = match insert_after { + Some(child) => ted::Position::after(child), + None => ted::Position::first_child_of(&item_list), + }; + + let indent = IndentLevel::from_node(&item_list); + let leading_indent = indent + 1; + let leading_ws = make::tokens::whitespace(&format!("\n{leading_indent}")); + impl_.indent(indent); + + ted::insert_all(position, vec![leading_ws.into(), impl_.syntax().clone().into()]); + } + GeneratedFunctionTarget::InImpl(_) => { + unreachable!("can't insert an impl inside an impl") + } + } + } + + fn insert_fn_at(&self, edit: &mut SourceChangeBuilder, func: ast::Fn) { + match self { + GeneratedFunctionTarget::AfterItem(item) => { + let item = edit.make_syntax_mut(item.clone()); + let position = if item.parent().is_some() { + ted::Position::after(&item) + } else { + ted::Position::first_child_of(&item) + }; + + let indent = IndentLevel::from_node(&item); + let leading_ws = make::tokens::whitespace(&format!("\n\n{indent}")); + func.indent(indent); + + ted::insert_all_raw( + position, + vec![leading_ws.into(), func.syntax().clone().into()], + ); + } + GeneratedFunctionTarget::InEmptyItemList(item_list) => { + let item_list = edit.make_syntax_mut(item_list.clone()); + let insert_after = + item_list.children_with_tokens().find_or_first(|child| child.kind() == T!['{']); + let position = match insert_after { + Some(child) => ted::Position::after(child), + None => ted::Position::first_child_of(&item_list), + }; + + let indent = IndentLevel::from_node(&item_list); + let leading_indent = indent + 1; + let leading_ws = make::tokens::whitespace(&format!("\n{leading_indent}")); + let trailing_ws = make::tokens::whitespace(&format!("\n{indent}")); + func.indent(leading_indent); + + ted::insert_all( + position, + vec![leading_ws.into(), func.syntax().clone().into(), trailing_ws.into()], + ); + } + GeneratedFunctionTarget::InImpl(impl_) => { + let impl_ = edit.make_mut(impl_.clone()); + + let leading_indent = impl_.indent_level() + 1; + func.indent(leading_indent); + + impl_.get_or_create_assoc_item_list().add_item(func.into()); + } } } } @@ -1026,43 +1065,40 @@ fn next_space_for_fn_after_call_site(expr: ast::CallableExpr) -> Option, -) -> Option<(FileId, GeneratedFunctionTarget)> { - let file = module_source.file_id.original_file(db); + db: &dyn hir::db::HirDatabase, + target_module: hir::Module, +) -> (FileId, GeneratedFunctionTarget) { + let module_source = target_module.definition_source(db); + let file = module_source.file_id.original_file(db.upcast()); let assist_item = match &module_source.value { hir::ModuleSource::SourceFile(it) => match it.items().last() { - Some(last_item) => GeneratedFunctionTarget::BehindItem(last_item.syntax().clone()), - None => GeneratedFunctionTarget::BehindItem(it.syntax().clone()), + Some(last_item) => GeneratedFunctionTarget::AfterItem(last_item.syntax().clone()), + None => GeneratedFunctionTarget::AfterItem(it.syntax().clone()), }, hir::ModuleSource::Module(it) => match it.item_list().and_then(|it| it.items().last()) { - Some(last_item) => GeneratedFunctionTarget::BehindItem(last_item.syntax().clone()), - None => GeneratedFunctionTarget::InEmptyItemList(it.item_list()?.syntax().clone()), + Some(last_item) => GeneratedFunctionTarget::AfterItem(last_item.syntax().clone()), + None => { + let item_list = + it.item_list().expect("module definition source should have an item list"); + GeneratedFunctionTarget::InEmptyItemList(item_list.syntax().clone()) + } }, hir::ModuleSource::BlockExpr(it) => { if let Some(last_item) = it.statements().take_while(|stmt| matches!(stmt, ast::Stmt::Item(_))).last() { - GeneratedFunctionTarget::BehindItem(last_item.syntax().clone()) + GeneratedFunctionTarget::AfterItem(last_item.syntax().clone()) } else { GeneratedFunctionTarget::InEmptyItemList(it.syntax().clone()) } } }; - Some((file, assist_item)) -} -fn next_space_for_fn_in_impl(impl_: &ast::Impl) -> Option { - let assoc_item_list = impl_.assoc_item_list()?; - if let Some(last_item) = assoc_item_list.assoc_items().last() { - Some(GeneratedFunctionTarget::BehindItem(last_item.syntax().clone())) - } else { - Some(GeneratedFunctionTarget::InEmptyItemList(assoc_item_list.syntax().clone())) - } + (file, assist_item) } #[derive(Clone, Copy)] diff --git a/crates/ide-assists/src/handlers/inline_call.rs b/crates/ide-assists/src/handlers/inline_call.rs index 5b9cc5f66cd..2eb7089b7c3 100644 --- a/crates/ide-assists/src/handlers/inline_call.rs +++ b/crates/ide-assists/src/handlers/inline_call.rs @@ -315,17 +315,6 @@ fn inline( } else { fn_body.clone_for_update() }; - if let Some(imp) = body.syntax().ancestors().find_map(ast::Impl::cast) { - if !node.syntax().ancestors().any(|anc| &anc == imp.syntax()) { - if let Some(t) = imp.self_ty() { - body.syntax() - .descendants_with_tokens() - .filter_map(NodeOrToken::into_token) - .filter(|tok| tok.kind() == SyntaxKind::SELF_TYPE_KW) - .for_each(|tok| ted::replace(tok, t.syntax())); - } - } - } let usages_for_locals = |local| { Definition::Local(local) .usages(sema) @@ -381,6 +370,27 @@ fn inline( } } + // We should place the following code after last usage of `usages_for_locals` + // because `ted::replace` will change the offset in syntax tree, which makes + // `FileReference` incorrect + if let Some(imp) = + sema.ancestors_with_macros(fn_body.syntax().clone()).find_map(ast::Impl::cast) + { + if !node.syntax().ancestors().any(|anc| &anc == imp.syntax()) { + if let Some(t) = imp.self_ty() { + while let Some(self_tok) = body + .syntax() + .descendants_with_tokens() + .filter_map(NodeOrToken::into_token) + .find(|tok| tok.kind() == SyntaxKind::SELF_TYPE_KW) + { + let replace_with = t.clone_subtree().syntax().clone_for_update(); + ted::replace(self_tok, replace_with); + } + } + } + } + let mut func_let_vars: BTreeSet = BTreeSet::new(); // grab all of the local variable declarations in the function @@ -1510,4 +1520,106 @@ fn main() { "#, ); } + + #[test] + fn inline_call_with_multiple_self_types_eq() { + check_assist( + inline_call, + r#" +#[derive(PartialEq, Eq)] +enum Enum { + A, + B, +} + +impl Enum { + fn a_or_b_eq(&self) -> bool { + self == &Self::A || self == &Self::B + } +} + +fn a() -> bool { + Enum::A.$0a_or_b_eq() +} +"#, + r#" +#[derive(PartialEq, Eq)] +enum Enum { + A, + B, +} + +impl Enum { + fn a_or_b_eq(&self) -> bool { + self == &Self::A || self == &Self::B + } +} + +fn a() -> bool { + { + let ref this = Enum::A; + this == &Enum::A || this == &Enum::B + } +} +"#, + ) + } + + #[test] + fn inline_call_with_self_type_in_macros() { + check_assist( + inline_call, + r#" +trait Trait { + fn f(a: T1) -> Self; +} + +macro_rules! impl_from { + ($t: ty) => { + impl Trait<$t> for $t { + fn f(a: $t) -> Self { + a as Self + } + } + }; +} + +struct A {} + +impl_from!(A); + +fn main() { + let a: A = A{}; + let b = >::$0f(a); +} +"#, + r#" +trait Trait { + fn f(a: T1) -> Self; +} + +macro_rules! impl_from { + ($t: ty) => { + impl Trait<$t> for $t { + fn f(a: $t) -> Self { + a as Self + } + } + }; +} + +struct A {} + +impl_from!(A); + +fn main() { + let a: A = A{}; + let b = { + let a = a; + a as A + }; +} +"#, + ) + } } diff --git a/crates/ide-assists/src/handlers/introduce_named_generic.rs b/crates/ide-assists/src/handlers/introduce_named_generic.rs index b0d35c02d67..b1daa7802ed 100644 --- a/crates/ide-assists/src/handlers/introduce_named_generic.rs +++ b/crates/ide-assists/src/handlers/introduce_named_generic.rs @@ -18,7 +18,7 @@ // ``` pub(crate) fn introduce_named_generic(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let impl_trait_type = ctx.find_node_at_offset::()?; - let param = impl_trait_type.syntax().parent().and_then(ast::Param::cast)?; + let param = impl_trait_type.syntax().ancestors().find_map(|node| ast::Param::cast(node))?; let fn_ = param.syntax().ancestors().find_map(ast::Fn::cast)?; let type_bound_list = impl_trait_type.type_bound_list()?; @@ -31,15 +31,16 @@ pub(crate) fn introduce_named_generic(acc: &mut Assists, ctx: &AssistContext<'_> |edit| { let impl_trait_type = edit.make_mut(impl_trait_type); let fn_ = edit.make_mut(fn_); - - let type_param_name = suggest_name::for_generic_parameter(&impl_trait_type); + let fn_generic_param_list = fn_.get_or_create_generic_param_list(); + let type_param_name = + suggest_name::for_impl_trait_as_generic(&impl_trait_type, &fn_generic_param_list); let type_param = make::type_param(make::name(&type_param_name), Some(type_bound_list)) .clone_for_update(); let new_ty = make::ty(&type_param_name).clone_for_update(); ted::replace(impl_trait_type.syntax(), new_ty.syntax()); - fn_.get_or_create_generic_param_list().add_generic_param(type_param.into()); + fn_generic_param_list.add_generic_param(type_param.into()); if let Some(cap) = ctx.config.snippet_cap { if let Some(generic_param) = @@ -111,12 +112,19 @@ fn foo<$0B: Bar #[test] fn replace_impl_trait_with_exist_generic_letter() { - // FIXME: This is wrong, we should pick a different name if the one we - // want is already bound. check_assist( introduce_named_generic, r#"fn foo(bar: $0impl Bar) {}"#, - r#"fn foo(bar: B) {}"#, + r#"fn foo(bar: B0) {}"#, + ); + } + + #[test] + fn replace_impl_trait_with_more_exist_generic_letter() { + check_assist( + introduce_named_generic, + r#"fn foo(bar: $0impl Bar) {}"#, + r#"fn foo(bar: B2) {}"#, ); } @@ -149,4 +157,22 @@ fn replace_impl_trait_multiple() { r#"fn foo<$0F: Foo + Bar>(bar: F) {}"#, ); } + + #[test] + fn replace_impl_with_mut() { + check_assist( + introduce_named_generic, + r#"fn f(iter: &mut $0impl Iterator) {}"#, + r#"fn f<$0I: Iterator>(iter: &mut I) {}"#, + ); + } + + #[test] + fn replace_impl_inside() { + check_assist( + introduce_named_generic, + r#"fn f(x: &mut Vec<$0impl Iterator>) {}"#, + r#"fn f<$0I: Iterator>(x: &mut Vec) {}"#, + ); + } } diff --git a/crates/ide-assists/src/handlers/promote_local_to_const.rs b/crates/ide-assists/src/handlers/promote_local_to_const.rs index 6ed9bd85fcc..67fea772c79 100644 --- a/crates/ide-assists/src/handlers/promote_local_to_const.rs +++ b/crates/ide-assists/src/handlers/promote_local_to_const.rs @@ -11,7 +11,10 @@ ted, AstNode, WalkEvent, }; -use crate::assist_context::{AssistContext, Assists}; +use crate::{ + assist_context::{AssistContext, Assists}, + utils, +}; // Assist: promote_local_to_const // @@ -79,15 +82,13 @@ pub(crate) fn promote_local_to_const(acc: &mut Assists, ctx: &AssistContext<'_>) let name_ref = make::name_ref(&name); for usage in usages { - let Some(usage) = usage.name.as_name_ref().cloned() else { continue }; - if let Some(record_field) = ast::RecordExprField::for_name_ref(&usage) { - let record_field = edit.make_mut(record_field); - let name_expr = - make::expr_path(make::path_from_text(&name)).clone_for_update(); - record_field.replace_expr(name_expr); + let Some(usage_name) = usage.name.as_name_ref().cloned() else { continue }; + if let Some(record_field) = ast::RecordExprField::for_name_ref(&usage_name) { + let name_expr = make::expr_path(make::path_from_text(&name)); + utils::replace_record_field_expr(ctx, edit, record_field, name_expr); } else { - let usage = edit.make_mut(usage); - ted::replace(usage.syntax(), name_ref.clone_for_update().syntax()); + let usage_range = usage.range; + edit.replace(usage_range, name_ref.syntax().text()); } } } @@ -212,6 +213,76 @@ fn main() { ) } + #[test] + fn usage_in_macro() { + check_assist( + promote_local_to_const, + r" +macro_rules! identity { + ($body:expr) => { + $body + } +} + +fn baz() -> usize { + let $0foo = 2; + identity![foo] +} +", + r" +macro_rules! identity { + ($body:expr) => { + $body + } +} + +fn baz() -> usize { + const $0FOO: usize = 2; + identity![FOO] +} +", + ) + } + + #[test] + fn usage_shorthand_in_macro() { + check_assist( + promote_local_to_const, + r" +struct Foo { + foo: usize, +} + +macro_rules! identity { + ($body:expr) => { + $body + }; +} + +fn baz() -> Foo { + let $0foo = 2; + identity![Foo { foo }] +} +", + r" +struct Foo { + foo: usize, +} + +macro_rules! identity { + ($body:expr) => { + $body + }; +} + +fn baz() -> Foo { + const $0FOO: usize = 2; + identity![Foo { foo: FOO }] +} +", + ) + } + #[test] fn not_applicable_non_const_meth_call() { cov_mark::check!(promote_local_non_const); diff --git a/crates/ide-assists/src/handlers/remove_unused_imports.rs b/crates/ide-assists/src/handlers/remove_unused_imports.rs index ee44064e7c5..859ed1476c4 100644 --- a/crates/ide-assists/src/handlers/remove_unused_imports.rs +++ b/crates/ide-assists/src/handlers/remove_unused_imports.rs @@ -423,7 +423,7 @@ fn w() { struct X(); struct Y(); mod z { - use super::{X}; + use super::X; fn w() { let x = X(); @@ -495,7 +495,7 @@ fn f() { mod y { struct Y(); mod z { - use crate::{X}; + use crate::X; fn f() { let x = X(); } @@ -526,7 +526,7 @@ fn f() { mod y { struct Y(); mod z { - use crate::{y::Y}; + use crate::y::Y; fn f() { let y = Y(); } @@ -536,6 +536,184 @@ fn f() { ); } + #[test] + fn remove_unused_auto_remove_brace_nested() { + check_assist( + remove_unused_imports, + r#" +mod a { + pub struct A(); +} +mod b { + struct F(); + mod c { + $0use {{super::{{ + {d::{{{{{{{S, U}}}}}}}}, + {{{{e::{H, L, {{{R}}}}}}}}, + F, super::a::A + }}}};$0 + fn f() { + let f = F(); + let l = L(); + let a = A(); + let s = S(); + let h = H(); + } + } + + mod d { + pub struct S(); + pub struct U(); + } + + mod e { + pub struct H(); + pub struct L(); + pub struct R(); + } +} +"#, + r#" +mod a { + pub struct A(); +} +mod b { + struct F(); + mod c { + use super::{ + d::S, + e::{H, L}, + F, super::a::A + }; + fn f() { + let f = F(); + let l = L(); + let a = A(); + let s = S(); + let h = H(); + } + } + + mod d { + pub struct S(); + pub struct U(); + } + + mod e { + pub struct H(); + pub struct L(); + pub struct R(); + } +} +"#, + ); + } + + #[test] + fn remove_comma_after_auto_remove_brace() { + check_assist( + remove_unused_imports, + r#" +mod m { + pub mod x { + pub struct A; + pub struct B; + } + pub mod y { + pub struct C; + } +} + +$0use m::{ + x::{A, B}, + y::C, +};$0 + +fn main() { + B; +} +"#, + r#" +mod m { + pub mod x { + pub struct A; + pub struct B; + } + pub mod y { + pub struct C; + } +} + +use m:: + x::B +; + +fn main() { + B; +} +"#, + ); + check_assist( + remove_unused_imports, + r#" +mod m { + pub mod x { + pub struct A; + pub struct B; + } + pub mod y { + pub struct C; + pub struct D; + } + pub mod z { + pub struct E; + pub struct F; + } +} + +$0use m::{ + x::{A, B}, + y::{C, D,}, + z::{E, F}, +};$0 + +fn main() { + B; + C; + F; +} +"#, + r#" +mod m { + pub mod x { + pub struct A; + pub struct B; + } + pub mod y { + pub struct C; + pub struct D; + } + pub mod z { + pub struct E; + pub struct F; + } +} + +use m::{ + x::B, + y::C, + z::F, +}; + +fn main() { + B; + C; + F; +} +"#, + ); + } + #[test] fn remove_nested_all_unused() { check_assist( diff --git a/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs b/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs index b1daaea1ed1..09759019baa 100644 --- a/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs +++ b/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs @@ -1,4 +1,7 @@ -use syntax::ast::{self, AstNode}; +use syntax::{ + ast::{self, make, AstNode}, + ted, +}; use crate::{utils::suggest_name, AssistContext, AssistId, AssistKind, Assists}; @@ -42,19 +45,34 @@ pub(crate) fn replace_is_method_with_if_let_method( suggest_name::for_variable(&receiver, &ctx.sema) }; - let target = call_expr.syntax().text_range(); - let (assist_id, message, text) = if name_ref.text() == "is_some" { ("replace_is_some_with_if_let_some", "Replace `is_some` with `if let Some`", "Some") } else { ("replace_is_ok_with_if_let_ok", "Replace `is_ok` with `if let Ok`", "Ok") }; - acc.add(AssistId(assist_id, AssistKind::RefactorRewrite), message, target, |edit| { - let var_name = format!("${{0:{}}}", var_name); - let replacement = format!("let {}({}) = {}", text, var_name, receiver); - edit.replace(target, replacement); - }) + acc.add( + AssistId(assist_id, AssistKind::RefactorRewrite), + message, + call_expr.syntax().text_range(), + |edit| { + let call_expr = edit.make_mut(call_expr); + + let var_pat = make::ident_pat(false, false, make::name(&var_name)); + let pat = make::tuple_struct_pat(make::ext::ident_path(text), [var_pat.into()]); + let let_expr = make::expr_let(pat.into(), receiver).clone_for_update(); + + if let Some(cap) = ctx.config.snippet_cap { + if let Some(ast::Pat::TupleStructPat(pat)) = let_expr.pat() { + if let Some(first_var) = pat.fields().next() { + edit.add_placeholder_snippet(cap, first_var); + } + } + } + + ted::replace(call_expr.syntax(), let_expr.syntax()); + }, + ) } _ => return None, } diff --git a/crates/ide-assists/src/tests.rs b/crates/ide-assists/src/tests.rs index 25b3d6d9da9..95b9eb52948 100644 --- a/crates/ide-assists/src/tests.rs +++ b/crates/ide-assists/src/tests.rs @@ -5,13 +5,14 @@ use expect_test::expect; use hir::Semantics; use ide_db::{ - base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt}, + base_db::{FileId, FileRange, SourceDatabaseExt}, imports::insert_use::{ImportGranularity, InsertUseConfig}, source_change::FileSystemEdit, RootDatabase, SnippetCap, }; use stdx::{format_to, trim_indent}; use syntax::TextRange; +use test_fixture::WithFixture; use test_utils::{assert_eq_text, extract_offset}; use crate::{ @@ -504,16 +505,33 @@ pub fn test_some_range(a: int) -> bool { TextEdit { indels: [ Indel { - insert: "let $0var_name = 5;\n ", - delete: 45..45, + insert: "let", + delete: 45..47, }, Indel { insert: "var_name", - delete: 59..60, + delete: 48..60, + }, + Indel { + insert: "=", + delete: 61..81, + }, + Indel { + insert: "5;\n if let 2..6 = var_name {\n true\n } else {\n false\n }", + delete: 82..108, }, ], }, - None, + Some( + SnippetEdit( + [ + ( + 0, + 49..49, + ), + ], + ), + ), ), }, file_system_edits: [], @@ -566,16 +584,33 @@ pub fn test_some_range(a: int) -> bool { TextEdit { indels: [ Indel { - insert: "let $0var_name = 5;\n ", - delete: 45..45, + insert: "let", + delete: 45..47, }, Indel { insert: "var_name", - delete: 59..60, + delete: 48..60, + }, + Indel { + insert: "=", + delete: 61..81, + }, + Indel { + insert: "5;\n if let 2..6 = var_name {\n true\n } else {\n false\n }", + delete: 82..108, }, ], }, - None, + Some( + SnippetEdit( + [ + ( + 0, + 49..49, + ), + ], + ), + ), ), }, file_system_edits: [], diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index da5822bba9c..0c2331796f9 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -1153,7 +1153,7 @@ fn fn_(arg: u32) -> u32 { } fn method_(&mut self) -> bool { - ::method_( &mut self.a ) + ::method_(&mut self.a) } } "#####, diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs index f51e99a914e..927a8e3c19a 100644 --- a/crates/ide-assists/src/utils.rs +++ b/crates/ide-assists/src/utils.rs @@ -813,3 +813,21 @@ fn test_required_hashes() { assert_eq!(3, required_hashes("#ab\"##c")); assert_eq!(5, required_hashes("#ab\"##\"####c")); } + +/// Replaces the record expression, handling field shorthands including inside macros. +pub(crate) fn replace_record_field_expr( + ctx: &AssistContext<'_>, + edit: &mut SourceChangeBuilder, + record_field: ast::RecordExprField, + initializer: ast::Expr, +) { + if let Some(ast::Expr::PathExpr(path_expr)) = record_field.expr() { + // replace field shorthand + let file_range = ctx.sema.original_range(path_expr.syntax()); + edit.insert(file_range.range.end(), format!(": {}", initializer.syntax().text())) + } else if let Some(expr) = record_field.expr() { + // just replace expr + let file_range = ctx.sema.original_range(expr.syntax()); + edit.replace(file_range.range, initializer.syntax().text()); + } +} diff --git a/crates/ide-assists/src/utils/suggest_name.rs b/crates/ide-assists/src/utils/suggest_name.rs index 16704d598ef..b4c6cbff2a4 100644 --- a/crates/ide-assists/src/utils/suggest_name.rs +++ b/crates/ide-assists/src/utils/suggest_name.rs @@ -1,5 +1,7 @@ //! This module contains functions to suggest names for expressions, functions and other items +use std::collections::HashSet; + use hir::Semantics; use ide_db::RootDatabase; use itertools::Itertools; @@ -58,12 +60,59 @@ "into_future", ]; -pub(crate) fn for_generic_parameter(ty: &ast::ImplTraitType) -> SmolStr { +/// Suggest a unique name for generic parameter. +/// +/// `existing_params` is used to check if the name conflicts with existing +/// generic parameters. +/// +/// The function checks if the name conflicts with existing generic parameters. +/// If so, it will try to resolve the conflict by adding a number suffix, e.g. +/// `T`, `T0`, `T1`, ... +pub(crate) fn for_unique_generic_name( + name: &str, + existing_params: &ast::GenericParamList, +) -> SmolStr { + let param_names = existing_params + .generic_params() + .map(|param| match param { + ast::GenericParam::TypeParam(t) => t.name().unwrap().to_string(), + p => p.to_string(), + }) + .collect::>(); + let mut name = name.to_string(); + let base_len = name.len(); + let mut count = 0; + while param_names.contains(&name) { + name.truncate(base_len); + name.push_str(&count.to_string()); + count += 1; + } + + name.into() +} + +/// Suggest name of impl trait type +/// +/// `existing_params` is used to check if the name conflicts with existing +/// generic parameters. +/// +/// # Current implementation +/// +/// In current implementation, the function tries to get the name from the first +/// character of the name for the first type bound. +/// +/// If the name conflicts with existing generic parameters, it will try to +/// resolve the conflict with `for_unique_generic_name`. +pub(crate) fn for_impl_trait_as_generic( + ty: &ast::ImplTraitType, + existing_params: &ast::GenericParamList, +) -> SmolStr { let c = ty .type_bound_list() .and_then(|bounds| bounds.syntax().text().char_at(0.into())) .unwrap_or('T'); - c.encode_utf8(&mut [0; 4]).into() + + for_unique_generic_name(c.encode_utf8(&mut [0; 4]), existing_params) } /// Suggest name of variable for given expression @@ -275,7 +324,8 @@ fn from_field_name(expr: &ast::Expr) -> Option { #[cfg(test)] mod tests { - use ide_db::base_db::{fixture::WithFixture, FileRange}; + use ide_db::base_db::FileRange; + use test_fixture::WithFixture; use super::*; diff --git a/crates/ide-completion/Cargo.toml b/crates/ide-completion/Cargo.toml index 60f90a41b96..7fbcf3d19e0 100644 --- a/crates/ide-completion/Cargo.toml +++ b/crates/ide-completion/Cargo.toml @@ -35,3 +35,7 @@ expect-test = "1.4.0" # local deps test-utils.workspace = true +test-fixture.workspace = true + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/ide-completion/src/completions/attribute.rs b/crates/ide-completion/src/completions/attribute.rs index 466f0b1fb7f..9155caa2e0b 100644 --- a/crates/ide-completion/src/completions/attribute.rs +++ b/crates/ide-completion/src/completions/attribute.rs @@ -26,6 +26,7 @@ mod derive; mod lint; mod repr; +mod macro_use; pub(crate) use self::derive::complete_derive_path; @@ -35,6 +36,7 @@ pub(crate) fn complete_known_attribute_input( ctx: &CompletionContext<'_>, &colon_prefix: &bool, fake_attribute_under_caret: &ast::Attr, + extern_crate: Option<&ast::ExternCrate>, ) -> Option<()> { let attribute = fake_attribute_under_caret; let name_ref = match attribute.path() { @@ -66,6 +68,9 @@ 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)?) + } _ => (), } Some(()) diff --git a/crates/ide-completion/src/completions/attribute/macro_use.rs b/crates/ide-completion/src/completions/attribute/macro_use.rs new file mode 100644 index 00000000000..f45f9cba258 --- /dev/null +++ b/crates/ide-completion/src/completions/attribute/macro_use.rs @@ -0,0 +1,35 @@ +//! Completion for macros in `#[macro_use(...)]` +use hir::ModuleDef; +use ide_db::SymbolKind; +use syntax::ast; + +use crate::{context::CompletionContext, item::CompletionItem, Completions}; + +pub(super) fn complete_macro_use( + acc: &mut Completions, + ctx: &CompletionContext<'_>, + extern_crate: Option<&ast::ExternCrate>, + existing_imports: &[ast::Path], +) { + let Some(extern_crate) = extern_crate else { return }; + let Some(extern_crate) = ctx.sema.to_def(extern_crate) else { return }; + let Some(krate) = extern_crate.resolved_crate(ctx.db) else { return }; + + for mod_def in krate.root_module().declarations(ctx.db) { + if let ModuleDef::Macro(mac) = mod_def { + let mac_name = mac.name(ctx.db); + let Some(mac_name) = mac_name.as_str() else { continue }; + + let existing_import = existing_imports + .iter() + .filter_map(|p| p.as_single_name_ref()) + .find(|n| n.text() == mac_name); + if existing_import.is_some() { + continue; + } + + let item = CompletionItem::new(SymbolKind::Macro, ctx.source_range(), mac_name); + item.add_to(acc, ctx.db); + } + } +} diff --git a/crates/ide-completion/src/completions/dot.rs b/crates/ide-completion/src/completions/dot.rs index 613a35dcb10..53a1c8405c2 100644 --- a/crates/ide-completion/src/completions/dot.rs +++ b/crates/ide-completion/src/completions/dot.rs @@ -27,6 +27,8 @@ pub(crate) fn complete_dot( } let is_field_access = matches!(dot_access.kind, DotAccessKind::Field { .. }); + let is_method_acces_with_parens = + matches!(dot_access.kind, DotAccessKind::Method { has_parens: true }); complete_fields( acc, @@ -35,6 +37,7 @@ pub(crate) fn complete_dot( |acc, field, ty| acc.add_field(ctx, dot_access, None, field, &ty), |acc, field, ty| acc.add_tuple_field(ctx, None, field, &ty), is_field_access, + is_method_acces_with_parens, ); complete_methods(ctx, receiver_ty, |func| acc.add_method(ctx, dot_access, func, None, None)); @@ -83,6 +86,7 @@ pub(crate) fn complete_undotted_self( }, |acc, field, ty| acc.add_tuple_field(ctx, Some(hir::known::SELF_PARAM), field, &ty), true, + false, ); complete_methods(ctx, &ty, |func| { acc.add_method( @@ -106,12 +110,14 @@ fn complete_fields( mut named_field: impl FnMut(&mut Completions, hir::Field, hir::Type), mut tuple_index: impl FnMut(&mut Completions, usize, hir::Type), is_field_access: bool, + is_method_acess_with_parens: bool, ) { let mut seen_names = FxHashSet::default(); for receiver in receiver.autoderef(ctx.db) { for (field, ty) in receiver.fields(ctx.db) { if seen_names.insert(field.name(ctx.db)) - && (is_field_access || ty.is_fn() || ty.is_closure()) + && (is_field_access + || (is_method_acess_with_parens && (ty.is_fn() || ty.is_closure()))) { named_field(acc, field, ty); } @@ -120,7 +126,8 @@ fn complete_fields( // Tuples are always the last type in a deref chain, so just check if the name is // already seen without inserting into the hashset. if !seen_names.contains(&hir::Name::new_tuple_field(i)) - && (is_field_access || ty.is_fn() || ty.is_closure()) + && (is_field_access + || (is_method_acess_with_parens && (ty.is_fn() || ty.is_closure()))) { // Tuple fields are always public (tuple struct fields are handled above). tuple_index(acc, i, ty); @@ -1236,4 +1243,24 @@ fn foo() { "#, ) } + + #[test] + fn test_fn_field_dot_access_method_has_parens_false() { + check( + r#" +struct Foo { baz: fn() } +impl Foo { + fn bar(self, t: T): T { t } +} + +fn baz() { + let foo = Foo{ baz: || {} }; + foo.ba$0::<>; +} +"#, + expect![[r#" + me bar(…) fn(self, T) + "#]], + ); + } } diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index 0da7ba6d000..108b040de6b 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -371,6 +371,7 @@ pub(super) enum CompletionAnalysis { UnexpandedAttrTT { colon_prefix: bool, fake_attribute_under_caret: Option, + extern_crate: Option, }, } @@ -693,7 +694,7 @@ pub(super) fn new( let krate = scope.krate(); let module = scope.module(); - let toolchain = db.crate_graph()[krate.into()].channel; + let toolchain = db.crate_graph()[krate.into()].channel(); // `toolchain == None` means we're in some detached files. Since we have no information on // the toolchain being used, let's just allow unstable items to be listed. let is_nightly = matches!(toolchain, Some(base_db::ReleaseChannel::Nightly) | None); diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs index 1e6b2f319aa..7da66483657 100644 --- a/crates/ide-completion/src/context/analysis.rs +++ b/crates/ide-completion/src/context/analysis.rs @@ -1,7 +1,7 @@ //! Module responsible for analyzing the code surrounding the cursor for completion. use std::iter; -use hir::{HasSource, Semantics, Type, TypeInfo, Variant}; +use hir::{Semantics, Type, TypeInfo, Variant}; use ide_db::{active_parameter::ActiveParameter, RootDatabase}; use syntax::{ algo::{find_node_at_offset, non_trivia_sibling}, @@ -254,11 +254,13 @@ fn analyze( { let colon_prefix = previous_non_trivia_token(self_token.clone()) .map_or(false, |it| T![:] == it.kind()); + CompletionAnalysis::UnexpandedAttrTT { fake_attribute_under_caret: fake_ident_token .parent_ancestors() .find_map(ast::Attr::cast), colon_prefix, + extern_crate: p.ancestors().find_map(ast::ExternCrate::cast), } } else { return None; @@ -359,7 +361,12 @@ fn expected_type_and_name( let ty = it.pat() .and_then(|pat| sema.type_of_pat(&pat)) .or_else(|| it.initializer().and_then(|it| sema.type_of_expr(&it))) - .map(TypeInfo::original); + .map(TypeInfo::original) + .filter(|ty| { + // don't infer the let type if the expr is a function, + // preventing parenthesis from vanishing + it.ty().is_some() || !ty.is_fn() + }); let name = match it.pat() { Some(ast::Pat::IdentPat(ident)) => ident.name().map(NameOrNameRef::Name), Some(_) | None => None, @@ -413,20 +420,16 @@ fn expected_type_and_name( })().unwrap_or((None, None)) }, ast::RecordExprField(it) => { + let field_ty = sema.resolve_record_field(&it).map(|(_, _, ty)| ty); + let field_name = it.field_name().map(NameOrNameRef::NameRef); if let Some(expr) = it.expr() { cov_mark::hit!(expected_type_struct_field_with_leading_char); - ( - sema.type_of_expr(&expr).map(TypeInfo::original), - it.field_name().map(NameOrNameRef::NameRef), - ) + let ty = field_ty + .or_else(|| sema.type_of_expr(&expr).map(TypeInfo::original)); + (ty, field_name) } else { cov_mark::hit!(expected_type_struct_field_followed_by_comma); - let ty = sema.resolve_record_field(&it) - .map(|(_, _, ty)| ty); - ( - ty, - it.field_name().map(NameOrNameRef::NameRef), - ) + (field_ty, field_name) } }, // match foo { $0 } @@ -740,13 +743,13 @@ fn classify_name_ref( match sema.resolve_path(&segment.parent_path().top_path())? { hir::PathResolution::Def(def) => match def { hir::ModuleDef::Function(func) => { - func.source(sema.db)?.value.generic_param_list() + sema.source(func)?.value.generic_param_list() } hir::ModuleDef::Adt(adt) => { - adt.source(sema.db)?.value.generic_param_list() + sema.source(adt)?.value.generic_param_list() } hir::ModuleDef::Variant(variant) => { - variant.parent_enum(sema.db).source(sema.db)?.value.generic_param_list() + sema.source(variant.parent_enum(sema.db))?.value.generic_param_list() } hir::ModuleDef::Trait(trait_) => { if let ast::GenericArg::AssocTypeArg(arg) = &arg { @@ -772,14 +775,14 @@ fn classify_name_ref( return None; } else { in_trait = Some(trait_); - trait_.source(sema.db)?.value.generic_param_list() + sema.source(trait_)?.value.generic_param_list() } } hir::ModuleDef::TraitAlias(trait_) => { - trait_.source(sema.db)?.value.generic_param_list() + sema.source(trait_)?.value.generic_param_list() } hir::ModuleDef::TypeAlias(ty_) => { - ty_.source(sema.db)?.value.generic_param_list() + sema.source(ty_)?.value.generic_param_list() } _ => None, }, @@ -788,7 +791,7 @@ fn classify_name_ref( }, ast::MethodCallExpr(call) => { let func = sema.resolve_method_call(&call)?; - func.source(sema.db)?.value.generic_param_list() + sema.source(func)?.value.generic_param_list() }, ast::AssocTypeArg(arg) => { let trait_ = ast::PathSegment::cast(arg.syntax().parent()?.parent()?)?; @@ -805,7 +808,7 @@ fn classify_name_ref( }, _ => None, })?; - assoc_ty.source(sema.db)?.value.generic_param_list() + sema.source(*assoc_ty)?.value.generic_param_list() } _ => None, }, diff --git a/crates/ide-completion/src/lib.rs b/crates/ide-completion/src/lib.rs index 37a2828e8dc..ff324e7a56d 100644 --- a/crates/ide-completion/src/lib.rs +++ b/crates/ide-completion/src/lib.rs @@ -211,12 +211,14 @@ pub fn completions( CompletionAnalysis::UnexpandedAttrTT { colon_prefix, fake_attribute_under_caret: Some(attr), + extern_crate, } => { completions::attribute::complete_known_attribute_input( acc, ctx, colon_prefix, attr, + extern_crate.as_ref(), ); } CompletionAnalysis::UnexpandedAttrTT { .. } | CompletionAnalysis::String { .. } => (), diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs index 2ea3f74d18b..581d557e831 100644 --- a/crates/ide-completion/src/render.rs +++ b/crates/ide-completion/src/render.rs @@ -837,11 +837,11 @@ fn main() { } "#, expect![[r#" - fn main [] - fn test [] + fn main() [] + fn test(…) [] md dep [] fn function (use dep::test_mod_a::function) [requires_import] - fn function (use dep::test_mod_b::function) [requires_import] + fn function(…) (use dep::test_mod_b::function) [requires_import] "#]], ); } diff --git a/crates/ide-completion/src/render/function.rs b/crates/ide-completion/src/render/function.rs index d23ed71fdcc..b306bede653 100644 --- a/crates/ide-completion/src/render/function.rs +++ b/crates/ide-completion/src/render/function.rs @@ -305,12 +305,15 @@ fn params( return None; } - // Don't add parentheses if the expected type is some function reference. - if let Some(ty) = &ctx.expected_type { - // FIXME: check signature matches? - if ty.is_fn() { - cov_mark::hit!(no_call_parens_if_fn_ptr_needed); - return None; + // Don't add parentheses if the expected type is a function reference with the same signature. + if let Some(expected) = ctx.expected_type.as_ref().filter(|e| e.is_fn()) { + if let Some(expected) = expected.as_callable(ctx.db) { + if let Some(completed) = func.ty(ctx.db).as_callable(ctx.db) { + if expected.sig() == completed.sig() { + cov_mark::hit!(no_call_parens_if_fn_ptr_needed); + return None; + } + } } } diff --git a/crates/ide-completion/src/tests.rs b/crates/ide-completion/src/tests.rs index f28afacc586..f13754e2ded 100644 --- a/crates/ide-completion/src/tests.rs +++ b/crates/ide-completion/src/tests.rs @@ -26,12 +26,13 @@ use expect_test::Expect; use hir::PrefixKind; use ide_db::{ - base_db::{fixture::ChangeFixture, FileLoader, FilePosition}, + base_db::{FileLoader, FilePosition}, imports::insert_use::{ImportGranularity, InsertUseConfig}, RootDatabase, SnippetCap, }; use itertools::Itertools; use stdx::{format_to, trim_indent}; +use test_fixture::ChangeFixture; use test_utils::assert_eq_text; use crate::{ diff --git a/crates/ide-completion/src/tests/attribute.rs b/crates/ide-completion/src/tests/attribute.rs index d8c134c533b..351abe9850b 100644 --- a/crates/ide-completion/src/tests/attribute.rs +++ b/crates/ide-completion/src/tests/attribute.rs @@ -1067,3 +1067,82 @@ fn prim() { ); } } + +mod macro_use { + use super::*; + + #[test] + fn completes_macros() { + check( + r#" +//- /dep.rs crate:dep +#[macro_export] +macro_rules! foo { + () => {}; +} + +#[macro_export] +macro_rules! bar { + () => {}; +} + +//- /main.rs crate:main deps:dep +#[macro_use($0)] +extern crate dep; +"#, + expect![[r#" + ma bar + ma foo + "#]], + ) + } + + #[test] + fn only_completes_exported_macros() { + check( + r#" +//- /dep.rs crate:dep +#[macro_export] +macro_rules! foo { + () => {}; +} + +macro_rules! bar { + () => {}; +} + +//- /main.rs crate:main deps:dep +#[macro_use($0)] +extern crate dep; +"#, + expect![[r#" + ma foo + "#]], + ) + } + + #[test] + fn does_not_completes_already_imported_macros() { + check( + r#" +//- /dep.rs crate:dep +#[macro_export] +macro_rules! foo { + () => {}; +} + +#[macro_export] +macro_rules! bar { + () => {}; +} + +//- /main.rs crate:main deps:dep +#[macro_use(foo, $0)] +extern crate dep; +"#, + expect![[r#" + ma bar + "#]], + ) + } +} diff --git a/crates/ide-db/Cargo.toml b/crates/ide-db/Cargo.toml index 4a2e770f193..f14d9ed1b93 100644 --- a/crates/ide-db/Cargo.toml +++ b/crates/ide-db/Cargo.toml @@ -16,11 +16,11 @@ cov-mark = "2.0.0-pre.1" tracing.workspace = true rayon.workspace = true fst = { version = "0.4.7", default-features = false } -rustc-hash = "1.1.0" +rustc-hash.workspace = true once_cell = "1.17.0" either.workspace = true itertools.workspace = true -arrayvec = "0.7.2" +arrayvec.workspace = true indexmap.workspace = true memchr = "2.6.4" triomphe.workspace = true @@ -34,6 +34,7 @@ profile.workspace = true stdx.workspace = true syntax.workspace = true text-edit.workspace = true +span.workspace = true # ide should depend only on the top-level `hir` package. if you need # something from some `hir-xxx` subpackage, reexport the API via `hir`. hir.workspace = true @@ -47,4 +48,8 @@ xshell.workspace = true # local deps test-utils.workspace = true +test-fixture.workspace = true sourcegen.workspace = true + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/ide-db/src/apply_change.rs b/crates/ide-db/src/apply_change.rs index 343be870c9e..db6cd128e83 100644 --- a/crates/ide-db/src/apply_change.rs +++ b/crates/ide-db/src/apply_change.rs @@ -5,13 +5,13 @@ debug::{DebugQueryTable, TableEntry}, Database, Durability, Query, QueryTable, }, - Change, SourceRootId, + SourceRootId, }; use profile::{memory_usage, Bytes}; use rustc_hash::FxHashSet; use triomphe::Arc; -use crate::{symbol_index::SymbolsDatabase, RootDatabase}; +use crate::{symbol_index::SymbolsDatabase, Change, RootDatabase}; impl RootDatabase { pub fn request_cancellation(&mut self) { @@ -23,7 +23,7 @@ pub fn apply_change(&mut self, change: Change) { let _p = profile::span("RootDatabase::apply_change"); self.request_cancellation(); tracing::trace!("apply_change {:?}", change); - if let Some(roots) = &change.roots { + if let Some(roots) = &change.source_change.roots { let mut local_roots = FxHashSet::default(); let mut library_roots = FxHashSet::default(); for (idx, root) in roots.iter().enumerate() { @@ -87,7 +87,6 @@ macro_rules! purge_each_query { // SourceDatabase base_db::ParseQuery base_db::CrateGraphQuery - base_db::ProcMacrosQuery // SourceDatabaseExt base_db::FileTextQuery @@ -104,6 +103,7 @@ macro_rules! purge_each_query { hir::db::MacroArgQuery hir::db::ParseMacroExpansionQuery hir::db::RealSpanMapQuery + hir::db::ProcMacrosQuery // DefDatabase hir::db::FileItemTreeQuery diff --git a/crates/ide-db/src/documentation.rs b/crates/ide-db/src/documentation.rs index 26f3cd28a27..cc8e8431708 100644 --- a/crates/ide-db/src/documentation.rs +++ b/crates/ide-db/src/documentation.rs @@ -138,15 +138,13 @@ pub fn docs_from_attrs(attrs: &hir::Attrs) -> Option { for doc in docs { // str::lines doesn't yield anything for the empty string if !doc.is_empty() { - buf.extend(Itertools::intersperse( - doc.lines().map(|line| { - line.char_indices() - .nth(indent) - .map_or(line, |(offset, _)| &line[offset..]) - .trim_end() - }), - "\n", - )); + // We don't trim trailing whitespace from doc comments as multiple trailing spaces + // indicates a hard line break in Markdown. + let lines = doc.lines().map(|line| { + line.char_indices().nth(indent).map_or(line, |(offset, _)| &line[offset..]) + }); + + buf.extend(Itertools::intersperse(lines, "\n")); } buf.push('\n'); } diff --git a/crates/ide-db/src/imports/insert_use/tests.rs b/crates/ide-db/src/imports/insert_use/tests.rs index 01d2f1970c3..a3abce89642 100644 --- a/crates/ide-db/src/imports/insert_use/tests.rs +++ b/crates/ide-db/src/imports/insert_use/tests.rs @@ -1,6 +1,6 @@ -use base_db::fixture::WithFixture; use hir::PrefixKind; use stdx::trim_indent; +use test_fixture::WithFixture; use test_utils::{assert_eq_text, CURSOR_MARKER}; use super::*; diff --git a/crates/ide-db/src/lib.rs b/crates/ide-db/src/lib.rs index fefc05e5355..128971994f6 100644 --- a/crates/ide-db/src/lib.rs +++ b/crates/ide-db/src/lib.rs @@ -43,6 +43,8 @@ pub mod syntax_helpers { pub use parser::LexedStr; } +pub use hir::Change; + use std::{fmt, mem::ManuallyDrop}; use base_db::{ diff --git a/crates/ide-db/src/path_transform.rs b/crates/ide-db/src/path_transform.rs index fb4c0c12691..8c1a6e6e40b 100644 --- a/crates/ide-db/src/path_transform.rs +++ b/crates/ide-db/src/path_transform.rs @@ -82,6 +82,34 @@ pub fn function_call( } } + pub fn impl_transformation( + target_scope: &'a SemanticsScope<'a>, + source_scope: &'a SemanticsScope<'a>, + impl_: hir::Impl, + generic_arg_list: ast::GenericArgList, + ) -> PathTransform<'a> { + PathTransform { + source_scope, + target_scope, + generic_def: Some(impl_.into()), + substs: get_type_args_from_arg_list(generic_arg_list).unwrap_or_default(), + } + } + + pub fn adt_transformation( + target_scope: &'a SemanticsScope<'a>, + source_scope: &'a SemanticsScope<'a>, + adt: hir::Adt, + generic_arg_list: ast::GenericArgList, + ) -> PathTransform<'a> { + PathTransform { + source_scope, + target_scope, + generic_def: Some(adt.into()), + substs: get_type_args_from_arg_list(generic_arg_list).unwrap_or_default(), + } + } + pub fn generic_transformation( target_scope: &'a SemanticsScope<'a>, source_scope: &'a SemanticsScope<'a>, diff --git a/crates/ide-db/src/rename.rs b/crates/ide-db/src/rename.rs index d2b6a732689..7f28965885a 100644 --- a/crates/ide-db/src/rename.rs +++ b/crates/ide-db/src/rename.rs @@ -22,9 +22,10 @@ //! Our current behavior is ¯\_(ツ)_/¯. use std::fmt; -use base_db::{span::SyntaxContextId, AnchoredPathBuf, FileId, FileRange}; +use base_db::{AnchoredPathBuf, FileId, FileRange}; use either::Either; use hir::{FieldSource, HasSource, HirFileIdExt, InFile, ModuleSource, Semantics}; +use span::SyntaxContextId; use stdx::{never, TupleExt}; use syntax::{ ast::{self, HasName}, @@ -515,7 +516,7 @@ fn source_edit_from_def( if let Definition::Local(local) = def { let mut file_id = None; for source in local.sources(sema.db) { - let source = match source.source.clone().original_ast_node(sema.db) { + let source = match source.source.clone().original_ast_node_rooted(sema.db) { Some(source) => source, None => match source .source @@ -559,7 +560,7 @@ fn source_edit_from_def( } } else { // Foo { ref mut field } -> Foo { field: ref mut new_name } - // ^ insert `field: ` + // original_ast_node_rootedd: ` // ^^^^^ replace this with `new_name` edit.insert( pat.syntax().text_range().start(), diff --git a/crates/ide-db/src/symbol_index.rs b/crates/ide-db/src/symbol_index.rs index be8566b759c..f5f0f0576f2 100644 --- a/crates/ide-db/src/symbol_index.rs +++ b/crates/ide-db/src/symbol_index.rs @@ -378,9 +378,9 @@ pub(crate) fn search(self, indices: &[Arc]) -> Vec { #[cfg(test)] mod tests { - use base_db::fixture::WithFixture; use expect_test::expect_file; use hir::symbols::SymbolCollector; + use test_fixture::WithFixture; use super::*; @@ -414,6 +414,12 @@ impl Struct { fn impl_fn() {} } +struct StructT; + +impl StructT { + fn generic_impl_fn() {} +} + trait Trait { fn trait_fn(&self); } diff --git a/crates/ide-db/src/test_data/test_symbol_index_collection.txt b/crates/ide-db/src/test_data/test_symbol_index_collection.txt index c9875c7f8f2..f0b97779c73 100644 --- a/crates/ide-db/src/test_data/test_symbol_index_collection.txt +++ b/crates/ide-db/src/test_data/test_symbol_index_collection.txt @@ -23,12 +23,12 @@ ), ptr: SyntaxNodePtr { kind: TYPE_ALIAS, - range: 397..417, + range: 470..490, }, name_ptr: AstPtr( SyntaxNodePtr { kind: NAME, - range: 402..407, + range: 475..480, }, ), }, @@ -51,12 +51,12 @@ ), ptr: SyntaxNodePtr { kind: CONST, - range: 340..361, + range: 413..434, }, name_ptr: AstPtr( SyntaxNodePtr { kind: NAME, - range: 346..351, + range: 419..424, }, ), }, @@ -79,12 +79,12 @@ ), ptr: SyntaxNodePtr { kind: CONST, - range: 520..592, + range: 593..665, }, name_ptr: AstPtr( SyntaxNodePtr { kind: NAME, - range: 526..542, + range: 599..615, }, ), }, @@ -139,12 +139,12 @@ ), ptr: SyntaxNodePtr { kind: USE_TREE, - range: 654..676, + range: 727..749, }, name_ptr: AstPtr( SyntaxNodePtr { kind: NAME, - range: 663..676, + range: 736..749, }, ), }, @@ -197,12 +197,12 @@ ), ptr: SyntaxNodePtr { kind: STATIC, - range: 362..396, + range: 435..469, }, name_ptr: AstPtr( SyntaxNodePtr { kind: NAME, - range: 369..375, + range: 442..448, }, ), }, @@ -276,7 +276,7 @@ Struct( Struct { id: StructId( - 4, + 5, ), }, ), @@ -287,12 +287,12 @@ ), ptr: SyntaxNodePtr { kind: STRUCT, - range: 318..336, + range: 391..409, }, name_ptr: AstPtr( SyntaxNodePtr { kind: NAME, - range: 325..335, + range: 398..408, }, ), }, @@ -308,7 +308,7 @@ Struct( Struct { id: StructId( - 5, + 6, ), }, ), @@ -319,12 +319,12 @@ ), ptr: SyntaxNodePtr { kind: STRUCT, - range: 555..581, + range: 628..654, }, name_ptr: AstPtr( SyntaxNodePtr { kind: NAME, - range: 562..580, + range: 635..653, }, ), }, @@ -340,7 +340,7 @@ Struct( Struct { id: StructId( - 6, + 7, ), }, ), @@ -351,12 +351,42 @@ ), ptr: SyntaxNodePtr { kind: STRUCT, - range: 479..507, + range: 552..580, }, name_ptr: AstPtr( SyntaxNodePtr { kind: NAME, - range: 486..506, + range: 559..579, + }, + ), + }, + container_name: None, + is_alias: false, + is_assoc: false, + }, + FileSymbol { + name: "StructT", + def: Adt( + Struct( + Struct { + id: StructId( + 2, + ), + }, + ), + ), + loc: DeclarationLocation { + hir_file_id: FileId( + 0, + ), + ptr: SyntaxNodePtr { + kind: STRUCT, + range: 261..279, + }, + name_ptr: AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 268..275, }, ), }, @@ -379,12 +409,12 @@ ), ptr: SyntaxNodePtr { kind: TRAIT, - range: 261..300, + range: 334..373, }, name_ptr: AstPtr( SyntaxNodePtr { kind: NAME, - range: 267..272, + range: 340..345, }, ), }, @@ -409,12 +439,12 @@ ), ptr: SyntaxNodePtr { kind: USE_TREE, - range: 682..696, + range: 755..769, }, name_ptr: AstPtr( SyntaxNodePtr { kind: NAME, - range: 691..696, + range: 764..769, }, ), }, @@ -469,12 +499,12 @@ ), ptr: SyntaxNodePtr { kind: MODULE, - range: 419..457, + range: 492..530, }, name_ptr: AstPtr( SyntaxNodePtr { kind: NAME, - range: 423..428, + range: 496..501, }, ), }, @@ -499,12 +529,12 @@ ), ptr: SyntaxNodePtr { kind: MODULE, - range: 594..604, + range: 667..677, }, name_ptr: AstPtr( SyntaxNodePtr { kind: NAME, - range: 598..603, + range: 671..676, }, ), }, @@ -542,6 +572,36 @@ is_alias: false, is_assoc: false, }, + FileSymbol { + name: "generic_impl_fn", + def: Function( + Function { + id: FunctionId( + 3, + ), + }, + ), + loc: DeclarationLocation { + hir_file_id: FileId( + 0, + ), + ptr: SyntaxNodePtr { + kind: FN, + range: 307..330, + }, + name_ptr: AstPtr( + SyntaxNodePtr { + kind: NAME, + range: 310..325, + }, + ), + }, + container_name: Some( + "StructT", + ), + is_alias: false, + is_assoc: true, + }, FileSymbol { name: "impl_fn", def: Function( @@ -566,7 +626,9 @@ }, ), }, - container_name: None, + container_name: Some( + "Struct", + ), is_alias: false, is_assoc: true, }, @@ -615,12 +677,12 @@ ), ptr: SyntaxNodePtr { kind: FN, - range: 302..338, + range: 375..411, }, name_ptr: AstPtr( SyntaxNodePtr { kind: NAME, - range: 305..309, + range: 378..382, }, ), }, @@ -645,12 +707,12 @@ ), ptr: SyntaxNodePtr { kind: USE_TREE, - range: 611..648, + range: 684..721, }, name_ptr: AstPtr( SyntaxNodePtr { kind: NAME, - range: 628..648, + range: 701..721, }, ), }, @@ -673,12 +735,12 @@ ), ptr: SyntaxNodePtr { kind: FN, - range: 279..298, + range: 352..371, }, name_ptr: AstPtr( SyntaxNodePtr { kind: NAME, - range: 282..290, + range: 355..363, }, ), }, @@ -705,7 +767,7 @@ Struct( Struct { id: StructId( - 2, + 3, ), }, ), @@ -716,12 +778,12 @@ ), ptr: SyntaxNodePtr { kind: STRUCT, - range: 435..455, + range: 508..528, }, name_ptr: AstPtr( SyntaxNodePtr { kind: NAME, - range: 442..454, + range: 515..527, }, ), }, @@ -776,7 +838,7 @@ Struct( Struct { id: StructId( - 3, + 4, ), }, ), @@ -836,7 +898,7 @@ Struct( Struct { id: StructId( - 3, + 4, ), }, ), @@ -866,7 +928,7 @@ Struct( Struct { id: StructId( - 3, + 4, ), }, ), diff --git a/crates/ide-db/src/traits.rs b/crates/ide-db/src/traits.rs index 9abbc344142..bbdfd81d653 100644 --- a/crates/ide-db/src/traits.rs +++ b/crates/ide-db/src/traits.rs @@ -113,10 +113,11 @@ fn assoc_item_of_trait( #[cfg(test)] mod tests { - use base_db::{fixture::ChangeFixture, FilePosition}; + use base_db::FilePosition; use expect_test::{expect, Expect}; use hir::Semantics; use syntax::ast::{self, AstNode}; + use test_fixture::ChangeFixture; use crate::RootDatabase; diff --git a/crates/ide-diagnostics/Cargo.toml b/crates/ide-diagnostics/Cargo.toml index f4055024cc3..3ed48457a28 100644 --- a/crates/ide-diagnostics/Cargo.toml +++ b/crates/ide-diagnostics/Cargo.toml @@ -32,7 +32,11 @@ expect-test = "1.4.0" # local deps test-utils.workspace = true +test-fixture.workspace = true sourcegen.workspace = true [features] in-rust-tree = [] + +[lints] +workspace = true \ No newline at end of file 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 82001439146..c202264bb56 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 @@ -76,4 +76,24 @@ fn boo() {} "#, ) } + + #[test] + fn dont_work_for_negative_impl() { + check_diagnostics( + r#" +trait Marker { + const FLAG: bool = false; + fn boo(); + fn foo () {} +} +struct Foo; +impl !Marker for Foo { + type T = i32; + const FLAG: bool = true; + fn bar() {} + fn boo() {} +} + "#, + ) + } } diff --git a/crates/ide-diagnostics/src/handlers/unresolved_assoc_item.rs b/crates/ide-diagnostics/src/handlers/unresolved_assoc_item.rs new file mode 100644 index 00000000000..f1c95993c84 --- /dev/null +++ b/crates/ide-diagnostics/src/handlers/unresolved_assoc_item.rs @@ -0,0 +1,52 @@ +use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext}; + +// Diagnostic: unresolved-assoc-item +// +// This diagnostic is triggered if the referenced associated item does not exist. +pub(crate) fn unresolved_assoc_item( + ctx: &DiagnosticsContext<'_>, + d: &hir::UnresolvedAssocItem, +) -> Diagnostic { + Diagnostic::new_with_syntax_node_ptr( + ctx, + DiagnosticCode::RustcHardError("E0599"), + "no such associated item", + d.expr_or_pat.clone().map(Into::into), + ) +} + +#[cfg(test)] +mod tests { + use crate::tests::check_diagnostics; + + #[test] + fn bare() { + check_diagnostics( + r#" +struct S; + +fn main() { + let _ = S::Assoc; + //^^^^^^^^ error: no such associated item +} +"#, + ); + } + + #[test] + fn unimplemented_trait() { + check_diagnostics( + r#" +struct S; +trait Foo { + const X: u32; +} + +fn main() { + let _ = S::X; + //^^^^ error: no such associated item +} +"#, + ); + } +} diff --git a/crates/ide-diagnostics/src/handlers/unresolved_method.rs b/crates/ide-diagnostics/src/handlers/unresolved_method.rs index 464b0a710ea..60a45a05a4a 100644 --- a/crates/ide-diagnostics/src/handlers/unresolved_method.rs +++ b/crates/ide-diagnostics/src/handlers/unresolved_method.rs @@ -1,11 +1,14 @@ -use hir::{db::ExpandDatabase, HirDisplay}; +use hir::{db::ExpandDatabase, AssocItem, HirDisplay, InFile}; use ide_db::{ assists::{Assist, AssistId, AssistKind}, base_db::FileRange, label::Label, source_change::SourceChange, }; -use syntax::{ast, AstNode, TextRange}; +use syntax::{ + ast::{self, make, HasArgList}, + AstNode, SmolStr, TextRange, +}; use text_edit::TextEdit; use crate::{adjusted_display_range_new, Diagnostic, DiagnosticCode, DiagnosticsContext}; @@ -17,15 +20,17 @@ pub(crate) fn unresolved_method( ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedMethodCall, ) -> Diagnostic { - let field_suffix = if d.field_with_same_name.is_some() { + let suffix = if d.field_with_same_name.is_some() { ", but a field with a similar name exists" + } else if d.assoc_func_with_same_name.is_some() { + ", but an associated function with a similar name exists" } else { "" }; Diagnostic::new( DiagnosticCode::RustcHardError("E0599"), format!( - "no method `{}` on type `{}`{field_suffix}", + "no method `{}` on type `{}`{suffix}", d.name.display(ctx.sema.db), d.receiver.display(ctx.sema.db) ), @@ -46,11 +51,27 @@ pub(crate) fn unresolved_method( } fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedMethodCall) -> Option> { - if let Some(ty) = &d.field_with_same_name { + let field_fix = if let Some(ty) = &d.field_with_same_name { field_fix(ctx, d, ty) } else { // FIXME: add quickfix None + }; + + let assoc_func_fix = assoc_func_fix(ctx, d); + + let mut fixes = vec![]; + if let Some(field_fix) = field_fix { + fixes.push(field_fix); + } + if let Some(assoc_func_fix) = assoc_func_fix { + fixes.push(assoc_func_fix); + } + + if fixes.is_empty() { + None + } else { + Some(fixes) } } @@ -58,7 +79,7 @@ fn field_fix( ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedMethodCall, ty: &hir::Type, -) -> Option> { +) -> Option { if !ty.impls_fnonce(ctx.sema.db) { return None; } @@ -78,7 +99,7 @@ fn field_fix( } _ => return None, }; - Some(vec![Assist { + Some(Assist { id: AssistId("expected-method-found-field-fix", AssistKind::QuickFix), label: Label::new("Use parentheses to call the value of the field".to_string()), group: None, @@ -88,13 +109,180 @@ fn field_fix( (file_id, TextEdit::insert(range.end(), ")".to_owned())), ])), trigger_signature_help: false, - }]) + }) +} + +fn assoc_func_fix(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedMethodCall) -> Option { + if let Some(assoc_item_id) = d.assoc_func_with_same_name { + let db = ctx.sema.db; + + let expr_ptr = &d.expr; + let root = db.parse_or_expand(expr_ptr.file_id); + let expr: ast::Expr = expr_ptr.value.to_node(&root); + + let call = ast::MethodCallExpr::cast(expr.syntax().clone())?; + let range = InFile::new(expr_ptr.file_id, call.syntax().text_range()) + .original_node_file_range_rooted(db) + .range; + + let receiver = call.receiver()?; + let receiver_type = &ctx.sema.type_of_expr(&receiver)?.original; + + let need_to_take_receiver_as_first_arg = match hir::AssocItem::from(assoc_item_id) { + AssocItem::Function(f) => { + let assoc_fn_params = f.assoc_fn_params(db); + if assoc_fn_params.is_empty() { + false + } else { + assoc_fn_params + .first() + .map(|first_arg| { + // For generic type, say `Box`, take `Box::into_raw(b: Self)` as example, + // type of `b` is `Self`, which is `Box`, containing unspecified generics. + // However, type of `receiver` is specified, it could be `Box` or something like that, + // so `first_arg.ty() == receiver_type` evaluate to `false` here. + // Here add `first_arg.ty().as_adt() == receiver_type.as_adt()` as guard, + // apply `.as_adt()` over `Box` or `Box` gets `Box`, so we get `true` here. + + // FIXME: it fails when type of `b` is `Box` with other generic param different from `receiver` + first_arg.ty() == receiver_type + || first_arg.ty().as_adt() == receiver_type.as_adt() + }) + .unwrap_or(false) + } + } + _ => false, + }; + + let mut receiver_type_adt_name = receiver_type.as_adt()?.name(db).to_smol_str().to_string(); + + let generic_parameters: Vec = receiver_type.generic_parameters(db).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() { + let generic_parameters = generic_parameters.join(", ").to_string(); + receiver_type_adt_name = + format!("{}::<{}>", receiver_type_adt_name, generic_parameters); + } + + let method_name = call.name_ref()?; + let assoc_func_call = format!("{}::{}()", receiver_type_adt_name, method_name); + + let assoc_func_call = make::expr_path(make::path_from_text(&assoc_func_call)); + + let args: Vec<_> = if need_to_take_receiver_as_first_arg { + std::iter::once(receiver).chain(call.arg_list()?.args()).collect() + } else { + call.arg_list()?.args().collect() + }; + let args = make::arg_list(args); + + let assoc_func_call_expr_string = make::expr_call(assoc_func_call, args).to_string(); + + let file_id = ctx.sema.original_range_opt(call.receiver()?.syntax())?.file_id; + + Some(Assist { + id: AssistId("method_call_to_assoc_func_call_fix", AssistKind::QuickFix), + label: Label::new(format!( + "Use associated func call instead: `{}`", + assoc_func_call_expr_string + )), + group: None, + target: range, + source_change: Some(SourceChange::from_text_edit( + file_id, + TextEdit::replace(range, assoc_func_call_expr_string), + )), + trigger_signature_help: false, + }) + } else { + None + } } #[cfg(test)] mod tests { use crate::tests::{check_diagnostics, check_fix}; + #[test] + fn test_assoc_func_fix() { + check_fix( + r#" +struct A {} + +impl A { + fn hello() {} +} +fn main() { + let a = A{}; + a.hello$0(); +} +"#, + r#" +struct A {} + +impl A { + fn hello() {} +} +fn main() { + let a = A{}; + A::hello(); +} +"#, + ); + } + + #[test] + fn test_assoc_func_diagnostic() { + check_diagnostics( + r#" +struct A {} +impl A { + fn hello() {} +} +fn main() { + let a = A{}; + a.hello(); + // ^^^^^ 💡 error: no method `hello` on type `A`, but an associated function with a similar name exists +} +"#, + ); + } + + #[test] + fn test_assoc_func_fix_with_generic() { + check_fix( + r#" +struct A { + a: T, + b: U +} + +impl A { + fn foo() {} +} +fn main() { + let a = A {a: 0, b: ""}; + a.foo()$0; +} +"#, + r#" +struct A { + a: T, + b: U +} + +impl A { + fn foo() {} +} +fn main() { + let a = A {a: 0, b: ""}; + A::::foo(); +} +"#, + ); + } + #[test] fn smoke_test() { check_diagnostics( diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs index 579386c72ef..c7ad09e7ebd 100644 --- a/crates/ide-diagnostics/src/lib.rs +++ b/crates/ide-diagnostics/src/lib.rs @@ -51,6 +51,7 @@ mod handlers { pub(crate) mod typed_hole; pub(crate) mod type_mismatch; pub(crate) mod unimplemented_builtin_macro; + pub(crate) mod unresolved_assoc_item; pub(crate) mod unresolved_extern_crate; pub(crate) mod unresolved_field; pub(crate) mod unresolved_method; @@ -371,7 +372,8 @@ pub fn diagnostics( AnyDiagnostic::TypeMismatch(d) => handlers::type_mismatch::type_mismatch(&ctx, &d), AnyDiagnostic::UndeclaredLabel(d) => handlers::undeclared_label::undeclared_label(&ctx, &d), AnyDiagnostic::UnimplementedBuiltinMacro(d) => handlers::unimplemented_builtin_macro::unimplemented_builtin_macro(&ctx, &d), - AnyDiagnostic::UnreachableLabel(d) => handlers::unreachable_label:: unreachable_label(&ctx, &d), + AnyDiagnostic::UnreachableLabel(d) => handlers::unreachable_label::unreachable_label(&ctx, &d), + AnyDiagnostic::UnresolvedAssocItem(d) => handlers::unresolved_assoc_item::unresolved_assoc_item(&ctx, &d), AnyDiagnostic::UnresolvedExternCrate(d) => handlers::unresolved_extern_crate::unresolved_extern_crate(&ctx, &d), AnyDiagnostic::UnresolvedField(d) => handlers::unresolved_field::unresolved_field(&ctx, &d), AnyDiagnostic::UnresolvedImport(d) => handlers::unresolved_import::unresolved_import(&ctx, &d), diff --git a/crates/ide-diagnostics/src/tests.rs b/crates/ide-diagnostics/src/tests.rs index 48e0363c9ca..67912a3a03e 100644 --- a/crates/ide-diagnostics/src/tests.rs +++ b/crates/ide-diagnostics/src/tests.rs @@ -3,12 +3,11 @@ use expect_test::Expect; use ide_db::{ - assists::AssistResolveStrategy, - base_db::{fixture::WithFixture, SourceDatabaseExt}, - LineIndexDatabase, RootDatabase, + assists::AssistResolveStrategy, base_db::SourceDatabaseExt, LineIndexDatabase, RootDatabase, }; use itertools::Itertools; use stdx::trim_indent; +use test_fixture::WithFixture; use test_utils::{assert_eq_text, extract_annotations, MiniCore}; use crate::{DiagnosticsConfig, ExprFillDefaultMode, Severity}; diff --git a/crates/ide-ssr/Cargo.toml b/crates/ide-ssr/Cargo.toml index 56b29f92b82..57b1f9465ad 100644 --- a/crates/ide-ssr/Cargo.toml +++ b/crates/ide-ssr/Cargo.toml @@ -31,3 +31,7 @@ expect-test = "1.4.0" # local deps test-utils.workspace = true +test-fixture.workspace = true + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/ide-ssr/src/tests.rs b/crates/ide-ssr/src/tests.rs index 424ba3d7fd5..7c7d146cb4a 100644 --- a/crates/ide-ssr/src/tests.rs +++ b/crates/ide-ssr/src/tests.rs @@ -65,8 +65,8 @@ fn parser_undefined_placeholder_in_replacement() { /// `code` may optionally contain a cursor marker `$0`. If it doesn't, then the position will be /// the start of the file. If there's a second cursor marker, then we'll return a single range. pub(crate) fn single_file(code: &str) -> (ide_db::RootDatabase, FilePosition, Vec) { - use ide_db::base_db::fixture::WithFixture; use ide_db::symbol_index::SymbolsDatabase; + use test_fixture::{WithFixture, WORKSPACE}; let (mut db, file_id, range_or_offset) = if code.contains(test_utils::CURSOR_MARKER) { ide_db::RootDatabase::with_range_or_offset(code) } else { @@ -86,7 +86,7 @@ pub(crate) fn single_file(code: &str) -> (ide_db::RootDatabase, FilePosition, Ve } } let mut local_roots = FxHashSet::default(); - local_roots.insert(ide_db::base_db::fixture::WORKSPACE); + local_roots.insert(WORKSPACE); db.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH); (db, position, selections) } diff --git a/crates/ide/Cargo.toml b/crates/ide/Cargo.toml index 0943574ec1b..9f0a2f30f65 100644 --- a/crates/ide/Cargo.toml +++ b/crates/ide/Cargo.toml @@ -14,7 +14,7 @@ doctest = false [dependencies] cov-mark = "2.0.0-pre.1" crossbeam-channel = "0.5.5" -arrayvec = "0.7.4" +arrayvec.workspace = true either.workspace = true itertools.workspace = true tracing.workspace = true @@ -50,6 +50,10 @@ expect-test = "1.4.0" # local deps test-utils.workspace = true +test-fixture.workspace = true [features] in-rust-tree = ["ide-assists/in-rust-tree", "ide-diagnostics/in-rust-tree"] + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/ide/src/annotations.rs b/crates/ide/src/annotations.rs index d7f82b4af3e..f49c5af0af1 100644 --- a/crates/ide/src/annotations.rs +++ b/crates/ide/src/annotations.rs @@ -3,8 +3,9 @@ base_db::{FileId, FilePosition, FileRange}, defs::Definition, helpers::visit_file_defs, - RootDatabase, + FxHashSet, RootDatabase, }; +use itertools::Itertools; use syntax::{ast::HasName, AstNode, TextRange}; use crate::{ @@ -23,13 +24,13 @@ // and running/debugging binaries. // // image::https://user-images.githubusercontent.com/48062697/113020672-b7c34f00-917a-11eb-8f6e-858735660a0e.png[] -#[derive(Debug)] +#[derive(Debug, Hash, PartialEq, Eq)] pub struct Annotation { pub range: TextRange, pub kind: AnnotationKind, } -#[derive(Debug)] +#[derive(Debug, Hash, PartialEq, Eq)] pub enum AnnotationKind { Runnable(Runnable), HasImpls { pos: FilePosition, data: Option> }, @@ -56,7 +57,7 @@ pub(crate) fn annotations( config: &AnnotationConfig, file_id: FileId, ) -> Vec { - let mut annotations = Vec::default(); + let mut annotations = FxHashSet::default(); if config.annotate_runnables { for runnable in runnables(db, file_id) { @@ -66,7 +67,7 @@ pub(crate) fn annotations( let range = runnable.nav.focus_or_full_range(); - annotations.push(Annotation { range, kind: AnnotationKind::Runnable(runnable) }); + annotations.insert(Annotation { range, kind: AnnotationKind::Runnable(runnable) }); } } @@ -99,13 +100,13 @@ pub(crate) fn annotations( }) .for_each(|range| { let (annotation_range, target_position) = mk_ranges(range); - annotations.push(Annotation { + annotations.insert(Annotation { range: annotation_range, kind: AnnotationKind::HasReferences { pos: target_position, data: None, }, - }) + }); }) } if config.annotate_references || config.annotate_impls { @@ -131,14 +132,14 @@ pub(crate) fn annotations( }; let (annotation_range, target_pos) = mk_ranges(range); if config.annotate_impls && !matches!(def, Definition::Const(_)) { - annotations.push(Annotation { + annotations.insert(Annotation { range: annotation_range, kind: AnnotationKind::HasImpls { pos: target_pos, data: None }, }); } if config.annotate_references { - annotations.push(Annotation { + annotations.insert(Annotation { range: annotation_range, kind: AnnotationKind::HasReferences { pos: target_pos, data: None }, }); @@ -149,7 +150,7 @@ fn name_range( node: InFile, source_file_id: FileId, ) -> Option<(TextRange, Option)> { - if let Some(InRealFile { file_id, value }) = node.original_ast_node(db) { + if let Some(InRealFile { file_id, value }) = node.original_ast_node_rooted(db) { if file_id == source_file_id { return Some(( value.syntax().text_range(), @@ -171,7 +172,7 @@ fn name_range( })); } - annotations + annotations.into_iter().sorted_by_key(|a| (a.range.start(), a.range.end())).collect() } pub(crate) fn resolve_annotation(db: &RootDatabase, mut annotation: Annotation) -> Annotation { @@ -252,25 +253,6 @@ fn main() { "#, expect![[r#" [ - Annotation { - range: 53..57, - kind: Runnable( - Runnable { - use_name_in_title: false, - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 50..85, - focus_range: 53..57, - name: "main", - kind: Function, - }, - kind: Bin, - cfg: None, - }, - ), - }, Annotation { range: 6..10, kind: HasReferences { @@ -306,6 +288,25 @@ fn main() { ), }, }, + Annotation { + range: 53..57, + kind: Runnable( + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 50..85, + focus_range: 53..57, + name: "main", + kind: Function, + }, + kind: Bin, + cfg: None, + }, + ), + }, Annotation { range: 53..57, kind: HasReferences { @@ -337,39 +338,6 @@ fn main() { "#, expect![[r#" [ - Annotation { - range: 17..21, - kind: Runnable( - Runnable { - use_name_in_title: false, - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 14..48, - focus_range: 17..21, - name: "main", - kind: Function, - }, - kind: Bin, - cfg: None, - }, - ), - }, - Annotation { - range: 7..11, - kind: HasImpls { - pos: FilePosition { - file_id: FileId( - 0, - ), - offset: 7, - }, - data: Some( - [], - ), - }, - }, Annotation { range: 7..11, kind: HasReferences { @@ -391,6 +359,39 @@ fn main() { ), }, }, + Annotation { + range: 7..11, + kind: HasImpls { + pos: FilePosition { + file_id: FileId( + 0, + ), + offset: 7, + }, + data: Some( + [], + ), + }, + }, + Annotation { + range: 17..21, + kind: Runnable( + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 14..48, + focus_range: 17..21, + name: "main", + kind: Function, + }, + kind: Bin, + cfg: None, + }, + ), + }, Annotation { range: 17..21, kind: HasReferences { @@ -426,49 +427,6 @@ fn main() { "#, expect![[r#" [ - Annotation { - range: 69..73, - kind: Runnable( - Runnable { - use_name_in_title: false, - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 66..100, - focus_range: 69..73, - name: "main", - kind: Function, - }, - kind: Bin, - cfg: None, - }, - ), - }, - Annotation { - range: 7..11, - kind: HasImpls { - pos: FilePosition { - file_id: FileId( - 0, - ), - offset: 7, - }, - data: Some( - [ - NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 36..64, - focus_range: 57..61, - name: "impl", - kind: Impl, - }, - ], - ), - }, - }, Annotation { range: 7..11, kind: HasReferences { @@ -496,6 +454,30 @@ fn main() { ), }, }, + Annotation { + range: 7..11, + kind: HasImpls { + pos: FilePosition { + file_id: FileId( + 0, + ), + offset: 7, + }, + data: Some( + [ + NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 36..64, + focus_range: 57..61, + name: "impl", + kind: Impl, + }, + ], + ), + }, + }, Annotation { range: 20..31, kind: HasImpls { @@ -555,6 +537,25 @@ fn main() { ), }, }, + Annotation { + range: 69..73, + kind: Runnable( + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 66..100, + focus_range: 69..73, + name: "main", + kind: Function, + }, + kind: Bin, + cfg: None, + }, + ), + }, ] "#]], ); @@ -622,49 +623,6 @@ fn main() { "#, expect![[r#" [ - Annotation { - range: 61..65, - kind: Runnable( - Runnable { - use_name_in_title: false, - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 58..95, - focus_range: 61..65, - name: "main", - kind: Function, - }, - kind: Bin, - cfg: None, - }, - ), - }, - Annotation { - range: 7..11, - kind: HasImpls { - pos: FilePosition { - file_id: FileId( - 0, - ), - offset: 7, - }, - data: Some( - [ - NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 14..56, - focus_range: 19..23, - name: "impl", - kind: Impl, - }, - ], - ), - }, - }, Annotation { range: 7..11, kind: HasReferences { @@ -692,6 +650,30 @@ fn main() { ), }, }, + Annotation { + range: 7..11, + kind: HasImpls { + pos: FilePosition { + file_id: FileId( + 0, + ), + offset: 7, + }, + data: Some( + [ + NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 14..56, + focus_range: 19..23, + name: "impl", + kind: Impl, + }, + ], + ), + }, + }, Annotation { range: 33..44, kind: HasReferences { @@ -727,6 +709,25 @@ fn main() { ), }, }, + Annotation { + range: 61..65, + kind: Runnable( + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 58..95, + focus_range: 61..65, + name: "main", + kind: Function, + }, + kind: Bin, + cfg: None, + }, + ), + }, ] "#]], ); @@ -745,6 +746,20 @@ fn my_cool_test() {} "#, expect![[r#" [ + Annotation { + range: 3..7, + kind: HasReferences { + pos: FilePosition { + file_id: FileId( + 0, + ), + offset: 3, + }, + data: Some( + [], + ), + }, + }, Annotation { range: 3..7, kind: Runnable( @@ -812,20 +827,6 @@ fn my_cool_test() {} }, ), }, - Annotation { - range: 3..7, - kind: HasReferences { - pos: FilePosition { - file_id: FileId( - 0, - ), - offset: 3, - }, - data: Some( - [], - ), - }, - }, ] "#]], ); @@ -877,7 +878,7 @@ fn test_annotations_appear_above_whole_item_when_configured_to_do_so() { [ Annotation { range: 0..71, - kind: HasImpls { + kind: HasReferences { pos: FilePosition { file_id: FileId( 0, @@ -891,7 +892,7 @@ fn test_annotations_appear_above_whole_item_when_configured_to_do_so() { }, Annotation { range: 0..71, - kind: HasReferences { + kind: HasImpls { pos: FilePosition { file_id: FileId( 0, diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs index 9760f9daf0a..a36082bafcf 100644 --- a/crates/ide/src/doc_links.rs +++ b/crates/ide/src/doc_links.rs @@ -492,7 +492,7 @@ fn get_doc_base_urls( let Some(krate) = def.krate(db) else { return Default::default() }; let Some(display_name) = krate.display_name(db) else { return Default::default() }; let crate_data = &db.crate_graph()[krate.into()]; - let channel = crate_data.channel.map_or("nightly", ReleaseChannel::as_str); + let channel = crate_data.channel().unwrap_or(ReleaseChannel::Nightly).as_str(); let (web_base, local_base) = match &crate_data.origin { // std and co do not specify `html_root_url` any longer so we gotta handwrite this ourself. diff --git a/crates/ide/src/fixture.rs b/crates/ide/src/fixture.rs index 2e5903c0602..3b19b85c4bc 100644 --- a/crates/ide/src/fixture.rs +++ b/crates/ide/src/fixture.rs @@ -1,5 +1,5 @@ //! Utilities for creating `Analysis` instances for tests. -use ide_db::base_db::fixture::ChangeFixture; +use test_fixture::ChangeFixture; use test_utils::{extract_annotations, RangeOrOffset}; use crate::{Analysis, AnalysisHost, FileId, FilePosition, FileRange}; diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 7491879a67f..e0beba8fb38 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -4,7 +4,7 @@ doc_links::token_as_doc_comment, navigation_target::ToNav, FilePosition, NavigationTarget, RangeInfo, TryToNav, }; -use hir::{AsAssocItem, AssocItem, DescendPreference, Semantics}; +use hir::{AsAssocItem, AssocItem, DescendPreference, ModuleDef, Semantics}; use ide_db::{ base_db::{AnchoredPath, FileId, FileLoader}, defs::{Definition, IdentClass}, @@ -73,10 +73,15 @@ pub(crate) fn goto_definition( .into_iter() .filter_map(|token| { let parent = token.parent()?; + if let Some(tt) = ast::TokenTree::cast(parent.clone()) { if let Some(x) = try_lookup_include_path(sema, tt, token.clone(), file_id) { return Some(vec![x]); } + + if let Some(x) = try_lookup_macro_def_in_macro_use(sema, token.clone()) { + return Some(vec![x]); + } } Some( IdentClass::classify_node(sema, &parent)? @@ -140,6 +145,27 @@ fn try_lookup_include_path( }) } +fn try_lookup_macro_def_in_macro_use( + sema: &Semantics<'_, RootDatabase>, + token: SyntaxToken, +) -> Option { + let extern_crate = token.parent()?.ancestors().find_map(ast::ExternCrate::cast)?; + let extern_crate = sema.to_def(&extern_crate)?; + let krate = extern_crate.resolved_crate(sema.db)?; + + for mod_def in krate.root_module().declarations(sema.db) { + if let ModuleDef::Macro(mac) = mod_def { + if mac.name(sema.db).as_str() == Some(token.text()) { + if let Some(nav) = mac.try_to_nav(sema.db) { + return Some(nav.call_site); + } + } + } + } + + None +} + /// finds the trait definition of an impl'd item, except function /// e.g. /// ```rust @@ -2081,4 +2107,47 @@ fn test() { "#, ); } + + #[test] + fn goto_macro_def_from_macro_use() { + check( + r#" +//- /main.rs crate:main deps:mac +#[macro_use(foo$0)] +extern crate mac; + +//- /mac.rs crate:mac +#[macro_export] +macro_rules! foo { + //^^^ + () => {}; +} + "#, + ); + + check( + r#" +//- /main.rs crate:main deps:mac +#[macro_use(foo, bar$0, baz)] +extern crate mac; + +//- /mac.rs crate:mac +#[macro_export] +macro_rules! foo { + () => {}; +} + +#[macro_export] +macro_rules! bar { + //^^^ + () => {}; +} + +#[macro_export] +macro_rules! baz { + () => {}; +} + "#, + ); + } } diff --git a/crates/ide/src/goto_implementation.rs b/crates/ide/src/goto_implementation.rs index 6384db39d7c..c1a4a7b1fc7 100644 --- a/crates/ide/src/goto_implementation.rs +++ b/crates/ide/src/goto_implementation.rs @@ -4,7 +4,6 @@ helpers::pick_best_token, RootDatabase, }; -use itertools::Itertools; use syntax::{ast, AstNode, SyntaxKind::*, T}; use crate::{FilePosition, NavigationTarget, RangeInfo, TryToNav}; @@ -34,10 +33,10 @@ pub(crate) fn goto_implementation( })?; let range = original_token.text_range(); let navs = - sema.descend_into_macros(DescendPreference::None, original_token) - .into_iter() - .filter_map(|token| token.parent().and_then(ast::NameLike::cast)) - .filter_map(|node| match &node { + 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), @@ -52,8 +51,7 @@ pub(crate) fn goto_implementation( }), ast::NameLike::Lifetime(_) => None, }) - .unique() - .filter_map(|def| { + .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)), @@ -75,8 +73,7 @@ pub(crate) fn goto_implementation( }; Some(navs) }) - .flatten() - .collect(); + .unwrap_or_default(); Some(RangeInfo { range, info: navs }) } diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index a19952e4cae..6ff16b9e2f7 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -67,6 +67,7 @@ macro_rules! eprintln { use cfg::CfgOptions; use fetch_crates::CrateInfo; +use hir::Change; use ide_db::{ base_db::{ salsa::{self, ParallelDatabase}, @@ -122,7 +123,7 @@ macro_rules! eprintln { }; pub use ide_db::{ base_db::{ - Cancelled, Change, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, + Cancelled, CrateGraph, CrateId, Edition, FileChange, FileId, FilePosition, FileRange, SourceRoot, SourceRootId, }, documentation::Documentation, @@ -183,7 +184,7 @@ pub fn analysis(&self) -> Analysis { /// Applies changes to the current state of the world. If there are /// outstanding snapshots, they will be canceled. pub fn apply_change(&mut self, change: Change) { - self.db.apply_change(change) + self.db.apply_change(change); } /// NB: this clears the database diff --git a/crates/ide/src/markdown_remove.rs b/crates/ide/src/markdown_remove.rs index 718868c8747..fa01875e204 100644 --- a/crates/ide/src/markdown_remove.rs +++ b/crates/ide/src/markdown_remove.rs @@ -6,6 +6,7 @@ /// Currently limited in styling, i.e. no ascii tables or lists pub(crate) fn remove_markdown(markdown: &str) -> String { let mut out = String::new(); + out.reserve_exact(markdown.len()); let parser = Parser::new(markdown); for event in parser { @@ -13,10 +14,7 @@ pub(crate) fn remove_markdown(markdown: &str) -> String { Event::Text(text) | Event::Code(text) => out.push_str(&text), Event::SoftBreak => out.push(' '), Event::HardBreak | Event::Rule | Event::End(Tag::CodeBlock(_)) => out.push('\n'), - Event::End(Tag::Paragraph) => { - out.push('\n'); - out.push('\n'); - } + Event::End(Tag::Paragraph) => out.push_str("\n\n"), Event::Start(_) | Event::End(_) | Event::Html(_) @@ -25,7 +23,10 @@ pub(crate) fn remove_markdown(markdown: &str) -> String { } } - if let Some(p) = out.rfind(|c| c != '\n') { + if let Some(mut p) = out.rfind(|c| c != '\n') { + while !out.is_char_boundary(p + 1) { + p += 1; + } out.drain(p + 1..); } @@ -153,4 +154,10 @@ fn generic_where(x: T) -> T For more information on the various types of functions and how they're used, consult the Rust book or the Reference."#]].assert_eq(&res); } + + #[test] + fn on_char_boundary() { + expect!["a┘"].assert_eq(&remove_markdown("```text\na┘\n```")); + expect!["وقار"].assert_eq(&remove_markdown("```\nوقار\n```\n")); + } } diff --git a/crates/ide/src/shuffle_crate_graph.rs b/crates/ide/src/shuffle_crate_graph.rs index f85700daf1f..bf6ad47a495 100644 --- a/crates/ide/src/shuffle_crate_graph.rs +++ b/crates/ide/src/shuffle_crate_graph.rs @@ -1,5 +1,6 @@ +use hir::{db::ExpandDatabase, ProcMacros}; use ide_db::{ - base_db::{salsa::Durability, CrateGraph, ProcMacros, SourceDatabase}, + base_db::{salsa::Durability, CrateGraph, SourceDatabase}, FxHashMap, RootDatabase, }; use triomphe::Arc; @@ -39,7 +40,7 @@ pub(crate) fn shuffle_crate_graph(db: &mut RootDatabase) { data.is_proc_macro, data.origin.clone(), data.target_layout.clone(), - data.channel, + data.toolchain.clone(), ); new_proc_macros.insert(new_id, proc_macros[&old_id].clone()); map.insert(old_id, new_id); diff --git a/crates/ide/src/signature_help.rs b/crates/ide/src/signature_help.rs index 990376a4965..483fb76d91c 100644 --- a/crates/ide/src/signature_help.rs +++ b/crates/ide/src/signature_help.rs @@ -646,8 +646,9 @@ mod tests { use std::iter; use expect_test::{expect, Expect}; - use ide_db::base_db::{fixture::ChangeFixture, FilePosition}; + use ide_db::base_db::FilePosition; use stdx::format_to; + use test_fixture::ChangeFixture; use crate::RootDatabase; diff --git a/crates/ide/src/ssr.rs b/crates/ide/src/ssr.rs index d8d81869a2f..f0d18fdefa7 100644 --- a/crates/ide/src/ssr.rs +++ b/crates/ide/src/ssr.rs @@ -59,10 +59,11 @@ mod tests { use expect_test::expect; use ide_assists::{Assist, AssistResolveStrategy}; use ide_db::{ - base_db::{fixture::WithFixture, salsa::Durability, FileRange}, + base_db::{salsa::Durability, FileRange}, symbol_index::SymbolsDatabase, FxHashSet, RootDatabase, }; + use test_fixture::WithFixture; use triomphe::Arc; use super::ssr_assists; @@ -70,7 +71,7 @@ mod tests { fn get_assists(ra_fixture: &str, resolve: AssistResolveStrategy) -> Vec { let (mut db, file_id, range_or_offset) = RootDatabase::with_range_or_offset(ra_fixture); let mut local_roots = FxHashSet::default(); - local_roots.insert(ide_db::base_db::fixture::WORKSPACE); + local_roots.insert(test_fixture::WORKSPACE); db.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH); ssr_assists(&db, &resolve, FileRange { file_id, range: range_or_offset.into() }) } diff --git a/crates/ide/src/status.rs b/crates/ide/src/status.rs index e7f97ebe6f7..b2b305c1d38 100644 --- a/crates/ide/src/status.rs +++ b/crates/ide/src/status.rs @@ -10,7 +10,7 @@ debug::{DebugQueryTable, TableEntry}, Query, QueryTable, }, - CrateId, FileId, FileTextQuery, ParseQuery, SourceDatabase, SourceRootId, + CrateData, FileId, FileTextQuery, ParseQuery, SourceDatabase, SourceRootId, }, symbol_index::ModuleSymbolsQuery, }; @@ -54,25 +54,54 @@ pub(crate) fn status(db: &RootDatabase, file_id: Option) -> String { format_to!(buf, "{} block def maps\n", collect_query_count(BlockDefMapQuery.in_db(db))); if let Some(file_id) = file_id { - format_to!(buf, "\nFile info:\n"); + format_to!(buf, "\nCrates for file {}:\n", file_id.index()); let crates = crate::parent_module::crates_for(db, file_id); if crates.is_empty() { format_to!(buf, "Does not belong to any crate"); } let crate_graph = db.crate_graph(); - for krate in crates { - let display_crate = |krate: CrateId| match &crate_graph[krate].display_name { - Some(it) => format!("{it}({})", krate.into_raw()), - None => format!("{}", krate.into_raw()), - }; - format_to!(buf, "Crate: {}\n", display_crate(krate)); - format_to!(buf, "Enabled cfgs: {:?}\n", crate_graph[krate].cfg_options); - let deps = crate_graph[krate] - .dependencies + for crate_id in crates { + let CrateData { + root_file_id, + edition, + version, + display_name, + cfg_options, + potential_cfg_options, + env, + dependencies, + origin, + is_proc_macro, + target_layout, + toolchain, + } = &crate_graph[crate_id]; + format_to!( + buf, + "Crate: {}\n", + match display_name { + Some(it) => format!("{it}({})", crate_id.into_raw()), + None => format!("{}", crate_id.into_raw()), + } + ); + format_to!(buf, " Root module file id: {}\n", root_file_id.index()); + format_to!(buf, " Edition: {}\n", edition); + format_to!(buf, " Version: {}\n", version.as_deref().unwrap_or("n/a")); + format_to!(buf, " Enabled cfgs: {:?}\n", cfg_options); + format_to!(buf, " Potential cfgs: {:?}\n", potential_cfg_options); + format_to!(buf, " Env: {:?}\n", env); + format_to!(buf, " Origin: {:?}\n", origin); + format_to!(buf, " Is a proc macro crate: {}\n", is_proc_macro); + format_to!(buf, " Workspace Target Layout: {:?}\n", target_layout); + format_to!( + buf, + " Workspace Toolchain: {}\n", + toolchain.as_ref().map_or_else(|| "n/a".into(), |v| v.to_string()) + ); + let deps = dependencies .iter() - .map(|dep| format!("{}={:?}", dep.name, dep.crate_id)) + .map(|dep| format!("{}={}", dep.name, dep.crate_id.into_raw())) .format(", "); - format_to!(buf, "Dependencies: {}\n", deps); + format_to!(buf, " Dependencies: {}\n", deps); } } diff --git a/crates/intern/Cargo.toml b/crates/intern/Cargo.toml index d9184b0fb6f..67b4164ce1f 100644 --- a/crates/intern/Cargo.toml +++ b/crates/intern/Cargo.toml @@ -16,5 +16,8 @@ doctest = false # We need to freeze the version of the crate, as the raw-api feature is considered unstable dashmap.workspace = true hashbrown.workspace = true -rustc-hash = "1.1.0" +rustc-hash.workspace = true triomphe.workspace = true + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/limit/Cargo.toml b/crates/limit/Cargo.toml index c0888690992..c89722cc40d 100644 --- a/crates/limit/Cargo.toml +++ b/crates/limit/Cargo.toml @@ -11,3 +11,6 @@ rust-version.workspace = true [features] tracking = [] default = ["tracking"] + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/load-cargo/Cargo.toml b/crates/load-cargo/Cargo.toml index 31b9f6c76d0..dcab6328a4e 100644 --- a/crates/load-cargo/Cargo.toml +++ b/crates/load-cargo/Cargo.toml @@ -12,7 +12,7 @@ authors.workspace = true [dependencies] anyhow.workspace = true -crossbeam-channel = "0.5.5" +crossbeam-channel.workspace = true itertools.workspace = true tracing.workspace = true @@ -23,3 +23,9 @@ project-model.workspace = true tt.workspace = true vfs.workspace = true vfs-notify.workspace = true +span.workspace = true + +hir-expand.workspace = true + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/load-cargo/src/lib.rs b/crates/load-cargo/src/lib.rs index db9654220dd..556ed73a04c 100644 --- a/crates/load-cargo/src/lib.rs +++ b/crates/load-cargo/src/lib.rs @@ -5,17 +5,19 @@ use std::{collections::hash_map::Entry, mem, path::Path, sync}; use crossbeam_channel::{unbounded, Receiver}; -use ide::{AnalysisHost, Change, SourceRoot}; +use hir_expand::proc_macro::{ + ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacroKind, ProcMacroLoadResult, + ProcMacros, +}; +use ide::{AnalysisHost, SourceRoot}; use ide_db::{ - base_db::{ - span::SpanData, CrateGraph, Env, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, - ProcMacroKind, ProcMacroLoadResult, ProcMacros, - }, - FxHashMap, + base_db::{CrateGraph, Env}, + Change, FxHashMap, }; use itertools::Itertools; use proc_macro_api::{MacroDylib, ProcMacroServer}; use project_model::{CargoConfig, PackageRoot, ProjectManifest, ProjectWorkspace}; +use span::Span; use tt::DelimSpan; use vfs::{file_set::FileSetConfig, loader::Handle, AbsPath, AbsPathBuf, VfsPath}; @@ -374,13 +376,13 @@ fn expander_to_proc_macro( impl ProcMacroExpander for Expander { fn expand( &self, - subtree: &tt::Subtree, - attrs: Option<&tt::Subtree>, + subtree: &tt::Subtree, + attrs: Option<&tt::Subtree>, env: &Env, - def_site: SpanData, - call_site: SpanData, - mixed_site: SpanData, - ) -> Result, ProcMacroExpansionError> { + def_site: Span, + call_site: Span, + mixed_site: Span, + ) -> Result, ProcMacroExpansionError> { let env = env.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect(); match self.0.expand(subtree, attrs, env, def_site, call_site, mixed_site) { Ok(Ok(subtree)) => Ok(subtree), @@ -397,13 +399,13 @@ fn expand( impl ProcMacroExpander for IdentityExpander { fn expand( &self, - subtree: &tt::Subtree, - _: Option<&tt::Subtree>, + subtree: &tt::Subtree, + _: Option<&tt::Subtree>, _: &Env, - _: SpanData, - _: SpanData, - _: SpanData, - ) -> Result, ProcMacroExpansionError> { + _: Span, + _: Span, + _: Span, + ) -> Result, ProcMacroExpansionError> { Ok(subtree.clone()) } } @@ -415,13 +417,13 @@ fn expand( impl ProcMacroExpander for EmptyExpander { fn expand( &self, - _: &tt::Subtree, - _: Option<&tt::Subtree>, + _: &tt::Subtree, + _: Option<&tt::Subtree>, _: &Env, - call_site: SpanData, - _: SpanData, - _: SpanData, - ) -> Result, ProcMacroExpansionError> { + call_site: Span, + _: Span, + _: Span, + ) -> Result, ProcMacroExpansionError> { Ok(tt::Subtree::empty(DelimSpan { open: call_site, close: call_site })) } } diff --git a/crates/mbe/Cargo.toml b/crates/mbe/Cargo.toml index adab1003d10..f50d796e139 100644 --- a/crates/mbe/Cargo.toml +++ b/crates/mbe/Cargo.toml @@ -13,7 +13,7 @@ doctest = false [dependencies] cov-mark = "2.0.0-pre.1" -rustc-hash = "1.1.0" +rustc-hash.workspace = true smallvec.workspace = true tracing.workspace = true @@ -22,6 +22,10 @@ syntax.workspace = true parser.workspace = true tt.workspace = true stdx.workspace = true +span.workspace = true [dev-dependencies] test-utils.workspace = true + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/mbe/src/benchmark.rs b/crates/mbe/src/benchmark.rs index f503aecce2c..6c3917b37f1 100644 --- a/crates/mbe/src/benchmark.rs +++ b/crates/mbe/src/benchmark.rs @@ -20,7 +20,10 @@ fn benchmark_parse_macro_rules() { let rules = macro_rules_fixtures_tt(); let hash: usize = { let _pt = bench("mbe parse macro rules"); - rules.values().map(|it| DeclarativeMacro::parse_macro_rules(it, true).rules.len()).sum() + rules + .values() + .map(|it| DeclarativeMacro::parse_macro_rules(it, true, true).rules.len()) + .sum() }; assert_eq!(hash, 1144); } @@ -38,7 +41,7 @@ fn benchmark_expand_macro_rules() { invocations .into_iter() .map(|(id, tt)| { - let res = rules[&id].expand(&tt, |_| ()); + let res = rules[&id].expand(&tt, |_| (), true, DUMMY); assert!(res.err.is_none()); res.value.token_trees.len() }) @@ -50,7 +53,7 @@ fn benchmark_expand_macro_rules() { fn macro_rules_fixtures() -> FxHashMap> { macro_rules_fixtures_tt() .into_iter() - .map(|(id, tt)| (id, DeclarativeMacro::parse_macro_rules(&tt, true))) + .map(|(id, tt)| (id, DeclarativeMacro::parse_macro_rules(&tt, true, true))) .collect() } @@ -64,8 +67,11 @@ fn macro_rules_fixtures_tt() -> FxHashMap .filter_map(ast::MacroRules::cast) .map(|rule| { let id = rule.name().unwrap().to_string(); - let def_tt = - syntax_node_to_token_tree(rule.token_tree().unwrap().syntax(), DummyTestSpanMap); + let def_tt = syntax_node_to_token_tree( + rule.token_tree().unwrap().syntax(), + DummyTestSpanMap, + DUMMY, + ); (id, def_tt) }) .collect() @@ -105,7 +111,7 @@ fn invocation_fixtures( for op in rule.lhs.iter() { collect_from_op(op, &mut subtree, &mut seed); } - if it.expand(&subtree, |_| ()).err.is_none() { + if it.expand(&subtree, |_| (), true, DUMMY).err.is_none() { res.push((name.clone(), subtree)); break; } @@ -199,7 +205,7 @@ fn collect_from_op( }); parent.token_trees.push(subtree.into()); } - Op::Ignore { .. } | Op::Index { .. } | Op::Count { .. } => {} + Op::Ignore { .. } | Op::Index { .. } | Op::Count { .. } | Op::Length { .. } => {} }; // Simple linear congruential generator for deterministic result diff --git a/crates/mbe/src/expander.rs b/crates/mbe/src/expander.rs index 0e755f69bf7..60483809dc1 100644 --- a/crates/mbe/src/expander.rs +++ b/crates/mbe/src/expander.rs @@ -16,6 +16,8 @@ pub(crate) fn expand_rules( input: &tt::Subtree, marker: impl Fn(&mut S) + Copy, is_2021: bool, + new_meta_vars: bool, + call_site: S, ) -> ExpandResult> { let mut match_: Option<(matcher::Match, &crate::Rule)> = None; for rule in rules { @@ -25,8 +27,13 @@ pub(crate) fn expand_rules( // If we find a rule that applies without errors, we're done. // Unconditionally returning the transcription here makes the // `test_repeat_bad_var` test fail. - let ExpandResult { value, err: transcribe_err } = - transcriber::transcribe(&rule.rhs, &new_match.bindings, marker); + let ExpandResult { value, err: transcribe_err } = transcriber::transcribe( + &rule.rhs, + &new_match.bindings, + marker, + new_meta_vars, + call_site, + ); if transcribe_err.is_none() { return ExpandResult::ok(value); } @@ -45,11 +52,14 @@ pub(crate) fn expand_rules( if let Some((match_, rule)) = match_ { // if we got here, there was no match without errors let ExpandResult { value, err: transcribe_err } = - transcriber::transcribe(&rule.rhs, &match_.bindings, marker); + transcriber::transcribe(&rule.rhs, &match_.bindings, marker, new_meta_vars, call_site); ExpandResult { value, err: match_.err.or(transcribe_err) } } else { ExpandResult::new( - tt::Subtree { delimiter: tt::Delimiter::DUMMY_INVISIBLE, token_trees: vec![] }, + tt::Subtree { + delimiter: tt::Delimiter::invisible_spanned(call_site), + token_trees: vec![], + }, ExpandError::NoMatchingRule, ) } @@ -121,6 +131,7 @@ enum Binding { #[derive(Debug, Clone, PartialEq, Eq)] enum Fragment { + Empty, /// token fragments are just copy-pasted into the output Tokens(tt::TokenTree), /// Expr ast fragments are surrounded with `()` on insertion to preserve diff --git a/crates/mbe/src/expander/matcher.rs b/crates/mbe/src/expander/matcher.rs index 012b02a3f87..40b4c7cdd65 100644 --- a/crates/mbe/src/expander/matcher.rs +++ b/crates/mbe/src/expander/matcher.rs @@ -63,7 +63,7 @@ use smallvec::{smallvec, SmallVec}; use syntax::SmolStr; -use tt::Span; +use tt::{DelimSpan, Span}; use crate::{ expander::{Binding, Bindings, ExpandResult, Fragment}, @@ -74,11 +74,7 @@ impl Bindings { fn push_optional(&mut self, name: &SmolStr) { - // FIXME: Do we have a better way to represent an empty token ? - // Insert an empty subtree for empty token - let tt = - tt::Subtree { delimiter: tt::Delimiter::DUMMY_INVISIBLE, token_trees: vec![] }.into(); - self.inner.insert(name.clone(), Binding::Fragment(Fragment::Tokens(tt))); + self.inner.insert(name.clone(), Binding::Fragment(Fragment::Empty)); } fn push_empty(&mut self, name: &SmolStr) { @@ -387,6 +383,7 @@ fn match_loop_inner<'t, S: Span>( eof_items: &mut SmallVec<[MatchState<'t, S>; 1]>, error_items: &mut SmallVec<[MatchState<'t, S>; 1]>, is_2021: bool, + delim_span: tt::DelimSpan, ) { macro_rules! try_push { ($items: expr, $it:expr) => { @@ -474,7 +471,7 @@ macro_rules! try_push { cur_items.push(new_item); } cur_items.push(MatchState { - dot: tokens.iter_delimited(None), + dot: tokens.iter_delimited(delim_span), stack: Default::default(), up: Some(Box::new(item)), sep: separator.clone(), @@ -489,7 +486,7 @@ macro_rules! try_push { if let Ok(subtree) = src.clone().expect_subtree() { if subtree.delimiter.kind == delimiter.kind { item.stack.push(item.dot); - item.dot = tokens.iter_delimited(Some(*delimiter)); + item.dot = tokens.iter_delimited_with(*delimiter); cur_items.push(item); } } @@ -497,7 +494,7 @@ macro_rules! try_push { OpDelimited::Op(Op::Var { kind, name, .. }) => { if let &Some(kind) = kind { let mut fork = src.clone(); - let match_res = match_meta_var(kind, &mut fork, is_2021); + let match_res = match_meta_var(kind, &mut fork, is_2021, delim_span); match match_res.err { None => { // Some meta variables are optional (e.g. vis) @@ -588,7 +585,9 @@ macro_rules! try_push { item.is_error = true; error_items.push(item); } - OpDelimited::Op(Op::Ignore { .. } | Op::Index { .. } | Op::Count { .. }) => { + OpDelimited::Op( + Op::Ignore { .. } | Op::Index { .. } | Op::Count { .. } | Op::Length { .. }, + ) => { stdx::never!("metavariable expression in lhs found"); } OpDelimited::Open => { @@ -609,6 +608,7 @@ macro_rules! try_push { } fn match_loop(pattern: &MetaTemplate, src: &tt::Subtree, is_2021: bool) -> Match { + let span = src.delimiter.delim_span(); let mut src = TtIter::new(src); let mut stack: SmallVec<[TtIter<'_, S>; 1]> = SmallVec::new(); let mut res = Match::default(); @@ -617,7 +617,7 @@ fn match_loop(pattern: &MetaTemplate, src: &tt::Subtree, is_2021: let mut bindings_builder = BindingsBuilder::default(); let mut cur_items = smallvec![MatchState { - dot: pattern.iter_delimited(None), + dot: pattern.iter_delimited(span), stack: Default::default(), up: None, sep: None, @@ -648,6 +648,7 @@ fn match_loop(pattern: &MetaTemplate, src: &tt::Subtree, is_2021: &mut eof_items, &mut error_items, is_2021, + span, ); stdx::always!(cur_items.is_empty()); @@ -761,12 +762,13 @@ fn match_meta_var( kind: MetaVarKind, input: &mut TtIter<'_, S>, is_2021: bool, + delim_span: DelimSpan, ) -> ExpandResult>> { let fragment = match kind { MetaVarKind::Path => { - return input - .expect_fragment(parser::PrefixEntryPoint::Path) - .map(|it| it.map(tt::TokenTree::subtree_or_wrap).map(Fragment::Path)); + return input.expect_fragment(parser::PrefixEntryPoint::Path).map(|it| { + it.map(|it| tt::TokenTree::subtree_or_wrap(it, delim_span)).map(Fragment::Path) + }); } MetaVarKind::Ty => parser::PrefixEntryPoint::Ty, MetaVarKind::Pat if is_2021 => parser::PrefixEntryPoint::PatTop, @@ -795,7 +797,7 @@ fn match_meta_var( return input.expect_fragment(parser::PrefixEntryPoint::Expr).map(|tt| { tt.map(|tt| match tt { tt::TokenTree::Leaf(leaf) => tt::Subtree { - delimiter: tt::Delimiter::dummy_invisible(), + delimiter: tt::Delimiter::invisible_spanned(*leaf.span()), token_trees: vec![leaf.into()], }, tt::TokenTree::Subtree(mut s) => { @@ -829,7 +831,7 @@ fn match_meta_var( match neg { None => lit.into(), Some(neg) => tt::TokenTree::Subtree(tt::Subtree { - delimiter: tt::Delimiter::dummy_invisible(), + delimiter: tt::Delimiter::invisible_spanned(*literal.span()), token_trees: vec![neg, lit.into()], }), } @@ -851,18 +853,21 @@ fn collect_vars(collector_fun: &mut impl FnMut(SmolStr), pattern: &Meta Op::Subtree { tokens, .. } => collect_vars(collector_fun, tokens), Op::Repeat { tokens, .. } => collect_vars(collector_fun, tokens), Op::Literal(_) | Op::Ident(_) | Op::Punct(_) => {} - Op::Ignore { .. } | Op::Index { .. } | Op::Count { .. } => { + Op::Ignore { .. } | Op::Index { .. } | Op::Count { .. } | Op::Length { .. } => { stdx::never!("metavariable expression in lhs found"); } } } } impl MetaTemplate { - fn iter_delimited(&self, delimited: Option>) -> OpDelimitedIter<'_, S> { + fn iter_delimited_with(&self, delimiter: tt::Delimiter) -> OpDelimitedIter<'_, S> { + OpDelimitedIter { inner: &self.0, idx: 0, delimited: delimiter } + } + fn iter_delimited(&self, span: tt::DelimSpan) -> OpDelimitedIter<'_, S> { OpDelimitedIter { inner: &self.0, idx: 0, - delimited: delimited.unwrap_or(tt::Delimiter::DUMMY_INVISIBLE), + delimited: tt::Delimiter::invisible_delim_spanned(span), } } } @@ -958,11 +963,13 @@ fn expect_tt(&mut self) -> Result, ()> { self.expect_lifetime() } else { let puncts = self.expect_glued_punct()?; + let delimiter = tt::Delimiter { + open: puncts.first().unwrap().span, + close: puncts.last().unwrap().span, + kind: tt::DelimiterKind::Invisible, + }; let token_trees = puncts.into_iter().map(|p| tt::Leaf::Punct(p).into()).collect(); - Ok(tt::TokenTree::Subtree(tt::Subtree { - delimiter: tt::Delimiter::dummy_invisible(), - token_trees, - })) + Ok(tt::TokenTree::Subtree(tt::Subtree { delimiter, token_trees })) } } else { self.next().ok_or(()).cloned() @@ -977,7 +984,11 @@ fn expect_lifetime(&mut self) -> Result, ()> { let ident = self.expect_ident_or_underscore()?; Ok(tt::Subtree { - delimiter: tt::Delimiter::dummy_invisible(), + delimiter: tt::Delimiter { + open: punct.span, + close: ident.span, + kind: tt::DelimiterKind::Invisible, + }, token_trees: vec![ tt::Leaf::Punct(*punct).into(), tt::Leaf::Ident(ident.clone()).into(), diff --git a/crates/mbe/src/expander/transcriber.rs b/crates/mbe/src/expander/transcriber.rs index 7a3e8653c28..6e79cdaa0b9 100644 --- a/crates/mbe/src/expander/transcriber.rs +++ b/crates/mbe/src/expander/transcriber.rs @@ -59,12 +59,12 @@ macro_rules! binding_err { token_trees: token_trees.clone(), }; Ok(match f { - Fragment::Tokens(_) => unreachable!(), + Fragment::Tokens(_) | Fragment::Empty => unreachable!(), Fragment::Expr(_) => Fragment::Expr, Fragment::Path(_) => Fragment::Path, }(subtree)) } - Binding::Fragment(it @ Fragment::Tokens(_)) => Ok(it.clone()), + Binding::Fragment(it @ (Fragment::Tokens(_) | Fragment::Empty)) => Ok(it.clone()), // emit some reasonable default expansion for missing bindings, // this gives better recovery than emitting the `$fragment-name` verbatim Binding::Missing(it) => Ok({ @@ -87,10 +87,7 @@ macro_rules! binding_err { })), // FIXME: Meta and Item should get proper defaults MetaVarKind::Meta | MetaVarKind::Item | MetaVarKind::Tt | MetaVarKind::Vis => { - Fragment::Tokens(tt::TokenTree::Subtree(tt::Subtree { - delimiter: tt::Delimiter::DUMMY_INVISIBLE, - token_trees: vec![], - })) + Fragment::Empty } MetaVarKind::Path | MetaVarKind::Ty @@ -131,8 +128,10 @@ pub(super) fn transcribe( template: &MetaTemplate, bindings: &Bindings, marker: impl Fn(&mut S) + Copy, + new_meta_vars: bool, + call_site: S, ) -> ExpandResult> { - let mut ctx = ExpandCtx { bindings, nesting: Vec::new() }; + let mut ctx = ExpandCtx { bindings, nesting: Vec::new(), new_meta_vars, call_site }; let mut arena: Vec> = Vec::new(); expand_subtree(&mut ctx, template, None, &mut arena, marker) } @@ -152,6 +151,8 @@ struct NestingState { struct ExpandCtx<'a, S> { bindings: &'a Bindings, nesting: Vec, + new_meta_vars: bool, + call_site: S, } fn expand_subtree( @@ -206,13 +207,13 @@ fn expand_subtree( Op::Var { name, id, .. } => { let ExpandResult { value: fragment, err: e } = expand_var(ctx, name, *id, marker); err = err.or(e); - push_fragment(arena, fragment); + push_fragment(ctx, arena, fragment); } Op::Repeat { tokens: subtree, kind, separator } => { let ExpandResult { value: fragment, err: e } = expand_repeat(ctx, subtree, *kind, separator, arena, marker); err = err.or(e); - push_fragment(arena, fragment) + push_fragment(ctx, arena, fragment) } Op::Ignore { name, id } => { // Expand the variable, but ignore the result. This registers the repetition count. @@ -225,8 +226,20 @@ fn expand_subtree( arena.push( tt::Leaf::Literal(tt::Literal { text: index.to_string().into(), - // FIXME - span: S::DUMMY, + span: ctx.call_site, + }) + .into(), + ); + } + Op::Length { depth } => { + let length = ctx.nesting.get(ctx.nesting.len() - 1 - depth).map_or(0, |_nest| { + // FIXME: to be implemented + 0 + }); + arena.push( + tt::Leaf::Literal(tt::Literal { + text: length.to_string().into(), + span: ctx.call_site, }) .into(), ); @@ -268,7 +281,13 @@ fn expand_subtree( } } - let c = match count(ctx, binding, 0, *depth) { + let res = if ctx.new_meta_vars { + count(ctx, binding, 0, depth.unwrap_or(0)) + } else { + count_old(ctx, binding, 0, *depth) + }; + + let c = match res { Ok(c) => c, Err(e) => { // XXX: It *might* make sense to emit a dummy integer value like `0` here. @@ -285,8 +304,7 @@ fn expand_subtree( arena.push( tt::Leaf::Literal(tt::Literal { text: c.to_string().into(), - // FIXME - span: S::DUMMY, + span: ctx.call_site, }) .into(), ); @@ -297,7 +315,7 @@ fn expand_subtree( let tts = arena.drain(start_elements..).collect(); ExpandResult { value: tt::Subtree { - delimiter: delimiter.unwrap_or_else(tt::Delimiter::dummy_invisible), + delimiter: delimiter.unwrap_or_else(|| tt::Delimiter::invisible_spanned(ctx.call_site)), token_trees: tts, }, err, @@ -330,7 +348,7 @@ fn expand_var( // ``` // We just treat it a normal tokens let tt = tt::Subtree { - delimiter: tt::Delimiter::DUMMY_INVISIBLE, + delimiter: tt::Delimiter::invisible_spanned(id), token_trees: vec![ tt::Leaf::from(tt::Punct { char: '$', spacing: tt::Spacing::Alone, span: id }) .into(), @@ -342,10 +360,8 @@ fn expand_var( } Err(e) => ExpandResult { value: Fragment::Tokens(tt::TokenTree::Subtree(tt::Subtree::empty(tt::DelimSpan { - // FIXME - open: S::DUMMY, - // FIXME - close: S::DUMMY, + open: ctx.call_site, + close: ctx.call_site, }))), err: Some(e), }, @@ -389,7 +405,7 @@ fn expand_repeat( return ExpandResult { value: Fragment::Tokens( tt::Subtree { - delimiter: tt::Delimiter::dummy_invisible(), + delimiter: tt::Delimiter::invisible_spanned(ctx.call_site), token_trees: vec![], } .into(), @@ -403,7 +419,7 @@ fn expand_repeat( continue; } - t.delimiter = tt::Delimiter::DUMMY_INVISIBLE; + t.delimiter.kind = tt::DelimiterKind::Invisible; push_subtree(&mut buf, t); if let Some(sep) = separator { @@ -437,7 +453,11 @@ fn expand_repeat( // Check if it is a single token subtree without any delimiter // e.g {Delimiter:None> ['>'] /Delimiter:None>} - let tt = tt::Subtree { delimiter: tt::Delimiter::DUMMY_INVISIBLE, token_trees: buf }.into(); + let tt = tt::Subtree { + delimiter: tt::Delimiter::invisible_spanned(ctx.call_site), + token_trees: buf, + } + .into(); if RepeatKind::OneOrMore == kind && counter == 0 { return ExpandResult { @@ -448,14 +468,19 @@ fn expand_repeat( ExpandResult { value: Fragment::Tokens(tt), err } } -fn push_fragment(buf: &mut Vec>, fragment: Fragment) { +fn push_fragment( + ctx: &ExpandCtx<'_, S>, + buf: &mut Vec>, + fragment: Fragment, +) { match fragment { Fragment::Tokens(tt::TokenTree::Subtree(tt)) => push_subtree(buf, tt), Fragment::Expr(sub) => { push_subtree(buf, sub); } - Fragment::Path(tt) => fix_up_and_push_path_tt(buf, tt), + Fragment::Path(tt) => fix_up_and_push_path_tt(ctx, buf, tt), Fragment::Tokens(tt) => buf.push(tt), + Fragment::Empty => (), } } @@ -469,7 +494,11 @@ fn push_subtree(buf: &mut Vec>, tt: tt::Subtree) { /// Inserts the path separator `::` between an identifier and its following generic /// argument list, and then pushes into the buffer. See [`Fragment::Path`] for why /// we need this fixup. -fn fix_up_and_push_path_tt(buf: &mut Vec>, subtree: tt::Subtree) { +fn fix_up_and_push_path_tt( + ctx: &ExpandCtx<'_, S>, + buf: &mut Vec>, + subtree: tt::Subtree, +) { stdx::always!(matches!(subtree.delimiter.kind, tt::DelimiterKind::Invisible)); let mut prev_was_ident = false; // Note that we only need to fix up the top-level `TokenTree`s because the @@ -486,8 +515,7 @@ fn fix_up_and_push_path_tt(buf: &mut Vec>, subtree: tt tt::Leaf::Punct(tt::Punct { char: ':', spacing: tt::Spacing::Joint, - // FIXME - span: S::DUMMY, + span: ctx.call_site, }) .into(), ); @@ -495,8 +523,7 @@ fn fix_up_and_push_path_tt(buf: &mut Vec>, subtree: tt tt::Leaf::Punct(tt::Punct { char: ':', spacing: tt::Spacing::Alone, - // FIXME - span: S::DUMMY, + span: ctx.call_site, }) .into(), ); @@ -510,6 +537,25 @@ fn fix_up_and_push_path_tt(buf: &mut Vec>, subtree: tt /// Handles `${count(t, depth)}`. `our_depth` is the recursion depth and `count_depth` is the depth /// defined by the metavar expression. fn count( + ctx: &ExpandCtx<'_, S>, + binding: &Binding, + depth_curr: usize, + depth_max: usize, +) -> Result { + match binding { + Binding::Nested(bs) => { + if depth_curr == depth_max { + Ok(bs.len()) + } else { + bs.iter().map(|b| count(ctx, b, depth_curr + 1, depth_max)).sum() + } + } + Binding::Empty => Ok(0), + Binding::Fragment(_) | Binding::Missing(_) => Ok(1), + } +} + +fn count_old( ctx: &ExpandCtx<'_, S>, binding: &Binding, our_depth: usize, @@ -517,9 +563,9 @@ fn count( ) -> Result { match binding { Binding::Nested(bs) => match count_depth { - None => bs.iter().map(|b| count(ctx, b, our_depth + 1, None)).sum(), + None => bs.iter().map(|b| count_old(ctx, b, our_depth + 1, None)).sum(), Some(0) => Ok(bs.len()), - Some(d) => bs.iter().map(|b| count(ctx, b, our_depth + 1, Some(d - 1))).sum(), + Some(d) => bs.iter().map(|b| count_old(ctx, b, our_depth + 1, Some(d - 1))).sum(), }, Binding::Empty => Ok(0), Binding::Fragment(_) | Binding::Missing(_) => { diff --git a/crates/mbe/src/lib.rs b/crates/mbe/src/lib.rs index 9331798589f..2622d7eac10 100644 --- a/crates/mbe/src/lib.rs +++ b/crates/mbe/src/lib.rs @@ -16,7 +16,6 @@ #[cfg(test)] mod benchmark; -mod token_map; use stdx::impl_from; use tt::Span; @@ -30,15 +29,12 @@ // FIXME: we probably should re-think `token_tree_to_syntax_node` interfaces pub use ::parser::TopEntryPoint; -pub use tt::{Delimiter, DelimiterKind, Punct, SyntaxContext}; +pub use tt::{Delimiter, DelimiterKind, Punct}; -pub use crate::{ - syntax_bridge::{ - parse_exprs_with_sep, parse_to_token_tree, parse_to_token_tree_static_span, - syntax_node_to_token_tree, syntax_node_to_token_tree_modified, token_tree_to_syntax_node, - SpanMapper, - }, - token_map::SpanMap, +pub use crate::syntax_bridge::{ + parse_exprs_with_sep, parse_to_token_tree, parse_to_token_tree_static_span, + syntax_node_to_token_tree, syntax_node_to_token_tree_modified, token_tree_to_syntax_node, + SpanMapper, }; pub use crate::syntax_bridge::dummy_test_span_utils::*; @@ -151,7 +147,12 @@ pub fn from_err(err: ParseError, is_2021: bool) -> DeclarativeMacro { } /// The old, `macro_rules! m {}` flavor. - pub fn parse_macro_rules(tt: &tt::Subtree, is_2021: bool) -> DeclarativeMacro { + pub fn parse_macro_rules( + tt: &tt::Subtree, + is_2021: bool, + // FIXME: Remove this once we drop support for rust 1.76 (defaults to true then) + new_meta_vars: bool, + ) -> DeclarativeMacro { // Note: this parsing can be implemented using mbe machinery itself, by // matching against `$($lhs:tt => $rhs:tt);*` pattern, but implementing // manually seems easier. @@ -160,7 +161,7 @@ pub fn parse_macro_rules(tt: &tt::Subtree, is_2021: bool) -> DeclarativeMacro let mut err = None; while src.len() > 0 { - let rule = match Rule::parse(&mut src, true) { + let rule = match Rule::parse(&mut src, true, new_meta_vars) { Ok(it) => it, Err(e) => { err = Some(Box::new(e)); @@ -187,7 +188,12 @@ pub fn parse_macro_rules(tt: &tt::Subtree, is_2021: bool) -> DeclarativeMacro } /// The new, unstable `macro m {}` flavor. - pub fn parse_macro2(tt: &tt::Subtree, is_2021: bool) -> DeclarativeMacro { + pub fn parse_macro2( + tt: &tt::Subtree, + is_2021: bool, + // FIXME: Remove this once we drop support for rust 1.76 (defaults to true then) + new_meta_vars: bool, + ) -> DeclarativeMacro { let mut src = TtIter::new(tt); let mut rules = Vec::new(); let mut err = None; @@ -195,7 +201,7 @@ pub fn parse_macro2(tt: &tt::Subtree, is_2021: bool) -> DeclarativeMacro { if tt::DelimiterKind::Brace == tt.delimiter.kind { cov_mark::hit!(parse_macro_def_rules); while src.len() > 0 { - let rule = match Rule::parse(&mut src, true) { + let rule = match Rule::parse(&mut src, true, new_meta_vars) { Ok(it) => it, Err(e) => { err = Some(Box::new(e)); @@ -214,7 +220,7 @@ pub fn parse_macro2(tt: &tt::Subtree, is_2021: bool) -> DeclarativeMacro { } } else { cov_mark::hit!(parse_macro_def_simple); - match Rule::parse(&mut src, false) { + match Rule::parse(&mut src, false, new_meta_vars) { Ok(rule) => { if src.len() != 0 { err = Some(Box::new(ParseError::expected("remaining tokens in macro def"))); @@ -245,13 +251,19 @@ pub fn expand( &self, tt: &tt::Subtree, marker: impl Fn(&mut S) + Copy, + new_meta_vars: bool, + call_site: S, ) -> ExpandResult> { - expander::expand_rules(&self.rules, &tt, marker, self.is_2021) + expander::expand_rules(&self.rules, &tt, marker, self.is_2021, new_meta_vars, call_site) } } impl Rule { - fn parse(src: &mut TtIter<'_, S>, expect_arrow: bool) -> Result { + fn parse( + src: &mut TtIter<'_, S>, + expect_arrow: bool, + new_meta_vars: bool, + ) -> Result { let lhs = src.expect_subtree().map_err(|()| ParseError::expected("expected subtree"))?; if expect_arrow { src.expect_char('=').map_err(|()| ParseError::expected("expected `=`"))?; @@ -260,7 +272,7 @@ fn parse(src: &mut TtIter<'_, S>, expect_arrow: bool) -> Result MetaTemplate { pub(crate) fn parse_pattern(pattern: &tt::Subtree) -> Result { - MetaTemplate::parse(pattern, Mode::Pattern) + MetaTemplate::parse(pattern, Mode::Pattern, false) } - pub(crate) fn parse_template(template: &tt::Subtree) -> Result { - MetaTemplate::parse(template, Mode::Template) + pub(crate) fn parse_template( + template: &tt::Subtree, + new_meta_vars: bool, + ) -> Result { + MetaTemplate::parse(template, Mode::Template, new_meta_vars) } pub(crate) fn iter(&self) -> impl Iterator> { self.0.iter() } - fn parse(tt: &tt::Subtree, mode: Mode) -> Result { + fn parse(tt: &tt::Subtree, mode: Mode, new_meta_vars: bool) -> Result { let mut src = TtIter::new(tt); let mut res = Vec::new(); while let Some(first) = src.peek_n(0) { - let op = next_op(first, &mut src, mode)?; + let op = next_op(first, &mut src, mode, new_meta_vars)?; res.push(op); } @@ -51,12 +54,35 @@ fn parse(tt: &tt::Subtree, mode: Mode) -> Result { #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum Op { - Var { name: SmolStr, kind: Option, id: S }, - Ignore { name: SmolStr, id: S }, - Index { depth: usize }, - Count { name: SmolStr, depth: Option }, - Repeat { tokens: MetaTemplate, kind: RepeatKind, separator: Option> }, - Subtree { tokens: MetaTemplate, delimiter: tt::Delimiter }, + Var { + name: SmolStr, + kind: Option, + id: S, + }, + Ignore { + name: SmolStr, + id: S, + }, + Index { + depth: usize, + }, + Length { + depth: usize, + }, + Count { + name: SmolStr, + // FIXME: `usize`` once we drop support for 1.76 + depth: Option, + }, + Repeat { + tokens: MetaTemplate, + kind: RepeatKind, + separator: Option>, + }, + Subtree { + tokens: MetaTemplate, + delimiter: tt::Delimiter, + }, Literal(tt::Literal), Punct(SmallVec<[tt::Punct; 3]>), Ident(tt::Ident), @@ -122,6 +148,7 @@ fn next_op( first_peeked: &tt::TokenTree, src: &mut TtIter<'_, S>, mode: Mode, + new_meta_vars: bool, ) -> Result, ParseError> { let res = match first_peeked { tt::TokenTree::Leaf(tt::Leaf::Punct(p @ tt::Punct { char: '$', .. })) => { @@ -135,14 +162,14 @@ fn next_op( tt::TokenTree::Subtree(subtree) => match subtree.delimiter.kind { tt::DelimiterKind::Parenthesis => { let (separator, kind) = parse_repeat(src)?; - let tokens = MetaTemplate::parse(subtree, mode)?; + let tokens = MetaTemplate::parse(subtree, mode, new_meta_vars)?; Op::Repeat { tokens, separator, kind } } tt::DelimiterKind::Brace => match mode { Mode::Template => { - parse_metavar_expr(&mut TtIter::new(subtree)).map_err(|()| { - ParseError::unexpected("invalid metavariable expression") - })? + parse_metavar_expr(new_meta_vars, &mut TtIter::new(subtree)).map_err( + |()| ParseError::unexpected("invalid metavariable expression"), + )? } Mode::Pattern => { return Err(ParseError::unexpected( @@ -206,7 +233,7 @@ fn next_op( tt::TokenTree::Subtree(subtree) => { src.next().expect("first token already peeked"); - let tokens = MetaTemplate::parse(subtree, mode)?; + let tokens = MetaTemplate::parse(subtree, mode, new_meta_vars)?; Op::Subtree { tokens, delimiter: subtree.delimiter } } }; @@ -287,7 +314,7 @@ fn parse_repeat( Err(ParseError::InvalidRepeat) } -fn parse_metavar_expr(src: &mut TtIter<'_, S>) -> Result, ()> { +fn parse_metavar_expr(new_meta_vars: bool, src: &mut TtIter<'_, S>) -> Result, ()> { let func = src.expect_ident()?; let args = src.expect_subtree()?; @@ -299,14 +326,19 @@ fn parse_metavar_expr(src: &mut TtIter<'_, S>) -> Result, ()> { let op = match &*func.text { "ignore" => { + if new_meta_vars { + args.expect_dollar()?; + } let ident = args.expect_ident()?; Op::Ignore { name: ident.text.clone(), id: ident.span } } "index" => Op::Index { depth: parse_depth(&mut args)? }, + "length" => Op::Length { depth: parse_depth(&mut args)? }, "count" => { + if new_meta_vars { + args.expect_dollar()?; + } let ident = args.expect_ident()?; - // `${count(t)}` and `${count(t,)}` have different meanings. Not sure if this is a bug - // but that's how it's implemented in rustc as of this writing. See rust-lang/rust#111904. let depth = if try_eat_comma(&mut args) { Some(parse_depth(&mut args)?) } else { None }; Op::Count { name: ident.text.clone(), depth } } diff --git a/crates/mbe/src/syntax_bridge.rs b/crates/mbe/src/syntax_bridge.rs index b89bfd74a6e..8fa04ab983f 100644 --- a/crates/mbe/src/syntax_bridge.rs +++ b/crates/mbe/src/syntax_bridge.rs @@ -1,6 +1,7 @@ //! Conversions between [`SyntaxNode`] and [`tt::TokenTree`]. use rustc_hash::{FxHashMap, FxHashSet}; +use span::{SpanAnchor, SpanData, SpanMap}; use stdx::{never, non_empty_vec::NonEmptyVec}; use syntax::{ ast::{self, make::tokens::doc_comment}, @@ -10,10 +11,10 @@ }; use tt::{ buffer::{Cursor, TokenBuffer}, - Span, SpanData, SyntaxContext, + Span, }; -use crate::{to_parser_input::to_parser_input, tt_iter::TtIter, SpanMap}; +use crate::{to_parser_input::to_parser_input, tt_iter::TtIter}; #[cfg(test)] mod tests; @@ -36,66 +37,70 @@ fn span_for(&self, range: TextRange) -> S { /// Dummy things for testing where spans don't matter. pub(crate) mod dummy_test_span_utils { + use super::*; - pub type DummyTestSpanData = tt::SpanData; - pub const DUMMY: DummyTestSpanData = DummyTestSpanData::DUMMY; + pub type DummyTestSpanData = span::SpanData; + pub const DUMMY: DummyTestSpanData = span::SpanData { + range: TextRange::empty(TextSize::new(0)), + anchor: span::SpanAnchor { + file_id: span::FileId::BOGUS, + ast_id: span::ROOT_ERASED_FILE_AST_ID, + }, + ctx: DummyTestSyntaxContext, + }; - #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] - pub struct DummyTestSpanAnchor; - impl tt::SpanAnchor for DummyTestSpanAnchor { - const DUMMY: Self = DummyTestSpanAnchor; - } #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct DummyTestSyntaxContext; - impl SyntaxContext for DummyTestSyntaxContext { - const DUMMY: Self = DummyTestSyntaxContext; - } pub struct DummyTestSpanMap; - impl SpanMapper> for DummyTestSpanMap { - fn span_for( - &self, - range: syntax::TextRange, - ) -> tt::SpanData { - tt::SpanData { range, anchor: DummyTestSpanAnchor, ctx: DummyTestSyntaxContext } + impl SpanMapper> for DummyTestSpanMap { + fn span_for(&self, range: syntax::TextRange) -> span::SpanData { + span::SpanData { + range, + anchor: span::SpanAnchor { + file_id: span::FileId::BOGUS, + ast_id: span::ROOT_ERASED_FILE_AST_ID, + }, + ctx: DummyTestSyntaxContext, + } } } } /// Converts a syntax tree to a [`tt::Subtree`] using the provided span map to populate the /// subtree's spans. -pub fn syntax_node_to_token_tree( +pub fn syntax_node_to_token_tree( node: &SyntaxNode, map: SpanMap, -) -> tt::Subtree> + span: SpanData, +) -> tt::Subtree> where - SpanData: Span, - Anchor: Copy, - Ctx: SyntaxContext, - SpanMap: SpanMapper>, + SpanData: Span, + Ctx: Copy, + SpanMap: SpanMapper>, { - let mut c = Converter::new(node, map, Default::default(), Default::default()); + let mut c = Converter::new(node, map, Default::default(), Default::default(), span); convert_tokens(&mut c) } /// Converts a syntax tree to a [`tt::Subtree`] using the provided span map to populate the /// subtree's spans. Additionally using the append and remove parameters, the additional tokens can /// be injected or hidden from the output. -pub fn syntax_node_to_token_tree_modified( +pub fn syntax_node_to_token_tree_modified( node: &SyntaxNode, map: SpanMap, - append: FxHashMap>>>, + append: FxHashMap>>>, remove: FxHashSet, -) -> tt::Subtree> + call_site: SpanData, +) -> tt::Subtree> where - SpanMap: SpanMapper>, - SpanData: Span, - Anchor: Copy, - Ctx: SyntaxContext, + SpanMap: SpanMapper>, + SpanData: Span, + Ctx: Copy, { - let mut c = Converter::new(node, map, append, remove); + let mut c = Converter::new(node, map, append, remove, call_site); convert_tokens(&mut c) } @@ -113,14 +118,13 @@ pub fn syntax_node_to_token_tree_modified( /// Converts a [`tt::Subtree`] back to a [`SyntaxNode`]. /// The produced `SpanMap` contains a mapping from the syntax nodes offsets to the subtree's spans. -pub fn token_tree_to_syntax_node( - tt: &tt::Subtree>, +pub fn token_tree_to_syntax_node( + tt: &tt::Subtree>, entry_point: parser::TopEntryPoint, -) -> (Parse, SpanMap>) +) -> (Parse, SpanMap>) where - SpanData: Span, - Anchor: Copy, - Ctx: SyntaxContext, + SpanData: Span, + Ctx: Copy, { let buffer = match tt { tt::Subtree { @@ -150,21 +154,20 @@ pub fn token_tree_to_syntax_node( /// Convert a string to a `TokenTree`. The spans of the subtree will be anchored to the provided /// anchor with the given context. -pub fn parse_to_token_tree( - anchor: Anchor, +pub fn parse_to_token_tree( + anchor: SpanAnchor, ctx: Ctx, text: &str, -) -> Option>> +) -> Option>> where - SpanData: Span, - Anchor: Copy, - Ctx: SyntaxContext, + SpanData: Span, + Ctx: Copy, { let lexed = parser::LexedStr::new(text); if lexed.errors().next().is_some() { return None; } - let mut conv = RawConverter { lexed, pos: 0, anchor, ctx }; + let mut conv = RawConverter { lexed, anchor, pos: 0, ctx }; Some(convert_tokens(&mut conv)) } @@ -182,7 +185,11 @@ pub fn parse_to_token_tree_static_span(span: S, text: &str) -> Option(tt: &tt::Subtree, sep: char) -> Vec> { +pub fn parse_exprs_with_sep( + tt: &tt::Subtree, + sep: char, + span: S, +) -> Vec> { if tt.token_trees.is_empty() { return Vec::new(); } @@ -195,7 +202,7 @@ pub fn parse_exprs_with_sep(tt: &tt::Subtree, sep: char) -> Vec break, - Some(tt) => tt.subtree_or_wrap(), + Some(tt) => tt.subtree_or_wrap(tt::DelimSpan { open: span, close: span }), }); let mut fork = iter.clone(); @@ -207,7 +214,7 @@ pub fn parse_exprs_with_sep(tt: &tt::Subtree, sep: char) -> Vec(conv: &mut C) -> tt::Subtree C: TokenConverter, S: Span, { - let entry = tt::Subtree { delimiter: tt::Delimiter::DUMMY_INVISIBLE, token_trees: vec![] }; + let entry = tt::Subtree { + delimiter: tt::Delimiter::invisible_spanned(conv.call_site()), + token_trees: vec![], + }; let mut stack = NonEmptyVec::new(entry); while let Some((token, abs_range)) = conv.bump() { @@ -401,9 +411,20 @@ fn doc_comment_text(comment: &ast::Comment) -> SmolStr { text = &text[0..text.len() - 2]; } - // Quote the string + let mut num_of_hashes = 0; + let mut count = 0; + for ch in text.chars() { + count = match ch { + '"' => 1, + '#' if count > 0 => count + 1, + _ => 0, + }; + num_of_hashes = num_of_hashes.max(count); + } + + // Quote raw string with delimiters // Note that `tt::Literal` expect an escaped string - let text = format!("\"{}\"", text.escape_debug()); + let text = format!("r{delim}\"{text}\"{delim}", delim = "#".repeat(num_of_hashes)); text.into() } @@ -450,10 +471,10 @@ fn convert_doc_comment( } /// A raw token (straight from lexer) converter -struct RawConverter<'a, Anchor, Ctx> { +struct RawConverter<'a, Ctx> { lexed: parser::LexedStr<'a>, pos: usize, - anchor: Anchor, + anchor: SpanAnchor, ctx: Ctx, } /// A raw token (straight from lexer) converter that gives every token the same span. @@ -485,18 +506,20 @@ trait TokenConverter: Sized { fn peek(&self) -> Option; fn span_for(&self, range: TextRange) -> S; + + fn call_site(&self) -> S; } -impl SrcToken, S> for usize { - fn kind(&self, ctx: &RawConverter<'_, Anchor, Ctx>) -> SyntaxKind { +impl SrcToken, S> for usize { + fn kind(&self, ctx: &RawConverter<'_, Ctx>) -> SyntaxKind { ctx.lexed.kind(*self) } - fn to_char(&self, ctx: &RawConverter<'_, Anchor, Ctx>) -> Option { + fn to_char(&self, ctx: &RawConverter<'_, Ctx>) -> Option { ctx.lexed.text(*self).chars().next() } - fn to_text(&self, ctx: &RawConverter<'_, Anchor, Ctx>) -> SmolStr { + fn to_text(&self, ctx: &RawConverter<'_, Ctx>) -> SmolStr { ctx.lexed.text(*self).into() } } @@ -515,18 +538,17 @@ fn to_text(&self, ctx: &StaticRawConverter<'_, S>) -> SmolStr { } } -impl TokenConverter> - for RawConverter<'_, Anchor, Ctx> +impl TokenConverter> for RawConverter<'_, Ctx> where - SpanData: Span, + SpanData: Span, { type Token = usize; fn convert_doc_comment( &self, &token: &usize, - span: SpanData, - ) -> Option>>> { + span: SpanData, + ) -> Option>>> { let text = self.lexed.text(token); convert_doc_comment(&doc_comment(text), span) } @@ -550,9 +572,13 @@ fn peek(&self) -> Option { Some(self.pos) } - fn span_for(&self, range: TextRange) -> SpanData { + fn span_for(&self, range: TextRange) -> SpanData { SpanData { range, anchor: self.anchor, ctx: self.ctx } } + + fn call_site(&self) -> SpanData { + SpanData { range: TextRange::empty(0.into()), anchor: self.anchor, ctx: self.ctx } + } } impl TokenConverter for StaticRawConverter<'_, S> @@ -588,6 +614,10 @@ fn peek(&self) -> Option { fn span_for(&self, _: TextRange) -> S { self.span } + + fn call_site(&self) -> S { + self.span + } } struct Converter { @@ -600,6 +630,7 @@ struct Converter { map: SpanMap, append: FxHashMap>>, remove: FxHashSet, + call_site: S, } impl Converter { @@ -608,6 +639,7 @@ fn new( map: SpanMap, append: FxHashMap>>, remove: FxHashSet, + call_site: S, ) -> Self { let mut this = Converter { current: None, @@ -617,6 +649,7 @@ fn new( map, append, remove, + call_site, current_leafs: vec![], }; let first = this.next_token(); @@ -776,24 +809,27 @@ fn peek(&self) -> Option { fn span_for(&self, range: TextRange) -> S { self.map.span_for(range) } + fn call_site(&self) -> S { + self.call_site + } } -struct TtTreeSink<'a, Anchor, Ctx> +struct TtTreeSink<'a, Ctx> where - SpanData: Span, + SpanData: Span, { buf: String, - cursor: Cursor<'a, SpanData>, + cursor: Cursor<'a, SpanData>, text_pos: TextSize, inner: SyntaxTreeBuilder, - token_map: SpanMap>, + token_map: SpanMap>, } -impl<'a, Anchor, Ctx> TtTreeSink<'a, Anchor, Ctx> +impl<'a, Ctx> TtTreeSink<'a, Ctx> where - SpanData: Span, + SpanData: Span, { - fn new(cursor: Cursor<'a, SpanData>) -> Self { + fn new(cursor: Cursor<'a, SpanData>) -> Self { TtTreeSink { buf: String::new(), cursor, @@ -803,7 +839,7 @@ fn new(cursor: Cursor<'a, SpanData>) -> Self { } } - fn finish(mut self) -> (Parse, SpanMap>) { + fn finish(mut self) -> (Parse, SpanMap>) { self.token_map.finish(); (self.inner.finish(), self.token_map) } @@ -821,9 +857,9 @@ fn delim_to_str(d: tt::DelimiterKind, closing: bool) -> Option<&'static str> { Some(&texts[idx..texts.len() - (1 - idx)]) } -impl TtTreeSink<'_, Anchor, Ctx> +impl TtTreeSink<'_, Ctx> where - SpanData: Span, + SpanData: Span, { /// Parses a float literal as if it was a one to two name ref nodes with a dot inbetween. /// This occurs when a float literal is used as a field access. diff --git a/crates/mbe/src/syntax_bridge/tests.rs b/crates/mbe/src/syntax_bridge/tests.rs index bd8187a148a..e5569138dbf 100644 --- a/crates/mbe/src/syntax_bridge/tests.rs +++ b/crates/mbe/src/syntax_bridge/tests.rs @@ -7,11 +7,11 @@ Leaf, Punct, Spacing, }; -use crate::{syntax_node_to_token_tree, DummyTestSpanData, DummyTestSpanMap}; +use crate::{syntax_node_to_token_tree, DummyTestSpanData, DummyTestSpanMap, DUMMY}; fn check_punct_spacing(fixture: &str) { let source_file = ast::SourceFile::parse(fixture).ok().unwrap(); - let subtree = syntax_node_to_token_tree(source_file.syntax(), DummyTestSpanMap); + let subtree = syntax_node_to_token_tree(source_file.syntax(), DummyTestSpanMap, DUMMY); let mut annotations: HashMap<_, _> = extract_annotations(fixture) .into_iter() .map(|(range, annotation)| { diff --git a/crates/mbe/src/tt_iter.rs b/crates/mbe/src/tt_iter.rs index 40e8a2385f4..71513ef4391 100644 --- a/crates/mbe/src/tt_iter.rs +++ b/crates/mbe/src/tt_iter.rs @@ -51,6 +51,13 @@ pub(crate) fn expect_leaf(&mut self) -> Result<&'a tt::Leaf, ()> { } } + pub(crate) fn expect_dollar(&mut self) -> Result<(), ()> { + match self.expect_leaf()? { + tt::Leaf::Punct(tt::Punct { char: '$', .. }) => Ok(()), + _ => Err(()), + } + } + pub(crate) fn expect_ident(&mut self) -> Result<&'a tt::Ident, ()> { match self.expect_leaf()? { tt::Leaf::Ident(it) if it.text != "_" => Ok(it), @@ -169,10 +176,10 @@ pub(crate) fn expect_fragment( } self.inner = self.inner.as_slice()[res.len()..].iter(); - let res = match res.len() { - 0 | 1 => res.pop(), - _ => Some(tt::TokenTree::Subtree(tt::Subtree { - delimiter: tt::Delimiter::DUMMY_INVISIBLE, + let res = match &*res { + [] | [_] => res.pop(), + [first, ..] => Some(tt::TokenTree::Subtree(tt::Subtree { + delimiter: tt::Delimiter::invisible_spanned(first.first_span()), token_trees: res, })), }; diff --git a/crates/parser/Cargo.toml b/crates/parser/Cargo.toml index efb326323f9..0c63484634b 100644 --- a/crates/parser/Cargo.toml +++ b/crates/parser/Cargo.toml @@ -25,3 +25,6 @@ sourcegen.workspace = true [features] in-rust-tree = ["rustc-dependencies/in-rust-tree"] + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/paths/Cargo.toml b/crates/paths/Cargo.toml index 28b54be5212..3d8752b5a82 100644 --- a/crates/paths/Cargo.toml +++ b/crates/paths/Cargo.toml @@ -16,3 +16,6 @@ doctest = false # serde-derive crate. Even though we don't activate the derive feature here, # someone else in the crate graph certainly does! # serde.workspace = true + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/proc-macro-api/Cargo.toml b/crates/proc-macro-api/Cargo.toml index 2cbbc9489a2..49a0979f4f5 100644 --- a/crates/proc-macro-api/Cargo.toml +++ b/crates/proc-macro-api/Cargo.toml @@ -33,7 +33,11 @@ tt.workspace = true stdx.workspace = true profile.workspace = true text-size.workspace = true +span.workspace = true # Ideally this crate would not depend on salsa things, but we need span information here which wraps # InternIds for the syntax context base-db.workspace = true la-arena.workspace = true + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/proc-macro-api/src/lib.rs b/crates/proc-macro-api/src/lib.rs index f697ecd3518..a87becd63e2 100644 --- a/crates/proc-macro-api/src/lib.rs +++ b/crates/proc-macro-api/src/lib.rs @@ -11,16 +11,19 @@ mod process; mod version; -use base_db::span::SpanData; use indexmap::IndexSet; use paths::AbsPathBuf; +use span::Span; use std::{fmt, io, sync::Mutex}; use triomphe::Arc; use serde::{Deserialize, Serialize}; use crate::{ - msg::{ExpandMacro, ExpnGlobals, FlatTree, PanicMessage, HAS_GLOBAL_SPANS}, + msg::{ + deserialize_span_data_index_map, flat::serialize_span_data_index_map, ExpandMacro, + ExpnGlobals, FlatTree, PanicMessage, HAS_GLOBAL_SPANS, RUST_ANALYZER_SPAN_SUPPORT, + }, process::ProcMacroProcessSrv, }; @@ -136,13 +139,13 @@ pub fn kind(&self) -> ProcMacroKind { pub fn expand( &self, - subtree: &tt::Subtree, - attr: Option<&tt::Subtree>, + subtree: &tt::Subtree, + attr: Option<&tt::Subtree>, env: Vec<(String, String)>, - def_site: SpanData, - call_site: SpanData, - mixed_site: SpanData, - ) -> Result, PanicMessage>, ServerError> { + def_site: Span, + call_site: Span, + mixed_site: Span, + ) -> Result, PanicMessage>, ServerError> { let version = self.process.lock().unwrap_or_else(|e| e.into_inner()).version(); let current_dir = env .iter() @@ -166,6 +169,11 @@ pub fn expand( call_site, mixed_site, }, + span_data_table: if version >= RUST_ANALYZER_SPAN_SUPPORT { + serialize_span_data_index_map(&span_data_table) + } else { + Vec::new() + }, }; let response = self @@ -178,9 +186,14 @@ pub fn expand( msg::Response::ExpandMacro(it) => { Ok(it.map(|tree| FlatTree::to_subtree_resolved(tree, version, &span_data_table))) } - msg::Response::ListMacros(..) | msg::Response::ApiVersionCheck(..) => { - Err(ServerError { message: "unexpected response".to_string(), io: None }) - } + msg::Response::ExpandMacroExtended(it) => Ok(it.map(|resp| { + FlatTree::to_subtree_resolved( + resp.tree, + version, + &deserialize_span_data_index_map(&resp.span_data_table), + ) + })), + _ => Err(ServerError { message: "unexpected response".to_string(), io: None }), } } } diff --git a/crates/proc-macro-api/src/msg.rs b/crates/proc-macro-api/src/msg.rs index 1d3e45aff38..557ddba5c78 100644 --- a/crates/proc-macro-api/src/msg.rs +++ b/crates/proc-macro-api/src/msg.rs @@ -10,28 +10,63 @@ use crate::ProcMacroKind; -pub use crate::msg::flat::{FlatTree, TokenId}; +pub use crate::msg::flat::{ + deserialize_span_data_index_map, serialize_span_data_index_map, FlatTree, SpanDataIndexMap, + TokenId, +}; // The versions of the server protocol pub const NO_VERSION_CHECK_VERSION: u32 = 0; pub const VERSION_CHECK_VERSION: u32 = 1; pub const ENCODE_CLOSE_SPAN_VERSION: u32 = 2; pub const HAS_GLOBAL_SPANS: u32 = 3; +pub const RUST_ANALYZER_SPAN_SUPPORT: u32 = 4; -pub const CURRENT_API_VERSION: u32 = HAS_GLOBAL_SPANS; +pub const CURRENT_API_VERSION: u32 = RUST_ANALYZER_SPAN_SUPPORT; #[derive(Debug, Serialize, Deserialize)] pub enum Request { + /// Since [`NO_VERSION_CHECK_VERSION`] ListMacros { dylib_path: PathBuf }, + /// Since [`NO_VERSION_CHECK_VERSION`] ExpandMacro(ExpandMacro), + /// Since [`VERSION_CHECK_VERSION`] ApiVersionCheck {}, + /// Since [`RUST_ANALYZER_SPAN_SUPPORT`] + SetConfig(ServerConfig), +} + +#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize)] +pub enum SpanMode { + #[default] + Id, + RustAnalyzer, } #[derive(Debug, Serialize, Deserialize)] pub enum Response { + /// Since [`NO_VERSION_CHECK_VERSION`] ListMacros(Result, String>), + /// Since [`NO_VERSION_CHECK_VERSION`] ExpandMacro(Result), + /// Since [`NO_VERSION_CHECK_VERSION`] ApiVersionCheck(u32), + /// Since [`RUST_ANALYZER_SPAN_SUPPORT`] + SetConfig(ServerConfig), + /// Since [`RUST_ANALYZER_SPAN_SUPPORT`] + ExpandMacroExtended(Result), +} + +#[derive(Debug, Serialize, Deserialize, Default)] +#[serde(default)] +pub struct ServerConfig { + pub span_mode: SpanMode, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ExpandMacroExtended { + pub tree: FlatTree, + pub span_data_table: Vec, } #[derive(Debug, Serialize, Deserialize)] @@ -64,9 +99,12 @@ pub struct ExpandMacro { #[serde(skip_serializing_if = "ExpnGlobals::skip_serializing_if")] #[serde(default)] pub has_global_spans: ExpnGlobals, + #[serde(skip_serializing_if = "Vec::is_empty")] + #[serde(default)] + pub span_data_table: Vec, } -#[derive(Default, Debug, Serialize, Deserialize)] +#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize)] pub struct ExpnGlobals { #[serde(skip_serializing)] #[serde(default)] @@ -136,29 +174,27 @@ fn write_json(out: &mut impl Write, msg: &str) -> io::Result<()> { #[cfg(test)] mod tests { - use base_db::{ - span::{ErasedFileAstId, SpanAnchor, SpanData, SyntaxContextId}, - FileId, - }; + use base_db::FileId; use la_arena::RawIdx; + use span::{ErasedFileAstId, Span, SpanAnchor, SyntaxContextId}; use text_size::{TextRange, TextSize}; use tt::{Delimiter, DelimiterKind, Ident, Leaf, Literal, Punct, Spacing, Subtree, TokenTree}; use super::*; - fn fixture_token_tree() -> Subtree { + fn fixture_token_tree() -> Subtree { let anchor = SpanAnchor { file_id: FileId::from_raw(0), ast_id: ErasedFileAstId::from_raw(RawIdx::from(0)), }; let mut subtree = Subtree { delimiter: Delimiter { - open: SpanData { + open: Span { range: TextRange::empty(TextSize::new(0)), anchor, ctx: SyntaxContextId::ROOT, }, - close: SpanData { + close: Span { range: TextRange::empty(TextSize::new(13)), anchor, ctx: SyntaxContextId::ROOT, @@ -170,7 +206,7 @@ fn fixture_token_tree() -> Subtree { subtree.token_trees.push(TokenTree::Leaf( Ident { text: "struct".into(), - span: SpanData { + span: Span { range: TextRange::at(TextSize::new(0), TextSize::of("struct")), anchor, ctx: SyntaxContextId::ROOT, @@ -181,7 +217,7 @@ fn fixture_token_tree() -> Subtree { subtree.token_trees.push(TokenTree::Leaf( Ident { text: "Foo".into(), - span: SpanData { + span: Span { range: TextRange::at(TextSize::new(5), TextSize::of("Foo")), anchor, ctx: SyntaxContextId::ROOT, @@ -192,7 +228,7 @@ fn fixture_token_tree() -> Subtree { subtree.token_trees.push(TokenTree::Leaf(Leaf::Literal(Literal { text: "Foo".into(), - span: SpanData { + span: Span { range: TextRange::at(TextSize::new(8), TextSize::of("Foo")), anchor, ctx: SyntaxContextId::ROOT, @@ -200,7 +236,7 @@ fn fixture_token_tree() -> Subtree { }))); subtree.token_trees.push(TokenTree::Leaf(Leaf::Punct(Punct { char: '@', - span: SpanData { + span: Span { range: TextRange::at(TextSize::new(11), TextSize::of('@')), anchor, ctx: SyntaxContextId::ROOT, @@ -209,12 +245,12 @@ fn fixture_token_tree() -> Subtree { }))); subtree.token_trees.push(TokenTree::Subtree(Subtree { delimiter: Delimiter { - open: SpanData { + open: Span { range: TextRange::at(TextSize::new(12), TextSize::of('{')), anchor, ctx: SyntaxContextId::ROOT, }, - close: SpanData { + close: Span { range: TextRange::at(TextSize::new(13), TextSize::of('}')), anchor, ctx: SyntaxContextId::ROOT, @@ -243,6 +279,7 @@ fn test_proc_macro_rpc_works() { call_site: 0, mixed_site: 0, }, + span_data_table: Vec::new(), }; let json = serde_json::to_string(&task).unwrap(); diff --git a/crates/proc-macro-api/src/msg/flat.rs b/crates/proc-macro-api/src/msg/flat.rs index 5835718628e..8dfaba52625 100644 --- a/crates/proc-macro-api/src/msg/flat.rs +++ b/crates/proc-macro-api/src/msg/flat.rs @@ -37,13 +37,46 @@ use std::collections::{HashMap, VecDeque}; -use base_db::span::SpanData; use indexmap::IndexSet; +use la_arena::RawIdx; use serde::{Deserialize, Serialize}; +use span::{ErasedFileAstId, FileId, Span, SpanAnchor, SyntaxContextId}; +use text_size::TextRange; use crate::msg::ENCODE_CLOSE_SPAN_VERSION; -type SpanDataIndexMap = IndexSet; +pub type SpanDataIndexMap = IndexSet; + +pub fn serialize_span_data_index_map(map: &SpanDataIndexMap) -> Vec { + map.iter() + .flat_map(|span| { + [ + span.anchor.file_id.index(), + span.anchor.ast_id.into_raw().into_u32(), + span.range.start().into(), + span.range.end().into(), + span.ctx.into_u32(), + ] + }) + .collect() +} + +pub fn deserialize_span_data_index_map(map: &[u32]) -> SpanDataIndexMap { + debug_assert!(map.len() % 5 == 0); + map.chunks_exact(5) + .map(|span| { + let &[file_id, ast_id, start, end, e] = span else { unreachable!() }; + Span { + anchor: SpanAnchor { + file_id: FileId::from_raw(file_id), + ast_id: ErasedFileAstId::from_raw(RawIdx::from_u32(ast_id)), + }, + range: TextRange::new(start.into(), end.into()), + ctx: SyntaxContextId::from_u32(e), + } + }) + .collect() +} #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct TokenId(pub u32); @@ -54,9 +87,7 @@ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { } } -impl tt::Span for TokenId { - const DUMMY: Self = TokenId(!0); -} +impl tt::Span for TokenId {} #[derive(Serialize, Deserialize, Debug)] pub struct FlatTree { @@ -93,7 +124,7 @@ struct IdentRepr { impl FlatTree { pub fn new( - subtree: &tt::Subtree, + subtree: &tt::Subtree, version: u32, span_data_table: &mut SpanDataIndexMap, ) -> FlatTree { @@ -158,7 +189,7 @@ pub fn to_subtree_resolved( self, version: u32, span_data_table: &SpanDataIndexMap, - ) -> tt::Subtree { + ) -> tt::Subtree { Reader { subtree: if version >= ENCODE_CLOSE_SPAN_VERSION { read_vec(self.subtree, SubtreeRepr::read_with_close_span) @@ -281,13 +312,13 @@ impl IdentRepr { } } -trait Span: Copy { +trait InternableSpan: Copy { type Table; fn token_id_of(table: &mut Self::Table, s: Self) -> TokenId; fn span_for_token_id(table: &Self::Table, id: TokenId) -> Self; } -impl Span for TokenId { +impl InternableSpan for TokenId { type Table = (); fn token_id_of((): &mut Self::Table, token_id: Self) -> TokenId { token_id @@ -297,8 +328,8 @@ fn span_for_token_id((): &Self::Table, id: TokenId) -> Self { id } } -impl Span for SpanData { - type Table = IndexSet; +impl InternableSpan for Span { + type Table = IndexSet; fn token_id_of(table: &mut Self::Table, span: Self) -> TokenId { TokenId(table.insert_full(span).0 as u32) } @@ -307,7 +338,7 @@ fn span_for_token_id(table: &Self::Table, id: TokenId) -> Self { } } -struct Writer<'a, 'span, S: Span> { +struct Writer<'a, 'span, S: InternableSpan> { work: VecDeque<(usize, &'a tt::Subtree)>, string_table: HashMap<&'a str, u32>, span_data_table: &'span mut S::Table, @@ -320,7 +351,7 @@ struct Writer<'a, 'span, S: Span> { text: Vec, } -impl<'a, 'span, S: Span> Writer<'a, 'span, S> { +impl<'a, 'span, S: InternableSpan> Writer<'a, 'span, S> { fn write(&mut self, root: &'a tt::Subtree) { self.enqueue(root); while let Some((idx, subtree)) = self.work.pop_front() { @@ -393,7 +424,7 @@ pub(crate) fn intern(&mut self, text: &'a str) -> u32 { } } -struct Reader<'span, S: Span> { +struct Reader<'span, S: InternableSpan> { subtree: Vec, literal: Vec, punct: Vec, @@ -403,7 +434,7 @@ struct Reader<'span, S: Span> { span_data_table: &'span S::Table, } -impl<'span, S: Span> Reader<'span, S> { +impl<'span, S: InternableSpan> Reader<'span, S> { pub(crate) fn read(self) -> tt::Subtree { let mut res: Vec>> = vec![None; self.subtree.len()]; let read_span = |id| S::span_for_token_id(self.span_data_table, id); diff --git a/crates/proc-macro-api/src/process.rs b/crates/proc-macro-api/src/process.rs index 9a20fa63ed7..3494164c067 100644 --- a/crates/proc-macro-api/src/process.rs +++ b/crates/proc-macro-api/src/process.rs @@ -9,7 +9,7 @@ use stdx::JodChild; use crate::{ - msg::{Message, Request, Response, CURRENT_API_VERSION}, + msg::{Message, Request, Response, SpanMode, CURRENT_API_VERSION, RUST_ANALYZER_SPAN_SUPPORT}, ProcMacroKind, ServerError, }; @@ -19,6 +19,7 @@ pub(crate) struct ProcMacroProcessSrv { stdin: ChildStdin, stdout: BufReader, version: u32, + mode: SpanMode, } impl ProcMacroProcessSrv { @@ -27,7 +28,13 @@ pub(crate) fn run(process_path: AbsPathBuf) -> io::Result { let mut process = Process::run(process_path.clone(), null_stderr)?; let (stdin, stdout) = process.stdio().expect("couldn't access child stdio"); - io::Result::Ok(ProcMacroProcessSrv { _process: process, stdin, stdout, version: 0 }) + io::Result::Ok(ProcMacroProcessSrv { + _process: process, + stdin, + stdout, + version: 0, + mode: SpanMode::Id, + }) }; let mut srv = create_srv(true)?; tracing::info!("sending version check"); @@ -43,6 +50,11 @@ pub(crate) fn run(process_path: AbsPathBuf) -> io::Result { tracing::info!("got version {v}"); srv = create_srv(false)?; srv.version = v; + if srv.version > RUST_ANALYZER_SPAN_SUPPORT { + if let Ok(mode) = srv.enable_rust_analyzer_spans() { + srv.mode = mode; + } + } Ok(srv) } Err(e) => { @@ -62,9 +74,19 @@ pub(crate) fn version_check(&mut self) -> Result { match response { Response::ApiVersionCheck(version) => Ok(version), - Response::ExpandMacro { .. } | Response::ListMacros { .. } => { - Err(ServerError { message: "unexpected response".to_string(), io: None }) - } + _ => Err(ServerError { message: "unexpected response".to_string(), io: None }), + } + } + + fn enable_rust_analyzer_spans(&mut self) -> Result { + let request = Request::SetConfig(crate::msg::ServerConfig { + span_mode: crate::msg::SpanMode::RustAnalyzer, + }); + let response = self.send_task(request)?; + + match response { + Response::SetConfig(crate::msg::ServerConfig { span_mode }) => Ok(span_mode), + _ => Err(ServerError { message: "unexpected response".to_string(), io: None }), } } @@ -78,9 +100,7 @@ pub(crate) fn find_proc_macros( match response { Response::ListMacros(it) => Ok(it), - Response::ExpandMacro { .. } | Response::ApiVersionCheck { .. } => { - Err(ServerError { message: "unexpected response".to_string(), io: None }) - } + _ => Err(ServerError { message: "unexpected response".to_string(), io: None }), } } diff --git a/crates/proc-macro-srv-cli/Cargo.toml b/crates/proc-macro-srv-cli/Cargo.toml index 8f03c6ec7b5..980eab2696b 100644 --- a/crates/proc-macro-srv-cli/Cargo.toml +++ b/crates/proc-macro-srv-cli/Cargo.toml @@ -18,3 +18,6 @@ sysroot-abi = ["proc-macro-srv/sysroot-abi"] [[bin]] name = "rust-analyzer-proc-macro-srv" path = "src/main.rs" + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/proc-macro-srv-cli/src/main.rs b/crates/proc-macro-srv-cli/src/main.rs index 50ce586fc42..000a526e9f9 100644 --- a/crates/proc-macro-srv-cli/src/main.rs +++ b/crates/proc-macro-srv-cli/src/main.rs @@ -39,10 +39,22 @@ fn run() -> io::Result<()> { msg::Request::ListMacros { dylib_path } => { msg::Response::ListMacros(srv.list_macros(&dylib_path)) } - msg::Request::ExpandMacro(task) => msg::Response::ExpandMacro(srv.expand(task)), + msg::Request::ExpandMacro(task) => match srv.span_mode() { + msg::SpanMode::Id => msg::Response::ExpandMacro(srv.expand(task).map(|(it, _)| it)), + msg::SpanMode::RustAnalyzer => msg::Response::ExpandMacroExtended( + srv.expand(task).map(|(tree, span_data_table)| msg::ExpandMacroExtended { + tree, + span_data_table, + }), + ), + }, msg::Request::ApiVersionCheck {} => { msg::Response::ApiVersionCheck(proc_macro_api::msg::CURRENT_API_VERSION) } + msg::Request::SetConfig(config) => { + srv.set_span_mode(config.span_mode); + msg::Response::SetConfig(config) + } }; write_response(res)? } diff --git a/crates/proc-macro-srv/Cargo.toml b/crates/proc-macro-srv/Cargo.toml index 99993f16e27..b6686fa5b65 100644 --- a/crates/proc-macro-srv/Cargo.toml +++ b/crates/proc-macro-srv/Cargo.toml @@ -26,13 +26,18 @@ stdx.workspace = true tt.workspace = true mbe.workspace = true paths.workspace = true +base-db.workspace = true +span.workspace = true proc-macro-api.workspace = true [dev-dependencies] expect-test = "1.4.0" # used as proc macro test targets -proc-macro-test.workspace = true +proc-macro-test.path = "./proc-macro-test" [features] -sysroot-abi = [] +sysroot-abi = ["proc-macro-test/sysroot-abi"] + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/proc-macro-srv/proc-macro-test/Cargo.toml b/crates/proc-macro-srv/proc-macro-test/Cargo.toml new file mode 100644 index 00000000000..55be6bc23bb --- /dev/null +++ b/crates/proc-macro-srv/proc-macro-test/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "proc-macro-test" +version = "0.0.0" +publish = false + +edition = "2021" +license = "MIT OR Apache-2.0" + +[lib] +doctest = false + +[build-dependencies] +cargo_metadata = "0.18.1" + +# local deps +toolchain = { path = "../../toolchain", version = "0.0.0" } + +[features] +sysroot-abi = [] diff --git a/crates/proc-macro-test/build.rs b/crates/proc-macro-srv/proc-macro-test/build.rs similarity index 97% rename from crates/proc-macro-test/build.rs rename to crates/proc-macro-srv/proc-macro-test/build.rs index 7827157865a..7299147686d 100644 --- a/crates/proc-macro-test/build.rs +++ b/crates/proc-macro-srv/proc-macro-test/build.rs @@ -70,6 +70,9 @@ fn main() { // instance to use the same target directory. .arg("--target-dir") .arg(&target_dir); + if cfg!(feature = "sysroot-abi") { + cmd.args(["--features", "sysroot-abi"]); + } if let Ok(target) = std::env::var("TARGET") { cmd.args(["--target", &target]); diff --git a/crates/proc-macro-test/imp/.gitignore b/crates/proc-macro-srv/proc-macro-test/imp/.gitignore similarity index 100% rename from crates/proc-macro-test/imp/.gitignore rename to crates/proc-macro-srv/proc-macro-test/imp/.gitignore diff --git a/crates/proc-macro-test/imp/Cargo.toml b/crates/proc-macro-srv/proc-macro-test/imp/Cargo.toml similarity index 91% rename from crates/proc-macro-test/imp/Cargo.toml rename to crates/proc-macro-srv/proc-macro-test/imp/Cargo.toml index 2a36737cef0..dc94fcd61a4 100644 --- a/crates/proc-macro-test/imp/Cargo.toml +++ b/crates/proc-macro-srv/proc-macro-test/imp/Cargo.toml @@ -9,8 +9,11 @@ publish = false doctest = false proc-macro = true -[workspace] - [dependencies] # this crate should not have any dependencies, since it uses its own workspace, # and its own `Cargo.lock` + +[features] +sysroot-abi = [] + +[workspace] diff --git a/crates/proc-macro-test/imp/src/lib.rs b/crates/proc-macro-srv/proc-macro-test/imp/src/lib.rs similarity index 80% rename from crates/proc-macro-test/imp/src/lib.rs rename to crates/proc-macro-srv/proc-macro-test/imp/src/lib.rs index 32510fba2f8..03241b16be6 100644 --- a/crates/proc-macro-test/imp/src/lib.rs +++ b/crates/proc-macro-srv/proc-macro-test/imp/src/lib.rs @@ -1,6 +1,8 @@ //! Exports a few trivial procedural macros for testing. +#![cfg(any(feature = "sysroot-abi", rust_analyzer))] #![warn(rust_2018_idioms, unused_lifetimes)] +#![feature(proc_macro_span, proc_macro_def_site)] use proc_macro::{Group, Ident, Literal, Punct, Span, TokenStream, TokenTree}; @@ -49,6 +51,29 @@ pub fn fn_like_mk_idents(_args: TokenStream) -> TokenStream { TokenStream::from_iter(trees) } +#[proc_macro] +pub fn fn_like_span_join(args: TokenStream) -> TokenStream { + let args = &mut args.into_iter(); + let first = args.next().unwrap(); + let second = args.next().unwrap(); + TokenStream::from(TokenTree::from(Ident::new_raw( + "joined", + first.span().join(second.span()).unwrap(), + ))) +} + +#[proc_macro] +pub fn fn_like_span_ops(args: TokenStream) -> TokenStream { + let args = &mut args.into_iter(); + let mut first = args.next().unwrap(); + first.set_span(Span::def_site()); + let mut second = args.next().unwrap(); + second.set_span(second.span().resolved_at(Span::def_site())); + let mut third = args.next().unwrap(); + third.set_span(third.span().start()); + TokenStream::from_iter(vec![first, second, third]) +} + #[proc_macro_attribute] pub fn attr_noop(_args: TokenStream, item: TokenStream) -> TokenStream { item diff --git a/crates/proc-macro-test/src/lib.rs b/crates/proc-macro-srv/proc-macro-test/src/lib.rs similarity index 100% rename from crates/proc-macro-test/src/lib.rs rename to crates/proc-macro-srv/proc-macro-test/src/lib.rs diff --git a/crates/proc-macro-srv/src/dylib.rs b/crates/proc-macro-srv/src/dylib.rs index f20e6832f6e..52b4cced5f5 100644 --- a/crates/proc-macro-srv/src/dylib.rs +++ b/crates/proc-macro-srv/src/dylib.rs @@ -11,7 +11,10 @@ use memmap2::Mmap; use object::Object; use paths::AbsPath; -use proc_macro_api::{msg::TokenId, read_dylib_info, ProcMacroKind}; +use proc_macro::bridge; +use proc_macro_api::{read_dylib_info, ProcMacroKind}; + +use crate::ProcMacroSrvSpan; const NEW_REGISTRAR_SYMBOL: &str = "_rustc_proc_macro_decls_"; @@ -147,15 +150,18 @@ pub fn new(lib: &Path) -> Result { Ok(Expander { inner: library }) } - pub fn expand( + pub fn expand( &self, macro_name: &str, - macro_body: &crate::tt::Subtree, - attributes: Option<&crate::tt::Subtree>, - def_site: TokenId, - call_site: TokenId, - mixed_site: TokenId, - ) -> Result { + macro_body: tt::Subtree, + attributes: Option>, + def_site: S, + call_site: S, + mixed_site: S, + ) -> Result, String> + where + ::TokenStream: Default, + { let result = self .inner .proc_macros diff --git a/crates/proc-macro-srv/src/lib.rs b/crates/proc-macro-srv/src/lib.rs index 56529f71d85..7cd6df2df86 100644 --- a/crates/proc-macro-srv/src/lib.rs +++ b/crates/proc-macro-srv/src/lib.rs @@ -32,36 +32,67 @@ }; use proc_macro_api::{ - msg::{self, ExpnGlobals, TokenId, CURRENT_API_VERSION}, + msg::{ + self, deserialize_span_data_index_map, serialize_span_data_index_map, ExpnGlobals, + SpanMode, TokenId, CURRENT_API_VERSION, + }, ProcMacroKind, }; +use span::Span; -mod tt { - pub use proc_macro_api::msg::TokenId; - - pub use ::tt::*; - - pub type Subtree = ::tt::Subtree; - pub type TokenTree = ::tt::TokenTree; - pub type Delimiter = ::tt::Delimiter; - pub type Leaf = ::tt::Leaf; - pub type Literal = ::tt::Literal; - pub type Punct = ::tt::Punct; - pub type Ident = ::tt::Ident; -} +use crate::server::TokenStream; // see `build.rs` include!(concat!(env!("OUT_DIR"), "/rustc_version.rs")); +trait ProcMacroSrvSpan: tt::Span { + type Server: proc_macro::bridge::server::Server>; + fn make_server(call_site: Self, def_site: Self, mixed_site: Self) -> Self::Server; +} + +impl ProcMacroSrvSpan for TokenId { + type Server = server::token_id::TokenIdServer; + + fn make_server(call_site: Self, def_site: Self, mixed_site: Self) -> Self::Server { + Self::Server { interner: &server::SYMBOL_INTERNER, call_site, def_site, mixed_site } + } +} +impl ProcMacroSrvSpan for Span { + type Server = server::rust_analyzer_span::RaSpanServer; + fn make_server(call_site: Self, def_site: Self, mixed_site: Self) -> Self::Server { + Self::Server { + interner: &server::SYMBOL_INTERNER, + call_site, + def_site, + mixed_site, + tracked_env_vars: Default::default(), + tracked_paths: Default::default(), + } + } +} + #[derive(Default)] pub struct ProcMacroSrv { expanders: HashMap<(PathBuf, SystemTime), dylib::Expander>, + span_mode: SpanMode, } const EXPANDER_STACK_SIZE: usize = 8 * 1024 * 1024; impl ProcMacroSrv { - pub fn expand(&mut self, task: msg::ExpandMacro) -> Result { + pub fn set_span_mode(&mut self, span_mode: SpanMode) { + self.span_mode = span_mode; + } + + pub fn span_mode(&self) -> SpanMode { + self.span_mode + } + + pub fn expand( + &mut self, + task: msg::ExpandMacro, + ) -> Result<(msg::FlatTree, Vec), msg::PanicMessage> { + let span_mode = self.span_mode; let expander = self.expander(task.lib.as_ref()).map_err(|err| { debug_assert!(false, "should list macros before asking to expand"); msg::PanicMessage(format!("failed to load macro: {err}")) @@ -71,10 +102,10 @@ pub fn expand(&mut self, task: msg::ExpandMacro) -> Result { let prev_working_dir = std::env::current_dir().ok(); - if let Err(err) = std::env::set_current_dir(&dir) { + if let Err(err) = std::env::set_current_dir(dir) { eprintln!("Failed to set the current working dir to {dir}. Error: {err:?}") } prev_working_dir @@ -83,38 +114,15 @@ pub fn expand(&mut self, task: msg::ExpandMacro) -> Result handle.join(), - Err(e) => std::panic::resume_unwind(Box::new(e)), - }; - - match res { - Ok(res) => res, - Err(e) => std::panic::resume_unwind(e), + let result = match span_mode { + SpanMode::Id => { + expand_id(task, expander, def_site, call_site, mixed_site).map(|it| (it, vec![])) } - }); + SpanMode::RustAnalyzer => { + expand_ra_span(task, expander, def_site, call_site, mixed_site) + } + }; prev_env.rollback(); @@ -155,6 +163,98 @@ fn expander(&mut self, path: &Path) -> Result<&dylib::Expander, String> { } } +fn expand_id( + task: msg::ExpandMacro, + expander: &dylib::Expander, + def_site: usize, + call_site: usize, + mixed_site: usize, +) -> Result { + let def_site = TokenId(def_site as u32); + let call_site = TokenId(call_site as u32); + let mixed_site = TokenId(mixed_site as u32); + + let macro_body = task.macro_body.to_subtree_unresolved(CURRENT_API_VERSION); + let attributes = task.attributes.map(|it| it.to_subtree_unresolved(CURRENT_API_VERSION)); + let result = thread::scope(|s| { + let thread = thread::Builder::new() + .stack_size(EXPANDER_STACK_SIZE) + .name(task.macro_name.clone()) + .spawn_scoped(s, || { + expander + .expand( + &task.macro_name, + macro_body, + attributes, + def_site, + call_site, + mixed_site, + ) + .map(|it| msg::FlatTree::new_raw(&it, CURRENT_API_VERSION)) + }); + let res = match thread { + Ok(handle) => handle.join(), + Err(e) => std::panic::resume_unwind(Box::new(e)), + }; + + match res { + Ok(res) => res, + Err(e) => std::panic::resume_unwind(e), + } + }); + result +} + +fn expand_ra_span( + task: msg::ExpandMacro, + expander: &dylib::Expander, + def_site: usize, + call_site: usize, + mixed_site: usize, +) -> Result<(msg::FlatTree, Vec), String> { + let mut span_data_table = deserialize_span_data_index_map(&task.span_data_table); + + let def_site = span_data_table[def_site]; + let call_site = span_data_table[call_site]; + let mixed_site = span_data_table[mixed_site]; + + let macro_body = task.macro_body.to_subtree_resolved(CURRENT_API_VERSION, &span_data_table); + let attributes = + task.attributes.map(|it| it.to_subtree_resolved(CURRENT_API_VERSION, &span_data_table)); + let result = thread::scope(|s| { + let thread = thread::Builder::new() + .stack_size(EXPANDER_STACK_SIZE) + .name(task.macro_name.clone()) + .spawn_scoped(s, || { + expander + .expand( + &task.macro_name, + macro_body, + attributes, + def_site, + call_site, + mixed_site, + ) + .map(|it| { + ( + msg::FlatTree::new(&it, CURRENT_API_VERSION, &mut span_data_table), + serialize_span_data_index_map(&span_data_table), + ) + }) + }); + let res = match thread { + Ok(handle) => handle.join(), + Err(e) => std::panic::resume_unwind(Box::new(e)), + }; + + match res { + Ok(res) => res, + Err(e) => std::panic::resume_unwind(e), + } + }); + result +} + pub struct PanicMessage { message: Option, } diff --git a/crates/proc-macro-srv/src/proc_macros.rs b/crates/proc-macro-srv/src/proc_macros.rs index 716b85d096d..3fe968c81ca 100644 --- a/crates/proc-macro-srv/src/proc_macros.rs +++ b/crates/proc-macro-srv/src/proc_macros.rs @@ -2,9 +2,9 @@ use libloading::Library; use proc_macro::bridge; -use proc_macro_api::{msg::TokenId, ProcMacroKind, RustCInfo}; +use proc_macro_api::{ProcMacroKind, RustCInfo}; -use crate::{dylib::LoadProcMacroDylibError, server::SYMBOL_INTERNER, tt}; +use crate::{dylib::LoadProcMacroDylibError, ProcMacroSrvSpan}; pub(crate) struct ProcMacros { exported_macros: Vec, @@ -40,19 +40,19 @@ pub(crate) fn from_lib( Err(LoadProcMacroDylibError::AbiMismatch(info.version_string)) } - pub(crate) fn expand( + pub(crate) fn expand( &self, macro_name: &str, - macro_body: &tt::Subtree, - attributes: Option<&tt::Subtree>, - def_site: TokenId, - call_site: TokenId, - mixed_site: TokenId, - ) -> Result { - let parsed_body = crate::server::TokenStream::with_subtree(macro_body.clone()); + macro_body: tt::Subtree, + attributes: Option>, + def_site: S, + call_site: S, + mixed_site: S, + ) -> Result, crate::PanicMessage> { + let parsed_body = crate::server::TokenStream::with_subtree(macro_body); - let parsed_attributes = attributes.map_or(crate::server::TokenStream::new(), |attr| { - crate::server::TokenStream::with_subtree(attr.clone()) + let parsed_attributes = attributes.map_or_else(crate::server::TokenStream::new, |attr| { + crate::server::TokenStream::with_subtree(attr) }); for proc_macro in &self.exported_macros { @@ -62,12 +62,7 @@ pub(crate) fn expand( { let res = client.run( &bridge::server::SameThread, - crate::server::RustAnalyzer { - interner: &SYMBOL_INTERNER, - call_site, - def_site, - mixed_site, - }, + S::make_server(call_site, def_site, mixed_site), parsed_body, false, ); @@ -78,12 +73,7 @@ pub(crate) fn expand( bridge::client::ProcMacro::Bang { name, client } if *name == macro_name => { let res = client.run( &bridge::server::SameThread, - crate::server::RustAnalyzer { - interner: &SYMBOL_INTERNER, - call_site, - def_site, - mixed_site, - }, + S::make_server(call_site, def_site, mixed_site), parsed_body, false, ); @@ -94,13 +84,7 @@ pub(crate) fn expand( bridge::client::ProcMacro::Attr { name, client } if *name == macro_name => { let res = client.run( &bridge::server::SameThread, - crate::server::RustAnalyzer { - interner: &SYMBOL_INTERNER, - - call_site, - def_site, - mixed_site, - }, + S::make_server(call_site, def_site, mixed_site), parsed_attributes, parsed_body, false, @@ -113,7 +97,7 @@ pub(crate) fn expand( } } - Err(bridge::PanicMessage::String("Nothing to expand".to_string()).into()) + Err(bridge::PanicMessage::String(format!("proc-macro `{macro_name}` is missing")).into()) } pub(crate) fn list_macros(&self) -> Vec<(String, ProcMacroKind)> { diff --git a/crates/proc-macro-srv/src/server.rs b/crates/proc-macro-srv/src/server.rs index 917d8a6e26a..1854322ddb5 100644 --- a/crates/proc-macro-srv/src/server.rs +++ b/crates/proc-macro-srv/src/server.rs @@ -8,226 +8,18 @@ //! //! FIXME: No span and source file information is implemented yet -use proc_macro::bridge::{self, server}; +use proc_macro::bridge; mod token_stream; -use proc_macro_api::msg::TokenId; pub use token_stream::TokenStream; -use token_stream::TokenStreamBuilder; +pub mod token_id; +pub mod rust_analyzer_span; mod symbol; pub use symbol::*; +use tt::Spacing; -use std::{ - iter, - ops::{Bound, Range}, -}; - -use crate::tt; - -type Group = tt::Subtree; -type TokenTree = tt::TokenTree; -#[allow(unused)] -type Punct = tt::Punct; -type Spacing = tt::Spacing; -#[allow(unused)] -type Literal = tt::Literal; -type Span = tt::TokenId; - -#[derive(Clone)] -pub struct SourceFile { - // FIXME stub -} - -pub struct FreeFunctions; - -pub struct RustAnalyzer { - // FIXME: store span information here. - pub(crate) interner: SymbolInternerRef, - pub call_site: TokenId, - pub def_site: TokenId, - pub mixed_site: TokenId, -} - -impl server::Types for RustAnalyzer { - type FreeFunctions = FreeFunctions; - type TokenStream = TokenStream; - type SourceFile = SourceFile; - type Span = Span; - type Symbol = Symbol; -} - -impl server::FreeFunctions for RustAnalyzer { - fn injected_env_var(&mut self, _var: &str) -> Option { - None - } - - fn track_env_var(&mut self, _var: &str, _value: Option<&str>) { - // FIXME: track env var accesses - // https://github.com/rust-lang/rust/pull/71858 - } - fn track_path(&mut self, _path: &str) {} - - fn literal_from_str( - &mut self, - s: &str, - ) -> Result, ()> { - // FIXME: keep track of LitKind and Suffix - Ok(bridge::Literal { - kind: bridge::LitKind::Err, - symbol: Symbol::intern(self.interner, s), - suffix: None, - span: self.call_site, - }) - } - - fn emit_diagnostic(&mut self, _: bridge::Diagnostic) { - // FIXME handle diagnostic - } -} - -impl server::TokenStream for RustAnalyzer { - fn is_empty(&mut self, stream: &Self::TokenStream) -> bool { - stream.is_empty() - } - fn from_str(&mut self, src: &str) -> Self::TokenStream { - Self::TokenStream::from_str(src, self.call_site).expect("cannot parse string") - } - fn to_string(&mut self, stream: &Self::TokenStream) -> String { - stream.to_string() - } - fn from_token_tree( - &mut self, - tree: bridge::TokenTree, - ) -> Self::TokenStream { - match tree { - bridge::TokenTree::Group(group) => { - let group = Group { - delimiter: delim_to_internal(group.delimiter, group.span), - token_trees: match group.stream { - Some(stream) => stream.into_iter().collect(), - None => Vec::new(), - }, - }; - let tree = TokenTree::from(group); - Self::TokenStream::from_iter(iter::once(tree)) - } - - bridge::TokenTree::Ident(ident) => { - let text = ident.sym.text(self.interner); - let text = - if ident.is_raw { ::tt::SmolStr::from_iter(["r#", &text]) } else { text }; - let ident: tt::Ident = tt::Ident { text, span: ident.span }; - let leaf = tt::Leaf::from(ident); - let tree = TokenTree::from(leaf); - Self::TokenStream::from_iter(iter::once(tree)) - } - - bridge::TokenTree::Literal(literal) => { - let literal = LiteralFormatter(literal); - let text = literal.with_stringify_parts(self.interner, |parts| { - ::tt::SmolStr::from_iter(parts.iter().copied()) - }); - - let literal = tt::Literal { text, span: literal.0.span }; - let leaf = tt::Leaf::from(literal); - let tree = TokenTree::from(leaf); - Self::TokenStream::from_iter(iter::once(tree)) - } - - bridge::TokenTree::Punct(p) => { - let punct = tt::Punct { - char: p.ch as char, - spacing: if p.joint { Spacing::Joint } else { Spacing::Alone }, - span: p.span, - }; - let leaf = tt::Leaf::from(punct); - let tree = TokenTree::from(leaf); - Self::TokenStream::from_iter(iter::once(tree)) - } - } - } - - fn expand_expr(&mut self, self_: &Self::TokenStream) -> Result { - Ok(self_.clone()) - } - - fn concat_trees( - &mut self, - base: Option, - trees: Vec>, - ) -> Self::TokenStream { - let mut builder = TokenStreamBuilder::new(); - if let Some(base) = base { - builder.push(base); - } - for tree in trees { - builder.push(self.from_token_tree(tree)); - } - builder.build() - } - - fn concat_streams( - &mut self, - base: Option, - streams: Vec, - ) -> Self::TokenStream { - let mut builder = TokenStreamBuilder::new(); - if let Some(base) = base { - builder.push(base); - } - for stream in streams { - builder.push(stream); - } - builder.build() - } - - fn into_trees( - &mut self, - stream: Self::TokenStream, - ) -> Vec> { - stream - .into_iter() - .map(|tree| match tree { - tt::TokenTree::Leaf(tt::Leaf::Ident(ident)) => { - bridge::TokenTree::Ident(bridge::Ident { - sym: Symbol::intern(self.interner, ident.text.trim_start_matches("r#")), - is_raw: ident.text.starts_with("r#"), - span: ident.span, - }) - } - tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) => { - bridge::TokenTree::Literal(bridge::Literal { - // FIXME: handle literal kinds - kind: bridge::LitKind::Err, - symbol: Symbol::intern(self.interner, &lit.text), - // FIXME: handle suffixes - suffix: None, - span: lit.span, - }) - } - tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) => { - bridge::TokenTree::Punct(bridge::Punct { - ch: punct.char as u8, - joint: punct.spacing == Spacing::Joint, - span: punct.span, - }) - } - tt::TokenTree::Subtree(subtree) => bridge::TokenTree::Group(bridge::Group { - delimiter: delim_to_external(subtree.delimiter), - stream: if subtree.token_trees.is_empty() { - None - } else { - Some(subtree.token_trees.into_iter().collect()) - }, - span: bridge::DelimSpan::from_single(subtree.delimiter.open), - }), - }) - .collect() - } -} - -fn delim_to_internal(d: proc_macro::Delimiter, span: bridge::DelimSpan) -> tt::Delimiter { +fn delim_to_internal(d: proc_macro::Delimiter, span: bridge::DelimSpan) -> tt::Delimiter { let kind = match d { proc_macro::Delimiter::Parenthesis => tt::DelimiterKind::Parenthesis, proc_macro::Delimiter::Brace => tt::DelimiterKind::Brace, @@ -237,7 +29,7 @@ fn delim_to_internal(d: proc_macro::Delimiter, span: bridge::DelimSpan) -> tt::Delimiter { open: span.open, close: span.close, kind } } -fn delim_to_external(d: tt::Delimiter) -> proc_macro::Delimiter { +fn delim_to_external(d: tt::Delimiter) -> proc_macro::Delimiter { match d.kind { tt::DelimiterKind::Parenthesis => proc_macro::Delimiter::Parenthesis, tt::DelimiterKind::Brace => proc_macro::Delimiter::Brace, @@ -262,121 +54,9 @@ fn spacing_to_external(spacing: Spacing) -> proc_macro::Spacing { } } -impl server::SourceFile for RustAnalyzer { - // FIXME these are all stubs - fn eq(&mut self, _file1: &Self::SourceFile, _file2: &Self::SourceFile) -> bool { - true - } - fn path(&mut self, _file: &Self::SourceFile) -> String { - String::new() - } - fn is_real(&mut self, _file: &Self::SourceFile) -> bool { - true - } -} +struct LiteralFormatter(bridge::Literal); -impl server::Span for RustAnalyzer { - fn debug(&mut self, span: Self::Span) -> String { - format!("{:?}", span.0) - } - fn source_file(&mut self, _span: Self::Span) -> Self::SourceFile { - SourceFile {} - } - fn save_span(&mut self, _span: Self::Span) -> usize { - // FIXME stub - 0 - } - fn recover_proc_macro_span(&mut self, _id: usize) -> Self::Span { - // FIXME stub - self.call_site - } - /// Recent feature, not yet in the proc_macro - /// - /// See PR: - /// https://github.com/rust-lang/rust/pull/55780 - fn source_text(&mut self, _span: Self::Span) -> Option { - None - } - - fn parent(&mut self, _span: Self::Span) -> Option { - // FIXME handle span - None - } - fn source(&mut self, span: Self::Span) -> Self::Span { - // FIXME handle span - span - } - fn byte_range(&mut self, _span: Self::Span) -> Range { - // FIXME handle span - Range { start: 0, end: 0 } - } - fn join(&mut self, first: Self::Span, _second: Self::Span) -> Option { - // Just return the first span again, because some macros will unwrap the result. - Some(first) - } - fn subspan( - &mut self, - span: Self::Span, - _start: Bound, - _end: Bound, - ) -> Option { - // Just return the span again, because some macros will unwrap the result. - Some(span) - } - fn resolved_at(&mut self, _span: Self::Span, _at: Self::Span) -> Self::Span { - // FIXME handle span - self.call_site - } - - fn end(&mut self, _self_: Self::Span) -> Self::Span { - self.call_site - } - - fn start(&mut self, _self_: Self::Span) -> Self::Span { - self.call_site - } - - fn line(&mut self, _span: Self::Span) -> usize { - // FIXME handle line - 0 - } - - fn column(&mut self, _span: Self::Span) -> usize { - // FIXME handle column - 0 - } -} - -impl server::Symbol for RustAnalyzer { - fn normalize_and_validate_ident(&mut self, string: &str) -> Result { - // FIXME: nfc-normalize and validate idents - Ok(::intern_symbol(string)) - } -} - -impl server::Server for RustAnalyzer { - fn globals(&mut self) -> bridge::ExpnGlobals { - bridge::ExpnGlobals { - def_site: self.def_site, - call_site: self.call_site, - mixed_site: self.mixed_site, - } - } - - fn intern_symbol(ident: &str) -> Self::Symbol { - // FIXME: should be `self.interner` once the proc-macro api allows it. - Symbol::intern(&SYMBOL_INTERNER, &::tt::SmolStr::from(ident)) - } - - fn with_symbol_string(symbol: &Self::Symbol, f: impl FnOnce(&str)) { - // FIXME: should be `self.interner` once the proc-macro api allows it. - f(symbol.text(&SYMBOL_INTERNER).as_str()) - } -} - -struct LiteralFormatter(bridge::Literal); - -impl LiteralFormatter { +impl LiteralFormatter { /// Invokes the callback with a `&[&str]` consisting of each part of the /// literal's representation. This is done to allow the `ToString` and /// `Display` implementations to borrow references to symbol values, and @@ -427,66 +107,3 @@ fn with_symbol_and_suffix( f(symbol.as_str(), suffix.as_str()) } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_ra_server_to_string() { - let s = TokenStream { - token_trees: vec![ - tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { - text: "struct".into(), - span: tt::TokenId(0), - })), - tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { - text: "T".into(), - span: tt::TokenId(0), - })), - tt::TokenTree::Subtree(tt::Subtree { - delimiter: tt::Delimiter { - open: tt::TokenId(0), - close: tt::TokenId(0), - kind: tt::DelimiterKind::Brace, - }, - token_trees: vec![], - }), - ], - }; - - assert_eq!(s.to_string(), "struct T {}"); - } - - #[test] - fn test_ra_server_from_str() { - let subtree_paren_a = tt::TokenTree::Subtree(tt::Subtree { - delimiter: tt::Delimiter { - open: tt::TokenId(0), - close: tt::TokenId(0), - kind: tt::DelimiterKind::Parenthesis, - }, - token_trees: vec![tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { - text: "a".into(), - span: tt::TokenId(0), - }))], - }); - - let t1 = TokenStream::from_str("(a)", tt::TokenId(0)).unwrap(); - assert_eq!(t1.token_trees.len(), 1); - assert_eq!(t1.token_trees[0], subtree_paren_a); - - let t2 = TokenStream::from_str("(a);", tt::TokenId(0)).unwrap(); - assert_eq!(t2.token_trees.len(), 2); - assert_eq!(t2.token_trees[0], subtree_paren_a); - - let underscore = TokenStream::from_str("_", tt::TokenId(0)).unwrap(); - assert_eq!( - underscore.token_trees[0], - tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { - text: "_".into(), - span: tt::TokenId(0), - })) - ); - } -} diff --git a/crates/proc-macro-srv/src/server/rust_analyzer_span.rs b/crates/proc-macro-srv/src/server/rust_analyzer_span.rs new file mode 100644 index 00000000000..bcf3600d273 --- /dev/null +++ b/crates/proc-macro-srv/src/server/rust_analyzer_span.rs @@ -0,0 +1,411 @@ +//! proc-macro server backend based on rust-analyzer's internal span represention +//! This backend is used solely by rust-analyzer as it ties into rust-analyzer internals. +//! +//! It is an unfortunate result of how the proc-macro API works that we need to look into the +//! concrete representation of the spans, and as such, RustRover cannot make use of this unless they +//! change their representation to be compatible with rust-analyzer's. +use std::{ + collections::{HashMap, HashSet}, + iter, + ops::{Bound, Range}, +}; + +use ::tt::{TextRange, TextSize}; +use proc_macro::bridge::{self, server}; +use span::{Span, FIXUP_ERASED_FILE_AST_ID_MARKER}; + +use crate::server::{ + delim_to_external, delim_to_internal, token_stream::TokenStreamBuilder, LiteralFormatter, + Symbol, SymbolInternerRef, SYMBOL_INTERNER, +}; +mod tt { + pub use ::tt::*; + + pub type Subtree = ::tt::Subtree; + pub type TokenTree = ::tt::TokenTree; + pub type Leaf = ::tt::Leaf; + pub type Literal = ::tt::Literal; + pub type Punct = ::tt::Punct; + pub type Ident = ::tt::Ident; +} + +type TokenStream = crate::server::TokenStream; + +#[derive(Clone)] +pub struct SourceFile; +pub struct FreeFunctions; + +pub struct RaSpanServer { + pub(crate) interner: SymbolInternerRef, + // FIXME: Report this back to the caller to track as dependencies + pub tracked_env_vars: HashMap, Option>>, + // FIXME: Report this back to the caller to track as dependencies + pub tracked_paths: HashSet>, + pub call_site: Span, + pub def_site: Span, + pub mixed_site: Span, +} + +impl server::Types for RaSpanServer { + type FreeFunctions = FreeFunctions; + type TokenStream = TokenStream; + type SourceFile = SourceFile; + type Span = Span; + type Symbol = Symbol; +} + +impl server::FreeFunctions for RaSpanServer { + fn injected_env_var(&mut self, _: &str) -> Option { + None + } + + fn track_env_var(&mut self, var: &str, value: Option<&str>) { + self.tracked_env_vars.insert(var.into(), value.map(Into::into)); + } + fn track_path(&mut self, path: &str) { + self.tracked_paths.insert(path.into()); + } + + fn literal_from_str( + &mut self, + s: &str, + ) -> Result, ()> { + // FIXME: keep track of LitKind and Suffix + Ok(bridge::Literal { + kind: bridge::LitKind::Err, + symbol: Symbol::intern(self.interner, s), + suffix: None, + span: self.call_site, + }) + } + + fn emit_diagnostic(&mut self, _: bridge::Diagnostic) { + // FIXME handle diagnostic + } +} + +impl server::TokenStream for RaSpanServer { + fn is_empty(&mut self, stream: &Self::TokenStream) -> bool { + stream.is_empty() + } + fn from_str(&mut self, src: &str) -> Self::TokenStream { + Self::TokenStream::from_str(src, self.call_site).expect("cannot parse string") + } + fn to_string(&mut self, stream: &Self::TokenStream) -> String { + stream.to_string() + } + fn from_token_tree( + &mut self, + tree: bridge::TokenTree, + ) -> Self::TokenStream { + match tree { + bridge::TokenTree::Group(group) => { + let group = tt::Subtree { + delimiter: delim_to_internal(group.delimiter, group.span), + token_trees: match group.stream { + Some(stream) => stream.into_iter().collect(), + None => Vec::new(), + }, + }; + let tree = tt::TokenTree::from(group); + Self::TokenStream::from_iter(iter::once(tree)) + } + + bridge::TokenTree::Ident(ident) => { + let text = ident.sym.text(self.interner); + let text = + if ident.is_raw { ::tt::SmolStr::from_iter(["r#", &text]) } else { text }; + let ident: tt::Ident = tt::Ident { text, span: ident.span }; + let leaf = tt::Leaf::from(ident); + let tree = tt::TokenTree::from(leaf); + Self::TokenStream::from_iter(iter::once(tree)) + } + + bridge::TokenTree::Literal(literal) => { + let literal = LiteralFormatter(literal); + let text = literal.with_stringify_parts(self.interner, |parts| { + ::tt::SmolStr::from_iter(parts.iter().copied()) + }); + + let literal = tt::Literal { text, span: literal.0.span }; + let leaf: tt::Leaf = tt::Leaf::from(literal); + let tree = tt::TokenTree::from(leaf); + Self::TokenStream::from_iter(iter::once(tree)) + } + + bridge::TokenTree::Punct(p) => { + let punct = tt::Punct { + char: p.ch as char, + spacing: if p.joint { tt::Spacing::Joint } else { tt::Spacing::Alone }, + span: p.span, + }; + let leaf = tt::Leaf::from(punct); + let tree = tt::TokenTree::from(leaf); + Self::TokenStream::from_iter(iter::once(tree)) + } + } + } + + fn expand_expr(&mut self, self_: &Self::TokenStream) -> Result { + // FIXME: requires db, more importantly this requires name resolution so we would need to + // eagerly expand this proc-macro, but we can't know that this proc-macro is eager until we + // expand it ... + // This calls for some kind of marker that a proc-macro wants to access this eager API, + // otherwise we need to treat every proc-macro eagerly / or not support this. + Ok(self_.clone()) + } + + fn concat_trees( + &mut self, + base: Option, + trees: Vec>, + ) -> Self::TokenStream { + let mut builder = TokenStreamBuilder::new(); + if let Some(base) = base { + builder.push(base); + } + for tree in trees { + builder.push(self.from_token_tree(tree)); + } + builder.build() + } + + fn concat_streams( + &mut self, + base: Option, + streams: Vec, + ) -> Self::TokenStream { + let mut builder = TokenStreamBuilder::new(); + if let Some(base) = base { + builder.push(base); + } + for stream in streams { + builder.push(stream); + } + builder.build() + } + + fn into_trees( + &mut self, + stream: Self::TokenStream, + ) -> Vec> { + stream + .into_iter() + .map(|tree| match tree { + tt::TokenTree::Leaf(tt::Leaf::Ident(ident)) => { + bridge::TokenTree::Ident(bridge::Ident { + sym: Symbol::intern(self.interner, ident.text.trim_start_matches("r#")), + is_raw: ident.text.starts_with("r#"), + span: ident.span, + }) + } + tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) => { + bridge::TokenTree::Literal(bridge::Literal { + // FIXME: handle literal kinds + kind: bridge::LitKind::Err, + symbol: Symbol::intern(self.interner, &lit.text), + // FIXME: handle suffixes + suffix: None, + span: lit.span, + }) + } + tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) => { + bridge::TokenTree::Punct(bridge::Punct { + ch: punct.char as u8, + joint: punct.spacing == tt::Spacing::Joint, + span: punct.span, + }) + } + tt::TokenTree::Subtree(subtree) => bridge::TokenTree::Group(bridge::Group { + delimiter: delim_to_external(subtree.delimiter), + stream: if subtree.token_trees.is_empty() { + None + } else { + Some(subtree.token_trees.into_iter().collect()) + }, + span: bridge::DelimSpan::from_single(subtree.delimiter.open), + }), + }) + .collect() + } +} + +impl server::SourceFile for RaSpanServer { + // FIXME these are all stubs + fn eq(&mut self, _file1: &Self::SourceFile, _file2: &Self::SourceFile) -> bool { + true + } + fn path(&mut self, _file: &Self::SourceFile) -> String { + String::new() + } + fn is_real(&mut self, _file: &Self::SourceFile) -> bool { + true + } +} + +impl server::Span for RaSpanServer { + fn debug(&mut self, span: Self::Span) -> String { + format!("{:?}", span) + } + fn source_file(&mut self, _span: Self::Span) -> Self::SourceFile { + // FIXME stub, requires db + SourceFile {} + } + fn save_span(&mut self, _span: Self::Span) -> usize { + // FIXME stub, requires builtin quote! implementation + 0 + } + fn recover_proc_macro_span(&mut self, _id: usize) -> Self::Span { + // FIXME stub, requires builtin quote! implementation + self.call_site + } + /// Recent feature, not yet in the proc_macro + /// + /// See PR: + /// https://github.com/rust-lang/rust/pull/55780 + fn source_text(&mut self, _span: Self::Span) -> Option { + // FIXME requires db, needs special handling wrt fixup spans + None + } + + fn parent(&mut self, _span: Self::Span) -> Option { + // FIXME requires db, looks up the parent call site + None + } + fn source(&mut self, span: Self::Span) -> Self::Span { + // FIXME requires db, returns the top level call site + span + } + fn byte_range(&mut self, span: Self::Span) -> Range { + // FIXME requires db to resolve the ast id, THIS IS NOT INCREMENTAL + Range { start: span.range.start().into(), end: span.range.end().into() } + } + fn join(&mut self, first: Self::Span, second: Self::Span) -> Option { + // We can't modify the span range for fixup spans, those are meaningful to fixup, so just + // prefer the non-fixup span. + if first.anchor.ast_id == FIXUP_ERASED_FILE_AST_ID_MARKER { + return Some(second); + } + if second.anchor.ast_id == FIXUP_ERASED_FILE_AST_ID_MARKER { + return Some(first); + } + // FIXME: Once we can talk back to the client, implement a "long join" request for anchors + // that differ in [AstId]s as joining those spans requires resolving the AstIds. + if first.anchor != second.anchor { + return None; + } + // Differing context, we can't merge these so prefer the one that's root + if first.ctx != second.ctx { + if first.ctx.is_root() { + return Some(second); + } else if second.ctx.is_root() { + return Some(first); + } + } + Some(Span { + range: first.range.cover(second.range), + anchor: second.anchor, + ctx: second.ctx, + }) + } + fn subspan( + &mut self, + span: Self::Span, + start: Bound, + end: Bound, + ) -> Option { + // We can't modify the span range for fixup spans, those are meaningful to fixup. + if span.anchor.ast_id == FIXUP_ERASED_FILE_AST_ID_MARKER { + return Some(span); + } + let length = span.range.len().into(); + + let start: u32 = match start { + Bound::Included(lo) => lo, + Bound::Excluded(lo) => lo.checked_add(1)?, + Bound::Unbounded => 0, + } + .try_into() + .ok()?; + + let end: u32 = match end { + Bound::Included(hi) => hi.checked_add(1)?, + Bound::Excluded(hi) => hi, + Bound::Unbounded => span.range.len().into(), + } + .try_into() + .ok()?; + + // Bounds check the values, preventing addition overflow and OOB spans. + let span_start = span.range.start().into(); + if (u32::MAX - start) < span_start + || (u32::MAX - end) < span_start + || start >= end + || end > length + { + return None; + } + + Some(Span { + range: TextRange::new(TextSize::from(start), TextSize::from(end)) + span.range.start(), + ..span + }) + } + + fn resolved_at(&mut self, span: Self::Span, at: Self::Span) -> Self::Span { + Span { ctx: at.ctx, ..span } + } + + fn end(&mut self, span: Self::Span) -> Self::Span { + // We can't modify the span range for fixup spans, those are meaningful to fixup. + if span.anchor.ast_id == FIXUP_ERASED_FILE_AST_ID_MARKER { + return span; + } + Span { range: TextRange::empty(span.range.end()), ..span } + } + + fn start(&mut self, span: Self::Span) -> Self::Span { + // We can't modify the span range for fixup spans, those are meaningful to fixup. + if span.anchor.ast_id == FIXUP_ERASED_FILE_AST_ID_MARKER { + return span; + } + Span { range: TextRange::empty(span.range.start()), ..span } + } + + fn line(&mut self, _span: Self::Span) -> usize { + // FIXME requires db to resolve line index, THIS IS NOT INCREMENTAL + 0 + } + + fn column(&mut self, _span: Self::Span) -> usize { + // FIXME requires db to resolve line index, THIS IS NOT INCREMENTAL + 0 + } +} + +impl server::Symbol for RaSpanServer { + fn normalize_and_validate_ident(&mut self, string: &str) -> Result { + // FIXME: nfc-normalize and validate idents + Ok(::intern_symbol(string)) + } +} + +impl server::Server for RaSpanServer { + fn globals(&mut self) -> bridge::ExpnGlobals { + bridge::ExpnGlobals { + def_site: self.def_site, + call_site: self.call_site, + mixed_site: self.mixed_site, + } + } + + fn intern_symbol(ident: &str) -> Self::Symbol { + // FIXME: should be `self.interner` once the proc-macro api allows it. + Symbol::intern(&SYMBOL_INTERNER, &::tt::SmolStr::from(ident)) + } + + fn with_symbol_string(symbol: &Self::Symbol, f: impl FnOnce(&str)) { + // FIXME: should be `self.interner` once the proc-macro api allows it. + f(symbol.text(&SYMBOL_INTERNER).as_str()) + } +} diff --git a/crates/proc-macro-srv/src/server/token_id.rs b/crates/proc-macro-srv/src/server/token_id.rs new file mode 100644 index 00000000000..12526ad4f3a --- /dev/null +++ b/crates/proc-macro-srv/src/server/token_id.rs @@ -0,0 +1,380 @@ +//! proc-macro server backend based on [`proc_macro_api::msg::TokenId`] as the backing span. +//! This backend is rather inflexible, used by RustRover and older rust-analyzer versions. +use std::{ + iter, + ops::{Bound, Range}, +}; + +use proc_macro::bridge::{self, server}; + +use crate::server::{ + delim_to_external, delim_to_internal, token_stream::TokenStreamBuilder, LiteralFormatter, + Symbol, SymbolInternerRef, SYMBOL_INTERNER, +}; +mod tt { + pub use proc_macro_api::msg::TokenId; + + pub use ::tt::*; + + pub type Subtree = ::tt::Subtree; + pub type TokenTree = ::tt::TokenTree; + pub type Leaf = ::tt::Leaf; + pub type Literal = ::tt::Literal; + pub type Punct = ::tt::Punct; + pub type Ident = ::tt::Ident; +} +type Group = tt::Subtree; +type TokenTree = tt::TokenTree; +#[allow(unused)] +type Punct = tt::Punct; +type Spacing = tt::Spacing; +#[allow(unused)] +type Literal = tt::Literal; +type Span = tt::TokenId; +type TokenStream = crate::server::TokenStream; + +#[derive(Clone)] +pub struct SourceFile; +pub struct FreeFunctions; + +pub struct TokenIdServer { + pub(crate) interner: SymbolInternerRef, + pub call_site: Span, + pub def_site: Span, + pub mixed_site: Span, +} + +impl server::Types for TokenIdServer { + type FreeFunctions = FreeFunctions; + type TokenStream = TokenStream; + type SourceFile = SourceFile; + type Span = Span; + type Symbol = Symbol; +} + +impl server::FreeFunctions for TokenIdServer { + fn injected_env_var(&mut self, _: &str) -> Option { + None + } + fn track_env_var(&mut self, _var: &str, _value: Option<&str>) {} + fn track_path(&mut self, _path: &str) {} + fn literal_from_str( + &mut self, + s: &str, + ) -> Result, ()> { + // FIXME: keep track of LitKind and Suffix + Ok(bridge::Literal { + kind: bridge::LitKind::Err, + symbol: Symbol::intern(self.interner, s), + suffix: None, + span: self.call_site, + }) + } + + fn emit_diagnostic(&mut self, _: bridge::Diagnostic) {} +} + +impl server::TokenStream for TokenIdServer { + fn is_empty(&mut self, stream: &Self::TokenStream) -> bool { + stream.is_empty() + } + fn from_str(&mut self, src: &str) -> Self::TokenStream { + Self::TokenStream::from_str(src, self.call_site).expect("cannot parse string") + } + fn to_string(&mut self, stream: &Self::TokenStream) -> String { + stream.to_string() + } + fn from_token_tree( + &mut self, + tree: bridge::TokenTree, + ) -> Self::TokenStream { + match tree { + bridge::TokenTree::Group(group) => { + let group = Group { + delimiter: delim_to_internal(group.delimiter, group.span), + token_trees: match group.stream { + Some(stream) => stream.into_iter().collect(), + None => Vec::new(), + }, + }; + let tree = TokenTree::from(group); + Self::TokenStream::from_iter(iter::once(tree)) + } + + bridge::TokenTree::Ident(ident) => { + let text = ident.sym.text(self.interner); + let text = + if ident.is_raw { ::tt::SmolStr::from_iter(["r#", &text]) } else { text }; + let ident: tt::Ident = tt::Ident { text, span: ident.span }; + let leaf = tt::Leaf::from(ident); + let tree = TokenTree::from(leaf); + Self::TokenStream::from_iter(iter::once(tree)) + } + + bridge::TokenTree::Literal(literal) => { + let literal = LiteralFormatter(literal); + let text = literal.with_stringify_parts(self.interner, |parts| { + ::tt::SmolStr::from_iter(parts.iter().copied()) + }); + + let literal = tt::Literal { text, span: literal.0.span }; + let leaf = tt::Leaf::from(literal); + let tree = TokenTree::from(leaf); + Self::TokenStream::from_iter(iter::once(tree)) + } + + bridge::TokenTree::Punct(p) => { + let punct = tt::Punct { + char: p.ch as char, + spacing: if p.joint { Spacing::Joint } else { Spacing::Alone }, + span: p.span, + }; + let leaf = tt::Leaf::from(punct); + let tree = TokenTree::from(leaf); + Self::TokenStream::from_iter(iter::once(tree)) + } + } + } + + fn expand_expr(&mut self, self_: &Self::TokenStream) -> Result { + Ok(self_.clone()) + } + + fn concat_trees( + &mut self, + base: Option, + trees: Vec>, + ) -> Self::TokenStream { + let mut builder = TokenStreamBuilder::new(); + if let Some(base) = base { + builder.push(base); + } + for tree in trees { + builder.push(self.from_token_tree(tree)); + } + builder.build() + } + + fn concat_streams( + &mut self, + base: Option, + streams: Vec, + ) -> Self::TokenStream { + let mut builder = TokenStreamBuilder::new(); + if let Some(base) = base { + builder.push(base); + } + for stream in streams { + builder.push(stream); + } + builder.build() + } + + fn into_trees( + &mut self, + stream: Self::TokenStream, + ) -> Vec> { + stream + .into_iter() + .map(|tree| match tree { + tt::TokenTree::Leaf(tt::Leaf::Ident(ident)) => { + bridge::TokenTree::Ident(bridge::Ident { + sym: Symbol::intern(self.interner, ident.text.trim_start_matches("r#")), + is_raw: ident.text.starts_with("r#"), + span: ident.span, + }) + } + tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) => { + bridge::TokenTree::Literal(bridge::Literal { + // FIXME: handle literal kinds + kind: bridge::LitKind::Err, + symbol: Symbol::intern(self.interner, &lit.text), + // FIXME: handle suffixes + suffix: None, + span: lit.span, + }) + } + tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) => { + bridge::TokenTree::Punct(bridge::Punct { + ch: punct.char as u8, + joint: punct.spacing == Spacing::Joint, + span: punct.span, + }) + } + tt::TokenTree::Subtree(subtree) => bridge::TokenTree::Group(bridge::Group { + delimiter: delim_to_external(subtree.delimiter), + stream: if subtree.token_trees.is_empty() { + None + } else { + Some(subtree.token_trees.into_iter().collect()) + }, + span: bridge::DelimSpan::from_single(subtree.delimiter.open), + }), + }) + .collect() + } +} + +impl server::SourceFile for TokenIdServer { + fn eq(&mut self, _file1: &Self::SourceFile, _file2: &Self::SourceFile) -> bool { + true + } + fn path(&mut self, _file: &Self::SourceFile) -> String { + String::new() + } + fn is_real(&mut self, _file: &Self::SourceFile) -> bool { + true + } +} + +impl server::Span for TokenIdServer { + fn debug(&mut self, span: Self::Span) -> String { + format!("{:?}", span.0) + } + fn source_file(&mut self, _span: Self::Span) -> Self::SourceFile { + SourceFile {} + } + fn save_span(&mut self, _span: Self::Span) -> usize { + 0 + } + fn recover_proc_macro_span(&mut self, _id: usize) -> Self::Span { + self.call_site + } + /// Recent feature, not yet in the proc_macro + /// + /// See PR: + /// https://github.com/rust-lang/rust/pull/55780 + fn source_text(&mut self, _span: Self::Span) -> Option { + None + } + + fn parent(&mut self, _span: Self::Span) -> Option { + None + } + fn source(&mut self, span: Self::Span) -> Self::Span { + span + } + fn byte_range(&mut self, _span: Self::Span) -> Range { + Range { start: 0, end: 0 } + } + fn join(&mut self, first: Self::Span, _second: Self::Span) -> Option { + // Just return the first span again, because some macros will unwrap the result. + Some(first) + } + fn subspan( + &mut self, + span: Self::Span, + _start: Bound, + _end: Bound, + ) -> Option { + // Just return the span again, because some macros will unwrap the result. + Some(span) + } + fn resolved_at(&mut self, _span: Self::Span, _at: Self::Span) -> Self::Span { + self.call_site + } + + fn end(&mut self, _self_: Self::Span) -> Self::Span { + self.call_site + } + + fn start(&mut self, _self_: Self::Span) -> Self::Span { + self.call_site + } + + fn line(&mut self, _span: Self::Span) -> usize { + 0 + } + + fn column(&mut self, _span: Self::Span) -> usize { + 0 + } +} + +impl server::Symbol for TokenIdServer { + fn normalize_and_validate_ident(&mut self, string: &str) -> Result { + // FIXME: nfc-normalize and validate idents + Ok(::intern_symbol(string)) + } +} + +impl server::Server for TokenIdServer { + fn globals(&mut self) -> bridge::ExpnGlobals { + bridge::ExpnGlobals { + def_site: self.def_site, + call_site: self.call_site, + mixed_site: self.mixed_site, + } + } + + fn intern_symbol(ident: &str) -> Self::Symbol { + Symbol::intern(&SYMBOL_INTERNER, &::tt::SmolStr::from(ident)) + } + + fn with_symbol_string(symbol: &Self::Symbol, f: impl FnOnce(&str)) { + f(symbol.text(&SYMBOL_INTERNER).as_str()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_ra_server_to_string() { + let s = TokenStream { + token_trees: vec![ + tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { + text: "struct".into(), + span: tt::TokenId(0), + })), + tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { + text: "T".into(), + span: tt::TokenId(0), + })), + tt::TokenTree::Subtree(tt::Subtree { + delimiter: tt::Delimiter { + open: tt::TokenId(0), + close: tt::TokenId(0), + kind: tt::DelimiterKind::Brace, + }, + token_trees: vec![], + }), + ], + }; + + assert_eq!(s.to_string(), "struct T {}"); + } + + #[test] + fn test_ra_server_from_str() { + let subtree_paren_a = tt::TokenTree::Subtree(tt::Subtree { + delimiter: tt::Delimiter { + open: tt::TokenId(0), + close: tt::TokenId(0), + kind: tt::DelimiterKind::Parenthesis, + }, + token_trees: vec![tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { + text: "a".into(), + span: tt::TokenId(0), + }))], + }); + + let t1 = TokenStream::from_str("(a)", tt::TokenId(0)).unwrap(); + assert_eq!(t1.token_trees.len(), 1); + assert_eq!(t1.token_trees[0], subtree_paren_a); + + let t2 = TokenStream::from_str("(a);", tt::TokenId(0)).unwrap(); + assert_eq!(t2.token_trees.len(), 2); + assert_eq!(t2.token_trees[0], subtree_paren_a); + + let underscore = TokenStream::from_str("_", tt::TokenId(0)).unwrap(); + assert_eq!( + underscore.token_trees[0], + tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { + text: "_".into(), + span: tt::TokenId(0), + })) + ); + } +} diff --git a/crates/proc-macro-srv/src/server/token_stream.rs b/crates/proc-macro-srv/src/server/token_stream.rs index 36be8825038..8f669a30494 100644 --- a/crates/proc-macro-srv/src/server/token_stream.rs +++ b/crates/proc-macro-srv/src/server/token_stream.rs @@ -1,20 +1,24 @@ //! TokenStream implementation used by sysroot ABI -use proc_macro_api::msg::TokenId; +use tt::TokenTree; -use crate::tt::{self, TokenTree}; - -#[derive(Debug, Default, Clone)] -pub struct TokenStream { - pub(super) token_trees: Vec, +#[derive(Debug, Clone)] +pub struct TokenStream { + pub(super) token_trees: Vec>, } -impl TokenStream { +impl Default for TokenStream { + fn default() -> Self { + Self { token_trees: vec![] } + } +} + +impl TokenStream { pub(crate) fn new() -> Self { - TokenStream::default() + TokenStream { token_trees: vec![] } } - pub(crate) fn with_subtree(subtree: tt::Subtree) -> Self { + pub(crate) fn with_subtree(subtree: tt::Subtree) -> Self { if subtree.delimiter.kind != tt::DelimiterKind::Invisible { TokenStream { token_trees: vec![TokenTree::Subtree(subtree)] } } else { @@ -22,7 +26,10 @@ pub(crate) fn with_subtree(subtree: tt::Subtree) -> Self { } } - pub(crate) fn into_subtree(self, call_site: TokenId) -> tt::Subtree { + pub(crate) fn into_subtree(self, call_site: S) -> tt::Subtree + where + S: Copy, + { tt::Subtree { delimiter: tt::Delimiter { open: call_site, @@ -39,37 +46,37 @@ pub(super) fn is_empty(&self) -> bool { } /// Creates a token stream containing a single token tree. -impl From for TokenStream { - fn from(tree: TokenTree) -> TokenStream { +impl From> for TokenStream { + fn from(tree: TokenTree) -> TokenStream { TokenStream { token_trees: vec![tree] } } } /// Collects a number of token trees into a single stream. -impl FromIterator for TokenStream { - fn from_iter>(trees: I) -> Self { +impl FromIterator> for TokenStream { + fn from_iter>>(trees: I) -> Self { trees.into_iter().map(TokenStream::from).collect() } } /// A "flattening" operation on token streams, collects token trees /// from multiple token streams into a single stream. -impl FromIterator for TokenStream { - fn from_iter>(streams: I) -> Self { +impl FromIterator> for TokenStream { + fn from_iter>>(streams: I) -> Self { let mut builder = TokenStreamBuilder::new(); streams.into_iter().for_each(|stream| builder.push(stream)); builder.build() } } -impl Extend for TokenStream { - fn extend>(&mut self, trees: I) { +impl Extend> for TokenStream { + fn extend>>(&mut self, trees: I) { self.extend(trees.into_iter().map(TokenStream::from)); } } -impl Extend for TokenStream { - fn extend>(&mut self, streams: I) { +impl Extend> for TokenStream { + fn extend>>(&mut self, streams: I) { for item in streams { for tkn in item { match tkn { @@ -87,22 +94,21 @@ fn extend>(&mut self, streams: I) { } } -pub(super) struct TokenStreamBuilder { - acc: TokenStream, +pub(super) struct TokenStreamBuilder { + acc: TokenStream, } /// pub(super)lic implementation details for the `TokenStream` type, such as iterators. pub(super) mod token_stream { - use proc_macro_api::msg::TokenId; - use super::{tt, TokenStream, TokenTree}; + use super::{TokenStream, TokenTree}; /// An iterator over `TokenStream`'s `TokenTree`s. /// The iteration is "shallow", e.g., the iterator doesn't recurse into delimited groups, /// and returns whole groups as token trees. - impl IntoIterator for TokenStream { - type Item = TokenTree; - type IntoIter = std::vec::IntoIter; + impl IntoIterator for TokenStream { + type Item = TokenTree; + type IntoIter = std::vec::IntoIter>; fn into_iter(self) -> Self::IntoIter { self.token_trees.into_iter() @@ -119,71 +125,34 @@ fn into_iter(self) -> Self::IntoIter { /// NOTE: some errors may cause panics instead of returning `LexError`. We reserve the right to /// change these errors into `LexError`s later. #[rustfmt::skip] - impl /*FromStr for*/ TokenStream { + impl /*FromStr for*/ TokenStream { // type Err = LexError; - pub(crate) fn from_str(src: &str, call_site: TokenId) -> Result { + pub(crate) fn from_str(src: &str, call_site: S) -> Result, LexError> { let subtree = mbe::parse_to_token_tree_static_span(call_site, src).ok_or("Failed to parse from mbe")?; - let subtree = subtree_replace_token_ids_with_call_site(subtree,call_site); Ok(TokenStream::with_subtree(subtree)) } } - impl ToString for TokenStream { + impl ToString for TokenStream { fn to_string(&self) -> String { ::tt::pretty(&self.token_trees) } } - - fn subtree_replace_token_ids_with_call_site( - subtree: tt::Subtree, - call_site: TokenId, - ) -> tt::Subtree { - tt::Subtree { - delimiter: tt::Delimiter { open: call_site, close: call_site, ..subtree.delimiter }, - token_trees: subtree - .token_trees - .into_iter() - .map(|it| token_tree_replace_token_ids_with_call_site(it, call_site)) - .collect(), - } - } - - fn token_tree_replace_token_ids_with_call_site( - tt: tt::TokenTree, - call_site: TokenId, - ) -> tt::TokenTree { - match tt { - tt::TokenTree::Leaf(leaf) => { - tt::TokenTree::Leaf(leaf_replace_token_ids_with_call_site(leaf, call_site)) - } - tt::TokenTree::Subtree(subtree) => { - tt::TokenTree::Subtree(subtree_replace_token_ids_with_call_site(subtree, call_site)) - } - } - } - - fn leaf_replace_token_ids_with_call_site(leaf: tt::Leaf, call_site: TokenId) -> tt::Leaf { - match leaf { - tt::Leaf::Literal(lit) => tt::Leaf::Literal(tt::Literal { span: call_site, ..lit }), - tt::Leaf::Punct(punct) => tt::Leaf::Punct(tt::Punct { span: call_site, ..punct }), - tt::Leaf::Ident(ident) => tt::Leaf::Ident(tt::Ident { span: call_site, ..ident }), - } - } } -impl TokenStreamBuilder { - pub(super) fn new() -> TokenStreamBuilder { +impl TokenStreamBuilder { + pub(super) fn new() -> TokenStreamBuilder { TokenStreamBuilder { acc: TokenStream::new() } } - pub(super) fn push(&mut self, stream: TokenStream) { + pub(super) fn push(&mut self, stream: TokenStream) { self.acc.extend(stream.into_iter()) } - pub(super) fn build(self) -> TokenStream { + pub(super) fn build(self) -> TokenStream { self.acc } } diff --git a/crates/proc-macro-srv/src/tests/mod.rs b/crates/proc-macro-srv/src/tests/mod.rs index b04e3ca19ac..87d832cc76f 100644 --- a/crates/proc-macro-srv/src/tests/mod.rs +++ b/crates/proc-macro-srv/src/tests/mod.rs @@ -8,7 +8,7 @@ #[test] fn test_derive_empty() { - assert_expand("DeriveEmpty", r#"struct S;"#, expect!["SUBTREE $$ 1 1"]); + assert_expand("DeriveEmpty", r#"struct S;"#, expect!["SUBTREE $$ 1 1"], expect!["SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"]); } #[test] @@ -23,6 +23,13 @@ fn test_derive_error() { SUBTREE () 1 1 LITERAL "#[derive(DeriveError)] struct S ;" 1 PUNCH ; [alone] 1"##]], + expect![[r##" + SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + IDENT compile_error SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + PUNCH ! [alone] SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + SUBTREE () SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + LITERAL "#[derive(DeriveError)] struct S ;" SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + PUNCH ; [alone] SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"##]], ); } @@ -40,6 +47,15 @@ fn test_fn_like_macro_noop() { LITERAL 1 1 PUNCH , [alone] 1 SUBTREE [] 1 1"#]], + expect![[r#" + SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + IDENT ident SpanData { range: 0..5, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + PUNCH , [alone] SpanData { range: 5..6, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + LITERAL 0 SpanData { range: 7..8, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + PUNCH , [alone] SpanData { range: 8..9, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + LITERAL 1 SpanData { range: 10..11, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + PUNCH , [alone] SpanData { range: 11..12, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + SUBTREE [] SpanData { range: 13..14, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 14..15, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"#]], ); } @@ -53,6 +69,11 @@ fn test_fn_like_macro_clone_ident_subtree() { IDENT ident 1 PUNCH , [alone] 1 SUBTREE [] 1 1"#]], + expect![[r#" + SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + IDENT ident SpanData { range: 0..5, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + PUNCH , [alone] SpanData { range: 5..6, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + SUBTREE [] SpanData { range: 7..8, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 7..8, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"#]], ); } @@ -64,6 +85,41 @@ fn test_fn_like_macro_clone_raw_ident() { expect![[r#" SUBTREE $$ 1 1 IDENT r#async 1"#]], + expect![[r#" + SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + IDENT r#async SpanData { range: 0..7, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"#]], + ); +} + +#[test] +fn test_fn_like_fn_like_span_join() { + assert_expand( + "fn_like_span_join", + "foo bar", + expect![[r#" + SUBTREE $$ 1 1 + IDENT r#joined 1"#]], + expect![[r#" + SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + IDENT r#joined SpanData { range: 0..11, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"#]], + ); +} + +#[test] +fn test_fn_like_fn_like_span_ops() { + assert_expand( + "fn_like_span_ops", + "set_def_site resolved_at_def_site start_span", + expect![[r#" + SUBTREE $$ 1 1 + IDENT set_def_site 0 + IDENT resolved_at_def_site 1 + IDENT start_span 1"#]], + expect![[r#" + SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + IDENT set_def_site SpanData { range: 0..150, anchor: SpanAnchor(FileId(41), 1), ctx: SyntaxContextId(0) } + IDENT resolved_at_def_site SpanData { range: 13..33, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + IDENT start_span SpanData { range: 34..34, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"#]], ); } @@ -81,6 +137,15 @@ fn test_fn_like_mk_literals() { LITERAL 3.14 1 LITERAL 123i64 1 LITERAL 123 1"#]], + expect![[r#" + SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + LITERAL b"byte_string" SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + LITERAL 'c' SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + LITERAL "string" SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + LITERAL 3.14f64 SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + LITERAL 3.14 SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + LITERAL 123i64 SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + LITERAL 123 SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"#]], ); } @@ -93,6 +158,10 @@ fn test_fn_like_mk_idents() { SUBTREE $$ 1 1 IDENT standard 1 IDENT r#raw 1"#]], + expect![[r#" + SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + IDENT standard SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + IDENT r#raw SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"#]], ); } @@ -113,6 +182,18 @@ fn test_fn_like_macro_clone_literals() { LITERAL 3.14f32 1 PUNCH , [alone] 1 LITERAL "hello bridge" 1"#]], + expect![[r#" + SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + LITERAL 1u16 SpanData { range: 0..4, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + PUNCH , [alone] SpanData { range: 4..5, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + LITERAL 2_u32 SpanData { range: 6..11, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + PUNCH , [alone] SpanData { range: 11..12, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + PUNCH - [alone] SpanData { range: 13..14, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + LITERAL 4i64 SpanData { range: 14..18, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + PUNCH , [alone] SpanData { range: 18..19, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + LITERAL 3.14f32 SpanData { range: 20..27, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + PUNCH , [alone] SpanData { range: 27..28, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + LITERAL "hello bridge" SpanData { range: 29..43, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"#]], ); } @@ -132,6 +213,13 @@ fn test_attr_macro() { SUBTREE () 1 1 LITERAL "#[attr_error(some arguments)] mod m {}" 1 PUNCH ; [alone] 1"##]], + expect![[r##" + SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + IDENT compile_error SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + PUNCH ! [alone] SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + SUBTREE () SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + LITERAL "#[attr_error(some arguments)] mod m {}" SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } + PUNCH ; [alone] SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"##]], ); } @@ -147,6 +235,8 @@ fn list_test_macros() { fn_like_clone_tokens [FuncLike] fn_like_mk_literals [FuncLike] fn_like_mk_idents [FuncLike] + fn_like_span_join [FuncLike] + fn_like_span_ops [FuncLike] attr_noop [Attr] attr_panic [Attr] attr_error [Attr] diff --git a/crates/proc-macro-srv/src/tests/utils.rs b/crates/proc-macro-srv/src/tests/utils.rs index c12096d140c..9a1311d9550 100644 --- a/crates/proc-macro-srv/src/tests/utils.rs +++ b/crates/proc-macro-srv/src/tests/utils.rs @@ -2,47 +2,96 @@ use expect_test::Expect; use proc_macro_api::msg::TokenId; +use span::{ErasedFileAstId, FileId, Span, SpanAnchor, SyntaxContextId}; +use tt::TextRange; use crate::{dylib, proc_macro_test_dylib_path, ProcMacroSrv}; -fn parse_string(code: &str, call_site: TokenId) -> Option { - // This is a bit strange. We need to parse a string into a token stream into - // order to create a tt::SubTree from it in fixtures. `into_subtree` is - // implemented by all the ABIs we have so we arbitrarily choose one ABI to - // write a `parse_string` function for and use that. The tests don't really - // care which ABI we're using as the `into_subtree` function isn't part of - // the ABI and shouldn't change between ABI versions. - crate::server::TokenStream::from_str(code, call_site).ok() +fn parse_string(call_site: TokenId, src: &str) -> crate::server::TokenStream { + crate::server::TokenStream::with_subtree( + mbe::parse_to_token_tree_static_span(call_site, src).unwrap(), + ) } -pub fn assert_expand(macro_name: &str, ra_fixture: &str, expect: Expect) { - assert_expand_impl(macro_name, ra_fixture, None, expect); +fn parse_string_spanned( + anchor: SpanAnchor, + call_site: SyntaxContextId, + src: &str, +) -> crate::server::TokenStream { + crate::server::TokenStream::with_subtree( + mbe::parse_to_token_tree(anchor, call_site, src).unwrap(), + ) } -pub fn assert_expand_attr(macro_name: &str, ra_fixture: &str, attr_args: &str, expect: Expect) { - assert_expand_impl(macro_name, ra_fixture, Some(attr_args), expect); +pub fn assert_expand(macro_name: &str, ra_fixture: &str, expect: Expect, expect_s: Expect) { + assert_expand_impl(macro_name, ra_fixture, None, expect, expect_s); } -fn assert_expand_impl(macro_name: &str, input: &str, attr: Option<&str>, expect: Expect) { +pub fn assert_expand_attr( + macro_name: &str, + ra_fixture: &str, + attr_args: &str, + expect: Expect, + expect_s: Expect, +) { + assert_expand_impl(macro_name, ra_fixture, Some(attr_args), expect, expect_s); +} + +fn assert_expand_impl( + macro_name: &str, + input: &str, + attr: Option<&str>, + expect: Expect, + expect_s: Expect, +) { + let path = proc_macro_test_dylib_path(); + let expander = dylib::Expander::new(&path).unwrap(); + let def_site = TokenId(0); let call_site = TokenId(1); let mixed_site = TokenId(2); - let path = proc_macro_test_dylib_path(); - let expander = dylib::Expander::new(&path).unwrap(); - let fixture = parse_string(input, call_site).unwrap(); - let attr = attr.map(|attr| parse_string(attr, call_site).unwrap().into_subtree(call_site)); + let input_ts = parse_string(call_site, input); + let attr_ts = attr.map(|attr| parse_string(call_site, attr).into_subtree(call_site)); let res = expander .expand( macro_name, - &fixture.into_subtree(call_site), - attr.as_ref(), + input_ts.into_subtree(call_site), + attr_ts, def_site, call_site, mixed_site, ) .unwrap(); expect.assert_eq(&format!("{res:?}")); + + let def_site = Span { + range: TextRange::new(0.into(), 150.into()), + anchor: SpanAnchor { + file_id: FileId::from_raw(41), + ast_id: ErasedFileAstId::from_raw(From::from(1)), + }, + ctx: SyntaxContextId::ROOT, + }; + let call_site = Span { + range: TextRange::new(0.into(), 100.into()), + anchor: SpanAnchor { + file_id: FileId::from_raw(42), + ast_id: ErasedFileAstId::from_raw(From::from(2)), + }, + ctx: SyntaxContextId::ROOT, + }; + let mixed_site = call_site; + + let fixture = parse_string_spanned(call_site.anchor, call_site.ctx, input); + let attr = attr.map(|attr| { + parse_string_spanned(call_site.anchor, call_site.ctx, attr).into_subtree(call_site) + }); + + let res = expander + .expand(macro_name, fixture.into_subtree(call_site), attr, def_site, call_site, mixed_site) + .unwrap(); + expect_s.assert_eq(&format!("{res:?}")); } pub(crate) fn list() -> Vec { diff --git a/crates/proc-macro-test/Cargo.toml b/crates/proc-macro-test/Cargo.toml deleted file mode 100644 index 12d7c07d3ed..00000000000 --- a/crates/proc-macro-test/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "proc-macro-test" -version = "0.0.0" -publish = false - -authors.workspace = true -edition.workspace = true -license.workspace = true -rust-version.workspace = true - -[lib] -doctest = false - -[build-dependencies] -cargo_metadata.workspace = true - -proc-macro-test-impl = { path = "imp", version = "0.0.0" } - -# local deps -toolchain.workspace = true diff --git a/crates/profile/Cargo.toml b/crates/profile/Cargo.toml index 56ce9d11c08..5350023c88f 100644 --- a/crates/profile/Cargo.toml +++ b/crates/profile/Cargo.toml @@ -31,3 +31,6 @@ jemalloc = ["jemalloc-ctl"] # Uncomment to enable for the whole crate graph # default = [ "cpu_profiler" ] + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/project-model/Cargo.toml b/crates/project-model/Cargo.toml index 3e48de6456b..3552ed19162 100644 --- a/crates/project-model/Cargo.toml +++ b/crates/project-model/Cargo.toml @@ -14,8 +14,8 @@ doctest = false [dependencies] anyhow.workspace = true cargo_metadata.workspace = true -rustc-hash = "1.1.0" -semver = "1.0.14" +rustc-hash.workspace = true +semver.workspace = true serde_json.workspace = true serde.workspace = true tracing.workspace = true @@ -33,3 +33,6 @@ toolchain.workspace = true [dev-dependencies] expect-test = "1.4.0" + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/project-model/src/cargo_workspace.rs b/crates/project-model/src/cargo_workspace.rs index ca3d6e0596c..d89c4598afc 100644 --- a/crates/project-model/src/cargo_workspace.rs +++ b/crates/project-model/src/cargo_workspace.rs @@ -330,6 +330,7 @@ pub fn new(mut meta: cargo_metadata::Metadata) -> CargoWorkspace { cargo_metadata::Edition::E2015 => Edition::Edition2015, cargo_metadata::Edition::E2018 => Edition::Edition2018, cargo_metadata::Edition::E2021 => Edition::Edition2021, + cargo_metadata::Edition::_E2024 => Edition::Edition2024, _ => { tracing::error!("Unsupported edition `{:?}`", edition); Edition::CURRENT diff --git a/crates/project-model/src/project_json.rs b/crates/project-model/src/project_json.rs index 931eba11576..cf3231498f3 100644 --- a/crates/project-model/src/project_json.rs +++ b/crates/project-model/src/project_json.rs @@ -213,6 +213,8 @@ enum EditionData { Edition2018, #[serde(rename = "2021")] Edition2021, + #[serde(rename = "2024")] + Edition2024, } impl From for Edition { @@ -221,6 +223,7 @@ fn from(data: EditionData) -> Self { EditionData::Edition2015 => Edition::Edition2015, EditionData::Edition2018 => Edition::Edition2018, EditionData::Edition2021 => Edition::Edition2021, + EditionData::Edition2024 => Edition::Edition2024, } } } diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs index 9333570354a..4057493fa3a 100644 --- a/crates/project-model/src/workspace.rs +++ b/crates/project-model/src/workspace.rs @@ -7,7 +7,7 @@ use anyhow::{format_err, Context}; use base_db::{ CrateDisplayName, CrateGraph, CrateId, CrateName, CrateOrigin, Dependency, DependencyKind, - Edition, Env, FileId, LangCrateOrigin, ProcMacroPaths, ReleaseChannel, TargetLayoutLoadResult, + Edition, Env, FileId, LangCrateOrigin, ProcMacroPaths, TargetLayoutLoadResult, }; use cfg::{CfgDiff, CfgOptions}; use paths::{AbsPath, AbsPathBuf}; @@ -619,7 +619,7 @@ pub fn to_crate_graph( sysroot.as_ref().ok(), extra_env, Err("rust-project.json projects have no target layout set".into()), - toolchain.as_ref().and_then(|it| ReleaseChannel::from_str(it.pre.as_str())), + toolchain.clone(), ) } ProjectWorkspace::Cargo { @@ -644,7 +644,7 @@ pub fn to_crate_graph( Ok(it) => Ok(Arc::from(it.as_str())), Err(it) => Err(Arc::from(it.as_str())), }, - toolchain.as_ref().and_then(|it| ReleaseChannel::from_str(it.pre.as_str())), + toolchain.as_ref(), ), ProjectWorkspace::DetachedFiles { files, sysroot, rustc_cfg } => { detached_files_to_crate_graph( @@ -733,7 +733,7 @@ fn project_json_to_crate_graph( sysroot: Option<&Sysroot>, extra_env: &FxHashMap, target_layout: TargetLayoutLoadResult, - channel: Option, + toolchain: Option, ) -> (CrateGraph, ProcMacroPaths) { let mut res = (CrateGraph::default(), ProcMacroPaths::default()); let (crate_graph, proc_macros) = &mut res; @@ -744,7 +744,7 @@ fn project_json_to_crate_graph( rustc_cfg.clone(), target_layout.clone(), load, - channel, + toolchain.as_ref(), ) }); @@ -807,7 +807,7 @@ fn project_json_to_crate_graph( CrateOrigin::Local { repo: None, name: None } }, target_layout.clone(), - channel, + toolchain.clone(), ); if *is_proc_macro { if let Some(path) = proc_macro_dylib_path.clone() { @@ -853,7 +853,7 @@ fn cargo_to_crate_graph( forced_cfg: Option, build_scripts: &WorkspaceBuildScripts, target_layout: TargetLayoutLoadResult, - channel: Option, + toolchain: Option<&Version>, ) -> (CrateGraph, ProcMacroPaths) { let _p = profile::span("cargo_to_crate_graph"); let mut res = (CrateGraph::default(), ProcMacroPaths::default()); @@ -866,7 +866,7 @@ fn cargo_to_crate_graph( rustc_cfg.clone(), target_layout.clone(), load, - channel, + toolchain, ), None => (SysrootPublicDeps::default(), None), }; @@ -950,7 +950,7 @@ fn cargo_to_crate_graph( is_proc_macro, target_layout.clone(), false, - channel, + toolchain.cloned(), ); if kind == TargetKind::Lib { lib_tgt = Some((crate_id, name.clone())); @@ -1038,7 +1038,7 @@ fn cargo_to_crate_graph( rustc_build_scripts }, target_layout, - channel, + toolchain, ); } } @@ -1117,7 +1117,7 @@ fn handle_rustc_crates( override_cfg: &CfgOverrides, build_scripts: &WorkspaceBuildScripts, target_layout: TargetLayoutLoadResult, - channel: Option, + toolchain: Option<&Version>, ) { let mut rustc_pkg_crates = FxHashMap::default(); // The root package of the rustc-dev component is rustc_driver, so we match that @@ -1172,7 +1172,7 @@ fn handle_rustc_crates( rustc_workspace[tgt].is_proc_macro, target_layout.clone(), true, - channel, + toolchain.cloned(), ); pkg_to_lib_crate.insert(pkg, crate_id); // Add dependencies on core / std / alloc for this crate @@ -1248,7 +1248,7 @@ fn add_target_crate_root( is_proc_macro: bool, target_layout: TargetLayoutLoadResult, rustc_crate: bool, - channel: Option, + toolchain: Option, ) -> CrateId { let edition = pkg.edition; let potential_cfg_options = if pkg.features.is_empty() { @@ -1304,7 +1304,7 @@ fn add_target_crate_root( CrateOrigin::Library { repo: pkg.repository.clone(), name: pkg.name.clone() } }, target_layout, - channel, + toolchain, ); if is_proc_macro { let proc_macro = match build_data.as_ref().map(|it| it.proc_macro_dylib_path.as_ref()) { @@ -1346,7 +1346,7 @@ fn sysroot_to_crate_graph( rustc_cfg: Vec, target_layout: TargetLayoutLoadResult, load: &mut dyn FnMut(&AbsPath) -> Option, - channel: Option, + toolchain: Option<&Version>, ) -> (SysrootPublicDeps, Option) { let _p = profile::span("sysroot_to_crate_graph"); let cfg_options = create_cfg_options(rustc_cfg.clone()); @@ -1357,7 +1357,7 @@ fn sysroot_to_crate_graph( rustc_cfg, cfg_options, target_layout, - channel, + toolchain, crate_graph, sysroot, ), @@ -1380,7 +1380,7 @@ fn sysroot_to_crate_graph( false, CrateOrigin::Lang(LangCrateOrigin::from(&*sysroot[krate].name)), target_layout.clone(), - channel, + toolchain.cloned(), ); Some((krate, crate_id)) }) @@ -1412,7 +1412,7 @@ fn handle_hack_cargo_workspace( rustc_cfg: Vec, cfg_options: CfgOptions, target_layout: Result, Arc>, - channel: Option, + toolchain: Option<&Version>, crate_graph: &mut CrateGraph, sysroot: &Sysroot, ) -> FxHashMap { @@ -1426,7 +1426,7 @@ fn handle_hack_cargo_workspace( Some(cfg_options), &WorkspaceBuildScripts::default(), target_layout, - channel, + toolchain, ); crate_graph.extend(cg, &mut pm); for crate_name in ["std", "alloc", "core"] { diff --git a/crates/project-model/test_data/output/cargo_hello_world_project_model.txt b/crates/project-model/test_data/output/cargo_hello_world_project_model.txt index e98f016ca7d..d8d9e559e5c 100644 --- a/crates/project-model/test_data/output/cargo_hello_world_project_model.txt +++ b/crates/project-model/test_data/output/cargo_hello_world_project_model.txt @@ -62,7 +62,7 @@ target_layout: Err( "target_data_layout not loaded", ), - channel: None, + toolchain: None, }, 1: CrateData { root_file_id: FileId( @@ -135,7 +135,7 @@ target_layout: Err( "target_data_layout not loaded", ), - channel: None, + toolchain: None, }, 2: CrateData { root_file_id: FileId( @@ -208,7 +208,7 @@ target_layout: Err( "target_data_layout not loaded", ), - channel: None, + toolchain: None, }, 3: CrateData { root_file_id: FileId( @@ -281,7 +281,7 @@ target_layout: Err( "target_data_layout not loaded", ), - channel: None, + toolchain: None, }, 4: CrateData { root_file_id: FileId( @@ -350,6 +350,6 @@ target_layout: Err( "target_data_layout not loaded", ), - channel: None, + toolchain: None, }, } \ No newline at end of file diff --git a/crates/project-model/test_data/output/cargo_hello_world_project_model_with_selective_overrides.txt b/crates/project-model/test_data/output/cargo_hello_world_project_model_with_selective_overrides.txt index e98f016ca7d..d8d9e559e5c 100644 --- a/crates/project-model/test_data/output/cargo_hello_world_project_model_with_selective_overrides.txt +++ b/crates/project-model/test_data/output/cargo_hello_world_project_model_with_selective_overrides.txt @@ -62,7 +62,7 @@ target_layout: Err( "target_data_layout not loaded", ), - channel: None, + toolchain: None, }, 1: CrateData { root_file_id: FileId( @@ -135,7 +135,7 @@ target_layout: Err( "target_data_layout not loaded", ), - channel: None, + toolchain: None, }, 2: CrateData { root_file_id: FileId( @@ -208,7 +208,7 @@ target_layout: Err( "target_data_layout not loaded", ), - channel: None, + toolchain: None, }, 3: CrateData { root_file_id: FileId( @@ -281,7 +281,7 @@ target_layout: Err( "target_data_layout not loaded", ), - channel: None, + toolchain: None, }, 4: CrateData { root_file_id: FileId( @@ -350,6 +350,6 @@ target_layout: Err( "target_data_layout not loaded", ), - channel: None, + toolchain: None, }, } \ No newline at end of file diff --git a/crates/project-model/test_data/output/cargo_hello_world_project_model_with_wildcard_overrides.txt b/crates/project-model/test_data/output/cargo_hello_world_project_model_with_wildcard_overrides.txt index 7ecd53572e2..e0ba5ed498f 100644 --- a/crates/project-model/test_data/output/cargo_hello_world_project_model_with_wildcard_overrides.txt +++ b/crates/project-model/test_data/output/cargo_hello_world_project_model_with_wildcard_overrides.txt @@ -61,7 +61,7 @@ target_layout: Err( "target_data_layout not loaded", ), - channel: None, + toolchain: None, }, 1: CrateData { root_file_id: FileId( @@ -133,7 +133,7 @@ target_layout: Err( "target_data_layout not loaded", ), - channel: None, + toolchain: None, }, 2: CrateData { root_file_id: FileId( @@ -205,7 +205,7 @@ target_layout: Err( "target_data_layout not loaded", ), - channel: None, + toolchain: None, }, 3: CrateData { root_file_id: FileId( @@ -277,7 +277,7 @@ target_layout: Err( "target_data_layout not loaded", ), - channel: None, + toolchain: None, }, 4: CrateData { root_file_id: FileId( @@ -346,6 +346,6 @@ target_layout: Err( "target_data_layout not loaded", ), - channel: None, + toolchain: None, }, } \ No newline at end of file diff --git a/crates/project-model/test_data/output/rust_project_hello_world_project_model.txt b/crates/project-model/test_data/output/rust_project_hello_world_project_model.txt index 581a6afc148..e35f0fc7327 100644 --- a/crates/project-model/test_data/output/rust_project_hello_world_project_model.txt +++ b/crates/project-model/test_data/output/rust_project_hello_world_project_model.txt @@ -39,7 +39,7 @@ target_layout: Err( "rust-project.json projects have no target layout set", ), - channel: None, + toolchain: None, }, 1: CrateData { root_file_id: FileId( @@ -72,7 +72,7 @@ target_layout: Err( "rust-project.json projects have no target layout set", ), - channel: None, + toolchain: None, }, 2: CrateData { root_file_id: FileId( @@ -105,7 +105,7 @@ target_layout: Err( "rust-project.json projects have no target layout set", ), - channel: None, + toolchain: None, }, 3: CrateData { root_file_id: FileId( @@ -138,7 +138,7 @@ target_layout: Err( "rust-project.json projects have no target layout set", ), - channel: None, + toolchain: None, }, 4: CrateData { root_file_id: FileId( @@ -188,7 +188,7 @@ target_layout: Err( "rust-project.json projects have no target layout set", ), - channel: None, + toolchain: None, }, 5: CrateData { root_file_id: FileId( @@ -221,7 +221,7 @@ target_layout: Err( "rust-project.json projects have no target layout set", ), - channel: None, + toolchain: None, }, 6: CrateData { root_file_id: FileId( @@ -319,7 +319,7 @@ target_layout: Err( "rust-project.json projects have no target layout set", ), - channel: None, + toolchain: None, }, 7: CrateData { root_file_id: FileId( @@ -352,7 +352,7 @@ target_layout: Err( "rust-project.json projects have no target layout set", ), - channel: None, + toolchain: None, }, 8: CrateData { root_file_id: FileId( @@ -385,7 +385,7 @@ target_layout: Err( "rust-project.json projects have no target layout set", ), - channel: None, + toolchain: None, }, 9: CrateData { root_file_id: FileId( @@ -418,7 +418,7 @@ target_layout: Err( "rust-project.json projects have no target layout set", ), - channel: None, + toolchain: None, }, 10: CrateData { root_file_id: FileId( @@ -495,6 +495,6 @@ target_layout: Err( "rust-project.json projects have no target layout set", ), - channel: None, + toolchain: None, }, } \ No newline at end of file diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml index 39ac338aa1a..ad24d6d28cd 100644 --- a/crates/rust-analyzer/Cargo.toml +++ b/crates/rust-analyzer/Cargo.toml @@ -24,12 +24,12 @@ crossbeam-channel = "0.5.5" dissimilar.workspace = true itertools.workspace = true scip = "0.3.1" -lsp-types = { version = "=0.94.0", features = ["proposed"] } +lsp-types = { version = "=0.95.0", features = ["proposed"] } parking_lot = "0.12.1" xflags = "0.3.0" oorandom = "11.1.3" rayon.workspace = true -rustc-hash = "1.1.0" +rustc-hash.workspace = true serde_json = { workspace = true, features = ["preserve_order"] } serde.workspace = true num_cpus = "1.15.0" @@ -76,6 +76,7 @@ expect-test = "1.4.0" xshell.workspace = true test-utils.workspace = true +test-fixture.workspace = true sourcegen.workspace = true mbe.workspace = true @@ -93,3 +94,6 @@ in-rust-tree = [ "hir-def/in-rust-tree", "hir-ty/in-rust-tree", ] + +[lints] +workspace = true \ 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 8472e49de98..6f40a4c88ed 100644 --- a/crates/rust-analyzer/src/bin/main.rs +++ b/crates/rust-analyzer/src/bin/main.rs @@ -172,7 +172,15 @@ fn run_server() -> anyhow::Result<()> { let (connection, io_threads) = Connection::stdio(); - let (initialize_id, initialize_params) = connection.initialize_start()?; + let (initialize_id, initialize_params) = match connection.initialize_start() { + Ok(it) => it, + Err(e) => { + if e.channel_is_disconnected() { + io_threads.join()?; + } + return Err(e.into()); + } + }; tracing::info!("InitializeParams: {}", initialize_params); let lsp_types::InitializeParams { root_uri, @@ -240,7 +248,12 @@ fn run_server() -> anyhow::Result<()> { let initialize_result = serde_json::to_value(initialize_result).unwrap(); - connection.initialize_finish(initialize_id, initialize_result)?; + if let Err(e) = connection.initialize_finish(initialize_id, initialize_result) { + if e.channel_is_disconnected() { + io_threads.join()?; + } + return Err(e.into()); + } if !config.has_linked_projects() && config.detached_files().is_empty() { config.rediscover_workspaces(); diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs index 8c9261ab05e..94eab97e8fc 100644 --- a/crates/rust-analyzer/src/caps.rs +++ b/crates/rust-analyzer/src/caps.rs @@ -157,6 +157,8 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities { "ssr": true, "workspaceSymbolScopeKindFiltering": true, })), + diagnostic_provider: None, + inline_completion_provider: None, } } diff --git a/crates/rust-analyzer/src/cargo_target_spec.rs b/crates/rust-analyzer/src/cargo_target_spec.rs index 728bade0d0a..0190ca3cab8 100644 --- a/crates/rust-analyzer/src/cargo_target_spec.rs +++ b/crates/rust-analyzer/src/cargo_target_spec.rs @@ -209,7 +209,7 @@ mod tests { use super::*; use cfg::CfgExpr; - use mbe::{syntax_node_to_token_tree, DummyTestSpanMap}; + use mbe::{syntax_node_to_token_tree, DummyTestSpanMap, DUMMY}; use syntax::{ ast::{self, AstNode}, SmolStr, @@ -219,7 +219,7 @@ fn check(cfg: &str, expected_features: &[&str]) { let cfg_expr = { let source_file = ast::SourceFile::parse(cfg).ok().unwrap(); let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); - let tt = syntax_node_to_token_tree(tt.syntax(), &DummyTestSpanMap); + let tt = syntax_node_to_token_tree(tt.syntax(), &DummyTestSpanMap, DUMMY); CfgExpr::parse(&tt) }; diff --git a/crates/rust-analyzer/src/cli/rustc_tests.rs b/crates/rust-analyzer/src/cli/rustc_tests.rs index c89b88ac0f9..b8f6138161e 100644 --- a/crates/rust-analyzer/src/cli/rustc_tests.rs +++ b/crates/rust-analyzer/src/cli/rustc_tests.rs @@ -4,8 +4,8 @@ cell::RefCell, collections::HashMap, fs::read_to_string, panic::AssertUnwindSafe, path::PathBuf, }; -use hir::Crate; -use ide::{AnalysisHost, Change, DiagnosticCode, DiagnosticsConfig}; +use hir::{Change, Crate}; +use ide::{AnalysisHost, DiagnosticCode, DiagnosticsConfig}; use profile::StopWatch; use project_model::{CargoConfig, ProjectWorkspace, RustLibSource, Sysroot}; diff --git a/crates/rust-analyzer/src/cli/scip.rs b/crates/rust-analyzer/src/cli/scip.rs index 30e11402cd8..95c8798d43c 100644 --- a/crates/rust-analyzer/src/cli/scip.rs +++ b/crates/rust-analyzer/src/cli/scip.rs @@ -278,8 +278,8 @@ fn token_to_symbol(token: &TokenStaticData) -> Option { mod test { use super::*; use ide::{AnalysisHost, FilePosition, StaticIndex, TextSize}; - use ide_db::base_db::fixture::ChangeFixture; use scip::symbol::format_symbol; + use test_fixture::ChangeFixture; fn position(ra_fixture: &str) -> (AnalysisHost, FilePosition) { let mut host = AnalysisHost::default(); diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 258f7410639..88fb3708449 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -7,7 +7,11 @@ //! 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, path::PathBuf}; +use std::{ + fmt, iter, + ops::Not, + path::{Path, PathBuf}, +}; use cfg::{CfgAtom, CfgDiff}; use flycheck::FlycheckConfig; @@ -105,6 +109,9 @@ struct ConfigData { /// ``` /// . cargo_buildScripts_overrideCommand: Option> = "null", + /// Rerun proc-macros building/build-scripts running when proc-macro + /// or build-script sources change and are saved. + cargo_buildScripts_rebuildOnSave: bool = "false", /// Use `RUSTC_WRAPPER=rust-analyzer` when running build scripts to /// avoid checking unnecessary things. cargo_buildScripts_useRustcWrapper: bool = "true", @@ -164,15 +171,15 @@ struct ConfigData { /// 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.cargo.check.invocationStrategy#` is set to `once`. + /// 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.cargo.check.overrideCommand#` + /// This config only has an effect when `#rust-analyzer.check.overrideCommand#` /// is set. check_invocationLocation | checkOnSave_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. - /// This config only has an effect when `#rust-analyzer.cargo.check.overrideCommand#` + /// This config only has an effect when `#rust-analyzer.check.overrideCommand#` /// is set. check_invocationStrategy | checkOnSave_invocationStrategy: InvocationStrategy = "\"per_workspace\"", /// Whether to pass `--no-default-features` to Cargo. Defaults to @@ -191,8 +198,8 @@ struct ConfigData { /// 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.check.invocationStrategy#` and - /// `#rust-analyzer.cargo.check.invocationLocation#`. + /// by changing `#rust-analyzer.check.invocationStrategy#` and + /// `#rust-analyzer.check.invocationLocation#`. /// /// An example command would be: /// @@ -917,7 +924,19 @@ impl Config { pub fn has_linked_projects(&self) -> bool { !self.data.linkedProjects.is_empty() } - pub fn linked_projects(&self) -> Vec { + pub fn linked_manifests(&self) -> impl Iterator + '_ { + self.data.linkedProjects.iter().filter_map(|it| match it { + ManifestOrProjectJson::Manifest(p) => Some(&**p), + ManifestOrProjectJson::ProjectJson(_) => None, + }) + } + pub fn has_linked_project_jsons(&self) -> bool { + self.data + .linkedProjects + .iter() + .any(|it| matches!(it, ManifestOrProjectJson::ProjectJson(_))) + } + pub fn linked_or_discovered_projects(&self) -> Vec { match self.data.linkedProjects.as_slice() { [] => { let exclude_dirs: Vec<_> = @@ -952,15 +971,6 @@ pub fn linked_projects(&self) -> Vec { } } - pub fn add_linked_projects(&mut self, linked_projects: Vec) { - let mut linked_projects = linked_projects - .into_iter() - .map(ManifestOrProjectJson::ProjectJson) - .collect::>(); - - self.data.linkedProjects.append(&mut linked_projects); - } - pub fn did_save_text_document_dynamic_registration(&self) -> bool { let caps = try_or_def!(self.caps.text_document.as_ref()?.synchronization.clone()?); caps.did_save == Some(true) && caps.dynamic_registration == Some(true) @@ -1369,6 +1379,10 @@ pub fn check_on_save(&self) -> bool { self.data.checkOnSave } + pub fn script_rebuild_on_save(&self) -> bool { + self.data.cargo_buildScripts_rebuildOnSave + } + pub fn runnables(&self) -> RunnablesConfig { RunnablesConfig { override_cargo: self.data.runnables_command.clone(), diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs index 0f31fe16054..f57a27305f0 100644 --- a/crates/rust-analyzer/src/global_state.rs +++ b/crates/rust-analyzer/src/global_state.rs @@ -7,7 +7,8 @@ use crossbeam_channel::{unbounded, Receiver, Sender}; use flycheck::FlycheckHandle; -use ide::{Analysis, AnalysisHost, Cancellable, Change, FileId}; +use hir::Change; +use ide::{Analysis, AnalysisHost, Cancellable, FileId}; use ide_db::base_db::{CrateId, FileLoader, ProcMacroPaths, SourceDatabase}; use load_cargo::SourceRootConfig; use lsp_types::{SemanticTokens, Url}; diff --git a/crates/rust-analyzer/src/handlers/notification.rs b/crates/rust-analyzer/src/handlers/notification.rs index f9070d27353..7e6219991b6 100644 --- a/crates/rust-analyzer/src/handlers/notification.rs +++ b/crates/rust-analyzer/src/handlers/notification.rs @@ -130,6 +130,13 @@ pub(crate) fn handle_did_save_text_document( state: &mut GlobalState, params: DidSaveTextDocumentParams, ) -> anyhow::Result<()> { + if state.config.script_rebuild_on_save() && state.proc_macro_changed { + // reset the flag + state.proc_macro_changed = false; + // rebuild the proc macros + state.fetch_build_data_queue.request_op("ScriptRebuildOnSave".to_owned(), ()); + } + if let Ok(vfs_path) = from_proto::vfs_path(¶ms.text_document.uri) { // Re-fetch workspaces if a workspace related file has changed if let Some(abs_path) = vfs_path.as_path() { diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index d8a590c8088..f1317ce2b40 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -453,7 +453,7 @@ fn flatten_document_symbol( pub(crate) fn handle_workspace_symbol( snap: GlobalStateSnapshot, params: WorkspaceSymbolParams, -) -> anyhow::Result>> { +) -> anyhow::Result> { let _p = profile::span("handle_workspace_symbol"); let config = snap.config.workspace_symbol(); @@ -479,7 +479,7 @@ pub(crate) fn handle_workspace_symbol( res = exec_query(&snap, query)?; } - return Ok(Some(res)); + return Ok(Some(lsp_types::WorkspaceSymbolResponse::Nested(res))); fn decide_search_scope_and_kind( params: &WorkspaceSymbolParams, @@ -519,13 +519,12 @@ fn decide_search_scope_and_kind( fn exec_query( snap: &GlobalStateSnapshot, query: Query, - ) -> anyhow::Result> { + ) -> anyhow::Result> { let mut res = Vec::new(); for nav in snap.analysis.symbol_search(query)? { let container_name = nav.container_name.as_ref().map(|v| v.to_string()); - #[allow(deprecated)] - let info = SymbolInformation { + let info = lsp_types::WorkspaceSymbol { name: match &nav.alias { Some(alias) => format!("{} (alias for {})", alias, nav.name), None => format!("{}", nav.name), @@ -534,10 +533,11 @@ fn exec_query( .kind .map(to_proto::symbol_kind) .unwrap_or(lsp_types::SymbolKind::VARIABLE), + // FIXME: Set deprecation tags: None, - location: to_proto::location_from_nav(snap, nav)?, container_name, - deprecated: None, + location: lsp_types::OneOf::Left(to_proto::location_from_nav(snap, nav)?), + data: None, }; res.push(info); } @@ -801,7 +801,7 @@ pub(crate) fn handle_runnables( } } None => { - if !snap.config.linked_projects().is_empty() { + if !snap.config.linked_or_discovered_projects().is_empty() { res.push(lsp_ext::Runnable { label: "cargo check --workspace".to_string(), location: None, diff --git a/crates/rust-analyzer/src/integrated_benchmarks.rs b/crates/rust-analyzer/src/integrated_benchmarks.rs index 41ff17f5e43..d94f7cefa60 100644 --- a/crates/rust-analyzer/src/integrated_benchmarks.rs +++ b/crates/rust-analyzer/src/integrated_benchmarks.rs @@ -10,7 +10,8 @@ //! in release mode in VS Code. There's however "rust-analyzer: Copy Run Command Line" //! which you can use to paste the command in terminal and add `--release` manually. -use ide::{CallableSnippets, Change, CompletionConfig, FilePosition, TextSize}; +use hir::Change; +use ide::{CallableSnippets, CompletionConfig, FilePosition, TextSize}; use ide_db::{ imports::insert_use::{ImportGranularity, InsertUseConfig}, SnippetCap, diff --git a/crates/rust-analyzer/src/lsp/ext.rs b/crates/rust-analyzer/src/lsp/ext.rs index ad56899163d..35c8fad3741 100644 --- a/crates/rust-analyzer/src/lsp/ext.rs +++ b/crates/rust-analyzer/src/lsp/ext.rs @@ -627,7 +627,7 @@ pub enum WorkspaceSymbol {} impl Request for WorkspaceSymbol { type Params = WorkspaceSymbolParams; - type Result = Option>; + type Result = Option; const METHOD: &'static str = "workspace/symbol"; } diff --git a/crates/rust-analyzer/src/lsp/to_proto.rs b/crates/rust-analyzer/src/lsp/to_proto.rs index dae560c5de1..7f3c3aa7a15 100644 --- a/crates/rust-analyzer/src/lsp/to_proto.rs +++ b/crates/rust-analyzer/src/lsp/to_proto.rs @@ -857,7 +857,7 @@ pub(crate) fn location_from_nav( ) -> Cancellable { let url = url(snap, nav.file_id); let line_index = snap.file_line_index(nav.file_id)?; - let range = range(&line_index, nav.full_range); + let range = range(&line_index, nav.focus_or_full_range()); let loc = lsp_types::Location::new(url, range); Ok(loc) } diff --git a/crates/rust-analyzer/src/lsp/utils.rs b/crates/rust-analyzer/src/lsp/utils.rs index b388b317599..a4417e4d4a1 100644 --- a/crates/rust-analyzer/src/lsp/utils.rs +++ b/crates/rust-analyzer/src/lsp/utils.rs @@ -171,30 +171,19 @@ pub(crate) fn apply_document_changes( file_contents: impl FnOnce() -> String, mut content_changes: Vec, ) -> String { - // Skip to the last full document change, as it invalidates all previous changes anyways. - let mut start = content_changes - .iter() - .rev() - .position(|change| change.range.is_none()) - .map(|idx| content_changes.len() - idx - 1) - .unwrap_or(0); - - let mut text: String = match content_changes.get_mut(start) { - // peek at the first content change as an optimization - Some(lsp_types::TextDocumentContentChangeEvent { range: None, text, .. }) => { - let text = mem::take(text); - start += 1; - - // The only change is a full document update - if start == content_changes.len() { - return text; + // If at least one of the changes is a full document change, use the last + // of them as the starting point and ignore all previous changes. + let (mut text, content_changes) = + match content_changes.iter().rposition(|change| change.range.is_none()) { + Some(idx) => { + let text = mem::take(&mut content_changes[idx].text); + (text, &content_changes[idx + 1..]) } - text - } - Some(_) => file_contents(), - // we received no content changes - None => return file_contents(), - }; + None => (file_contents(), &content_changes[..]), + }; + if content_changes.is_empty() { + return text; + } let mut line_index = LineIndex { // the index will be overwritten in the bottom loop's first iteration diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index 7ab528f4975..91dc6c2e4b4 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs @@ -16,10 +16,9 @@ use std::{iter, mem}; use flycheck::{FlycheckConfig, FlycheckHandle}; -use hir::db::DefDatabase; -use ide::Change; +use hir::{db::DefDatabase, Change, ProcMacros}; use ide_db::{ - base_db::{salsa::Durability, CrateGraph, ProcMacroPaths, ProcMacros}, + base_db::{salsa::Durability, CrateGraph, ProcMacroPaths}, FxHashMap, }; use itertools::Itertools; @@ -81,7 +80,8 @@ pub(crate) fn update_configuration(&mut self, config: Config) { &self.config.lru_query_capacities().cloned().unwrap_or_default(), ); } - if self.config.linked_projects() != old_config.linked_projects() { + if self.config.linked_or_discovered_projects() != old_config.linked_or_discovered_projects() + { self.fetch_workspaces_queue.request_op("linked projects changed".to_string(), false) } else if self.config.flycheck() != old_config.flycheck() { self.reload_flycheck(); @@ -129,7 +129,7 @@ pub(crate) fn current_status(&self) -> lsp_ext::ServerStatusParams { status.health = lsp_ext::Health::Warning; message.push_str("Auto-reloading is disabled and the workspace has changed, a manual workspace reload is required.\n\n"); } - if self.config.linked_projects().is_empty() + if self.config.linked_or_discovered_projects().is_empty() && self.config.detached_files().is_empty() && self.config.notifications().cargo_toml_not_found { @@ -175,7 +175,21 @@ pub(crate) fn current_status(&self) -> lsp_ext::ServerStatusParams { if let Err(_) = self.fetch_workspace_error() { status.health = lsp_ext::Health::Error; - message.push_str("Failed to load workspaces.\n\n"); + message.push_str("Failed to load workspaces."); + + if self.config.has_linked_projects() { + message.push_str( + "`rust-analyzer.linkedProjects` have been specified, which may be incorrect. Specified project paths:\n", + ); + message.push_str(&format!( + " {}", + self.config.linked_manifests().map(|it| it.display()).format("\n ") + )); + if self.config.has_linked_project_jsons() { + message.push_str("\nAdditionally, one or more project jsons are specified") + } + } + message.push_str("\n\n"); } if !message.is_empty() { @@ -188,7 +202,7 @@ pub(crate) fn fetch_workspaces(&mut self, cause: Cause, force_crate_graph_reload tracing::info!(%cause, "will fetch workspaces"); self.task_pool.handle.spawn_with_sender(ThreadIntent::Worker, { - let linked_projects = self.config.linked_projects(); + let linked_projects = self.config.linked_or_discovered_projects(); let detached_files = self.config.detached_files().to_vec(); let cargo_config = self.config.cargo(); diff --git a/crates/rust-analyzer/tests/slow-tests/main.rs b/crates/rust-analyzer/tests/slow-tests/main.rs index ec8e5c6dd96..78411e2d58d 100644 --- a/crates/rust-analyzer/tests/slow-tests/main.rs +++ b/crates/rust-analyzer/tests/slow-tests/main.rs @@ -832,7 +832,7 @@ fn main() { } #[test] -#[cfg(feature = "sysroot-abi")] +#[cfg(any(feature = "sysroot-abi", rust_analyzer))] fn resolve_proc_macro() { use expect_test::expect; if skip_slow_tests() { diff --git a/crates/rustc-dependencies/Cargo.toml b/crates/rustc-dependencies/Cargo.toml index 1b3b6ec735e..0bf04301df1 100644 --- a/crates/rustc-dependencies/Cargo.toml +++ b/crates/rustc-dependencies/Cargo.toml @@ -18,3 +18,6 @@ ra-ap-rustc_abi = { version = "0.21.0", default-features = false } [features] in-rust-tree = [] + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/sourcegen/Cargo.toml b/crates/sourcegen/Cargo.toml index 0514af8e783..d5ea4c39aa1 100644 --- a/crates/sourcegen/Cargo.toml +++ b/crates/sourcegen/Cargo.toml @@ -2,6 +2,7 @@ name = "sourcegen" version = "0.0.0" description = "TBD" +publish = false authors.workspace = true edition.workspace = true @@ -13,3 +14,6 @@ doctest = false [dependencies] xshell.workspace = true + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/span/Cargo.toml b/crates/span/Cargo.toml new file mode 100644 index 00000000000..69b88b5a17b --- /dev/null +++ b/crates/span/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "span" +version = "0.0.0" +rust-version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true + + +[dependencies] +la-arena.workspace = true +rust-analyzer-salsa.workspace = true + + +# local deps +vfs.workspace = true +syntax.workspace = true +stdx.workspace = true + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/base-db/src/span.rs b/crates/span/src/lib.rs similarity index 72% rename from crates/base-db/src/span.rs rename to crates/span/src/lib.rs index d8990eb7cae..7617acde64a 100644 --- a/crates/base-db/src/span.rs +++ b/crates/span/src/lib.rs @@ -1,10 +1,28 @@ //! File and span related types. -// FIXME: This should probably be moved into its own crate. +// FIXME: This should be moved into its own crate to get rid of the dependency inversion, base-db +// has business depending on tt, tt should depend on a span crate only (which unforunately will have +// to depend on salsa) use std::fmt; use salsa::InternId; -use tt::SyntaxContext; -use vfs::FileId; + +mod map; + +pub use crate::map::{RealSpanMap, SpanMap}; +pub use syntax::{TextRange, TextSize}; +pub use vfs::FileId; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct FilePosition { + pub file_id: FileId, + pub offset: TextSize, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +pub struct FileRange { + pub file_id: FileId, + pub range: TextRange, +} pub type ErasedFileAstId = la_arena::Idx; @@ -12,7 +30,33 @@ pub const ROOT_ERASED_FILE_AST_ID: ErasedFileAstId = la_arena::Idx::from_raw(la_arena::RawIdx::from_u32(0)); -pub type SpanData = tt::SpanData; +/// FileId used as the span for syntax node fixups. Any Span containing this file id is to be +/// considered fake. +pub const FIXUP_ERASED_FILE_AST_ID_MARKER: ErasedFileAstId = + // we pick the second to last for this in case we every consider making this a NonMaxU32, this + // is required to be stable for the proc-macro-server + la_arena::Idx::from_raw(la_arena::RawIdx::from_u32(!0 - 1)); + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub struct SpanData { + /// The text range of this span, relative to the anchor. + /// We need the anchor for incrementality, as storing absolute ranges will require + /// recomputation on every change in a file at all times. + pub range: TextRange, + pub anchor: SpanAnchor, + /// The syntax context of the span. + pub ctx: Ctx, +} +impl Span { + #[deprecated = "dummy spans will panic if surfaced incorrectly, as such they should be replaced appropriately"] + pub const DUMMY: Self = SpanData { + range: TextRange::empty(TextSize::new(0)), + anchor: SpanAnchor { file_id: FileId::BOGUS, ast_id: ROOT_ERASED_FILE_AST_ID }, + ctx: SyntaxContextId::ROOT, + }; +} + +pub type Span = SpanData; #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct SyntaxContextId(InternId); @@ -33,7 +77,15 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { } } } -crate::impl_intern_key!(SyntaxContextId); + +impl salsa::InternKey for SyntaxContextId { + fn from_intern_id(v: salsa::InternId) -> Self { + SyntaxContextId(v) + } + fn as_intern_id(&self) -> salsa::InternId { + self.0 + } +} impl fmt::Display for SyntaxContextId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -41,9 +93,6 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { } } -impl SyntaxContext for SyntaxContextId { - const DUMMY: Self = Self::ROOT; -} // inherent trait impls please tyvm impl SyntaxContextId { pub const ROOT: Self = SyntaxContextId(unsafe { InternId::new_unchecked(0) }); @@ -55,6 +104,14 @@ impl SyntaxContextId { pub fn is_root(self) -> bool { self == Self::ROOT } + + pub fn into_u32(self) -> u32 { + self.0.as_u32() + } + + pub fn from_u32(u32: u32) -> Self { + Self(InternId::from(u32)) + } } #[derive(Copy, Clone, PartialEq, Eq, Hash)] @@ -69,10 +126,6 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { } } -impl tt::SpanAnchor for SpanAnchor { - const DUMMY: Self = SpanAnchor { file_id: FileId::BOGUS, ast_id: ROOT_ERASED_FILE_AST_ID }; -} - /// Input to the analyzer is a set of files, where each file is identified by /// `FileId` and contains source code. However, another source of source code in /// Rust are macros: each macro can be thought of as producing a "temporary @@ -90,6 +143,7 @@ impl tt::SpanAnchor for SpanAnchor { /// The two variants are encoded in a single u32 which are differentiated by the MSB. /// If the MSB is 0, the value represents a `FileId`, otherwise the remaining 31 bits represent a /// `MacroCallId`. +// FIXME: Give this a better fitting name #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct HirFileId(u32); @@ -120,7 +174,15 @@ pub struct MacroFileId { /// `println!("Hello, {}", world)`. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct MacroCallId(salsa::InternId); -crate::impl_intern_key!(MacroCallId); + +impl salsa::InternKey for MacroCallId { + fn from_intern_id(v: salsa::InternId) -> Self { + MacroCallId(v) + } + fn as_intern_id(&self) -> salsa::InternId { + self.0 + } +} impl MacroCallId { pub fn as_file(self) -> HirFileId { diff --git a/crates/mbe/src/token_map.rs b/crates/span/src/map.rs similarity index 59% rename from crates/mbe/src/token_map.rs rename to crates/span/src/map.rs index 7d15812f8cb..d69df91b63e 100644 --- a/crates/mbe/src/token_map.rs +++ b/crates/span/src/map.rs @@ -1,18 +1,23 @@ -//! Mapping between `TokenId`s and the token's position in macro definitions or inputs. +//! A map that maps a span to every position in a file. Usually maps a span to some range of positions. +//! Allows bidirectional lookup. use std::hash::Hash; use stdx::{always, itertools::Itertools}; use syntax::{TextRange, TextSize}; -use tt::Span; +use vfs::FileId; + +use crate::{ErasedFileAstId, Span, SpanAnchor, SyntaxContextId, ROOT_ERASED_FILE_AST_ID}; /// Maps absolute text ranges for the corresponding file to the relevant span data. #[derive(Debug, PartialEq, Eq, Clone, Hash)] -pub struct SpanMap { +pub struct SpanMap { spans: Vec<(TextSize, S)>, + // FIXME: Should be + // spans: Vec<(TextSize, crate::SyntaxContextId)>, } -impl SpanMap { +impl SpanMap { /// Creates a new empty [`SpanMap`]. pub fn empty() -> Self { Self { spans: Vec::new() } @@ -44,7 +49,10 @@ pub fn push(&mut self, offset: TextSize, span: S) { /// Returns all [`TextRange`]s that correspond to the given span. /// /// Note this does a linear search through the entire backing vector. - pub fn ranges_with_span(&self, span: S) -> impl Iterator + '_ { + pub fn ranges_with_span(&self, span: S) -> impl Iterator + '_ + where + S: Eq, + { // FIXME: This should ignore the syntax context! self.spans.iter().enumerate().filter_map(move |(idx, &(end, s))| { if s != span { @@ -74,3 +82,50 @@ pub fn iter(&self) -> impl Iterator + '_ { self.spans.iter().copied() } } + +#[derive(PartialEq, Eq, Hash, Debug)] +pub struct RealSpanMap { + file_id: FileId, + /// Invariant: Sorted vec over TextSize + // FIXME: SortedVec<(TextSize, ErasedFileAstId)>? + pairs: Box<[(TextSize, ErasedFileAstId)]>, + end: TextSize, +} + +impl RealSpanMap { + /// Creates a real file span map that returns absolute ranges (relative ranges to the root ast id). + pub fn absolute(file_id: FileId) -> Self { + RealSpanMap { + file_id, + pairs: Box::from([(TextSize::new(0), ROOT_ERASED_FILE_AST_ID)]), + end: TextSize::new(!0), + } + } + + pub fn from_file( + file_id: FileId, + pairs: Box<[(TextSize, ErasedFileAstId)]>, + end: TextSize, + ) -> Self { + Self { file_id, pairs, end } + } + + pub fn span_for_range(&self, range: TextRange) -> Span { + assert!( + range.end() <= self.end, + "range {range:?} goes beyond the end of the file {:?}", + self.end + ); + let start = range.start(); + let idx = self + .pairs + .binary_search_by(|&(it, _)| it.cmp(&start).then(std::cmp::Ordering::Less)) + .unwrap_err(); + let (offset, ast_id) = self.pairs[idx - 1]; + Span { + range: range - offset, + anchor: SpanAnchor { file_id: self.file_id, ast_id }, + ctx: SyntaxContextId::ROOT, + } + } +} diff --git a/crates/stdx/Cargo.toml b/crates/stdx/Cargo.toml index c914ae2144b..e6014cf812e 100644 --- a/crates/stdx/Cargo.toml +++ b/crates/stdx/Cargo.toml @@ -27,3 +27,6 @@ winapi = { version = "0.3.9", features = ["winerror"] } [features] # Uncomment to enable for the whole crate graph # default = [ "backtrace" ] + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/syntax/Cargo.toml b/crates/syntax/Cargo.toml index 7a7c0d267fe..40a93fec2ce 100644 --- a/crates/syntax/Cargo.toml +++ b/crates/syntax/Cargo.toml @@ -17,7 +17,7 @@ cov-mark = "2.0.0-pre.1" either.workspace = true itertools.workspace = true rowan = "0.15.15" -rustc-hash = "1.1.0" +rustc-hash.workspace = true once_cell = "1.17.0" indexmap.workspace = true smol_str.workspace = true @@ -42,3 +42,6 @@ sourcegen.workspace = true [features] in-rust-tree = ["rustc-dependencies/in-rust-tree"] + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/syntax/fuzz/Cargo.toml b/crates/syntax/fuzz/Cargo.toml index 6070222f1f1..ebf538aa247 100644 --- a/crates/syntax/fuzz/Cargo.toml +++ b/crates/syntax/fuzz/Cargo.toml @@ -24,3 +24,6 @@ path = "fuzz_targets/parser.rs" [[bin]] name = "reparse" path = "fuzz_targets/reparse.rs" + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs index 37d8212042d..4c2878f49f0 100644 --- a/crates/syntax/src/ast/edit_in_place.rs +++ b/crates/syntax/src/ast/edit_in_place.rs @@ -13,7 +13,7 @@ SyntaxNode, SyntaxToken, }; -use super::{HasArgList, HasName}; +use super::{GenericParam, HasArgList, HasName}; pub trait GenericParamsOwnerEdit: ast::HasGenericParams { fn get_or_create_generic_param_list(&self) -> ast::GenericParamList; @@ -272,6 +272,36 @@ pub fn remove_generic_param(&self, generic_param: ast::GenericParam) { } } + /// Find the params corresponded to generic arg + pub fn find_generic_arg(&self, generic_arg: &ast::GenericArg) -> Option { + self.generic_params().find_map(move |param| match (¶m, &generic_arg) { + (ast::GenericParam::LifetimeParam(a), ast::GenericArg::LifetimeArg(b)) => { + (a.lifetime()?.lifetime_ident_token()?.text() + == b.lifetime()?.lifetime_ident_token()?.text()) + .then_some(param) + } + (ast::GenericParam::TypeParam(a), ast::GenericArg::TypeArg(b)) => { + debug_assert_eq!(b.syntax().first_token(), b.syntax().last_token()); + (a.name()?.text() == b.syntax().first_token()?.text()).then_some(param) + } + (ast::GenericParam::ConstParam(a), ast::GenericArg::TypeArg(b)) => { + debug_assert_eq!(b.syntax().first_token(), b.syntax().last_token()); + (a.name()?.text() == b.syntax().first_token()?.text()).then_some(param) + } + _ => None, + }) + } + + /// Removes the corresponding generic arg + pub fn remove_generic_arg(&self, generic_arg: &ast::GenericArg) -> Option { + let param_to_remove = self.find_generic_arg(generic_arg); + + if let Some(param) = ¶m_to_remove { + self.remove_generic_param(param.clone()); + } + param_to_remove + } + /// Constructs a matching [`ast::GenericArgList`] pub fn to_generic_args(&self) -> ast::GenericArgList { let args = self.generic_params().filter_map(|param| match param { @@ -300,6 +330,20 @@ pub fn add_predicate(&self, predicate: ast::WherePred) { } ted::append_child(self.syntax(), predicate.syntax()); } + + pub fn remove_predicate(&self, predicate: ast::WherePred) { + if let Some(previous) = predicate.syntax().prev_sibling() { + if let Some(next_token) = previous.next_sibling_or_token() { + ted::remove_all(next_token..=predicate.syntax().clone().into()); + } + } else if let Some(next) = predicate.syntax().next_sibling() { + if let Some(next_token) = next.prev_sibling_or_token() { + ted::remove_all(predicate.syntax().clone().into()..=next_token); + } + } else { + ted::remove(predicate.syntax()); + } + } } impl ast::TypeParam { @@ -414,6 +458,7 @@ pub fn remove_recursive(self) { u.remove_recursive(); } } + u.remove_unnecessary_braces(); } } diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs index ad63cc55862..2abbfc81f67 100644 --- a/crates/syntax/src/ast/make.rs +++ b/crates/syntax/src/ast/make.rs @@ -207,10 +207,28 @@ fn merge_gen_params( (None, Some(bs)) => Some(bs), (Some(ps), None) => Some(ps), (Some(ps), Some(bs)) => { - for b in bs.generic_params() { - ps.add_generic_param(b); - } - Some(ps) + // make sure lifetime is placed before other generic params + let generic_params = ps.generic_params().merge_by(bs.generic_params(), |_, b| { + !matches!(b, ast::GenericParam::LifetimeParam(_)) + }); + Some(generic_param_list(generic_params)) + } + } +} + +fn merge_where_clause( + ps: Option, + bs: Option, +) -> Option { + match (ps, bs) { + (None, None) => None, + (None, Some(bs)) => Some(bs), + (Some(ps), None) => Some(ps), + (Some(ps), Some(bs)) => { + let preds = where_clause(std::iter::empty()).clone_for_update(); + ps.predicates().for_each(|p| preds.add_predicate(p)); + bs.predicates().for_each(|p| preds.add_predicate(p)); + Some(preds) } } } @@ -251,9 +269,9 @@ pub fn impl_( pub fn impl_trait( is_unsafe: bool, trait_gen_params: Option, - trait_gen_args: Option, + trait_gen_args: Option, type_gen_params: Option, - type_gen_args: Option, + type_gen_args: Option, is_negative: bool, path_type: ast::Type, ty: ast::Type, @@ -262,15 +280,9 @@ pub fn impl_trait( body: Option>>, ) -> ast::Impl { let is_unsafe = if is_unsafe { "unsafe " } else { "" }; - let ty_gen_args = match merge_gen_params(type_gen_params.clone(), type_gen_args) { - Some(pars) => pars.to_generic_args().to_string(), - None => String::new(), - }; - let tr_gen_args = match merge_gen_params(trait_gen_params.clone(), trait_gen_args) { - Some(pars) => pars.to_generic_args().to_string(), - None => String::new(), - }; + let trait_gen_args = trait_gen_args.map(|args| args.to_string()).unwrap_or_default(); + let type_gen_args = type_gen_args.map(|args| args.to_string()).unwrap_or_default(); let gen_params = match merge_gen_params(trait_gen_params, type_gen_params) { Some(pars) => pars.to_string(), @@ -279,25 +291,15 @@ pub fn impl_trait( let is_negative = if is_negative { "! " } else { "" }; - let where_clause = match (ty_where_clause, trait_where_clause) { - (None, None) => " ".to_string(), - (None, Some(tr)) => format!("\n{}\n", tr).to_string(), - (Some(ty), None) => format!("\n{}\n", ty).to_string(), - (Some(ty), Some(tr)) => { - let updated = ty.clone_for_update(); - tr.predicates().for_each(|p| { - ty.add_predicate(p); - }); - format!("\n{}\n", updated).to_string() - } - }; + let where_clause = merge_where_clause(ty_where_clause, trait_where_clause) + .map_or_else(|| " ".to_string(), |wc| format!("\n{}\n", wc)); let body = match body { Some(bd) => bd.iter().map(|elem| elem.to_string()).join(""), None => String::new(), }; - ast_from_text(&format!("{is_unsafe}impl{gen_params} {is_negative}{path_type}{tr_gen_args} for {ty}{ty_gen_args}{where_clause}{{{}}}" , body)) + ast_from_text(&format!("{is_unsafe}impl{gen_params} {is_negative}{path_type}{trait_gen_args} for {ty}{type_gen_args}{where_clause}{{{}}}" , body)) } pub fn impl_trait_type(bounds: ast::TypeBoundList) -> ast::ImplTraitType { @@ -922,6 +924,10 @@ pub fn type_param(name: ast::Name, bounds: Option) -> ast::T ast_from_text(&format!("fn f<{name}{bounds}>() {{ }}")) } +pub fn const_param(name: ast::Name, ty: ast::Type) -> ast::ConstParam { + ast_from_text(&format!("fn f() {{ }}")) +} + pub fn lifetime_param(lifetime: ast::Lifetime) -> ast::LifetimeParam { ast_from_text(&format!("fn f<{lifetime}>() {{ }}")) } @@ -948,9 +954,7 @@ pub fn turbofish_generic_arg_list( ast_from_text(&format!("const S: T::<{args}> = ();")) } -pub(crate) fn generic_arg_list( - args: impl IntoIterator, -) -> ast::GenericArgList { +pub fn generic_arg_list(args: impl IntoIterator) -> ast::GenericArgList { let args = args.into_iter().join(", "); ast_from_text(&format!("const S: T<{args}> = ();")) } diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs index f81dff8840c..a7e4899fb7e 100644 --- a/crates/syntax/src/ast/node_ext.rs +++ b/crates/syntax/src/ast/node_ext.rs @@ -11,7 +11,7 @@ use crate::{ ast::{self, support, AstNode, AstToken, HasAttrs, HasGenericParams, HasName, SyntaxNode}, - NodeOrToken, SmolStr, SyntaxElement, SyntaxToken, TokenText, T, + ted, NodeOrToken, SmolStr, SyntaxElement, SyntaxToken, TokenText, T, }; impl ast::Lifetime { @@ -323,6 +323,10 @@ impl ast::UseTree { pub fn is_simple_path(&self) -> bool { self.use_tree_list().is_none() && self.star_token().is_none() } + + pub fn parent_use_tree_list(&self) -> Option { + self.syntax().parent().and_then(ast::UseTreeList::cast) + } } impl ast::UseTreeList { @@ -340,6 +344,34 @@ pub fn has_inner_comment(&self) -> bool { .find_map(ast::Comment::cast) .is_some() } + + pub fn comma(&self) -> impl Iterator { + self.syntax() + .children_with_tokens() + .filter_map(|it| it.into_token().filter(|it| it.kind() == T![,])) + } + + /// Remove the unnecessary braces in current `UseTreeList` + pub fn remove_unnecessary_braces(mut self) { + let remove_brace_in_use_tree_list = |u: &ast::UseTreeList| { + let use_tree_count = u.use_trees().count(); + if use_tree_count == 1 { + u.l_curly_token().map(ted::remove); + u.r_curly_token().map(ted::remove); + u.comma().for_each(ted::remove); + } + }; + + // take `use crate::{{{{A}}}}` for example + // the below remove the innermost {}, got `use crate::{{{A}}}` + remove_brace_in_use_tree_list(&self); + + // the below remove othe unnecessary {}, got `use crate::A` + while let Some(parent_use_tree_list) = self.parent_use_tree().parent_use_tree_list() { + remove_brace_in_use_tree_list(&parent_use_tree_list); + self = parent_use_tree_list; + } + } } impl ast::Impl { @@ -585,6 +617,16 @@ pub fn generic_param_list(&self) -> Option { } } +impl ast::Type { + pub fn generic_arg_list(&self) -> Option { + if let ast::Type::PathType(path_type) = self { + path_type.path()?.segment()?.generic_arg_list() + } else { + None + } + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum FieldKind { Name(ast::NameRef), diff --git a/crates/test-fixture/Cargo.toml b/crates/test-fixture/Cargo.toml new file mode 100644 index 00000000000..35e39229894 --- /dev/null +++ b/crates/test-fixture/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "test-fixture" +version = "0.0.0" +rust-version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true +publish = false + +[dependencies] +hir-expand.workspace = true +test-utils.workspace = true +tt.workspace = true +cfg.workspace = true +base-db.workspace = true +rustc-hash.workspace = true +span.workspace = true +stdx.workspace = true + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/base-db/src/fixture.rs b/crates/test-fixture/src/lib.rs similarity index 87% rename from crates/base-db/src/fixture.rs rename to crates/test-fixture/src/lib.rs index bfdd21555f0..1a042b2dea2 100644 --- a/crates/base-db/src/fixture.rs +++ b/crates/test-fixture/src/lib.rs @@ -1,27 +1,30 @@ //! A set of high-level utility fixture methods to use in tests. -use std::{mem, str::FromStr, sync}; +use std::{mem, ops::Not, str::FromStr, sync}; +use base_db::{ + CrateDisplayName, CrateGraph, CrateId, CrateName, CrateOrigin, Dependency, DependencyKind, + Edition, Env, FileChange, FileSet, LangCrateOrigin, SourceDatabaseExt, SourceRoot, Version, + VfsPath, +}; use cfg::CfgOptions; +use hir_expand::{ + change::Change, + db::ExpandDatabase, + proc_macro::{ + ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacroKind, ProcMacros, + }, +}; use rustc_hash::FxHashMap; +use span::{FileId, FilePosition, FileRange, Span}; use test_utils::{ extract_range_or_offset, Fixture, FixtureWithProjectMeta, RangeOrOffset, CURSOR_MARKER, ESCAPED_CURSOR_MARKER, }; -use triomphe::Arc; use tt::{Leaf, Subtree, TokenTree}; -use vfs::{file_set::FileSet, VfsPath}; -use crate::{ - input::{CrateName, CrateOrigin, LangCrateOrigin}, - span::SpanData, - Change, CrateDisplayName, CrateGraph, CrateId, Dependency, DependencyKind, Edition, Env, - FileId, FilePosition, FileRange, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, - ProcMacros, ReleaseChannel, SourceDatabaseExt, SourceRoot, SourceRootId, -}; +pub const WORKSPACE: base_db::SourceRootId = base_db::SourceRootId(0); -pub const WORKSPACE: SourceRootId = SourceRootId(0); - -pub trait WithFixture: Default + SourceDatabaseExt + 'static { +pub trait WithFixture: Default + ExpandDatabase + SourceDatabaseExt + 'static { #[track_caller] fn with_single_file(ra_fixture: &str) -> (Self, FileId) { let fixture = ChangeFixture::parse(ra_fixture); @@ -80,6 +83,7 @@ fn with_range_or_offset(ra_fixture: &str) -> (Self, FileId, RangeOrOffset) { let fixture = ChangeFixture::parse(ra_fixture); let mut db = Self::default(); fixture.change.apply(&mut db); + let (file_id, range_or_offset) = fixture .file_position .expect("Could not find file position in fixture. Did you forget to add an `$0`?"); @@ -95,7 +99,7 @@ fn test_crate(&self) -> CrateId { } } -impl WithFixture for DB {} +impl WithFixture for DB {} pub struct ChangeFixture { pub file_position: Option<(FileId, RangeOrOffset)>, @@ -116,13 +120,11 @@ pub fn parse_with_proc_macros( ) -> ChangeFixture { let FixtureWithProjectMeta { fixture, mini_core, proc_macro_names, toolchain } = FixtureWithProjectMeta::parse(ra_fixture); - let toolchain = toolchain - .map(|it| { - ReleaseChannel::from_str(&it) - .unwrap_or_else(|| panic!("unknown release channel found: {it}")) - }) - .unwrap_or(ReleaseChannel::Stable); - let mut change = Change::new(); + let toolchain = Some({ + let channel = toolchain.as_deref().unwrap_or("stable"); + Version::parse(&format!("1.76.0-{channel}")).unwrap() + }); + let mut source_change = FileChange::new(); let mut files = Vec::new(); let mut crate_graph = CrateGraph::default(); @@ -187,9 +189,9 @@ pub fn parse_with_proc_macros( origin, meta.target_data_layout .as_deref() - .map(Arc::from) + .map(From::from) .ok_or_else(|| "target_data_layout unset".into()), - Some(toolchain), + toolchain.clone(), ); let prev = crates.insert(crate_name.clone(), crate_id); assert!(prev.is_none(), "multiple crates with same name: {}", crate_name); @@ -206,7 +208,7 @@ pub fn parse_with_proc_macros( default_target_data_layout = meta.target_data_layout; } - change.change_file(file_id, Some(Arc::from(text))); + source_change.change_file(file_id, Some(text.into())); let path = VfsPath::new_virtual_path(meta.path); file_set.insert(file_id, path); files.push(file_id); @@ -229,7 +231,7 @@ pub fn parse_with_proc_macros( default_target_data_layout .map(|it| it.into()) .ok_or_else(|| "target_data_layout unset".into()), - Some(toolchain), + toolchain.clone(), ); } else { for (from, to, prelude) in crate_deps { @@ -261,7 +263,7 @@ pub fn parse_with_proc_macros( fs.insert(core_file, VfsPath::new_virtual_path("/sysroot/core/lib.rs".to_string())); roots.push(SourceRoot::new_library(fs)); - change.change_file(core_file, Some(Arc::from(mini_core.source_code()))); + source_change.change_file(core_file, Some(mini_core.source_code().into())); let all_crates = crate_graph.crates_in_topological_order(); @@ -276,7 +278,7 @@ pub fn parse_with_proc_macros( false, CrateOrigin::Lang(LangCrateOrigin::Core), target_layout.clone(), - Some(toolchain), + toolchain.clone(), ); for krate in all_crates { @@ -306,7 +308,7 @@ pub fn parse_with_proc_macros( ); roots.push(SourceRoot::new_library(fs)); - change.change_file(proc_lib_file, Some(Arc::from(source))); + source_change.change_file(proc_lib_file, Some(source.into())); let all_crates = crate_graph.crates_in_topological_order(); @@ -321,7 +323,7 @@ pub fn parse_with_proc_macros( true, CrateOrigin::Local { repo: None, name: None }, target_layout, - Some(toolchain), + toolchain, ); proc_macros.insert(proc_macros_crate, Ok(proc_macro)); @@ -344,11 +346,17 @@ pub fn parse_with_proc_macros( SourceRootKind::Library => SourceRoot::new_library(mem::take(&mut file_set)), }; roots.push(root); - change.set_roots(roots); - change.set_crate_graph(crate_graph); - change.set_proc_macros(proc_macros); + source_change.set_roots(roots); + source_change.set_crate_graph(crate_graph); - ChangeFixture { file_position, files, change } + ChangeFixture { + file_position, + files, + change: Change { + source_change, + proc_macros: proc_macros.is_empty().not().then(|| proc_macros), + }, + } } } @@ -364,7 +372,7 @@ pub fn identity(_attr: TokenStream, item: TokenStream) -> TokenStream { .into(), ProcMacro { name: "identity".into(), - kind: crate::ProcMacroKind::Attr, + kind: ProcMacroKind::Attr, expander: sync::Arc::new(IdentityProcMacroExpander), }, ), @@ -378,7 +386,7 @@ pub fn derive_identity(item: TokenStream) -> TokenStream { .into(), ProcMacro { name: "DeriveIdentity".into(), - kind: crate::ProcMacroKind::CustomDerive, + kind: ProcMacroKind::CustomDerive, expander: sync::Arc::new(IdentityProcMacroExpander), }, ), @@ -392,7 +400,7 @@ pub fn input_replace(attr: TokenStream, _item: TokenStream) -> TokenStream { .into(), ProcMacro { name: "input_replace".into(), - kind: crate::ProcMacroKind::Attr, + kind: ProcMacroKind::Attr, expander: sync::Arc::new(AttributeInputReplaceProcMacroExpander), }, ), @@ -406,7 +414,7 @@ pub fn mirror(input: TokenStream) -> TokenStream { .into(), ProcMacro { name: "mirror".into(), - kind: crate::ProcMacroKind::FuncLike, + kind: ProcMacroKind::FuncLike, expander: sync::Arc::new(MirrorProcMacroExpander), }, ), @@ -420,7 +428,7 @@ pub fn shorten(input: TokenStream) -> TokenStream { .into(), ProcMacro { name: "shorten".into(), - kind: crate::ProcMacroKind::FuncLike, + kind: ProcMacroKind::FuncLike, expander: sync::Arc::new(ShortenProcMacroExpander), }, ), @@ -539,13 +547,13 @@ fn parse_crate( impl ProcMacroExpander for IdentityProcMacroExpander { fn expand( &self, - subtree: &Subtree, - _: Option<&Subtree>, + subtree: &Subtree, + _: Option<&Subtree>, _: &Env, - _: SpanData, - _: SpanData, - _: SpanData, - ) -> Result, ProcMacroExpansionError> { + _: Span, + _: Span, + _: Span, + ) -> Result, ProcMacroExpansionError> { Ok(subtree.clone()) } } @@ -556,13 +564,13 @@ fn expand( impl ProcMacroExpander for AttributeInputReplaceProcMacroExpander { fn expand( &self, - _: &Subtree, - attrs: Option<&Subtree>, + _: &Subtree, + attrs: Option<&Subtree>, _: &Env, - _: SpanData, - _: SpanData, - _: SpanData, - ) -> Result, ProcMacroExpansionError> { + _: Span, + _: Span, + _: Span, + ) -> Result, ProcMacroExpansionError> { attrs .cloned() .ok_or_else(|| ProcMacroExpansionError::Panic("Expected attribute input".into())) @@ -574,14 +582,14 @@ fn expand( impl ProcMacroExpander for MirrorProcMacroExpander { fn expand( &self, - input: &Subtree, - _: Option<&Subtree>, + input: &Subtree, + _: Option<&Subtree>, _: &Env, - _: SpanData, - _: SpanData, - _: SpanData, - ) -> Result, ProcMacroExpansionError> { - fn traverse(input: &Subtree) -> Subtree { + _: Span, + _: Span, + _: Span, + ) -> Result, ProcMacroExpansionError> { + fn traverse(input: &Subtree) -> Subtree { let mut token_trees = vec![]; for tt in input.token_trees.iter().rev() { let tt = match tt { @@ -604,16 +612,16 @@ fn traverse(input: &Subtree) -> Subtree { impl ProcMacroExpander for ShortenProcMacroExpander { fn expand( &self, - input: &Subtree, - _: Option<&Subtree>, + input: &Subtree, + _: Option<&Subtree>, _: &Env, - _: SpanData, - _: SpanData, - _: SpanData, - ) -> Result, ProcMacroExpansionError> { + _: Span, + _: Span, + _: Span, + ) -> Result, ProcMacroExpansionError> { return Ok(traverse(input)); - fn traverse(input: &Subtree) -> Subtree { + fn traverse(input: &Subtree) -> Subtree { let token_trees = input .token_trees .iter() @@ -625,7 +633,7 @@ fn traverse(input: &Subtree) -> Subtree { Subtree { delimiter: input.delimiter, token_trees } } - fn modify_leaf(leaf: &Leaf) -> Leaf { + fn modify_leaf(leaf: &Leaf) -> Leaf { let mut leaf = leaf.clone(); match &mut leaf { Leaf::Literal(it) => { diff --git a/crates/test-utils/Cargo.toml b/crates/test-utils/Cargo.toml index 438b599ffaa..56067d83417 100644 --- a/crates/test-utils/Cargo.toml +++ b/crates/test-utils/Cargo.toml @@ -15,7 +15,10 @@ doctest = false # Avoid adding deps here, this crate is widely used in tests it should compile fast! dissimilar = "1.0.7" text-size.workspace = true -rustc-hash = "1.1.0" +rustc-hash.workspace = true stdx.workspace = true profile.workspace = true + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs index f766747d707..1f3136404c6 100644 --- a/crates/test-utils/src/minicore.rs +++ b/crates/test-utils/src/minicore.rs @@ -1381,6 +1381,7 @@ macro_rules! asm { // region:assert #[macro_export] #[rustc_builtin_macro] + #[allow_internal_unstable(core_panic, edition_panic, generic_assert_internals)] macro_rules! assert { ($($arg:tt)*) => { /* compiler built-in */ @@ -1389,6 +1390,7 @@ macro_rules! assert { // endregion:assert // region:fmt + #[allow_internal_unstable(fmt_internals, const_fmt_arguments_new)] #[macro_export] #[rustc_builtin_macro] macro_rules! const_format_args { @@ -1396,6 +1398,7 @@ macro_rules! const_format_args { ($fmt:expr, $($args:tt)*) => {{ /* compiler built-in */ }}; } + #[allow_internal_unstable(fmt_internals)] #[macro_export] #[rustc_builtin_macro] macro_rules! format_args { @@ -1403,6 +1406,7 @@ macro_rules! format_args { ($fmt:expr, $($args:tt)*) => {{ /* compiler built-in */ }}; } + #[allow_internal_unstable(fmt_internals)] #[macro_export] #[rustc_builtin_macro] macro_rules! format_args_nl { diff --git a/crates/text-edit/Cargo.toml b/crates/text-edit/Cargo.toml index 4620cc72d0a..f745674794c 100644 --- a/crates/text-edit/Cargo.toml +++ b/crates/text-edit/Cargo.toml @@ -14,3 +14,6 @@ doctest = false [dependencies] itertools.workspace = true text-size.workspace = true + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/toolchain/Cargo.toml b/crates/toolchain/Cargo.toml index a283f9a8842..f9b120772f0 100644 --- a/crates/toolchain/Cargo.toml +++ b/crates/toolchain/Cargo.toml @@ -13,3 +13,6 @@ doctest = false [dependencies] home = "0.5.4" + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/tt/Cargo.toml b/crates/tt/Cargo.toml index 57222449790..77683fd48af 100644 --- a/crates/tt/Cargo.toml +++ b/crates/tt/Cargo.toml @@ -16,3 +16,9 @@ smol_str.workspace = true text-size.workspace = true stdx.workspace = true + +# FIXME: Remove this dependency once the `Span` trait is gone (that is once Span::DUMMY has been removed) +span.workspace = true + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/tt/src/lib.rs b/crates/tt/src/lib.rs index 481d575403a..b3b0eeda75a 100644 --- a/crates/tt/src/lib.rs +++ b/crates/tt/src/lib.rs @@ -11,46 +11,10 @@ pub use smol_str::SmolStr; pub use text_size::{TextRange, TextSize}; -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] -pub struct SpanData { - /// The text range of this span, relative to the anchor. - /// We need the anchor for incrementality, as storing absolute ranges will require - /// recomputation on every change in a file at all times. - pub range: TextRange, - pub anchor: Anchor, - /// The syntax context of the span. - pub ctx: Ctx, -} +pub trait Span: std::fmt::Debug + Copy + Sized + Eq {} -impl Span for SpanData { - #[allow(deprecated)] - const DUMMY: Self = SpanData { - range: TextRange::empty(TextSize::new(0)), - anchor: Anchor::DUMMY, - ctx: Ctx::DUMMY, - }; -} - -pub trait Span: std::fmt::Debug + Copy + Sized + Eq { - // FIXME: Should not exist. Dummy spans will always be wrong if they leak somewhere. Instead, - // the call site or def site spans should be used in relevant places, its just that we don't - // expose those everywhere in the yet. - const DUMMY: Self; -} - -// FIXME: Should not exist -pub trait SpanAnchor: - std::fmt::Debug + Copy + Sized + Eq + Copy + fmt::Debug + std::hash::Hash -{ - #[deprecated(note = "this should not exist")] - const DUMMY: Self; -} - -// FIXME: Should not exist -pub trait SyntaxContext: std::fmt::Debug + Copy + Sized + Eq { - #[deprecated(note = "this should not exist")] - const DUMMY: Self; -} +impl Span for span::SpanData where span::SpanData: std::fmt::Debug + Copy + Sized + Eq +{} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum TokenTree { @@ -66,15 +30,7 @@ pub const fn empty(span: S) -> Self { }) } - pub fn subtree_or_wrap(self) -> Subtree { - match self { - TokenTree::Leaf(_) => { - Subtree { delimiter: Delimiter::DUMMY_INVISIBLE, token_trees: vec![self] } - } - TokenTree::Subtree(s) => s, - } - } - pub fn subtree_or_wrap2(self, span: DelimSpan) -> Subtree { + pub fn subtree_or_wrap(self, span: DelimSpan) -> Subtree { match self { TokenTree::Leaf(_) => Subtree { delimiter: Delimiter::invisible_delim_spanned(span), @@ -83,6 +39,13 @@ pub fn subtree_or_wrap2(self, span: DelimSpan) -> Subtree { TokenTree::Subtree(s) => s, } } + + pub fn first_span(&self) -> S { + match self { + TokenTree::Leaf(l) => *l.span(), + TokenTree::Subtree(s) => s.delimiter.open, + } + } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -134,11 +97,6 @@ pub struct DelimSpan { pub close: S, } -impl DelimSpan { - // FIXME should not exist - pub const DUMMY: Self = Self { open: S::DUMMY, close: S::DUMMY }; -} - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct Delimiter { pub open: S, @@ -147,15 +105,6 @@ pub struct Delimiter { } impl Delimiter { - // FIXME should not exist - pub const DUMMY_INVISIBLE: Self = - Self { open: S::DUMMY, close: S::DUMMY, kind: DelimiterKind::Invisible }; - - // FIXME should not exist - pub const fn dummy_invisible() -> Self { - Self::DUMMY_INVISIBLE - } - pub const fn invisible_spanned(span: S) -> Self { Delimiter { open: span, close: span, kind: DelimiterKind::Invisible } } diff --git a/crates/vfs-notify/Cargo.toml b/crates/vfs-notify/Cargo.toml index fe6cb0a2c3f..a6d5027c3a6 100644 --- a/crates/vfs-notify/Cargo.toml +++ b/crates/vfs-notify/Cargo.toml @@ -20,3 +20,6 @@ notify = "6.1.1" stdx.workspace = true vfs.workspace = true paths.workspace = true + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/vfs/Cargo.toml b/crates/vfs/Cargo.toml index 11409f2eb81..c88f3466559 100644 --- a/crates/vfs/Cargo.toml +++ b/crates/vfs/Cargo.toml @@ -12,10 +12,13 @@ rust-version.workspace = true doctest = false [dependencies] -rustc-hash = "1.1.0" +rustc-hash.workspace = true fst = "0.4.7" indexmap.workspace = true nohash-hasher.workspace = true paths.workspace = true stdx.workspace = true + +[lints] +workspace = true \ No newline at end of file diff --git a/docs/dev/architecture.md b/docs/dev/architecture.md index b7d585cafb3..4303a800a04 100644 --- a/docs/dev/architecture.md +++ b/docs/dev/architecture.md @@ -134,29 +134,29 @@ This is to enable parallel parsing of all files. **Architecture Invariant:** Syntax trees are by design incomplete and do not enforce well-formedness. If an AST method returns an `Option`, it *can* be `None` at runtime, even if this is forbidden by the grammar. -### `crates/base_db` +### `crates/base-db` We use the [salsa](https://github.com/salsa-rs/salsa) crate for incremental and on-demand computation. Roughly, you can think of salsa as a key-value store, but it can also compute derived values using specified functions. -The `base_db` crate provides basic infrastructure for interacting with salsa. +The `base-db` crate provides basic infrastructure for interacting with salsa. Crucially, it defines most of the "input" queries: facts supplied by the client of the analyzer. Reading the docs of the `base_db::input` module should be useful: everything else is strictly derived from those inputs. **Architecture Invariant:** particularities of the build system are *not* the part of the ground state. -In particular, `base_db` knows nothing about cargo. +In particular, `base-db` knows nothing about cargo. For example, `cfg` flags are a part of `base_db`, but `feature`s are not. A `foo` feature is a Cargo-level concept, which is lowered by Cargo to `--cfg feature=foo` argument on the command line. The `CrateGraph` structure is used to represent the dependencies between the crates abstractly. -**Architecture Invariant:** `base_db` doesn't know about file system and file paths. +**Architecture Invariant:** `base-db` doesn't know about file system and file paths. Files are represented with opaque `FileId`, there's no operation to get an `std::path::Path` out of the `FileId`. -### `crates/hir_expand`, `crates/hir_def`, `crates/hir_ty` +### `crates/hir-expand`, `crates/hir-def`, `crates/hir_ty` These crates are the *brain* of rust-analyzer. This is the compiler part of the IDE. -`hir_xxx` crates have a strong [ECS](https://en.wikipedia.org/wiki/Entity_component_system) flavor, in that they work with raw ids and directly query the database. +`hir-xxx` crates have a strong [ECS](https://en.wikipedia.org/wiki/Entity_component_system) flavor, in that they work with raw ids and directly query the database. There's little abstraction here. These crates integrate deeply with salsa and chalk. @@ -186,7 +186,7 @@ If you think about "using rust-analyzer as a library", `hir` crate is most likel It wraps ECS-style internal API into a more OO-flavored API (with an extra `db` argument for each call). **Architecture Invariant:** `hir` provides a static, fully resolved view of the code. -While internal `hir_*` crates _compute_ things, `hir`, from the outside, looks like an inert data structure. +While internal `hir-*` crates _compute_ things, `hir`, from the outside, looks like an inert data structure. `hir` also handles the delicate task of going from syntax to the corresponding `hir`. Remember that the mapping here is one-to-many. @@ -200,7 +200,7 @@ Then we look for our node in the set of children. This is the heart of many IDE features, like goto definition, which start with figuring out the hir node at the cursor. This is some kind of (yet unnamed) uber-IDE pattern, as it is present in Roslyn and Kotlin as well. -### `crates/ide` +### `crates/ide`, `crates/ide-db`, `crates/ide-assists`, `crates/ide-completion`, `crates/ide-diagnostics`, `crates/ide-ssr` The `ide` crate builds on top of `hir` semantic model to provide high-level IDE features like completion or goto definition. It is an **API Boundary**. @@ -217,8 +217,8 @@ Shout outs to LSP developers for popularizing the idea that "UI" is a good place `AnalysisHost` is a state to which you can transactionally `apply_change`. `Analysis` is an immutable snapshot of the state. -Internally, `ide` is split across several crates. `ide_assists`, `ide_completion` and `ide_ssr` implement large isolated features. -`ide_db` implements common IDE functionality (notably, reference search is implemented here). +Internally, `ide` is split across several crates. `ide-assists`, `ide-completion`, `ide-diagnostics` and `ide-ssr` implement large isolated features. +`ide-db` implements common IDE functionality (notably, reference search is implemented here). The `ide` contains a public API/façade, as well as implementation for a plethora of smaller features. **Architecture Invariant:** `ide` crate strives to provide a _perfect_ API. @@ -251,14 +251,14 @@ This is a tricky business. **Architecture Invariant:** `rust-analyzer` should be partially available even when the build is broken. Reloading process should not prevent IDE features from working. -### `crates/toolchain`, `crates/project_model`, `crates/flycheck` +### `crates/toolchain`, `crates/project-model`, `crates/flycheck` These crates deal with invoking `cargo` to learn about project structure and get compiler errors for the "check on save" feature. -They use `crates/path` heavily instead of `std::path`. +They use `crates/paths` heavily instead of `std::path`. A single `rust-analyzer` process can serve many projects, so it is important that server's current directory does not leak. -### `crates/mbe`, `crates/tt`, `crates/proc_macro_api`, `crates/proc_macro_srv` +### `crates/mbe`, `crates/tt`, `crates/proc-macro-api`, `crates/proc-macro-srv`, `crates/proc-macro-srv-cli` These crates implement macros as token tree -> token tree transforms. They are independent from the rest of the code. @@ -268,8 +268,8 @@ They are independent from the rest of the code. And it also handles the actual parsing and expansion of declarative macro (a-la "Macros By Example" or mbe). For proc macros, the client-server model are used. -We start a separate process (`proc_macro_srv`) which loads and runs the proc-macros for us. -And the client (`proc_macro_api`) provides an interface to talk to that server separately. +We start a separate process (`proc-macro-srv-cli`) which loads and runs the proc-macros for us. +And the client (`proc-macro-api`) provides an interface to talk to that server separately. And then token trees are passed from client, and the server will load the corresponding dynamic library (which built by `cargo`). And due to the fact the api for getting result from proc macro are always unstable in `rustc`, @@ -283,7 +283,7 @@ And they may be non-deterministic which conflict how `salsa` works, so special a This crate is responsible for parsing, evaluation and general definition of `cfg` attributes. -### `crates/vfs`, `crates/vfs-notify` +### `crates/vfs`, `crates/vfs-notify`, `crates/paths` These crates implement a virtual file system. They provide consistent snapshots of the underlying file system and insulate messy OS paths. @@ -301,6 +301,25 @@ as copies of unstable std items we would like to make use of already, like `std: This crate contains utilities for CPU and memory profiling. +### `crates/intern` + +This crate contains infrastructure for globally interning things via `Arc`. + +### `crates/load-cargo` + +This crate exposes several utilities for loading projects, used by the main `rust-analyzer` crate +and other downstream consumers. + +### `crates/rustc-dependencies` + +This crate wraps the `rustc_*` crates rust-analyzer relies on and conditionally points them to +mirrored crates-io releases such that rust-analyzer keeps building on stable. + +### `crates/span` + +This crate exposes types and functions related to rust-analyzer's span for macros. + +A span is effectively a text range relative to some item in a file with a given `SyntaxContext` (hygiene). ## Cross-Cutting Concerns diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md index b66c9c943a1..3251dd75268 100644 --- a/docs/dev/lsp-extensions.md +++ b/docs/dev/lsp-extensions.md @@ -1,5 +1,5 @@