From b66bc91b4bf01cb18a7bc106a25bf7cf579ecab7 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Mon, 25 Nov 2024 14:52:58 +0200 Subject: [PATCH 01/95] Fix a bug when synthetic AST node were searched in the AST ID map and caused panics --- crates/hir-def/src/lower.rs | 26 ++++++++++- crates/hir/src/semantics.rs | 13 ++++-- .../src/handlers/add_missing_impl_members.rs | 45 +++++++++++++++++++ 3 files changed, 80 insertions(+), 4 deletions(-) diff --git a/crates/hir-def/src/lower.rs b/crates/hir-def/src/lower.rs index 6d1a3d1744..350bb8d517 100644 --- a/crates/hir-def/src/lower.rs +++ b/crates/hir-def/src/lower.rs @@ -2,7 +2,7 @@ use std::{cell::OnceCell, mem}; use hir_expand::{span_map::SpanMap, AstId, HirFileId, InFile}; -use span::{AstIdMap, AstIdNode}; +use span::{AstIdMap, AstIdNode, Edition, EditionedFileId, FileId, RealSpanMap}; use stdx::thin_vec::ThinVec; use syntax::ast; use triomphe::Arc; @@ -63,6 +63,30 @@ impl<'a> LowerCtx<'a> { } } + /// Prepares a `LowerCtx` for synthetic AST that needs to be lowered. This is intended for IDE things. + pub fn for_synthetic_ast( + db: &'a dyn DefDatabase, + ast_id_map: Arc, + types_map: &'a mut TypesMap, + types_source_map: &'a mut TypesSourceMap, + ) -> Self { + let file_id = EditionedFileId::new( + FileId::from_raw(EditionedFileId::MAX_FILE_ID), + Edition::Edition2015, + ); + LowerCtx { + db, + // Make up an invalid file id, so that if we will try to actually access it salsa will panic. + file_id: file_id.into(), + span_map: SpanMap::RealSpanMap(Arc::new(RealSpanMap::absolute(file_id))).into(), + ast_id_map: ast_id_map.into(), + impl_trait_bounds: Vec::new(), + outer_impl_trait: false, + types_map, + types_source_map, + } + } + pub(crate) fn span_map(&self) -> &SpanMap { self.span_map.get_or_init(|| self.db.span_map(self.file_id)) } diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 9d3f8e5fba..46766fcc5b 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -34,7 +34,7 @@ use intern::Symbol; use itertools::Itertools; use rustc_hash::{FxHashMap, FxHashSet}; use smallvec::{smallvec, SmallVec}; -use span::{EditionedFileId, FileId, HirFileIdRepr, SyntaxContextId}; +use span::{AstIdMap, EditionedFileId, FileId, HirFileIdRepr, SyntaxContextId}; use stdx::TupleExt; use syntax::{ algo::skip_trivia_token, @@ -42,6 +42,7 @@ use syntax::{ AstNode, AstToken, Direction, SyntaxKind, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange, TextSize, }; +use triomphe::Arc; use crate::{ db::HirDatabase, @@ -1973,10 +1974,16 @@ impl SemanticsScope<'_> { /// Resolve a path as-if it was written at the given scope. This is /// necessary a heuristic, as it doesn't take hygiene into account. pub fn speculative_resolve(&self, ast_path: &ast::Path) -> Option { + let root = ast_path.syntax().ancestors().last().unwrap(); + let ast_id_map = Arc::new(AstIdMap::from_source(&root)); let (mut types_map, mut types_source_map) = (TypesMap::default(), TypesSourceMap::default()); - let mut ctx = - LowerCtx::new(self.db.upcast(), self.file_id, &mut types_map, &mut types_source_map); + let mut ctx = LowerCtx::for_synthetic_ast( + self.db.upcast(), + ast_id_map, + &mut types_map, + &mut types_source_map, + ); let path = Path::from_src(&mut ctx, ast_path.clone())?; resolve_hir_path( self.db, diff --git a/crates/ide-assists/src/handlers/add_missing_impl_members.rs b/crates/ide-assists/src/handlers/add_missing_impl_members.rs index 7f8ea44fb1..57df39d541 100644 --- a/crates/ide-assists/src/handlers/add_missing_impl_members.rs +++ b/crates/ide-assists/src/handlers/add_missing_impl_members.rs @@ -2318,4 +2318,49 @@ impl<'a> Test<'a, i32> for bool { "#, ); } + + #[test] + fn issue_17321() { + check_assist( + add_missing_impl_members, + r#" +fn main() {} + +mod other_file_1 { + pub const SOME_CONSTANT: usize = 8; +} + +mod other_file_2 { + use crate::other_file_1::SOME_CONSTANT; + + pub trait Trait { + type Iter: Iterator; + } +} + +pub struct MyStruct; + +impl other_file_2::Trait for MyStruct$0 {}"#, + r#" +fn main() {} + +mod other_file_1 { + pub const SOME_CONSTANT: usize = 8; +} + +mod other_file_2 { + use crate::other_file_1::SOME_CONSTANT; + + pub trait Trait { + type Iter: Iterator; + } +} + +pub struct MyStruct; + +impl other_file_2::Trait for MyStruct { + $0type Iter; +}"#, + ); + } } From 6aa5ea5411478112554dfbb63f7d14900d3f6efe Mon Sep 17 00:00:00 2001 From: Shoyu Vanilla Date: Mon, 25 Nov 2024 01:01:50 +0900 Subject: [PATCH 02/95] Migrate `add_turbo_fish` to `SyntaxEditor` --- .../src/handlers/add_turbo_fish.rs | 83 +++++++++++++------ .../src/ast/syntax_factory/constructors.rs | 18 ++++ 2 files changed, 77 insertions(+), 24 deletions(-) diff --git a/crates/ide-assists/src/handlers/add_turbo_fish.rs b/crates/ide-assists/src/handlers/add_turbo_fish.rs index 17efbcbd6c..0f6970d940 100644 --- a/crates/ide-assists/src/handlers/add_turbo_fish.rs +++ b/crates/ide-assists/src/handlers/add_turbo_fish.rs @@ -1,8 +1,9 @@ use either::Either; use ide_db::defs::{Definition, NameRefClass}; use syntax::{ - ast::{self, make, HasArgList, HasGenericArgs}, - ted, AstNode, + ast::{self, make, syntax_factory::SyntaxFactory, HasArgList, HasGenericArgs}, + syntax_editor::Position, + AstNode, }; use crate::{ @@ -91,20 +92,34 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti AssistId("add_type_ascription", AssistKind::RefactorRewrite), "Add `: _` before assignment operator", ident.text_range(), - |edit| { - let let_stmt = edit.make_mut(let_stmt); + |builder| { + let mut editor = builder.make_editor(let_stmt.syntax()); if let_stmt.semicolon_token().is_none() { - ted::append_child(let_stmt.syntax(), make::tokens::semicolon()); + editor.insert( + Position::last_child_of(let_stmt.syntax()), + make::tokens::semicolon(), + ); } let placeholder_ty = make::ty_placeholder().clone_for_update(); - let_stmt.set_ty(Some(placeholder_ty.clone())); - - if let Some(cap) = ctx.config.snippet_cap { - edit.add_placeholder_snippet(cap, placeholder_ty); + if let Some(pat) = let_stmt.pat() { + let elements = vec![ + make::token(syntax::SyntaxKind::COLON).into(), + make::token(syntax::SyntaxKind::WHITESPACE).into(), + placeholder_ty.syntax().clone().into(), + ]; + editor.insert_all(Position::after(pat.syntax()), elements); + if let Some(cap) = ctx.config.snippet_cap { + editor.add_annotation( + placeholder_ty.syntax(), + builder.make_placeholder_snippet(cap), + ); + } } + + builder.add_file_edits(ctx.file_id(), editor); }, )? } else { @@ -123,38 +138,58 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti AssistId("add_turbo_fish", AssistKind::RefactorRewrite), "Add `::<>`", ident.text_range(), - |edit| { - edit.trigger_parameter_hints(); + |builder| { + builder.trigger_parameter_hints(); - let new_arg_list = match turbofish_target { + let make = SyntaxFactory::new(); + let mut editor = match &turbofish_target { + Either::Left(it) => builder.make_editor(it.syntax()), + Either::Right(it) => builder.make_editor(it.syntax()), + }; + + let fish_head = get_fish_head(&make, number_of_arguments); + + match turbofish_target { Either::Left(path_segment) => { - edit.make_mut(path_segment).get_or_create_generic_arg_list() + if let Some(generic_arg_list) = path_segment.generic_arg_list() { + editor.replace(generic_arg_list.syntax(), fish_head.syntax()); + } else { + editor.insert( + Position::last_child_of(path_segment.syntax()), + fish_head.syntax(), + ); + } } Either::Right(method_call) => { - edit.make_mut(method_call).get_or_create_generic_arg_list() + if let Some(generic_arg_list) = method_call.generic_arg_list() { + editor.replace(generic_arg_list.syntax(), fish_head.syntax()); + } else { + let position = if let Some(arg_list) = method_call.arg_list() { + Position::before(arg_list.syntax()) + } else { + Position::last_child_of(method_call.syntax()) + }; + editor.insert(position, fish_head.syntax()); + } } }; - let fish_head = get_fish_head(number_of_arguments).clone_for_update(); - - // Note: we need to replace the `new_arg_list` instead of being able to use something like - // `GenericArgList::add_generic_arg` as `PathSegment::get_or_create_generic_arg_list` - // always creates a non-turbofish form generic arg list. - ted::replace(new_arg_list.syntax(), fish_head.syntax()); - if let Some(cap) = ctx.config.snippet_cap { for arg in fish_head.generic_args() { - edit.add_placeholder_snippet(cap, arg) + editor.add_annotation(arg.syntax(), builder.make_placeholder_snippet(cap)); } } + + editor.add_mappings(make.finish_with_mappings()); + builder.add_file_edits(ctx.file_id(), editor); }, ) } /// This will create a turbofish generic arg list corresponding to the number of arguments -fn get_fish_head(number_of_arguments: usize) -> ast::GenericArgList { +fn get_fish_head(make: &SyntaxFactory, number_of_arguments: usize) -> ast::GenericArgList { let args = (0..number_of_arguments).map(|_| make::type_arg(make::ty_placeholder()).into()); - make::turbofish_generic_arg_list(args) + make.turbofish_generic_arg_list(args) } #[cfg(test)] diff --git a/crates/syntax/src/ast/syntax_factory/constructors.rs b/crates/syntax/src/ast/syntax_factory/constructors.rs index 9f88add0f7..6e90b2ba47 100644 --- a/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -107,4 +107,22 @@ impl SyntaxFactory { ast } + + pub fn turbofish_generic_arg_list( + &self, + args: impl IntoIterator + Clone, + ) -> ast::GenericArgList { + let ast = make::turbofish_generic_arg_list(args.clone()).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_children( + args.into_iter().map(|arg| arg.syntax().clone()), + ast.generic_args().map(|arg| arg.syntax().clone()), + ); + builder.finish(&mut mapping); + } + + ast + } } From a53ee64e7065a0a474037f8eda1f9ff02787d464 Mon Sep 17 00:00:00 2001 From: Mark Murphy Date: Tue, 26 Nov 2024 00:42:27 -0500 Subject: [PATCH 03/95] Add macro expansion test for raw variable names --- .../src/macro_expansion_tests/mbe/matching.rs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/crates/hir-def/src/macro_expansion_tests/mbe/matching.rs b/crates/hir-def/src/macro_expansion_tests/mbe/matching.rs index 23d8b023b8..e9a977da91 100644 --- a/crates/hir-def/src/macro_expansion_tests/mbe/matching.rs +++ b/crates/hir-def/src/macro_expansion_tests/mbe/matching.rs @@ -184,3 +184,31 @@ fn test() { "#]], ); } + +#[test] +fn meta_variable_raw_name_equals_non_raw() { + check( + r#" +macro_rules! m { + ($r#name:tt) => { + $name + } +} + +fn test() { + m!(1234) +} +"#, + expect![[r#" +macro_rules! m { + ($r#name:tt) => { + $name + } +} + +fn test() { + 1234 +} +"#]], + ); +} From 4a910095da1f1e97f8d3b451994b8ca42ac00a9b Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Tue, 26 Nov 2024 18:37:39 +0100 Subject: [PATCH 04/95] Revert #18197 Our first attempt to make flycheck only check the current crate if the crate is one of bin/bench/test targets had caused `check_workspace` to be ignored, which should have been a config with higher precedence all along. This commit revert #18197 and closes #18562 --- crates/rust-analyzer/src/handlers/notification.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rust-analyzer/src/handlers/notification.rs b/crates/rust-analyzer/src/handlers/notification.rs index bb03eb3c89..49b1ba32a7 100644 --- a/crates/rust-analyzer/src/handlers/notification.rs +++ b/crates/rust-analyzer/src/handlers/notification.rs @@ -380,7 +380,7 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool { if id == flycheck.id() { updated = true; match package.filter(|_| { - !world.config.flycheck_workspace(source_root_id) && target.is_some() + !world.config.flycheck_workspace(source_root_id) || target.is_some() }) { Some(package) => flycheck .restart_for_package(package, target.clone().map(TupleExt::head)), From 61e9c02dd861fc7b0a2730d8a8bad23e34fbb1b2 Mon Sep 17 00:00:00 2001 From: Ali Bektas Date: Tue, 26 Nov 2024 18:54:25 +0100 Subject: [PATCH 05/95] Remove target.is_some() to handle it later inside ra::flycheck --- crates/rust-analyzer/src/handlers/notification.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/rust-analyzer/src/handlers/notification.rs b/crates/rust-analyzer/src/handlers/notification.rs index 49b1ba32a7..5e7262b14c 100644 --- a/crates/rust-analyzer/src/handlers/notification.rs +++ b/crates/rust-analyzer/src/handlers/notification.rs @@ -379,9 +379,7 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool { for (id, package) in workspace_ids.clone() { if id == flycheck.id() { updated = true; - match package.filter(|_| { - !world.config.flycheck_workspace(source_root_id) || target.is_some() - }) { + match package.filter(|_| !world.config.flycheck_workspace(source_root_id)) { Some(package) => flycheck .restart_for_package(package, target.clone().map(TupleExt::head)), None => flycheck.restart_workspace(saved_file.clone()), From 662ff031a36034af7fc1854d6f3df23025b8eb71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauren=C8=9Biu=20Nicola?= Date: Thu, 28 Nov 2024 16:01:40 +0200 Subject: [PATCH 06/95] Preparing for merge from rust-lang/rust --- rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-version b/rust-version index 8f41ed9e14..945939a2ff 100644 --- a/rust-version +++ b/rust-version @@ -1 +1 @@ -f005c7437def424a1c43cbc380352a58d8ac920b +9b4d7c6a40b328d212095c28670c629facf1557d From 29d59c58d80507cea122f4dc20f3b37cac48e70f Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sat, 30 Nov 2024 03:33:33 +0000 Subject: [PATCH 07/95] Remove redundant associated type bounds from dyn --- crates/hir-ty/src/infer/unify.rs | 4 ++-- crates/hir-ty/src/lib.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/hir-ty/src/infer/unify.rs b/crates/hir-ty/src/infer/unify.rs index 54aa18ce20..4d0539c135 100644 --- a/crates/hir-ty/src/infer/unify.rs +++ b/crates/hir-ty/src/infer/unify.rs @@ -666,7 +666,7 @@ impl<'a> InferenceTable<'a> { highest_known_var: InferenceVar, } impl TypeFolder for VarFudger<'_, '_> { - fn as_dyn(&mut self) -> &mut dyn TypeFolder { + fn as_dyn(&mut self) -> &mut dyn TypeFolder { self } @@ -1004,7 +1004,7 @@ mod resolve { where F: Fn(InferenceVar, VariableKind, GenericArg, DebruijnIndex) -> GenericArg, { - fn as_dyn(&mut self) -> &mut dyn TypeFolder { + fn as_dyn(&mut self) -> &mut dyn TypeFolder { self } diff --git a/crates/hir-ty/src/lib.rs b/crates/hir-ty/src/lib.rs index 22e7b1d920..975625fdfa 100644 --- a/crates/hir-ty/src/lib.rs +++ b/crates/hir-ty/src/lib.rs @@ -646,7 +646,7 @@ pub(crate) fn fold_free_vars + TypeFoldable< F2: FnMut(Ty, BoundVar, DebruijnIndex) -> Const, > TypeFolder for FreeVarFolder { - fn as_dyn(&mut self) -> &mut dyn TypeFolder { + fn as_dyn(&mut self) -> &mut dyn TypeFolder { self } @@ -697,7 +697,7 @@ pub(crate) fn fold_tys_and_consts + TypeFold impl, DebruijnIndex) -> Either> TypeFolder for TyFolder { - fn as_dyn(&mut self) -> &mut dyn TypeFolder { + fn as_dyn(&mut self) -> &mut dyn TypeFolder { self } From d75f9967222b3271b583120d0c7da13d5e410e80 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Mon, 2 Dec 2024 12:58:43 +0100 Subject: [PATCH 08/95] Fix debug configuration querying not inheriting environment --- editors/code/src/config.ts | 27 +++- editors/code/src/debug.ts | 12 +- editors/code/src/run.ts | 35 ++---- editors/code/tests/unit/runnable_env.test.ts | 122 ------------------- 4 files changed, 42 insertions(+), 154 deletions(-) delete mode 100644 editors/code/tests/unit/runnable_env.test.ts diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 67bc72f1e1..f979d54547 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -261,9 +261,9 @@ export class Config { return this.get("testExplorer"); } - get runnablesExtraEnv() { + runnablesExtraEnv(label: string): Record | undefined { const item = this.get("runnables.extraEnv") ?? this.get("runnableEnv"); - if (!item) return item; + if (!item) return undefined; const fixRecord = (r: Record) => { for (const key in r) { if (typeof r[key] !== "string") { @@ -271,11 +271,28 @@ export class Config { } } }; + + const platform = process.platform; + const checkPlatform = (it: RunnableEnvCfgItem) => { + if (it.platform) { + const platforms = Array.isArray(it.platform) ? it.platform : [it.platform]; + return platforms.indexOf(platform) >= 0; + } + return true; + }; + if (item instanceof Array) { - item.forEach((x) => fixRecord(x.env)); - } else { - fixRecord(item); + const env = {}; + for (const it of item) { + const masked = !it.mask || new RegExp(it.mask).test(label); + if (masked && checkPlatform(it)) { + Object.assign(env, it.env); + } + } + fixRecord(env); + return env; } + fixRecord(item); return item; } diff --git a/editors/code/src/debug.ts b/editors/code/src/debug.ts index 9e2e3d2185..f21ca2e8f9 100644 --- a/editors/code/src/debug.ts +++ b/editors/code/src/debug.ts @@ -148,8 +148,16 @@ async function getDebugConfiguration( return path.normalize(p).replace(wsFolder, `\${workspaceFolder${workspaceQualifier}}`); } - const env = prepareEnv(inheritEnv, runnable.label, runnableArgs, config.runnablesExtraEnv); - const executable = await getDebugExecutable(runnableArgs, env); + const executable = await getDebugExecutable( + runnableArgs, + prepareEnv(true, {}, config.runnablesExtraEnv(runnable.label)), + ); + + const env = prepareEnv( + inheritEnv, + runnableArgs.environment, + config.runnablesExtraEnv(runnable.label), + ); let sourceFileMap = debugOptions.sourceFileMap; if (sourceFileMap === "auto") { diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts index 8a82a5a58c..f71ab7ffbd 100644 --- a/editors/code/src/run.ts +++ b/editors/code/src/run.ts @@ -5,7 +5,7 @@ import * as tasks from "./tasks"; import type { CtxInit } from "./ctx"; import { makeDebugConfig } from "./debug"; -import type { Config, RunnableEnvCfg, RunnableEnvCfgItem } from "./config"; +import type { Config } from "./config"; import type { LanguageClient } from "vscode-languageclient/node"; import { unwrapUndefinable, type RustEditor } from "./util"; @@ -81,32 +81,13 @@ export function prepareBaseEnv( export function prepareEnv( inheritEnv: boolean, - label: string, - runnableArgs: ra.CargoRunnableArgs, - runnableEnvCfg?: RunnableEnvCfg, + runnableEnv?: Record, + runnableEnvCfg?: Record, ): Record { - const env = prepareBaseEnv(inheritEnv, runnableArgs.environment); - const platform = process.platform; - - const checkPlatform = (it: RunnableEnvCfgItem) => { - if (it.platform) { - const platforms = Array.isArray(it.platform) ? it.platform : [it.platform]; - return platforms.indexOf(platform) >= 0; - } - return true; - }; + const env = prepareBaseEnv(inheritEnv, runnableEnv); if (runnableEnvCfg) { - if (Array.isArray(runnableEnvCfg)) { - for (const it of runnableEnvCfg) { - const masked = !it.mask || new RegExp(it.mask).test(label); - if (masked && checkPlatform(it)) { - Object.assign(env, it.env); - } - } - } else { - Object.assign(env, runnableEnvCfg); - } + Object.assign(env, runnableEnvCfg); } return env; @@ -140,7 +121,11 @@ export async function createTaskFromRunnable( }; options = { cwd: runnableArgs.workspaceRoot || ".", - env: prepareEnv(true, runnable.label, runnableArgs, config.runnablesExtraEnv), + env: prepareEnv( + true, + runnableArgs.environment, + config.runnablesExtraEnv(runnable.label), + ), }; } else { const runnableArgs = runnable.args; diff --git a/editors/code/tests/unit/runnable_env.test.ts b/editors/code/tests/unit/runnable_env.test.ts deleted file mode 100644 index f0a62a3cce..0000000000 --- a/editors/code/tests/unit/runnable_env.test.ts +++ /dev/null @@ -1,122 +0,0 @@ -import * as assert from "assert"; -import { prepareEnv } from "../../src/run"; -import type { RunnableEnvCfg } from "../../src/config"; -import type { Context } from "."; -import type * as ra from "../../src/lsp_ext"; - -function makeRunnable(label: string): ra.Runnable { - return { - label, - kind: "cargo", - args: { - cargoArgs: [], - cwd: ".", - executableArgs: [], - }, - }; -} - -function fakePrepareEnv(runnableName: string, config?: RunnableEnvCfg): Record { - const runnable = makeRunnable(runnableName); - const runnableArgs = runnable.args as ra.CargoRunnableArgs; - return prepareEnv(false, runnable.label, runnableArgs, config); -} - -export async function getTests(ctx: Context) { - await ctx.suite("Runnable env", (suite) => { - suite.addTest("Global config works", async () => { - const binEnv = fakePrepareEnv("run project_name", { GLOBAL: "g" }); - assert.strictEqual(binEnv["GLOBAL"], "g"); - - const testEnv = fakePrepareEnv("test some::mod::test_name", { GLOBAL: "g" }); - assert.strictEqual(testEnv["GLOBAL"], "g"); - }); - - suite.addTest("null mask works", async () => { - const config = [ - { - env: { DATA: "data" }, - }, - ]; - const binEnv = fakePrepareEnv("run project_name", config); - assert.strictEqual(binEnv["DATA"], "data"); - - const testEnv = fakePrepareEnv("test some::mod::test_name", config); - assert.strictEqual(testEnv["DATA"], "data"); - }); - - suite.addTest("order works", async () => { - const config = [ - { - env: { DATA: "data" }, - }, - { - env: { DATA: "newdata" }, - }, - ]; - const binEnv = fakePrepareEnv("run project_name", config); - assert.strictEqual(binEnv["DATA"], "newdata"); - - const testEnv = fakePrepareEnv("test some::mod::test_name", config); - assert.strictEqual(testEnv["DATA"], "newdata"); - }); - - suite.addTest("mask works", async () => { - const config = [ - { - env: { DATA: "data" }, - }, - { - mask: "^run", - env: { DATA: "rundata" }, - }, - { - mask: "special_test$", - env: { DATA: "special_test" }, - }, - ]; - const binEnv = fakePrepareEnv("run project_name", config); - assert.strictEqual(binEnv["DATA"], "rundata"); - - const testEnv = fakePrepareEnv("test some::mod::test_name", config); - assert.strictEqual(testEnv["DATA"], "data"); - - const specialTestEnv = fakePrepareEnv("test some::mod::special_test", config); - assert.strictEqual(specialTestEnv["DATA"], "special_test"); - }); - - suite.addTest("exact test name works", async () => { - const config = [ - { - env: { DATA: "data" }, - }, - { - mask: "some::mod::test_name", - env: { DATA: "test special" }, - }, - ]; - const testEnv = fakePrepareEnv("test some::mod::test_name", config); - assert.strictEqual(testEnv["DATA"], "test special"); - - const specialTestEnv = fakePrepareEnv("test some::mod::another_test", config); - assert.strictEqual(specialTestEnv["DATA"], "data"); - }); - - suite.addTest("test mod name works", async () => { - const config = [ - { - env: { DATA: "data" }, - }, - { - mask: "some::mod", - env: { DATA: "mod special" }, - }, - ]; - const testEnv = fakePrepareEnv("test some::mod::test_name", config); - assert.strictEqual(testEnv["DATA"], "mod special"); - - const specialTestEnv = fakePrepareEnv("test some::mod::another_test", config); - assert.strictEqual(specialTestEnv["DATA"], "mod special"); - }); - }); -} From e7b96dd2de8a6da7dcddd566d367805c144fce01 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Mon, 2 Dec 2024 13:35:58 +0100 Subject: [PATCH 09/95] fix: Fix syntax fixup inserting unnecessary semicolons --- crates/hir-expand/src/fixup.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/crates/hir-expand/src/fixup.rs b/crates/hir-expand/src/fixup.rs index b6d5828da9..0af29681a1 100644 --- a/crates/hir-expand/src/fixup.rs +++ b/crates/hir-expand/src/fixup.rs @@ -110,7 +110,8 @@ pub(crate) fn fixup_syntax( } }, ast::ExprStmt(it) => { - if it.semicolon_token().is_none() { + let needs_semi = it.semicolon_token().is_none() && it.expr().map_or(false, |e| e.syntax().kind() != SyntaxKind::BLOCK_EXPR); + if needs_semi { append.insert(node.clone().into(), vec![ Leaf::Punct(Punct { char: ';', @@ -905,6 +906,21 @@ fn foo() { "#, expect![[r#" fn foo () {|| __ra_fixup} +"#]], + ); + } + + #[test] + fn fixup_regression_() { + check( + r#" +fn foo() { + {} + {} +} +"#, + expect![[r#" +fn foo () {{} {}} "#]], ); } From 4261ac7856e5f88d16993edd3ff7631c8e7abe10 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 3 Dec 2024 10:41:29 +0200 Subject: [PATCH 10/95] Advertise completions and inlay hints resolve server capabilities based on the client capabilities. --- crates/ide-completion/src/lib.rs | 14 ++++++++++++- crates/ide/src/inlay_hints.rs | 12 ++++++++++- crates/rust-analyzer/src/config.rs | 22 ++++++-------------- crates/rust-analyzer/src/lsp/capabilities.rs | 19 +++++++++++++---- 4 files changed, 45 insertions(+), 22 deletions(-) diff --git a/crates/ide-completion/src/lib.rs b/crates/ide-completion/src/lib.rs index dfee01b187..cffdfa29f1 100644 --- a/crates/ide-completion/src/lib.rs +++ b/crates/ide-completion/src/lib.rs @@ -19,7 +19,7 @@ use ide_db::{ }, items_locator, syntax_helpers::tree_diff::diff, - FilePosition, RootDatabase, + FilePosition, FxHashSet, RootDatabase, }; use crate::{ @@ -50,6 +50,18 @@ pub struct CompletionFieldsToResolve { } impl CompletionFieldsToResolve { + pub fn from_client_capabilities(client_capability_fields: &FxHashSet<&str>) -> Self { + Self { + resolve_label_details: client_capability_fields.contains("labelDetails"), + resolve_tags: client_capability_fields.contains("tags"), + resolve_detail: client_capability_fields.contains("detail"), + resolve_documentation: client_capability_fields.contains("documentation"), + resolve_filter_text: client_capability_fields.contains("filterText"), + resolve_text_edit: client_capability_fields.contains("textEdit"), + resolve_command: client_capability_fields.contains("command"), + } + } + pub const fn empty() -> Self { Self { resolve_label_details: false, diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs index c58ca0f01c..1ae8bfa9b6 100644 --- a/crates/ide/src/inlay_hints.rs +++ b/crates/ide/src/inlay_hints.rs @@ -8,8 +8,8 @@ use hir::{ sym, ClosureStyle, HasVisibility, HirDisplay, HirDisplayError, HirWrite, ModuleDef, ModuleDefId, Semantics, }; -use ide_db::text_edit::TextEdit; use ide_db::{famous_defs::FamousDefs, FileRange, RootDatabase}; +use ide_db::{text_edit::TextEdit, FxHashSet}; use itertools::Itertools; use smallvec::{smallvec, SmallVec}; use span::{Edition, EditionedFileId}; @@ -289,6 +289,16 @@ pub struct InlayFieldsToResolve { } impl InlayFieldsToResolve { + pub fn from_client_capabilities(client_capability_fields: &FxHashSet<&str>) -> Self { + Self { + resolve_text_edits: client_capability_fields.contains("textEdits"), + resolve_hint_tooltip: client_capability_fields.contains("tooltip"), + resolve_label_tooltip: client_capability_fields.contains("label.tooltip"), + resolve_label_location: client_capability_fields.contains("label.location"), + resolve_label_command: client_capability_fields.contains("label.command"), + } + } + pub const fn empty() -> Self { Self { resolve_text_edits: false, diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 37d45255e2..152ce2944a 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -1446,15 +1446,9 @@ impl Config { limit: self.completion_limit(source_root).to_owned(), enable_term_search: self.completion_termSearch_enable(source_root).to_owned(), term_search_fuel: self.completion_termSearch_fuel(source_root).to_owned() as u64, - fields_to_resolve: CompletionFieldsToResolve { - resolve_label_details: client_capability_fields.contains("labelDetails"), - resolve_tags: client_capability_fields.contains("tags"), - resolve_detail: client_capability_fields.contains("detail"), - resolve_documentation: client_capability_fields.contains("documentation"), - resolve_filter_text: client_capability_fields.contains("filterText"), - resolve_text_edit: client_capability_fields.contains("textEdit"), - resolve_command: client_capability_fields.contains("command"), - }, + fields_to_resolve: CompletionFieldsToResolve::from_client_capabilities( + &client_capability_fields, + ), } } @@ -1614,13 +1608,9 @@ impl Config { } else { None }, - fields_to_resolve: InlayFieldsToResolve { - resolve_text_edits: client_capability_fields.contains("textEdits"), - resolve_hint_tooltip: client_capability_fields.contains("tooltip"), - resolve_label_tooltip: client_capability_fields.contains("label.tooltip"), - resolve_label_location: client_capability_fields.contains("label.location"), - resolve_label_command: client_capability_fields.contains("label.command"), - }, + fields_to_resolve: InlayFieldsToResolve::from_client_capabilities( + &client_capability_fields, + ), implicit_drop_hints: self.inlayHints_implicitDrops_enable().to_owned(), range_exclusive_hints: self.inlayHints_rangeExclusiveHints_enable().to_owned(), } diff --git a/crates/rust-analyzer/src/lsp/capabilities.rs b/crates/rust-analyzer/src/lsp/capabilities.rs index 1db616898e..bd496e8ddc 100644 --- a/crates/rust-analyzer/src/lsp/capabilities.rs +++ b/crates/rust-analyzer/src/lsp/capabilities.rs @@ -1,4 +1,5 @@ //! Advertises the capabilities of the LSP Server. +use ide::{CompletionFieldsToResolve, InlayFieldsToResolve}; use ide_db::{line_index::WideEncoding, FxHashSet}; use lsp_types::{ CallHierarchyServerCapability, CodeActionKind, CodeActionOptions, CodeActionProviderCapability, @@ -40,7 +41,7 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities { })), hover_provider: Some(HoverProviderCapability::Simple(true)), completion_provider: Some(CompletionOptions { - resolve_provider: config.caps().completions_resolve_provider(), + resolve_provider: Some(config.caps().completions_resolve_provider()), trigger_characters: Some(vec![ ":".to_owned(), ".".to_owned(), @@ -136,7 +137,7 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities { inlay_hint_provider: Some(OneOf::Right(InlayHintServerCapabilities::Options( InlayHintOptions { work_done_progress_options: Default::default(), - resolve_provider: Some(true), + resolve_provider: Some(config.caps().inlay_hints_resolve_provider()), }, ))), inline_value_provider: None, @@ -176,8 +177,18 @@ impl ClientCapabilities { Self(caps) } - fn completions_resolve_provider(&self) -> Option { - self.completion_item_edit_resolve().then_some(true) + fn completions_resolve_provider(&self) -> bool { + let client_capabilities = self.completion_resolve_support_properties(); + let fields_to_resolve = + CompletionFieldsToResolve::from_client_capabilities(&client_capabilities); + fields_to_resolve != CompletionFieldsToResolve::empty() + } + + fn inlay_hints_resolve_provider(&self) -> bool { + let client_capabilities = self.inlay_hint_resolve_support_properties(); + let fields_to_resolve = + InlayFieldsToResolve::from_client_capabilities(&client_capabilities); + fields_to_resolve != InlayFieldsToResolve::empty() } fn experimental_bool(&self, index: &'static str) -> bool { From cf80dddb590b286b2ca747410f254bdaf87bf1e2 Mon Sep 17 00:00:00 2001 From: David Richey Date: Tue, 3 Dec 2024 14:14:53 +0000 Subject: [PATCH 11/95] Only show status bar item in relevant files --- editors/code/package.json | 35 +++++++++++++++++++++++++++++++++++ editors/code/src/config.ts | 4 ++++ editors/code/src/ctx.ts | 18 +++++++++++++++++- 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/editors/code/package.json b/editors/code/package.json index 82c43b76fd..46f7803c8a 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -425,6 +425,41 @@ ], "default": "openLogs", "markdownDescription": "Action to run when clicking the extension status bar item." + }, + "rust-analyzer.statusBar.documentSelector": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "language": { + "type": [ + "string", + "null" + ] + }, + "pattern": { + "type": [ + "string", + "null" + ] + } + } + }, + "default": [ + { + "language": "rust" + }, + { + "pattern": "**/Cargo.toml" + }, + { + "pattern": "**/Cargo.lock" + } + ], + "markdownDescription": "Determines when to show the extension status bar item based on the currently open file. Use `{ \"pattern\": \"**\" }` to always show. Use `null` to never show." } } }, diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index f979d54547..f7ef80df2b 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -348,6 +348,10 @@ export class Config { return this.get("statusBar.clickAction"); } + get statusBarDocumentSelector() { + return this.get("statusBar.documentSelector"); + } + get initializeStopped() { return this.get("initializeStopped"); } diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index 234fe6ab02..4a3f66b00d 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -88,6 +88,7 @@ export class Ctx implements RustAnalyzerExtensionApi { private _treeView: vscode.TreeView | undefined; private lastStatus: ServerStatusParams | { health: "stopped" } = { health: "stopped" }; private _serverVersion: string; + private statusBarActiveEditorListener: Disposable; get serverPath(): string | undefined { return this._serverPath; @@ -119,6 +120,10 @@ export class Ctx implements RustAnalyzerExtensionApi { this._serverVersion = ""; this.config = new Config(extCtx.subscriptions); this.statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); + this.updateStatusBarVisibility(vscode.window.activeTextEditor); + this.statusBarActiveEditorListener = vscode.window.onDidChangeActiveTextEditor((editor) => + this.updateStatusBarVisibility(editor), + ); if (this.config.testExplorer) { this.testController = vscode.tests.createTestController( "rustAnalyzerTestController", @@ -141,6 +146,7 @@ export class Ctx implements RustAnalyzerExtensionApi { dispose() { this.config.dispose(); this.statusBar.dispose(); + this.statusBarActiveEditorListener.dispose(); this.testController?.dispose(); void this.disposeClient(); this.commandDisposables.forEach((disposable) => disposable.dispose()); @@ -404,7 +410,6 @@ export class Ctx implements RustAnalyzerExtensionApi { let icon = ""; const status = this.lastStatus; const statusBar = this.statusBar; - statusBar.show(); statusBar.tooltip = new vscode.MarkdownString("", true); statusBar.tooltip.isTrusted = true; switch (status.health) { @@ -472,6 +477,17 @@ export class Ctx implements RustAnalyzerExtensionApi { statusBar.text = `${icon}rust-analyzer`; } + private updateStatusBarVisibility(editor: vscode.TextEditor | undefined) { + const documentSelector = this.config.statusBarDocumentSelector; + if (documentSelector != null) { + if (editor != null && vscode.languages.match(documentSelector, editor.document) > 0) { + this.statusBar.show(); + return; + } + } + this.statusBar.hide(); + } + pushExtCleanup(d: Disposable) { this.extCtx.subscriptions.push(d); } From 4049c3b6a9cba53744d7e32ecf933f457e29db6c Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Tue, 3 Dec 2024 20:46:45 +0200 Subject: [PATCH 12/95] Support `AsyncFnX` traits Only in calls, because to support them in bounds we need support from Chalk. However we don't yet report error from bounds anyway, so this is less severe. The returned future is shown in its name within inlay hints instead of as a nicer `impl Future`, but that can wait for another PR. --- crates/hir-def/src/lang_item.rs | 3 + crates/hir-ty/src/infer/expr.rs | 6 +- crates/hir-ty/src/infer/unify.rs | 110 ++++++++-------- crates/hir-ty/src/mir/lower.rs | 12 +- crates/hir-ty/src/tests/traits.rs | 50 ++++++++ crates/hir-ty/src/traits.rs | 26 +++- .../src/handlers/expected_function.rs | 21 ++++ crates/intern/src/symbol/symbols.rs | 8 ++ crates/test-utils/src/minicore.rs | 119 +++++++++++++++++- 9 files changed, 289 insertions(+), 66 deletions(-) diff --git a/crates/hir-def/src/lang_item.rs b/crates/hir-def/src/lang_item.rs index 166c965d14..0629d87e54 100644 --- a/crates/hir-def/src/lang_item.rs +++ b/crates/hir-def/src/lang_item.rs @@ -376,6 +376,9 @@ language_item_table! { Fn, sym::fn_, fn_trait, Target::Trait, GenericRequirement::Exact(1); FnMut, sym::fn_mut, fn_mut_trait, Target::Trait, GenericRequirement::Exact(1); FnOnce, sym::fn_once, fn_once_trait, Target::Trait, GenericRequirement::Exact(1); + AsyncFn, sym::async_fn, async_fn_trait, Target::Trait, GenericRequirement::Exact(1); + AsyncFnMut, sym::async_fn_mut, async_fn_mut_trait, Target::Trait, GenericRequirement::Exact(1); + AsyncFnOnce, sym::async_fn_once, async_fn_once_trait, Target::Trait, GenericRequirement::Exact(1); FnOnceOutput, sym::fn_once_output, fn_once_output, Target::AssocTy, GenericRequirement::None; diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 32b4ea2f28..c21ff19c45 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -1287,8 +1287,8 @@ impl InferenceContext<'_> { tgt_expr: ExprId, ) { match fn_x { - FnTrait::FnOnce => (), - FnTrait::FnMut => { + FnTrait::FnOnce | FnTrait::AsyncFnOnce => (), + FnTrait::FnMut | FnTrait::AsyncFnMut => { if let TyKind::Ref(Mutability::Mut, lt, inner) = derefed_callee.kind(Interner) { if adjustments .last() @@ -1312,7 +1312,7 @@ impl InferenceContext<'_> { )); } } - FnTrait::Fn => { + FnTrait::Fn | FnTrait::AsyncFn => { if !matches!(derefed_callee.kind(Interner), TyKind::Ref(Mutability::Not, _, _)) { adjustments.push(Adjustment::borrow( Mutability::Not, diff --git a/crates/hir-ty/src/infer/unify.rs b/crates/hir-ty/src/infer/unify.rs index 4d0539c135..165861c1b1 100644 --- a/crates/hir-ty/src/infer/unify.rs +++ b/crates/hir-ty/src/infer/unify.rs @@ -794,69 +794,75 @@ impl<'a> InferenceTable<'a> { ty: &Ty, num_args: usize, ) -> Option<(FnTrait, Vec, Ty)> { - let krate = self.trait_env.krate; - let fn_once_trait = FnTrait::FnOnce.get_id(self.db, krate)?; - let trait_data = self.db.trait_data(fn_once_trait); - let output_assoc_type = - trait_data.associated_type_by_name(&Name::new_symbol_root(sym::Output.clone()))?; + for (fn_trait_name, output_assoc_name, subtraits) in [ + (FnTrait::FnOnce, sym::Output.clone(), &[FnTrait::Fn, FnTrait::FnMut][..]), + (FnTrait::AsyncFnMut, sym::CallRefFuture.clone(), &[FnTrait::AsyncFn]), + (FnTrait::AsyncFnOnce, sym::CallOnceFuture.clone(), &[]), + ] { + let krate = self.trait_env.krate; + let fn_trait = fn_trait_name.get_id(self.db, krate)?; + let trait_data = self.db.trait_data(fn_trait); + let output_assoc_type = + trait_data.associated_type_by_name(&Name::new_symbol_root(output_assoc_name))?; - let mut arg_tys = Vec::with_capacity(num_args); - let arg_ty = TyBuilder::tuple(num_args) - .fill(|it| { - let arg = match it { - ParamKind::Type => self.new_type_var(), - ParamKind::Lifetime => unreachable!("Tuple with lifetime parameter"), - ParamKind::Const(_) => unreachable!("Tuple with const parameter"), - }; - arg_tys.push(arg.clone()); - arg.cast(Interner) - }) - .build(); + let mut arg_tys = Vec::with_capacity(num_args); + let arg_ty = TyBuilder::tuple(num_args) + .fill(|it| { + let arg = match it { + ParamKind::Type => self.new_type_var(), + ParamKind::Lifetime => unreachable!("Tuple with lifetime parameter"), + ParamKind::Const(_) => unreachable!("Tuple with const parameter"), + }; + arg_tys.push(arg.clone()); + arg.cast(Interner) + }) + .build(); - let b = TyBuilder::trait_ref(self.db, fn_once_trait); - if b.remaining() != 2 { - return None; - } - let mut trait_ref = b.push(ty.clone()).push(arg_ty).build(); + let b = TyBuilder::trait_ref(self.db, fn_trait); + if b.remaining() != 2 { + return None; + } + let mut trait_ref = b.push(ty.clone()).push(arg_ty).build(); - let projection = { - TyBuilder::assoc_type_projection( + let projection = TyBuilder::assoc_type_projection( self.db, output_assoc_type, Some(trait_ref.substitution.clone()), ) - .build() - }; + .fill_with_unknown() + .build(); - let trait_env = self.trait_env.env.clone(); - let obligation = InEnvironment { - goal: trait_ref.clone().cast(Interner), - environment: trait_env.clone(), - }; - let canonical = self.canonicalize(obligation.clone()); - if self.db.trait_solve(krate, self.trait_env.block, canonical.cast(Interner)).is_some() { - self.register_obligation(obligation.goal); - let return_ty = self.normalize_projection_ty(projection); - for fn_x in [FnTrait::Fn, FnTrait::FnMut, FnTrait::FnOnce] { - let fn_x_trait = fn_x.get_id(self.db, krate)?; - trait_ref.trait_id = to_chalk_trait_id(fn_x_trait); - let obligation: chalk_ir::InEnvironment> = InEnvironment { - goal: trait_ref.clone().cast(Interner), - environment: trait_env.clone(), - }; - let canonical = self.canonicalize(obligation.clone()); - if self - .db - .trait_solve(krate, self.trait_env.block, canonical.cast(Interner)) - .is_some() - { - return Some((fn_x, arg_tys, return_ty)); + let trait_env = self.trait_env.env.clone(); + let obligation = InEnvironment { + goal: trait_ref.clone().cast(Interner), + environment: trait_env.clone(), + }; + let canonical = self.canonicalize(obligation.clone()); + if self.db.trait_solve(krate, self.trait_env.block, canonical.cast(Interner)).is_some() + { + self.register_obligation(obligation.goal); + let return_ty = self.normalize_projection_ty(projection); + for &fn_x in subtraits { + let fn_x_trait = fn_x.get_id(self.db, krate)?; + trait_ref.trait_id = to_chalk_trait_id(fn_x_trait); + let obligation: chalk_ir::InEnvironment> = + InEnvironment { + goal: trait_ref.clone().cast(Interner), + environment: trait_env.clone(), + }; + let canonical = self.canonicalize(obligation.clone()); + if self + .db + .trait_solve(krate, self.trait_env.block, canonical.cast(Interner)) + .is_some() + { + return Some((fn_x, arg_tys, return_ty)); + } } + return Some((fn_trait_name, arg_tys, return_ty)); } - unreachable!("It should at least implement FnOnce at this point"); - } else { - None } + None } pub(super) fn insert_type_vars(&mut self, ty: T) -> T diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs index c4e0640051..1d1044df6e 100644 --- a/crates/hir-ty/src/mir/lower.rs +++ b/crates/hir-ty/src/mir/lower.rs @@ -2023,11 +2023,11 @@ pub fn mir_body_for_closure_query( ctx.result.locals.alloc(Local { ty: infer[*root].clone() }); let closure_local = ctx.result.locals.alloc(Local { ty: match kind { - FnTrait::FnOnce => infer[expr].clone(), - FnTrait::FnMut => { + FnTrait::FnOnce | FnTrait::AsyncFnOnce => infer[expr].clone(), + FnTrait::FnMut | FnTrait::AsyncFnMut => { TyKind::Ref(Mutability::Mut, error_lifetime(), infer[expr].clone()).intern(Interner) } - FnTrait::Fn => { + FnTrait::Fn | FnTrait::AsyncFn => { TyKind::Ref(Mutability::Not, error_lifetime(), infer[expr].clone()).intern(Interner) } }, @@ -2055,8 +2055,10 @@ pub fn mir_body_for_closure_query( let mut err = None; let closure_local = ctx.result.locals.iter().nth(1).unwrap().0; let closure_projection = match kind { - FnTrait::FnOnce => vec![], - FnTrait::FnMut | FnTrait::Fn => vec![ProjectionElem::Deref], + FnTrait::FnOnce | FnTrait::AsyncFnOnce => vec![], + FnTrait::FnMut | FnTrait::Fn | FnTrait::AsyncFnMut | FnTrait::AsyncFn => { + vec![ProjectionElem::Deref] + } }; ctx.result.walk_places(|p, store| { if let Some(it) = upvar_map.get(&p.local) { diff --git a/crates/hir-ty/src/tests/traits.rs b/crates/hir-ty/src/tests/traits.rs index 624148cab2..82ff51927e 100644 --- a/crates/hir-ty/src/tests/traits.rs +++ b/crates/hir-ty/src/tests/traits.rs @@ -4811,3 +4811,53 @@ fn bar(v: *const ()) { "#]], ); } + +#[test] +fn async_fn_traits() { + check_infer( + r#" +//- minicore: async_fn +async fn foo i32>(a: T) { + let fut1 = a(0); + fut1.await; +} +async fn bar i32>(mut b: T) { + let fut2 = b(0); + fut2.await; +} +async fn baz i32>(c: T) { + let fut3 = c(0); + fut3.await; +} + "#, + expect![[r#" + 37..38 'a': T + 43..83 '{ ...ait; }': () + 43..83 '{ ...ait; }': impl Future + 53..57 'fut1': AsyncFnMut::CallRefFuture<'?, T, (u32,)> + 60..61 'a': T + 60..64 'a(0)': AsyncFnMut::CallRefFuture<'?, T, (u32,)> + 62..63 '0': u32 + 70..74 'fut1': AsyncFnMut::CallRefFuture<'?, T, (u32,)> + 70..80 'fut1.await': i32 + 124..129 'mut b': T + 134..174 '{ ...ait; }': () + 134..174 '{ ...ait; }': impl Future + 144..148 'fut2': AsyncFnMut::CallRefFuture<'?, T, (u32,)> + 151..152 'b': T + 151..155 'b(0)': AsyncFnMut::CallRefFuture<'?, T, (u32,)> + 153..154 '0': u32 + 161..165 'fut2': AsyncFnMut::CallRefFuture<'?, T, (u32,)> + 161..171 'fut2.await': i32 + 216..217 'c': T + 222..262 '{ ...ait; }': () + 222..262 '{ ...ait; }': impl Future + 232..236 'fut3': AsyncFnOnce::CallOnceFuture + 239..240 'c': T + 239..243 'c(0)': AsyncFnOnce::CallOnceFuture + 241..242 '0': u32 + 249..253 'fut3': AsyncFnOnce::CallOnceFuture + 249..259 'fut3.await': i32 + "#]], + ); +} diff --git a/crates/hir-ty/src/traits.rs b/crates/hir-ty/src/traits.rs index 51ccd4ef29..8cb7dbf60f 100644 --- a/crates/hir-ty/src/traits.rs +++ b/crates/hir-ty/src/traits.rs @@ -220,6 +220,10 @@ pub enum FnTrait { FnOnce, FnMut, Fn, + + AsyncFnOnce, + AsyncFnMut, + AsyncFn, } impl fmt::Display for FnTrait { @@ -228,6 +232,9 @@ impl fmt::Display for FnTrait { FnTrait::FnOnce => write!(f, "FnOnce"), FnTrait::FnMut => write!(f, "FnMut"), FnTrait::Fn => write!(f, "Fn"), + FnTrait::AsyncFnOnce => write!(f, "AsyncFnOnce"), + FnTrait::AsyncFnMut => write!(f, "AsyncFnMut"), + FnTrait::AsyncFn => write!(f, "AsyncFn"), } } } @@ -238,6 +245,9 @@ impl FnTrait { FnTrait::FnOnce => "call_once", FnTrait::FnMut => "call_mut", FnTrait::Fn => "call", + FnTrait::AsyncFnOnce => "async_call_once", + FnTrait::AsyncFnMut => "async_call_mut", + FnTrait::AsyncFn => "async_call", } } @@ -246,6 +256,9 @@ impl FnTrait { FnTrait::FnOnce => LangItem::FnOnce, FnTrait::FnMut => LangItem::FnMut, FnTrait::Fn => LangItem::Fn, + FnTrait::AsyncFnOnce => LangItem::AsyncFnOnce, + FnTrait::AsyncFnMut => LangItem::AsyncFnMut, + FnTrait::AsyncFn => LangItem::AsyncFn, } } @@ -254,15 +267,19 @@ impl FnTrait { LangItem::FnOnce => Some(FnTrait::FnOnce), LangItem::FnMut => Some(FnTrait::FnMut), LangItem::Fn => Some(FnTrait::Fn), + LangItem::AsyncFnOnce => Some(FnTrait::AsyncFnOnce), + LangItem::AsyncFnMut => Some(FnTrait::AsyncFnMut), + LangItem::AsyncFn => Some(FnTrait::AsyncFn), _ => None, } } pub const fn to_chalk_ir(self) -> rust_ir::ClosureKind { + // Chalk doesn't support async fn traits. match self { - FnTrait::FnOnce => rust_ir::ClosureKind::FnOnce, - FnTrait::FnMut => rust_ir::ClosureKind::FnMut, - FnTrait::Fn => rust_ir::ClosureKind::Fn, + FnTrait::AsyncFnOnce | FnTrait::FnOnce => rust_ir::ClosureKind::FnOnce, + FnTrait::AsyncFnMut | FnTrait::FnMut => rust_ir::ClosureKind::FnMut, + FnTrait::AsyncFn | FnTrait::Fn => rust_ir::ClosureKind::Fn, } } @@ -271,6 +288,9 @@ impl FnTrait { FnTrait::FnOnce => Name::new_symbol_root(sym::call_once.clone()), FnTrait::FnMut => Name::new_symbol_root(sym::call_mut.clone()), FnTrait::Fn => Name::new_symbol_root(sym::call.clone()), + FnTrait::AsyncFnOnce => Name::new_symbol_root(sym::async_call_once.clone()), + FnTrait::AsyncFnMut => Name::new_symbol_root(sym::async_call_mut.clone()), + FnTrait::AsyncFn => Name::new_symbol_root(sym::async_call.clone()), } } diff --git a/crates/ide-diagnostics/src/handlers/expected_function.rs b/crates/ide-diagnostics/src/handlers/expected_function.rs index 02299197b1..e3a1e12e02 100644 --- a/crates/ide-diagnostics/src/handlers/expected_function.rs +++ b/crates/ide-diagnostics/src/handlers/expected_function.rs @@ -37,4 +37,25 @@ fn foo() { "#, ); } + + #[test] + fn no_error_for_async_fn_traits() { + check_diagnostics( + r#" +//- minicore: async_fn +async fn f(it: impl AsyncFn(u32) -> i32) { + let fut = it(0); + let _: i32 = fut.await; +} +async fn g(mut it: impl AsyncFnMut(u32) -> i32) { + let fut = it(0); + let _: i32 = fut.await; +} +async fn h(it: impl AsyncFnOnce(u32) -> i32) { + let fut = it(0); + let _: i32 = fut.await; +} + "#, + ); + } } diff --git a/crates/intern/src/symbol/symbols.rs b/crates/intern/src/symbol/symbols.rs index 865518fe94..ee96eff330 100644 --- a/crates/intern/src/symbol/symbols.rs +++ b/crates/intern/src/symbol/symbols.rs @@ -150,6 +150,9 @@ define_symbols! { C, call_mut, call_once, + async_call_once, + async_call_mut, + async_call, call, cdecl, Center, @@ -221,6 +224,9 @@ define_symbols! { fn_mut, fn_once_output, fn_once, + async_fn_once, + async_fn_mut, + async_fn, fn_ptr_addr, fn_ptr_trait, format_alignment, @@ -334,6 +340,8 @@ define_symbols! { Option, Ord, Output, + CallRefFuture, + CallOnceFuture, owned_box, packed, panic_2015, diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs index 07767d5ae9..f5c8466cb9 100644 --- a/crates/test-utils/src/minicore.rs +++ b/crates/test-utils/src/minicore.rs @@ -12,6 +12,7 @@ //! asm: //! assert: //! as_ref: sized +//! async_fn: fn, tuple, future, copy //! bool_impl: option, fn //! builtin_impls: //! cell: copy, drop @@ -29,7 +30,7 @@ //! eq: sized //! error: fmt //! fmt: option, result, transmute, coerce_unsized, copy, clone, derive -//! fn: +//! fn: tuple //! from: sized //! future: pin //! coroutine: pin @@ -60,6 +61,7 @@ //! sync: sized //! transmute: //! try: infallible +//! tuple: //! unpin: sized //! unsize: sized //! todo: panic @@ -138,10 +140,10 @@ pub mod marker { } // endregion:copy - // region:fn + // region:tuple #[lang = "tuple_trait"] pub trait Tuple {} - // endregion:fn + // endregion:tuple // region:phantom_data #[lang = "phantom_data"] @@ -682,6 +684,116 @@ pub mod ops { } pub use self::function::{Fn, FnMut, FnOnce}; // endregion:fn + + // region:async_fn + mod async_function { + use crate::{future::Future, marker::Tuple}; + + #[lang = "async_fn"] + #[fundamental] + pub trait AsyncFn: AsyncFnMut { + extern "rust-call" fn async_call(&self, args: Args) -> Self::CallRefFuture<'_>; + } + + #[lang = "async_fn_mut"] + #[fundamental] + pub trait AsyncFnMut: AsyncFnOnce { + #[lang = "call_ref_future"] + type CallRefFuture<'a>: Future + where + Self: 'a; + extern "rust-call" fn async_call_mut(&mut self, args: Args) -> Self::CallRefFuture<'_>; + } + + #[lang = "async_fn_once"] + #[fundamental] + pub trait AsyncFnOnce { + #[lang = "async_fn_once_output"] + type Output; + #[lang = "call_once_future"] + type CallOnceFuture: Future; + extern "rust-call" fn async_call_once(self, args: Args) -> Self::CallOnceFuture; + } + + mod impls { + use super::{AsyncFn, AsyncFnMut, AsyncFnOnce}; + use crate::marker::Tuple; + + impl AsyncFn for &F + where + F: AsyncFn, + { + extern "rust-call" fn async_call(&self, args: A) -> Self::CallRefFuture<'_> { + F::async_call(*self, args) + } + } + + #[unstable(feature = "async_fn_traits", issue = "none")] + impl AsyncFnMut for &F + where + F: AsyncFn, + { + type CallRefFuture<'a> + = F::CallRefFuture<'a> + where + Self: 'a; + + extern "rust-call" fn async_call_mut( + &mut self, + args: A, + ) -> Self::CallRefFuture<'_> { + F::async_call(*self, args) + } + } + + #[unstable(feature = "async_fn_traits", issue = "none")] + impl<'a, A: Tuple, F: ?Sized> AsyncFnOnce for &'a F + where + F: AsyncFn, + { + type Output = F::Output; + type CallOnceFuture = F::CallRefFuture<'a>; + + extern "rust-call" fn async_call_once(self, args: A) -> Self::CallOnceFuture { + F::async_call(self, args) + } + } + + #[unstable(feature = "async_fn_traits", issue = "none")] + impl AsyncFnMut for &mut F + where + F: AsyncFnMut, + { + type CallRefFuture<'a> + = F::CallRefFuture<'a> + where + Self: 'a; + + extern "rust-call" fn async_call_mut( + &mut self, + args: A, + ) -> Self::CallRefFuture<'_> { + F::async_call_mut(*self, args) + } + } + + #[unstable(feature = "async_fn_traits", issue = "none")] + impl<'a, A: Tuple, F: ?Sized> AsyncFnOnce for &'a mut F + where + F: AsyncFnMut, + { + type Output = F::Output; + type CallOnceFuture = F::CallRefFuture<'a>; + + extern "rust-call" fn async_call_once(self, args: A) -> Self::CallOnceFuture { + F::async_call_mut(self, args) + } + } + } + } + pub use self::async_function::{AsyncFn, AsyncFnMut, AsyncFnOnce}; + // endregion:async_fn + // region:try mod try_ { use crate::convert::Infallible; @@ -1684,6 +1796,7 @@ pub mod prelude { marker::Sync, // :sync mem::drop, // :drop ops::Drop, // :drop + ops::{AsyncFn, AsyncFnMut, AsyncFnOnce}, // :async_fn ops::{Fn, FnMut, FnOnce}, // :fn option::Option::{self, None, Some}, // :option panic, // :panic From d6b701e251012c7417ae61aea56b11f68cf87871 Mon Sep 17 00:00:00 2001 From: Tarek Date: Sun, 17 Nov 2024 15:25:23 +0200 Subject: [PATCH 13/95] internal: Make exclude characters for typing assists configurable, default to None Signed-off-by: Tarek --- crates/ide/src/lib.rs | 6 ++++++ crates/rust-analyzer/src/config.rs | 6 ++++++ crates/rust-analyzer/src/handlers/request.rs | 9 +++++++-- docs/user/generated_config.adoc | 5 +++++ editors/code/package.json | 13 +++++++++++++ 5 files changed, 37 insertions(+), 2 deletions(-) diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index d4ef9570e1..b4f3de3b17 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -411,6 +411,7 @@ impl Analysis { position: FilePosition, char_typed: char, autoclose: bool, + chars_to_exclude: Option, ) -> Cancellable> { // Fast path to not even parse the file. if !typing::TRIGGER_CHARS.contains(char_typed) { @@ -419,6 +420,11 @@ impl Analysis { if char_typed == '<' && !autoclose { return Ok(None); } + if let Some(chars_to_exclude) = chars_to_exclude { + if chars_to_exclude.contains(char_typed) { + return Ok(None); + } + } self.with_db(|db| typing::on_char_typed(db, position, char_typed)) } diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 152ce2944a..c2d6196ca4 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -310,6 +310,8 @@ config_data! { /// Whether to insert closing angle brackets when typing an opening angle bracket of a generic argument list. typing_autoClosingAngleBrackets_enable: bool = false, + /// Specify the characters to exclude from triggering typing assists. The default trigger characters are `.`, `=`, `<`, `>`, `{`, and `(`. Setting this to a string will disable typing assists for the specified characters. + typing_excludeChars: Option = None, /// Enables automatic discovery of projects using [`DiscoverWorkspaceConfig::command`]. @@ -2160,6 +2162,10 @@ impl Config { *self.typing_autoClosingAngleBrackets_enable() } + pub fn typing_exclude_chars(&self) -> Option { + self.typing_excludeChars().clone() + } + // VSCode is our reference implementation, so we allow ourselves to work around issues by // special casing certain versions pub fn visual_studio_code_version(&self) -> Option<&Version> { diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index 4975467ece..29820a3e37 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -459,9 +459,14 @@ pub(crate) fn handle_on_type_formatting( if char_typed == '>' { return Ok(None); } + let chars_to_exclude = snap.config.typing_exclude_chars(); - let edit = - snap.analysis.on_char_typed(position, char_typed, snap.config.typing_autoclose_angle())?; + let edit = snap.analysis.on_char_typed( + position, + char_typed, + snap.config.typing_autoclose_angle(), + chars_to_exclude, + )?; let edit = match edit { Some(it) => it, None => return Ok(None), diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index 052d0a2a41..d0c95912c3 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -997,6 +997,11 @@ Show documentation. -- Whether to insert closing angle brackets when typing an opening angle bracket of a generic argument list. -- +[[rust-analyzer.typing.excludeChars]]rust-analyzer.typing.excludeChars (default: `null`):: ++ +-- +Specify the characters to exclude from triggering typing assists. The default trigger characters are `.`, `=`, `<`, `>`, `{`, and `(`. Setting this to a string will disable typing assists for the specified characters. +-- [[rust-analyzer.workspace.discoverConfig]]rust-analyzer.workspace.discoverConfig (default: `null`):: + -- diff --git a/editors/code/package.json b/editors/code/package.json index 46f7803c8a..e98205e0ea 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -2612,6 +2612,19 @@ } } }, + { + "title": "typing", + "properties": { + "rust-analyzer.typing.excludeChars": { + "markdownDescription": "Specify the characters to exclude from triggering typing assists. The default trigger characters are `.`, `=`, `<`, `>`, `{`, and `(`. Setting this to a string will disable typing assists for the specified characters.", + "default": null, + "type": [ + "null", + "string" + ] + } + } + }, { "title": "workspace", "properties": { From 9aff46632eb88a9193cda7de24161909843d81ad Mon Sep 17 00:00:00 2001 From: Tarek Date: Tue, 3 Dec 2024 22:38:21 +0200 Subject: [PATCH 14/95] chore: deprecate `typing.autoClosingAngleBrackets` configuration Signed-off-by: Tarek --- crates/ide/src/lib.rs | 4 ---- crates/rust-analyzer/src/config.rs | 6 ------ crates/rust-analyzer/src/handlers/request.rs | 7 +------ docs/user/generated_config.adoc | 5 ----- editors/code/package.json | 10 ---------- 5 files changed, 1 insertion(+), 31 deletions(-) diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index b4f3de3b17..b43685ffee 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -410,16 +410,12 @@ impl Analysis { &self, position: FilePosition, char_typed: char, - autoclose: bool, chars_to_exclude: Option, ) -> Cancellable> { // Fast path to not even parse the file. if !typing::TRIGGER_CHARS.contains(char_typed) { return Ok(None); } - if char_typed == '<' && !autoclose { - return Ok(None); - } if let Some(chars_to_exclude) = chars_to_exclude { if chars_to_exclude.contains(char_typed) { return Ok(None); diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index c2d6196ca4..392bfbf15f 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -308,8 +308,6 @@ config_data! { /// Show documentation. signatureInfo_documentation_enable: bool = true, - /// Whether to insert closing angle brackets when typing an opening angle bracket of a generic argument list. - typing_autoClosingAngleBrackets_enable: bool = false, /// Specify the characters to exclude from triggering typing assists. The default trigger characters are `.`, `=`, `<`, `>`, `{`, and `(`. Setting this to a string will disable typing assists for the specified characters. typing_excludeChars: Option = None, @@ -2158,10 +2156,6 @@ impl Config { } } - pub fn typing_autoclose_angle(&self) -> bool { - *self.typing_autoClosingAngleBrackets_enable() - } - pub fn typing_exclude_chars(&self) -> Option { self.typing_excludeChars().clone() } diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index 29820a3e37..0fadfa6c42 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -461,12 +461,7 @@ pub(crate) fn handle_on_type_formatting( } let chars_to_exclude = snap.config.typing_exclude_chars(); - let edit = snap.analysis.on_char_typed( - position, - char_typed, - snap.config.typing_autoclose_angle(), - chars_to_exclude, - )?; + let edit = snap.analysis.on_char_typed(position, char_typed, chars_to_exclude)?; let edit = match edit { Some(it) => it, None => return Ok(None), diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index d0c95912c3..a3172c7ca2 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -992,11 +992,6 @@ Show full signature of the callable. Only shows parameters if disabled. -- Show documentation. -- -[[rust-analyzer.typing.autoClosingAngleBrackets.enable]]rust-analyzer.typing.autoClosingAngleBrackets.enable (default: `false`):: -+ --- -Whether to insert closing angle brackets when typing an opening angle bracket of a generic argument list. --- [[rust-analyzer.typing.excludeChars]]rust-analyzer.typing.excludeChars (default: `null`):: + -- diff --git a/editors/code/package.json b/editors/code/package.json index e98205e0ea..68c61e4bf6 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -2602,16 +2602,6 @@ } } }, - { - "title": "typing", - "properties": { - "rust-analyzer.typing.autoClosingAngleBrackets.enable": { - "markdownDescription": "Whether to insert closing angle brackets when typing an opening angle bracket of a generic argument list.", - "default": false, - "type": "boolean" - } - } - }, { "title": "typing", "properties": { From 1dbe6817574d37c1a1767b7eca1f76db78ef024a Mon Sep 17 00:00:00 2001 From: Mark Murphy Date: Tue, 3 Dec 2024 17:30:17 -0500 Subject: [PATCH 15/95] Remove references to platform-intrinsic ABI --- crates/hir-ty/src/lib.rs | 3 --- crates/ide-completion/src/completions/extern_abi.rs | 1 - crates/intern/src/symbol/symbols.rs | 1 - 3 files changed, 5 deletions(-) diff --git a/crates/hir-ty/src/lib.rs b/crates/hir-ty/src/lib.rs index 975625fdfa..fdc6579763 100644 --- a/crates/hir-ty/src/lib.rs +++ b/crates/hir-ty/src/lib.rs @@ -385,7 +385,6 @@ pub enum FnAbi { Fastcall, FastcallUnwind, Msp430Interrupt, - PlatformIntrinsic, PtxKernel, RiscvInterruptM, RiscvInterruptS, @@ -444,7 +443,6 @@ impl FnAbi { s if *s == sym::fastcall_dash_unwind => FnAbi::FastcallUnwind, s if *s == sym::fastcall => FnAbi::Fastcall, s if *s == sym::msp430_dash_interrupt => FnAbi::Msp430Interrupt, - s if *s == sym::platform_dash_intrinsic => FnAbi::PlatformIntrinsic, s if *s == sym::ptx_dash_kernel => FnAbi::PtxKernel, s if *s == sym::riscv_dash_interrupt_dash_m => FnAbi::RiscvInterruptM, s if *s == sym::riscv_dash_interrupt_dash_s => FnAbi::RiscvInterruptS, @@ -487,7 +485,6 @@ impl FnAbi { FnAbi::Fastcall => "fastcall", FnAbi::FastcallUnwind => "fastcall-unwind", FnAbi::Msp430Interrupt => "msp430-interrupt", - FnAbi::PlatformIntrinsic => "platform-intrinsic", FnAbi::PtxKernel => "ptx-kernel", FnAbi::RiscvInterruptM => "riscv-interrupt-m", FnAbi::RiscvInterruptS => "riscv-interrupt-s", diff --git a/crates/ide-completion/src/completions/extern_abi.rs b/crates/ide-completion/src/completions/extern_abi.rs index b0e417e6b3..847fa4cf88 100644 --- a/crates/ide-completion/src/completions/extern_abi.rs +++ b/crates/ide-completion/src/completions/extern_abi.rs @@ -38,7 +38,6 @@ const SUPPORTED_CALLING_CONVENTIONS: &[&str] = &[ "system-unwind", "rust-intrinsic", "rust-call", - "platform-intrinsic", "unadjusted", ]; diff --git a/crates/intern/src/symbol/symbols.rs b/crates/intern/src/symbol/symbols.rs index 865518fe94..8f79cf2007 100644 --- a/crates/intern/src/symbol/symbols.rs +++ b/crates/intern/src/symbol/symbols.rs @@ -99,7 +99,6 @@ define_symbols! { cdecl_dash_unwind = "cdecl-unwind", fastcall_dash_unwind = "fastcall-unwind", msp430_dash_interrupt = "msp430-interrupt", - platform_dash_intrinsic = "platform-intrinsic", ptx_dash_kernel = "ptx-kernel", riscv_dash_interrupt_dash_m = "riscv-interrupt-m", riscv_dash_interrupt_dash_s = "riscv-interrupt-s", From 215cbe9e7145b6cdba06be53d38aa5500833d77d Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Wed, 4 Dec 2024 03:15:32 +0200 Subject: [PATCH 16/95] Fixed another bug with glob imports When a glob import overriding the visibility of a previous glob import was not properly resolved when the items are only available in the next fixpoint iteration. The bug was hidden until #18390. --- crates/hir-def/src/nameres/collector.rs | 9 ++++-- crates/hir-def/src/nameres/tests/globs.rs | 39 +++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/crates/hir-def/src/nameres/collector.rs b/crates/hir-def/src/nameres/collector.rs index a37e3c70e2..98b08bcf70 100644 --- a/crates/hir-def/src/nameres/collector.rs +++ b/crates/hir-def/src/nameres/collector.rs @@ -910,8 +910,13 @@ impl DefCollector<'_> { self.update(module_id, &items, vis, Some(ImportType::Glob(id))); // record the glob import in case we add further items let glob = self.glob_imports.entry(m.local_id).or_default(); - if !glob.iter().any(|(mid, _, _)| *mid == module_id) { - glob.push((module_id, vis, id)); + match glob.iter_mut().find(|(mid, _, _)| *mid == module_id) { + None => glob.push((module_id, vis, id)), + Some((_, old_vis, _)) => { + if let Some(new_vis) = old_vis.max(vis, &self.def_map) { + *old_vis = new_vis; + } + } } } } diff --git a/crates/hir-def/src/nameres/tests/globs.rs b/crates/hir-def/src/nameres/tests/globs.rs index 543ab41cd5..8963a57679 100644 --- a/crates/hir-def/src/nameres/tests/globs.rs +++ b/crates/hir-def/src/nameres/tests/globs.rs @@ -451,3 +451,42 @@ mod glob_target { "#]], ); } + +#[test] +fn regression_18580() { + check( + r#" +pub mod libs { + pub struct Placeholder; +} + +pub mod reexport_2 { + use reexport_1::*; + pub use reexport_1::*; + + pub mod reexport_1 { + pub use crate::libs::*; + } +} + +use reexport_2::*; +"#, + expect![[r#" + crate + Placeholder: t v + libs: t + reexport_1: t + reexport_2: t + + crate::libs + Placeholder: t v + + crate::reexport_2 + Placeholder: t v + reexport_1: t + + crate::reexport_2::reexport_1 + Placeholder: t v + "#]], + ); +} From 3aeb5e66c4c437759242af6a91f9c43b02f86c2a Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Wed, 4 Dec 2024 03:38:37 +0200 Subject: [PATCH 17/95] Improve soundness a bit by making `TaggedArcPtr::try_as_arc_owned()` unsafe Since the `ManuallyDrop` it returns can be safely used to consume the `Arc`, which is can cause UB if done incorrectly. See #18499. --- crates/intern/src/symbol.rs | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/crates/intern/src/symbol.rs b/crates/intern/src/symbol.rs index ef76192ba8..200b14027f 100644 --- a/crates/intern/src/symbol.rs +++ b/crates/intern/src/symbol.rs @@ -77,8 +77,12 @@ impl TaggedArcPtr { } /// Retrieves the tag. + /// + /// # Safety + /// + /// You can only drop the `Arc` if the instance is dropped. #[inline] - pub(crate) fn try_as_arc_owned(self) -> Option>>> { + pub(crate) unsafe fn try_as_arc_owned(self) -> Option>>> { // Unpack the tag from the alignment niche let tag = Strict::addr(self.packed.as_ptr()) & Self::BOOL_BITS; if tag != 0 { @@ -245,16 +249,14 @@ impl Symbol { } } - ManuallyDrop::into_inner( - match shard.raw_entry_mut().from_key_hashed_nocheck::(hash, arc.as_ref()) { - RawEntryMut::Occupied(occ) => occ.remove_entry(), - RawEntryMut::Vacant(_) => unreachable!(), - } - .0 - .0 - .try_as_arc_owned() - .unwrap(), - ); + let ptr = match shard.raw_entry_mut().from_key_hashed_nocheck::(hash, arc.as_ref()) { + RawEntryMut::Occupied(occ) => occ.remove_entry(), + RawEntryMut::Vacant(_) => unreachable!(), + } + .0 + .0; + // SAFETY: We're dropping, we have ownership. + ManuallyDrop::into_inner(unsafe { ptr.try_as_arc_owned().unwrap() }); debug_assert_eq!(Arc::count(arc), 1); // Shrink the backing storage if the shard is less than 50% occupied. @@ -267,7 +269,8 @@ impl Symbol { impl Drop for Symbol { #[inline] fn drop(&mut self) { - let Some(arc) = self.repr.try_as_arc_owned() else { + // SAFETY: We're dropping, we have ownership. + let Some(arc) = (unsafe { self.repr.try_as_arc_owned() }) else { return; }; // When the last `Ref` is dropped, remove the object from the global map. @@ -288,7 +291,8 @@ impl Clone for Symbol { } fn increase_arc_refcount(repr: TaggedArcPtr) -> TaggedArcPtr { - let Some(arc) = repr.try_as_arc_owned() else { + // SAFETY: We're not dropping the `Arc`. + let Some(arc) = (unsafe { repr.try_as_arc_owned() }) else { return repr; }; // increase the ref count From 4ec7e61229cb721784aaea780c7f1f763d7d9f20 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Wed, 4 Dec 2024 04:02:54 +0200 Subject: [PATCH 18/95] Fix shadowing of record enum variant in patterns --- crates/hir-def/src/body/lower.rs | 14 +++++++------- crates/hir-def/src/body/tests.rs | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs index 1ab49e9156..3b73d40963 100644 --- a/crates/hir-def/src/body/lower.rs +++ b/crates/hir-def/src/body/lower.rs @@ -1510,20 +1510,20 @@ impl ExprCollector<'_> { BuiltinShadowMode::Other, None, ); + // Funnily enough, record structs/variants *can* be shadowed + // by pattern bindings (but unit or tuple structs/variants + // can't). match resolved.take_values() { Some(ModuleDefId::ConstId(_)) => (None, Pat::Path(name.into())), - Some(ModuleDefId::EnumVariantId(_)) => { - // this is only really valid for unit variants, but - // shadowing other enum variants with a pattern is - // an error anyway + Some(ModuleDefId::EnumVariantId(variant)) + if self.db.variant_data(variant.into()).kind() + != StructKind::Record => + { (None, Pat::Path(name.into())) } Some(ModuleDefId::AdtId(AdtId::StructId(s))) if self.db.struct_data(s).variant_data.kind() != StructKind::Record => { - // Funnily enough, record structs *can* be shadowed - // by pattern bindings (but unit or tuple structs - // can't). (None, Pat::Path(name.into())) } // shadowing statics is an error as well, so we just ignore that case here diff --git a/crates/hir-def/src/body/tests.rs b/crates/hir-def/src/body/tests.rs index 3b29d98d19..82d46d2e49 100644 --- a/crates/hir-def/src/body/tests.rs +++ b/crates/hir-def/src/body/tests.rs @@ -404,3 +404,26 @@ fn foo() { }"#]] .assert_eq(&body.pretty_print(&db, def, Edition::CURRENT)) } + +#[test] +fn shadowing_record_variant() { + let (_, body, _) = lower( + r#" +enum A { + B { field: i32 }, +} +fn f() { + use A::*; + match () { + B => {} + }; +} + "#, + ); + assert_eq!(body.bindings.len(), 1, "should have a binding for `B`"); + assert_eq!( + body.bindings[BindingId::from_raw(RawIdx::from_u32(0))].name.as_str(), + "B", + "should have a binding for `B`", + ); +} From b6fc9c14acba07761d7a65b77ac669c5d82429dc Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 4 Dec 2024 06:27:43 +0100 Subject: [PATCH 19/95] Better parser recovery for incomplete attributes --- crates/parser/src/grammar/attributes.rs | 10 ++- crates/parser/src/grammar/paths.rs | 19 ++-- crates/parser/src/parser.rs | 11 ++- crates/parser/test_data/generated/runner.rs | 2 + .../parser/err/0004_use_path_bad_segment.rast | 5 +- .../parser/err/0048_double_fish.rast | 10 ++- .../parser/inline/err/meta_recovery.rast | 86 +++++++++++++++++++ .../parser/inline/err/meta_recovery.rs | 6 ++ 8 files changed, 130 insertions(+), 19 deletions(-) create mode 100644 crates/parser/test_data/parser/inline/err/meta_recovery.rast create mode 100644 crates/parser/test_data/parser/inline/err/meta_recovery.rs diff --git a/crates/parser/src/grammar/attributes.rs b/crates/parser/src/grammar/attributes.rs index 82e4d66148..ccb556b2cc 100644 --- a/crates/parser/src/grammar/attributes.rs +++ b/crates/parser/src/grammar/attributes.rs @@ -36,6 +36,14 @@ fn attr(p: &mut Parser<'_>, inner: bool) { attr.complete(p, ATTR); } +// test_err meta_recovery +// #![] +// #![p = ] +// #![p::] +// #![p:: =] +// #![unsafe] +// #![unsafe =] + // test metas // #![simple_ident] // #![simple::path] @@ -63,7 +71,7 @@ pub(super) fn meta(p: &mut Parser<'_>) { if is_unsafe { p.expect(T!['(']); } - paths::use_path(p); + paths::attr_path(p); match p.current() { T![=] => { diff --git a/crates/parser/src/grammar/paths.rs b/crates/parser/src/grammar/paths.rs index 09db921803..057a691ef0 100644 --- a/crates/parser/src/grammar/paths.rs +++ b/crates/parser/src/grammar/paths.rs @@ -19,6 +19,10 @@ pub(super) fn use_path(p: &mut Parser<'_>) { path(p, Mode::Use); } +pub(super) fn attr_path(p: &mut Parser<'_>) { + path(p, Mode::Attr); +} + pub(crate) fn type_path(p: &mut Parser<'_>) { path(p, Mode::Type); } @@ -37,6 +41,7 @@ pub(crate) fn type_path_for_qualifier( #[derive(Clone, Copy, Eq, PartialEq)] enum Mode { Use, + Attr, Type, Expr, } @@ -93,12 +98,7 @@ fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) { p.error("expected `::`"); } } else { - let empty = if first { - p.eat(T![::]); - false - } else { - true - }; + let mut empty = if first { !p.eat(T![::]) } else { true }; match p.current() { IDENT => { name_ref(p); @@ -114,10 +114,13 @@ fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) { _ => { let recover_set = match mode { Mode::Use => items::ITEM_RECOVERY_SET, + Mode::Attr => { + items::ITEM_RECOVERY_SET.union(TokenSet::new(&[T![']'], T![=], T![#]])) + } Mode::Type => TYPE_PATH_SEGMENT_RECOVERY_SET, Mode::Expr => EXPR_PATH_SEGMENT_RECOVERY_SET, }; - p.err_recover("expected identifier", recover_set); + empty &= p.err_recover("expected identifier", recover_set); if empty { // test_err empty_segment // use crate::; @@ -132,7 +135,7 @@ fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) { fn opt_path_type_args(p: &mut Parser<'_>, mode: Mode) { match mode { - Mode::Use => {} + Mode::Use | Mode::Attr => {} Mode::Type => { // test typepathfn_with_coloncolon // type F = Start::(Middle) -> (Middle)::End; diff --git a/crates/parser/src/parser.rs b/crates/parser/src/parser.rs index f6b3783d1c..8078532e0b 100644 --- a/crates/parser/src/parser.rs +++ b/crates/parser/src/parser.rs @@ -258,22 +258,25 @@ impl<'t> Parser<'t> { self.err_recover(message, TokenSet::EMPTY); } - /// Create an error node and consume the next token. - pub(crate) fn err_recover(&mut self, message: &str, recovery: TokenSet) { + /// Create an error node and consume the next token unless it is in the recovery set. + /// + /// Returns true if recovery kicked in. + pub(crate) fn err_recover(&mut self, message: &str, recovery: TokenSet) -> bool { if matches!(self.current(), T!['{'] | T!['}']) { self.error(message); - return; + return true; } if self.at_ts(recovery) { self.error(message); - return; + return true; } let m = self.start(); self.error(message); self.bump_any(); m.complete(self, ERROR); + false } fn do_bump(&mut self, kind: SyntaxKind, n_raw_tokens: u8) { diff --git a/crates/parser/test_data/generated/runner.rs b/crates/parser/test_data/generated/runner.rs index 62b381b668..3db8b51a4b 100644 --- a/crates/parser/test_data/generated/runner.rs +++ b/crates/parser/test_data/generated/runner.rs @@ -772,6 +772,8 @@ mod err { run_and_expect_errors("test_data/parser/inline/err/match_arms_recovery.rs"); } #[test] + fn meta_recovery() { run_and_expect_errors("test_data/parser/inline/err/meta_recovery.rs"); } + #[test] fn method_call_missing_argument_list() { run_and_expect_errors("test_data/parser/inline/err/method_call_missing_argument_list.rs"); } diff --git a/crates/parser/test_data/parser/err/0004_use_path_bad_segment.rast b/crates/parser/test_data/parser/err/0004_use_path_bad_segment.rast index 44e192a5fc..cf455934e9 100644 --- a/crates/parser/test_data/parser/err/0004_use_path_bad_segment.rast +++ b/crates/parser/test_data/parser/err/0004_use_path_bad_segment.rast @@ -9,7 +9,8 @@ SOURCE_FILE NAME_REF IDENT "foo" COLON2 "::" - ERROR - INT_NUMBER "92" + PATH_SEGMENT + ERROR + INT_NUMBER "92" SEMICOLON ";" error 9: expected identifier diff --git a/crates/parser/test_data/parser/err/0048_double_fish.rast b/crates/parser/test_data/parser/err/0048_double_fish.rast index 207a5c24df..7ef1eb98fc 100644 --- a/crates/parser/test_data/parser/err/0048_double_fish.rast +++ b/crates/parser/test_data/parser/err/0048_double_fish.rast @@ -39,8 +39,9 @@ SOURCE_FILE IDENT "lol" R_ANGLE ">" COLON2 "::" - ERROR - L_ANGLE "<" + PATH_SEGMENT + ERROR + L_ANGLE "<" TYPE_ARG PATH_TYPE PATH @@ -91,8 +92,9 @@ SOURCE_FILE IDENT "lol" R_ANGLE ">" COLON2 "::" - ERROR - L_ANGLE "<" + PATH_SEGMENT + ERROR + L_ANGLE "<" EXPR_STMT BIN_EXPR PATH_EXPR diff --git a/crates/parser/test_data/parser/inline/err/meta_recovery.rast b/crates/parser/test_data/parser/inline/err/meta_recovery.rast new file mode 100644 index 0000000000..c4bec849e7 --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/meta_recovery.rast @@ -0,0 +1,86 @@ +SOURCE_FILE + ATTR + POUND "#" + BANG "!" + L_BRACK "[" + META + PATH + R_BRACK "]" + WHITESPACE "\n" + ATTR + POUND "#" + BANG "!" + L_BRACK "[" + META + PATH + PATH_SEGMENT + NAME_REF + IDENT "p" + WHITESPACE " " + EQ "=" + WHITESPACE " " + R_BRACK "]" + WHITESPACE "\n" + ATTR + POUND "#" + BANG "!" + L_BRACK "[" + META + PATH + PATH + PATH_SEGMENT + NAME_REF + IDENT "p" + COLON2 "::" + R_BRACK "]" + WHITESPACE "\n" + ATTR + POUND "#" + BANG "!" + L_BRACK "[" + META + PATH + PATH + PATH_SEGMENT + NAME_REF + IDENT "p" + COLON2 "::" + WHITESPACE " " + EQ "=" + R_BRACK "]" + WHITESPACE "\n" + ATTR + POUND "#" + BANG "!" + L_BRACK "[" + META + UNSAFE_KW "unsafe" + PATH + R_BRACK "]" + WHITESPACE "\n" + ATTR + POUND "#" + BANG "!" + L_BRACK "[" + META + UNSAFE_KW "unsafe" + WHITESPACE " " + PATH + EQ "=" + R_BRACK "]" + WHITESPACE "\n" +error 3: expected identifier +error 11: expected expression +error 11: expected expression +error 20: expected identifier +error 28: expected identifier +error 30: expected expression +error 30: expected expression +error 41: expected L_PAREN +error 41: expected identifier +error 41: expected R_PAREN +error 52: expected L_PAREN +error 53: expected identifier +error 54: expected expression +error 54: expected expression +error 54: expected R_PAREN diff --git a/crates/parser/test_data/parser/inline/err/meta_recovery.rs b/crates/parser/test_data/parser/inline/err/meta_recovery.rs new file mode 100644 index 0000000000..51d30adf8b --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/meta_recovery.rs @@ -0,0 +1,6 @@ +#![] +#![p = ] +#![p::] +#![p:: =] +#![unsafe] +#![unsafe =] From caba872f8883a4ab7f39ba5736ac147d98412fd7 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 4 Dec 2024 06:49:27 +0100 Subject: [PATCH 20/95] fix: Don't create empty path nodes --- crates/parser/src/grammar.rs | 4 ++-- crates/parser/src/grammar/generic_args.rs | 4 ++-- crates/parser/src/grammar/paths.rs | 23 +++++++++++++------ crates/parser/src/parser.rs | 8 +++---- .../err/crate_visibility_empty_recover.rast | 6 +---- .../parser/inline/err/meta_recovery.rast | 5 +--- 6 files changed, 26 insertions(+), 24 deletions(-) diff --git a/crates/parser/src/grammar.rs b/crates/parser/src/grammar.rs index a50a2182a7..c402c49855 100644 --- a/crates/parser/src/grammar.rs +++ b/crates/parser/src/grammar.rs @@ -242,7 +242,7 @@ fn opt_visibility(p: &mut Parser<'_>, in_tuple_field: bool) -> bool { // struct MyStruct(pub ()); if !(in_tuple_field && matches!(p.nth(1), T![ident] | T![')'])) { p.bump(T!['(']); - paths::use_path(p); + paths::vis_path(p); p.expect(T![')']); } } @@ -252,7 +252,7 @@ fn opt_visibility(p: &mut Parser<'_>, in_tuple_field: bool) -> bool { T![in] => { p.bump(T!['(']); p.bump(T![in]); - paths::use_path(p); + paths::vis_path(p); p.expect(T![')']); } _ => {} diff --git a/crates/parser/src/grammar/generic_args.rs b/crates/parser/src/grammar/generic_args.rs index c62c8a9d3f..77379ef147 100644 --- a/crates/parser/src/grammar/generic_args.rs +++ b/crates/parser/src/grammar/generic_args.rs @@ -168,10 +168,10 @@ pub(super) fn const_arg_expr(p: &mut Parser<'_>) { expressions::literal(p); lm.complete(p, PREFIX_EXPR); } - _ if paths::is_use_path_start(p) => { + _ if paths::is_path_start(p) => { // This shouldn't be hit by `const_arg` let lm = p.start(); - paths::use_path(p); + paths::expr_path(p); lm.complete(p, PATH_EXPR); } _ => { diff --git a/crates/parser/src/grammar/paths.rs b/crates/parser/src/grammar/paths.rs index 057a691ef0..15b3529642 100644 --- a/crates/parser/src/grammar/paths.rs +++ b/crates/parser/src/grammar/paths.rs @@ -19,6 +19,10 @@ pub(super) fn use_path(p: &mut Parser<'_>) { path(p, Mode::Use); } +pub(super) fn vis_path(p: &mut Parser<'_>) { + path(p, Mode::Vis); +} + pub(super) fn attr_path(p: &mut Parser<'_>) { path(p, Mode::Attr); } @@ -44,13 +48,17 @@ enum Mode { Attr, Type, Expr, + Vis, } -fn path(p: &mut Parser<'_>, mode: Mode) { +fn path(p: &mut Parser<'_>, mode: Mode) -> Option { let path = p.start(); - path_segment(p, mode, true); + if path_segment(p, mode, true).is_none() { + path.abandon(p); + return None; + } let qual = path.complete(p, PATH); - path_for_qualifier(p, mode, qual); + Some(path_for_qualifier(p, mode, qual)) } fn path_for_qualifier( @@ -76,7 +84,7 @@ const EXPR_PATH_SEGMENT_RECOVERY_SET: TokenSet = items::ITEM_RECOVERY_SET.union(TokenSet::new(&[T![')'], T![,], T![let]])); const TYPE_PATH_SEGMENT_RECOVERY_SET: TokenSet = types::TYPE_RECOVERY_SET; -fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) { +fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) -> Option { let m = p.start(); // test qual_paths // type X = ::Output; @@ -117,6 +125,7 @@ fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) { Mode::Attr => { items::ITEM_RECOVERY_SET.union(TokenSet::new(&[T![']'], T![=], T![#]])) } + Mode::Vis => items::ITEM_RECOVERY_SET.union(TokenSet::new(&[T![')']])), Mode::Type => TYPE_PATH_SEGMENT_RECOVERY_SET, Mode::Expr => EXPR_PATH_SEGMENT_RECOVERY_SET, }; @@ -125,17 +134,17 @@ fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) { // test_err empty_segment // use crate::; m.abandon(p); - return; + return None; } } }; } - m.complete(p, PATH_SEGMENT); + Some(m.complete(p, PATH_SEGMENT)) } fn opt_path_type_args(p: &mut Parser<'_>, mode: Mode) { match mode { - Mode::Use | Mode::Attr => {} + Mode::Use | Mode::Attr | Mode::Vis => {} Mode::Type => { // test typepathfn_with_coloncolon // type F = Start::(Middle) -> (Middle)::End; diff --git a/crates/parser/src/parser.rs b/crates/parser/src/parser.rs index 8078532e0b..75a75f601c 100644 --- a/crates/parser/src/parser.rs +++ b/crates/parser/src/parser.rs @@ -327,10 +327,10 @@ impl Marker { self.bomb.defuse(); let idx = self.pos as usize; if idx == p.events.len() - 1 { - match p.events.pop() { - Some(Event::Start { kind: TOMBSTONE, forward_parent: None }) => (), - _ => unreachable!(), - } + assert!(matches!( + p.events.pop(), + Some(Event::Start { kind: TOMBSTONE, forward_parent: None }) + )); } } } diff --git a/crates/parser/test_data/parser/inline/err/crate_visibility_empty_recover.rast b/crates/parser/test_data/parser/inline/err/crate_visibility_empty_recover.rast index 0fe4ca42d7..681ca6b6e0 100644 --- a/crates/parser/test_data/parser/inline/err/crate_visibility_empty_recover.rast +++ b/crates/parser/test_data/parser/inline/err/crate_visibility_empty_recover.rast @@ -3,10 +3,7 @@ SOURCE_FILE VISIBILITY PUB_KW "pub" L_PAREN "(" - PATH - PATH_SEGMENT - ERROR - R_PAREN ")" + R_PAREN ")" WHITESPACE " " STRUCT_KW "struct" WHITESPACE " " @@ -15,4 +12,3 @@ SOURCE_FILE SEMICOLON ";" WHITESPACE "\n" error 4: expected identifier -error 5: expected R_PAREN diff --git a/crates/parser/test_data/parser/inline/err/meta_recovery.rast b/crates/parser/test_data/parser/inline/err/meta_recovery.rast index c4bec849e7..926dd50fc8 100644 --- a/crates/parser/test_data/parser/inline/err/meta_recovery.rast +++ b/crates/parser/test_data/parser/inline/err/meta_recovery.rast @@ -4,7 +4,6 @@ SOURCE_FILE BANG "!" L_BRACK "[" META - PATH R_BRACK "]" WHITESPACE "\n" ATTR @@ -55,7 +54,6 @@ SOURCE_FILE L_BRACK "[" META UNSAFE_KW "unsafe" - PATH R_BRACK "]" WHITESPACE "\n" ATTR @@ -65,7 +63,6 @@ SOURCE_FILE META UNSAFE_KW "unsafe" WHITESPACE " " - PATH EQ "=" R_BRACK "]" WHITESPACE "\n" @@ -80,7 +77,7 @@ error 41: expected L_PAREN error 41: expected identifier error 41: expected R_PAREN error 52: expected L_PAREN -error 53: expected identifier +error 52: expected identifier error 54: expected expression error 54: expected expression error 54: expected R_PAREN From 83f5150978d58614e0ba7c40c2a1e75c9c7fda1b Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 4 Dec 2024 07:03:01 +0100 Subject: [PATCH 21/95] Update mbe test output --- crates/hir-def/src/macro_expansion_tests/mbe.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/hir-def/src/macro_expansion_tests/mbe.rs b/crates/hir-def/src/macro_expansion_tests/mbe.rs index d568f6faa7..5c03fad613 100644 --- a/crates/hir-def/src/macro_expansion_tests/mbe.rs +++ b/crates/hir-def/src/macro_expansion_tests/mbe.rs @@ -1759,8 +1759,9 @@ fn f() { // NAME_REF@6..7 // IDENT@6..7 "K" // COLON2@7..9 "::" -// ERROR@9..10 -// L_PAREN@9..10 "(" +// PATH_SEGMENT@9..10 +// ERROR@9..10 +// L_PAREN@9..10 "(" // EXPR_STMT@10..16 // CALL_EXPR@10..16 // PATH_EXPR@10..11 From 890d155ffeb3ac05ea8364c32995a3f782f32a44 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Wed, 4 Dec 2024 01:34:52 +0200 Subject: [PATCH 22/95] Complete derive helper attributes Only their names, anything can go inside. --- crates/hir/src/semantics.rs | 16 +++++ .../src/completions/attribute.rs | 13 +++- crates/ide-completion/src/context.rs | 5 +- crates/ide-completion/src/context/analysis.rs | 17 ++++- crates/ide-completion/src/tests/attribute.rs | 64 +++++++++++++++++++ 5 files changed, 111 insertions(+), 4 deletions(-) diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 46766fcc5b..65470d061b 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -510,6 +510,22 @@ impl<'db> SemanticsImpl<'db> { self.with_ctx(|ctx| ctx.has_derives(adt)) } + pub fn derive_helpers_in_scope(&self, adt: &ast::Adt) -> Option> { + let sa = self.analyze_no_infer(adt.syntax())?; + let id = self.db.ast_id_map(sa.file_id).ast_id(adt); + let result = sa + .resolver + .def_map() + .derive_helpers_in_scope(InFile::new(sa.file_id, id))? + .iter() + .map(|(name, macro_, _)| { + let macro_name = Macro::from(*macro_).name(self.db).symbol().clone(); + (name.symbol().clone(), macro_name) + }) + .collect(); + Some(result) + } + pub fn derive_helper(&self, attr: &ast::Attr) -> Option> { let adt = attr.syntax().ancestors().find_map(ast::Item::cast).and_then(|it| match it { ast::Item::Struct(it) => Some(ast::Adt::Struct(it)), diff --git a/crates/ide-completion/src/completions/attribute.rs b/crates/ide-completion/src/completions/attribute.rs index d0b489c4e8..cf5427bae3 100644 --- a/crates/ide-completion/src/completions/attribute.rs +++ b/crates/ide-completion/src/completions/attribute.rs @@ -86,10 +86,21 @@ pub(crate) fn complete_attribute_path( acc: &mut Completions, ctx: &CompletionContext<'_>, path_ctx @ PathCompletionCtx { qualified, .. }: &PathCompletionCtx, - &AttrCtx { kind, annotated_item_kind }: &AttrCtx, + &AttrCtx { kind, annotated_item_kind, ref derive_helpers }: &AttrCtx, ) { let is_inner = kind == AttrKind::Inner; + for (derive_helper, derive_name) in derive_helpers { + let mut item = CompletionItem::new( + SymbolKind::Attribute, + ctx.source_range(), + derive_helper.as_str(), + ctx.edition, + ); + item.detail(format!("derive helper of `{derive_name}`")); + item.add_to(acc, ctx.db); + } + match qualified { Qualified::With { resolution: Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))), diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index efbee39a2d..5b8d1c30a2 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -7,8 +7,8 @@ mod tests; use std::{iter, ops::ControlFlow}; use hir::{ - HasAttrs, Local, ModuleSource, Name, PathResolution, ScopeDef, Semantics, SemanticsScope, Type, - TypeInfo, + HasAttrs, Local, ModuleSource, Name, PathResolution, ScopeDef, Semantics, SemanticsScope, + Symbol, Type, TypeInfo, }; use ide_db::{ base_db::SourceDatabase, famous_defs::FamousDefs, helpers::is_editable_crate, FilePosition, @@ -133,6 +133,7 @@ pub(crate) type ExistingDerives = FxHashSet; pub(crate) struct AttrCtx { pub(crate) kind: AttrKind, pub(crate) annotated_item_kind: Option, + pub(crate) derive_helpers: Vec<(Symbol, Symbol)>, } #[derive(Debug, PartialEq, Eq)] diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs index 468ad81ad2..a4e018b180 100644 --- a/crates/ide-completion/src/context/analysis.rs +++ b/crates/ide-completion/src/context/analysis.rs @@ -1129,7 +1129,22 @@ fn classify_name_ref( let is_trailing_outer_attr = kind != AttrKind::Inner && non_trivia_sibling(attr.syntax().clone().into(), syntax::Direction::Next).is_none(); let annotated_item_kind = if is_trailing_outer_attr { None } else { Some(attached.kind()) }; - Some(PathKind::Attr { attr_ctx: AttrCtx { kind, annotated_item_kind } }) + let derive_helpers = annotated_item_kind + .filter(|kind| { + matches!( + kind, + SyntaxKind::STRUCT + | SyntaxKind::ENUM + | SyntaxKind::UNION + | SyntaxKind::VARIANT + | SyntaxKind::TUPLE_FIELD + | SyntaxKind::RECORD_FIELD + ) + }) + .and_then(|_| nameref.as_ref()?.syntax().ancestors().find_map(ast::Adt::cast)) + .and_then(|adt| sema.derive_helpers_in_scope(&adt)) + .unwrap_or_default(); + Some(PathKind::Attr { attr_ctx: AttrCtx { kind, annotated_item_kind, derive_helpers } }) }; // Infer the path kind diff --git a/crates/ide-completion/src/tests/attribute.rs b/crates/ide-completion/src/tests/attribute.rs index 45679355b4..1443ebc6c0 100644 --- a/crates/ide-completion/src/tests/attribute.rs +++ b/crates/ide-completion/src/tests/attribute.rs @@ -8,6 +8,70 @@ fn check(ra_fixture: &str, expect: Expect) { expect.assert_eq(&actual); } +#[test] +fn derive_helpers() { + check( + r#" +//- /mac.rs crate:mac +#![crate_type = "proc-macro"] + +#[proc_macro_derive(MyDerive, attributes(my_cool_helper_attribute))] +pub fn my_derive() {} + +//- /lib.rs crate:lib deps:mac +#[rustc_builtin_macro] +pub macro derive($item:item) {} + +#[derive(mac::MyDerive)] +pub struct Foo(#[m$0] i32); +"#, + expect![[r#" + at allow(…) + at automatically_derived + at cfg(…) + at cfg_attr(…) + at cold + at deny(…) + at deprecated + at derive macro derive + at derive(…) + at doc = "…" + at doc(alias = "…") + at doc(hidden) + at expect(…) + at export_name = "…" + at forbid(…) + at global_allocator + at ignore = "…" + at inline + at link + at link_name = "…" + at link_section = "…" + at macro_export + at macro_use + at must_use + at my_cool_helper_attribute derive helper of `MyDerive` + at no_mangle + at non_exhaustive + at panic_handler + at path = "…" + at proc_macro + at proc_macro_attribute + at proc_macro_derive(…) + at repr(…) + at should_panic + at target_feature(enable = "…") + at test + at track_caller + at used + at warn(…) + md mac + kw crate:: + kw self:: + "#]], + ) +} + #[test] fn proc_macros() { check( From 327b8c9f9374e73e4a2d05f87e91f34557f19ce9 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Wed, 4 Dec 2024 08:05:23 +0200 Subject: [PATCH 23/95] Extend reported unsafe operations We add union fields access (in both expressions and patterns) and inline assembly. That completes the unsafe check (there are some other unsafe things but they are unstable), and so also opens the door to reporting unused unsafe without annoying people about their not-unused unsafe blocks. --- crates/hir-def/src/body.rs | 139 +++++++- crates/hir-ty/src/diagnostics.rs | 2 +- crates/hir-ty/src/diagnostics/unsafe_check.rs | 319 +++++++++++++----- crates/hir/src/diagnostics.rs | 7 +- crates/hir/src/lib.rs | 9 +- crates/hir/src/source_analyzer.rs | 6 +- .../src/handlers/missing_unsafe.rs | 154 +++++++-- 7 files changed, 511 insertions(+), 125 deletions(-) diff --git a/crates/hir-def/src/body.rs b/crates/hir-def/src/body.rs index 5a386f6cf8..d4a1120908 100644 --- a/crates/hir-def/src/body.rs +++ b/crates/hir-def/src/body.rs @@ -408,7 +408,8 @@ impl Body { f(else_branch); } } - Expr::Let { expr, .. } => { + Expr::Let { expr, pat } => { + self.walk_exprs_in_pat(*pat, &mut f); f(*expr); } Expr::Block { statements, tail, .. } @@ -442,6 +443,137 @@ impl Body { f(*receiver); args.iter().copied().for_each(f); } + Expr::Match { expr, arms } => { + f(*expr); + arms.iter().for_each(|arm| { + f(arm.expr); + self.walk_exprs_in_pat(arm.pat, &mut f); + }); + } + Expr::Break { expr, .. } + | Expr::Return { expr } + | Expr::Yield { expr } + | Expr::Yeet { expr } => { + if let &Some(expr) = expr { + f(expr); + } + } + Expr::Become { expr } => f(*expr), + Expr::RecordLit { fields, spread, .. } => { + for field in fields.iter() { + f(field.expr); + } + if let &Some(expr) = spread { + f(expr); + } + } + Expr::Closure { body, .. } => { + f(*body); + } + Expr::BinaryOp { lhs, rhs, .. } => { + f(*lhs); + f(*rhs); + } + Expr::Range { lhs, rhs, .. } => { + if let &Some(lhs) = rhs { + f(lhs); + } + if let &Some(rhs) = lhs { + f(rhs); + } + } + Expr::Index { base, index, .. } => { + f(*base); + f(*index); + } + Expr::Field { expr, .. } + | Expr::Await { expr } + | Expr::Cast { expr, .. } + | Expr::Ref { expr, .. } + | Expr::UnaryOp { expr, .. } + | Expr::Box { expr } => { + f(*expr); + } + Expr::Tuple { exprs, .. } => exprs.iter().copied().for_each(f), + Expr::Array(a) => match a { + Array::ElementList { elements, .. } => elements.iter().copied().for_each(f), + Array::Repeat { initializer, repeat } => { + f(*initializer); + f(*repeat) + } + }, + &Expr::Assignment { target, value } => { + self.walk_exprs_in_pat(target, &mut f); + f(value); + } + } + } + + pub fn walk_child_exprs_without_pats(&self, expr_id: ExprId, mut f: impl FnMut(ExprId)) { + let expr = &self[expr_id]; + match expr { + Expr::Continue { .. } + | Expr::Const(_) + | Expr::Missing + | Expr::Path(_) + | Expr::OffsetOf(_) + | Expr::Literal(_) + | Expr::Underscore => {} + Expr::InlineAsm(it) => it.operands.iter().for_each(|(_, op)| match op { + AsmOperand::In { expr, .. } + | AsmOperand::Out { expr: Some(expr), .. } + | AsmOperand::InOut { expr, .. } => f(*expr), + AsmOperand::SplitInOut { in_expr, out_expr, .. } => { + f(*in_expr); + if let Some(out_expr) = out_expr { + f(*out_expr); + } + } + AsmOperand::Out { expr: None, .. } + | AsmOperand::Const(_) + | AsmOperand::Label(_) + | AsmOperand::Sym(_) => (), + }), + Expr::If { condition, then_branch, else_branch } => { + f(*condition); + f(*then_branch); + if let &Some(else_branch) = else_branch { + f(else_branch); + } + } + Expr::Let { expr, .. } => { + f(*expr); + } + Expr::Block { statements, tail, .. } + | Expr::Unsafe { statements, tail, .. } + | Expr::Async { statements, tail, .. } => { + for stmt in statements.iter() { + match stmt { + Statement::Let { initializer, else_branch, .. } => { + if let &Some(expr) = initializer { + f(expr); + } + if let &Some(expr) = else_branch { + f(expr); + } + } + Statement::Expr { expr: expression, .. } => f(*expression), + Statement::Item(_) => (), + } + } + if let &Some(expr) = tail { + f(expr); + } + } + Expr::Loop { body, .. } => f(*body), + Expr::Call { callee, args, .. } => { + f(*callee); + args.iter().copied().for_each(f); + } + Expr::MethodCall { receiver, args, .. } => { + f(*receiver); + args.iter().copied().for_each(f); + } Expr::Match { expr, arms } => { f(*expr); arms.iter().map(|arm| arm.expr).for_each(f); @@ -498,10 +630,7 @@ impl Body { f(*repeat) } }, - &Expr::Assignment { target, value } => { - self.walk_exprs_in_pat(target, &mut f); - f(value); - } + &Expr::Assignment { target: _, value } => f(value), } } diff --git a/crates/hir-ty/src/diagnostics.rs b/crates/hir-ty/src/diagnostics.rs index af4d2c9fc0..30c02a2936 100644 --- a/crates/hir-ty/src/diagnostics.rs +++ b/crates/hir-ty/src/diagnostics.rs @@ -9,5 +9,5 @@ pub use crate::diagnostics::{ expr::{ record_literal_missing_fields, record_pattern_missing_fields, BodyValidationDiagnostic, }, - unsafe_check::{missing_unsafe, unsafe_expressions, UnsafeExpr}, + unsafe_check::{missing_unsafe, unsafe_expressions, InsideUnsafeBlock, UnsafetyReason}, }; diff --git a/crates/hir-ty/src/diagnostics/unsafe_check.rs b/crates/hir-ty/src/diagnostics/unsafe_check.rs index c7f7fb7ad3..193aaa52c2 100644 --- a/crates/hir-ty/src/diagnostics/unsafe_check.rs +++ b/crates/hir-ty/src/diagnostics/unsafe_check.rs @@ -1,12 +1,16 @@ //! Provides validations for unsafe code. Currently checks if unsafe functions are missing //! unsafe blocks. +use std::mem; + +use either::Either; use hir_def::{ body::Body, - hir::{Expr, ExprId, ExprOrPatId, Pat, UnaryOp}, - resolver::{resolver_for_expr, ResolveValueResult, Resolver, ValueNs}, + hir::{Expr, ExprId, ExprOrPatId, Pat, PatId, Statement, UnaryOp}, + path::Path, + resolver::{HasResolver, ResolveValueResult, Resolver, ValueNs}, type_ref::Rawness, - DefWithBodyId, + AdtId, DefWithBodyId, FieldId, VariantId, }; use crate::{ @@ -16,7 +20,10 @@ use crate::{ /// Returns `(unsafe_exprs, fn_is_unsafe)`. /// /// If `fn_is_unsafe` is false, `unsafe_exprs` are hard errors. If true, they're `unsafe_op_in_unsafe_fn`. -pub fn missing_unsafe(db: &dyn HirDatabase, def: DefWithBodyId) -> (Vec, bool) { +pub fn missing_unsafe( + db: &dyn HirDatabase, + def: DefWithBodyId, +) -> (Vec<(ExprOrPatId, UnsafetyReason)>, bool) { let _p = tracing::info_span!("missing_unsafe").entered(); let mut res = Vec::new(); @@ -30,111 +37,243 @@ pub fn missing_unsafe(db: &dyn HirDatabase, def: DefWithBodyId) -> (Vec { + db: &'a dyn HirDatabase, + infer: &'a InferenceResult, + body: &'a Body, + resolver: Resolver, def: DefWithBodyId, - current: ExprId, - inside_unsafe_block: bool, - unsafe_expr_cb: &mut dyn FnMut(UnsafeExpr), -) { - let mut mark_unsafe_path = |path, node| { - let g = resolver.update_to_inner_scope(db.upcast(), def, current); - let hygiene = body.expr_or_pat_path_hygiene(node); - let value_or_partial = resolver.resolve_path_in_value_ns(db.upcast(), path, hygiene); - if let Some(ResolveValueResult::ValueNs(ValueNs::StaticId(id), _)) = value_or_partial { - let static_data = db.static_data(id); - if static_data.mutable || (static_data.is_extern && !static_data.has_safe_kw) { - unsafe_expr_cb(UnsafeExpr { node, inside_unsafe_block }); - } - } - resolver.reset_to_guard(g); - }; + inside_unsafe_block: InsideUnsafeBlock, + inside_assignment: bool, + inside_union_destructure: bool, + unsafe_expr_cb: &'a mut dyn FnMut(ExprOrPatId, InsideUnsafeBlock, UnsafetyReason), +} - let expr = &body.exprs[current]; - match expr { - &Expr::Call { callee, .. } => { - if let Some(func) = infer[callee].as_fn_def(db) { - if is_fn_unsafe_to_call(db, func) { - unsafe_expr_cb(UnsafeExpr { node: current.into(), inside_unsafe_block }); - } - } +impl<'a> UnsafeVisitor<'a> { + fn new( + db: &'a dyn HirDatabase, + infer: &'a InferenceResult, + body: &'a Body, + def: DefWithBodyId, + unsafe_expr_cb: &'a mut dyn FnMut(ExprOrPatId, InsideUnsafeBlock, UnsafetyReason), + ) -> Self { + let resolver = def.resolver(db.upcast()); + Self { + db, + infer, + body, + resolver, + def, + inside_unsafe_block: InsideUnsafeBlock::No, + inside_assignment: false, + inside_union_destructure: false, + unsafe_expr_cb, } - Expr::Path(path) => mark_unsafe_path(path, current.into()), - Expr::Ref { expr, rawness: Rawness::RawPtr, mutability: _ } => { - if let Expr::Path(_) = body.exprs[*expr] { - // Do not report unsafe for `addr_of[_mut]!(EXTERN_OR_MUT_STATIC)`, - // see https://github.com/rust-lang/rust/pull/125834. - return; - } - } - Expr::MethodCall { .. } => { - if infer - .method_resolution(current) - .map(|(func, _)| is_fn_unsafe_to_call(db, func)) - .unwrap_or(false) - { - unsafe_expr_cb(UnsafeExpr { node: current.into(), inside_unsafe_block }); - } - } - Expr::UnaryOp { expr, op: UnaryOp::Deref } => { - if let TyKind::Raw(..) = &infer[*expr].kind(Interner) { - unsafe_expr_cb(UnsafeExpr { node: current.into(), inside_unsafe_block }); - } - } - Expr::Unsafe { .. } => { - return body.walk_child_exprs(current, |child| { - walk_unsafe(db, infer, body, resolver, def, child, true, unsafe_expr_cb); - }); - } - &Expr::Assignment { target, value: _ } => { - body.walk_pats(target, &mut |pat| { - if let Pat::Path(path) = &body[pat] { - mark_unsafe_path(path, pat.into()); - } - }); - } - _ => {} } - body.walk_child_exprs(current, |child| { - walk_unsafe(db, infer, body, resolver, def, child, inside_unsafe_block, unsafe_expr_cb); - }); + fn call_cb(&mut self, node: ExprOrPatId, reason: UnsafetyReason) { + (self.unsafe_expr_cb)(node, self.inside_unsafe_block, reason); + } + + fn walk_pats_top(&mut self, pats: impl Iterator, parent_expr: ExprId) { + let guard = self.resolver.update_to_inner_scope(self.db.upcast(), self.def, parent_expr); + pats.for_each(|pat| self.walk_pat(pat)); + self.resolver.reset_to_guard(guard); + } + + fn walk_pat(&mut self, current: PatId) { + let pat = &self.body.pats[current]; + + if self.inside_union_destructure { + match pat { + Pat::Tuple { .. } + | Pat::Record { .. } + | Pat::Range { .. } + | Pat::Slice { .. } + | Pat::Path(..) + | Pat::Lit(..) + | Pat::Bind { .. } + | Pat::TupleStruct { .. } + | Pat::Ref { .. } + | Pat::Box { .. } + | Pat::Expr(..) + | Pat::ConstBlock(..) => self.call_cb(current.into(), UnsafetyReason::UnionField), + // `Or` only wraps other patterns, and `Missing`/`Wild` do not constitute a read. + Pat::Missing | Pat::Wild | Pat::Or(_) => {} + } + } + + match pat { + Pat::Record { .. } => { + if let Some((AdtId::UnionId(_), _)) = self.infer[current].as_adt() { + let old_inside_union_destructure = + mem::replace(&mut self.inside_union_destructure, true); + self.body.walk_pats_shallow(current, |pat| self.walk_pat(pat)); + self.inside_union_destructure = old_inside_union_destructure; + return; + } + } + Pat::Path(path) => self.mark_unsafe_path(current.into(), path), + &Pat::ConstBlock(expr) => { + let old_inside_assignment = mem::replace(&mut self.inside_assignment, false); + self.walk_expr(expr); + self.inside_assignment = old_inside_assignment; + } + &Pat::Expr(expr) => self.walk_expr(expr), + _ => {} + } + + self.body.walk_pats_shallow(current, |pat| self.walk_pat(pat)); + } + + fn walk_expr(&mut self, current: ExprId) { + let expr = &self.body.exprs[current]; + let inside_assignment = mem::replace(&mut self.inside_assignment, false); + match expr { + &Expr::Call { callee, .. } => { + if let Some(func) = self.infer[callee].as_fn_def(self.db) { + if is_fn_unsafe_to_call(self.db, func) { + self.call_cb(current.into(), UnsafetyReason::UnsafeFnCall); + } + } + } + Expr::Path(path) => { + let guard = + self.resolver.update_to_inner_scope(self.db.upcast(), self.def, current); + self.mark_unsafe_path(current.into(), path); + self.resolver.reset_to_guard(guard); + } + Expr::Ref { expr, rawness: Rawness::RawPtr, mutability: _ } => { + if let Expr::Path(_) = self.body.exprs[*expr] { + // Do not report unsafe for `addr_of[_mut]!(EXTERN_OR_MUT_STATIC)`, + // see https://github.com/rust-lang/rust/pull/125834. + return; + } + } + Expr::MethodCall { .. } => { + if self + .infer + .method_resolution(current) + .map(|(func, _)| is_fn_unsafe_to_call(self.db, func)) + .unwrap_or(false) + { + self.call_cb(current.into(), UnsafetyReason::UnsafeFnCall); + } + } + Expr::UnaryOp { expr, op: UnaryOp::Deref } => { + if let TyKind::Raw(..) = &self.infer[*expr].kind(Interner) { + self.call_cb(current.into(), UnsafetyReason::RawPtrDeref); + } + } + Expr::Unsafe { .. } => { + let old_inside_unsafe_block = + mem::replace(&mut self.inside_unsafe_block, InsideUnsafeBlock::Yes); + self.body.walk_child_exprs_without_pats(current, |child| self.walk_expr(child)); + self.inside_unsafe_block = old_inside_unsafe_block; + return; + } + &Expr::Assignment { target, value: _ } => { + let old_inside_assignment = mem::replace(&mut self.inside_assignment, true); + self.walk_pats_top(std::iter::once(target), current); + self.inside_assignment = old_inside_assignment; + } + Expr::InlineAsm(_) => self.call_cb(current.into(), UnsafetyReason::InlineAsm), + // rustc allows union assignment to propagate through field accesses and casts. + Expr::Cast { .. } => self.inside_assignment = inside_assignment, + Expr::Field { .. } => { + self.inside_assignment = inside_assignment; + if !inside_assignment { + if let Some(Either::Left(FieldId { parent: VariantId::UnionId(_), .. })) = + self.infer.field_resolution(current) + { + self.call_cb(current.into(), UnsafetyReason::UnionField); + } + } + } + Expr::Block { statements, .. } | Expr::Async { statements, .. } => { + self.walk_pats_top( + statements.iter().filter_map(|statement| match statement { + &Statement::Let { pat, .. } => Some(pat), + _ => None, + }), + current, + ); + } + Expr::Match { arms, .. } => { + self.walk_pats_top(arms.iter().map(|arm| arm.pat), current); + } + &Expr::Let { pat, .. } => { + self.walk_pats_top(std::iter::once(pat), current); + } + Expr::Closure { args, .. } => { + self.walk_pats_top(args.iter().copied(), current); + } + _ => {} + } + + self.body.walk_child_exprs_without_pats(current, |child| self.walk_expr(child)); + } + + fn mark_unsafe_path(&mut self, node: ExprOrPatId, path: &Path) { + let hygiene = self.body.expr_or_pat_path_hygiene(node); + let value_or_partial = + self.resolver.resolve_path_in_value_ns(self.db.upcast(), path, hygiene); + if let Some(ResolveValueResult::ValueNs(ValueNs::StaticId(id), _)) = value_or_partial { + let static_data = self.db.static_data(id); + if static_data.mutable { + self.call_cb(node, UnsafetyReason::MutableStatic); + } else if static_data.is_extern && !static_data.has_safe_kw { + self.call_cb(node, UnsafetyReason::ExternStatic); + } + } + } } diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index 8297acde85..9ca021027d 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -5,7 +5,9 @@ //! be expressed in terms of hir types themselves. pub use hir_ty::diagnostics::{CaseType, IncorrectCase}; use hir_ty::{ - db::HirDatabase, diagnostics::BodyValidationDiagnostic, CastError, InferenceDiagnostic, + db::HirDatabase, + diagnostics::{BodyValidationDiagnostic, UnsafetyReason}, + CastError, InferenceDiagnostic, }; use cfg::{CfgExpr, CfgOptions}; @@ -258,9 +260,10 @@ pub struct PrivateField { #[derive(Debug)] pub struct MissingUnsafe { - pub expr: InFile>>, + pub node: InFile>>, /// If true, the diagnostics is an `unsafe_op_in_unsafe_fn` lint instead of a hard error. pub only_lint: bool, + pub reason: UnsafetyReason, } #[derive(Debug)] diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index c9498b3aea..0b2ba56b1f 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -147,6 +147,7 @@ pub use { }, hir_ty::{ consteval::ConstEvalError, + diagnostics::UnsafetyReason, display::{ClosureStyle, HirDisplay, HirDisplayError, HirWrite}, dyn_compatibility::{DynCompatibilityViolation, MethodViolationCode}, layout::LayoutError, @@ -1890,10 +1891,10 @@ impl DefWithBody { ); } - let (unafe_exprs, only_lint) = hir_ty::diagnostics::missing_unsafe(db, self.into()); - for expr in unafe_exprs { - match source_map.expr_or_pat_syntax(expr) { - Ok(expr) => acc.push(MissingUnsafe { expr, only_lint }.into()), + let (unsafe_exprs, only_lint) = hir_ty::diagnostics::missing_unsafe(db, self.into()); + for (node, reason) in unsafe_exprs { + match source_map.expr_or_pat_syntax(node) { + Ok(node) => acc.push(MissingUnsafe { node, only_lint, reason }.into()), Err(SyntheticSyntax) => { // FIXME: Here and elsewhere in this file, the `expr` was // desugared, report or assert that this doesn't happen. diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs index c16454cff6..56ed81f053 100644 --- a/crates/hir/src/source_analyzer.rs +++ b/crates/hir/src/source_analyzer.rs @@ -36,7 +36,7 @@ use hir_expand::{ use hir_ty::{ diagnostics::{ record_literal_missing_fields, record_pattern_missing_fields, unsafe_expressions, - UnsafeExpr, + InsideUnsafeBlock, }, lang_items::lang_items_for_bin_op, method_resolution, Adjustment, InferenceResult, Interner, Substitution, Ty, TyExt, TyKind, @@ -939,8 +939,8 @@ impl SourceAnalyzer { *def, body, expr_id, - &mut |UnsafeExpr { inside_unsafe_block, .. }| { - is_unsafe |= !inside_unsafe_block + &mut |_, inside_unsafe_block, _| { + is_unsafe |= inside_unsafe_block == InsideUnsafeBlock::No }, ) }; diff --git a/crates/ide-diagnostics/src/handlers/missing_unsafe.rs b/crates/ide-diagnostics/src/handlers/missing_unsafe.rs index a630d3c7c3..2bfdda3565 100644 --- a/crates/ide-diagnostics/src/handlers/missing_unsafe.rs +++ b/crates/ide-diagnostics/src/handlers/missing_unsafe.rs @@ -1,5 +1,5 @@ use hir::db::ExpandDatabase; -use hir::HirFileIdExt; +use hir::{HirFileIdExt, UnsafetyReason}; use ide_db::text_edit::TextEdit; use ide_db::{assists::Assist, source_change::SourceChange}; use syntax::{ast, SyntaxNode}; @@ -16,23 +16,35 @@ pub(crate) fn missing_unsafe(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsaf } else { DiagnosticCode::RustcHardError("E0133") }; + let operation = display_unsafety_reason(d.reason); Diagnostic::new_with_syntax_node_ptr( ctx, code, - "this operation is unsafe and requires an unsafe function or block", - d.expr.map(|it| it.into()), + format!("{operation} is unsafe and requires an unsafe function or block"), + d.node.map(|it| it.into()), ) .with_fixes(fixes(ctx, d)) } +fn display_unsafety_reason(reason: UnsafetyReason) -> &'static str { + match reason { + UnsafetyReason::UnionField => "access to union field", + UnsafetyReason::UnsafeFnCall => "call to unsafe function", + UnsafetyReason::InlineAsm => "use of inline assembly", + UnsafetyReason::RawPtrDeref => "dereference of raw pointer", + UnsafetyReason::MutableStatic => "use of mutable static", + UnsafetyReason::ExternStatic => "use of extern static", + } +} + fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsafe) -> Option> { // The fixit will not work correctly for macro expansions, so we don't offer it in that case. - if d.expr.file_id.is_macro() { + if d.node.file_id.is_macro() { return None; } - let root = ctx.sema.db.parse_or_expand(d.expr.file_id); - let node = d.expr.value.to_node(&root); + let root = ctx.sema.db.parse_or_expand(d.node.file_id); + let node = d.node.value.to_node(&root); let expr = node.syntax().ancestors().find_map(ast::Expr::cast)?; let node_to_add_unsafe_block = pick_best_node_to_add_unsafe_block(&expr)?; @@ -40,7 +52,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsafe) -> Option u8 { fn main() { ed2021::safe(); ed2024::not_safe(); - //^^^^^^^^^^^^^^^^^^💡 error: this operation is unsafe and requires an unsafe function or block + //^^^^^^^^^^^^^^^^^^💡 error: call to unsafe function is unsafe and requires an unsafe function or block } "#, ) @@ -591,7 +603,7 @@ unsafe fn foo(p: *mut i32) { #![warn(unsafe_op_in_unsafe_fn)] unsafe fn foo(p: *mut i32) { *p = 123; - //^^💡 warn: this operation is unsafe and requires an unsafe function or block + //^^💡 warn: dereference of raw pointer is unsafe and requires an unsafe function or block } "#, ) @@ -618,17 +630,119 @@ unsafe extern { fn main() { f(); g(); - //^^^💡 error: this operation is unsafe and requires an unsafe function or block + //^^^💡 error: call to unsafe function is unsafe and requires an unsafe function or block h(); - //^^^💡 error: this operation is unsafe and requires an unsafe function or block + //^^^💡 error: call to unsafe function is unsafe and requires an unsafe function or block let _ = S1; let _ = S2; - //^^💡 error: this operation is unsafe and requires an unsafe function or block + //^^💡 error: use of extern static is unsafe and requires an unsafe function or block let _ = S3; - //^^💡 error: this operation is unsafe and requires an unsafe function or block + //^^💡 error: use of extern static is unsafe and requires an unsafe function or block } "#, ); } + + #[test] + fn no_unsafe_diagnostic_when_destructuring_union_with_wildcard() { + check_diagnostics( + r#" +union Union { field: i32 } +fn foo(v: &Union) { + let Union { field: _ } = v; + let Union { field: _ | _ } = v; + Union { field: _ } = *v; +} +"#, + ); + } + + #[test] + fn union_destructuring() { + check_diagnostics( + r#" +union Union { field: u8 } +fn foo(v @ Union { field: _field }: &Union) { + // ^^^^^^ error: access to union field is unsafe and requires an unsafe function or block + let Union { mut field } = v; + // ^^^^^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block + let Union { field: 0..=255 } = v; + // ^^^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block + let Union { field: 0 + // ^💡 error: access to union field is unsafe and requires an unsafe function or block + | 1..=255 } = v; + // ^^^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block + Union { field } = *v; + // ^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block + match v { + Union { field: _field } => {} + // ^^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block + } + if let Union { field: _field } = v {} + // ^^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block + (|&Union { field }| { _ = field; })(v); + // ^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block +} +"#, + ); + } + + #[test] + fn union_field_access() { + check_diagnostics( + r#" +union Union { field: u8 } +fn foo(v: &Union) { + v.field; + // ^^^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block +} +"#, + ); + } + + #[test] + fn inline_asm() { + check_diagnostics( + r#" +//- minicore: asm +fn foo() { + core::arch::asm!(""); + // ^^^^ error: use of inline assembly is unsafe and requires an unsafe function or block +} +"#, + ); + } + + #[test] + fn unsafe_op_in_unsafe_fn_dismissed_in_signature() { + check_diagnostics( + r#" +#![warn(unsafe_op_in_unsafe_fn)] +union Union { field: u32 } +unsafe fn foo(Union { field: _field }: Union) {} + "#, + ) + } + + #[test] + fn union_assignment_allowed() { + check_diagnostics( + r#" +union Union { field: u32 } +fn foo(mut v: Union) { + v.field = 123; + (v.field,) = (123,); + *&mut v.field = 123; + // ^^^^^^^💡 error: access to union field is unsafe and requires an unsafe function or block +} +struct Struct { field: u32 } +union Union2 { field: Struct } +fn bar(mut v: Union2) { + v.field.field = 123; +} + + "#, + ) + } } From 7149c4dab91476e295695ad4293d4879f4b3f9dd Mon Sep 17 00:00:00 2001 From: Tarek Date: Wed, 27 Nov 2024 12:08:21 +0200 Subject: [PATCH 24/95] =?UTF-8?q?feat:=20migrate=C2=A0`sort=5Fitems`=C2=A0?= =?UTF-8?q?assist=20to=20use=C2=A0`SyntaxFactory`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tarek --- crates/ide-assists/src/handlers/sort_items.rs | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/crates/ide-assists/src/handlers/sort_items.rs b/crates/ide-assists/src/handlers/sort_items.rs index 64e30b1834..6a37a72996 100644 --- a/crates/ide-assists/src/handlers/sort_items.rs +++ b/crates/ide-assists/src/handlers/sort_items.rs @@ -4,7 +4,7 @@ use itertools::Itertools; use syntax::{ ast::{self, HasName}, - ted, AstNode, TextRange, + AstNode, SyntaxNode, }; use crate::{utils::get_methods, AssistContext, AssistId, AssistKind, Assists}; @@ -114,7 +114,7 @@ trait AddRewrite { label: &str, old: Vec, new: Vec, - target: TextRange, + target: SyntaxNode, ) -> Option<()>; } @@ -124,15 +124,24 @@ impl AddRewrite for Assists { label: &str, old: Vec, new: Vec, - target: TextRange, + target: SyntaxNode, ) -> Option<()> { - self.add(AssistId("sort_items", AssistKind::RefactorRewrite), label, target, |builder| { - let mutable: Vec = old.into_iter().map(|it| builder.make_mut(it)).collect(); - mutable - .into_iter() - .zip(new) - .for_each(|(old, new)| ted::replace(old.syntax(), new.clone_for_update().syntax())); - }) + let node = old.first().unwrap().syntax().parent().unwrap(); + self.add( + AssistId("sort_items", AssistKind::RefactorRewrite), + label, + target.text_range(), + |builder| { + let mut editor = builder.make_editor(&node); + + old.into_iter().zip(new).for_each(|(old, new)| { + // FIXME: remove `clone_for_update` when `SyntaxEditor` handles it for us + editor.replace(old.syntax(), new.clone_for_update().syntax()) + }); + + builder.add_file_edits(builder.file_id, editor) + }, + ) } } @@ -167,7 +176,7 @@ fn add_sort_methods_assist( return None; } - acc.add_rewrite("Sort methods alphabetically", methods, sorted, item_list.syntax().text_range()) + acc.add_rewrite("Sort methods alphabetically", methods, sorted, item_list.syntax().clone()) } fn add_sort_fields_assist( @@ -186,7 +195,7 @@ fn add_sort_fields_assist( "Sort fields alphabetically", fields, sorted, - record_field_list.syntax().text_range(), + record_field_list.syntax().clone(), ) } @@ -199,12 +208,7 @@ fn add_sort_variants_assist(acc: &mut Assists, variant_list: ast::VariantList) - return None; } - acc.add_rewrite( - "Sort variants alphabetically", - variants, - sorted, - variant_list.syntax().text_range(), - ) + acc.add_rewrite("Sort variants alphabetically", variants, sorted, variant_list.syntax().clone()) } fn sort_by_name(initial: &[T]) -> Vec { From c43d565deff0ba3c5400d987007c4fb75b961b7b Mon Sep 17 00:00:00 2001 From: Tarek Date: Wed, 4 Dec 2024 12:42:07 +0200 Subject: [PATCH 25/95] refactor: change target parameter to a reference in add_rewrite method Signed-off-by: Tarek --- crates/ide-assists/src/handlers/sort_items.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/crates/ide-assists/src/handlers/sort_items.rs b/crates/ide-assists/src/handlers/sort_items.rs index 6a37a72996..7307325e49 100644 --- a/crates/ide-assists/src/handlers/sort_items.rs +++ b/crates/ide-assists/src/handlers/sort_items.rs @@ -114,7 +114,7 @@ trait AddRewrite { label: &str, old: Vec, new: Vec, - target: SyntaxNode, + target: &SyntaxNode, ) -> Option<()>; } @@ -124,15 +124,14 @@ impl AddRewrite for Assists { label: &str, old: Vec, new: Vec, - target: SyntaxNode, + target: &SyntaxNode, ) -> Option<()> { - let node = old.first().unwrap().syntax().parent().unwrap(); self.add( AssistId("sort_items", AssistKind::RefactorRewrite), label, target.text_range(), |builder| { - let mut editor = builder.make_editor(&node); + let mut editor = builder.make_editor(target); old.into_iter().zip(new).for_each(|(old, new)| { // FIXME: remove `clone_for_update` when `SyntaxEditor` handles it for us @@ -176,7 +175,7 @@ fn add_sort_methods_assist( return None; } - acc.add_rewrite("Sort methods alphabetically", methods, sorted, item_list.syntax().clone()) + acc.add_rewrite("Sort methods alphabetically", methods, sorted, item_list.syntax()) } fn add_sort_fields_assist( @@ -191,12 +190,7 @@ fn add_sort_fields_assist( return None; } - acc.add_rewrite( - "Sort fields alphabetically", - fields, - sorted, - record_field_list.syntax().clone(), - ) + acc.add_rewrite("Sort fields alphabetically", fields, sorted, record_field_list.syntax()) } fn add_sort_variants_assist(acc: &mut Assists, variant_list: ast::VariantList) -> Option<()> { @@ -208,7 +202,7 @@ fn add_sort_variants_assist(acc: &mut Assists, variant_list: ast::VariantList) - return None; } - acc.add_rewrite("Sort variants alphabetically", variants, sorted, variant_list.syntax().clone()) + acc.add_rewrite("Sort variants alphabetically", variants, sorted, variant_list.syntax()) } fn sort_by_name(initial: &[T]) -> Vec { From 65c0b297204fcce1096ed6d6fe3f0f224563dbb8 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 3 Dec 2024 18:29:00 +0100 Subject: [PATCH 26/95] Fix parsing of parenthesized type args and RTN --- crates/hir-def/src/path/lower.rs | 8 +- crates/ide-db/src/path_transform.rs | 3 +- crates/ide-ssr/src/matching.rs | 4 +- crates/ide/src/inlay_hints/lifetime.rs | 94 ++-- crates/parser/src/grammar/expressions.rs | 2 +- crates/parser/src/grammar/generic_args.rs | 65 +-- crates/parser/src/grammar/params.rs | 18 +- crates/parser/src/grammar/paths.rs | 104 ++-- crates/parser/src/grammar/types.rs | 4 +- crates/parser/src/syntax_kind/generated.rs | 1 + crates/parser/test_data/generated/runner.rs | 32 +- .../inline/err/generic_arg_list_recover.rast | 33 ++ .../inline/err/generic_arg_list_recover.rs | 1 + .../err/generic_arg_list_recover_expr.rast | 79 +++ .../err/generic_arg_list_recover_expr.rs | 2 + .../parser/inline/ok/assoc_type_bound.rast | 39 ++ .../parser/inline/ok/assoc_type_bound.rs | 1 + .../inline/ok/associated_type_bounds.rast | 111 ----- .../inline/ok/associated_type_bounds.rs | 1 - .../parser/inline/ok/async_trait_bound.rast | 4 +- ..._dyn_types_with_paren_as_generic_args.rast | 175 ------- ...re_dyn_types_with_paren_as_generic_args.rs | 4 - .../parser/inline/ok/generic_arg_bounds.rast | 461 ++++++++++++++++++ .../parser/inline/ok/generic_arg_bounds.rs | 4 + .../inline/ok/param_list_opt_patterns.rast | 48 -- .../inline/ok/param_list_opt_patterns.rs | 1 - .../parser/inline/ok/path_fn_trait_args.rast | 128 ++++- .../parser/inline/ok/path_fn_trait_args.rs | 3 + .../return_type_syntax_assoc_type_bound.rast | 49 -- .../ok/return_type_syntax_assoc_type_bound.rs | 1 - .../inline/ok/return_type_syntax_in_path.rast | 43 ++ .../inline/ok/return_type_syntax_in_path.rs | 2 + .../inline/ok/typepathfn_with_coloncolon.rast | 10 +- .../ok/value_parameters_no_patterns.rast | 60 --- .../inline/ok/value_parameters_no_patterns.rs | 1 - .../parser/inline/ok/where_pred_for.rast | 4 +- .../test_data/parser/ok/0045_block_attrs.rast | 2 +- .../parser/ok/0051_parameter_attrs.rast | 57 --- .../parser/ok/0051_parameter_attrs.rs | 2 - .../parser/ok/0054_qual_path_in_type_arg.rast | 8 +- .../ok/0065_plus_after_fn_trait_bound.rast | 2 +- .../parser/ok/0067_where_for_pred.rast | 8 +- crates/syntax/rust.ungram | 5 +- crates/syntax/src/ast/generated/nodes.rs | 38 +- 44 files changed, 1022 insertions(+), 700 deletions(-) create mode 100644 crates/parser/test_data/parser/inline/err/generic_arg_list_recover_expr.rast create mode 100644 crates/parser/test_data/parser/inline/err/generic_arg_list_recover_expr.rs delete mode 100644 crates/parser/test_data/parser/inline/ok/associated_type_bounds.rast delete mode 100644 crates/parser/test_data/parser/inline/ok/associated_type_bounds.rs delete mode 100644 crates/parser/test_data/parser/inline/ok/bare_dyn_types_with_paren_as_generic_args.rast delete mode 100644 crates/parser/test_data/parser/inline/ok/bare_dyn_types_with_paren_as_generic_args.rs create mode 100644 crates/parser/test_data/parser/inline/ok/generic_arg_bounds.rast create mode 100644 crates/parser/test_data/parser/inline/ok/generic_arg_bounds.rs delete mode 100644 crates/parser/test_data/parser/inline/ok/param_list_opt_patterns.rast delete mode 100644 crates/parser/test_data/parser/inline/ok/param_list_opt_patterns.rs delete mode 100644 crates/parser/test_data/parser/inline/ok/return_type_syntax_assoc_type_bound.rast delete mode 100644 crates/parser/test_data/parser/inline/ok/return_type_syntax_assoc_type_bound.rs delete mode 100644 crates/parser/test_data/parser/inline/ok/value_parameters_no_patterns.rast delete mode 100644 crates/parser/test_data/parser/inline/ok/value_parameters_no_patterns.rs diff --git a/crates/hir-def/src/path/lower.rs b/crates/hir-def/src/path/lower.rs index 553e615b94..37c94a5f11 100644 --- a/crates/hir-def/src/path/lower.rs +++ b/crates/hir-def/src/path/lower.rs @@ -48,7 +48,7 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option< .or_else(|| { lower_generic_args_from_fn_path( ctx, - segment.param_list(), + segment.parenthesized_arg_list(), segment.ret_type(), ) }); @@ -247,12 +247,12 @@ pub(super) fn lower_generic_args( /// -> Z` (which desugars to `Fn<(X, Y), Output=Z>`). fn lower_generic_args_from_fn_path( ctx: &mut LowerCtx<'_>, - params: Option, + args: Option, ret_type: Option, ) -> Option { - let params = params?; + let params = args?; let mut param_types = Vec::new(); - for param in params.params() { + for param in params.type_args() { let type_ref = TypeRef::from_ast_opt(ctx, param.ty()); param_types.push(type_ref); } diff --git a/crates/ide-db/src/path_transform.rs b/crates/ide-db/src/path_transform.rs index 49b3ca290f..a508f2fedd 100644 --- a/crates/ide-db/src/path_transform.rs +++ b/crates/ide-db/src/path_transform.rs @@ -286,7 +286,8 @@ impl Ctx<'_> { return None; } if path.segment().map_or(false, |s| { - s.param_list().is_some() || (s.self_token().is_some() && path.parent_path().is_none()) + s.parenthesized_arg_list().is_some() + || (s.self_token().is_some() && path.parent_path().is_none()) }) { // don't try to qualify `Fn(Foo) -> Bar` paths, they are in prelude anyway // don't try to qualify sole `self` either, they are usually locals, but are returned as modules due to namespace clashing diff --git a/crates/ide-ssr/src/matching.rs b/crates/ide-ssr/src/matching.rs index 6569f0f555..4edc3633fb 100644 --- a/crates/ide-ssr/src/matching.rs +++ b/crates/ide-ssr/src/matching.rs @@ -359,8 +359,8 @@ impl<'db, 'sema> Matcher<'db, 'sema> { )?; self.attempt_match_opt( phase, - pattern_segment.param_list(), - code_segment.param_list(), + pattern_segment.parenthesized_arg_list(), + code_segment.parenthesized_arg_list(), )?; } if matches!(phase, Phase::Second(_)) { diff --git a/crates/ide/src/inlay_hints/lifetime.rs b/crates/ide/src/inlay_hints/lifetime.rs index 2163c959b1..1fdd698991 100644 --- a/crates/ide/src/inlay_hints/lifetime.rs +++ b/crates/ide/src/inlay_hints/lifetime.rs @@ -41,7 +41,15 @@ pub(super) fn fn_hints( fd, config, file_id, - param_list, + param_list.params().filter_map(|it| { + Some(( + it.pat().and_then(|it| match it { + ast::Pat::IdentPat(p) => p.name(), + _ => None, + }), + it.ty()?, + )) + }), generic_param_list, ret_type, self_param, @@ -90,7 +98,15 @@ pub(super) fn fn_ptr_hints( fd, config, file_id, - param_list, + param_list.params().filter_map(|it| { + Some(( + it.pat().and_then(|it| match it { + ast::Pat::IdentPat(p) => p.name(), + _ => None, + }), + it.ty()?, + )) + }), generic_param_list, ret_type, None, @@ -148,7 +164,7 @@ pub(super) fn fn_path_hints( fd, config, file_id, - param_list, + param_list.type_args().filter_map(|it| Some((None, it.ty()?))), generic_param_list, ret_type, None, @@ -177,8 +193,8 @@ pub(super) fn fn_path_hints( ) } -fn path_as_fn(path: &ast::Path) -> Option<(ast::ParamList, Option)> { - path.segment().and_then(|it| it.param_list().zip(Some(it.ret_type()))) +fn path_as_fn(path: &ast::Path) -> Option<(ast::ParenthesizedArgList, Option)> { + path.segment().and_then(|it| it.parenthesized_arg_list().zip(Some(it.ret_type()))) } fn hints_( @@ -187,7 +203,7 @@ fn hints_( FamousDefs(_, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, _file_id: EditionedFileId, - param_list: ast::ParamList, + params: impl Iterator, ast::Type)>, generic_param_list: Option, ret_type: Option, self_param: Option, @@ -217,45 +233,34 @@ fn hints_( let is_elided = is_elided(&lifetime); acc.push((None, self_param.amp_token(), lifetime, is_elided)); } - param_list - .params() - .filter_map(|it| { - Some(( - it.pat().and_then(|it| match it { - ast::Pat::IdentPat(p) => p.name(), - _ => None, - }), - it.ty()?, - )) - }) - .for_each(|(name, ty)| { - // FIXME: check path types - walk_ty(&ty, &mut |ty| match ty { - ast::Type::RefType(r) => { - let lifetime = r.lifetime(); - let is_elided = is_elided(&lifetime); - acc.push((name.clone(), r.amp_token(), lifetime, is_elided)); - false - } - ast::Type::FnPtrType(_) => { + params.for_each(|(name, ty)| { + // FIXME: check path types + walk_ty(&ty, &mut |ty| match ty { + ast::Type::RefType(r) => { + let lifetime = r.lifetime(); + let is_elided = is_elided(&lifetime); + acc.push((name.clone(), r.amp_token(), lifetime, is_elided)); + false + } + ast::Type::FnPtrType(_) => { + is_trivial = false; + true + } + ast::Type::PathType(t) => { + if t.path() + .and_then(|it| it.segment()) + .and_then(|it| it.parenthesized_arg_list()) + .is_some() + { is_trivial = false; true + } else { + false } - ast::Type::PathType(t) => { - if t.path() - .and_then(|it| it.segment()) - .and_then(|it| it.param_list()) - .is_some() - { - is_trivial = false; - true - } else { - false - } - } - _ => false, - }) - }); + } + _ => false, + }) + }); acc }; @@ -339,7 +344,10 @@ fn hints_( true } ast::Type::PathType(t) => { - if t.path().and_then(|it| it.segment()).and_then(|it| it.param_list()).is_some() + if t.path() + .and_then(|it| it.segment()) + .and_then(|it| it.parenthesized_arg_list()) + .is_some() { is_trivial = false; true diff --git a/crates/parser/src/grammar/expressions.rs b/crates/parser/src/grammar/expressions.rs index e565874a42..349bce0939 100644 --- a/crates/parser/src/grammar/expressions.rs +++ b/crates/parser/src/grammar/expressions.rs @@ -525,7 +525,7 @@ fn method_call_expr( p.bump(T![.]); } name_ref(p); - generic_args::opt_generic_arg_list(p, true); + generic_args::opt_generic_arg_list_expr(p); if p.at(T!['(']) { arg_list(p); } else { diff --git a/crates/parser/src/grammar/generic_args.rs b/crates/parser/src/grammar/generic_args.rs index 77379ef147..737010985b 100644 --- a/crates/parser/src/grammar/generic_args.rs +++ b/crates/parser/src/grammar/generic_args.rs @@ -1,14 +1,13 @@ use super::*; -// test_err generic_arg_list_recover -// type T = T<0, ,T>; -pub(super) fn opt_generic_arg_list(p: &mut Parser<'_>, colon_colon_required: bool) { +// test_err generic_arg_list_recover_expr +// const _: () = T::<0, ,T>; +// const _: () = T::<0, ,T>(); +pub(super) fn opt_generic_arg_list_expr(p: &mut Parser<'_>) { let m; if p.at(T![::]) && p.nth(2) == T![<] { m = p.start(); p.bump(T![::]); - } else if !colon_colon_required && p.at(T![<]) && p.nth(1) != T![=] { - m = p.start(); } else { return; } @@ -25,7 +24,7 @@ pub(super) fn opt_generic_arg_list(p: &mut Parser<'_>, colon_colon_required: boo m.complete(p, GENERIC_ARG_LIST); } -const GENERIC_ARG_FIRST: TokenSet = TokenSet::new(&[ +pub(crate) const GENERIC_ARG_FIRST: TokenSet = TokenSet::new(&[ LIFETIME_IDENT, IDENT, T!['{'], @@ -47,20 +46,23 @@ const GENERIC_ARG_RECOVERY_SET: TokenSet = TokenSet::new(&[T![>], T![,]]); // test generic_arg // type T = S; -fn generic_arg(p: &mut Parser<'_>) -> bool { +pub(crate) fn generic_arg(p: &mut Parser<'_>) -> bool { match p.current() { LIFETIME_IDENT if !p.nth_at(1, T![+]) => lifetime_arg(p), T!['{'] | T![true] | T![false] | T![-] => const_arg(p), k if k.is_literal() => const_arg(p), - // test associated_type_bounds - // fn print_all, Item: Display, Item<'a> = Item>>(printables: T) {} + // test generic_arg_bounds + // type Plain = Foo; + // type GenericArgs = Foo, Item::, Item: Bound, Item::: Bound, Item = Item, Item:: = Item>; + // type ParenthesizedArgs = Foo; + // type RTN = Foo; // test macro_inside_generic_arg // type A = Foo; - IDENT if [T![<], T![=], T![:]].contains(&p.nth(1)) && !p.nth_at(1, T![::]) => { + IDENT => { let m = p.start(); name_ref(p); - opt_generic_arg_list(p, false); + paths::opt_path_type_args(p); match p.current() { T![=] => { p.bump_any(); @@ -88,45 +90,26 @@ fn generic_arg(p: &mut Parser<'_>) -> bool { } // test assoc_type_bound // type T = StreamingIterator: Clone>; + // type T = StreamingIterator; T![:] if !p.at(T![::]) => { generic_params::bounds(p); m.complete(p, ASSOC_TYPE_ARG); } + // Turned out to be just a normal path type (mirror `path_or_macro_type`) _ => { let m = m.complete(p, PATH_SEGMENT).precede(p).complete(p, PATH); let m = paths::type_path_for_qualifier(p, m); - m.precede(p).complete(p, PATH_TYPE).precede(p).complete(p, TYPE_ARG); + let m = if p.at(T![!]) && !p.at(T![!=]) { + let m = m.precede(p); + items::macro_call_after_excl(p); + m.complete(p, MACRO_CALL).precede(p).complete(p, MACRO_TYPE) + } else { + m.precede(p).complete(p, PATH_TYPE) + }; + types::opt_type_bounds_as_dyn_trait_type(p, m).precede(p).complete(p, TYPE_ARG); } } } - IDENT if p.nth_at(1, T!['(']) => { - let m = p.start(); - name_ref(p); - if p.nth_at(1, T![..]) { - let rtn = p.start(); - p.bump(T!['(']); - p.bump(T![..]); - p.expect(T![')']); - rtn.complete(p, RETURN_TYPE_SYNTAX); - // test return_type_syntax_assoc_type_bound - // fn foo>() {} - generic_params::bounds(p); - m.complete(p, ASSOC_TYPE_ARG); - } else { - params::param_list_fn_trait(p); - // test bare_dyn_types_with_paren_as_generic_args - // type A = S; - // type A = S; - // type B = S i32>; - // type C = S i32 + Send>; - opt_ret_type(p); - let m = m.complete(p, PATH_SEGMENT).precede(p).complete(p, PATH); - let m = paths::type_path_for_qualifier(p, m); - let m = m.precede(p).complete(p, PATH_TYPE); - let m = types::opt_type_bounds_as_dyn_trait_type(p, m); - m.precede(p).complete(p, TYPE_ARG); - } - } _ if p.at_ts(types::TYPE_FIRST) => type_arg(p), _ => return false, } @@ -190,7 +173,7 @@ pub(super) fn const_arg(p: &mut Parser<'_>) { m.complete(p, CONST_ARG); } -fn type_arg(p: &mut Parser<'_>) { +pub(crate) fn type_arg(p: &mut Parser<'_>) { let m = p.start(); types::type_(p); m.complete(p, TYPE_ARG); diff --git a/crates/parser/src/grammar/params.rs b/crates/parser/src/grammar/params.rs index c535267c16..51ffcd0706 100644 --- a/crates/parser/src/grammar/params.rs +++ b/crates/parser/src/grammar/params.rs @@ -14,12 +14,6 @@ pub(super) fn param_list_fn_def(p: &mut Parser<'_>) { list_(p, Flavor::FnDef); } -// test param_list_opt_patterns -// fn foo)>(){} -pub(super) fn param_list_fn_trait(p: &mut Parser<'_>) { - list_(p, Flavor::FnTrait); -} - pub(super) fn param_list_fn_ptr(p: &mut Parser<'_>) { list_(p, Flavor::FnPointer); } @@ -28,10 +22,9 @@ pub(super) fn param_list_closure(p: &mut Parser<'_>) { list_(p, Flavor::Closure); } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Flavor { - FnDef, // Includes trait fn params; omitted param idents are not supported - FnTrait, // Params for `Fn(...)`/`FnMut(...)`/`FnOnce(...)` annotations + FnDef, // Includes trait fn params; omitted param idents are not supported FnPointer, Closure, } @@ -41,7 +34,7 @@ fn list_(p: &mut Parser<'_>, flavor: Flavor) { let (bra, ket) = match flavor { Closure => (T![|], T![|]), - FnDef | FnTrait | FnPointer => (T!['('], T![')']), + FnDef | FnPointer => (T!['('], T![')']), }; let list_marker = p.start(); @@ -119,11 +112,6 @@ fn param(p: &mut Parser<'_>, m: Marker, flavor: Flavor) { } } } - // test value_parameters_no_patterns - // type F = Box; - Flavor::FnTrait => { - types::type_(p); - } // test fn_pointer_param_ident_path // type Foo = fn(Bar::Baz); // type Qux = fn(baz: Bar::Baz); diff --git a/crates/parser/src/grammar/paths.rs b/crates/parser/src/grammar/paths.rs index 15b3529642..b3652f7cd3 100644 --- a/crates/parser/src/grammar/paths.rs +++ b/crates/parser/src/grammar/paths.rs @@ -110,7 +110,7 @@ fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) -> Option { name_ref(p); - opt_path_type_args(p, mode); + opt_path_args(p, mode); } // test crate_path // use crate::foo; @@ -142,38 +142,74 @@ fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) -> Option, mode: Mode) { - match mode { - Mode::Use | Mode::Attr | Mode::Vis => {} - Mode::Type => { - // test typepathfn_with_coloncolon - // type F = Start::(Middle) -> (Middle)::End; - // type GenericArg = S; - if p.at(T![::]) && p.nth_at(2, T!['(']) { - p.bump(T![::]); - } - if p.at(T!['(']) { - if p.nth_at(1, T![..]) { - // test return_type_syntax_in_path - // fn foo() - // where - // T::method(..): Send, - // {} - let rtn = p.start(); - p.bump(T!['(']); - p.bump(T![..]); - p.expect(T![')']); - rtn.complete(p, RETURN_TYPE_SYNTAX); - } else { - // test path_fn_trait_args - // type F = Box ()>; - params::param_list_fn_trait(p); - opt_ret_type(p); - } - } else { - generic_args::opt_generic_arg_list(p, false); - } - } - Mode::Expr => generic_args::opt_generic_arg_list(p, true), +pub(crate) fn opt_path_type_args(p: &mut Parser<'_>) { + // test typepathfn_with_coloncolon + // type F = Start::(Middle) -> (Middle)::End; + // type GenericArg = S; + let m; + if p.at(T![::]) && matches!(p.nth(2), T![<] | T!['(']) { + m = p.start(); + p.bump(T![::]); + } else if (p.current() == T![<] && p.nth(1) != T![=]) || p.current() == T!['('] { + m = p.start(); + } else { + return; + } + let current = p.current(); + if current == T![<] { + // test_err generic_arg_list_recover + // type T = T<0, ,T>; + // type T = T::<0, ,T>; + delimited( + p, + T![<], + T![>], + T![,], + || "expected generic argument".into(), + generic_args::GENERIC_ARG_FIRST, + generic_args::generic_arg, + ); + m.complete(p, GENERIC_ARG_LIST); + } else if p.nth_at(1, T![..]) { + // test return_type_syntax_in_path + // fn foo() + // where + // T::method(..): Send, + // method(..): Send, + // method::(..): Send, + // {} + p.bump(T!['(']); + p.bump(T![..]); + p.expect(T![')']); + m.complete(p, RETURN_TYPE_SYNTAX); + } else { + // test path_fn_trait_args + // type F = Box ()>; + // type F = Box<::Fn(i32) -> ()>; + // type F = Box ()>; + // type F = Box<::Fn::(i32) -> ()>; + delimited( + p, + T!['('], + T![')'], + T![,], + || "expected type".into(), + types::TYPE_FIRST, + |p| { + let progress = types::TYPE_FIRST.contains(p.current()); + generic_args::type_arg(p); + progress + }, + ); + m.complete(p, PARENTHESIZED_ARG_LIST); + opt_ret_type(p); + } +} + +fn opt_path_args(p: &mut Parser<'_>, mode: Mode) { + match mode { + Mode::Use | Mode::Attr | Mode::Vis => {} + Mode::Type => opt_path_type_args(p), + Mode::Expr => generic_args::opt_generic_arg_list_expr(p), } } diff --git a/crates/parser/src/grammar/types.rs b/crates/parser/src/grammar/types.rs index f4e57d3d6f..35198ccbe3 100644 --- a/crates/parser/src/grammar/types.rs +++ b/crates/parser/src/grammar/types.rs @@ -50,7 +50,7 @@ fn type_with_bounds_cond(p: &mut Parser<'_>, allow_bounds: bool) { // Some path types are not allowed to have bounds (no plus) T![<] => path_type_bounds(p, allow_bounds), T![ident] if !p.edition().at_least_2018() && is_dyn_weak(p) => dyn_trait_type_weak(p), - _ if paths::is_path_start(p) => path_or_macro_type_(p, allow_bounds), + _ if paths::is_path_start(p) => path_or_macro_type(p, allow_bounds), LIFETIME_IDENT if p.nth_at(1, T![+]) => bare_dyn_trait_type(p), _ => { p.err_recover("expected type", TYPE_RECOVERY_SET); @@ -337,7 +337,7 @@ pub(super) fn path_type(p: &mut Parser<'_>) { // test macro_call_type // type A = foo!(); // type B = crate::foo!(); -fn path_or_macro_type_(p: &mut Parser<'_>, allow_bounds: bool) { +fn path_or_macro_type(p: &mut Parser<'_>, allow_bounds: bool) { assert!(paths::is_path_start(p)); let r = p.start(); let m = p.start(); diff --git a/crates/parser/src/syntax_kind/generated.rs b/crates/parser/src/syntax_kind/generated.rs index 21730244a3..0c9c6ffd71 100644 --- a/crates/parser/src/syntax_kind/generated.rs +++ b/crates/parser/src/syntax_kind/generated.rs @@ -256,6 +256,7 @@ pub enum SyntaxKind { OR_PAT, PARAM, PARAM_LIST, + PARENTHESIZED_ARG_LIST, PAREN_EXPR, PAREN_PAT, PAREN_TYPE, diff --git a/crates/parser/test_data/generated/runner.rs b/crates/parser/test_data/generated/runner.rs index 3db8b51a4b..6d114037bf 100644 --- a/crates/parser/test_data/generated/runner.rs +++ b/crates/parser/test_data/generated/runner.rs @@ -39,10 +39,6 @@ mod ok { #[test] fn assoc_type_eq() { run_and_expect_no_errors("test_data/parser/inline/ok/assoc_type_eq.rs"); } #[test] - fn associated_type_bounds() { - run_and_expect_no_errors("test_data/parser/inline/ok/associated_type_bounds.rs"); - } - #[test] fn async_trait_bound() { run_and_expect_no_errors("test_data/parser/inline/ok/async_trait_bound.rs"); } @@ -59,12 +55,6 @@ mod ok { ); } #[test] - fn bare_dyn_types_with_paren_as_generic_args() { - run_and_expect_no_errors( - "test_data/parser/inline/ok/bare_dyn_types_with_paren_as_generic_args.rs", - ); - } - #[test] fn become_expr() { run_and_expect_no_errors("test_data/parser/inline/ok/become_expr.rs"); } #[test] fn bind_pat() { run_and_expect_no_errors("test_data/parser/inline/ok/bind_pat.rs"); } @@ -281,6 +271,10 @@ mod ok { #[test] fn generic_arg() { run_and_expect_no_errors("test_data/parser/inline/ok/generic_arg.rs"); } #[test] + fn generic_arg_bounds() { + run_and_expect_no_errors("test_data/parser/inline/ok/generic_arg_bounds.rs"); + } + #[test] fn generic_param_attribute() { run_and_expect_no_errors("test_data/parser/inline/ok/generic_param_attribute.rs"); } @@ -423,10 +417,6 @@ mod ok { #[test] fn param_list() { run_and_expect_no_errors("test_data/parser/inline/ok/param_list.rs"); } #[test] - fn param_list_opt_patterns() { - run_and_expect_no_errors("test_data/parser/inline/ok/param_list_opt_patterns.rs"); - } - #[test] fn param_list_vararg() { run_and_expect_no_errors("test_data/parser/inline/ok/param_list_vararg.rs"); } @@ -521,12 +511,6 @@ mod ok { #[test] fn return_expr() { run_and_expect_no_errors("test_data/parser/inline/ok/return_expr.rs"); } #[test] - fn return_type_syntax_assoc_type_bound() { - run_and_expect_no_errors( - "test_data/parser/inline/ok/return_type_syntax_assoc_type_bound.rs", - ); - } - #[test] fn return_type_syntax_in_path() { run_and_expect_no_errors("test_data/parser/inline/ok/return_type_syntax_in_path.rs"); } @@ -685,10 +669,6 @@ mod ok { #[test] fn use_tree_star() { run_and_expect_no_errors("test_data/parser/inline/ok/use_tree_star.rs"); } #[test] - fn value_parameters_no_patterns() { - run_and_expect_no_errors("test_data/parser/inline/ok/value_parameters_no_patterns.rs"); - } - #[test] fn variant_discriminant() { run_and_expect_no_errors("test_data/parser/inline/ok/variant_discriminant.rs"); } @@ -754,6 +734,10 @@ mod err { run_and_expect_errors("test_data/parser/inline/err/generic_arg_list_recover.rs"); } #[test] + fn generic_arg_list_recover_expr() { + run_and_expect_errors("test_data/parser/inline/err/generic_arg_list_recover_expr.rs"); + } + #[test] fn generic_param_list_recover() { run_and_expect_errors("test_data/parser/inline/err/generic_param_list_recover.rs"); } diff --git a/crates/parser/test_data/parser/inline/err/generic_arg_list_recover.rast b/crates/parser/test_data/parser/inline/err/generic_arg_list_recover.rast index 4cf5a3386b..16183ce9ef 100644 --- a/crates/parser/test_data/parser/inline/err/generic_arg_list_recover.rast +++ b/crates/parser/test_data/parser/inline/err/generic_arg_list_recover.rast @@ -30,4 +30,37 @@ SOURCE_FILE R_ANGLE ">" SEMICOLON ";" WHITESPACE "\n" + TYPE_ALIAS + TYPE_KW "type" + WHITESPACE " " + NAME + IDENT "T" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + GENERIC_ARG_LIST + COLON2 "::" + L_ANGLE "<" + CONST_ARG + LITERAL + INT_NUMBER "0" + COMMA "," + WHITESPACE " " + ERROR + COMMA "," + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_ANGLE ">" + SEMICOLON ";" + WHITESPACE "\n" error 14: expected generic argument +error 35: expected generic argument diff --git a/crates/parser/test_data/parser/inline/err/generic_arg_list_recover.rs b/crates/parser/test_data/parser/inline/err/generic_arg_list_recover.rs index 7d849aa1be..aa65a0673a 100644 --- a/crates/parser/test_data/parser/inline/err/generic_arg_list_recover.rs +++ b/crates/parser/test_data/parser/inline/err/generic_arg_list_recover.rs @@ -1 +1,2 @@ type T = T<0, ,T>; +type T = T::<0, ,T>; diff --git a/crates/parser/test_data/parser/inline/err/generic_arg_list_recover_expr.rast b/crates/parser/test_data/parser/inline/err/generic_arg_list_recover_expr.rast new file mode 100644 index 0000000000..de403d3049 --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/generic_arg_list_recover_expr.rast @@ -0,0 +1,79 @@ +SOURCE_FILE + CONST + CONST_KW "const" + WHITESPACE " " + UNDERSCORE "_" + COLON ":" + WHITESPACE " " + TUPLE_TYPE + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + GENERIC_ARG_LIST + COLON2 "::" + L_ANGLE "<" + CONST_ARG + LITERAL + INT_NUMBER "0" + COMMA "," + WHITESPACE " " + ERROR + COMMA "," + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_ANGLE ">" + SEMICOLON ";" + WHITESPACE "\n" + CONST + CONST_KW "const" + WHITESPACE " " + UNDERSCORE "_" + COLON ":" + WHITESPACE " " + TUPLE_TYPE + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + EQ "=" + WHITESPACE " " + CALL_EXPR + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + GENERIC_ARG_LIST + COLON2 "::" + L_ANGLE "<" + CONST_ARG + LITERAL + INT_NUMBER "0" + COMMA "," + WHITESPACE " " + ERROR + COMMA "," + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_ANGLE ">" + ARG_LIST + L_PAREN "(" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" +error 21: expected generic argument +error 47: expected generic argument diff --git a/crates/parser/test_data/parser/inline/err/generic_arg_list_recover_expr.rs b/crates/parser/test_data/parser/inline/err/generic_arg_list_recover_expr.rs new file mode 100644 index 0000000000..74cc63ac4a --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/generic_arg_list_recover_expr.rs @@ -0,0 +1,2 @@ +const _: () = T::<0, ,T>; +const _: () = T::<0, ,T>(); diff --git a/crates/parser/test_data/parser/inline/ok/assoc_type_bound.rast b/crates/parser/test_data/parser/inline/ok/assoc_type_bound.rast index f2e4e01069..0f62e1dd18 100644 --- a/crates/parser/test_data/parser/inline/ok/assoc_type_bound.rast +++ b/crates/parser/test_data/parser/inline/ok/assoc_type_bound.rast @@ -35,3 +35,42 @@ SOURCE_FILE R_ANGLE ">" SEMICOLON ";" WHITESPACE "\n" + TYPE_ALIAS + TYPE_KW "type" + WHITESPACE " " + NAME + IDENT "T" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "StreamingIterator" + GENERIC_ARG_LIST + L_ANGLE "<" + ASSOC_TYPE_ARG + NAME_REF + IDENT "Item" + PARENTHESIZED_ARG_LIST + L_PAREN "(" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_PAREN ")" + COLON ":" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Clone" + R_ANGLE ">" + SEMICOLON ";" + WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/assoc_type_bound.rs b/crates/parser/test_data/parser/inline/ok/assoc_type_bound.rs index daae97e4fd..83fea1c6c8 100644 --- a/crates/parser/test_data/parser/inline/ok/assoc_type_bound.rs +++ b/crates/parser/test_data/parser/inline/ok/assoc_type_bound.rs @@ -1 +1,2 @@ type T = StreamingIterator: Clone>; +type T = StreamingIterator; diff --git a/crates/parser/test_data/parser/inline/ok/associated_type_bounds.rast b/crates/parser/test_data/parser/inline/ok/associated_type_bounds.rast deleted file mode 100644 index 8cbc98c51c..0000000000 --- a/crates/parser/test_data/parser/inline/ok/associated_type_bounds.rast +++ /dev/null @@ -1,111 +0,0 @@ -SOURCE_FILE - FN - FN_KW "fn" - WHITESPACE " " - NAME - IDENT "print_all" - GENERIC_PARAM_LIST - L_ANGLE "<" - TYPE_PARAM - NAME - IDENT "T" - COLON ":" - WHITESPACE " " - TYPE_BOUND_LIST - TYPE_BOUND - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Iterator" - GENERIC_ARG_LIST - L_ANGLE "<" - TYPE_ARG - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Item" - COMMA "," - WHITESPACE " " - TYPE_ARG - PATH_TYPE - PATH - PATH - PATH_SEGMENT - NAME_REF - IDENT "Item" - COLON2 "::" - PATH_SEGMENT - NAME_REF - IDENT "Item" - COMMA "," - WHITESPACE " " - TYPE_ARG - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Item" - GENERIC_ARG_LIST - COLON2 "::" - L_ANGLE "<" - CONST_ARG - LITERAL - TRUE_KW "true" - R_ANGLE ">" - COMMA "," - WHITESPACE " " - ASSOC_TYPE_ARG - NAME_REF - IDENT "Item" - COLON ":" - WHITESPACE " " - TYPE_BOUND_LIST - TYPE_BOUND - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Display" - COMMA "," - WHITESPACE " " - ASSOC_TYPE_ARG - NAME_REF - IDENT "Item" - GENERIC_ARG_LIST - L_ANGLE "<" - LIFETIME_ARG - LIFETIME - LIFETIME_IDENT "'a" - R_ANGLE ">" - WHITESPACE " " - EQ "=" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Item" - R_ANGLE ">" - R_ANGLE ">" - PARAM_LIST - L_PAREN "(" - PARAM - IDENT_PAT - NAME - IDENT "printables" - COLON ":" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "T" - R_PAREN ")" - WHITESPACE " " - BLOCK_EXPR - STMT_LIST - L_CURLY "{" - R_CURLY "}" - WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/associated_type_bounds.rs b/crates/parser/test_data/parser/inline/ok/associated_type_bounds.rs deleted file mode 100644 index 0f7a2d1608..0000000000 --- a/crates/parser/test_data/parser/inline/ok/associated_type_bounds.rs +++ /dev/null @@ -1 +0,0 @@ -fn print_all, Item: Display, Item<'a> = Item>>(printables: T) {} diff --git a/crates/parser/test_data/parser/inline/ok/async_trait_bound.rast b/crates/parser/test_data/parser/inline/ok/async_trait_bound.rast index ebf758286a..df0ba55d03 100644 --- a/crates/parser/test_data/parser/inline/ok/async_trait_bound.rast +++ b/crates/parser/test_data/parser/inline/ok/async_trait_bound.rast @@ -23,9 +23,9 @@ SOURCE_FILE PATH_SEGMENT NAME_REF IDENT "Fn" - PARAM_LIST + PARENTHESIZED_ARG_LIST L_PAREN "(" - PARAM + TYPE_ARG REF_TYPE AMP "&" PATH_TYPE diff --git a/crates/parser/test_data/parser/inline/ok/bare_dyn_types_with_paren_as_generic_args.rast b/crates/parser/test_data/parser/inline/ok/bare_dyn_types_with_paren_as_generic_args.rast deleted file mode 100644 index d5f97bad89..0000000000 --- a/crates/parser/test_data/parser/inline/ok/bare_dyn_types_with_paren_as_generic_args.rast +++ /dev/null @@ -1,175 +0,0 @@ -SOURCE_FILE - TYPE_ALIAS - TYPE_KW "type" - WHITESPACE " " - NAME - IDENT "A" - WHITESPACE " " - EQ "=" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "S" - GENERIC_ARG_LIST - L_ANGLE "<" - TYPE_ARG - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Fn" - PARAM_LIST - L_PAREN "(" - PARAM - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "i32" - R_PAREN ")" - R_ANGLE ">" - SEMICOLON ";" - WHITESPACE "\n" - TYPE_ALIAS - TYPE_KW "type" - WHITESPACE " " - NAME - IDENT "A" - WHITESPACE " " - EQ "=" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "S" - GENERIC_ARG_LIST - L_ANGLE "<" - TYPE_ARG - DYN_TRAIT_TYPE - TYPE_BOUND_LIST - TYPE_BOUND - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Fn" - PARAM_LIST - L_PAREN "(" - PARAM - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "i32" - R_PAREN ")" - WHITESPACE " " - PLUS "+" - WHITESPACE " " - TYPE_BOUND - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Send" - R_ANGLE ">" - SEMICOLON ";" - WHITESPACE "\n" - TYPE_ALIAS - TYPE_KW "type" - WHITESPACE " " - NAME - IDENT "B" - WHITESPACE " " - EQ "=" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "S" - GENERIC_ARG_LIST - L_ANGLE "<" - TYPE_ARG - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Fn" - PARAM_LIST - L_PAREN "(" - PARAM - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "i32" - R_PAREN ")" - WHITESPACE " " - RET_TYPE - THIN_ARROW "->" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "i32" - R_ANGLE ">" - SEMICOLON ";" - WHITESPACE "\n" - TYPE_ALIAS - TYPE_KW "type" - WHITESPACE " " - NAME - IDENT "C" - WHITESPACE " " - EQ "=" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "S" - GENERIC_ARG_LIST - L_ANGLE "<" - TYPE_ARG - DYN_TRAIT_TYPE - TYPE_BOUND_LIST - TYPE_BOUND - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Fn" - PARAM_LIST - L_PAREN "(" - PARAM - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "i32" - R_PAREN ")" - WHITESPACE " " - RET_TYPE - THIN_ARROW "->" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "i32" - WHITESPACE " " - PLUS "+" - WHITESPACE " " - TYPE_BOUND - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Send" - R_ANGLE ">" - SEMICOLON ";" - WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/bare_dyn_types_with_paren_as_generic_args.rs b/crates/parser/test_data/parser/inline/ok/bare_dyn_types_with_paren_as_generic_args.rs deleted file mode 100644 index 800002b1b8..0000000000 --- a/crates/parser/test_data/parser/inline/ok/bare_dyn_types_with_paren_as_generic_args.rs +++ /dev/null @@ -1,4 +0,0 @@ -type A = S; -type A = S; -type B = S i32>; -type C = S i32 + Send>; diff --git a/crates/parser/test_data/parser/inline/ok/generic_arg_bounds.rast b/crates/parser/test_data/parser/inline/ok/generic_arg_bounds.rast new file mode 100644 index 0000000000..fee5913aca --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/generic_arg_bounds.rast @@ -0,0 +1,461 @@ +SOURCE_FILE + TYPE_ALIAS + TYPE_KW "type" + WHITESPACE " " + NAME + IDENT "Plain" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Foo" + GENERIC_ARG_LIST + L_ANGLE "<" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Item" + COMMA "," + WHITESPACE " " + TYPE_ARG + PATH_TYPE + PATH + PATH + PATH_SEGMENT + NAME_REF + IDENT "Item" + COLON2 "::" + PATH_SEGMENT + NAME_REF + IDENT "Item" + COMMA "," + WHITESPACE " " + ASSOC_TYPE_ARG + NAME_REF + IDENT "Item" + COLON ":" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Bound" + COMMA "," + WHITESPACE " " + ASSOC_TYPE_ARG + NAME_REF + IDENT "Item" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Item" + R_ANGLE ">" + SEMICOLON ";" + WHITESPACE "\n" + TYPE_ALIAS + TYPE_KW "type" + WHITESPACE " " + NAME + IDENT "GenericArgs" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Foo" + GENERIC_ARG_LIST + L_ANGLE "<" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Item" + GENERIC_ARG_LIST + L_ANGLE "<" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_ANGLE ">" + COMMA "," + WHITESPACE " " + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Item" + GENERIC_ARG_LIST + COLON2 "::" + L_ANGLE "<" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_ANGLE ">" + COMMA "," + WHITESPACE " " + ASSOC_TYPE_ARG + NAME_REF + IDENT "Item" + GENERIC_ARG_LIST + L_ANGLE "<" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_ANGLE ">" + COLON ":" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Bound" + COMMA "," + WHITESPACE " " + ASSOC_TYPE_ARG + NAME_REF + IDENT "Item" + GENERIC_ARG_LIST + COLON2 "::" + L_ANGLE "<" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_ANGLE ">" + COLON ":" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Bound" + COMMA "," + WHITESPACE " " + ASSOC_TYPE_ARG + NAME_REF + IDENT "Item" + GENERIC_ARG_LIST + L_ANGLE "<" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_ANGLE ">" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Item" + COMMA "," + WHITESPACE " " + ASSOC_TYPE_ARG + NAME_REF + IDENT "Item" + GENERIC_ARG_LIST + COLON2 "::" + L_ANGLE "<" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_ANGLE ">" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Item" + R_ANGLE ">" + SEMICOLON ";" + WHITESPACE "\n" + TYPE_ALIAS + TYPE_KW "type" + WHITESPACE " " + NAME + IDENT "ParenthesizedArgs" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Foo" + GENERIC_ARG_LIST + L_ANGLE "<" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Item" + PARENTHESIZED_ARG_LIST + L_PAREN "(" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_PAREN ")" + COMMA "," + WHITESPACE " " + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Item" + PARENTHESIZED_ARG_LIST + COLON2 "::" + L_PAREN "(" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_PAREN ")" + COMMA "," + WHITESPACE " " + ASSOC_TYPE_ARG + NAME_REF + IDENT "Item" + PARENTHESIZED_ARG_LIST + L_PAREN "(" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_PAREN ")" + COLON ":" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Bound" + COMMA "," + WHITESPACE " " + ASSOC_TYPE_ARG + NAME_REF + IDENT "Item" + PARENTHESIZED_ARG_LIST + COLON2 "::" + L_PAREN "(" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_PAREN ")" + COLON ":" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Bound" + COMMA "," + WHITESPACE " " + ASSOC_TYPE_ARG + NAME_REF + IDENT "Item" + PARENTHESIZED_ARG_LIST + L_PAREN "(" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_PAREN ")" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Item" + COMMA "," + WHITESPACE " " + ASSOC_TYPE_ARG + NAME_REF + IDENT "Item" + PARENTHESIZED_ARG_LIST + COLON2 "::" + L_PAREN "(" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_PAREN ")" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Item" + R_ANGLE ">" + SEMICOLON ";" + WHITESPACE "\n" + TYPE_ALIAS + TYPE_KW "type" + WHITESPACE " " + NAME + IDENT "RTN" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Foo" + GENERIC_ARG_LIST + L_ANGLE "<" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Item" + RETURN_TYPE_SYNTAX + L_PAREN "(" + DOT2 ".." + R_PAREN ")" + COMMA "," + WHITESPACE " " + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Item" + RETURN_TYPE_SYNTAX + L_PAREN "(" + DOT2 ".." + R_PAREN ")" + COMMA "," + WHITESPACE " " + ASSOC_TYPE_ARG + NAME_REF + IDENT "Item" + RETURN_TYPE_SYNTAX + L_PAREN "(" + DOT2 ".." + R_PAREN ")" + COLON ":" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Bound" + COMMA "," + WHITESPACE " " + ASSOC_TYPE_ARG + NAME_REF + IDENT "Item" + RETURN_TYPE_SYNTAX + L_PAREN "(" + DOT2 ".." + R_PAREN ")" + COLON ":" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Bound" + COMMA "," + WHITESPACE " " + ASSOC_TYPE_ARG + NAME_REF + IDENT "Item" + RETURN_TYPE_SYNTAX + L_PAREN "(" + DOT2 ".." + R_PAREN ")" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Item" + COMMA "," + WHITESPACE " " + ASSOC_TYPE_ARG + NAME_REF + IDENT "Item" + RETURN_TYPE_SYNTAX + L_PAREN "(" + DOT2 ".." + R_PAREN ")" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Item" + R_ANGLE ">" + SEMICOLON ";" + WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/generic_arg_bounds.rs b/crates/parser/test_data/parser/inline/ok/generic_arg_bounds.rs new file mode 100644 index 0000000000..1abd0aeb59 --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/generic_arg_bounds.rs @@ -0,0 +1,4 @@ +type Plain = Foo; +type GenericArgs = Foo, Item::, Item: Bound, Item::: Bound, Item = Item, Item:: = Item>; +type ParenthesizedArgs = Foo; +type RTN = Foo; diff --git a/crates/parser/test_data/parser/inline/ok/param_list_opt_patterns.rast b/crates/parser/test_data/parser/inline/ok/param_list_opt_patterns.rast deleted file mode 100644 index e9d93a0d0a..0000000000 --- a/crates/parser/test_data/parser/inline/ok/param_list_opt_patterns.rast +++ /dev/null @@ -1,48 +0,0 @@ -SOURCE_FILE - FN - FN_KW "fn" - WHITESPACE " " - NAME - IDENT "foo" - GENERIC_PARAM_LIST - L_ANGLE "<" - TYPE_PARAM - NAME - IDENT "F" - COLON ":" - WHITESPACE " " - TYPE_BOUND_LIST - TYPE_BOUND - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "FnMut" - PARAM_LIST - L_PAREN "(" - PARAM - REF_TYPE - AMP "&" - MUT_KW "mut" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Foo" - GENERIC_ARG_LIST - L_ANGLE "<" - LIFETIME_ARG - LIFETIME - LIFETIME_IDENT "'a" - R_ANGLE ">" - R_PAREN ")" - R_ANGLE ">" - PARAM_LIST - L_PAREN "(" - R_PAREN ")" - BLOCK_EXPR - STMT_LIST - L_CURLY "{" - R_CURLY "}" - WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/param_list_opt_patterns.rs b/crates/parser/test_data/parser/inline/ok/param_list_opt_patterns.rs deleted file mode 100644 index 9b93442c0f..0000000000 --- a/crates/parser/test_data/parser/inline/ok/param_list_opt_patterns.rs +++ /dev/null @@ -1 +0,0 @@ -fn foo)>(){} diff --git a/crates/parser/test_data/parser/inline/ok/path_fn_trait_args.rast b/crates/parser/test_data/parser/inline/ok/path_fn_trait_args.rast index fd83daf841..924f7ba2c9 100644 --- a/crates/parser/test_data/parser/inline/ok/path_fn_trait_args.rast +++ b/crates/parser/test_data/parser/inline/ok/path_fn_trait_args.rast @@ -20,9 +20,133 @@ SOURCE_FILE PATH_SEGMENT NAME_REF IDENT "Fn" - PARAM_LIST + PARENTHESIZED_ARG_LIST L_PAREN "(" - PARAM + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "i32" + R_PAREN ")" + WHITESPACE " " + RET_TYPE + THIN_ARROW "->" + WHITESPACE " " + TUPLE_TYPE + L_PAREN "(" + R_PAREN ")" + R_ANGLE ">" + SEMICOLON ";" + WHITESPACE "\n" + TYPE_ALIAS + TYPE_KW "type" + WHITESPACE " " + NAME + IDENT "F" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Box" + GENERIC_ARG_LIST + L_ANGLE "<" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + COLON2 "::" + NAME_REF + IDENT "Fn" + PARENTHESIZED_ARG_LIST + L_PAREN "(" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "i32" + R_PAREN ")" + WHITESPACE " " + RET_TYPE + THIN_ARROW "->" + WHITESPACE " " + TUPLE_TYPE + L_PAREN "(" + R_PAREN ")" + R_ANGLE ">" + SEMICOLON ";" + WHITESPACE "\n" + TYPE_ALIAS + TYPE_KW "type" + WHITESPACE " " + NAME + IDENT "F" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Box" + GENERIC_ARG_LIST + L_ANGLE "<" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Fn" + PARENTHESIZED_ARG_LIST + COLON2 "::" + L_PAREN "(" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "i32" + R_PAREN ")" + WHITESPACE " " + RET_TYPE + THIN_ARROW "->" + WHITESPACE " " + TUPLE_TYPE + L_PAREN "(" + R_PAREN ")" + R_ANGLE ">" + SEMICOLON ";" + WHITESPACE "\n" + TYPE_ALIAS + TYPE_KW "type" + WHITESPACE " " + NAME + IDENT "F" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Box" + GENERIC_ARG_LIST + L_ANGLE "<" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + COLON2 "::" + NAME_REF + IDENT "Fn" + PARENTHESIZED_ARG_LIST + COLON2 "::" + L_PAREN "(" + TYPE_ARG PATH_TYPE PATH PATH_SEGMENT diff --git a/crates/parser/test_data/parser/inline/ok/path_fn_trait_args.rs b/crates/parser/test_data/parser/inline/ok/path_fn_trait_args.rs index 17ed20e5b1..7aa655d7ea 100644 --- a/crates/parser/test_data/parser/inline/ok/path_fn_trait_args.rs +++ b/crates/parser/test_data/parser/inline/ok/path_fn_trait_args.rs @@ -1 +1,4 @@ type F = Box ()>; +type F = Box<::Fn(i32) -> ()>; +type F = Box ()>; +type F = Box<::Fn::(i32) -> ()>; diff --git a/crates/parser/test_data/parser/inline/ok/return_type_syntax_assoc_type_bound.rast b/crates/parser/test_data/parser/inline/ok/return_type_syntax_assoc_type_bound.rast deleted file mode 100644 index 30e0e73bbd..0000000000 --- a/crates/parser/test_data/parser/inline/ok/return_type_syntax_assoc_type_bound.rast +++ /dev/null @@ -1,49 +0,0 @@ -SOURCE_FILE - FN - FN_KW "fn" - WHITESPACE " " - NAME - IDENT "foo" - GENERIC_PARAM_LIST - L_ANGLE "<" - TYPE_PARAM - NAME - IDENT "T" - COLON ":" - WHITESPACE " " - TYPE_BOUND_LIST - TYPE_BOUND - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Trait" - GENERIC_ARG_LIST - L_ANGLE "<" - ASSOC_TYPE_ARG - NAME_REF - IDENT "method" - RETURN_TYPE_SYNTAX - L_PAREN "(" - DOT2 ".." - R_PAREN ")" - COLON ":" - WHITESPACE " " - TYPE_BOUND_LIST - TYPE_BOUND - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Send" - R_ANGLE ">" - R_ANGLE ">" - PARAM_LIST - L_PAREN "(" - R_PAREN ")" - WHITESPACE " " - BLOCK_EXPR - STMT_LIST - L_CURLY "{" - R_CURLY "}" - WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/return_type_syntax_assoc_type_bound.rs b/crates/parser/test_data/parser/inline/ok/return_type_syntax_assoc_type_bound.rs deleted file mode 100644 index 8a4cf4c3a0..0000000000 --- a/crates/parser/test_data/parser/inline/ok/return_type_syntax_assoc_type_bound.rs +++ /dev/null @@ -1 +0,0 @@ -fn foo>() {} diff --git a/crates/parser/test_data/parser/inline/ok/return_type_syntax_in_path.rast b/crates/parser/test_data/parser/inline/ok/return_type_syntax_in_path.rast index 501dccd79d..15a0558b53 100644 --- a/crates/parser/test_data/parser/inline/ok/return_type_syntax_in_path.rast +++ b/crates/parser/test_data/parser/inline/ok/return_type_syntax_in_path.rast @@ -42,6 +42,49 @@ SOURCE_FILE NAME_REF IDENT "Send" COMMA "," + WHITESPACE "\n " + WHERE_PRED + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "method" + RETURN_TYPE_SYNTAX + L_PAREN "(" + DOT2 ".." + R_PAREN ")" + COLON ":" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Send" + COMMA "," + WHITESPACE "\n " + WHERE_PRED + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "method" + RETURN_TYPE_SYNTAX + COLON2 "::" + L_PAREN "(" + DOT2 ".." + R_PAREN ")" + COLON ":" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Send" + COMMA "," WHITESPACE "\n" BLOCK_EXPR STMT_LIST diff --git a/crates/parser/test_data/parser/inline/ok/return_type_syntax_in_path.rs b/crates/parser/test_data/parser/inline/ok/return_type_syntax_in_path.rs index a9b63fb01c..64b48c1638 100644 --- a/crates/parser/test_data/parser/inline/ok/return_type_syntax_in_path.rs +++ b/crates/parser/test_data/parser/inline/ok/return_type_syntax_in_path.rs @@ -1,4 +1,6 @@ fn foo() where T::method(..): Send, + method(..): Send, + method::(..): Send, {} diff --git a/crates/parser/test_data/parser/inline/ok/typepathfn_with_coloncolon.rast b/crates/parser/test_data/parser/inline/ok/typepathfn_with_coloncolon.rast index 67277d0639..fb442bfb73 100644 --- a/crates/parser/test_data/parser/inline/ok/typepathfn_with_coloncolon.rast +++ b/crates/parser/test_data/parser/inline/ok/typepathfn_with_coloncolon.rast @@ -13,10 +13,10 @@ SOURCE_FILE PATH_SEGMENT NAME_REF IDENT "Start" - COLON2 "::" - PARAM_LIST + PARENTHESIZED_ARG_LIST + COLON2 "::" L_PAREN "(" - PARAM + TYPE_ARG PATH_TYPE PATH PATH_SEGMENT @@ -63,9 +63,9 @@ SOURCE_FILE PATH_SEGMENT NAME_REF IDENT "Start" - PARAM_LIST + PARENTHESIZED_ARG_LIST L_PAREN "(" - PARAM + TYPE_ARG PATH_TYPE PATH PATH_SEGMENT diff --git a/crates/parser/test_data/parser/inline/ok/value_parameters_no_patterns.rast b/crates/parser/test_data/parser/inline/ok/value_parameters_no_patterns.rast deleted file mode 100644 index 902b06484c..0000000000 --- a/crates/parser/test_data/parser/inline/ok/value_parameters_no_patterns.rast +++ /dev/null @@ -1,60 +0,0 @@ -SOURCE_FILE - TYPE_ALIAS - TYPE_KW "type" - WHITESPACE " " - NAME - IDENT "F" - WHITESPACE " " - EQ "=" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Box" - GENERIC_ARG_LIST - L_ANGLE "<" - TYPE_ARG - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Fn" - PARAM_LIST - L_PAREN "(" - PARAM - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "i32" - COMMA "," - WHITESPACE " " - PARAM - REF_TYPE - AMP "&" - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "i32" - COMMA "," - WHITESPACE " " - PARAM - REF_TYPE - AMP "&" - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "i32" - COMMA "," - WHITESPACE " " - PARAM - TUPLE_TYPE - L_PAREN "(" - R_PAREN ")" - R_PAREN ")" - R_ANGLE ">" - SEMICOLON ";" - WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/value_parameters_no_patterns.rs b/crates/parser/test_data/parser/inline/ok/value_parameters_no_patterns.rs deleted file mode 100644 index 93636e926e..0000000000 --- a/crates/parser/test_data/parser/inline/ok/value_parameters_no_patterns.rs +++ /dev/null @@ -1 +0,0 @@ -type F = Box; diff --git a/crates/parser/test_data/parser/inline/ok/where_pred_for.rast b/crates/parser/test_data/parser/inline/ok/where_pred_for.rast index 8407e99f61..0cc365efbe 100644 --- a/crates/parser/test_data/parser/inline/ok/where_pred_for.rast +++ b/crates/parser/test_data/parser/inline/ok/where_pred_for.rast @@ -40,9 +40,9 @@ SOURCE_FILE PATH_SEGMENT NAME_REF IDENT "Fn" - PARAM_LIST + PARENTHESIZED_ARG_LIST L_PAREN "(" - PARAM + TYPE_ARG REF_TYPE AMP "&" LIFETIME diff --git a/crates/parser/test_data/parser/ok/0045_block_attrs.rast b/crates/parser/test_data/parser/ok/0045_block_attrs.rast index fad574a476..c22d99f1ae 100644 --- a/crates/parser/test_data/parser/ok/0045_block_attrs.rast +++ b/crates/parser/test_data/parser/ok/0045_block_attrs.rast @@ -180,7 +180,7 @@ SOURCE_FILE PATH_SEGMENT NAME_REF IDENT "Fn" - PARAM_LIST + PARENTHESIZED_ARG_LIST L_PAREN "(" R_PAREN ")" WHITESPACE " " diff --git a/crates/parser/test_data/parser/ok/0051_parameter_attrs.rast b/crates/parser/test_data/parser/ok/0051_parameter_attrs.rast index f8b11e7782..eafee90db4 100644 --- a/crates/parser/test_data/parser/ok/0051_parameter_attrs.rast +++ b/crates/parser/test_data/parser/ok/0051_parameter_attrs.rast @@ -138,63 +138,6 @@ SOURCE_FILE WHITESPACE " " R_CURLY "}" WHITESPACE "\n\n" - FN - FN_KW "fn" - WHITESPACE " " - NAME - IDENT "foo" - GENERIC_PARAM_LIST - L_ANGLE "<" - TYPE_PARAM - NAME - IDENT "F" - COLON ":" - WHITESPACE " " - TYPE_BOUND_LIST - TYPE_BOUND - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "FnMut" - PARAM_LIST - L_PAREN "(" - PARAM - ATTR - POUND "#" - L_BRACK "[" - META - PATH - PATH_SEGMENT - NAME_REF - IDENT "attr" - R_BRACK "]" - WHITESPACE " " - REF_TYPE - AMP "&" - MUT_KW "mut" - WHITESPACE " " - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Foo" - GENERIC_ARG_LIST - L_ANGLE "<" - LIFETIME_ARG - LIFETIME - LIFETIME_IDENT "'a" - R_ANGLE ">" - R_PAREN ")" - R_ANGLE ">" - PARAM_LIST - L_PAREN "(" - R_PAREN ")" - BLOCK_EXPR - STMT_LIST - L_CURLY "{" - R_CURLY "}" - WHITESPACE "\n\n" TRAIT TRAIT_KW "trait" WHITESPACE " " diff --git a/crates/parser/test_data/parser/ok/0051_parameter_attrs.rs b/crates/parser/test_data/parser/ok/0051_parameter_attrs.rs index de350d8587..0a0100e5d0 100644 --- a/crates/parser/test_data/parser/ok/0051_parameter_attrs.rs +++ b/crates/parser/test_data/parser/ok/0051_parameter_attrs.rs @@ -3,8 +3,6 @@ fn g2(#[attr1] x: u8) {} extern "C" { fn printf(format: *const i8, #[attr] ...) -> i32; } -fn foo)>(){} - trait Foo { fn bar(#[attr] _: u64, # [attr] mut x: i32); } diff --git a/crates/parser/test_data/parser/ok/0054_qual_path_in_type_arg.rast b/crates/parser/test_data/parser/ok/0054_qual_path_in_type_arg.rast index 4e1e31f376..31cca601ca 100644 --- a/crates/parser/test_data/parser/ok/0054_qual_path_in_type_arg.rast +++ b/crates/parser/test_data/parser/ok/0054_qual_path_in_type_arg.rast @@ -58,9 +58,9 @@ SOURCE_FILE PATH_SEGMENT NAME_REF IDENT "FnMut" - PARAM_LIST + PARENTHESIZED_ARG_LIST L_PAREN "(" - PARAM + TYPE_ARG PATH_TYPE PATH PATH @@ -101,9 +101,9 @@ SOURCE_FILE PATH_SEGMENT NAME_REF IDENT "FnMut" - PARAM_LIST + PARENTHESIZED_ARG_LIST L_PAREN "(" - PARAM + TYPE_ARG REF_TYPE AMP "&" PATH_TYPE diff --git a/crates/parser/test_data/parser/ok/0065_plus_after_fn_trait_bound.rast b/crates/parser/test_data/parser/ok/0065_plus_after_fn_trait_bound.rast index ba7b6042a9..d7ee11077c 100644 --- a/crates/parser/test_data/parser/ok/0065_plus_after_fn_trait_bound.rast +++ b/crates/parser/test_data/parser/ok/0065_plus_after_fn_trait_bound.rast @@ -32,7 +32,7 @@ SOURCE_FILE PATH_SEGMENT NAME_REF IDENT "Fn" - PARAM_LIST + PARENTHESIZED_ARG_LIST L_PAREN "(" R_PAREN ")" WHITESPACE " " diff --git a/crates/parser/test_data/parser/ok/0067_where_for_pred.rast b/crates/parser/test_data/parser/ok/0067_where_for_pred.rast index 136fce93d7..cd3b21ae94 100644 --- a/crates/parser/test_data/parser/ok/0067_where_for_pred.rast +++ b/crates/parser/test_data/parser/ok/0067_where_for_pred.rast @@ -40,9 +40,9 @@ SOURCE_FILE PATH_SEGMENT NAME_REF IDENT "Fn" - PARAM_LIST + PARENTHESIZED_ARG_LIST L_PAREN "(" - PARAM + TYPE_ARG REF_TYPE AMP "&" LIFETIME @@ -165,9 +165,9 @@ SOURCE_FILE PATH_SEGMENT NAME_REF IDENT "Fn" - PARAM_LIST + PARENTHESIZED_ARG_LIST L_PAREN "(" - PARAM + TYPE_ARG REF_TYPE AMP "&" LIFETIME diff --git a/crates/syntax/rust.ungram b/crates/syntax/rust.ungram index 02c59646a9..30428329dd 100644 --- a/crates/syntax/rust.ungram +++ b/crates/syntax/rust.ungram @@ -37,7 +37,7 @@ Path = PathSegment = '::'? NameRef | NameRef GenericArgList? -| NameRef ParamList RetType? +| NameRef ParenthesizedArgList RetType? | NameRef ReturnTypeSyntax | '<' Type ('as' PathType)? '>' @@ -49,6 +49,9 @@ ReturnTypeSyntax = // Generics // //*************************// +ParenthesizedArgList = + '::'? '(' (TypeArg (',' TypeArg)* ','?)? ')' + GenericArgList = '::'? '<' (GenericArg (',' GenericArg)* ','?)? '>' diff --git a/crates/syntax/src/ast/generated/nodes.rs b/crates/syntax/src/ast/generated/nodes.rs index 23d2b355a9..01dcb646b3 100644 --- a/crates/syntax/src/ast/generated/nodes.rs +++ b/crates/syntax/src/ast/generated/nodes.rs @@ -1362,6 +1362,21 @@ impl ParenType { pub fn r_paren_token(&self) -> Option { support::token(&self.syntax, T![')']) } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ParenthesizedArgList { + pub(crate) syntax: SyntaxNode, +} +impl ParenthesizedArgList { + #[inline] + pub fn type_args(&self) -> AstChildren { support::children(&self.syntax) } + #[inline] + pub fn l_paren_token(&self) -> Option { support::token(&self.syntax, T!['(']) } + #[inline] + pub fn r_paren_token(&self) -> Option { support::token(&self.syntax, T![')']) } + #[inline] + pub fn coloncolon_token(&self) -> Option { support::token(&self.syntax, T![::]) } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Path { pub(crate) syntax: SyntaxNode, @@ -1403,7 +1418,9 @@ impl PathSegment { #[inline] pub fn name_ref(&self) -> Option { support::child(&self.syntax) } #[inline] - pub fn param_list(&self) -> Option { support::child(&self.syntax) } + pub fn parenthesized_arg_list(&self) -> Option { + support::child(&self.syntax) + } #[inline] pub fn path_type(&self) -> Option { support::child(&self.syntax) } #[inline] @@ -3760,6 +3777,20 @@ impl AstNode for ParenType { #[inline] fn syntax(&self) -> &SyntaxNode { &self.syntax } } +impl AstNode for ParenthesizedArgList { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { kind == PARENTHESIZED_ARG_LIST } + #[inline] + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + #[inline] + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} impl AstNode for Path { #[inline] fn can_cast(kind: SyntaxKind) -> bool { kind == PATH } @@ -7097,6 +7128,11 @@ impl std::fmt::Display for ParenType { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for ParenthesizedArgList { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for Path { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) From 4e475a32456c95449d350159ecf544377b775a82 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Tue, 19 Nov 2024 19:57:07 +0200 Subject: [PATCH 27/95] Store some hir_def Paths in the type ref source maps Most paths are types and therefore already are in the source map, but the trait in impl trait and in bounds are not. We do this by storing them basically as `TypeRef`s. For convenience, I created a wrapper around `TypeRefId` called `PathId` that always stores a path, and implemented indexing from the types map to it. Fortunately, this change impacts memory usage negligibly (adds 2mb to `analysis-stats .`, but that could be just fluff). Probably because there aren't that many trait bounds and impl traits, and this also shrinks `TypeBound` by 8 bytes. I also added an accessor to `TypesSourceMap` to get the source code, which will be needed for diagnostics. --- crates/hir-def/src/body.rs | 5 +- crates/hir-def/src/data.rs | 2 +- crates/hir-def/src/generics.rs | 24 ++++-- crates/hir-def/src/hir/type_ref.rs | 84 ++++++++++++++----- crates/hir-def/src/item_tree/lower.rs | 9 +- crates/hir-def/src/item_tree/pretty.rs | 2 +- crates/hir-def/src/lib.rs | 3 + crates/hir-def/src/lower.rs | 6 +- crates/hir-def/src/pretty.rs | 4 +- crates/hir-def/src/resolver.rs | 10 ++- crates/hir-ty/src/display.rs | 6 +- crates/hir-ty/src/lower.rs | 33 +++++--- crates/hir-ty/src/tests.rs | 4 +- crates/hir-ty/src/utils.rs | 2 +- crates/hir/src/diagnostics.rs | 2 +- crates/hir/src/display.rs | 18 ++-- crates/hir/src/lib.rs | 6 +- .../rust-analyzer/src/cli/analysis_stats.rs | 3 +- 18 files changed, 145 insertions(+), 78 deletions(-) diff --git a/crates/hir-def/src/body.rs b/crates/hir-def/src/body.rs index d4a1120908..908f2050b5 100644 --- a/crates/hir-def/src/body.rs +++ b/crates/hir-def/src/body.rs @@ -31,7 +31,7 @@ use crate::{ path::{ModPath, Path}, src::HasSource, type_ref::{TypeRef, TypeRefId, TypesMap, TypesSourceMap}, - BlockId, DefWithBodyId, HasModule, Lookup, + BlockId, DefWithBodyId, HasModule, Lookup, SyntheticSyntax, }; /// A wrapper around [`span::SyntaxContextId`] that is intended only for comparisons. @@ -160,9 +160,6 @@ pub struct BodySourceMap { diagnostics: Vec, } -#[derive(Default, Debug, Eq, PartialEq, Clone, Copy)] -pub struct SyntheticSyntax; - #[derive(Debug, Eq, PartialEq)] pub enum BodyDiagnostic { InactiveCode { node: InFile, cfg: CfgExpr, opts: CfgOptions }, diff --git a/crates/hir-def/src/data.rs b/crates/hir-def/src/data.rs index 2a13f74aac..15dd6aba31 100644 --- a/crates/hir-def/src/data.rs +++ b/crates/hir-def/src/data.rs @@ -369,7 +369,7 @@ impl ImplData { let item_tree = tree_id.item_tree(db); let impl_def = &item_tree[tree_id.value]; - let target_trait = impl_def.target_trait.clone(); + let target_trait = impl_def.target_trait; let self_ty = impl_def.self_ty; let is_negative = impl_def.is_negative; let is_unsafe = impl_def.is_unsafe; diff --git a/crates/hir-def/src/generics.rs b/crates/hir-def/src/generics.rs index 11e9bb0d88..fdcb10e998 100644 --- a/crates/hir-def/src/generics.rs +++ b/crates/hir-def/src/generics.rs @@ -26,8 +26,8 @@ use crate::{ nameres::{DefMap, MacroSubNs}, path::{AssociatedTypeBinding, GenericArg, GenericArgs, NormalPath, Path}, type_ref::{ - ArrayType, ConstRef, FnType, LifetimeRef, RefType, TypeBound, TypeRef, TypeRefId, TypesMap, - TypesSourceMap, + ArrayType, ConstRef, FnType, LifetimeRef, PathId, RefType, TypeBound, TypeRef, TypeRefId, + TypesMap, TypesSourceMap, }, AdtId, ConstParamId, GenericDefId, HasModule, ItemTreeLoc, LifetimeParamId, LocalLifetimeParamId, LocalTypeOrConstParamId, Lookup, TypeOrConstParamId, TypeParamId, @@ -874,14 +874,20 @@ fn copy_type_bound( to: &mut TypesMap, to_source_map: &mut TypesSourceMap, ) -> TypeBound { - match bound { - TypeBound::Path(path, modifier) => { - TypeBound::Path(copy_path(path, from, from_source_map, to, to_source_map), *modifier) + let mut copy_path_id = |path: PathId| { + let new_path = copy_path(&from[path], from, from_source_map, to, to_source_map); + let new_path_id = to.types.alloc(TypeRef::Path(new_path)); + if let Some(&ptr) = from_source_map.types_map_back.get(path.type_ref()) { + to_source_map.types_map_back.insert(new_path_id, ptr); + } + PathId::from_type_ref_unchecked(new_path_id) + }; + + match bound { + &TypeBound::Path(path, modifier) => TypeBound::Path(copy_path_id(path), modifier), + TypeBound::ForLifetime(lifetimes, path) => { + TypeBound::ForLifetime(lifetimes.clone(), copy_path_id(*path)) } - TypeBound::ForLifetime(lifetimes, path) => TypeBound::ForLifetime( - lifetimes.clone(), - copy_path(path, from, from_source_map, to, to_source_map), - ), TypeBound::Lifetime(lifetime) => TypeBound::Lifetime(lifetime.clone()), TypeBound::Use(use_args) => TypeBound::Use(use_args.clone()), TypeBound::Error => TypeBound::Error, diff --git a/crates/hir-def/src/hir/type_ref.rs b/crates/hir-def/src/hir/type_ref.rs index 4d83ef99c8..15980ddcca 100644 --- a/crates/hir-def/src/hir/type_ref.rs +++ b/crates/hir-def/src/hir/type_ref.rs @@ -23,6 +23,7 @@ use crate::{ hir::Literal, lower::LowerCtx, path::{GenericArg, Path}, + SyntheticSyntax, }; #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] @@ -91,19 +92,37 @@ impl Rawness { } } -#[derive(Clone, PartialEq, Eq, Hash, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +/// A `TypeRefId` that is guaranteed to always be `TypeRef::Path`. We use this for things like +/// impl's trait, that are always paths but need to be traced back to source code. +pub struct PathId(TypeRefId); + +impl PathId { + #[inline] + pub fn from_type_ref_unchecked(type_ref: TypeRefId) -> Self { + Self(type_ref) + } + + #[inline] + pub fn type_ref(self) -> TypeRefId { + self.0 + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub struct TraitRef { - pub path: Path, + pub path: PathId, } impl TraitRef { /// Converts an `ast::PathType` to a `hir::TraitRef`. pub(crate) fn from_ast(ctx: &mut LowerCtx<'_>, node: ast::Type) -> Option { // FIXME: Use `Path::from_src` - match node { - ast::Type::PathType(path) => { - path.path().and_then(|it| ctx.lower_path(it)).map(|path| TraitRef { path }) - } + match &node { + ast::Type::PathType(path) => path + .path() + .and_then(|it| ctx.lower_path(it)) + .map(|path| TraitRef { path: ctx.alloc_path(path, AstPtr::new(&node)) }), _ => None, } } @@ -173,11 +192,24 @@ impl TypesMap { impl Index for TypesMap { type Output = TypeRef; + #[inline] fn index(&self, index: TypeRefId) -> &Self::Output { &self.types[index] } } +impl Index for TypesMap { + type Output = Path; + + #[inline] + fn index(&self, index: PathId) -> &Self::Output { + let TypeRef::Path(path) = &self[index.type_ref()] else { + unreachable!("`PathId` always points to `TypeRef::Path`"); + }; + path + } +} + pub type TypePtr = AstPtr; pub type TypeSource = InFile; @@ -187,6 +219,10 @@ pub struct TypesSourceMap { } impl TypesSourceMap { + pub fn type_syntax(&self, id: TypeRefId) -> Result { + self.types_map_back.get(id).cloned().ok_or(SyntheticSyntax) + } + pub(crate) fn shrink_to_fit(&mut self) { let TypesSourceMap { types_map_back } = self; types_map_back.shrink_to_fit(); @@ -214,15 +250,15 @@ impl LifetimeRef { #[derive(Clone, PartialEq, Eq, Hash, Debug)] pub enum TypeBound { - Path(Path, TraitBoundModifier), - ForLifetime(Box<[Name]>, Path), + Path(PathId, TraitBoundModifier), + ForLifetime(Box<[Name]>, PathId), Lifetime(LifetimeRef), Use(Box<[UseArgRef]>), Error, } #[cfg(target_pointer_width = "64")] -const _: [(); 32] = [(); ::std::mem::size_of::()]; +const _: [(); 24] = [(); ::std::mem::size_of::()]; #[derive(Clone, PartialEq, Eq, Hash, Debug)] pub enum UseArgRef { @@ -365,8 +401,8 @@ impl TypeRef { TypeRef::ImplTrait(bounds) | TypeRef::DynTrait(bounds) => { for bound in bounds { match bound { - TypeBound::Path(path, _) | TypeBound::ForLifetime(_, path) => { - go_path(path, f, map) + &TypeBound::Path(path, _) | &TypeBound::ForLifetime(_, path) => { + go_path(&map[path], f, map) } TypeBound::Lifetime(_) | TypeBound::Error | TypeBound::Use(_) => (), } @@ -397,8 +433,8 @@ impl TypeRef { } for bound in binding.bounds.iter() { match bound { - TypeBound::Path(path, _) | TypeBound::ForLifetime(_, path) => { - go_path(path, f, map) + &TypeBound::Path(path, _) | &TypeBound::ForLifetime(_, path) => { + go_path(&map[path], f, map) } TypeBound::Lifetime(_) | TypeBound::Error | TypeBound::Use(_) => (), } @@ -425,7 +461,7 @@ pub(crate) fn type_bounds_from_ast( impl TypeBound { pub(crate) fn from_ast(ctx: &mut LowerCtx<'_>, node: ast::TypeBound) -> Self { - let mut lower_path_type = |path_type: ast::PathType| ctx.lower_path(path_type.path()?); + let mut lower_path_type = |path_type: &ast::PathType| ctx.lower_path(path_type.path()?); match node.kind() { ast::TypeBoundKind::PathType(path_type) => { @@ -433,8 +469,10 @@ impl TypeBound { Some(_) => TraitBoundModifier::Maybe, None => TraitBoundModifier::None, }; - lower_path_type(path_type) - .map(|p| TypeBound::Path(p, m)) + lower_path_type(&path_type) + .map(|p| { + TypeBound::Path(ctx.alloc_path(p, AstPtr::new(&path_type).upcast()), m) + }) .unwrap_or(TypeBound::Error) } ast::TypeBoundKind::ForType(for_type) => { @@ -445,12 +483,14 @@ impl TypeBound { .collect(), None => Box::default(), }; - let path = for_type.ty().and_then(|ty| match ty { - ast::Type::PathType(path_type) => lower_path_type(path_type), + let path = for_type.ty().and_then(|ty| match &ty { + ast::Type::PathType(path_type) => lower_path_type(path_type).map(|p| (p, ty)), _ => None, }); match path { - Some(p) => TypeBound::ForLifetime(lt_refs, p), + Some((p, ty)) => { + TypeBound::ForLifetime(lt_refs, ctx.alloc_path(p, AstPtr::new(&ty))) + } None => TypeBound::Error, } } @@ -470,10 +510,10 @@ impl TypeBound { } } - pub fn as_path(&self) -> Option<(&Path, &TraitBoundModifier)> { + pub fn as_path<'a>(&self, map: &'a TypesMap) -> Option<(&'a Path, TraitBoundModifier)> { match self { - TypeBound::Path(p, m) => Some((p, m)), - TypeBound::ForLifetime(_, p) => Some((p, &TraitBoundModifier::None)), + &TypeBound::Path(p, m) => Some((&map[p], m)), + &TypeBound::ForLifetime(_, p) => Some((&map[p], TraitBoundModifier::None)), TypeBound::Lifetime(_) | TypeBound::Error | TypeBound::Use(_) => None, } } diff --git a/crates/hir-def/src/item_tree/lower.rs b/crates/hir-def/src/item_tree/lower.rs index d519c1708b..71848845a8 100644 --- a/crates/hir-def/src/item_tree/lower.rs +++ b/crates/hir-def/src/item_tree/lower.rs @@ -34,7 +34,7 @@ use crate::{ lower::LowerCtx, path::AssociatedTypeBinding, type_ref::{ - LifetimeRef, RefType, TraitBoundModifier, TraitRef, TypeBound, TypeRef, TypeRefId, + LifetimeRef, PathId, RefType, TraitBoundModifier, TraitRef, TypeBound, TypeRef, TypeRefId, TypesMap, TypesSourceMap, }, visibility::RawVisibility, @@ -514,7 +514,7 @@ impl<'a> Ctx<'a> { }; let ret_type = if func.async_token().is_some() { - let future_impl = desugar_future_path(ret_type); + let future_impl = desugar_future_path(&mut body_ctx, ret_type); let ty_bound = TypeBound::Path(future_impl, TraitBoundModifier::None); body_ctx.alloc_type_ref_desugared(TypeRef::ImplTrait(ThinVec::from_iter([ty_bound]))) } else { @@ -936,7 +936,7 @@ impl<'a> Ctx<'a> { } } -fn desugar_future_path(orig: TypeRefId) -> Path { +fn desugar_future_path(ctx: &mut LowerCtx<'_>, orig: TypeRefId) -> PathId { let path = path![core::future::Future]; let mut generic_args: Vec<_> = std::iter::repeat(None).take(path.segments().len() - 1).collect(); @@ -948,7 +948,8 @@ fn desugar_future_path(orig: TypeRefId) -> Path { }; generic_args.push(Some(GenericArgs { bindings: Box::new([binding]), ..GenericArgs::empty() })); - Path::from_known_path(path, generic_args) + let path = Path::from_known_path(path, generic_args); + PathId::from_type_ref_unchecked(ctx.alloc_type_ref_desugared(TypeRef::Path(path))) } enum HasImplicitSelf { diff --git a/crates/hir-def/src/item_tree/pretty.rs b/crates/hir-def/src/item_tree/pretty.rs index b6816a1f96..0c5e3a3620 100644 --- a/crates/hir-def/src/item_tree/pretty.rs +++ b/crates/hir-def/src/item_tree/pretty.rs @@ -484,7 +484,7 @@ impl Printer<'_> { w!(self, "!"); } if let Some(tr) = target_trait { - self.print_path(&tr.path, types_map); + self.print_path(&types_map[tr.path], types_map); w!(self, " for "); } self.print_type_ref(*self_ty, types_map); diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs index eb55ba1d53..8af27513eb 100644 --- a/crates/hir-def/src/lib.rs +++ b/crates/hir-def/src/lib.rs @@ -1535,3 +1535,6 @@ fn macro_call_as_call_id_with_eager( pub struct UnresolvedMacro { pub path: hir_expand::mod_path::ModPath, } + +#[derive(Default, Debug, Eq, PartialEq, Clone, Copy)] +pub struct SyntheticSyntax; diff --git a/crates/hir-def/src/lower.rs b/crates/hir-def/src/lower.rs index 350bb8d517..7cddd48eb1 100644 --- a/crates/hir-def/src/lower.rs +++ b/crates/hir-def/src/lower.rs @@ -10,7 +10,7 @@ use triomphe::Arc; use crate::{ db::DefDatabase, path::Path, - type_ref::{TypeBound, TypePtr, TypeRef, TypeRefId, TypesMap, TypesSourceMap}, + type_ref::{PathId, TypeBound, TypePtr, TypeRef, TypeRefId, TypesMap, TypesSourceMap}, }; pub struct LowerCtx<'a> { @@ -142,4 +142,8 @@ impl<'a> LowerCtx<'a> { pub(crate) fn alloc_error_type(&mut self) -> TypeRefId { self.types_map.types.alloc(TypeRef::Error) } + + pub(crate) fn alloc_path(&mut self, path: Path, node: TypePtr) -> PathId { + PathId::from_type_ref_unchecked(self.alloc_type_ref(TypeRef::Path(path), node)) + } } diff --git a/crates/hir-def/src/pretty.rs b/crates/hir-def/src/pretty.rs index 9ceb82d5fd..eb9488feaa 100644 --- a/crates/hir-def/src/pretty.rs +++ b/crates/hir-def/src/pretty.rs @@ -271,7 +271,7 @@ pub(crate) fn print_type_bounds( TraitBoundModifier::None => (), TraitBoundModifier::Maybe => write!(buf, "?")?, } - print_path(db, path, map, buf, edition)?; + print_path(db, &map[*path], map, buf, edition)?; } TypeBound::ForLifetime(lifetimes, path) => { write!( @@ -279,7 +279,7 @@ pub(crate) fn print_type_bounds( "for<{}> ", lifetimes.iter().map(|it| it.display(db.upcast(), edition)).format(", ") )?; - print_path(db, path, map, buf, edition)?; + print_path(db, &map[*path], map, buf, edition)?; } TypeBound::Lifetime(lt) => write!(buf, "{}", lt.name.display(db.upcast(), edition))?, TypeBound::Use(args) => { diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs index 26655e40ca..0ca7070fd0 100644 --- a/crates/hir-def/src/resolver.rs +++ b/crates/hir-def/src/resolver.rs @@ -576,10 +576,12 @@ impl Resolver { match scope { Scope::BlockScope(m) => traits.extend(m.def_map[m.module_id].scope.traits()), &Scope::ImplDefScope(impl_) => { - if let Some(target_trait) = &db.impl_data(impl_).target_trait { - if let Some(TypeNs::TraitId(trait_)) = - self.resolve_path_in_type_ns_fully(db, &target_trait.path) - { + let impl_data = db.impl_data(impl_); + if let Some(target_trait) = impl_data.target_trait { + if let Some(TypeNs::TraitId(trait_)) = self.resolve_path_in_type_ns_fully( + db, + &impl_data.types_map[target_trait.path], + ) { traits.insert(trait_); } } diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs index 4e95bdf219..94a340fbec 100644 --- a/crates/hir-ty/src/display.rs +++ b/crates/hir-ty/src/display.rs @@ -2062,12 +2062,12 @@ impl HirDisplayWithTypesMap for TypeBound { types_map: &TypesMap, ) -> Result<(), HirDisplayError> { match self { - TypeBound::Path(path, modifier) => { + &TypeBound::Path(path, modifier) => { match modifier { TraitBoundModifier::None => (), TraitBoundModifier::Maybe => write!(f, "?")?, } - path.hir_fmt(f, types_map) + types_map[path].hir_fmt(f, types_map) } TypeBound::Lifetime(lifetime) => { write!(f, "{}", lifetime.name.display(f.db.upcast(), f.edition())) @@ -2079,7 +2079,7 @@ impl HirDisplayWithTypesMap for TypeBound { "for<{}> ", lifetimes.iter().map(|it| it.display(f.db.upcast(), edition)).format(", ") )?; - path.hir_fmt(f, types_map) + types_map[*path].hir_fmt(f, types_map) } TypeBound::Use(args) => { let edition = f.edition(); diff --git a/crates/hir-ty/src/lower.rs b/crates/hir-ty/src/lower.rs index b868ea95f8..fc0af0cec9 100644 --- a/crates/hir-ty/src/lower.rs +++ b/crates/hir-ty/src/lower.rs @@ -33,8 +33,8 @@ use hir_def::{ path::{GenericArg, GenericArgs, ModPath, Path, PathKind, PathSegment, PathSegments}, resolver::{HasResolver, LifetimeNs, Resolver, TypeNs}, type_ref::{ - ConstRef, LifetimeRef, TraitBoundModifier, TraitRef as HirTraitRef, TypeBound, TypeRef, - TypeRefId, TypesMap, TypesSourceMap, + ConstRef, LifetimeRef, PathId, TraitBoundModifier, TraitRef as HirTraitRef, TypeBound, + TypeRef, TypeRefId, TypesMap, TypesSourceMap, }, AdtId, AssocItemId, CallableDefId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, FunctionId, GenericDefId, GenericParamId, HasModule, ImplId, InTypeConstLoc, ItemContainerId, @@ -264,7 +264,8 @@ impl<'a> TyLoweringContext<'a> { .intern(Interner) } TypeRef::Path(path) => { - let (ty, res_) = self.lower_path(path); + let (ty, res_) = + self.lower_path(path, PathId::from_type_ref_unchecked(type_ref_id)); res = res_; ty } @@ -677,7 +678,7 @@ impl<'a> TyLoweringContext<'a> { self.lower_ty_relative_path(ty, Some(resolution), remaining_segments) } - pub(crate) fn lower_path(&mut self, path: &Path) -> (Ty, Option) { + pub(crate) fn lower_path(&mut self, path: &Path, path_id: PathId) -> (Ty, Option) { // Resolve the path (in type namespace) if let Some(type_ref) = path.type_anchor() { let (ty, res) = self.lower_ty_ext(type_ref); @@ -692,7 +693,7 @@ impl<'a> TyLoweringContext<'a> { if matches!(resolution, TypeNs::TraitId(_)) && remaining_index.is_none() { // trait object type without dyn - let bound = TypeBound::Path(path.clone(), TraitBoundModifier::None); + let bound = TypeBound::Path(path_id, TraitBoundModifier::None); let ty = self.lower_dyn_trait(&[bound]); return (ty, None); } @@ -998,7 +999,12 @@ impl<'a> TyLoweringContext<'a> { TraitRef { trait_id: to_chalk_trait_id(resolved), substitution: substs } } - fn lower_trait_ref_from_path(&mut self, path: &Path, explicit_self_ty: Ty) -> Option { + fn lower_trait_ref_from_path( + &mut self, + path_id: PathId, + explicit_self_ty: Ty, + ) -> Option { + let path = &self.types_map[path_id]; let resolved = match self.resolver.resolve_path_in_type_ns_fully(self.db.upcast(), path)? { // FIXME(trait_alias): We need to handle trait alias here. TypeNs::TraitId(tr) => tr, @@ -1013,7 +1019,7 @@ impl<'a> TyLoweringContext<'a> { trait_ref: &HirTraitRef, explicit_self_ty: Ty, ) -> Option { - self.lower_trait_ref_from_path(&trait_ref.path, explicit_self_ty) + self.lower_trait_ref_from_path(trait_ref.path, explicit_self_ty) } fn trait_ref_substs_from_path( @@ -1072,11 +1078,11 @@ impl<'a> TyLoweringContext<'a> { ) -> impl Iterator + use<'b, 'a> { let mut trait_ref = None; let clause = match bound { - TypeBound::Path(path, TraitBoundModifier::None) => { + &TypeBound::Path(path, TraitBoundModifier::None) => { trait_ref = self.lower_trait_ref_from_path(path, self_ty); trait_ref.clone().map(WhereClause::Implemented).map(crate::wrap_empty_binders) } - TypeBound::Path(path, TraitBoundModifier::Maybe) => { + &TypeBound::Path(path, TraitBoundModifier::Maybe) => { let sized_trait = self .db .lang_item(self.resolver.krate(), LangItem::Sized) @@ -1092,7 +1098,7 @@ impl<'a> TyLoweringContext<'a> { } None } - TypeBound::ForLifetime(_, path) => { + &TypeBound::ForLifetime(_, path) => { // FIXME Don't silently drop the hrtb lifetimes here trait_ref = self.lower_trait_ref_from_path(path, self_ty); trait_ref.clone().map(WhereClause::Implemented).map(crate::wrap_empty_binders) @@ -1121,8 +1127,8 @@ impl<'a> TyLoweringContext<'a> { trait_ref: TraitRef, ) -> impl Iterator + use<'b, 'a> { let last_segment = match bound { - TypeBound::Path(path, TraitBoundModifier::None) | TypeBound::ForLifetime(_, path) => { - path.segments().last() + &TypeBound::Path(path, TraitBoundModifier::None) | &TypeBound::ForLifetime(_, path) => { + self.types_map[path].segments().last() } TypeBound::Path(_, TraitBoundModifier::Maybe) | TypeBound::Use(_) @@ -1593,9 +1599,10 @@ pub(crate) fn generic_predicates_for_param_query( } match bound { - TypeBound::ForLifetime(_, path) | TypeBound::Path(path, _) => { + &TypeBound::ForLifetime(_, path) | &TypeBound::Path(path, _) => { // Only lower the bound if the trait could possibly define the associated // type we're looking for. + let path = &ctx.types_map[path]; let Some(assoc_name) = &assoc_name else { return true }; let Some(TypeNs::TraitId(tr)) = diff --git a/crates/hir-ty/src/tests.rs b/crates/hir-ty/src/tests.rs index bcf9d5ceff..cabeeea2bd 100644 --- a/crates/hir-ty/src/tests.rs +++ b/crates/hir-ty/src/tests.rs @@ -18,13 +18,13 @@ use std::sync::LazyLock; use base_db::SourceDatabaseFileInputExt as _; use expect_test::Expect; use hir_def::{ - body::{Body, BodySourceMap, SyntheticSyntax}, + body::{Body, BodySourceMap}, db::DefDatabase, hir::{ExprId, Pat, PatId}, item_scope::ItemScope, nameres::DefMap, src::HasSource, - AssocItemId, DefWithBodyId, HasModule, LocalModuleId, Lookup, ModuleDefId, + AssocItemId, DefWithBodyId, HasModule, LocalModuleId, Lookup, ModuleDefId, SyntheticSyntax, }; use hir_expand::{db::ExpandDatabase, FileRange, InFile}; use itertools::Itertools; diff --git a/crates/hir-ty/src/utils.rs b/crates/hir-ty/src/utils.rs index 28bda1e10e..06719b09f7 100644 --- a/crates/hir-ty/src/utils.rs +++ b/crates/hir-ty/src/utils.rs @@ -185,7 +185,7 @@ fn direct_super_traits_cb(db: &dyn DefDatabase, trait_: TraitId, cb: impl FnMut( } }; match is_trait { - true => bound.as_path(), + true => bound.as_path(&generic_params.types_map), false => None, } } diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index 9ca021027d..9df6ca58f5 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -13,7 +13,7 @@ use hir_ty::{ use cfg::{CfgExpr, CfgOptions}; use either::Either; pub use hir_def::VariantId; -use hir_def::{body::SyntheticSyntax, hir::ExprOrPatId, path::ModPath, AssocItemId, DefWithBodyId}; +use hir_def::{hir::ExprOrPatId, path::ModPath, AssocItemId, DefWithBodyId, SyntheticSyntax}; use hir_expand::{name::Name, HirFileId, InFile}; use syntax::{ast, AstPtr, SyntaxError, SyntaxNodePtr, TextRange}; use triomphe::Arc; diff --git a/crates/hir/src/display.rs b/crates/hir/src/display.rs index 9275f45d88..959d62d595 100644 --- a/crates/hir/src/display.rs +++ b/crates/hir/src/display.rs @@ -132,12 +132,18 @@ impl HirDisplay for Function { } else { match &data.types_map[data.ret_type] { TypeRef::ImplTrait(bounds) => match &bounds[0] { - TypeBound::Path(path, _) => Some( - *path.segments().iter().last().unwrap().args_and_bindings.unwrap().bindings - [0] - .type_ref - .as_ref() - .unwrap(), + &TypeBound::Path(path, _) => Some( + *data.types_map[path] + .segments() + .iter() + .last() + .unwrap() + .args_and_bindings + .unwrap() + .bindings[0] + .type_ref + .as_ref() + .unwrap(), ), _ => None, }, diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 0b2ba56b1f..dc1d1efe60 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -43,7 +43,7 @@ use arrayvec::ArrayVec; use base_db::{CrateDisplayName, CrateId, CrateOrigin}; use either::Either; use hir_def::{ - body::{BodyDiagnostic, SyntheticSyntax}, + body::BodyDiagnostic, data::adt::VariantData, generics::{LifetimeParamData, TypeOrConstParamData, TypeParamProvenance}, hir::{BindingAnnotation, BindingId, ExprId, ExprOrPatId, LabelId, Pat}, @@ -57,8 +57,8 @@ use hir_def::{ AssocItemId, AssocItemLoc, AttrDefId, CallableDefId, ConstId, ConstParamId, CrateRootModuleId, DefWithBodyId, EnumId, EnumVariantId, ExternCrateId, FunctionId, GenericDefId, GenericParamId, HasModule, ImplId, InTypeConstId, ItemContainerId, LifetimeParamId, LocalFieldId, Lookup, - MacroExpander, ModuleId, StaticId, StructId, TraitAliasId, TraitId, TupleId, TypeAliasId, - TypeOrConstParamId, TypeParamId, UnionId, + MacroExpander, ModuleId, StaticId, StructId, SyntheticSyntax, TraitAliasId, TraitId, TupleId, + TypeAliasId, TypeOrConstParamId, TypeParamId, UnionId, }; use hir_expand::{ attrs::collect_attrs, proc_macro::ProcMacroKind, AstId, MacroCallKind, RenderedExpandError, diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs index 802d0c69a4..e3ea441f3a 100644 --- a/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -13,8 +13,9 @@ use hir::{ ModuleDef, Name, }; use hir_def::{ - body::{BodySourceMap, SyntheticSyntax}, + body::BodySourceMap, hir::{ExprId, PatId}, + SyntheticSyntax, }; use hir_ty::{Interner, Substitution, TyExt, TypeFlags}; use ide::{ From 5f25ae3d1b51ef713010598a8a4ca0d87a6a6a3c Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Wed, 20 Nov 2024 23:05:48 +0200 Subject: [PATCH 28/95] Lay the foundation for diagnostics in ty lowering, and implement a first diagnostic The diagnostic implemented is a simple one (E0109). It serves as a test for the new foundation. This commit only implements diagnostics for type in bodies and body-carrying signatures; the next commit will include diagnostics in the rest of the things. Also fix one weird bug that was detected when implementing this that caused `Fn::(A, B) -> C` (which is a valid, if bizarre, alternative syntax to `Fn(A, B) -> C` to lower incorrectly. And also fix a maybe-bug where parentheses were sneaked into a code string needlessly; this was not detected until now because the parentheses were removed (by the make-AST family API), but with a change in this commit they are now inserted. So fix that too. --- Cargo.lock | 4 + crates/hir-def/src/body.rs | 2 +- crates/hir-def/src/hir/type_ref.rs | 2 + crates/hir-def/src/path.rs | 12 +- crates/hir-def/src/path/lower.rs | 80 +++++- crates/hir-def/src/path/tests.rs | 126 +++++++++ crates/hir-expand/src/files.rs | 7 + crates/hir-ty/src/infer.rs | 96 +++++-- crates/hir-ty/src/infer/expr.rs | 2 +- crates/hir-ty/src/infer/path.rs | 6 +- crates/hir-ty/src/lib.rs | 7 +- crates/hir-ty/src/lower.rs | 187 +++++++++++--- crates/hir/Cargo.toml | 8 + crates/hir/src/diagnostics.rs | 67 ++++- crates/hir/src/lib.rs | 33 ++- .../src/handlers/generic_args_prohibited.rs | 242 ++++++++++++++++++ .../src/handlers/unresolved_method.rs | 6 +- crates/ide-diagnostics/src/lib.rs | 2 + crates/syntax/src/ast/make.rs | 2 +- 19 files changed, 811 insertions(+), 80 deletions(-) create mode 100644 crates/hir-def/src/path/tests.rs create mode 100644 crates/ide-diagnostics/src/handlers/generic_args_prohibited.rs diff --git a/Cargo.lock b/Cargo.lock index 2bd4d17fe2..5861833d53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -509,6 +509,7 @@ dependencies = [ "base-db", "cfg", "either", + "expect-test", "hir-def", "hir-expand", "hir-ty", @@ -519,6 +520,9 @@ dependencies = [ "span", "stdx", "syntax", + "syntax-bridge", + "test-fixture", + "test-utils", "tracing", "triomphe", "tt", diff --git a/crates/hir-def/src/body.rs b/crates/hir-def/src/body.rs index 908f2050b5..867bee95be 100644 --- a/crates/hir-def/src/body.rs +++ b/crates/hir-def/src/body.rs @@ -141,7 +141,7 @@ pub struct BodySourceMap { field_map_back: FxHashMap, pat_field_map_back: FxHashMap, - types: TypesSourceMap, + pub types: TypesSourceMap, // FIXME: Make this a sane struct. template_map: Option< diff --git a/crates/hir-def/src/hir/type_ref.rs b/crates/hir-def/src/hir/type_ref.rs index 15980ddcca..6d4d519cd2 100644 --- a/crates/hir-def/src/hir/type_ref.rs +++ b/crates/hir-def/src/hir/type_ref.rs @@ -219,6 +219,8 @@ pub struct TypesSourceMap { } impl TypesSourceMap { + pub const EMPTY: Self = Self { types_map_back: ArenaMap::new() }; + pub fn type_syntax(&self, id: TypeRefId) -> Result { self.types_map_back.get(id).cloned().ok_or(SyntheticSyntax) } diff --git a/crates/hir-def/src/path.rs b/crates/hir-def/src/path.rs index aa2c4a6f1b..44e132061a 100644 --- a/crates/hir-def/src/path.rs +++ b/crates/hir-def/src/path.rs @@ -1,5 +1,7 @@ //! A desugared representation of paths like `crate::foo` or `::bar`. mod lower; +#[cfg(test)] +mod tests; use std::{ fmt::{self, Display}, @@ -19,6 +21,8 @@ use syntax::ast; pub use hir_expand::mod_path::{path, ModPath, PathKind}; +pub use lower::hir_segment_to_ast_segment; + #[derive(Debug, Clone, PartialEq, Eq)] pub enum ImportAlias { /// Unnamed alias, as in `use Foo as _;` @@ -230,7 +234,7 @@ impl Path { } } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct PathSegment<'a> { pub name: &'a Name, pub args_and_bindings: Option<&'a GenericArgs>, @@ -274,6 +278,12 @@ impl<'a> PathSegments<'a> { generic_args: self.generic_args.map(|it| it.get(..len).unwrap_or(it)), } } + pub fn strip_last(&self) -> PathSegments<'a> { + PathSegments { + segments: self.segments.split_last().map_or(&[], |it| it.1), + generic_args: self.generic_args.map(|it| it.split_last().map_or(&[][..], |it| it.1)), + } + } pub fn iter(&self) -> impl Iterator> { self.segments .iter() diff --git a/crates/hir-def/src/path/lower.rs b/crates/hir-def/src/path/lower.rs index 37c94a5f11..3b7e7653fb 100644 --- a/crates/hir-def/src/path/lower.rs +++ b/crates/hir-def/src/path/lower.rs @@ -17,13 +17,31 @@ use crate::{ type_ref::{LifetimeRef, TypeBound, TypeRef}, }; +#[cfg(test)] +thread_local! { + /// This is used to test `hir_segment_to_ast_segment()`. It's a hack, but it makes testing much easier. + pub(super) static SEGMENT_LOWERING_MAP: std::cell::RefCell> = std::cell::RefCell::default(); +} + /// Converts an `ast::Path` to `Path`. Works with use trees. /// It correctly handles `$crate` based path from macro call. +// If you modify the logic of the lowering, make sure to check if `hir_segment_to_ast_segment()` +// also needs an update. pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option { let mut kind = PathKind::Plain; let mut type_anchor = None; let mut segments = Vec::new(); let mut generic_args = Vec::new(); + #[cfg(test)] + let mut ast_segments = Vec::new(); + #[cfg(test)] + let mut ast_segments_offset = 0; + #[allow(unused_mut)] + let mut push_segment = |_segment: &ast::PathSegment, segments: &mut Vec, name| { + #[cfg(test)] + ast_segments.push(_segment.clone()); + segments.push(name); + }; loop { let segment = path.segment()?; @@ -34,6 +52,10 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option< match segment.kind()? { ast::PathSegmentKind::Name(name_ref) => { if name_ref.text() == "$crate" { + if path.qualifier().is_some() { + // FIXME: Report an error. + return None; + } break kind = resolve_crate_root( ctx.db.upcast(), ctx.span_map().span_for_range(name_ref.syntax().text_range()).ctx, @@ -56,10 +78,10 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option< generic_args.resize(segments.len(), None); generic_args.push(args); } - segments.push(name); + push_segment(&segment, &mut segments, name); } ast::PathSegmentKind::SelfTypeKw => { - segments.push(Name::new_symbol_root(sym::Self_.clone())); + push_segment(&segment, &mut segments, Name::new_symbol_root(sym::Self_.clone())); } ast::PathSegmentKind::Type { type_ref, trait_ref } => { assert!(path.qualifier().is_none()); // this can only occur at the first segment @@ -81,6 +103,10 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option< kind = mod_path.kind; segments.extend(mod_path.segments().iter().cloned().rev()); + #[cfg(test)] + { + ast_segments_offset = mod_path.segments().len(); + } if let Some(path_generic_args) = path_generic_args { generic_args.resize(segments.len() - num_segments, None); generic_args.extend(Vec::from(path_generic_args).into_iter().rev()); @@ -112,10 +138,18 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option< } } ast::PathSegmentKind::CrateKw => { + if path.qualifier().is_some() { + // FIXME: Report an error. + return None; + } kind = PathKind::Crate; break; } ast::PathSegmentKind::SelfKw => { + if path.qualifier().is_some() { + // FIXME: Report an error. + return None; + } // don't break out if `self` is the last segment of a path, this mean we got a // use tree like `foo::{self}` which we want to resolve as `foo` if !segments.is_empty() { @@ -162,6 +196,13 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option< } } + #[cfg(test)] + { + ast_segments.reverse(); + SEGMENT_LOWERING_MAP + .with_borrow_mut(|map| map.extend(ast_segments.into_iter().zip(ast_segments_offset..))); + } + let mod_path = Interned::new(ModPath::from_segments(kind, segments)); if type_anchor.is_none() && generic_args.is_empty() { return Some(Path::BarePath(mod_path)); @@ -181,6 +222,41 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option< } } +/// This function finds the AST segment that corresponds to the HIR segment +/// with index `segment_idx` on the path that is lowered from `path`. +pub fn hir_segment_to_ast_segment(path: &ast::Path, segment_idx: u32) -> Option { + // Too tightly coupled to `lower_path()`, but unfortunately we cannot decouple them, + // as keeping source maps for all paths segments will have a severe impact on memory usage. + + let mut segments = path.segments(); + if let Some(ast::PathSegmentKind::Type { trait_ref: Some(trait_ref), .. }) = + segments.clone().next().and_then(|it| it.kind()) + { + segments.next(); + return find_segment(trait_ref.path()?.segments().chain(segments), segment_idx); + } + return find_segment(segments, segment_idx); + + fn find_segment( + segments: impl Iterator, + segment_idx: u32, + ) -> Option { + segments + .filter(|segment| match segment.kind() { + Some( + ast::PathSegmentKind::CrateKw + | ast::PathSegmentKind::SelfKw + | ast::PathSegmentKind::SuperKw + | ast::PathSegmentKind::Type { .. }, + ) + | None => false, + Some(ast::PathSegmentKind::Name(name)) => name.text() != "$crate", + Some(ast::PathSegmentKind::SelfTypeKw) => true, + }) + .nth(segment_idx as usize) + } +} + pub(super) fn lower_generic_args( lower_ctx: &mut LowerCtx<'_>, node: ast::GenericArgList, diff --git a/crates/hir-def/src/path/tests.rs b/crates/hir-def/src/path/tests.rs new file mode 100644 index 0000000000..67a27bf85e --- /dev/null +++ b/crates/hir-def/src/path/tests.rs @@ -0,0 +1,126 @@ +use expect_test::{expect, Expect}; +use span::Edition; +use syntax::ast::{self, make}; +use test_fixture::WithFixture; + +use crate::{ + lower::LowerCtx, + path::{ + lower::{hir_segment_to_ast_segment, SEGMENT_LOWERING_MAP}, + Path, + }, + pretty, + test_db::TestDB, + type_ref::{TypesMap, TypesSourceMap}, +}; + +fn lower_path(path: ast::Path) -> (TestDB, TypesMap, Option) { + let (db, file_id) = TestDB::with_single_file(""); + let mut types_map = TypesMap::default(); + let mut types_source_map = TypesSourceMap::default(); + let mut ctx = LowerCtx::new(&db, file_id.into(), &mut types_map, &mut types_source_map); + let lowered_path = ctx.lower_path(path); + (db, types_map, lowered_path) +} + +#[track_caller] +fn check_hir_to_ast(path: &str, ignore_segments: &[&str]) { + let path = make::path_from_text(path); + SEGMENT_LOWERING_MAP.with_borrow_mut(|map| map.clear()); + let _ = lower_path(path.clone()).2.expect("failed to lower path"); + SEGMENT_LOWERING_MAP.with_borrow(|map| { + for (segment, segment_idx) in map { + if ignore_segments.contains(&&*segment.to_string()) { + continue; + } + + let restored_segment = hir_segment_to_ast_segment(&path, *segment_idx as u32) + .unwrap_or_else(|| { + panic!( + "failed to map back segment `{segment}` \ + numbered {segment_idx} in HIR from path `{path}`" + ) + }); + assert_eq!( + segment, &restored_segment, + "mapping back `{segment}` numbered {segment_idx} in HIR \ + from path `{path}` produced incorrect segment `{restored_segment}`" + ); + } + }); +} + +#[test] +fn hir_to_ast_trait_ref() { + check_hir_to_ast("::E::F", &["A"]); +} + +#[test] +fn hir_to_ast_plain_path() { + check_hir_to_ast("A::B::C::D::E::F", &[]); +} + +#[test] +fn hir_to_ast_crate_path() { + check_hir_to_ast("crate::A::B::C", &[]); + check_hir_to_ast("crate::super::super::A::B::C", &[]); +} + +#[test] +fn hir_to_ast_self_path() { + check_hir_to_ast("self::A::B::C", &[]); + check_hir_to_ast("self::super::super::A::B::C", &[]); +} + +#[test] +fn hir_to_ast_super_path() { + check_hir_to_ast("super::A::B::C", &[]); + check_hir_to_ast("super::super::super::A::B::C", &[]); +} + +#[test] +fn hir_to_ast_type_anchor_path() { + check_hir_to_ast("::C::D", &["A", "B"]); +} + +#[test] +fn hir_to_ast_path_super_in_middle() { + check_hir_to_ast("A::super::B::super::super::C::D", &[]); +} + +#[track_caller] +fn check_fail_lowering(path: &str) { + let (_, _, lowered_path) = lower_path(make::path_from_text(path)); + assert!(lowered_path.is_none(), "path `{path}` should fail lowering"); +} + +#[test] +fn keywords_in_middle_fail_lowering1() { + check_fail_lowering("self::A::self::B::super::C::crate::D"); +} + +#[test] +fn keywords_in_middle_fail_lowering2() { + check_fail_lowering("A::super::self::C::D"); +} + +#[test] +fn keywords_in_middle_fail_lowering3() { + check_fail_lowering("A::crate::B::C::D"); +} + +#[track_caller] +fn check_path_lowering(path: &str, expected: Expect) { + let (db, types_map, lowered_path) = lower_path(make::path_from_text(path)); + let lowered_path = lowered_path.expect("failed to lower path"); + let mut buf = String::new(); + pretty::print_path(&db, &lowered_path, &types_map, &mut buf, Edition::CURRENT) + .expect("failed to pretty-print path"); + expected.assert_eq(&buf); +} + +#[test] +fn fn_like_path_with_coloncolon() { + check_path_lowering("Fn::(A, B) -> C", expect![[r#"Fn::<(A, B), Output = C>"#]]); + check_path_lowering("Fn::(A, B)", expect![[r#"Fn::<(A, B), Output = ()>"#]]); +} diff --git a/crates/hir-expand/src/files.rs b/crates/hir-expand/src/files.rs index d41f69812e..8c04d05402 100644 --- a/crates/hir-expand/src/files.rs +++ b/crates/hir-expand/src/files.rs @@ -180,6 +180,13 @@ impl InFileWrapper { } } +#[allow(private_bounds)] +impl InFileWrapper> { + pub fn to_node(&self, db: &dyn ExpandDatabase) -> N { + self.value.to_node(&self.file_syntax(db)) + } +} + impl InFileWrapper { pub fn syntax(&self) -> InFileWrapper { self.with_value(self.value.syntax()) diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 01e0b635b2..dbee5a1a91 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -58,7 +58,7 @@ use crate::{ fold_tys, generics::Generics, infer::{coerce::CoerceMany, expr::ExprIsRead, unify::InferenceTable}, - lower::ImplTraitLoweringMode, + lower::{ImplTraitLoweringMode, TyLoweringDiagnostic}, mir::MirSpan, to_assoc_type_id, traits::FnTrait, @@ -191,6 +191,14 @@ impl InferOk { } } +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum InferenceTyDiagnosticSource { + /// Diagnostics that come from types in the body. + Body, + /// Diagnostics that come from types in fn parameters/return type, or static & const types. + Signature, +} + #[derive(Debug)] pub(crate) struct TypeError; pub(crate) type InferResult = Result, TypeError>; @@ -264,6 +272,10 @@ pub enum InferenceDiagnostic { expr_ty: Ty, cast_ty: Ty, }, + TyDiagnostic { + source: InferenceTyDiagnosticSource, + diag: TyLoweringDiagnostic, + }, } /// A mismatch between an expected and an inferred type. @@ -858,7 +870,8 @@ impl<'a> InferenceContext<'a> { } fn collect_const(&mut self, data: &ConstData) { - let return_ty = self.make_ty(data.type_ref, &data.types_map); + let return_ty = + self.make_ty(data.type_ref, &data.types_map, InferenceTyDiagnosticSource::Signature); // Constants might be defining usage sites of TAITs. self.make_tait_coercion_table(iter::once(&return_ty)); @@ -867,7 +880,8 @@ impl<'a> InferenceContext<'a> { } fn collect_static(&mut self, data: &StaticData) { - let return_ty = self.make_ty(data.type_ref, &data.types_map); + let return_ty = + self.make_ty(data.type_ref, &data.types_map, InferenceTyDiagnosticSource::Signature); // Statics might be defining usage sites of TAITs. self.make_tait_coercion_table(iter::once(&return_ty)); @@ -877,11 +891,12 @@ impl<'a> InferenceContext<'a> { fn collect_fn(&mut self, func: FunctionId) { let data = self.db.function_data(func); - let mut param_tys = self.with_ty_lowering(&data.types_map, |ctx| { - ctx.type_param_mode(ParamLoweringMode::Placeholder) - .impl_trait_mode(ImplTraitLoweringMode::Param); - data.params.iter().map(|&type_ref| ctx.lower_ty(type_ref)).collect::>() - }); + let mut param_tys = + self.with_ty_lowering(&data.types_map, InferenceTyDiagnosticSource::Signature, |ctx| { + ctx.type_param_mode(ParamLoweringMode::Placeholder) + .impl_trait_mode(ImplTraitLoweringMode::Param); + data.params.iter().map(|&type_ref| ctx.lower_ty(type_ref)).collect::>() + }); // Check if function contains a va_list, if it does then we append it to the parameter types // that are collected from the function data if data.is_varargs() { @@ -918,11 +933,12 @@ impl<'a> InferenceContext<'a> { } let return_ty = data.ret_type; - let return_ty = self.with_ty_lowering(&data.types_map, |ctx| { - ctx.type_param_mode(ParamLoweringMode::Placeholder) - .impl_trait_mode(ImplTraitLoweringMode::Opaque) - .lower_ty(return_ty) - }); + let return_ty = + self.with_ty_lowering(&data.types_map, InferenceTyDiagnosticSource::Signature, |ctx| { + ctx.type_param_mode(ParamLoweringMode::Placeholder) + .impl_trait_mode(ImplTraitLoweringMode::Opaque) + .lower_ty(return_ty) + }); let return_ty = self.insert_type_vars(return_ty); let return_ty = if let Some(rpits) = self.db.return_type_impl_traits(func) { @@ -1226,9 +1242,20 @@ impl<'a> InferenceContext<'a> { self.result.diagnostics.push(diagnostic); } + fn push_ty_diagnostics( + &mut self, + source: InferenceTyDiagnosticSource, + diagnostics: Vec, + ) { + self.result.diagnostics.extend( + diagnostics.into_iter().map(|diag| InferenceDiagnostic::TyDiagnostic { source, diag }), + ); + } + fn with_ty_lowering( - &self, + &mut self, types_map: &TypesMap, + types_source: InferenceTyDiagnosticSource, f: impl FnOnce(&mut crate::lower::TyLoweringContext<'_>) -> R, ) -> R { let mut ctx = crate::lower::TyLoweringContext::new( @@ -1237,32 +1264,41 @@ impl<'a> InferenceContext<'a> { types_map, self.owner.into(), ); - f(&mut ctx) + let result = f(&mut ctx); + self.push_ty_diagnostics(types_source, ctx.diagnostics); + result } fn with_body_ty_lowering( - &self, + &mut self, f: impl FnOnce(&mut crate::lower::TyLoweringContext<'_>) -> R, ) -> R { - self.with_ty_lowering(&self.body.types, f) + self.with_ty_lowering(&self.body.types, InferenceTyDiagnosticSource::Body, f) } - fn make_ty(&mut self, type_ref: TypeRefId, types_map: &TypesMap) -> Ty { - let ty = self.with_ty_lowering(types_map, |ctx| ctx.lower_ty(type_ref)); + fn make_ty( + &mut self, + type_ref: TypeRefId, + types_map: &TypesMap, + type_source: InferenceTyDiagnosticSource, + ) -> Ty { + let ty = self.with_ty_lowering(types_map, type_source, |ctx| ctx.lower_ty(type_ref)); let ty = self.insert_type_vars(ty); self.normalize_associated_types_in(ty) } fn make_body_ty(&mut self, type_ref: TypeRefId) -> Ty { - self.make_ty(type_ref, &self.body.types) + self.make_ty(type_ref, &self.body.types, InferenceTyDiagnosticSource::Body) } fn err_ty(&self) -> Ty { self.result.standard_types.unknown.clone() } - fn make_lifetime(&mut self, lifetime_ref: &LifetimeRef) -> Lifetime { - let lt = self.with_ty_lowering(TypesMap::EMPTY, |ctx| ctx.lower_lifetime(lifetime_ref)); + fn make_body_lifetime(&mut self, lifetime_ref: &LifetimeRef) -> Lifetime { + let lt = self.with_ty_lowering(TypesMap::EMPTY, InferenceTyDiagnosticSource::Body, |ctx| { + ctx.lower_lifetime(lifetime_ref) + }); self.insert_type_vars(lt) } @@ -1431,12 +1467,20 @@ impl<'a> InferenceContext<'a> { Some(ResolveValueResult::ValueNs(value, _)) => match value { ValueNs::EnumVariantId(var) => { let substs = ctx.substs_from_path(path, var.into(), true); + self.push_ty_diagnostics( + InferenceTyDiagnosticSource::Body, + ctx.diagnostics, + ); let ty = self.db.ty(var.lookup(self.db.upcast()).parent.into()); let ty = self.insert_type_vars(ty.substitute(Interner, &substs)); return (ty, Some(var.into())); } ValueNs::StructId(strukt) => { let substs = ctx.substs_from_path(path, strukt.into(), true); + self.push_ty_diagnostics( + InferenceTyDiagnosticSource::Body, + ctx.diagnostics, + ); let ty = self.db.ty(strukt.into()); let ty = self.insert_type_vars(ty.substitute(Interner, &substs)); return (ty, Some(strukt.into())); @@ -1462,18 +1506,21 @@ impl<'a> InferenceContext<'a> { return match resolution { TypeNs::AdtId(AdtId::StructId(strukt)) => { let substs = ctx.substs_from_path(path, strukt.into(), true); + self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics); let ty = self.db.ty(strukt.into()); let ty = self.insert_type_vars(ty.substitute(Interner, &substs)); forbid_unresolved_segments((ty, Some(strukt.into())), unresolved) } TypeNs::AdtId(AdtId::UnionId(u)) => { let substs = ctx.substs_from_path(path, u.into(), true); + self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics); let ty = self.db.ty(u.into()); let ty = self.insert_type_vars(ty.substitute(Interner, &substs)); forbid_unresolved_segments((ty, Some(u.into())), unresolved) } TypeNs::EnumVariantId(var) => { let substs = ctx.substs_from_path(path, var.into(), true); + self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics); let ty = self.db.ty(var.lookup(self.db.upcast()).parent.into()); let ty = self.insert_type_vars(ty.substitute(Interner, &substs)); forbid_unresolved_segments((ty, Some(var.into())), unresolved) @@ -1519,6 +1566,9 @@ impl<'a> InferenceContext<'a> { resolved_segment, current_segment, false, + &mut |_, _reason| { + // FIXME: Report an error. + }, ); ty = self.table.insert_type_vars(ty); @@ -1532,6 +1582,7 @@ impl<'a> InferenceContext<'a> { remaining_idx += 1; remaining_segments = remaining_segments.skip(1); } + self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics); let variant = ty.as_adt().and_then(|(id, _)| match id { AdtId::StructId(s) => Some(VariantId::StructId(s)), @@ -1550,6 +1601,7 @@ impl<'a> InferenceContext<'a> { }; let substs = ctx.substs_from_path_segment(resolved_seg, Some(it.into()), true, None); + self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics); let ty = self.db.ty(it.into()); let ty = self.insert_type_vars(ty.substitute(Interner, &substs)); diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 32b4ea2f28..e060b37f15 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -2155,7 +2155,7 @@ impl InferenceContext<'_> { DebruijnIndex::INNERMOST, ) }, - |this, lt_ref| this.make_lifetime(lt_ref), + |this, lt_ref| this.make_body_lifetime(lt_ref), ), }; diff --git a/crates/hir-ty/src/infer/path.rs b/crates/hir-ty/src/infer/path.rs index 7550d197a3..a6296c3af2 100644 --- a/crates/hir-ty/src/infer/path.rs +++ b/crates/hir-ty/src/infer/path.rs @@ -19,7 +19,7 @@ use crate::{ TyBuilder, TyExt, TyKind, ValueTyDefId, }; -use super::{ExprOrPatId, InferenceContext}; +use super::{ExprOrPatId, InferenceContext, InferenceTyDiagnosticSource}; impl InferenceContext<'_> { pub(super) fn infer_path(&mut self, path: &Path, id: ExprOrPatId) -> Option { @@ -163,6 +163,7 @@ impl InferenceContext<'_> { let remaining_segments_for_ty = path.segments().take(path.segments().len() - 1); let (ty, _) = ctx.lower_ty_relative_path(ty, orig_ns, remaining_segments_for_ty); + self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics); let ty = self.table.insert_type_vars(ty); let ty = self.table.normalize_associated_types_in(ty); self.resolve_ty_assoc_item(ty, last.name, id).map(|(it, substs)| (it, Some(substs)))? @@ -265,6 +266,9 @@ impl InferenceContext<'_> { resolved_segment, remaining_segments_for_ty, true, + &mut |_, _reason| { + // FIXME: Report an error. + }, ) }); if ty.is_unknown() { diff --git a/crates/hir-ty/src/lib.rs b/crates/hir-ty/src/lib.rs index fdc6579763..77ae295cee 100644 --- a/crates/hir-ty/src/lib.rs +++ b/crates/hir-ty/src/lib.rs @@ -84,12 +84,13 @@ pub use infer::{ cast::CastError, closure::{CaptureKind, CapturedItem}, could_coerce, could_unify, could_unify_deeply, Adjust, Adjustment, AutoBorrow, BindingMode, - InferenceDiagnostic, InferenceResult, OverloadedDeref, PointerCast, + InferenceDiagnostic, InferenceResult, InferenceTyDiagnosticSource, OverloadedDeref, + PointerCast, }; pub use interner::Interner; pub use lower::{ - associated_type_shorthand_candidates, ImplTraitLoweringMode, ParamLoweringMode, TyDefId, - TyLoweringContext, ValueTyDefId, + associated_type_shorthand_candidates, GenericArgsProhibitedReason, ImplTraitLoweringMode, + ParamLoweringMode, TyDefId, TyLoweringContext, TyLoweringDiagnosticKind, ValueTyDefId, }; pub use mapping::{ from_assoc_type_id, from_chalk_trait_id, from_foreign_def_id, from_placeholder_idx, diff --git a/crates/hir-ty/src/lower.rs b/crates/hir-ty/src/lower.rs index fc0af0cec9..b73967be71 100644 --- a/crates/hir-ty/src/lower.rs +++ b/crates/hir-ty/src/lower.rs @@ -102,6 +102,31 @@ impl ImplTraitLoweringState { } } +type TypeSource = Either; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct TyLoweringDiagnostic { + pub source: TypeSource, + pub kind: TyLoweringDiagnosticKind, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum TyLoweringDiagnosticKind { + GenericArgsProhibited { segment: u32, reason: GenericArgsProhibitedReason }, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum GenericArgsProhibitedReason { + Module, + TyParam, + SelfTy, + PrimitiveTy, + /// When there is a generic enum, within the expression `Enum::Variant`, + /// either `Enum` or `Variant` are allowed to have generic arguments, but not both. + // FIXME: This is not used now but it should be. + EnumVariant, +} + #[derive(Debug)] pub struct TyLoweringContext<'a> { pub db: &'a dyn HirDatabase, @@ -125,6 +150,7 @@ pub struct TyLoweringContext<'a> { expander: Option, /// Tracks types with explicit `?Sized` bounds. pub(crate) unsized_types: FxHashSet, + pub(crate) diagnostics: Vec, } impl<'a> TyLoweringContext<'a> { @@ -159,6 +185,7 @@ impl<'a> TyLoweringContext<'a> { type_param_mode, expander: None, unsized_types: FxHashSet::default(), + diagnostics: Vec::new(), } } @@ -198,6 +225,20 @@ impl<'a> TyLoweringContext<'a> { self.type_param_mode = type_param_mode; self } + + pub fn push_diagnostic(&mut self, type_ref: TypeRefId, kind: TyLoweringDiagnosticKind) { + let source = match self.types_source_map { + Some(source_map) => { + let Ok(source) = source_map.type_syntax(type_ref) else { + stdx::never!("error in synthetic type"); + return; + }; + Either::Right(source) + } + None => Either::Left(type_ref), + }; + self.diagnostics.push(TyLoweringDiagnostic { source, kind }); + } } #[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] @@ -464,6 +505,7 @@ impl<'a> TyLoweringContext<'a> { impl_trait_mode: mem::take(&mut self.impl_trait_mode), expander: self.expander.take(), unsized_types: mem::take(&mut self.unsized_types), + diagnostics: mem::take(&mut self.diagnostics), }; let ty = inner_ctx.lower_ty(type_ref); @@ -471,6 +513,7 @@ impl<'a> TyLoweringContext<'a> { self.impl_trait_mode = inner_ctx.impl_trait_mode; self.expander = inner_ctx.expander; self.unsized_types = inner_ctx.unsized_types; + self.diagnostics = inner_ctx.diagnostics; self.expander.as_mut().unwrap().exit(mark); Some(ty) @@ -542,6 +585,10 @@ impl<'a> TyLoweringContext<'a> { resolved_segment: PathSegment<'_>, remaining_segments: PathSegments<'_>, infer_args: bool, + on_prohibited_generics_for_resolved_segment: &mut dyn FnMut( + &mut Self, + GenericArgsProhibitedReason, + ), ) -> (Ty, Option) { let ty = match resolution { TypeNs::TraitId(trait_) => { @@ -608,28 +655,44 @@ impl<'a> TyLoweringContext<'a> { // FIXME(trait_alias): Implement trait alias. return (TyKind::Error.intern(Interner), None); } - TypeNs::GenericParam(param_id) => match self.type_param_mode { - ParamLoweringMode::Placeholder => { - TyKind::Placeholder(to_placeholder_idx(self.db, param_id.into())) + TypeNs::GenericParam(param_id) => { + if resolved_segment.args_and_bindings.is_some() { + on_prohibited_generics_for_resolved_segment( + self, + GenericArgsProhibitedReason::TyParam, + ); } - ParamLoweringMode::Variable => { - let idx = match self - .generics() - .expect("generics in scope") - .type_or_const_param_idx(param_id.into()) - { - None => { - never!("no matching generics"); - return (TyKind::Error.intern(Interner), None); - } - Some(idx) => idx, - }; - TyKind::BoundVar(BoundVar::new(self.in_binders, idx)) + match self.type_param_mode { + ParamLoweringMode::Placeholder => { + TyKind::Placeholder(to_placeholder_idx(self.db, param_id.into())) + } + ParamLoweringMode::Variable => { + let idx = match self + .generics() + .expect("generics in scope") + .type_or_const_param_idx(param_id.into()) + { + None => { + never!("no matching generics"); + return (TyKind::Error.intern(Interner), None); + } + Some(idx) => idx, + }; + + TyKind::BoundVar(BoundVar::new(self.in_binders, idx)) + } } + .intern(Interner) } - .intern(Interner), TypeNs::SelfType(impl_id) => { + if resolved_segment.args_and_bindings.is_some() { + on_prohibited_generics_for_resolved_segment( + self, + GenericArgsProhibitedReason::SelfTy, + ); + } + let generics = self.generics().expect("impl should have generic param scope"); match self.type_param_mode { @@ -655,6 +718,13 @@ impl<'a> TyLoweringContext<'a> { } } TypeNs::AdtSelfType(adt) => { + if resolved_segment.args_and_bindings.is_some() { + on_prohibited_generics_for_resolved_segment( + self, + GenericArgsProhibitedReason::SelfTy, + ); + } + let generics = generics(self.db.upcast(), adt.into()); let substs = match self.type_param_mode { ParamLoweringMode::Placeholder => generics.placeholder_subst(self.db), @@ -667,6 +737,12 @@ impl<'a> TyLoweringContext<'a> { TypeNs::AdtId(it) => self.lower_path_inner(resolved_segment, it.into(), infer_args), TypeNs::BuiltinType(it) => { + if resolved_segment.args_and_bindings.is_some() { + on_prohibited_generics_for_resolved_segment( + self, + GenericArgsProhibitedReason::PrimitiveTy, + ); + } self.lower_path_inner(resolved_segment, it.into(), infer_args) } TypeNs::TypeAliasId(it) => { @@ -698,14 +774,39 @@ impl<'a> TyLoweringContext<'a> { return (ty, None); } - let (resolved_segment, remaining_segments) = match remaining_index { - None => ( - path.segments().last().expect("resolved path has at least one element"), - PathSegments::EMPTY, - ), - Some(i) => (path.segments().get(i - 1).unwrap(), path.segments().skip(i)), - }; - self.lower_partly_resolved_path(resolution, resolved_segment, remaining_segments, false) + let (module_segments, resolved_segment_idx, resolved_segment, remaining_segments) = + match remaining_index { + None => ( + path.segments().strip_last(), + path.segments().len() - 1, + path.segments().last().expect("resolved path has at least one element"), + PathSegments::EMPTY, + ), + Some(i) => ( + path.segments().take(i - 1), + i - 1, + path.segments().get(i - 1).unwrap(), + path.segments().skip(i), + ), + }; + + self.prohibit_generics(path_id, 0, module_segments, GenericArgsProhibitedReason::Module); + + self.lower_partly_resolved_path( + resolution, + resolved_segment, + remaining_segments, + false, + &mut |this, reason| { + this.push_diagnostic( + path_id.type_ref(), + TyLoweringDiagnosticKind::GenericArgsProhibited { + segment: resolved_segment_idx as u32, + reason, + }, + ) + }, + ) } fn select_associated_type(&mut self, res: Option, segment: PathSegment<'_>) -> Ty { @@ -742,12 +843,8 @@ impl<'a> TyLoweringContext<'a> { // generic params. It's inefficient to splice the `Substitution`s, so we may want // that method to optionally take parent `Substitution` as we already know them at // this point (`t.substitution`). - let substs = self.substs_from_path_segment( - segment.clone(), - Some(associated_ty.into()), - false, - None, - ); + let substs = + self.substs_from_path_segment(segment, Some(associated_ty.into()), false, None); let len_self = crate::generics::generics(self.db.upcast(), associated_ty.into()).len_self(); @@ -999,6 +1096,23 @@ impl<'a> TyLoweringContext<'a> { TraitRef { trait_id: to_chalk_trait_id(resolved), substitution: substs } } + fn prohibit_generics( + &mut self, + path_id: PathId, + idx: u32, + segments: PathSegments<'_>, + reason: GenericArgsProhibitedReason, + ) { + segments.iter().zip(idx..).for_each(|(segment, idx)| { + if segment.args_and_bindings.is_some() { + self.push_diagnostic( + path_id.type_ref(), + TyLoweringDiagnosticKind::GenericArgsProhibited { segment: idx, reason }, + ); + } + }); + } + fn lower_trait_ref_from_path( &mut self, path_id: PathId, @@ -1010,6 +1124,13 @@ impl<'a> TyLoweringContext<'a> { TypeNs::TraitId(tr) => tr, _ => return None, }; + // Do this after we verify it's indeed a trait to not confuse the user if they're not modules. + self.prohibit_generics( + path_id, + 0, + path.segments().strip_last(), + GenericArgsProhibitedReason::Module, + ); let segment = path.segments().last().expect("path should have at least one segment"); Some(self.lower_trait_ref_from_resolved_path(resolved, segment, explicit_self_ty)) } @@ -1233,7 +1354,9 @@ impl<'a> TyLoweringContext<'a> { } _ => unreachable!(), } - ext.lower_ty(type_ref) + let ty = ext.lower_ty(type_ref); + self.diagnostics.extend(ext.diagnostics); + ty } else { self.lower_ty(type_ref) }; diff --git a/crates/hir/Cargo.toml b/crates/hir/Cargo.toml index 26666d6feb..6aadc5c4f7 100644 --- a/crates/hir/Cargo.toml +++ b/crates/hir/Cargo.toml @@ -33,6 +33,14 @@ syntax.workspace = true tt.workspace = true span.workspace = true +[dev-dependencies] +expect-test.workspace = true + +# local deps +test-utils.workspace = true +test-fixture.workspace = true +syntax-bridge.workspace = true + [features] in-rust-tree = ["hir-expand/in-rust-tree"] diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index 9df6ca58f5..3657cf5e16 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -3,23 +3,34 @@ //! //! This probably isn't the best way to do this -- ideally, diagnostics should //! be expressed in terms of hir types themselves. -pub use hir_ty::diagnostics::{CaseType, IncorrectCase}; +use cfg::{CfgExpr, CfgOptions}; +use either::Either; +use hir_def::{ + hir::ExprOrPatId, + path::{hir_segment_to_ast_segment, ModPath}, + type_ref::TypesSourceMap, + AssocItemId, DefWithBodyId, SyntheticSyntax, +}; +use hir_expand::{name::Name, HirFileId, InFile}; use hir_ty::{ db::HirDatabase, diagnostics::{BodyValidationDiagnostic, UnsafetyReason}, - CastError, InferenceDiagnostic, + CastError, InferenceDiagnostic, InferenceTyDiagnosticSource, TyLoweringDiagnosticKind, +}; +use syntax::{ + ast::{self, HasGenericArgs}, + AstPtr, SyntaxError, SyntaxNodePtr, TextRange, }; - -use cfg::{CfgExpr, CfgOptions}; -use either::Either; -pub use hir_def::VariantId; -use hir_def::{hir::ExprOrPatId, path::ModPath, AssocItemId, DefWithBodyId, SyntheticSyntax}; -use hir_expand::{name::Name, HirFileId, InFile}; -use syntax::{ast, AstPtr, SyntaxError, SyntaxNodePtr, TextRange}; use triomphe::Arc; use crate::{AssocItem, Field, Local, Trait, Type}; +pub use hir_def::VariantId; +pub use hir_ty::{ + diagnostics::{CaseType, IncorrectCase}, + GenericArgsProhibitedReason, +}; + macro_rules! diagnostics { ($($diag:ident,)*) => { #[derive(Debug)] @@ -98,6 +109,7 @@ diagnostics![ UnresolvedIdent, UnusedMut, UnusedVariable, + GenericArgsProhibited, ]; #[derive(Debug)] @@ -388,6 +400,12 @@ pub struct InvalidCast { pub cast_ty: Type, } +#[derive(Debug)] +pub struct GenericArgsProhibited { + pub args: InFile>>, + pub reason: GenericArgsProhibitedReason, +} + impl AnyDiagnostic { pub(crate) fn body_validation_diagnostic( db: &dyn HirDatabase, @@ -527,6 +545,7 @@ impl AnyDiagnostic { db: &dyn HirDatabase, def: DefWithBodyId, d: &InferenceDiagnostic, + outer_types_source_map: &TypesSourceMap, source_map: &hir_def::body::BodySourceMap, ) -> Option { let expr_syntax = |expr| { @@ -640,6 +659,36 @@ impl AnyDiagnostic { let cast_ty = Type::new(db, def, cast_ty.clone()); InvalidCast { expr, error: *error, expr_ty, cast_ty }.into() } + InferenceDiagnostic::TyDiagnostic { source, diag } => { + let source_map = match source { + InferenceTyDiagnosticSource::Body => &source_map.types, + InferenceTyDiagnosticSource::Signature => outer_types_source_map, + }; + let source = match diag.source { + Either::Left(type_ref_id) => { + let Ok(source) = source_map.type_syntax(type_ref_id) else { + stdx::never!("error on synthetic type syntax"); + return None; + }; + source + } + Either::Right(source) => source, + }; + let syntax = || source.value.to_node(&db.parse_or_expand(source.file_id)); + match diag.kind { + TyLoweringDiagnosticKind::GenericArgsProhibited { segment, reason } => { + let ast::Type::PathType(syntax) = syntax() else { return None }; + let segment = hir_segment_to_ast_segment(&syntax.path()?, segment)?; + let args = if let Some(generics) = segment.generic_arg_list() { + AstPtr::new(&generics).wrap_left() + } else { + AstPtr::new(&segment.param_list()?).wrap_right() + }; + let args = source.with_value(args); + GenericArgsProhibited { args, reason }.into() + } + } + } }) } } diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index dc1d1efe60..cd46276747 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -20,12 +20,11 @@ #![cfg_attr(feature = "in-rust-tree", feature(rustc_private))] #![recursion_limit = "512"] -mod semantics; -mod source_analyzer; - mod attrs; mod from_id; mod has_source; +mod semantics; +mod source_analyzer; pub mod db; pub mod diagnostics; @@ -54,6 +53,7 @@ use hir_def::{ path::ImportAlias, per_ns::PerNs, resolver::{HasResolver, Resolver}, + type_ref::TypesSourceMap, AssocItemId, AssocItemLoc, AttrDefId, CallableDefId, ConstId, ConstParamId, CrateRootModuleId, DefWithBodyId, EnumId, EnumVariantId, ExternCrateId, FunctionId, GenericDefId, GenericParamId, HasModule, ImplId, InTypeConstId, ItemContainerId, LifetimeParamId, LocalFieldId, Lookup, @@ -1802,6 +1802,25 @@ impl DefWithBody { let krate = self.module(db).id.krate(); let (body, source_map) = db.body_with_source_map(self.into()); + let item_tree_source_maps; + let outer_types_source_map = match self { + DefWithBody::Function(function) => { + let function = function.id.lookup(db.upcast()).id; + item_tree_source_maps = function.item_tree_with_source_map(db.upcast()).1; + item_tree_source_maps.function(function.value).item() + } + DefWithBody::Static(statik) => { + let statik = statik.id.lookup(db.upcast()).id; + item_tree_source_maps = statik.item_tree_with_source_map(db.upcast()).1; + item_tree_source_maps.statik(statik.value) + } + DefWithBody::Const(konst) => { + let konst = konst.id.lookup(db.upcast()).id; + item_tree_source_maps = konst.item_tree_with_source_map(db.upcast()).1; + item_tree_source_maps.konst(konst.value) + } + DefWithBody::Variant(_) | DefWithBody::InTypeConst(_) => &TypesSourceMap::EMPTY, + }; for (_, def_map) in body.blocks(db.upcast()) { Module { id: def_map.module_id(DefMap::ROOT) }.diagnostics(db, acc, style_lints); @@ -1861,7 +1880,13 @@ impl DefWithBody { let infer = db.infer(self.into()); for d in &infer.diagnostics { - acc.extend(AnyDiagnostic::inference_diagnostic(db, self.into(), d, &source_map)); + acc.extend(AnyDiagnostic::inference_diagnostic( + db, + self.into(), + d, + outer_types_source_map, + &source_map, + )); } for (pat_or_expr, mismatch) in infer.type_mismatches() { diff --git a/crates/ide-diagnostics/src/handlers/generic_args_prohibited.rs b/crates/ide-diagnostics/src/handlers/generic_args_prohibited.rs new file mode 100644 index 0000000000..c3ad6704b6 --- /dev/null +++ b/crates/ide-diagnostics/src/handlers/generic_args_prohibited.rs @@ -0,0 +1,242 @@ +use either::Either; +use hir::GenericArgsProhibitedReason; +use ide_db::assists::Assist; +use ide_db::source_change::SourceChange; +use ide_db::text_edit::TextEdit; +use syntax::{ast, AstNode, TextRange}; + +use crate::{fix, Diagnostic, DiagnosticCode, DiagnosticsContext}; + +// Diagnostic: generic-args-prohibited +// +// This diagnostic is shown when generic arguments are provided for a type that does not accept +// generic arguments. +pub(crate) fn generic_args_prohibited( + ctx: &DiagnosticsContext<'_>, + d: &hir::GenericArgsProhibited, +) -> Diagnostic { + Diagnostic::new_with_syntax_node_ptr( + ctx, + DiagnosticCode::RustcHardError("E0109"), + describe_reason(d.reason), + d.args.map(Into::into), + ) + .with_fixes(fixes(ctx, d)) +} + +fn describe_reason(reason: GenericArgsProhibitedReason) -> String { + let kind = match reason { + GenericArgsProhibitedReason::Module => "modules", + GenericArgsProhibitedReason::TyParam => "type parameters", + GenericArgsProhibitedReason::SelfTy => "`Self`", + GenericArgsProhibitedReason::PrimitiveTy => "builtin types", + GenericArgsProhibitedReason::EnumVariant => { + return "you can specify generic arguments on either the enum or the variant, but not both" + .to_owned(); + } + }; + format!("generic arguments are not allowed on {kind}") +} + +fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::GenericArgsProhibited) -> Option> { + let file_id = d.args.file_id.file_id()?; + let syntax = d.args.to_node(ctx.sema.db); + let range = match &syntax { + Either::Left(_) => syntax.syntax().text_range(), + Either::Right(param_list) => { + let path_segment = ast::PathSegment::cast(param_list.syntax().parent()?)?; + let start = if let Some(coloncolon) = path_segment.coloncolon_token() { + coloncolon.text_range().start() + } else { + param_list.syntax().text_range().start() + }; + let end = if let Some(ret_type) = path_segment.ret_type() { + ret_type.syntax().text_range().end() + } else { + param_list.syntax().text_range().end() + }; + TextRange::new(start, end) + } + }; + Some(vec![fix( + "remove_generic_args", + "Remove these generics", + SourceChange::from_text_edit(file_id, TextEdit::delete(range)), + syntax.syntax().text_range(), + )]) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_diagnostics, check_fix}; + + #[test] + fn primitives() { + check_diagnostics( + r#" +//- /core.rs crate:core library +#![rustc_coherence_is_core] +impl str { + pub fn trim() {} +} + +//- /lib.rs crate:foo deps:core +fn bar() {} + +fn foo() { + let _: (bool<()>, ()); + // ^^^^ 💡 error: generic arguments are not allowed on builtin types + let _ = >::trim; + // ^^^^ 💡 error: generic arguments are not allowed on builtin types + bar::>(); + // ^^^^^^^^^^^^^^^^^^^^^ 💡 error: generic arguments are not allowed on builtin types +} + "#, + ); + } + + #[test] + fn modules() { + check_diagnostics( + r#" +pub mod foo { + pub mod bar { + pub struct Baz; + + impl Baz { + pub fn qux() {} + } + } +} + +fn foo() { + let _: foo::<'_>::bar::Baz; + // ^^^^^^ 💡 error: generic arguments are not allowed on modules + let _ = ::Baz>::qux; + // ^^^^ 💡 error: generic arguments are not allowed on modules +} + "#, + ); + } + + #[test] + fn type_parameters() { + check_diagnostics( + r#" +fn foo() { + let _: T<'a>; + // ^^^^ 💡 error: generic arguments are not allowed on type parameters + let _: U::<{ 1 + 2 }>; + // ^^^^^^^^^^^^^ 💡 error: generic arguments are not allowed on type parameters +} + "#, + ); + } + + #[test] + fn fn_like_generic_args() { + check_diagnostics( + r#" +fn foo() { + let _: bool(bool, i32) -> (); + // ^^^^^^^^^^^ 💡 error: generic arguments are not allowed on builtin types +} + "#, + ); + } + + #[test] + fn fn_signature() { + check_diagnostics( + r#" +fn foo( + _a: bool<'_>, + // ^^^^ 💡 error: generic arguments are not allowed on builtin types + _b: i32::, + // ^^^^^^^ 💡 error: generic arguments are not allowed on builtin types + _c: &(&str<1>) + // ^^^ 💡 error: generic arguments are not allowed on builtin types +) -> ((), i32) { + // ^^^^^^ 💡 error: generic arguments are not allowed on builtin types + ((), 0) +} + "#, + ); + } + + #[test] + fn const_static_type() { + check_diagnostics( + r#" +const A: i32 = 0; + // ^^^^^^ 💡 error: generic arguments are not allowed on builtin types +static A: i32::<{ 1 + 3 }> = 0; + // ^^^^^^^^^^^^^ 💡 error: generic arguments are not allowed on builtin types + "#, + ); + } + + #[test] + fn fix() { + check_fix( + r#" +fn foo() { + let _: bool<'_, (), { 1 + 1 }>$0; +}"#, + r#" +fn foo() { + let _: bool; +}"#, + ); + check_fix( + r#" +fn foo() { + let _: bool::$0<'_, (), { 1 + 1 }>; +}"#, + r#" +fn foo() { + let _: bool; +}"#, + ); + check_fix( + r#" +fn foo() { + let _: bool(i$032); +}"#, + r#" +fn foo() { + let _: bool; +}"#, + ); + check_fix( + r#" +fn foo() { + let _: bool$0(i32) -> i64; +}"#, + r#" +fn foo() { + let _: bool; +}"#, + ); + check_fix( + r#" +fn foo() { + let _: bool::(i$032) -> i64; +}"#, + r#" +fn foo() { + let _: bool; +}"#, + ); + check_fix( + r#" +fn foo() { + let _: bool::(i32)$0; +}"#, + r#" +fn foo() { + let _: bool; +}"#, + ); + } +} diff --git a/crates/ide-diagnostics/src/handlers/unresolved_method.rs b/crates/ide-diagnostics/src/handlers/unresolved_method.rs index 81cb452121..4ab649cc16 100644 --- a/crates/ide-diagnostics/src/handlers/unresolved_method.rs +++ b/crates/ide-diagnostics/src/handlers/unresolved_method.rs @@ -167,9 +167,9 @@ fn assoc_func_fix(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedMethodCall) - } let method_name = call.name_ref()?; - let assoc_func_call = format!("{receiver_type_adt_name}::{method_name}()"); + let assoc_func_path = format!("{receiver_type_adt_name}::{method_name}"); - let assoc_func_call = make::expr_path(make::path_from_text(&assoc_func_call)); + let assoc_func_path = make::expr_path(make::path_from_text(&assoc_func_path)); let args: Vec<_> = if need_to_take_receiver_as_first_arg { std::iter::once(receiver).chain(call.arg_list()?.args()).collect() @@ -178,7 +178,7 @@ fn assoc_func_fix(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedMethodCall) - }; let args = make::arg_list(args); - let assoc_func_call_expr_string = make::expr_call(assoc_func_call, args).to_string(); + let assoc_func_call_expr_string = make::expr_call(assoc_func_path, args).to_string(); let file_id = ctx.sema.original_range_opt(call.receiver()?.syntax())?.file_id; diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs index 1f1b6478d3..663fc6a677 100644 --- a/crates/ide-diagnostics/src/lib.rs +++ b/crates/ide-diagnostics/src/lib.rs @@ -27,6 +27,7 @@ mod handlers { pub(crate) mod await_outside_of_async; pub(crate) mod break_outside_of_loop; pub(crate) mod expected_function; + pub(crate) mod generic_args_prohibited; pub(crate) mod inactive_code; pub(crate) mod incoherent_impl; pub(crate) mod incorrect_case; @@ -468,6 +469,7 @@ pub fn semantic_diagnostics( Some(it) => it, None => continue, }, + AnyDiagnostic::GenericArgsProhibited(d) => handlers::generic_args_prohibited::generic_args_prohibited(&ctx, &d) }; res.push(d) } diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs index 2ec83d23b2..7eb3e08f54 100644 --- a/crates/syntax/src/ast/make.rs +++ b/crates/syntax/src/ast/make.rs @@ -402,7 +402,7 @@ pub fn join_paths(paths: impl IntoIterator) -> ast::Path { // FIXME: should not be pub pub fn path_from_text(text: &str) -> ast::Path { - ast_from_text(&format!("fn main() {{ let test = {text}; }}")) + ast_from_text(&format!("fn main() {{ let test: {text}; }}")) } pub fn use_tree_glob() -> ast::UseTree { From 21ad3b5b878478e7952c4c38ce39ad482ae8790c Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Thu, 21 Nov 2024 02:21:16 +0200 Subject: [PATCH 29/95] Complete diagnostics in ty lowering groundwork Implement diagnostics in all places left: generics (predicates, defaults, const params' types), fields, and type aliases. Unfortunately this results in a 20mb addition in `analysis-stats .` due to many type methods returning an addition diagnostics result now (even if it's `None` in most cases). I'm not sure if this can be improved. An alternative strategy that can prevent the memory usage growth is to never produce diagnostics in hir-ty methods. Instead, lower all types in the hir crate when computing diagnostics from scratch (with diagnostics this time). But this has two serious disadvantages: 1. This can cause code duplication (although it can probably be not that bad, it will still mean a lot more code). 2. I believe we eventually want to compute diagnostics for the *entire* workspace (either on-type or on-save or something alike), so users can know when they have diagnostics even in inactive files. Choosing this approach will mean we lose all precomputed salsa queries. For one file this is fine, for the whole workspace this will be very slow. --- crates/hir-def/src/generics.rs | 5 + crates/hir-ty/src/db.rs | 30 ++- crates/hir-ty/src/generics.rs | 16 +- crates/hir-ty/src/lib.rs | 3 +- crates/hir-ty/src/lower.rs | 213 ++++++++++++------ crates/hir/src/diagnostics.rs | 59 +++-- crates/hir/src/lib.rs | 198 +++++++++++++++- .../src/handlers/generic_args_prohibited.rs | 200 ++++++++++++++++ 8 files changed, 621 insertions(+), 103 deletions(-) diff --git a/crates/hir-def/src/generics.rs b/crates/hir-def/src/generics.rs index fdcb10e998..7b3f1d06d2 100644 --- a/crates/hir-def/src/generics.rs +++ b/crates/hir-def/src/generics.rs @@ -224,6 +224,11 @@ impl GenericParams { self.len() == 0 } + #[inline] + pub fn no_predicates(&self) -> bool { + self.where_predicates.is_empty() + } + #[inline] pub fn where_predicates(&self) -> std::slice::Iter<'_, WherePredicate> { self.where_predicates.iter() diff --git a/crates/hir-ty/src/db.rs b/crates/hir-ty/src/db.rs index 3a3a05c369..6856eaa3e0 100644 --- a/crates/hir-ty/src/db.rs +++ b/crates/hir-ty/src/db.rs @@ -22,7 +22,7 @@ use crate::{ consteval::ConstEvalError, dyn_compatibility::DynCompatibilityViolation, layout::{Layout, LayoutError}, - lower::{GenericDefaults, GenericPredicates}, + lower::{Diagnostics, GenericDefaults, GenericPredicates}, method_resolution::{InherentImpls, TraitImpls, TyFingerprint}, mir::{BorrowckResult, MirBody, MirLowerError}, Binders, ClosureId, Const, FnDefId, ImplTraitId, ImplTraits, InferenceResult, Interner, @@ -115,21 +115,35 @@ pub trait HirDatabase: DefDatabase + Upcast { #[ra_salsa::cycle(crate::lower::ty_recover)] fn ty(&self, def: TyDefId) -> Binders; + #[ra_salsa::invoke(crate::lower::type_for_type_alias_with_diagnostics_query)] + fn type_for_type_alias_with_diagnostics(&self, def: TypeAliasId) -> (Binders, Diagnostics); + /// Returns the type of the value of the given constant, or `None` if the `ValueTyDefId` is /// a `StructId` or `EnumVariantId` with a record constructor. #[ra_salsa::invoke(crate::lower::value_ty_query)] fn value_ty(&self, def: ValueTyDefId) -> Option>; + #[ra_salsa::invoke(crate::lower::impl_self_ty_with_diagnostics_query)] + #[ra_salsa::cycle(crate::lower::impl_self_ty_with_diagnostics_recover)] + fn impl_self_ty_with_diagnostics(&self, def: ImplId) -> (Binders, Diagnostics); #[ra_salsa::invoke(crate::lower::impl_self_ty_query)] - #[ra_salsa::cycle(crate::lower::impl_self_ty_recover)] fn impl_self_ty(&self, def: ImplId) -> Binders; + #[ra_salsa::invoke(crate::lower::const_param_ty_with_diagnostics_query)] + fn const_param_ty_with_diagnostics(&self, def: ConstParamId) -> (Ty, Diagnostics); #[ra_salsa::invoke(crate::lower::const_param_ty_query)] fn const_param_ty(&self, def: ConstParamId) -> Ty; + #[ra_salsa::invoke(crate::lower::impl_trait_with_diagnostics_query)] + fn impl_trait_with_diagnostics(&self, def: ImplId) -> Option<(Binders, Diagnostics)>; #[ra_salsa::invoke(crate::lower::impl_trait_query)] fn impl_trait(&self, def: ImplId) -> Option>; + #[ra_salsa::invoke(crate::lower::field_types_with_diagnostics_query)] + fn field_types_with_diagnostics( + &self, + var: VariantId, + ) -> (Arc>>, Diagnostics); #[ra_salsa::invoke(crate::lower::field_types_query)] fn field_types(&self, var: VariantId) -> Arc>>; @@ -154,6 +168,11 @@ pub trait HirDatabase: DefDatabase + Upcast { #[ra_salsa::invoke(crate::lower::generic_predicates_query)] fn generic_predicates(&self, def: GenericDefId) -> GenericPredicates; + #[ra_salsa::invoke(crate::lower::generic_predicates_without_parent_with_diagnostics_query)] + fn generic_predicates_without_parent_with_diagnostics( + &self, + def: GenericDefId, + ) -> (GenericPredicates, Diagnostics); #[ra_salsa::invoke(crate::lower::generic_predicates_without_parent_query)] fn generic_predicates_without_parent(&self, def: GenericDefId) -> GenericPredicates; @@ -164,8 +183,13 @@ pub trait HirDatabase: DefDatabase + Upcast { #[ra_salsa::invoke(crate::lower::trait_environment_query)] fn trait_environment(&self, def: GenericDefId) -> Arc; + #[ra_salsa::invoke(crate::lower::generic_defaults_with_diagnostics_query)] + #[ra_salsa::cycle(crate::lower::generic_defaults_with_diagnostics_recover)] + fn generic_defaults_with_diagnostics( + &self, + def: GenericDefId, + ) -> (GenericDefaults, Diagnostics); #[ra_salsa::invoke(crate::lower::generic_defaults_query)] - #[ra_salsa::cycle(crate::lower::generic_defaults_recover)] fn generic_defaults(&self, def: GenericDefId) -> GenericDefaults; #[ra_salsa::invoke(InherentImpls::inherent_impls_in_crate_query)] diff --git a/crates/hir-ty/src/generics.rs b/crates/hir-ty/src/generics.rs index c094bc3951..fe7541d237 100644 --- a/crates/hir-ty/src/generics.rs +++ b/crates/hir-ty/src/generics.rs @@ -55,6 +55,10 @@ impl Generics { self.def } + pub(crate) fn self_types_map(&self) -> &TypesMap { + &self.params.types_map + } + pub(crate) fn iter_id(&self) -> impl Iterator + '_ { self.iter_self_id().chain(self.iter_parent_id()) } @@ -86,15 +90,13 @@ impl Generics { self.iter_self().chain(self.iter_parent()) } - pub(crate) fn iter_with_types_map( + pub(crate) fn iter_parents_with_types_map( &self, ) -> impl Iterator), &TypesMap)> + '_ { - self.iter_self().zip(std::iter::repeat(&self.params.types_map)).chain( - self.iter_parent().zip( - self.parent_generics() - .into_iter() - .flat_map(|it| std::iter::repeat(&it.params.types_map)), - ), + self.iter_parent().zip( + self.parent_generics() + .into_iter() + .flat_map(|it| std::iter::repeat(&it.params.types_map)), ) } diff --git a/crates/hir-ty/src/lib.rs b/crates/hir-ty/src/lib.rs index 77ae295cee..8bb90ca31e 100644 --- a/crates/hir-ty/src/lib.rs +++ b/crates/hir-ty/src/lib.rs @@ -90,7 +90,8 @@ pub use infer::{ pub use interner::Interner; pub use lower::{ associated_type_shorthand_candidates, GenericArgsProhibitedReason, ImplTraitLoweringMode, - ParamLoweringMode, TyDefId, TyLoweringContext, TyLoweringDiagnosticKind, ValueTyDefId, + ParamLoweringMode, TyDefId, TyLoweringContext, TyLoweringDiagnostic, TyLoweringDiagnosticKind, + ValueTyDefId, }; pub use mapping::{ from_assoc_type_id, from_chalk_trait_id, from_foreign_def_id, from_placeholder_idx, diff --git a/crates/hir-ty/src/lower.rs b/crates/hir-ty/src/lower.rs index b73967be71..b23f2749ab 100644 --- a/crates/hir-ty/src/lower.rs +++ b/crates/hir-ty/src/lower.rs @@ -48,7 +48,7 @@ use rustc_pattern_analysis::Captures; use smallvec::SmallVec; use stdx::{impl_from, never}; use syntax::ast; -use triomphe::Arc; +use triomphe::{Arc, ThinArc}; use crate::{ all_super_traits, @@ -1652,11 +1652,24 @@ fn named_associated_type_shorthand_candidates( } } -/// Build the type of all specific fields of a struct or enum variant. +pub(crate) type Diagnostics = Option>; + +fn create_diagnostics(diagnostics: Vec) -> Diagnostics { + (!diagnostics.is_empty()).then(|| ThinArc::from_header_and_iter((), diagnostics.into_iter())) +} + pub(crate) fn field_types_query( db: &dyn HirDatabase, variant_id: VariantId, ) -> Arc>> { + db.field_types_with_diagnostics(variant_id).0 +} + +/// Build the type of all specific fields of a struct or enum variant. +pub(crate) fn field_types_with_diagnostics_query( + db: &dyn HirDatabase, + variant_id: VariantId, +) -> (Arc>>, Diagnostics) { let var_data = variant_id.variant_data(db.upcast()); let (resolver, def): (_, GenericDefId) = match variant_id { VariantId::StructId(it) => (it.resolver(db.upcast()), it.into()), @@ -1672,7 +1685,7 @@ pub(crate) fn field_types_query( for (field_id, field_data) in var_data.fields().iter() { res.insert(field_id, make_binders(db, &generics, ctx.lower_ty(field_data.type_ref))); } - Arc::new(res) + (Arc::new(res), create_diagnostics(ctx.diagnostics)) } /// This query exists only to be used when resolving short-hand associated types @@ -1873,15 +1886,22 @@ pub(crate) fn generic_predicates_query( db: &dyn HirDatabase, def: GenericDefId, ) -> GenericPredicates { - generic_predicates_filtered_by(db, def, |_, _| true) + generic_predicates_filtered_by(db, def, |_, _| true).0 } -/// Resolve the where clause(s) of an item with generics, -/// except the ones inherited from the parent pub(crate) fn generic_predicates_without_parent_query( db: &dyn HirDatabase, def: GenericDefId, ) -> GenericPredicates { + db.generic_predicates_without_parent_with_diagnostics(def).0 +} + +/// Resolve the where clause(s) of an item with generics, +/// except the ones inherited from the parent +pub(crate) fn generic_predicates_without_parent_with_diagnostics_query( + db: &dyn HirDatabase, + def: GenericDefId, +) -> (GenericPredicates, Diagnostics) { generic_predicates_filtered_by(db, def, |_, d| *d == def) } @@ -1891,7 +1911,7 @@ fn generic_predicates_filtered_by( db: &dyn HirDatabase, def: GenericDefId, filter: F, -) -> GenericPredicates +) -> (GenericPredicates, Diagnostics) where F: Fn(&WherePredicate, &GenericDefId) -> bool, { @@ -1932,7 +1952,10 @@ where ); }; } - GenericPredicates(predicates.is_empty().not().then(|| predicates.into())) + ( + GenericPredicates(predicates.is_empty().not().then(|| predicates.into())), + create_diagnostics(ctx.diagnostics), + ) } /// Generate implicit `: Sized` predicates for all generics that has no `?Sized` bound. @@ -1985,75 +2008,110 @@ impl ops::Deref for GenericDefaults { } } -/// Resolve the default type params from generics pub(crate) fn generic_defaults_query(db: &dyn HirDatabase, def: GenericDefId) -> GenericDefaults { + db.generic_defaults_with_diagnostics(def).0 +} + +/// Resolve the default type params from generics. +/// +/// Diagnostics are only returned for this `GenericDefId` (returned defaults include parents). +pub(crate) fn generic_defaults_with_diagnostics_query( + db: &dyn HirDatabase, + def: GenericDefId, +) -> (GenericDefaults, Diagnostics) { let generic_params = generics(db.upcast(), def); if generic_params.len() == 0 { - return GenericDefaults(None); + return (GenericDefaults(None), None); } let resolver = def.resolver(db.upcast()); let parent_start_idx = generic_params.len_self(); - let mut ctx = TyLoweringContext::new(db, &resolver, TypesMap::EMPTY, def.into()) - .with_impl_trait_mode(ImplTraitLoweringMode::Disallowed) - .with_type_param_mode(ParamLoweringMode::Variable); - GenericDefaults(Some(Arc::from_iter(generic_params.iter_with_types_map().enumerate().map( - |(idx, ((id, p), types_map))| { - ctx.types_map = types_map; - match p { - GenericParamDataRef::TypeParamData(p) => { - let ty = p.default.as_ref().map_or(TyKind::Error.intern(Interner), |ty| { - // Each default can only refer to previous parameters. - // Type variable default referring to parameter coming - // after it is forbidden (FIXME: report diagnostic) - fallback_bound_vars(ctx.lower_ty(*ty), idx, parent_start_idx) - }); - crate::make_binders(db, &generic_params, ty.cast(Interner)) - } - GenericParamDataRef::ConstParamData(p) => { - let GenericParamId::ConstParamId(id) = id else { - unreachable!("Unexpected lifetime or type argument") - }; + let mut ctx = + TyLoweringContext::new(db, &resolver, generic_params.self_types_map(), def.into()) + .with_impl_trait_mode(ImplTraitLoweringMode::Disallowed) + .with_type_param_mode(ParamLoweringMode::Variable); + let mut idx = 0; + let mut defaults = generic_params + .iter_self() + .map(|(id, p)| { + let result = + handle_generic_param(&mut ctx, idx, id, p, parent_start_idx, &generic_params); + idx += 1; + result + }) + .collect::>(); + let diagnostics = create_diagnostics(mem::take(&mut ctx.diagnostics)); + defaults.extend(generic_params.iter_parents_with_types_map().map(|((id, p), types_map)| { + ctx.types_map = types_map; + let result = handle_generic_param(&mut ctx, idx, id, p, parent_start_idx, &generic_params); + idx += 1; + result + })); + let defaults = GenericDefaults(Some(Arc::from_iter(defaults))); + return (defaults, diagnostics); - let mut val = p.default.as_ref().map_or_else( - || unknown_const_as_generic(db.const_param_ty(id)), - |c| { - let param_ty = ctx.lower_ty(p.ty); - let c = ctx.lower_const(c, param_ty); - c.cast(Interner) - }, - ); - // Each default can only refer to previous parameters, see above. - val = fallback_bound_vars(val, idx, parent_start_idx); - make_binders(db, &generic_params, val) - } - GenericParamDataRef::LifetimeParamData(_) => { - make_binders(db, &generic_params, error_lifetime().cast(Interner)) - } + fn handle_generic_param( + ctx: &mut TyLoweringContext<'_>, + idx: usize, + id: GenericParamId, + p: GenericParamDataRef<'_>, + parent_start_idx: usize, + generic_params: &Generics, + ) -> Binders { + match p { + GenericParamDataRef::TypeParamData(p) => { + let ty = p.default.as_ref().map_or(TyKind::Error.intern(Interner), |ty| { + // Each default can only refer to previous parameters. + // Type variable default referring to parameter coming + // after it is forbidden (FIXME: report diagnostic) + fallback_bound_vars(ctx.lower_ty(*ty), idx, parent_start_idx) + }); + crate::make_binders(ctx.db, generic_params, ty.cast(Interner)) } - }, - )))) + GenericParamDataRef::ConstParamData(p) => { + let GenericParamId::ConstParamId(id) = id else { + unreachable!("Unexpected lifetime or type argument") + }; + + let mut val = p.default.as_ref().map_or_else( + || unknown_const_as_generic(ctx.db.const_param_ty(id)), + |c| { + let param_ty = ctx.lower_ty(p.ty); + let c = ctx.lower_const(c, param_ty); + c.cast(Interner) + }, + ); + // Each default can only refer to previous parameters, see above. + val = fallback_bound_vars(val, idx, parent_start_idx); + make_binders(ctx.db, generic_params, val) + } + GenericParamDataRef::LifetimeParamData(_) => { + make_binders(ctx.db, generic_params, error_lifetime().cast(Interner)) + } + } + } } -pub(crate) fn generic_defaults_recover( +pub(crate) fn generic_defaults_with_diagnostics_recover( db: &dyn HirDatabase, _cycle: &Cycle, def: &GenericDefId, -) -> GenericDefaults { +) -> (GenericDefaults, Diagnostics) { let generic_params = generics(db.upcast(), *def); if generic_params.len() == 0 { - return GenericDefaults(None); + return (GenericDefaults(None), None); } // FIXME: this code is not covered in tests. // we still need one default per parameter - GenericDefaults(Some(Arc::from_iter(generic_params.iter_id().map(|id| { + let defaults = GenericDefaults(Some(Arc::from_iter(generic_params.iter_id().map(|id| { let val = match id { GenericParamId::TypeParamId(_) => TyKind::Error.intern(Interner).cast(Interner), GenericParamId::ConstParamId(id) => unknown_const_as_generic(db.const_param_ty(id)), GenericParamId::LifetimeParamId(_) => error_lifetime().cast(Interner), }; crate::make_binders(db, &generic_params, val) - })))) + })))); + (defaults, None) } fn fn_sig_for_fn(db: &dyn HirDatabase, def: FunctionId) -> PolyFnSig { @@ -2196,7 +2254,10 @@ fn type_for_adt(db: &dyn HirDatabase, adt: AdtId) -> Binders { make_binders(db, &generics, ty) } -fn type_for_type_alias(db: &dyn HirDatabase, t: TypeAliasId) -> Binders { +pub(crate) fn type_for_type_alias_with_diagnostics_query( + db: &dyn HirDatabase, + t: TypeAliasId, +) -> (Binders, Diagnostics) { let generics = generics(db.upcast(), t.into()); let resolver = t.resolver(db.upcast()); let type_alias_data = db.type_alias_data(t); @@ -2211,7 +2272,7 @@ fn type_for_type_alias(db: &dyn HirDatabase, t: TypeAliasId) -> Binders { .map(|type_ref| ctx.lower_ty(type_ref)) .unwrap_or_else(|| TyKind::Error.intern(Interner)) }; - make_binders(db, &generics, inner) + (make_binders(db, &generics, inner), create_diagnostics(ctx.diagnostics)) } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -2254,7 +2315,7 @@ pub(crate) fn ty_query(db: &dyn HirDatabase, def: TyDefId) -> Binders { match def { TyDefId::BuiltinType(it) => Binders::empty(Interner, TyBuilder::builtin(it)), TyDefId::AdtId(it) => type_for_adt(db, it), - TyDefId::TypeAliasId(it) => type_for_type_alias(db, it), + TyDefId::TypeAliasId(it) => db.type_for_type_alias_with_diagnostics(it).0, } } @@ -2279,47 +2340,73 @@ pub(crate) fn value_ty_query(db: &dyn HirDatabase, def: ValueTyDefId) -> Option< } pub(crate) fn impl_self_ty_query(db: &dyn HirDatabase, impl_id: ImplId) -> Binders { + db.impl_self_ty_with_diagnostics(impl_id).0 +} + +pub(crate) fn impl_self_ty_with_diagnostics_query( + db: &dyn HirDatabase, + impl_id: ImplId, +) -> (Binders, Diagnostics) { let impl_data = db.impl_data(impl_id); let resolver = impl_id.resolver(db.upcast()); let generics = generics(db.upcast(), impl_id.into()); let mut ctx = TyLoweringContext::new(db, &resolver, &impl_data.types_map, impl_id.into()) .with_type_param_mode(ParamLoweringMode::Variable); - make_binders(db, &generics, ctx.lower_ty(impl_data.self_ty)) + ( + make_binders(db, &generics, ctx.lower_ty(impl_data.self_ty)), + create_diagnostics(ctx.diagnostics), + ) +} + +pub(crate) fn const_param_ty_query(db: &dyn HirDatabase, def: ConstParamId) -> Ty { + db.const_param_ty_with_diagnostics(def).0 } // returns None if def is a type arg -pub(crate) fn const_param_ty_query(db: &dyn HirDatabase, def: ConstParamId) -> Ty { +pub(crate) fn const_param_ty_with_diagnostics_query( + db: &dyn HirDatabase, + def: ConstParamId, +) -> (Ty, Diagnostics) { let parent_data = db.generic_params(def.parent()); let data = &parent_data[def.local_id()]; let resolver = def.parent().resolver(db.upcast()); let mut ctx = TyLoweringContext::new(db, &resolver, &parent_data.types_map, def.parent().into()); - match data { + let ty = match data { TypeOrConstParamData::TypeParamData(_) => { never!(); Ty::new(Interner, TyKind::Error) } TypeOrConstParamData::ConstParamData(d) => ctx.lower_ty(d.ty), - } + }; + (ty, create_diagnostics(ctx.diagnostics)) } -pub(crate) fn impl_self_ty_recover( +pub(crate) fn impl_self_ty_with_diagnostics_recover( db: &dyn HirDatabase, _cycle: &Cycle, impl_id: &ImplId, -) -> Binders { +) -> (Binders, Diagnostics) { let generics = generics(db.upcast(), (*impl_id).into()); - make_binders(db, &generics, TyKind::Error.intern(Interner)) + (make_binders(db, &generics, TyKind::Error.intern(Interner)), None) } pub(crate) fn impl_trait_query(db: &dyn HirDatabase, impl_id: ImplId) -> Option> { + db.impl_trait_with_diagnostics(impl_id).map(|it| it.0) +} + +pub(crate) fn impl_trait_with_diagnostics_query( + db: &dyn HirDatabase, + impl_id: ImplId, +) -> Option<(Binders, Diagnostics)> { let impl_data = db.impl_data(impl_id); let resolver = impl_id.resolver(db.upcast()); let mut ctx = TyLoweringContext::new(db, &resolver, &impl_data.types_map, impl_id.into()) .with_type_param_mode(ParamLoweringMode::Variable); let (self_ty, binders) = db.impl_self_ty(impl_id).into_value_and_skipped_binders(); let target_trait = impl_data.target_trait.as_ref()?; - Some(Binders::new(binders, ctx.lower_trait_ref(target_trait, self_ty)?)) + let trait_ref = Binders::new(binders, ctx.lower_trait_ref(target_trait, self_ty)?); + Some((trait_ref, create_diagnostics(ctx.diagnostics))) } pub(crate) fn return_type_impl_traits( diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index 3657cf5e16..612c6adb20 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -15,7 +15,8 @@ use hir_expand::{name::Name, HirFileId, InFile}; use hir_ty::{ db::HirDatabase, diagnostics::{BodyValidationDiagnostic, UnsafetyReason}, - CastError, InferenceDiagnostic, InferenceTyDiagnosticSource, TyLoweringDiagnosticKind, + CastError, InferenceDiagnostic, InferenceTyDiagnosticSource, TyLoweringDiagnostic, + TyLoweringDiagnosticKind, }; use syntax::{ ast::{self, HasGenericArgs}, @@ -402,7 +403,7 @@ pub struct InvalidCast { #[derive(Debug)] pub struct GenericArgsProhibited { - pub args: InFile>>, + pub args: InFile>>, pub reason: GenericArgsProhibitedReason, } @@ -664,30 +665,38 @@ impl AnyDiagnostic { InferenceTyDiagnosticSource::Body => &source_map.types, InferenceTyDiagnosticSource::Signature => outer_types_source_map, }; - let source = match diag.source { - Either::Left(type_ref_id) => { - let Ok(source) = source_map.type_syntax(type_ref_id) else { - stdx::never!("error on synthetic type syntax"); - return None; - }; - source - } - Either::Right(source) => source, + Self::ty_diagnostic(diag, source_map, db)? + } + }) + } + + pub(crate) fn ty_diagnostic( + diag: &TyLoweringDiagnostic, + source_map: &TypesSourceMap, + db: &dyn HirDatabase, + ) -> Option { + let source = match diag.source { + Either::Left(type_ref_id) => { + let Ok(source) = source_map.type_syntax(type_ref_id) else { + stdx::never!("error on synthetic type syntax"); + return None; }; - let syntax = || source.value.to_node(&db.parse_or_expand(source.file_id)); - match diag.kind { - TyLoweringDiagnosticKind::GenericArgsProhibited { segment, reason } => { - let ast::Type::PathType(syntax) = syntax() else { return None }; - let segment = hir_segment_to_ast_segment(&syntax.path()?, segment)?; - let args = if let Some(generics) = segment.generic_arg_list() { - AstPtr::new(&generics).wrap_left() - } else { - AstPtr::new(&segment.param_list()?).wrap_right() - }; - let args = source.with_value(args); - GenericArgsProhibited { args, reason }.into() - } - } + source + } + Either::Right(source) => source, + }; + let syntax = || source.value.to_node(&db.parse_or_expand(source.file_id)); + Some(match diag.kind { + TyLoweringDiagnosticKind::GenericArgsProhibited { segment, reason } => { + let ast::Type::PathType(syntax) = syntax() else { return None }; + let segment = hir_segment_to_ast_segment(&syntax.path()?, segment)?; + let args = if let Some(generics) = segment.generic_arg_list() { + AstPtr::new(&generics).wrap_left() + } else { + AstPtr::new(&segment.parenthesized_arg_list()?).wrap_right() + }; + let args = source.with_value(args); + GenericArgsProhibited { args, reason }.into() } }) } diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index cd46276747..10365ba002 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -76,8 +76,8 @@ use hir_ty::{ traits::FnTrait, AliasTy, CallableSig, Canonical, CanonicalVarKinds, Cast, ClosureId, GenericArg, GenericArgData, Interner, ParamKind, QuantifiedWhereClause, Scalar, Substitution, - TraitEnvironment, TraitRefExt, Ty, TyBuilder, TyDefId, TyExt, TyKind, ValueTyDefId, - WhereClause, + TraitEnvironment, TraitRefExt, Ty, TyBuilder, TyDefId, TyExt, TyKind, TyLoweringDiagnostic, + ValueTyDefId, WhereClause, }; use itertools::Itertools; use nameres::diagnostics::DefDiagnosticKind; @@ -89,7 +89,7 @@ use syntax::{ ast::{self, HasAttrs as _, HasGenericParams, HasName}, format_smolstr, AstNode, AstPtr, SmolStr, SyntaxNode, SyntaxNodePtr, TextRange, ToSmolStr, T, }; -use triomphe::Arc; +use triomphe::{Arc, ThinArc}; use crate::db::{DefDatabase, HirDatabase}; @@ -411,6 +411,10 @@ impl ModuleDef { } } + if let Some(def) = self.as_self_generic_def() { + def.diagnostics(db, &mut acc); + } + acc } @@ -431,6 +435,23 @@ impl ModuleDef { } } + /// Returns only defs that have generics from themselves, not their parent. + pub fn as_self_generic_def(self) -> Option { + match self { + ModuleDef::Function(it) => Some(it.into()), + ModuleDef::Adt(it) => Some(it.into()), + ModuleDef::Trait(it) => Some(it.into()), + ModuleDef::TraitAlias(it) => Some(it.into()), + ModuleDef::TypeAlias(it) => Some(it.into()), + ModuleDef::Module(_) + | ModuleDef::Variant(_) + | ModuleDef::Static(_) + | ModuleDef::Const(_) + | ModuleDef::BuiltinType(_) + | ModuleDef::Macro(_) => None, + } + } + pub fn attrs(&self, db: &dyn HirDatabase) -> Option { Some(match self { ModuleDef::Module(it) => it.attrs(db), @@ -605,17 +626,42 @@ impl Module { ModuleDef::Adt(adt) => { match adt { Adt::Struct(s) => { + let tree_id = s.id.lookup(db.upcast()).id; + let tree_source_maps = tree_id.item_tree_with_source_map(db.upcast()).1; + push_ty_diagnostics( + db, + acc, + db.field_types_with_diagnostics(s.id.into()).1, + tree_source_maps.strukt(tree_id.value).item(), + ); for diag in db.struct_data_with_diagnostics(s.id).1.iter() { emit_def_diagnostic(db, acc, diag, edition); } } Adt::Union(u) => { + let tree_id = u.id.lookup(db.upcast()).id; + let tree_source_maps = tree_id.item_tree_with_source_map(db.upcast()).1; + push_ty_diagnostics( + db, + acc, + db.field_types_with_diagnostics(u.id.into()).1, + tree_source_maps.union(tree_id.value).item(), + ); for diag in db.union_data_with_diagnostics(u.id).1.iter() { emit_def_diagnostic(db, acc, diag, edition); } } Adt::Enum(e) => { for v in e.variants(db) { + let tree_id = v.id.lookup(db.upcast()).id; + let tree_source_maps = + tree_id.item_tree_with_source_map(db.upcast()).1; + push_ty_diagnostics( + db, + acc, + db.field_types_with_diagnostics(v.id.into()).1, + tree_source_maps.variant(tree_id.value), + ); acc.extend(ModuleDef::Variant(v).diagnostics(db, style_lints)); for diag in db.enum_variant_data_with_diagnostics(v.id).1.iter() { emit_def_diagnostic(db, acc, diag, edition); @@ -626,6 +672,17 @@ impl Module { acc.extend(def.diagnostics(db, style_lints)) } ModuleDef::Macro(m) => emit_macro_def_diagnostics(db, acc, m), + ModuleDef::TypeAlias(type_alias) => { + let tree_id = type_alias.id.lookup(db.upcast()).id; + let tree_source_maps = tree_id.item_tree_with_source_map(db.upcast()).1; + push_ty_diagnostics( + db, + acc, + db.type_for_type_alias_with_diagnostics(type_alias.id).1, + tree_source_maps.type_alias(tree_id.value).item(), + ); + acc.extend(def.diagnostics(db, style_lints)); + } _ => acc.extend(def.diagnostics(db, style_lints)), } } @@ -635,8 +692,11 @@ impl Module { let mut impl_assoc_items_scratch = vec![]; for impl_def in self.impl_defs(db) { + GenericDef::Impl(impl_def).diagnostics(db, acc); + let loc = impl_def.id.lookup(db.upcast()); - let tree = loc.id.item_tree(db.upcast()); + let (tree, tree_source_maps) = loc.id.item_tree_with_source_map(db.upcast()); + let source_map = tree_source_maps.impl_(loc.id.value).item(); let node = &tree[loc.id.value]; let file_id = loc.id.file_id(); if file_id.macro_file().map_or(false, |it| it.is_builtin_derive(db.upcast())) { @@ -771,6 +831,19 @@ impl Module { impl_assoc_items_scratch.clear(); } + push_ty_diagnostics( + db, + acc, + db.impl_self_ty_with_diagnostics(impl_def.id).1, + source_map, + ); + push_ty_diagnostics( + db, + acc, + db.impl_trait_with_diagnostics(impl_def.id).and_then(|it| it.1), + source_map, + ); + for &item in db.impl_data(impl_def.id).items.iter() { AssocItem::from(item).diagnostics(db, acc, style_lints); } @@ -3350,12 +3423,22 @@ impl AssocItem { ) { match self { AssocItem::Function(func) => { + GenericDef::Function(func).diagnostics(db, acc); DefWithBody::from(func).diagnostics(db, acc, style_lints); } AssocItem::Const(const_) => { DefWithBody::from(const_).diagnostics(db, acc, style_lints); } AssocItem::TypeAlias(type_alias) => { + GenericDef::TypeAlias(type_alias).diagnostics(db, acc); + let tree_id = type_alias.id.lookup(db.upcast()).id; + let tree_source_maps = tree_id.item_tree_with_source_map(db.upcast()).1; + push_ty_diagnostics( + db, + acc, + db.type_for_type_alias_with_diagnostics(type_alias.id).1, + tree_source_maps.type_alias(tree_id.value).item(), + ); for diag in hir_ty::diagnostics::incorrect_case(db, type_alias.id.into()) { acc.push(diag.into()); } @@ -3442,6 +3525,97 @@ impl GenericDef { }) .collect() } + + fn id(self) -> GenericDefId { + match self { + GenericDef::Function(it) => it.id.into(), + GenericDef::Adt(it) => it.into(), + GenericDef::Trait(it) => it.id.into(), + GenericDef::TraitAlias(it) => it.id.into(), + GenericDef::TypeAlias(it) => it.id.into(), + GenericDef::Impl(it) => it.id.into(), + GenericDef::Const(it) => it.id.into(), + } + } + + pub fn diagnostics(self, db: &dyn HirDatabase, acc: &mut Vec) { + let def = self.id(); + + let item_tree_source_maps; + let (generics, generics_source_map) = db.generic_params_with_source_map(def); + + if generics.is_empty() && generics.no_predicates() { + return; + } + + let source_map = match &generics_source_map { + Some(it) => it, + None => match def { + GenericDefId::FunctionId(it) => { + let id = it.lookup(db.upcast()).id; + item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1; + item_tree_source_maps.function(id.value).generics() + } + GenericDefId::AdtId(AdtId::EnumId(it)) => { + let id = it.lookup(db.upcast()).id; + item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1; + item_tree_source_maps.enum_generic(id.value) + } + GenericDefId::AdtId(AdtId::StructId(it)) => { + let id = it.lookup(db.upcast()).id; + item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1; + item_tree_source_maps.strukt(id.value).generics() + } + GenericDefId::AdtId(AdtId::UnionId(it)) => { + let id = it.lookup(db.upcast()).id; + item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1; + item_tree_source_maps.union(id.value).generics() + } + GenericDefId::TraitId(it) => { + let id = it.lookup(db.upcast()).id; + item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1; + item_tree_source_maps.trait_generic(id.value) + } + GenericDefId::TraitAliasId(it) => { + let id = it.lookup(db.upcast()).id; + item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1; + item_tree_source_maps.trait_alias_generic(id.value) + } + GenericDefId::TypeAliasId(it) => { + let id = it.lookup(db.upcast()).id; + item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1; + item_tree_source_maps.type_alias(id.value).generics() + } + GenericDefId::ImplId(it) => { + let id = it.lookup(db.upcast()).id; + item_tree_source_maps = id.item_tree_with_source_map(db.upcast()).1; + item_tree_source_maps.impl_(id.value).generics() + } + GenericDefId::ConstId(_) => return, + }, + }; + + push_ty_diagnostics(db, acc, db.generic_defaults_with_diagnostics(def).1, source_map); + push_ty_diagnostics( + db, + acc, + db.generic_predicates_without_parent_with_diagnostics(def).1, + source_map, + ); + for (param_id, param) in generics.iter_type_or_consts() { + if let TypeOrConstParamData::ConstParamData(_) = param { + push_ty_diagnostics( + db, + acc, + db.const_param_ty_with_diagnostics(ConstParamId::from_unchecked( + TypeOrConstParamId { parent: def, local_id: param_id }, + )) + .1, + source_map, + ); + } + } + } } /// A single local definition. @@ -5825,3 +5999,19 @@ pub enum DocLinkDef { Field(Field), SelfType(Trait), } + +fn push_ty_diagnostics( + db: &dyn HirDatabase, + acc: &mut Vec, + diagnostics: Option>, + source_map: &TypesSourceMap, +) { + if let Some(diagnostics) = diagnostics { + acc.extend( + diagnostics + .slice + .iter() + .filter_map(|diagnostic| AnyDiagnostic::ty_diagnostic(diagnostic, source_map, db)), + ); + } +} diff --git a/crates/ide-diagnostics/src/handlers/generic_args_prohibited.rs b/crates/ide-diagnostics/src/handlers/generic_args_prohibited.rs index c3ad6704b6..a319a0bcf6 100644 --- a/crates/ide-diagnostics/src/handlers/generic_args_prohibited.rs +++ b/crates/ide-diagnostics/src/handlers/generic_args_prohibited.rs @@ -68,6 +68,9 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::GenericArgsProhibited) -> Option #[cfg(test)] mod tests { + // This diagnostic was the first to be emitted in ty lowering, so the tests here also test + // diagnostics in ty lowering in general (which is why there are so many of them). + use crate::tests::{check_diagnostics, check_fix}; #[test] @@ -239,4 +242,201 @@ fn foo() { }"#, ); } + + #[test] + fn in_fields() { + check_diagnostics( + r#" +struct A(bool); + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types +struct B { v: bool<(), 1> } + // ^^^^^^^ 💡 error: generic arguments are not allowed on builtin types +union C { + a: bool, + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types + b: i32, + // ^^^^^^ 💡 error: generic arguments are not allowed on builtin types + } +enum D { + A(bool), + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types + B { v: i32 }, + // ^^^^^^ 💡 error: generic arguments are not allowed on builtin types +} + "#, + ); + } + + #[test] + fn in_generics() { + check_diagnostics( + r#" +mod foo { + pub trait Trait {} +} + +struct A::Trait>(A) + // ^^^^^^ 💡 error: generic arguments are not allowed on modules + where bool: foo::Trait; + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types +union B::Trait> + // ^^^^^^ 💡 error: generic arguments are not allowed on modules + where bool: foo::Trait + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types +{ a: A } +enum C::Trait> + // ^^^^^^ 💡 error: generic arguments are not allowed on modules + where bool: foo::Trait + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types +{} + +fn f::Trait>() + // ^^^^^^ 💡 error: generic arguments are not allowed on modules + where bool: foo::Trait + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types +{} + +type D::Trait> = A + // ^^^^^^ 💡 error: generic arguments are not allowed on modules + where bool: foo::Trait; + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types + +trait E::Trait> + // ^^^^^^ 💡 error: generic arguments are not allowed on modules + where bool: foo::Trait + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types +{ + fn f::Trait>() + // ^^^^^^ 💡 error: generic arguments are not allowed on modules + where bool: foo::Trait + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types + {} + + type D::Trait> = A + // ^^^^^^ 💡 error: generic arguments are not allowed on modules + where bool: foo::Trait; + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types +} + +impl::Trait> E for () + // ^^^^^^ 💡 error: generic arguments are not allowed on modules + where bool: foo::Trait + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types +{ + fn f::Trait>() + // ^^^^^^ 💡 error: generic arguments are not allowed on modules + where bool: foo::Trait + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types + {} + + type D::Trait> = A + // ^^^^^^ 💡 error: generic arguments are not allowed on modules + where bool: foo::Trait; + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types +} + "#, + ); + } + + #[test] + fn assoc_items() { + check_diagnostics( + r#" +struct Foo; + +trait Trait { + fn f() -> bool { true } + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types + type T = i32; + // ^^^^^^ 💡 error: generic arguments are not allowed on builtin types +} + +impl Trait for Foo { + fn f() -> bool { true } + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types + type T = i32; + // ^^^^^^ 💡 error: generic arguments are not allowed on builtin types +} + +impl Foo { + fn f() -> bool { true } + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types + type T = i32; + // ^^^^^^ 💡 error: generic arguments are not allowed on builtin types +} + "#, + ); + } + + #[test] + fn const_param_ty() { + check_diagnostics( + r#" +fn foo< + const A: bool, + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types + B, + C, + const D: bool, + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types + const E: bool, + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types +>() {} + "#, + ); + } + + #[test] + fn generic_defaults() { + check_diagnostics( + r#" +struct Foo>(A); + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types + "#, + ); + } + + #[test] + fn impl_self_ty() { + check_diagnostics( + r#" +struct Foo(A); +trait Trait {} +impl Foo> {} + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types +impl Trait for Foo> {} + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types + "#, + ); + } + + #[test] + fn impl_trait() { + check_diagnostics( + r#" +mod foo { + pub trait Trait {} +} +impl foo::<()>::Trait for () {} + // ^^^^^^ 💡 error: generic arguments are not allowed on modules + "#, + ); + } + + #[test] + fn type_alias() { + check_diagnostics( + r#" +pub trait Trait { + type Assoc; +} +type T = bool; + // ^^^^^ 💡 error: generic arguments are not allowed on builtin types +impl Trait for () { + type Assoc = i32; + // ^^^^^^ 💡 error: generic arguments are not allowed on builtin types +} + "#, + ); + } } From 1400aec433e38fa82ede8586517bcaaea3b0b138 Mon Sep 17 00:00:00 2001 From: Tarek Date: Mon, 28 Oct 2024 20:09:52 +0300 Subject: [PATCH 30/95] feat: migrate introduce_named_generic assist to use SyntaxFactory Signed-off-by: Tarek --- .../src/handlers/introduce_named_generic.rs | 25 +++++++++++++++---- .../src/ast/syntax_factory/constructors.rs | 21 ++++++++++++++++ 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/crates/ide-assists/src/handlers/introduce_named_generic.rs b/crates/ide-assists/src/handlers/introduce_named_generic.rs index bf6ac1719f..8edcd6db3d 100644 --- a/crates/ide-assists/src/handlers/introduce_named_generic.rs +++ b/crates/ide-assists/src/handlers/introduce_named_generic.rs @@ -1,8 +1,11 @@ use ide_db::syntax_helpers::suggest_name; use itertools::Itertools; use syntax::{ - ast::{self, edit_in_place::GenericParamsOwnerEdit, make, AstNode, HasGenericParams, HasName}, - ted, + ast::{ + self, edit_in_place::GenericParamsOwnerEdit, syntax_factory::SyntaxFactory, AstNode, + HasGenericParams, HasName, + }, + SyntaxElement, }; use crate::{AssistContext, AssistId, AssistKind, Assists}; @@ -25,12 +28,20 @@ pub(crate) fn introduce_named_generic(acc: &mut Assists, ctx: &AssistContext<'_> let type_bound_list = impl_trait_type.type_bound_list()?; + // FIXME: Is this node appropriate to use for SyntaxEditor in this case? + let parent_node = match ctx.covering_element() { + SyntaxElement::Node(n) => n, + SyntaxElement::Token(t) => t.parent()?, + }; + let make = SyntaxFactory::new(); + let target = fn_.syntax().text_range(); acc.add( AssistId("introduce_named_generic", AssistKind::RefactorRewrite), "Replace impl trait with generic", target, |edit| { + let mut editor = edit.make_editor(&parent_node); let impl_trait_type = edit.make_mut(impl_trait_type); let fn_ = edit.make_mut(fn_); let fn_generic_param_list = fn_.get_or_create_generic_param_list(); @@ -47,11 +58,12 @@ pub(crate) fn introduce_named_generic(acc: &mut Assists, ctx: &AssistContext<'_> ) .for_impl_trait_as_generic(&impl_trait_type); - let type_param = make::type_param(make::name(&type_param_name), Some(type_bound_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(); + let new_ty = make.ty(&type_param_name).clone_for_update(); - ted::replace(impl_trait_type.syntax(), new_ty.syntax()); + editor.replace(impl_trait_type.syntax(), new_ty.syntax()); fn_generic_param_list.add_generic_param(type_param.into()); if let Some(cap) = ctx.config.snippet_cap { @@ -61,6 +73,9 @@ pub(crate) fn introduce_named_generic(acc: &mut Assists, ctx: &AssistContext<'_> edit.add_tabstop_before(cap, generic_param); } } + + editor.add_mappings(make.finish_with_mappings()); + edit.add_file_edits(ctx.file_id(), editor); }, ) } diff --git a/crates/syntax/src/ast/syntax_factory/constructors.rs b/crates/syntax/src/ast/syntax_factory/constructors.rs index 9f88add0f7..680ba9381d 100644 --- a/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -14,6 +14,27 @@ impl SyntaxFactory { make::name(name).clone_for_update() } + pub fn ty(&self, text: &str) -> ast::Type { + // FIXME: Is there anything to map here? + make::ty(text).clone_for_update() + } + + pub fn type_param( + &self, + name: ast::Name, + bounds: Option, + ) -> ast::TypeParam { + let ast = make::type_param(name.clone(), bounds.clone()).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_node(name.syntax().clone(), ast.name().unwrap().syntax().clone()); + builder.finish(&mut mapping); + } + + ast + } + pub fn ident_pat(&self, ref_: bool, mut_: bool, name: ast::Name) -> ast::IdentPat { let ast = make::ident_pat(ref_, mut_, name.clone()).clone_for_update(); From 4af3d6f5988701f351e8b0c8623be6fe0f367da4 Mon Sep 17 00:00:00 2001 From: Tarek Date: Mon, 28 Oct 2024 20:45:09 +0300 Subject: [PATCH 31/95] fix: remove make_mut from introduce_named_generic assist Signed-off-by: Tarek --- crates/ide-assists/src/handlers/introduce_named_generic.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/ide-assists/src/handlers/introduce_named_generic.rs b/crates/ide-assists/src/handlers/introduce_named_generic.rs index 8edcd6db3d..cf9d58ca0d 100644 --- a/crates/ide-assists/src/handlers/introduce_named_generic.rs +++ b/crates/ide-assists/src/handlers/introduce_named_generic.rs @@ -42,8 +42,6 @@ pub(crate) fn introduce_named_generic(acc: &mut Assists, ctx: &AssistContext<'_> target, |edit| { let mut editor = edit.make_editor(&parent_node); - let impl_trait_type = edit.make_mut(impl_trait_type); - let fn_ = edit.make_mut(fn_); let fn_generic_param_list = fn_.get_or_create_generic_param_list(); let existing_names = fn_generic_param_list From 642d4f3385167f02770eea8574d8466ad0807e35 Mon Sep 17 00:00:00 2001 From: Tarek Date: Wed, 30 Oct 2024 17:56:02 +0300 Subject: [PATCH 32/95] define syntax_editor_add_generic_param Signed-off-by: Tarek --- .../src/handlers/introduce_named_generic.rs | 8 +++---- crates/syntax/src/ast/edit_in_place.rs | 24 +++++++++++++++++++ .../src/ast/syntax_factory/constructors.rs | 9 +++++-- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/crates/ide-assists/src/handlers/introduce_named_generic.rs b/crates/ide-assists/src/handlers/introduce_named_generic.rs index cf9d58ca0d..ae30dacfd0 100644 --- a/crates/ide-assists/src/handlers/introduce_named_generic.rs +++ b/crates/ide-assists/src/handlers/introduce_named_generic.rs @@ -56,13 +56,11 @@ pub(crate) fn introduce_named_generic(acc: &mut Assists, ctx: &AssistContext<'_> ) .for_impl_trait_as_generic(&impl_trait_type); - let type_param = make - .type_param(make.name(&type_param_name), Some(type_bound_list)) - .clone_for_update(); - let new_ty = make.ty(&type_param_name).clone_for_update(); + let type_param = make.type_param(make.name(&type_param_name), Some(type_bound_list)); + let new_ty = make.ty(&type_param_name); editor.replace(impl_trait_type.syntax(), new_ty.syntax()); - fn_generic_param_list.add_generic_param(type_param.into()); + fn_generic_param_list.syntax_editor_add_generic_param(&mut editor, type_param.into()); if let Some(cap) = ctx.config.snippet_cap { if let Some(generic_param) = diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs index f1286e7aa2..2b152a45c9 100644 --- a/crates/syntax/src/ast/edit_in_place.rs +++ b/crates/syntax/src/ast/edit_in_place.rs @@ -7,6 +7,7 @@ use parser::{SyntaxKind, T}; use crate::{ algo::{self, neighbor}, ast::{self, edit::IndentLevel, make, HasGenericArgs, HasGenericParams}, + syntax_editor::SyntaxEditor, ted::{self, Position}, AstNode, AstToken, Direction, SyntaxElement, SyntaxKind::{ATTR, COMMENT, WHITESPACE}, @@ -257,6 +258,29 @@ impl ast::GenericParamList { } } + pub fn syntax_editor_add_generic_param( + &self, + editor: &mut SyntaxEditor, + generic_param: ast::GenericParam, + ) { + match self.generic_params().last() { + Some(last_param) => { + let position = crate::syntax_editor::Position::after(last_param.syntax()); + let elements = vec![ + make::token(T![,]).into(), + make::tokens::single_space().into(), + generic_param.syntax().clone().into(), + ]; + editor.insert_all(position, elements); + } + None => { + let after_l_angle = + crate::syntax_editor::Position::after(self.l_angle_token().unwrap()); + editor.insert(after_l_angle, generic_param.syntax()); + } + } + } + /// Removes the existing generic param pub fn remove_generic_param(&self, generic_param: ast::GenericParam) { if let Some(previous) = generic_param.syntax().prev_sibling() { diff --git a/crates/syntax/src/ast/syntax_factory/constructors.rs b/crates/syntax/src/ast/syntax_factory/constructors.rs index 680ba9381d..35c467a1e8 100644 --- a/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -2,7 +2,7 @@ use itertools::Itertools; use crate::{ - ast::{self, make, HasName}, + ast::{self, make, HasName, HasTypeBounds}, syntax_editor::SyntaxMappingBuilder, AstNode, }; @@ -15,7 +15,6 @@ impl SyntaxFactory { } pub fn ty(&self, text: &str) -> ast::Type { - // FIXME: Is there anything to map here? make::ty(text).clone_for_update() } @@ -29,6 +28,12 @@ impl SyntaxFactory { if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); builder.map_node(name.syntax().clone(), ast.name().unwrap().syntax().clone()); + if let Some(input) = bounds { + builder.map_node( + input.syntax().clone(), + ast.type_bound_list().unwrap().syntax().clone(), + ); + } builder.finish(&mut mapping); } From 0990d5956d7d8686f5d12bfd56ab48900cab7128 Mon Sep 17 00:00:00 2001 From: Tarek Date: Tue, 12 Nov 2024 19:18:26 +0200 Subject: [PATCH 33/95] fix: implement `syntax_editor_create_generic_param_list` Signed-off-by: Tarek --- .../src/handlers/introduce_named_generic.rs | 8 ++--- crates/syntax/src/ast/edit_in_place.rs | 32 +++++++++++++++++++ 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/crates/ide-assists/src/handlers/introduce_named_generic.rs b/crates/ide-assists/src/handlers/introduce_named_generic.rs index ae30dacfd0..c6945d6245 100644 --- a/crates/ide-assists/src/handlers/introduce_named_generic.rs +++ b/crates/ide-assists/src/handlers/introduce_named_generic.rs @@ -1,10 +1,7 @@ use ide_db::syntax_helpers::suggest_name; use itertools::Itertools; use syntax::{ - ast::{ - self, edit_in_place::GenericParamsOwnerEdit, syntax_factory::SyntaxFactory, AstNode, - HasGenericParams, HasName, - }, + ast::{self, syntax_factory::SyntaxFactory, AstNode, HasGenericParams, HasName}, SyntaxElement, }; @@ -42,7 +39,8 @@ pub(crate) fn introduce_named_generic(acc: &mut Assists, ctx: &AssistContext<'_> target, |edit| { let mut editor = edit.make_editor(&parent_node); - let fn_generic_param_list = fn_.get_or_create_generic_param_list(); + let fn_generic_param_list = + fn_.syntax_editor_get_or_create_generic_param_list(&mut editor); let existing_names = fn_generic_param_list .generic_params() diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs index 2b152a45c9..61580a5cae 100644 --- a/crates/syntax/src/ast/edit_in_place.rs +++ b/crates/syntax/src/ast/edit_in_place.rs @@ -55,6 +55,29 @@ impl GenericParamsOwnerEdit for ast::Fn { } } +impl ast::Fn { + pub fn syntax_editor_get_or_create_generic_param_list( + &self, + editor: &mut SyntaxEditor, + ) -> ast::GenericParamList { + match self.generic_param_list() { + Some(it) => it, + None => { + let position = if let Some(name) = self.name() { + crate::syntax_editor::Position::after(name.syntax) + } else if let Some(fn_token) = self.fn_token() { + crate::syntax_editor::Position::after(fn_token) + } else if let Some(param_list) = self.param_list() { + crate::syntax_editor::Position::before(param_list.syntax) + } else { + crate::syntax_editor::Position::last_child_of(self.syntax()) + }; + syntax_editor_create_generic_param_list(editor, position) + } + } + } +} + impl GenericParamsOwnerEdit for ast::Impl { fn get_or_create_generic_param_list(&self) -> ast::GenericParamList { match self.generic_param_list() { @@ -191,6 +214,15 @@ fn create_generic_param_list(position: Position) -> ast::GenericParamList { gpl } +fn syntax_editor_create_generic_param_list( + editor: &mut SyntaxEditor, + position: crate::syntax_editor::Position, +) -> ast::GenericParamList { + let gpl = make::generic_param_list(empty()).clone_for_update(); + editor.insert(position, gpl.syntax()); + gpl +} + pub trait AttrsOwnerEdit: ast::HasAttrs { fn remove_attrs_and_docs(&self) { remove_attrs_and_docs(self.syntax()); From 98e10d76caa0cb3045ed35a27a28203513500bc3 Mon Sep 17 00:00:00 2001 From: Tarek Date: Fri, 22 Nov 2024 16:26:14 +0200 Subject: [PATCH 34/95] fix: refactor syntax_editor_add_generic_param to handle new generic parameters Signed-off-by: Tarek --- crates/syntax/src/ast/edit_in_place.rs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs index 61580a5cae..9a5c122e27 100644 --- a/crates/syntax/src/ast/edit_in_place.rs +++ b/crates/syntax/src/ast/edit_in_place.rs @@ -293,22 +293,21 @@ impl ast::GenericParamList { pub fn syntax_editor_add_generic_param( &self, editor: &mut SyntaxEditor, - generic_param: ast::GenericParam, + new_param: ast::GenericParam, ) { match self.generic_params().last() { - Some(last_param) => { - let position = crate::syntax_editor::Position::after(last_param.syntax()); - let elements = vec![ - make::token(T![,]).into(), - make::tokens::single_space().into(), - generic_param.syntax().clone().into(), - ]; - editor.insert_all(position, elements); + Some(_) => { + let mut params = + self.generic_params().map(|param| param.clone()).collect::>(); + params.push(new_param.into()); + let new_param_list = make::generic_param_list(params); + + editor.replace(self.syntax(), new_param_list.syntax()); } None => { - let after_l_angle = - crate::syntax_editor::Position::after(self.l_angle_token().unwrap()); - editor.insert(after_l_angle, generic_param.syntax()); + let position = crate::syntax_editor::Position::after(self.l_angle_token().unwrap()); + let new_param_list = make::generic_param_list(once(new_param.clone())); + editor.insert(position, new_param_list.syntax()); } } } From 54f7ed13f56bb130b000eb60eb676c979ca5e00d Mon Sep 17 00:00:00 2001 From: Tarek Date: Wed, 27 Nov 2024 14:56:56 +0200 Subject: [PATCH 35/95] fix: refactor `syntax_editor_add_generic_param` to handle adding new generic parameters Signed-off-by: Tarek --- .../src/handlers/introduce_named_generic.rs | 23 +++---- crates/syntax/src/ast/edit_in_place.rs | 65 +++++++++---------- 2 files changed, 42 insertions(+), 46 deletions(-) diff --git a/crates/ide-assists/src/handlers/introduce_named_generic.rs b/crates/ide-assists/src/handlers/introduce_named_generic.rs index c6945d6245..25628c1656 100644 --- a/crates/ide-assists/src/handlers/introduce_named_generic.rs +++ b/crates/ide-assists/src/handlers/introduce_named_generic.rs @@ -39,16 +39,17 @@ pub(crate) fn introduce_named_generic(acc: &mut Assists, ctx: &AssistContext<'_> target, |edit| { let mut editor = edit.make_editor(&parent_node); - let fn_generic_param_list = - fn_.syntax_editor_get_or_create_generic_param_list(&mut editor); - let existing_names = fn_generic_param_list - .generic_params() - .flat_map(|param| match param { - ast::GenericParam::TypeParam(t) => t.name().map(|name| name.to_string()), - p => Some(p.to_string()), - }) - .collect_vec(); + let existing_names = match fn_.generic_param_list() { + Some(generic_param_list) => generic_param_list + .generic_params() + .flat_map(|param| match param { + ast::GenericParam::TypeParam(t) => t.name().map(|name| name.to_string()), + p => Some(p.to_string()), + }) + .collect_vec(), + None => Vec::new(), + }; let type_param_name = suggest_name::NameGenerator::new_with_names( existing_names.iter().map(|s| s.as_str()), ) @@ -58,13 +59,13 @@ pub(crate) fn introduce_named_generic(acc: &mut Assists, ctx: &AssistContext<'_> let new_ty = make.ty(&type_param_name); editor.replace(impl_trait_type.syntax(), new_ty.syntax()); - fn_generic_param_list.syntax_editor_add_generic_param(&mut editor, type_param.into()); + fn_.syntax_editor_add_generic_param(&mut editor, type_param.into()); if let Some(cap) = ctx.config.snippet_cap { if let Some(generic_param) = fn_.generic_param_list().and_then(|it| it.generic_params().last()) { - edit.add_tabstop_before(cap, generic_param); + editor.add_annotation(generic_param.syntax(), edit.make_tabstop_before(cap)); } } diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs index 9a5c122e27..360ee14fa2 100644 --- a/crates/syntax/src/ast/edit_in_place.rs +++ b/crates/syntax/src/ast/edit_in_place.rs @@ -56,13 +56,37 @@ impl GenericParamsOwnerEdit for ast::Fn { } impl ast::Fn { - pub fn syntax_editor_get_or_create_generic_param_list( + pub fn syntax_editor_add_generic_param( &self, editor: &mut SyntaxEditor, - ) -> ast::GenericParamList { + new_param: GenericParam, + ) { match self.generic_param_list() { - Some(it) => it, + Some(generic_param_list) => match generic_param_list.generic_params().last() { + Some(_last_param) => { + // There exists a generic param list and it's not empty + let mut params = generic_param_list + .generic_params() + .map(|param| param.clone()) + .collect::>(); + params.push(new_param.into()); + let new_param_list = make::generic_param_list(params); + editor.replace( + generic_param_list.syntax(), + new_param_list.syntax().clone_for_update(), + ); + } + None => { + // There exists a generic param list but it's empty + let position = crate::syntax_editor::Position::after( + generic_param_list.l_angle_token().unwrap(), + ); + + editor.insert(position, new_param.syntax()); + } + }, None => { + // There was no generic param list let position = if let Some(name) = self.name() { crate::syntax_editor::Position::after(name.syntax) } else if let Some(fn_token) = self.fn_token() { @@ -72,7 +96,9 @@ impl ast::Fn { } else { crate::syntax_editor::Position::last_child_of(self.syntax()) }; - syntax_editor_create_generic_param_list(editor, position) + + let new_param_list = make::generic_param_list(once(new_param.clone())); + editor.insert(position, new_param_list.syntax().clone_for_update()); } } } @@ -214,15 +240,6 @@ fn create_generic_param_list(position: Position) -> ast::GenericParamList { gpl } -fn syntax_editor_create_generic_param_list( - editor: &mut SyntaxEditor, - position: crate::syntax_editor::Position, -) -> ast::GenericParamList { - let gpl = make::generic_param_list(empty()).clone_for_update(); - editor.insert(position, gpl.syntax()); - gpl -} - pub trait AttrsOwnerEdit: ast::HasAttrs { fn remove_attrs_and_docs(&self) { remove_attrs_and_docs(self.syntax()); @@ -290,28 +307,6 @@ impl ast::GenericParamList { } } - pub fn syntax_editor_add_generic_param( - &self, - editor: &mut SyntaxEditor, - new_param: ast::GenericParam, - ) { - match self.generic_params().last() { - Some(_) => { - let mut params = - self.generic_params().map(|param| param.clone()).collect::>(); - params.push(new_param.into()); - let new_param_list = make::generic_param_list(params); - - editor.replace(self.syntax(), new_param_list.syntax()); - } - None => { - let position = crate::syntax_editor::Position::after(self.l_angle_token().unwrap()); - let new_param_list = make::generic_param_list(once(new_param.clone())); - editor.insert(position, new_param_list.syntax()); - } - } - } - /// Removes the existing generic param pub fn remove_generic_param(&self, generic_param: ast::GenericParam) { if let Some(previous) = generic_param.syntax().prev_sibling() { From 70ef2f23c239d66b29281c12c87b52dc4ffc2095 Mon Sep 17 00:00:00 2001 From: Tarek Date: Tue, 3 Dec 2024 23:47:52 +0200 Subject: [PATCH 36/95] fix: refactor `syntax_editor_add_generic_param` Signed-off-by: Tarek --- .../src/handlers/introduce_named_generic.rs | 9 ++-- crates/syntax/src/ast/edit_in_place.rs | 51 ++++++++++++++----- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/crates/ide-assists/src/handlers/introduce_named_generic.rs b/crates/ide-assists/src/handlers/introduce_named_generic.rs index 25628c1656..4b4433419a 100644 --- a/crates/ide-assists/src/handlers/introduce_named_generic.rs +++ b/crates/ide-assists/src/handlers/introduce_named_generic.rs @@ -59,14 +59,11 @@ pub(crate) fn introduce_named_generic(acc: &mut Assists, ctx: &AssistContext<'_> let new_ty = make.ty(&type_param_name); editor.replace(impl_trait_type.syntax(), new_ty.syntax()); - fn_.syntax_editor_add_generic_param(&mut editor, type_param.into()); + let generic_param = syntax::ast::GenericParam::from(type_param); + fn_.syntax_editor_add_generic_param(&mut editor, generic_param.clone()); if let Some(cap) = ctx.config.snippet_cap { - if let Some(generic_param) = - fn_.generic_param_list().and_then(|it| it.generic_params().last()) - { - editor.add_annotation(generic_param.syntax(), edit.make_tabstop_before(cap)); - } + editor.add_annotation(generic_param.syntax(), edit.make_tabstop_before(cap)); } editor.add_mappings(make.finish_with_mappings()); diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs index 360ee14fa2..22afb8297b 100644 --- a/crates/syntax/src/ast/edit_in_place.rs +++ b/crates/syntax/src/ast/edit_in_place.rs @@ -56,6 +56,7 @@ impl GenericParamsOwnerEdit for ast::Fn { } impl ast::Fn { + /// Adds a new generic param to the function using `SyntaxEditor` pub fn syntax_editor_add_generic_param( &self, editor: &mut SyntaxEditor, @@ -65,23 +66,44 @@ impl ast::Fn { Some(generic_param_list) => match generic_param_list.generic_params().last() { Some(_last_param) => { // There exists a generic param list and it's not empty - let mut params = generic_param_list - .generic_params() - .map(|param| param.clone()) - .collect::>(); - params.push(new_param.into()); - let new_param_list = make::generic_param_list(params); - editor.replace( - generic_param_list.syntax(), - new_param_list.syntax().clone_for_update(), + let position = generic_param_list.r_angle_token().map_or_else( + || crate::syntax_editor::Position::last_child_of(self.syntax()), + crate::syntax_editor::Position::before, ); + + if let Some(last_param) = generic_param_list.generic_params().last() { + if last_param + .syntax() + .next_sibling_or_token() + .map_or(false, |it| it.kind() == SyntaxKind::COMMA) + { + editor.insert( + crate::syntax_editor::Position::after(last_param.syntax()), + new_param.syntax().clone(), + ); + editor.insert( + crate::syntax_editor::Position::after(last_param.syntax()), + make::token(SyntaxKind::WHITESPACE), + ); + editor.insert( + crate::syntax_editor::Position::after(last_param.syntax()), + make::token(SyntaxKind::COMMA), + ); + } else { + let elements = vec![ + make::token(SyntaxKind::COMMA).into(), + make::token(SyntaxKind::WHITESPACE).into(), + new_param.syntax().clone().into(), + ]; + editor.insert_all(position, elements); + } + }; } None => { // There exists a generic param list but it's empty let position = crate::syntax_editor::Position::after( generic_param_list.l_angle_token().unwrap(), ); - editor.insert(position, new_param.syntax()); } }, @@ -96,9 +118,12 @@ impl ast::Fn { } else { crate::syntax_editor::Position::last_child_of(self.syntax()) }; - - let new_param_list = make::generic_param_list(once(new_param.clone())); - editor.insert(position, new_param_list.syntax().clone_for_update()); + let elements = vec![ + make::token(SyntaxKind::L_ANGLE).into(), + new_param.syntax().clone().into(), + make::token(T![>]).into(), + ]; + editor.insert_all(position, elements); } } } From 5aaffe6dc9812bb032b526e1658be4e05a81c92f Mon Sep 17 00:00:00 2001 From: Tarek Date: Tue, 3 Dec 2024 23:57:11 +0200 Subject: [PATCH 37/95] fix: correct token type for closing angle bracket Signed-off-by: Tarek --- crates/syntax/src/ast/edit_in_place.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs index 22afb8297b..dcaeed4007 100644 --- a/crates/syntax/src/ast/edit_in_place.rs +++ b/crates/syntax/src/ast/edit_in_place.rs @@ -121,7 +121,7 @@ impl ast::Fn { let elements = vec![ make::token(SyntaxKind::L_ANGLE).into(), new_param.syntax().clone().into(), - make::token(T![>]).into(), + make::token(SyntaxKind::R_ANGLE).into(), ]; editor.insert_all(position, elements); } From 2fb563f1928be8c6719396601921e5cc46f731b1 Mon Sep 17 00:00:00 2001 From: Tarek Date: Wed, 4 Dec 2024 14:32:48 +0200 Subject: [PATCH 38/95] fix: refactor `introduce_named_generic` assist Signed-off-by: Tarek --- .../src/handlers/introduce_named_generic.rs | 19 ++----- crates/syntax/src/ast/edit_in_place.rs | 54 +++++++++---------- 2 files changed, 31 insertions(+), 42 deletions(-) diff --git a/crates/ide-assists/src/handlers/introduce_named_generic.rs b/crates/ide-assists/src/handlers/introduce_named_generic.rs index 4b4433419a..1edbd01b02 100644 --- a/crates/ide-assists/src/handlers/introduce_named_generic.rs +++ b/crates/ide-assists/src/handlers/introduce_named_generic.rs @@ -1,9 +1,6 @@ use ide_db::syntax_helpers::suggest_name; use itertools::Itertools; -use syntax::{ - ast::{self, syntax_factory::SyntaxFactory, AstNode, HasGenericParams, HasName}, - SyntaxElement, -}; +use syntax::ast::{self, syntax_factory::SyntaxFactory, AstNode, HasGenericParams, HasName}; use crate::{AssistContext, AssistId, AssistKind, Assists}; @@ -25,20 +22,14 @@ pub(crate) fn introduce_named_generic(acc: &mut Assists, ctx: &AssistContext<'_> let type_bound_list = impl_trait_type.type_bound_list()?; - // FIXME: Is this node appropriate to use for SyntaxEditor in this case? - let parent_node = match ctx.covering_element() { - SyntaxElement::Node(n) => n, - SyntaxElement::Token(t) => t.parent()?, - }; let make = SyntaxFactory::new(); - let target = fn_.syntax().text_range(); acc.add( AssistId("introduce_named_generic", AssistKind::RefactorRewrite), "Replace impl trait with generic", target, - |edit| { - let mut editor = edit.make_editor(&parent_node); + |builder| { + let mut editor = builder.make_editor(fn_.syntax()); let existing_names = match fn_.generic_param_list() { Some(generic_param_list) => generic_param_list @@ -63,11 +54,11 @@ pub(crate) fn introduce_named_generic(acc: &mut Assists, ctx: &AssistContext<'_> fn_.syntax_editor_add_generic_param(&mut editor, generic_param.clone()); if let Some(cap) = ctx.config.snippet_cap { - editor.add_annotation(generic_param.syntax(), edit.make_tabstop_before(cap)); + editor.add_annotation(generic_param.syntax(), builder.make_tabstop_before(cap)); } editor.add_mappings(make.finish_with_mappings()); - edit.add_file_edits(ctx.file_id(), editor); + builder.add_file_edits(ctx.file_id(), editor); }, ) } diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs index dcaeed4007..68447a6a27 100644 --- a/crates/syntax/src/ast/edit_in_place.rs +++ b/crates/syntax/src/ast/edit_in_place.rs @@ -64,40 +64,38 @@ impl ast::Fn { ) { match self.generic_param_list() { Some(generic_param_list) => match generic_param_list.generic_params().last() { - Some(_last_param) => { + Some(last_param) => { // There exists a generic param list and it's not empty let position = generic_param_list.r_angle_token().map_or_else( || crate::syntax_editor::Position::last_child_of(self.syntax()), crate::syntax_editor::Position::before, ); - if let Some(last_param) = generic_param_list.generic_params().last() { - if last_param - .syntax() - .next_sibling_or_token() - .map_or(false, |it| it.kind() == SyntaxKind::COMMA) - { - editor.insert( - crate::syntax_editor::Position::after(last_param.syntax()), - new_param.syntax().clone(), - ); - editor.insert( - crate::syntax_editor::Position::after(last_param.syntax()), - make::token(SyntaxKind::WHITESPACE), - ); - editor.insert( - crate::syntax_editor::Position::after(last_param.syntax()), - make::token(SyntaxKind::COMMA), - ); - } else { - let elements = vec![ - make::token(SyntaxKind::COMMA).into(), - make::token(SyntaxKind::WHITESPACE).into(), - new_param.syntax().clone().into(), - ]; - editor.insert_all(position, elements); - } - }; + if last_param + .syntax() + .next_sibling_or_token() + .map_or(false, |it| it.kind() == SyntaxKind::COMMA) + { + editor.insert( + crate::syntax_editor::Position::after(last_param.syntax()), + new_param.syntax().clone(), + ); + editor.insert( + crate::syntax_editor::Position::after(last_param.syntax()), + make::token(SyntaxKind::WHITESPACE), + ); + editor.insert( + crate::syntax_editor::Position::after(last_param.syntax()), + make::token(SyntaxKind::COMMA), + ); + } else { + let elements = vec![ + make::token(SyntaxKind::COMMA).into(), + make::token(SyntaxKind::WHITESPACE).into(), + new_param.syntax().clone().into(), + ]; + editor.insert_all(position, elements); + } } None => { // There exists a generic param list but it's empty From 9157761f5d444be56bb1163890436cd3d7a9dbd6 Mon Sep 17 00:00:00 2001 From: Tarek Date: Wed, 4 Dec 2024 14:51:48 +0200 Subject: [PATCH 39/95] refactor: move editing for ast using `SyntaxEditor` to a separate file Signed-off-by: Tarek --- .../src/handlers/introduce_named_generic.rs | 2 +- crates/syntax/src/ast/edit_in_place.rs | 73 ------------------- crates/syntax/src/syntax_editor.rs | 1 + crates/syntax/src/syntax_editor/edits.rs | 72 ++++++++++++++++++ 4 files changed, 74 insertions(+), 74 deletions(-) create mode 100644 crates/syntax/src/syntax_editor/edits.rs diff --git a/crates/ide-assists/src/handlers/introduce_named_generic.rs b/crates/ide-assists/src/handlers/introduce_named_generic.rs index 1edbd01b02..ecc96f791d 100644 --- a/crates/ide-assists/src/handlers/introduce_named_generic.rs +++ b/crates/ide-assists/src/handlers/introduce_named_generic.rs @@ -51,7 +51,7 @@ pub(crate) fn introduce_named_generic(acc: &mut Assists, ctx: &AssistContext<'_> editor.replace(impl_trait_type.syntax(), new_ty.syntax()); let generic_param = syntax::ast::GenericParam::from(type_param); - fn_.syntax_editor_add_generic_param(&mut editor, generic_param.clone()); + editor.syntax_editor_add_generic_param(fn_, generic_param.clone()); if let Some(cap) = ctx.config.snippet_cap { editor.add_annotation(generic_param.syntax(), builder.make_tabstop_before(cap)); diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs index 68447a6a27..f1286e7aa2 100644 --- a/crates/syntax/src/ast/edit_in_place.rs +++ b/crates/syntax/src/ast/edit_in_place.rs @@ -7,7 +7,6 @@ use parser::{SyntaxKind, T}; use crate::{ algo::{self, neighbor}, ast::{self, edit::IndentLevel, make, HasGenericArgs, HasGenericParams}, - syntax_editor::SyntaxEditor, ted::{self, Position}, AstNode, AstToken, Direction, SyntaxElement, SyntaxKind::{ATTR, COMMENT, WHITESPACE}, @@ -55,78 +54,6 @@ impl GenericParamsOwnerEdit for ast::Fn { } } -impl ast::Fn { - /// Adds a new generic param to the function using `SyntaxEditor` - pub fn syntax_editor_add_generic_param( - &self, - editor: &mut SyntaxEditor, - new_param: GenericParam, - ) { - match self.generic_param_list() { - Some(generic_param_list) => match generic_param_list.generic_params().last() { - Some(last_param) => { - // There exists a generic param list and it's not empty - let position = generic_param_list.r_angle_token().map_or_else( - || crate::syntax_editor::Position::last_child_of(self.syntax()), - crate::syntax_editor::Position::before, - ); - - if last_param - .syntax() - .next_sibling_or_token() - .map_or(false, |it| it.kind() == SyntaxKind::COMMA) - { - editor.insert( - crate::syntax_editor::Position::after(last_param.syntax()), - new_param.syntax().clone(), - ); - editor.insert( - crate::syntax_editor::Position::after(last_param.syntax()), - make::token(SyntaxKind::WHITESPACE), - ); - editor.insert( - crate::syntax_editor::Position::after(last_param.syntax()), - make::token(SyntaxKind::COMMA), - ); - } else { - let elements = vec![ - make::token(SyntaxKind::COMMA).into(), - make::token(SyntaxKind::WHITESPACE).into(), - new_param.syntax().clone().into(), - ]; - editor.insert_all(position, elements); - } - } - None => { - // There exists a generic param list but it's empty - let position = crate::syntax_editor::Position::after( - generic_param_list.l_angle_token().unwrap(), - ); - editor.insert(position, new_param.syntax()); - } - }, - None => { - // There was no generic param list - let position = if let Some(name) = self.name() { - crate::syntax_editor::Position::after(name.syntax) - } else if let Some(fn_token) = self.fn_token() { - crate::syntax_editor::Position::after(fn_token) - } else if let Some(param_list) = self.param_list() { - crate::syntax_editor::Position::before(param_list.syntax) - } else { - crate::syntax_editor::Position::last_child_of(self.syntax()) - }; - let elements = vec![ - make::token(SyntaxKind::L_ANGLE).into(), - new_param.syntax().clone().into(), - make::token(SyntaxKind::R_ANGLE).into(), - ]; - editor.insert_all(position, elements); - } - } - } -} - impl GenericParamsOwnerEdit for ast::Impl { fn get_or_create_generic_param_list(&self) -> ast::GenericParamList { match self.generic_param_list() { diff --git a/crates/syntax/src/syntax_editor.rs b/crates/syntax/src/syntax_editor.rs index 714f5a9911..7e5d0f27e0 100644 --- a/crates/syntax/src/syntax_editor.rs +++ b/crates/syntax/src/syntax_editor.rs @@ -16,6 +16,7 @@ use rustc_hash::FxHashMap; use crate::{SyntaxElement, SyntaxNode, SyntaxToken}; mod edit_algo; +mod edits; mod mapping; pub use mapping::{SyntaxMapping, SyntaxMappingBuilder}; diff --git a/crates/syntax/src/syntax_editor/edits.rs b/crates/syntax/src/syntax_editor/edits.rs new file mode 100644 index 0000000000..759b46c9c7 --- /dev/null +++ b/crates/syntax/src/syntax_editor/edits.rs @@ -0,0 +1,72 @@ +//! Structural editing for ast using `SyntaxEditor` + +use crate::{ + ast::make, ast::AstNode, ast::Fn, ast::GenericParam, ast::HasGenericParams, ast::HasName, + syntax_editor::Position, syntax_editor::SyntaxEditor, SyntaxKind, +}; + +impl SyntaxEditor { + /// Adds a new generic param to the function using `SyntaxEditor` + pub fn syntax_editor_add_generic_param(&mut self, function: Fn, new_param: GenericParam) { + match function.generic_param_list() { + Some(generic_param_list) => match generic_param_list.generic_params().last() { + Some(last_param) => { + // There exists a generic param list and it's not empty + let position = generic_param_list.r_angle_token().map_or_else( + || Position::last_child_of(function.syntax()), + Position::before, + ); + + if last_param + .syntax() + .next_sibling_or_token() + .map_or(false, |it| it.kind() == SyntaxKind::COMMA) + { + self.insert( + Position::after(last_param.syntax()), + new_param.syntax().clone(), + ); + self.insert( + Position::after(last_param.syntax()), + make::token(SyntaxKind::WHITESPACE), + ); + self.insert( + Position::after(last_param.syntax()), + make::token(SyntaxKind::COMMA), + ); + } else { + let elements = vec![ + make::token(SyntaxKind::COMMA).into(), + make::token(SyntaxKind::WHITESPACE).into(), + new_param.syntax().clone().into(), + ]; + self.insert_all(position, elements); + } + } + None => { + // There exists a generic param list but it's empty + let position = Position::after(generic_param_list.l_angle_token().unwrap()); + self.insert(position, new_param.syntax()); + } + }, + None => { + // There was no generic param list + let position = if let Some(name) = function.name() { + Position::after(name.syntax) + } else if let Some(fn_token) = function.fn_token() { + Position::after(fn_token) + } else if let Some(param_list) = function.param_list() { + Position::before(param_list.syntax) + } else { + Position::last_child_of(function.syntax()) + }; + let elements = vec![ + make::token(SyntaxKind::L_ANGLE).into(), + new_param.syntax().clone().into(), + make::token(SyntaxKind::R_ANGLE).into(), + ]; + self.insert_all(position, elements); + } + } + } +} From 33c7e81e90f00b01071d7b8daf1e92237fc8b1e6 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Wed, 4 Dec 2024 15:25:01 +0200 Subject: [PATCH 40/95] Do not report warnings from proc macros, ever --- crates/hir-expand/src/lib.rs | 7 +++++++ crates/ide-diagnostics/src/lib.rs | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/crates/hir-expand/src/lib.rs b/crates/hir-expand/src/lib.rs index 7d2f556406..2ee598dfbf 100644 --- a/crates/hir-expand/src/lib.rs +++ b/crates/hir-expand/src/lib.rs @@ -269,6 +269,13 @@ pub enum MacroDefKind { ProcMacro(AstId, CustomProcMacroExpander, ProcMacroKind), } +impl MacroDefKind { + #[inline] + pub fn is_declarative(&self) -> bool { + matches!(self, MacroDefKind::Declarative(..)) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct EagerCallInfo { /// The expanded argument of the eager macro. diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs index 1f1b6478d3..9fbed7db35 100644 --- a/crates/ide-diagnostics/src/lib.rs +++ b/crates/ide-diagnostics/src/lib.rs @@ -542,7 +542,13 @@ fn handle_diag_from_macros( sema.db.lookup_intern_syntax_context(span.ctx).outer_expn.is_some_and(|expansion| { let macro_call = sema.db.lookup_intern_macro_call(expansion.as_macro_file().macro_call_id); + // We don't want to show diagnostics for non-local macros at all, but proc macros authors + // seem to rely on being able to emit non-warning-free code, so we don't want to show warnings + // for them even when the proc macro comes from the same workspace (in rustc that's not a + // problem because it doesn't have the concept of workspaces, and proc macros always reside + // in a different crate). !Crate::from(macro_call.def.krate).origin(sema.db).is_local() + || !macro_call.def.kind.is_declarative() }) }) { // Disable suggestions for external macros, they'll change library code and it's just bad. From b7e613c0dcf624f1952ed74777546dc858f79fef Mon Sep 17 00:00:00 2001 From: Tarek Date: Wed, 4 Dec 2024 16:02:03 +0200 Subject: [PATCH 41/95] fix: update `introduce_named_generic` to use `type_param` directly Signed-off-by: Tarek --- crates/ide-assists/src/handlers/introduce_named_generic.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/ide-assists/src/handlers/introduce_named_generic.rs b/crates/ide-assists/src/handlers/introduce_named_generic.rs index ecc96f791d..28023ce5ed 100644 --- a/crates/ide-assists/src/handlers/introduce_named_generic.rs +++ b/crates/ide-assists/src/handlers/introduce_named_generic.rs @@ -50,11 +50,10 @@ pub(crate) fn introduce_named_generic(acc: &mut Assists, ctx: &AssistContext<'_> let new_ty = make.ty(&type_param_name); editor.replace(impl_trait_type.syntax(), new_ty.syntax()); - let generic_param = syntax::ast::GenericParam::from(type_param); - editor.syntax_editor_add_generic_param(fn_, generic_param.clone()); + editor.syntax_editor_add_generic_param(fn_, type_param.clone().into()); if let Some(cap) = ctx.config.snippet_cap { - editor.add_annotation(generic_param.syntax(), builder.make_tabstop_before(cap)); + editor.add_annotation(type_param.syntax(), builder.make_tabstop_before(cap)); } editor.add_mappings(make.finish_with_mappings()); From d71e41dba6298fd3aca377ad579039bd1d21dd27 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 5 Dec 2024 09:44:17 +0100 Subject: [PATCH 42/95] Disable `<` typing handler again --- crates/rust-analyzer/src/config.rs | 2 +- docs/user/generated_config.adoc | 2 +- editors/code/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 392bfbf15f..aa9f919679 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -309,7 +309,7 @@ config_data! { signatureInfo_documentation_enable: bool = true, /// Specify the characters to exclude from triggering typing assists. The default trigger characters are `.`, `=`, `<`, `>`, `{`, and `(`. Setting this to a string will disable typing assists for the specified characters. - typing_excludeChars: Option = None, + typing_excludeChars: Option = Some("<".to_owned()), /// Enables automatic discovery of projects using [`DiscoverWorkspaceConfig::command`]. diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index a3172c7ca2..96a2a5a27d 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -992,7 +992,7 @@ Show full signature of the callable. Only shows parameters if disabled. -- Show documentation. -- -[[rust-analyzer.typing.excludeChars]]rust-analyzer.typing.excludeChars (default: `null`):: +[[rust-analyzer.typing.excludeChars]]rust-analyzer.typing.excludeChars (default: `"<"`):: + -- Specify the characters to exclude from triggering typing assists. The default trigger characters are `.`, `=`, `<`, `>`, `{`, and `(`. Setting this to a string will disable typing assists for the specified characters. diff --git a/editors/code/package.json b/editors/code/package.json index 68c61e4bf6..7529651ca7 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -2607,7 +2607,7 @@ "properties": { "rust-analyzer.typing.excludeChars": { "markdownDescription": "Specify the characters to exclude from triggering typing assists. The default trigger characters are `.`, `=`, `<`, `>`, `{`, and `(`. Setting this to a string will disable typing assists for the specified characters.", - "default": null, + "default": "<", "type": [ "null", "string" From 8954a7f9e95c7c6a35ebfeaf2b5671655f4b05b4 Mon Sep 17 00:00:00 2001 From: Tarek Date: Thu, 5 Dec 2024 13:50:43 +0200 Subject: [PATCH 43/95] fix: rename `syntax_editor_add_generic_param` to `add_generic_param` Signed-off-by: Tarek --- crates/ide-assists/src/handlers/introduce_named_generic.rs | 2 +- crates/syntax/src/syntax_editor/edits.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ide-assists/src/handlers/introduce_named_generic.rs b/crates/ide-assists/src/handlers/introduce_named_generic.rs index 28023ce5ed..8c276415bb 100644 --- a/crates/ide-assists/src/handlers/introduce_named_generic.rs +++ b/crates/ide-assists/src/handlers/introduce_named_generic.rs @@ -50,7 +50,7 @@ pub(crate) fn introduce_named_generic(acc: &mut Assists, ctx: &AssistContext<'_> let new_ty = make.ty(&type_param_name); editor.replace(impl_trait_type.syntax(), new_ty.syntax()); - editor.syntax_editor_add_generic_param(fn_, type_param.clone().into()); + editor.add_generic_param(&fn_, type_param.clone().into()); if let Some(cap) = ctx.config.snippet_cap { editor.add_annotation(type_param.syntax(), builder.make_tabstop_before(cap)); diff --git a/crates/syntax/src/syntax_editor/edits.rs b/crates/syntax/src/syntax_editor/edits.rs index 759b46c9c7..73196f5cb1 100644 --- a/crates/syntax/src/syntax_editor/edits.rs +++ b/crates/syntax/src/syntax_editor/edits.rs @@ -7,7 +7,7 @@ use crate::{ impl SyntaxEditor { /// Adds a new generic param to the function using `SyntaxEditor` - pub fn syntax_editor_add_generic_param(&mut self, function: Fn, new_param: GenericParam) { + pub fn add_generic_param(&mut self, function: &Fn, new_param: GenericParam) { match function.generic_param_list() { Some(generic_param_list) => match generic_param_list.generic_params().last() { Some(last_param) => { From df7ab62a06cea79131de35c02e95ef65a82cacb9 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 5 Dec 2024 14:37:38 +0100 Subject: [PATCH 44/95] fix: Fix parsing of integer/keyword name refs in various places --- .../hir-def/src/macro_expansion_tests/mbe.rs | 2 +- crates/parser/src/grammar.rs | 44 ++++++++++- crates/parser/src/grammar/expressions.rs | 64 ++++++++------- crates/parser/src/grammar/expressions/atom.rs | 12 +-- crates/parser/src/grammar/generic_args.rs | 4 +- crates/parser/src/grammar/generic_params.rs | 11 ++- crates/parser/src/grammar/items.rs | 12 +-- crates/parser/src/grammar/paths.rs | 49 ++++++------ crates/parser/test_data/generated/runner.rs | 8 +- .../parser/err/0004_use_path_bad_segment.rast | 2 +- .../parser/err/0048_double_fish.rast | 4 +- .../parser/inline/err/arg_list_recovery.rast | 2 +- .../err/crate_visibility_empty_recover.rast | 2 +- .../parser/inline/err/empty_segment.rast | 2 +- .../parser/inline/err/meta_recovery.rast | 10 +-- .../inline/err/precise_capturing_invalid.rast | 28 +++++++ .../inline/err/precise_capturing_invalid.rs | 1 + ...cord_literal_before_ellipsis_recovery.rast | 37 ++++++++- ...record_literal_before_ellipsis_recovery.rs | 1 + .../err/record_literal_field_eq_recovery.rast | 29 ++++++- .../err/record_literal_field_eq_recovery.rs | 1 + ...ord_literal_missing_ellipsis_recovery.rast | 78 ++++++++++++++----- ...ecord_literal_missing_ellipsis_recovery.rs | 3 +- .../parser/inline/ok/extern_crate.rast | 9 +++ .../parser/inline/ok/extern_crate.rs | 1 + .../parser/inline/ok/extern_crate_rename.rast | 15 ++++ .../parser/inline/ok/extern_crate_rename.rs | 1 + .../parser/inline/ok/extern_crate_self.rast | 10 --- .../parser/inline/ok/extern_crate_self.rs | 1 - .../parser/inline/ok/field_expr.rast | 24 ++++++ .../test_data/parser/inline/ok/field_expr.rs | 2 + .../parser/inline/ok/method_call_expr.rast | 14 ++++ .../parser/inline/ok/method_call_expr.rs | 1 + 33 files changed, 351 insertions(+), 133 deletions(-) create mode 100644 crates/parser/test_data/parser/inline/err/precise_capturing_invalid.rast create mode 100644 crates/parser/test_data/parser/inline/err/precise_capturing_invalid.rs delete mode 100644 crates/parser/test_data/parser/inline/ok/extern_crate_self.rast delete mode 100644 crates/parser/test_data/parser/inline/ok/extern_crate_self.rs diff --git a/crates/hir-def/src/macro_expansion_tests/mbe.rs b/crates/hir-def/src/macro_expansion_tests/mbe.rs index 5c03fad613..511626b5ed 100644 --- a/crates/hir-def/src/macro_expansion_tests/mbe.rs +++ b/crates/hir-def/src/macro_expansion_tests/mbe.rs @@ -1733,7 +1733,7 @@ m!(C("0")); macro_rules! m { ($k:expr) => { fn f() { K::$k; } } } -/* parse error: expected identifier */ +/* parse error: expected identifier, `self`, `super`, `crate`, or `Self` */ /* parse error: expected SEMICOLON */ /* parse error: expected SEMICOLON */ /* parse error: expected expression, item or let statement */ diff --git a/crates/parser/src/grammar.rs b/crates/parser/src/grammar.rs index c402c49855..fe6b904bd8 100644 --- a/crates/parser/src/grammar.rs +++ b/crates/parser/src/grammar.rs @@ -307,13 +307,49 @@ fn name(p: &mut Parser<'_>) { name_r(p, TokenSet::EMPTY); } -fn name_ref(p: &mut Parser<'_>) { - if p.at(IDENT) { +fn name_ref_or_self(p: &mut Parser<'_>) { + if matches!(p.current(), T![ident] | T![self]) { let m = p.start(); - p.bump(IDENT); + p.bump_any(); m.complete(p, NAME_REF); } else { - p.err_and_bump("expected identifier"); + p.err_and_bump("expected identifier or `self`"); + } +} + +fn name_ref_or_upper_self(p: &mut Parser<'_>) { + if matches!(p.current(), T![ident] | T![Self]) { + let m = p.start(); + p.bump_any(); + m.complete(p, NAME_REF); + } else { + p.err_and_bump("expected identifier or `Self`"); + } +} + +const PATH_NAME_REF_KINDS: TokenSet = + TokenSet::new(&[IDENT, T![self], T![super], T![crate], T![Self]]); + +fn name_ref_mod_path(p: &mut Parser<'_>) { + if p.at_ts(PATH_NAME_REF_KINDS) { + let m = p.start(); + p.bump_any(); + m.complete(p, NAME_REF); + } else { + p.err_and_bump("expected identifier, `self`, `super`, `crate`, or `Self`"); + } +} + +const PATH_NAME_REF_OR_INDEX_KINDS: TokenSet = + PATH_NAME_REF_KINDS.union(TokenSet::new(&[INT_NUMBER])); + +fn name_ref_mod_path_or_index(p: &mut Parser<'_>) { + if p.at_ts(PATH_NAME_REF_OR_INDEX_KINDS) { + let m = p.start(); + p.bump_any(); + m.complete(p, NAME_REF); + } else { + p.err_and_bump("expected integer, identifier, `self`, `super`, `crate`, or `Self`"); } } diff --git a/crates/parser/src/grammar/expressions.rs b/crates/parser/src/grammar/expressions.rs index 349bce0939..3b3f11be13 100644 --- a/crates/parser/src/grammar/expressions.rs +++ b/crates/parser/src/grammar/expressions.rs @@ -449,7 +449,9 @@ fn postfix_dot_expr( let nth1 = if FLOAT_RECOVERY { 0 } else { 1 }; let nth2 = if FLOAT_RECOVERY { 1 } else { 2 }; - if p.nth(nth1) == IDENT && (p.nth(nth2) == T!['('] || p.nth_at(nth2, T![::])) { + if PATH_NAME_REF_KINDS.contains(p.nth(nth1)) + && (p.nth(nth2) == T!['('] || p.nth_at(nth2, T![::])) + { return Ok(method_call_expr::(p, lhs)); } @@ -510,21 +512,26 @@ fn index_expr(p: &mut Parser<'_>, lhs: CompletedMarker) -> CompletedMarker { // y.bar::(1, 2,); // x.0.0.call(); // x.0. call(); +// x.0() // } fn method_call_expr( p: &mut Parser<'_>, lhs: CompletedMarker, ) -> CompletedMarker { if FLOAT_RECOVERY { - assert!(p.nth(0) == IDENT && (p.nth(1) == T!['('] || p.nth_at(1, T![::]))); + assert!(p.at_ts(PATH_NAME_REF_KINDS) && (p.nth(1) == T!['('] || p.nth_at(1, T![::]))); } else { - assert!(p.at(T![.]) && p.nth(1) == IDENT && (p.nth(2) == T!['('] || p.nth_at(2, T![::]))); + assert!( + p.at(T![.]) + && PATH_NAME_REF_KINDS.contains(p.nth(1)) + && (p.nth(2) == T!['('] || p.nth_at(2, T![::])) + ); } let m = lhs.precede(p); if !FLOAT_RECOVERY { p.bump(T![.]); } - name_ref(p); + name_ref_mod_path(p); generic_args::opt_generic_arg_list_expr(p); if p.at(T!['(']) { arg_list(p); @@ -543,6 +550,8 @@ fn method_call_expr( // test field_expr // fn foo() { +// x.self; +// x.Self; // x.foo; // x.0.bar; // x.0.1; @@ -560,8 +569,8 @@ fn field_expr( if !FLOAT_RECOVERY { p.bump(T![.]); } - if p.at(IDENT) || p.at(INT_NUMBER) { - name_ref_or_index(p); + if p.at_ts(PATH_NAME_REF_OR_INDEX_KINDS) { + name_ref_mod_path_or_index(p); } else if p.at(FLOAT_NUMBER) { return match p.split_float(m) { (true, m) => { @@ -679,34 +688,37 @@ pub(crate) fn record_expr_field_list(p: &mut Parser<'_>) { IDENT | INT_NUMBER if p.nth_at(1, T![::]) => { // test_err record_literal_missing_ellipsis_recovery // fn main() { - // S { S::default() } + // S { S::default() }; + // S { 0::default() }; // } m.abandon(p); p.expect(T![..]); expr(p); } + IDENT | INT_NUMBER if p.nth_at(1, T![..]) => { + // test_err record_literal_before_ellipsis_recovery + // fn main() { + // S { field ..S::default() } + // S { 0 ..S::default() } + // } + name_ref_or_index(p); + p.error("expected `:`"); + m.complete(p, RECORD_EXPR_FIELD); + } IDENT | INT_NUMBER => { - if p.nth_at(1, T![..]) { - // test_err record_literal_before_ellipsis_recovery - // fn main() { - // S { field ..S::default() } - // } + // test_err record_literal_field_eq_recovery + // fn main() { + // S { field = foo } + // S { 0 = foo } + // } + if p.nth_at(1, T![:]) { name_ref_or_index(p); - p.error("expected `:`"); - } else { - // test_err record_literal_field_eq_recovery - // fn main() { - // S { field = foo } - // } - if p.nth_at(1, T![:]) { - name_ref_or_index(p); - p.bump(T![:]); - } else if p.nth_at(1, T![=]) { - name_ref_or_index(p); - p.err_and_bump("expected `:`"); - } - expr(p); + p.bump(T![:]); + } else if p.nth_at(1, T![=]) { + name_ref_or_index(p); + p.err_and_bump("expected `:`"); } + expr(p); m.complete(p, RECORD_EXPR_FIELD); } T![.] if p.at(T![..]) => { diff --git a/crates/parser/src/grammar/expressions/atom.rs b/crates/parser/src/grammar/expressions/atom.rs index 97e0392ce1..cd2ce59f62 100644 --- a/crates/parser/src/grammar/expressions/atom.rs +++ b/crates/parser/src/grammar/expressions/atom.rs @@ -259,13 +259,7 @@ fn builtin_expr(p: &mut Parser<'_>) -> Option { type_(p); p.expect(T![,]); while !p.at(EOF) && !p.at(T![')']) { - if p.at(IDENT) || p.at(INT_NUMBER) { - name_ref_or_index(p); - // } else if p.at(FLOAT_NUMBER) { - // FIXME: needs float hack - } else { - p.err_and_bump("expected field name or number"); - } + name_ref_mod_path_or_index(p); if !p.at(T![')']) { p.expect(T![.]); } @@ -465,9 +459,9 @@ fn parse_clobber_abi(p: &mut Parser<'_>) { fn parse_reg(p: &mut Parser<'_>) { p.expect(T!['(']); - if p.at(T![ident]) { + if p.at_ts(PATH_NAME_REF_KINDS) { let m = p.start(); - name_ref(p); + name_ref_mod_path(p); m.complete(p, ASM_REG_SPEC); } else if p.at(T![string]) { let m = p.start(); diff --git a/crates/parser/src/grammar/generic_args.rs b/crates/parser/src/grammar/generic_args.rs index 737010985b..c7d8040b24 100644 --- a/crates/parser/src/grammar/generic_args.rs +++ b/crates/parser/src/grammar/generic_args.rs @@ -59,9 +59,9 @@ pub(crate) fn generic_arg(p: &mut Parser<'_>) -> bool { // test macro_inside_generic_arg // type A = Foo; - IDENT => { + k if PATH_NAME_REF_KINDS.contains(k) => { let m = p.start(); - name_ref(p); + name_ref_mod_path(p); paths::opt_path_type_args(p); match p.current() { T![=] => { diff --git a/crates/parser/src/grammar/generic_params.rs b/crates/parser/src/grammar/generic_params.rs index 92311238c2..08b23cd92a 100644 --- a/crates/parser/src/grammar/generic_params.rs +++ b/crates/parser/src/grammar/generic_params.rs @@ -145,6 +145,9 @@ fn type_bound(p: &mut Parser<'_>) -> bool { T![for] => types::for_type(p, false), // test precise_capturing // fn captures<'a: 'a, 'b: 'b, T>() -> impl Sized + use<'b, T, Self> {} + + // test_err precise_capturing_invalid + // type T = impl use; T![use] if p.nth_at(1, T![<]) => { p.bump_any(); let m = p.start(); @@ -156,14 +159,10 @@ fn type_bound(p: &mut Parser<'_>) -> bool { || "expected identifier or lifetime".into(), TokenSet::new(&[T![Self], IDENT, LIFETIME_IDENT]), |p| { - if p.at(T![Self]) { - let m = p.start(); - p.bump(T![Self]); - m.complete(p, NAME_REF); - } else if p.at(LIFETIME_IDENT) { + if p.at(LIFETIME_IDENT) { lifetime(p); } else { - name_ref(p); + name_ref_or_upper_self(p); } true }, diff --git a/crates/parser/src/grammar/items.rs b/crates/parser/src/grammar/items.rs index 7d98499008..8ece5af527 100644 --- a/crates/parser/src/grammar/items.rs +++ b/crates/parser/src/grammar/items.rs @@ -254,22 +254,16 @@ fn opt_item_without_modifiers(p: &mut Parser<'_>, m: Marker) -> Result<(), Marke // test extern_crate // extern crate foo; +// extern crate self; fn extern_crate(p: &mut Parser<'_>, m: Marker) { p.bump(T![extern]); p.bump(T![crate]); - if p.at(T![self]) { - // test extern_crate_self - // extern crate self; - let m = p.start(); - p.bump(T![self]); - m.complete(p, NAME_REF); - } else { - name_ref(p); - } + name_ref_or_self(p); // test extern_crate_rename // extern crate foo as bar; + // extern crate self as bar; opt_rename(p); p.expect(T![;]); m.complete(p, EXTERN_CRATE); diff --git a/crates/parser/src/grammar/paths.rs b/crates/parser/src/grammar/paths.rs index b3652f7cd3..3410505cd4 100644 --- a/crates/parser/src/grammar/paths.rs +++ b/crates/parser/src/grammar/paths.rs @@ -107,37 +107,32 @@ fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) -> Option { - name_ref(p); - opt_path_args(p, mode); - } + if p.at_ts(PATH_NAME_REF_KINDS) { // test crate_path // use crate::foo; - T![self] | T![super] | T![crate] | T![Self] => { - let m = p.start(); - p.bump_any(); - m.complete(p, NAME_REF); - } - _ => { - let recover_set = match mode { - Mode::Use => items::ITEM_RECOVERY_SET, - Mode::Attr => { - items::ITEM_RECOVERY_SET.union(TokenSet::new(&[T![']'], T![=], T![#]])) - } - Mode::Vis => items::ITEM_RECOVERY_SET.union(TokenSet::new(&[T![')']])), - Mode::Type => TYPE_PATH_SEGMENT_RECOVERY_SET, - Mode::Expr => EXPR_PATH_SEGMENT_RECOVERY_SET, - }; - empty &= p.err_recover("expected identifier", recover_set); - if empty { - // test_err empty_segment - // use crate::; - m.abandon(p); - return None; + name_ref_mod_path(p); + opt_path_args(p, mode); + } else { + let recover_set = match mode { + Mode::Use => items::ITEM_RECOVERY_SET, + Mode::Attr => { + items::ITEM_RECOVERY_SET.union(TokenSet::new(&[T![']'], T![=], T![#]])) } + Mode::Vis => items::ITEM_RECOVERY_SET.union(TokenSet::new(&[T![')']])), + Mode::Type => TYPE_PATH_SEGMENT_RECOVERY_SET, + Mode::Expr => EXPR_PATH_SEGMENT_RECOVERY_SET, + }; + empty &= p.err_recover( + "expected identifier, `self`, `super`, `crate`, or `Self`", + recover_set, + ); + if empty { + // test_err empty_segment + // use crate::; + m.abandon(p); + return None; } - }; + } } Some(m.complete(p, PATH_SEGMENT)) } diff --git a/crates/parser/test_data/generated/runner.rs b/crates/parser/test_data/generated/runner.rs index 6d114037bf..f9486f53c2 100644 --- a/crates/parser/test_data/generated/runner.rs +++ b/crates/parser/test_data/generated/runner.rs @@ -210,10 +210,6 @@ mod ok { run_and_expect_no_errors("test_data/parser/inline/ok/extern_crate_rename.rs"); } #[test] - fn extern_crate_self() { - run_and_expect_no_errors("test_data/parser/inline/ok/extern_crate_self.rs"); - } - #[test] fn field_expr() { run_and_expect_no_errors("test_data/parser/inline/ok/field_expr.rs"); } #[test] fn fn_() { run_and_expect_no_errors("test_data/parser/inline/ok/fn_.rs"); } @@ -774,6 +770,10 @@ mod err { run_and_expect_errors("test_data/parser/inline/err/pointer_type_no_mutability.rs"); } #[test] + fn precise_capturing_invalid() { + run_and_expect_errors("test_data/parser/inline/err/precise_capturing_invalid.rs"); + } + #[test] fn pub_expr() { run_and_expect_errors("test_data/parser/inline/err/pub_expr.rs"); } #[test] fn record_literal_before_ellipsis_recovery() { diff --git a/crates/parser/test_data/parser/err/0004_use_path_bad_segment.rast b/crates/parser/test_data/parser/err/0004_use_path_bad_segment.rast index cf455934e9..7273c98456 100644 --- a/crates/parser/test_data/parser/err/0004_use_path_bad_segment.rast +++ b/crates/parser/test_data/parser/err/0004_use_path_bad_segment.rast @@ -13,4 +13,4 @@ SOURCE_FILE ERROR INT_NUMBER "92" SEMICOLON ";" -error 9: expected identifier +error 9: expected identifier, `self`, `super`, `crate`, or `Self` diff --git a/crates/parser/test_data/parser/err/0048_double_fish.rast b/crates/parser/test_data/parser/err/0048_double_fish.rast index 7ef1eb98fc..47daaf7b3b 100644 --- a/crates/parser/test_data/parser/err/0048_double_fish.rast +++ b/crates/parser/test_data/parser/err/0048_double_fish.rast @@ -115,10 +115,10 @@ SOURCE_FILE WHITESPACE "\n" R_CURLY "}" WHITESPACE "\n" -error 30: expected identifier +error 30: expected identifier, `self`, `super`, `crate`, or `Self` error 31: expected COMMA error 37: expected expression -error 75: expected identifier +error 75: expected identifier, `self`, `super`, `crate`, or `Self` error 76: expected SEMICOLON error 82: expected expression error 83: expected SEMICOLON diff --git a/crates/parser/test_data/parser/inline/err/arg_list_recovery.rast b/crates/parser/test_data/parser/inline/err/arg_list_recovery.rast index cd5aa680c6..755b20bb27 100644 --- a/crates/parser/test_data/parser/inline/err/arg_list_recovery.rast +++ b/crates/parser/test_data/parser/inline/err/arg_list_recovery.rast @@ -98,7 +98,7 @@ SOURCE_FILE WHITESPACE "\n" R_CURLY "}" WHITESPACE "\n" -error 25: expected identifier +error 25: expected identifier, `self`, `super`, `crate`, or `Self` error 39: expected COMMA error 39: expected expression error 55: expected expression diff --git a/crates/parser/test_data/parser/inline/err/crate_visibility_empty_recover.rast b/crates/parser/test_data/parser/inline/err/crate_visibility_empty_recover.rast index 681ca6b6e0..172bc099b5 100644 --- a/crates/parser/test_data/parser/inline/err/crate_visibility_empty_recover.rast +++ b/crates/parser/test_data/parser/inline/err/crate_visibility_empty_recover.rast @@ -11,4 +11,4 @@ SOURCE_FILE IDENT "S" SEMICOLON ";" WHITESPACE "\n" -error 4: expected identifier +error 4: expected identifier, `self`, `super`, `crate`, or `Self` diff --git a/crates/parser/test_data/parser/inline/err/empty_segment.rast b/crates/parser/test_data/parser/inline/err/empty_segment.rast index b03f5ad9f7..7f256218ef 100644 --- a/crates/parser/test_data/parser/inline/err/empty_segment.rast +++ b/crates/parser/test_data/parser/inline/err/empty_segment.rast @@ -11,4 +11,4 @@ SOURCE_FILE COLON2 "::" SEMICOLON ";" WHITESPACE "\n" -error 11: expected identifier +error 11: expected identifier, `self`, `super`, `crate`, or `Self` diff --git a/crates/parser/test_data/parser/inline/err/meta_recovery.rast b/crates/parser/test_data/parser/inline/err/meta_recovery.rast index 926dd50fc8..b5c16e0798 100644 --- a/crates/parser/test_data/parser/inline/err/meta_recovery.rast +++ b/crates/parser/test_data/parser/inline/err/meta_recovery.rast @@ -66,18 +66,18 @@ SOURCE_FILE EQ "=" R_BRACK "]" WHITESPACE "\n" -error 3: expected identifier +error 3: expected identifier, `self`, `super`, `crate`, or `Self` error 11: expected expression error 11: expected expression -error 20: expected identifier -error 28: expected identifier +error 20: expected identifier, `self`, `super`, `crate`, or `Self` +error 28: expected identifier, `self`, `super`, `crate`, or `Self` error 30: expected expression error 30: expected expression error 41: expected L_PAREN -error 41: expected identifier +error 41: expected identifier, `self`, `super`, `crate`, or `Self` error 41: expected R_PAREN error 52: expected L_PAREN -error 52: expected identifier +error 52: expected identifier, `self`, `super`, `crate`, or `Self` error 54: expected expression error 54: expected expression error 54: expected R_PAREN diff --git a/crates/parser/test_data/parser/inline/err/precise_capturing_invalid.rast b/crates/parser/test_data/parser/inline/err/precise_capturing_invalid.rast new file mode 100644 index 0000000000..5ae184c5fe --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/precise_capturing_invalid.rast @@ -0,0 +1,28 @@ +SOURCE_FILE + TYPE_ALIAS + TYPE_KW "type" + WHITESPACE " " + NAME + IDENT "T" + WHITESPACE " " + EQ "=" + WHITESPACE " " + IMPL_TRAIT_TYPE + IMPL_KW "impl" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + USE_KW "use" + USE_BOUND_GENERIC_ARGS + L_ANGLE "<" + ERROR + SELF_KW "self" + COMMA "," + WHITESPACE " " + ERROR + INT_NUMBER "1" + R_ANGLE ">" + SEMICOLON ";" + WHITESPACE "\n" +error 18: expected identifier or `Self` +error 24: expected identifier or `Self` diff --git a/crates/parser/test_data/parser/inline/err/precise_capturing_invalid.rs b/crates/parser/test_data/parser/inline/err/precise_capturing_invalid.rs new file mode 100644 index 0000000000..3180338d33 --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/precise_capturing_invalid.rs @@ -0,0 +1 @@ +type T = impl use; diff --git a/crates/parser/test_data/parser/inline/err/record_literal_before_ellipsis_recovery.rast b/crates/parser/test_data/parser/inline/err/record_literal_before_ellipsis_recovery.rast index 741b7845e7..08ae906421 100644 --- a/crates/parser/test_data/parser/inline/err/record_literal_before_ellipsis_recovery.rast +++ b/crates/parser/test_data/parser/inline/err/record_literal_before_ellipsis_recovery.rast @@ -12,6 +12,38 @@ SOURCE_FILE STMT_LIST L_CURLY "{" WHITESPACE "\n " + EXPR_STMT + RECORD_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" + WHITESPACE " " + RECORD_EXPR_FIELD_LIST + L_CURLY "{" + WHITESPACE " " + RECORD_EXPR_FIELD + NAME_REF + IDENT "field" + WHITESPACE " " + DOT2 ".." + CALL_EXPR + PATH_EXPR + PATH + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" + COLON2 "::" + PATH_SEGMENT + NAME_REF + IDENT "default" + ARG_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + R_CURLY "}" + WHITESPACE "\n " RECORD_EXPR PATH PATH_SEGMENT @@ -23,7 +55,7 @@ SOURCE_FILE WHITESPACE " " RECORD_EXPR_FIELD NAME_REF - IDENT "field" + INT_NUMBER "0" WHITESPACE " " DOT2 ".." CALL_EXPR @@ -47,3 +79,6 @@ SOURCE_FILE WHITESPACE "\n" error 25: expected `:` error 25: expected COMMA +error 42: expected SEMICOLON +error 52: expected `:` +error 52: expected COMMA diff --git a/crates/parser/test_data/parser/inline/err/record_literal_before_ellipsis_recovery.rs b/crates/parser/test_data/parser/inline/err/record_literal_before_ellipsis_recovery.rs index a4e5b2f693..65398ccb88 100644 --- a/crates/parser/test_data/parser/inline/err/record_literal_before_ellipsis_recovery.rs +++ b/crates/parser/test_data/parser/inline/err/record_literal_before_ellipsis_recovery.rs @@ -1,3 +1,4 @@ fn main() { S { field ..S::default() } + S { 0 ..S::default() } } diff --git a/crates/parser/test_data/parser/inline/err/record_literal_field_eq_recovery.rast b/crates/parser/test_data/parser/inline/err/record_literal_field_eq_recovery.rast index ad4deeb0b6..ad3b6f208e 100644 --- a/crates/parser/test_data/parser/inline/err/record_literal_field_eq_recovery.rast +++ b/crates/parser/test_data/parser/inline/err/record_literal_field_eq_recovery.rast @@ -12,6 +12,31 @@ SOURCE_FILE STMT_LIST L_CURLY "{" WHITESPACE "\n " + EXPR_STMT + RECORD_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" + WHITESPACE " " + RECORD_EXPR_FIELD_LIST + L_CURLY "{" + WHITESPACE " " + RECORD_EXPR_FIELD + NAME_REF + IDENT "field" + WHITESPACE " " + ERROR + EQ "=" + WHITESPACE " " + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "foo" + WHITESPACE " " + R_CURLY "}" + WHITESPACE "\n " RECORD_EXPR PATH PATH_SEGMENT @@ -23,7 +48,7 @@ SOURCE_FILE WHITESPACE " " RECORD_EXPR_FIELD NAME_REF - IDENT "field" + INT_NUMBER "0" WHITESPACE " " ERROR EQ "=" @@ -39,3 +64,5 @@ SOURCE_FILE R_CURLY "}" WHITESPACE "\n" error 26: expected `:` +error 33: expected SEMICOLON +error 44: expected `:` diff --git a/crates/parser/test_data/parser/inline/err/record_literal_field_eq_recovery.rs b/crates/parser/test_data/parser/inline/err/record_literal_field_eq_recovery.rs index 1eb1aa9b92..9ddc46e0da 100644 --- a/crates/parser/test_data/parser/inline/err/record_literal_field_eq_recovery.rs +++ b/crates/parser/test_data/parser/inline/err/record_literal_field_eq_recovery.rs @@ -1,3 +1,4 @@ fn main() { S { field = foo } + S { 0 = foo } } diff --git a/crates/parser/test_data/parser/inline/err/record_literal_missing_ellipsis_recovery.rast b/crates/parser/test_data/parser/inline/err/record_literal_missing_ellipsis_recovery.rast index 0c5b618e6f..9cd07d2ea3 100644 --- a/crates/parser/test_data/parser/inline/err/record_literal_missing_ellipsis_recovery.rast +++ b/crates/parser/test_data/parser/inline/err/record_literal_missing_ellipsis_recovery.rast @@ -12,32 +12,70 @@ SOURCE_FILE STMT_LIST L_CURLY "{" WHITESPACE "\n " - RECORD_EXPR - PATH - PATH_SEGMENT - NAME_REF - IDENT "S" - WHITESPACE " " - RECORD_EXPR_FIELD_LIST - L_CURLY "{" + EXPR_STMT + RECORD_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" WHITESPACE " " - CALL_EXPR - PATH_EXPR - PATH + RECORD_EXPR_FIELD_LIST + L_CURLY "{" + WHITESPACE " " + CALL_EXPR + PATH_EXPR PATH + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" + COLON2 "::" PATH_SEGMENT NAME_REF - IDENT "S" - COLON2 "::" - PATH_SEGMENT - NAME_REF - IDENT "default" - ARG_LIST - L_PAREN "(" - R_PAREN ")" + IDENT "default" + ARG_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + R_CURLY "}" + SEMICOLON ";" + WHITESPACE "\n " + EXPR_STMT + RECORD_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" WHITESPACE " " - R_CURLY "}" + RECORD_EXPR_FIELD_LIST + L_CURLY "{" + WHITESPACE " " + LITERAL + INT_NUMBER "0" + ERROR + COLON ":" + ERROR + COLON ":" + RECORD_EXPR_FIELD + CALL_EXPR + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "default" + ARG_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + R_CURLY "}" + SEMICOLON ";" WHITESPACE "\n" R_CURLY "}" WHITESPACE "\n" error 19: expected DOT2 +error 43: expected DOT2 +error 45: expected COMMA +error 45: expected identifier +error 46: expected COMMA +error 46: expected identifier +error 47: expected COMMA diff --git a/crates/parser/test_data/parser/inline/err/record_literal_missing_ellipsis_recovery.rs b/crates/parser/test_data/parser/inline/err/record_literal_missing_ellipsis_recovery.rs index 1b594e8ab9..a63c3c9e7c 100644 --- a/crates/parser/test_data/parser/inline/err/record_literal_missing_ellipsis_recovery.rs +++ b/crates/parser/test_data/parser/inline/err/record_literal_missing_ellipsis_recovery.rs @@ -1,3 +1,4 @@ fn main() { - S { S::default() } + S { S::default() }; + S { 0::default() }; } diff --git a/crates/parser/test_data/parser/inline/ok/extern_crate.rast b/crates/parser/test_data/parser/inline/ok/extern_crate.rast index 0a660957d1..aa555ed229 100644 --- a/crates/parser/test_data/parser/inline/ok/extern_crate.rast +++ b/crates/parser/test_data/parser/inline/ok/extern_crate.rast @@ -8,3 +8,12 @@ SOURCE_FILE IDENT "foo" SEMICOLON ";" WHITESPACE "\n" + EXTERN_CRATE + EXTERN_KW "extern" + WHITESPACE " " + CRATE_KW "crate" + WHITESPACE " " + NAME_REF + SELF_KW "self" + SEMICOLON ";" + WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/extern_crate.rs b/crates/parser/test_data/parser/inline/ok/extern_crate.rs index 49af74e1b7..3c498c8738 100644 --- a/crates/parser/test_data/parser/inline/ok/extern_crate.rs +++ b/crates/parser/test_data/parser/inline/ok/extern_crate.rs @@ -1 +1,2 @@ extern crate foo; +extern crate self; diff --git a/crates/parser/test_data/parser/inline/ok/extern_crate_rename.rast b/crates/parser/test_data/parser/inline/ok/extern_crate_rename.rast index 5a5aca96f9..5f0a5b5bb0 100644 --- a/crates/parser/test_data/parser/inline/ok/extern_crate_rename.rast +++ b/crates/parser/test_data/parser/inline/ok/extern_crate_rename.rast @@ -14,3 +14,18 @@ SOURCE_FILE IDENT "bar" SEMICOLON ";" WHITESPACE "\n" + EXTERN_CRATE + EXTERN_KW "extern" + WHITESPACE " " + CRATE_KW "crate" + WHITESPACE " " + NAME_REF + SELF_KW "self" + WHITESPACE " " + RENAME + AS_KW "as" + WHITESPACE " " + NAME + IDENT "bar" + SEMICOLON ";" + WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/extern_crate_rename.rs b/crates/parser/test_data/parser/inline/ok/extern_crate_rename.rs index fc76e17dda..6d1873d659 100644 --- a/crates/parser/test_data/parser/inline/ok/extern_crate_rename.rs +++ b/crates/parser/test_data/parser/inline/ok/extern_crate_rename.rs @@ -1 +1,2 @@ extern crate foo as bar; +extern crate self as bar; diff --git a/crates/parser/test_data/parser/inline/ok/extern_crate_self.rast b/crates/parser/test_data/parser/inline/ok/extern_crate_self.rast deleted file mode 100644 index edea4245f2..0000000000 --- a/crates/parser/test_data/parser/inline/ok/extern_crate_self.rast +++ /dev/null @@ -1,10 +0,0 @@ -SOURCE_FILE - EXTERN_CRATE - EXTERN_KW "extern" - WHITESPACE " " - CRATE_KW "crate" - WHITESPACE " " - NAME_REF - SELF_KW "self" - SEMICOLON ";" - WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/extern_crate_self.rs b/crates/parser/test_data/parser/inline/ok/extern_crate_self.rs deleted file mode 100644 index c969ed1093..0000000000 --- a/crates/parser/test_data/parser/inline/ok/extern_crate_self.rs +++ /dev/null @@ -1 +0,0 @@ -extern crate self; diff --git a/crates/parser/test_data/parser/inline/ok/field_expr.rast b/crates/parser/test_data/parser/inline/ok/field_expr.rast index dd27dc4896..3bac226d34 100644 --- a/crates/parser/test_data/parser/inline/ok/field_expr.rast +++ b/crates/parser/test_data/parser/inline/ok/field_expr.rast @@ -12,6 +12,30 @@ SOURCE_FILE STMT_LIST L_CURLY "{" WHITESPACE "\n " + EXPR_STMT + FIELD_EXPR + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "x" + DOT "." + NAME_REF + SELF_KW "self" + SEMICOLON ";" + WHITESPACE "\n " + EXPR_STMT + FIELD_EXPR + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "x" + DOT "." + NAME_REF + SELF_TYPE_KW "Self" + SEMICOLON ";" + WHITESPACE "\n " EXPR_STMT FIELD_EXPR PATH_EXPR diff --git a/crates/parser/test_data/parser/inline/ok/field_expr.rs b/crates/parser/test_data/parser/inline/ok/field_expr.rs index 98dbe45a7e..b156d6f7d8 100644 --- a/crates/parser/test_data/parser/inline/ok/field_expr.rs +++ b/crates/parser/test_data/parser/inline/ok/field_expr.rs @@ -1,4 +1,6 @@ fn foo() { + x.self; + x.Self; x.foo; x.0.bar; x.0.1; diff --git a/crates/parser/test_data/parser/inline/ok/method_call_expr.rast b/crates/parser/test_data/parser/inline/ok/method_call_expr.rast index b28b8eb673..3245042cf2 100644 --- a/crates/parser/test_data/parser/inline/ok/method_call_expr.rast +++ b/crates/parser/test_data/parser/inline/ok/method_call_expr.rast @@ -101,6 +101,20 @@ SOURCE_FILE L_PAREN "(" R_PAREN ")" SEMICOLON ";" + WHITESPACE "\n " + CALL_EXPR + FIELD_EXPR + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "x" + DOT "." + NAME_REF + INT_NUMBER "0" + ARG_LIST + L_PAREN "(" + R_PAREN ")" WHITESPACE "\n" R_CURLY "}" WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/method_call_expr.rs b/crates/parser/test_data/parser/inline/ok/method_call_expr.rs index 48bb6381e8..bb54d75c19 100644 --- a/crates/parser/test_data/parser/inline/ok/method_call_expr.rs +++ b/crates/parser/test_data/parser/inline/ok/method_call_expr.rs @@ -3,4 +3,5 @@ fn foo() { y.bar::(1, 2,); x.0.0.call(); x.0. call(); + x.0() } From 405520150d851034aa6e5909b348a38bb2e2b1f2 Mon Sep 17 00:00:00 2001 From: Shoyu Vanilla Date: Fri, 6 Dec 2024 01:10:46 +0900 Subject: [PATCH 45/95] fix: Panic when displaying generic params with defaults --- crates/hir-ty/src/display.rs | 6 ++++- crates/ide/src/hover/tests.rs | 50 +++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs index 94a340fbec..3dfa0e97ce 100644 --- a/crates/hir-ty/src/display.rs +++ b/crates/hir-ty/src/display.rs @@ -1047,10 +1047,14 @@ impl HirDisplay for Ty { ); // We print all params except implicit impl Trait params. Still a bit weird; should we leave out parent and self? if parameters.len() - impl_ > 0 { + let params_len = parameters.len(); // `parameters` are in the order of fn's params (including impl traits), fn's lifetimes let parameters = generic_args_sans_defaults(f, Some(generic_def_id), parameters); - let without_impl = self_param as usize + type_ + const_ + lifetime; + assert!(params_len >= parameters.len()); + let defaults = params_len - parameters.len(); + let without_impl = + self_param as usize + type_ + const_ + lifetime - defaults; // parent's params (those from enclosing impl or trait, if any). let (fn_params, parent_params) = parameters.split_at(without_impl + impl_); diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs index 0986d5542c..1c08514a67 100644 --- a/crates/ide/src/hover/tests.rs +++ b/crates/ide/src/hover/tests.rs @@ -9416,3 +9416,53 @@ fn f "#]], ); } + +#[test] +fn issue_18613() { + check( + r#" +fn main() { + struct S(); + let x$0 = S::<()>; +}"#, + expect![[r#" + *x* + + ```rust + let x: fn S<()>() -> S<()> + ``` + + --- + + size = 0, align = 1 + "#]], + ); + + check( + r#" +pub struct Global; +pub struct Box(T, A); + +impl Box { + pub fn new(x: T) -> Self { loop {} } +} + +pub struct String; + +fn main() { + let box_value$0 = Box::new(); +} +"#, + expect![[r#" + *box_value* + + ```rust + let box_value: fn Box(String, Global) -> Box + ``` + + --- + + size = 0, align = 1 + "#]], + ); +} From f3d7415bd6a7641a92b14735dd1bab32c967d231 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 5 Dec 2024 14:37:38 +0100 Subject: [PATCH 46/95] Parse lifetime bounds in lifetime param into TypeBoundList This mainly aids in error recovery but also makes it a bit easier to handle lifetime resolution. While doing so it also came apparent that we were not actually lowering lifetime outlives relationships within lifetime parameter declaration bounds, so this fixes that. --- crates/hir-def/src/item_tree/pretty.rs | 4 +- crates/hir-def/src/item_tree/tests.rs | 6 ++- crates/hir-ty/src/tests/traits.rs | 23 +++++++++ crates/hir/src/semantics.rs | 4 ++ .../src/completions/lifetime.rs | 36 ++++++-------- crates/ide-completion/src/context.rs | 5 +- crates/ide-completion/src/context/analysis.rs | 17 +++---- crates/ide-db/src/defs.rs | 10 ---- crates/intern/src/symbol/symbols.rs | 1 + crates/parser/src/grammar/generic_params.rs | 15 ++++-- .../parser/inline/ok/lifetime_param.rast | 6 ++- .../parser/inline/ok/precise_capturing.rast | 12 +++-- .../parser/ok/0018_struct_type_params.rast | 48 ++++++++++++------- .../parser/ok/0020_type_param_bounds.rast | 23 +++++---- 14 files changed, 125 insertions(+), 85 deletions(-) diff --git a/crates/hir-def/src/item_tree/pretty.rs b/crates/hir-def/src/item_tree/pretty.rs index 0c5e3a3620..70bf2f13c8 100644 --- a/crates/hir-def/src/item_tree/pretty.rs +++ b/crates/hir-def/src/item_tree/pretty.rs @@ -648,9 +648,9 @@ impl Printer<'_> { let (target, bound) = match pred { WherePredicate::TypeBound { target, bound } => (target, bound), WherePredicate::Lifetime { target, bound } => { - wln!( + w!( this, - "{}: {},", + "{}: {}", target.name.display(self.db.upcast(), edition), bound.name.display(self.db.upcast(), edition) ); diff --git a/crates/hir-def/src/item_tree/tests.rs b/crates/hir-def/src/item_tree/tests.rs index 5c07369f4b..0f53969d6c 100644 --- a/crates/hir-def/src/item_tree/tests.rs +++ b/crates/hir-def/src/item_tree/tests.rs @@ -351,7 +351,8 @@ trait Tr<'a, T: 'a>: Super where Self: for<'a> Tr<'a, T> {} where T: Copy, T: 'a, - T: 'b + T: 'b, + 'b: 'a { pub(self) field: &'a &'b T, } @@ -370,7 +371,8 @@ trait Tr<'a, T: 'a>: Super where Self: for<'a> Tr<'a, T> {} where T: Copy, T: 'a, - T: 'b + T: 'b, + 'b: 'a { // AstId: 9 pub(self) fn f( diff --git a/crates/hir-ty/src/tests/traits.rs b/crates/hir-ty/src/tests/traits.rs index 624148cab2..b62672d21e 100644 --- a/crates/hir-ty/src/tests/traits.rs +++ b/crates/hir-ty/src/tests/traits.rs @@ -1630,6 +1630,29 @@ fn test<'lifetime>( ); } +#[test] +fn lifetime_bounds() { + check_infer( + r#" +//- minicore: sized, coerce_unsized +trait Trait<'a>: Sized { + fn f(&'a self) {} +} +fn test<'a, 'b: 'a>(it: impl Trait<'a>){ + it.f(); +} +"#, + expect![[r#" + 38..42 'self': &'a Self + 44..46 '{}': () + 69..71 'it': impl Trait<'a> + 88..103 '{ it.f(); }': () + 94..96 'it': impl Trait<'a> + 94..100 'it.f()': () + "#]], + ); +} + #[test] fn error_bound_chalk() { check_types( diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 65470d061b..0b09cf2792 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -2026,6 +2026,10 @@ impl SemanticsScope<'_> { ) } + pub fn generic_def(&self) -> Option { + self.resolver.generic_def().map(|id| id.into()) + } + pub fn extern_crates(&self) -> impl Iterator + '_ { self.resolver.extern_crates_in_scope().map(|(name, id)| (name, Module { id })) } diff --git a/crates/ide-completion/src/completions/lifetime.rs b/crates/ide-completion/src/completions/lifetime.rs index 9efc52428e..0692446381 100644 --- a/crates/ide-completion/src/completions/lifetime.rs +++ b/crates/ide-completion/src/completions/lifetime.rs @@ -8,7 +8,6 @@ //! show up for normal completions, or they won't show completions other than lifetimes depending //! on the fixture input. use hir::{sym, Name, ScopeDef}; -use syntax::{ast, ToSmolStr, TokenText}; use crate::{ completions::Completions, @@ -21,33 +20,24 @@ pub(crate) fn complete_lifetime( ctx: &CompletionContext<'_>, lifetime_ctx: &LifetimeContext, ) { - let (lp, lifetime) = match lifetime_ctx { - LifetimeContext { kind: LifetimeKind::Lifetime, lifetime } => (None, lifetime), - LifetimeContext { - kind: LifetimeKind::LifetimeParam { is_decl: false, param }, - lifetime, - } => (Some(param), lifetime), - _ => return, + let &LifetimeContext { kind: LifetimeKind::Lifetime { in_lifetime_param_bound, def }, .. } = + lifetime_ctx + else { + return; }; - let param_lifetime = match (lifetime, lp.and_then(|lp| lp.lifetime())) { - (Some(lt), Some(lp)) if lp == lt.clone() => return, - (Some(_), Some(lp)) => Some(lp), - _ => None, - }; - let param_lifetime = param_lifetime.as_ref().map(ast::Lifetime::text); - let param_lifetime = param_lifetime.as_ref().map(TokenText::as_str); ctx.process_all_names_raw(&mut |name, res| { - if matches!( - res, - ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_)) - if param_lifetime != Some(&*name.display_no_db(ctx.edition).to_smolstr()) - ) { + if matches!(res, ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_))) { acc.add_lifetime(ctx, name); } }); - if param_lifetime.is_none() { - acc.add_lifetime(ctx, Name::new_symbol_root(sym::tick_static.clone())); + acc.add_lifetime(ctx, Name::new_symbol_root(sym::tick_static.clone())); + if !in_lifetime_param_bound + && def.is_some_and(|def| { + !matches!(def, hir::GenericDef::Function(_) | hir::GenericDef::Impl(_)) + }) + { + acc.add_lifetime(ctx, Name::new_symbol_root(sym::tick_underscore.clone())); } } @@ -222,6 +212,8 @@ fn foo<'footime, 'lifetime: 'a$0>() {} "#, expect![[r#" lt 'footime + lt 'lifetime + lt 'static "#]], ); } diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index 5b8d1c30a2..3a66170633 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -290,15 +290,14 @@ pub(crate) struct ParamContext { /// The state of the lifetime we are completing. #[derive(Debug)] pub(crate) struct LifetimeContext { - pub(crate) lifetime: Option, pub(crate) kind: LifetimeKind, } /// The kind of lifetime we are completing. #[derive(Debug)] pub(crate) enum LifetimeKind { - LifetimeParam { is_decl: bool, param: ast::LifetimeParam }, - Lifetime, + LifetimeParam, + Lifetime { in_lifetime_param_bound: bool, def: Option }, LabelRef, LabelDef, } diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs index a4e018b180..4a678963b9 100644 --- a/crates/ide-completion/src/context/analysis.rs +++ b/crates/ide-completion/src/context/analysis.rs @@ -562,7 +562,7 @@ fn expected_type_and_name( } fn classify_lifetime( - _sema: &Semantics<'_, RootDatabase>, + sema: &Semantics<'_, RootDatabase>, original_file: &SyntaxNode, lifetime: ast::Lifetime, ) -> Option { @@ -571,21 +571,22 @@ fn classify_lifetime( return None; } + let lifetime = + find_node_at_offset::(original_file, lifetime.syntax().text_range().start()); let kind = match_ast! { match parent { - ast::LifetimeParam(param) => LifetimeKind::LifetimeParam { - is_decl: param.lifetime().as_ref() == Some(&lifetime), - param - }, + ast::LifetimeParam(_) => LifetimeKind::LifetimeParam, ast::BreakExpr(_) => LifetimeKind::LabelRef, ast::ContinueExpr(_) => LifetimeKind::LabelRef, ast::Label(_) => LifetimeKind::LabelDef, - _ => LifetimeKind::Lifetime, + _ => { + let def = lifetime.as_ref().and_then(|lt| sema.scope(lt.syntax())?.generic_def()); + LifetimeKind::Lifetime { in_lifetime_param_bound: ast::TypeBound::can_cast(parent.kind()), def } + }, } }; - let lifetime = find_node_at_offset(original_file, lifetime.syntax().text_range().start()); - Some(LifetimeContext { lifetime, kind }) + Some(LifetimeContext { kind }) } fn classify_name( diff --git a/crates/ide-db/src/defs.rs b/crates/ide-db/src/defs.rs index fdac4dd2ef..5eec33636b 100644 --- a/crates/ide-db/src/defs.rs +++ b/crates/ide-db/src/defs.rs @@ -772,16 +772,6 @@ impl NameRefClass { .map(GenericParam::LifetimeParam) .map(Definition::GenericParam) .map(NameRefClass::Definition), - // lifetime bounds, as in the 'b in 'a: 'b aren't wrapped in TypeBound nodes so we gotta check - // if our lifetime is in a LifetimeParam without being the constrained lifetime - _ if ast::LifetimeParam::cast(parent).and_then(|param| param.lifetime()).as_ref() - != Some(lifetime) => - { - sema.resolve_lifetime_param(lifetime) - .map(GenericParam::LifetimeParam) - .map(Definition::GenericParam) - .map(NameRefClass::Definition) - } _ => None, } } diff --git a/crates/intern/src/symbol/symbols.rs b/crates/intern/src/symbol/symbols.rs index 8f79cf2007..ebc9c14059 100644 --- a/crates/intern/src/symbol/symbols.rs +++ b/crates/intern/src/symbol/symbols.rs @@ -80,6 +80,7 @@ define_symbols! { self_ = "self", Self_ = "Self", tick_static = "'static", + tick_underscore = "'_", dollar_crate = "$crate", MISSING_NAME = "[missing name]", fn_ = "fn", diff --git a/crates/parser/src/grammar/generic_params.rs b/crates/parser/src/grammar/generic_params.rs index 08b23cd92a..9d4fdbfaf2 100644 --- a/crates/parser/src/grammar/generic_params.rs +++ b/crates/parser/src/grammar/generic_params.rs @@ -56,7 +56,7 @@ fn generic_param(p: &mut Parser<'_>, m: Marker) -> bool { fn lifetime_param(p: &mut Parser<'_>, m: Marker) { assert!(p.at(LIFETIME_IDENT)); lifetime(p); - if p.at(T![:]) { + if p.eat(T![:]) { lifetime_bounds(p); } m.complete(p, LIFETIME_PARAM); @@ -106,14 +106,19 @@ fn const_param(p: &mut Parser<'_>, m: Marker) { } fn lifetime_bounds(p: &mut Parser<'_>) { - assert!(p.at(T![:])); - p.bump(T![:]); - while p.at(LIFETIME_IDENT) { - lifetime(p); + let marker = p.start(); + while { + if !matches!(p.current(), LIFETIME_IDENT | T![>] | T![,]) { + p.error("expected lifetime"); + } + + type_bound(p) + } { if !p.eat(T![+]) { break; } } + marker.complete(p, TYPE_BOUND_LIST); } // test type_param_bounds diff --git a/crates/parser/test_data/parser/inline/ok/lifetime_param.rast b/crates/parser/test_data/parser/inline/ok/lifetime_param.rast index c595031f35..315200aca2 100644 --- a/crates/parser/test_data/parser/inline/ok/lifetime_param.rast +++ b/crates/parser/test_data/parser/inline/ok/lifetime_param.rast @@ -11,8 +11,10 @@ SOURCE_FILE LIFETIME_IDENT "'a" COLON ":" WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'b" + TYPE_BOUND_LIST + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'b" R_ANGLE ">" PARAM_LIST L_PAREN "(" diff --git a/crates/parser/test_data/parser/inline/ok/precise_capturing.rast b/crates/parser/test_data/parser/inline/ok/precise_capturing.rast index f9c0a245af..5a67cc2176 100644 --- a/crates/parser/test_data/parser/inline/ok/precise_capturing.rast +++ b/crates/parser/test_data/parser/inline/ok/precise_capturing.rast @@ -11,8 +11,10 @@ SOURCE_FILE LIFETIME_IDENT "'a" COLON ":" WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'a" + TYPE_BOUND_LIST + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'a" COMMA "," WHITESPACE " " LIFETIME_PARAM @@ -20,8 +22,10 @@ SOURCE_FILE LIFETIME_IDENT "'b" COLON ":" WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'b" + TYPE_BOUND_LIST + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'b" COMMA "," WHITESPACE " " TYPE_PARAM diff --git a/crates/parser/test_data/parser/ok/0018_struct_type_params.rast b/crates/parser/test_data/parser/ok/0018_struct_type_params.rast index 11ebc7efb9..1e4eb15609 100644 --- a/crates/parser/test_data/parser/ok/0018_struct_type_params.rast +++ b/crates/parser/test_data/parser/ok/0018_struct_type_params.rast @@ -96,6 +96,7 @@ SOURCE_FILE LIFETIME LIFETIME_IDENT "'a" COLON ":" + TYPE_BOUND_LIST R_ANGLE ">" SEMICOLON ";" WHITESPACE "\n" @@ -111,8 +112,10 @@ SOURCE_FILE LIFETIME_IDENT "'a" COLON ":" WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'b" + TYPE_BOUND_LIST + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'b" R_ANGLE ">" SEMICOLON ";" WHITESPACE "\n" @@ -128,10 +131,12 @@ SOURCE_FILE LIFETIME_IDENT "'a" COLON ":" WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'b" - WHITESPACE " " - PLUS "+" + TYPE_BOUND_LIST + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'b" + WHITESPACE " " + PLUS "+" WHITESPACE " " R_ANGLE ">" SEMICOLON ";" @@ -148,13 +153,16 @@ SOURCE_FILE LIFETIME_IDENT "'a" COLON ":" WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'b" - WHITESPACE " " - PLUS "+" - WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'c" + TYPE_BOUND_LIST + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'b" + WHITESPACE " " + PLUS "+" + WHITESPACE " " + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'c" R_ANGLE ">" SEMICOLON ";" WHITESPACE "\n" @@ -202,9 +210,11 @@ SOURCE_FILE LIFETIME_IDENT "'a" COLON ":" WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'b" - PLUS "+" + TYPE_BOUND_LIST + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'b" + PLUS "+" COMMA "," WHITESPACE " " LIFETIME_PARAM @@ -212,8 +222,10 @@ SOURCE_FILE LIFETIME_IDENT "'b" COLON ":" WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'c" + TYPE_BOUND_LIST + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'c" COMMA "," R_ANGLE ">" SEMICOLON ";" diff --git a/crates/parser/test_data/parser/ok/0020_type_param_bounds.rast b/crates/parser/test_data/parser/ok/0020_type_param_bounds.rast index 043a966ff9..448cf49446 100644 --- a/crates/parser/test_data/parser/ok/0020_type_param_bounds.rast +++ b/crates/parser/test_data/parser/ok/0020_type_param_bounds.rast @@ -237,8 +237,10 @@ SOURCE_FILE LIFETIME_IDENT "'a" COLON ":" WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'d" + TYPE_BOUND_LIST + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'d" COMMA "," WHITESPACE " " LIFETIME_PARAM @@ -246,13 +248,16 @@ SOURCE_FILE LIFETIME_IDENT "'d" COLON ":" WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'a" - WHITESPACE " " - PLUS "+" - WHITESPACE " " - LIFETIME - LIFETIME_IDENT "'b" + TYPE_BOUND_LIST + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'a" + WHITESPACE " " + PLUS "+" + WHITESPACE " " + TYPE_BOUND + LIFETIME + LIFETIME_IDENT "'b" COMMA "," WHITESPACE " " TYPE_PARAM From d6b62265b5ca5de67e3da281b42c144f115357e4 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 5 Dec 2024 14:37:38 +0100 Subject: [PATCH 47/95] fix: Resolve generic parameters within use captures --- crates/hir-def/src/resolver.rs | 4 +- crates/hir/src/semantics.rs | 4 ++ crates/hir/src/source_analyzer.rs | 8 ++++ crates/ide-db/src/defs.rs | 7 ++++ crates/ide/src/rename.rs | 69 +++++++++++++++++++++++++++++++ 5 files changed, 90 insertions(+), 2 deletions(-) diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs index 0ca7070fd0..316406c151 100644 --- a/crates/hir-def/src/resolver.rs +++ b/crates/hir-def/src/resolver.rs @@ -642,9 +642,9 @@ impl Resolver { }) } - pub fn generic_params(&self) -> Option<&Arc> { + pub fn generic_params(&self) -> Option<&GenericParams> { self.scopes().find_map(|scope| match scope { - Scope::GenericParams { params, .. } => Some(params), + Scope::GenericParams { params, .. } => Some(&**params), _ => None, }) } diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 0b09cf2792..f9d3f9d07e 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -1517,6 +1517,10 @@ impl<'db> SemanticsImpl<'db> { self.analyze(path.syntax())?.resolve_path(self.db, path) } + pub fn resolve_use_type_arg(&self, name: &ast::NameRef) -> Option { + self.analyze(name.syntax())?.resolve_use_type_arg(name) + } + pub fn resolve_mod_path( &self, scope: &SyntaxNode, diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs index 56ed81f053..4329a888b3 100644 --- a/crates/hir/src/source_analyzer.rs +++ b/crates/hir/src/source_analyzer.rs @@ -642,6 +642,14 @@ impl SourceAnalyzer { } } + pub(crate) fn resolve_use_type_arg(&self, name: &ast::NameRef) -> Option { + let name = name.as_name(); + self.resolver + .all_generic_params() + .find_map(|(params, parent)| params.find_type_by_name(&name, *parent)) + .map(crate::TypeParam::from) + } + pub(crate) fn resolve_path( &self, db: &dyn HirDatabase, diff --git a/crates/ide-db/src/defs.rs b/crates/ide-db/src/defs.rs index 5eec33636b..932ca37302 100644 --- a/crates/ide-db/src/defs.rs +++ b/crates/ide-db/src/defs.rs @@ -733,6 +733,12 @@ impl NameRefClass { } None }, + ast::UseBoundGenericArgs(_) => { + sema.resolve_use_type_arg(name_ref) + .map(GenericParam::TypeParam) + .map(Definition::GenericParam) + .map(NameRefClass::Definition) + }, ast::ExternCrate(extern_crate_ast) => { let extern_crate = sema.to_def(&extern_crate_ast)?; let krate = extern_crate.resolved_crate(sema.db)?; @@ -764,6 +770,7 @@ impl NameRefClass { sema.resolve_label(lifetime).map(Definition::Label).map(NameRefClass::Definition) } SyntaxKind::LIFETIME_ARG + | SyntaxKind::USE_BOUND_GENERIC_ARGS | SyntaxKind::SELF_PARAM | SyntaxKind::TYPE_BOUND | SyntaxKind::WHERE_PRED diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs index 665fc954d2..a9519c03b3 100644 --- a/crates/ide/src/rename.rs +++ b/crates/ide/src/rename.rs @@ -3102,6 +3102,75 @@ fn main() { let _: S; } r#" use lib::S as Baz; fn main() { let _: Baz; } +"#, + ); + } + + #[test] + fn rename_type_param_ref_in_use_bound() { + check( + "U", + r#" +fn foo() -> impl use Trait {} +"#, + r#" +fn foo() -> impl use Trait {} +"#, + ); + } + + #[test] + fn rename_type_param_in_use_bound() { + check( + "U", + r#" +fn foo() -> impl use Trait {} +"#, + r#" +fn foo() -> impl use Trait {} +"#, + ); + } + + #[test] + fn rename_lifetime_param_ref_in_use_bound() { + check( + "u", + r#" +fn foo<'t>() -> impl use<'t$0> Trait {} +"#, + r#" +fn foo<'u>() -> impl use<'u> Trait {} +"#, + ); + } + + #[test] + fn rename_lifetime_param_in_use_bound() { + check( + "u", + r#" +fn foo<'t$0>() -> impl use<'t> Trait {} +"#, + r#" +fn foo<'u>() -> impl use<'u> Trait {} +"#, + ); + } + + #[test] + fn rename_parent_type_param_in_use_bound() { + check( + "U", + r#" +trait Trait { + fn foo() -> impl use Trait {} +} +"#, + r#" +trait Trait { + fn foo() -> impl use Trait {} +} "#, ); } From a049941c1af7e6e81532420fdb192f4324eeb178 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 5 Dec 2024 20:25:05 +0100 Subject: [PATCH 48/95] fix: Fix parsing of dyn T in generic arg on 2015 edition --- crates/parser/src/grammar/generic_args.rs | 5 ++- crates/parser/src/grammar/types.rs | 2 +- crates/parser/test_data/generated/runner.rs | 7 ++++ ...on_2015_dyn_prefix_inside_generic_arg.rast | 32 +++++++++++++++++++ ...tion_2015_dyn_prefix_inside_generic_arg.rs | 2 ++ .../parser/inline/ok/generic_arg.rast | 21 ++++++++++++ .../test_data/parser/inline/ok/generic_arg.rs | 2 +- 7 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 crates/parser/test_data/parser/inline/ok/edition_2015_dyn_prefix_inside_generic_arg.rast create mode 100644 crates/parser/test_data/parser/inline/ok/edition_2015_dyn_prefix_inside_generic_arg.rs diff --git a/crates/parser/src/grammar/generic_args.rs b/crates/parser/src/grammar/generic_args.rs index c7d8040b24..b9d5bff663 100644 --- a/crates/parser/src/grammar/generic_args.rs +++ b/crates/parser/src/grammar/generic_args.rs @@ -45,7 +45,7 @@ pub(crate) const GENERIC_ARG_FIRST: TokenSet = TokenSet::new(&[ const GENERIC_ARG_RECOVERY_SET: TokenSet = TokenSet::new(&[T![>], T![,]]); // test generic_arg -// type T = S; +// type T = S; pub(crate) fn generic_arg(p: &mut Parser<'_>) -> bool { match p.current() { LIFETIME_IDENT if !p.nth_at(1, T![+]) => lifetime_arg(p), @@ -57,6 +57,9 @@ pub(crate) fn generic_arg(p: &mut Parser<'_>) -> bool { // type ParenthesizedArgs = Foo; // type RTN = Foo; + // test edition_2015_dyn_prefix_inside_generic_arg 2015 + // type A = Foo; + T![ident] if !p.edition().at_least_2018() && types::is_dyn_weak(p) => type_arg(p), // test macro_inside_generic_arg // type A = Foo; k if PATH_NAME_REF_KINDS.contains(k) => { diff --git a/crates/parser/src/grammar/types.rs b/crates/parser/src/grammar/types.rs index 35198ccbe3..0133b7d5d8 100644 --- a/crates/parser/src/grammar/types.rs +++ b/crates/parser/src/grammar/types.rs @@ -58,7 +58,7 @@ fn type_with_bounds_cond(p: &mut Parser<'_>, allow_bounds: bool) { } } -fn is_dyn_weak(p: &Parser<'_>) -> bool { +pub(crate) fn is_dyn_weak(p: &Parser<'_>) -> bool { const WEAK_DYN_PATH_FIRST: TokenSet = TokenSet::new(&[ IDENT, T![self], diff --git a/crates/parser/test_data/generated/runner.rs b/crates/parser/test_data/generated/runner.rs index f9486f53c2..0beaf1ae3a 100644 --- a/crates/parser/test_data/generated/runner.rs +++ b/crates/parser/test_data/generated/runner.rs @@ -190,6 +190,13 @@ mod ok { ); } #[test] + fn edition_2015_dyn_prefix_inside_generic_arg() { + run_and_expect_no_errors_with_edition( + "test_data/parser/inline/ok/edition_2015_dyn_prefix_inside_generic_arg.rs", + crate::Edition::Edition2015, + ); + } + #[test] fn effect_blocks() { run_and_expect_no_errors("test_data/parser/inline/ok/effect_blocks.rs"); } #[test] fn exclusive_range_pat() { diff --git a/crates/parser/test_data/parser/inline/ok/edition_2015_dyn_prefix_inside_generic_arg.rast b/crates/parser/test_data/parser/inline/ok/edition_2015_dyn_prefix_inside_generic_arg.rast new file mode 100644 index 0000000000..24e671b343 --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/edition_2015_dyn_prefix_inside_generic_arg.rast @@ -0,0 +1,32 @@ +SOURCE_FILE + TYPE_ALIAS + COMMENT "// 2015" + WHITESPACE "\n" + TYPE_KW "type" + WHITESPACE " " + NAME + IDENT "A" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Foo" + GENERIC_ARG_LIST + L_ANGLE "<" + TYPE_ARG + DYN_TRAIT_TYPE + DYN_KW "dyn" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + R_ANGLE ">" + SEMICOLON ";" + WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/edition_2015_dyn_prefix_inside_generic_arg.rs b/crates/parser/test_data/parser/inline/ok/edition_2015_dyn_prefix_inside_generic_arg.rs new file mode 100644 index 0000000000..84cece5748 --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/edition_2015_dyn_prefix_inside_generic_arg.rs @@ -0,0 +1,2 @@ +// 2015 +type A = Foo; diff --git a/crates/parser/test_data/parser/inline/ok/generic_arg.rast b/crates/parser/test_data/parser/inline/ok/generic_arg.rast index 5a01f154ba..e32cf08565 100644 --- a/crates/parser/test_data/parser/inline/ok/generic_arg.rast +++ b/crates/parser/test_data/parser/inline/ok/generic_arg.rast @@ -20,6 +20,27 @@ SOURCE_FILE PATH_SEGMENT NAME_REF IDENT "i32" + COMMA "," + WHITESPACE " " + TYPE_ARG + DYN_TRAIT_TYPE + DYN_KW "dyn" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + COMMA "," + WHITESPACE " " + TYPE_ARG + FN_PTR_TYPE + FN_KW "fn" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" R_ANGLE ">" SEMICOLON ";" WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/generic_arg.rs b/crates/parser/test_data/parser/inline/ok/generic_arg.rs index f2ccc558bb..cf991b5b36 100644 --- a/crates/parser/test_data/parser/inline/ok/generic_arg.rs +++ b/crates/parser/test_data/parser/inline/ok/generic_arg.rs @@ -1 +1 @@ -type T = S; +type T = S; From 4a24e729b07bb4547b28e2eb8dc79e7a805687c6 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 6 Dec 2024 05:53:44 +0100 Subject: [PATCH 49/95] fix: Fix parser getting stuck for bad asm expressions --- crates/parser/src/grammar/expressions/atom.rs | 18 +++++-- crates/parser/test_data/generated/runner.rs | 2 + .../parser/inline/err/bad_asm_expr.rast | 50 +++++++++++++++++++ .../parser/inline/err/bad_asm_expr.rs | 5 ++ 4 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 crates/parser/test_data/parser/inline/err/bad_asm_expr.rast create mode 100644 crates/parser/test_data/parser/inline/err/bad_asm_expr.rs diff --git a/crates/parser/src/grammar/expressions/atom.rs b/crates/parser/src/grammar/expressions/atom.rs index cd2ce59f62..407320e1d0 100644 --- a/crates/parser/src/grammar/expressions/atom.rs +++ b/crates/parser/src/grammar/expressions/atom.rs @@ -345,10 +345,7 @@ fn parse_asm_expr(p: &mut Parser<'_>, m: Marker) -> Option { name(p); p.bump(T![=]); allow_templates = false; - true - } else { - false - }; + } let op = p.start(); let dir_spec = p.start(); @@ -399,6 +396,19 @@ fn parse_asm_expr(p: &mut Parser<'_>, m: Marker) -> Option { op.abandon(p); op_n.abandon(p); p.err_and_bump("expected asm operand"); + + // improves error recovery and handles err_and_bump recovering from `{` which gets + // the parser stuck here + if p.at(T!['{']) { + // test_err bad_asm_expr + // fn foo() { + // builtin#asm( + // label crashy = { return; } + // ); + // } + expr(p); + } + if p.at(T!['}']) { break; } diff --git a/crates/parser/test_data/generated/runner.rs b/crates/parser/test_data/generated/runner.rs index f9486f53c2..c4ffc6cadc 100644 --- a/crates/parser/test_data/generated/runner.rs +++ b/crates/parser/test_data/generated/runner.rs @@ -704,6 +704,8 @@ mod err { run_and_expect_errors("test_data/parser/inline/err/async_without_semicolon.rs"); } #[test] + fn bad_asm_expr() { run_and_expect_errors("test_data/parser/inline/err/bad_asm_expr.rs"); } + #[test] fn comma_after_functional_update_syntax() { run_and_expect_errors( "test_data/parser/inline/err/comma_after_functional_update_syntax.rs", diff --git a/crates/parser/test_data/parser/inline/err/bad_asm_expr.rast b/crates/parser/test_data/parser/inline/err/bad_asm_expr.rast new file mode 100644 index 0000000000..306446e64d --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/bad_asm_expr.rast @@ -0,0 +1,50 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "foo" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE "\n " + EXPR_STMT + ASM_EXPR + BUILTIN_KW "builtin" + POUND "#" + ASM_KW "asm" + L_PAREN "(" + WHITESPACE "\n " + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "label" + WHITESPACE " " + NAME + IDENT "crashy" + WHITESPACE " " + EQ "=" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE " " + EXPR_STMT + RETURN_EXPR + RETURN_KW "return" + SEMICOLON ";" + WHITESPACE " " + R_CURLY "}" + WHITESPACE "\n " + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" + R_CURLY "}" + WHITESPACE "\n" +error 41: expected COMMA +error 50: expected asm operand diff --git a/crates/parser/test_data/parser/inline/err/bad_asm_expr.rs b/crates/parser/test_data/parser/inline/err/bad_asm_expr.rs new file mode 100644 index 0000000000..6056f925e3 --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/bad_asm_expr.rs @@ -0,0 +1,5 @@ +fn foo() { + builtin#asm( + label crashy = { return; } + ); +} From bac0ed579f5474309686839d0691969d80b29783 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Mon, 4 Nov 2024 11:03:36 +0100 Subject: [PATCH 50/95] Make bracket typing handler work on more things --- crates/ide/src/typing.rs | 323 +++++++++++++++++------------ crates/rust-analyzer/src/config.rs | 2 +- docs/user/generated_config.adoc | 2 +- editors/code/package.json | 2 +- editors/code/src/client.ts | 2 +- 5 files changed, 200 insertions(+), 131 deletions(-) diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs index 9bb5de9f2e..5840719ff6 100644 --- a/crates/ide/src/typing.rs +++ b/crates/ide/src/typing.rs @@ -16,11 +16,11 @@ mod on_enter; use ide_db::{base_db::SourceDatabase, FilePosition, RootDatabase}; -use span::EditionedFileId; +use span::{Edition, EditionedFileId}; use syntax::{ algo::{ancestors_at_offset, find_node_at_offset}, ast::{self, edit::IndentLevel, AstToken}, - AstNode, Parse, SourceFile, SyntaxKind, TextRange, TextSize, T, + AstNode, Parse, SourceFile, SyntaxKind, TextRange, TextSize, }; use ide_db::text_edit::TextEdit; @@ -47,6 +47,7 @@ struct ExtendedTextEdit { // - typing `.` in a chain method call auto-indents // - typing `{` or `(` in front of an expression inserts a closing `}` or `)` after the expression // - typing `{` in a use item adds a closing `}` in the right place +// - typing `>` to complete a return type `->` will insert a whitespace after it // // VS Code:: // @@ -66,55 +67,65 @@ pub(crate) fn on_char_typed( if !stdx::always!(TRIGGER_CHARS.contains(char_typed)) { return None; } - let file = &db.parse(EditionedFileId::current_edition(position.file_id)); - if !stdx::always!(file.tree().syntax().text().char_at(position.offset) == Some(char_typed)) { + // FIXME: We need to figure out the edition of the file here, but that means hitting the + // database for more than just parsing the file which is bad. + // FIXME: We are hitting the database here, if we are unlucky this call might block momentarily + // causing the editor to feel sluggish! + let edition = Edition::CURRENT_FIXME; + let file = &db.parse(EditionedFileId::new(position.file_id, edition)); + let char_matches_position = + file.tree().syntax().text().char_at(position.offset) == Some(char_typed); + if !stdx::always!(char_matches_position) { return None; } - let edit = on_char_typed_inner(file, position.offset, char_typed)?; + + let edit = on_char_typed_(file, position.offset, char_typed, edition)?; + let mut sc = SourceChange::from_text_edit(position.file_id, edit.edit); sc.is_snippet = edit.is_snippet; Some(sc) } -fn on_char_typed_inner( +fn on_char_typed_( file: &Parse, offset: TextSize, char_typed: char, + edition: Edition, ) -> Option { - if !stdx::always!(TRIGGER_CHARS.contains(char_typed)) { - return None; - } - let conv = |text_edit: Option| { - Some(ExtendedTextEdit { edit: text_edit?, is_snippet: false }) - }; match char_typed { - '.' => conv(on_dot_typed(&file.tree(), offset)), - '=' => conv(on_eq_typed(&file.tree(), offset)), - '<' => on_left_angle_typed(&file.tree(), offset), - '>' => conv(on_right_angle_typed(&file.tree(), offset)), - '{' => conv(on_opening_bracket_typed(file, offset, '{')), - '(' => conv(on_opening_bracket_typed(file, offset, '(')), + '.' => on_dot_typed(&file.tree(), offset), + '=' => on_eq_typed(&file.tree(), offset), + '>' => on_right_angle_typed(&file.tree(), offset), + '{' | '(' | '<' => on_opening_delimiter_typed(file, offset, char_typed, edition), _ => None, } + .map(conv) } -/// Inserts a closing bracket when the user types an opening bracket, wrapping an existing expression in a +fn conv(edit: TextEdit) -> ExtendedTextEdit { + ExtendedTextEdit { edit, is_snippet: false } +} + +/// Inserts a closing delimiter when the user types an opening bracket, wrapping an existing expression in a /// block, or a part of a `use` item (for `{`). -fn on_opening_bracket_typed( +fn on_opening_delimiter_typed( file: &Parse, offset: TextSize, opening_bracket: char, + edition: Edition, ) -> Option { - let (closing_bracket, expected_ast_bracket) = match opening_bracket { - '{' => ('}', SyntaxKind::L_CURLY), - '(' => (')', SyntaxKind::L_PAREN), + type FilterFn = fn(SyntaxKind) -> bool; + let (closing_bracket, expected_ast_bracket, allowed_kinds) = match opening_bracket { + '{' => ('}', SyntaxKind::L_CURLY, &[ast::Expr::can_cast as FilterFn] as &[FilterFn]), + '(' => ( + ')', + SyntaxKind::L_PAREN, + &[ast::Expr::can_cast, ast::Pat::can_cast, ast::Type::can_cast] as &[FilterFn], + ), + '<' => ('>', SyntaxKind::L_ANGLE, &[ast::Type::can_cast as FilterFn] as &[FilterFn]), _ => return None, }; - if !stdx::always!(file.tree().syntax().text().char_at(offset) == Some(opening_bracket)) { - return None; - } - let brace_token = file.tree().syntax().token_at_offset(offset).right_biased()?; if brace_token.kind() != expected_ast_bracket { return None; @@ -125,58 +136,53 @@ fn on_opening_bracket_typed( if !stdx::always!(range.len() == TextSize::of(opening_bracket)) { return None; } - // FIXME: Edition - let file = file.reparse(range, "", span::Edition::CURRENT_FIXME); + let reparsed = file.reparse(range, "", edition).tree(); - if let Some(edit) = bracket_expr(&file.tree(), offset, opening_bracket, closing_bracket) { + if let Some(edit) = + on_delimited_node_typed(&reparsed, offset, opening_bracket, closing_bracket, allowed_kinds) + { return Some(edit); } - if closing_bracket == '}' { - if let Some(edit) = brace_use_path(&file.tree(), offset) { - return Some(edit); - } + match opening_bracket { + '{' => on_left_brace_typed(&reparsed, offset), + '<' => on_left_angle_typed(&file.tree(), &reparsed, offset), + _ => None, + } +} + +fn on_left_brace_typed(reparsed: &SourceFile, offset: TextSize) -> Option { + let segment: ast::PathSegment = find_node_at_offset(reparsed.syntax(), offset)?; + if segment.syntax().text_range().start() != offset { + return None; } - return None; + let tree: ast::UseTree = find_node_at_offset(reparsed.syntax(), offset)?; - fn brace_use_path(file: &SourceFile, offset: TextSize) -> Option { - let segment: ast::PathSegment = find_node_at_offset(file.syntax(), offset)?; - if segment.syntax().text_range().start() != offset { - return None; - } + Some(TextEdit::insert(tree.syntax().text_range().end() + TextSize::of("{"), "}".to_owned())) +} - let tree: ast::UseTree = find_node_at_offset(file.syntax(), offset)?; +fn on_delimited_node_typed( + reparsed: &SourceFile, + offset: TextSize, + opening_bracket: char, + closing_bracket: char, + kinds: &[fn(SyntaxKind) -> bool], +) -> Option { + let t = reparsed.syntax().token_at_offset(offset).right_biased()?; + let (filter, node) = t + .parent_ancestors() + .take_while(|n| n.text_range().start() == offset) + .find_map(|n| kinds.iter().find(|&kind_filter| kind_filter(n.kind())).zip(Some(n)))?; + let mut node = node + .ancestors() + .take_while(|n| n.text_range().start() == offset && filter(n.kind())) + .last()?; - Some(TextEdit::insert(tree.syntax().text_range().end() + TextSize::of("{"), "}".to_owned())) - } - - fn bracket_expr( - file: &SourceFile, - offset: TextSize, - opening_bracket: char, - closing_bracket: char, - ) -> Option { - let mut expr: ast::Expr = find_node_at_offset(file.syntax(), offset)?; - if expr.syntax().text_range().start() != offset { - return None; - } - - // Enclose the outermost expression starting at `offset` - while let Some(parent) = expr.syntax().parent() { - if parent.text_range().start() != expr.syntax().text_range().start() { - break; - } - - match ast::Expr::cast(parent) { - Some(parent) => expr = parent, - None => break, - } - } - - if let Some(parent) = expr.syntax().parent().and_then(ast::Expr::cast) { - let mut node = expr.syntax().clone(); - let all_prev_sib_attr = loop { + if let Some(parent) = node.parent().filter(|it| filter(it.kind())) { + let all_prev_sib_attr = { + let mut node = node.clone(); + loop { match node.prev_sibling() { Some(sib) if sib.kind().is_trivia() || sib.kind() == SyntaxKind::ATTR => { node = sib @@ -184,21 +190,20 @@ fn on_opening_bracket_typed( Some(_) => break false, None => break true, }; - }; - - if all_prev_sib_attr { - expr = parent; } + }; + + if all_prev_sib_attr { + node = parent; } - - // Insert the closing bracket right after the expression. - Some(TextEdit::insert( - expr.syntax().text_range().end() + TextSize::of(opening_bracket), - closing_bracket.to_string(), - )) } -} + // Insert the closing bracket right after the node. + Some(TextEdit::insert( + node.text_range().end() + TextSize::of(opening_bracket), + closing_bracket.to_string(), + )) +} /// Returns an edit which should be applied after `=` was typed. Primarily, /// this works when adding `let =`. // FIXME: use a snippet completion instead of this hack here. @@ -342,14 +347,15 @@ fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option { } /// Add closing `>` for generic arguments/parameters. -fn on_left_angle_typed(file: &SourceFile, offset: TextSize) -> Option { - let file_text = file.syntax().text(); - if !stdx::always!(file_text.char_at(offset) == Some('<')) { - return None; - } +fn on_left_angle_typed( + file: &SourceFile, + reparsed: &SourceFile, + offset: TextSize, +) -> Option { + let file_text = reparsed.syntax().text(); - // Find the next non-whitespace char in the line. - let mut next_offset = offset + TextSize::of('<'); + // Find the next non-whitespace char in the line, check if its a `>` + let mut next_offset = offset; while file_text.char_at(next_offset) == Some(' ') { next_offset += TextSize::of(' ') } @@ -357,23 +363,14 @@ fn on_left_angle_typed(file: &SourceFile, offset: TextSize) -> Option".to_owned()), - is_snippet: true, - }); - } - } - - if ancestors_at_offset(file.syntax(), offset).any(|n| { - ast::GenericParamList::can_cast(n.kind()) || ast::GenericArgList::can_cast(n.kind()) - }) { - Some(ExtendedTextEdit { - edit: TextEdit::replace(range, "<$0>".to_owned()), - is_snippet: true, + if ancestors_at_offset(file.syntax(), offset) + .take_while(|n| !ast::Item::can_cast(n.kind())) + .any(|n| { + ast::GenericParamList::can_cast(n.kind()) || ast::GenericArgList::can_cast(n.kind()) }) + { + // Insert the closing bracket right after + Some(TextEdit::insert(offset + TextSize::of('<'), '>'.to_string())) } else { None } @@ -411,7 +408,7 @@ mod tests { let edit = TextEdit::insert(offset, char_typed.to_string()); edit.apply(&mut before); let parse = SourceFile::parse(&before, span::Edition::CURRENT_FIXME); - on_char_typed_inner(&parse, offset, char_typed).map(|it| { + on_char_typed_(&parse, offset, char_typed, span::Edition::CURRENT_FIXME).map(|it| { it.apply(&mut before); before.to_string() }) @@ -426,7 +423,7 @@ mod tests { fn type_char_noop(char_typed: char, ra_fixture_before: &str) { let file_change = do_type_char(char_typed, ra_fixture_before); - assert!(file_change.is_none()) + assert_eq!(file_change, None) } #[test] @@ -1066,6 +1063,81 @@ fn f() { ); } + #[test] + fn adds_closing_parenthesis_for_pat() { + type_char( + '(', + r#" +fn f() { match () { $0() => () } } +"#, + r#" +fn f() { match () { (()) => () } } +"#, + ); + type_char( + '(', + r#" +fn f($0n: ()) {} +"#, + r#" +fn f((n): ()) {} +"#, + ); + } + + #[test] + fn adds_closing_parenthesis_for_ty() { + type_char( + '(', + r#" +fn f(n: $0()) {} +"#, + r#" +fn f(n: (())) {} +"#, + ); + type_char( + '(', + r#" +fn f(n: $0a::b::::c) {} +"#, + r#" +fn f(n: (a::b::::c)) {} +"#, + ); + } + + #[test] + fn adds_closing_angles_for_ty() { + type_char( + '<', + r#" +fn f(n: $0()) {} +"#, + r#" +fn f(n: <()>) {} +"#, + ); + type_char( + '<', + r#" +fn f(n: $0a::b::::c) {} +"#, + r#" +fn f(n: ::c>) {} +"#, + ); + type_char( + '<', + r#" +fn f(n: a$0b::::c) {} +"#, + r#" +fn f(n: a<>b::::c) {} +"#, + ); + } + #[test] fn parenthesis_noop_in_string_literal() { // Regression test for #9351 @@ -1154,6 +1226,12 @@ use $0Thing as _; type_char_noop( '(', r#" +use some::pa$0th::to::Item; + "#, + ); + type_char_noop( + '<', + r#" use some::pa$0th::to::Item; "#, ); @@ -1170,7 +1248,7 @@ fn foo() { "#, r#" fn foo() { - bar::<$0> + bar::<> } "#, ); @@ -1184,7 +1262,7 @@ fn foo(bar: &[u64]) { "#, r#" fn foo(bar: &[u64]) { - bar.iter().collect::<$0>(); + bar.iter().collect::<>(); } "#, ); @@ -1198,7 +1276,7 @@ fn foo(bar: &[u64]) { fn foo$0() {} "#, r#" -fn foo<$0>() {} +fn foo<>() {} "#, ); type_char( @@ -1207,7 +1285,7 @@ fn foo<$0>() {} fn foo$0 "#, r#" -fn foo<$0> +fn foo<> "#, ); type_char( @@ -1216,7 +1294,7 @@ fn foo<$0> struct Foo$0 {} "#, r#" -struct Foo<$0> {} +struct Foo<> {} "#, ); type_char( @@ -1225,7 +1303,7 @@ struct Foo<$0> {} struct Foo$0(); "#, r#" -struct Foo<$0>(); +struct Foo<>(); "#, ); type_char( @@ -1234,7 +1312,7 @@ struct Foo<$0>(); struct Foo$0 "#, r#" -struct Foo<$0> +struct Foo<> "#, ); type_char( @@ -1243,7 +1321,7 @@ struct Foo<$0> enum Foo$0 "#, r#" -enum Foo<$0> +enum Foo<> "#, ); type_char( @@ -1252,7 +1330,7 @@ enum Foo<$0> trait Foo$0 "#, r#" -trait Foo<$0> +trait Foo<> "#, ); type_char( @@ -1261,16 +1339,7 @@ trait Foo<$0> type Foo$0 = Bar; "#, r#" -type Foo<$0> = Bar; - "#, - ); - type_char( - '<', - r#" -impl$0 Foo {} - "#, - r#" -impl<$0> Foo {} +type Foo<> = Bar; "#, ); type_char( @@ -1279,7 +1348,7 @@ impl<$0> Foo {} impl Foo$0 {} "#, r#" -impl Foo<$0> {} +impl Foo<> {} "#, ); type_char( @@ -1288,7 +1357,7 @@ impl Foo<$0> {} impl Foo$0 {} "#, r#" -impl Foo<$0> {} +impl Foo<> {} "#, ); } diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index aa9f919679..392bfbf15f 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -309,7 +309,7 @@ config_data! { signatureInfo_documentation_enable: bool = true, /// Specify the characters to exclude from triggering typing assists. The default trigger characters are `.`, `=`, `<`, `>`, `{`, and `(`. Setting this to a string will disable typing assists for the specified characters. - typing_excludeChars: Option = Some("<".to_owned()), + typing_excludeChars: Option = None, /// Enables automatic discovery of projects using [`DiscoverWorkspaceConfig::command`]. diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index 96a2a5a27d..a3172c7ca2 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -992,7 +992,7 @@ Show full signature of the callable. Only shows parameters if disabled. -- Show documentation. -- -[[rust-analyzer.typing.excludeChars]]rust-analyzer.typing.excludeChars (default: `"<"`):: +[[rust-analyzer.typing.excludeChars]]rust-analyzer.typing.excludeChars (default: `null`):: + -- Specify the characters to exclude from triggering typing assists. The default trigger characters are `.`, `=`, `<`, `>`, `{`, and `(`. Setting this to a string will disable typing assists for the specified characters. diff --git a/editors/code/package.json b/editors/code/package.json index 7529651ca7..68c61e4bf6 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -2607,7 +2607,7 @@ "properties": { "rust-analyzer.typing.excludeChars": { "markdownDescription": "Specify the characters to exclude from triggering typing assists. The default trigger characters are `.`, `=`, `<`, `>`, `{`, and `(`. Setting this to a string will disable typing assists for the specified characters.", - "default": "<", + "default": null, "type": [ "null", "string" diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index eac7b849fd..4ce19f5c66 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -324,7 +324,7 @@ class ExperimentalFeatures implements lc.StaticFeature { } fillClientCapabilities(capabilities: lc.ClientCapabilities): void { capabilities.experimental = { - snippetTextEdit: true, + snippetTextEdit: false, codeActionGroup: true, hoverActions: true, serverStatusNotification: true, From 19465b94f5623a8c317ad60162291fec4dd6bdf4 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 4 Dec 2024 09:00:08 +0100 Subject: [PATCH 51/95] Add implict unsafety inlay hints for extern blocks --- crates/ide/src/inlay_hints.rs | 21 +++- crates/ide/src/inlay_hints/extern_block.rs | 138 +++++++++++++++++++++ 2 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 crates/ide/src/inlay_hints/extern_block.rs diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs index 1ae8bfa9b6..aa99ba49bc 100644 --- a/crates/ide/src/inlay_hints.rs +++ b/crates/ide/src/inlay_hints.rs @@ -29,6 +29,7 @@ mod closing_brace; mod closure_captures; mod closure_ret; mod discriminant; +mod extern_block; mod generic_param; mod implicit_drop; mod implicit_static; @@ -116,6 +117,7 @@ pub(crate) fn inlay_hints( #[derive(Default)] struct InlayHintCtx { lifetime_stacks: Vec>, + extern_block_parent: Option, } pub(crate) fn inlay_hints_resolve( @@ -174,12 +176,18 @@ fn handle_event(ctx: &mut InlayHintCtx, node: WalkEvent) -> Option { if ast::AnyHasGenericParams::can_cast(n.kind()) { ctx.lifetime_stacks.pop(); } + if ast::ExternBlock::can_cast(n.kind()) { + ctx.extern_block_parent = None; + } None } } @@ -234,12 +242,20 @@ fn hints( ast::Item(it) => match it { ast::Item::Fn(it) => { implicit_drop::hints(hints, famous_defs, config, file_id, &it); + if let Some(extern_block) = &ctx.extern_block_parent { + extern_block::fn_hints(hints, famous_defs, config, file_id, &it, extern_block); + } lifetime::fn_hints(hints, ctx, famous_defs, config, file_id, it) }, - // static type elisions - ast::Item::Static(it) => implicit_static::hints(hints, famous_defs, config, file_id, Either::Left(it)), + ast::Item::Static(it) => { + if let Some(extern_block) = &ctx.extern_block_parent { + extern_block::static_hints(hints, famous_defs, config, file_id, &it, extern_block); + } + implicit_static::hints(hints, famous_defs, config, file_id, Either::Left(it)) + }, ast::Item::Const(it) => implicit_static::hints(hints, famous_defs, config, file_id, Either::Right(it)), ast::Item::Enum(it) => discriminant::enum_hints(hints, famous_defs, config, file_id, it), + ast::Item::ExternBlock(it) => extern_block::extern_block_hints(hints, famous_defs, config, file_id, it), _ => None, }, // FIXME: trait object type elisions @@ -368,6 +384,7 @@ pub enum InlayKind { Type, Drop, RangeExclusive, + ExternUnsafety, } #[derive(Debug, Hash)] diff --git a/crates/ide/src/inlay_hints/extern_block.rs b/crates/ide/src/inlay_hints/extern_block.rs new file mode 100644 index 0000000000..4cc4925cda --- /dev/null +++ b/crates/ide/src/inlay_hints/extern_block.rs @@ -0,0 +1,138 @@ +//! Extern block hints +use ide_db::{famous_defs::FamousDefs, text_edit::TextEdit}; +use span::EditionedFileId; +use syntax::{ast, AstNode, SyntaxToken}; + +use crate::{InlayHint, InlayHintsConfig}; + +pub(super) fn extern_block_hints( + acc: &mut Vec, + FamousDefs(_sema, _): &FamousDefs<'_, '_>, + _config: &InlayHintsConfig, + _file_id: EditionedFileId, + extern_block: ast::ExternBlock, +) -> Option<()> { + if extern_block.unsafe_token().is_some() { + return None; + } + let abi = extern_block.abi()?; + acc.push(InlayHint { + range: abi.syntax().text_range(), + position: crate::InlayHintPosition::Before, + pad_left: false, + pad_right: true, + kind: crate::InlayKind::ExternUnsafety, + label: crate::InlayHintLabel::from("unsafe"), + text_edit: Some(TextEdit::insert(abi.syntax().text_range().start(), "unsafe ".to_owned())), + resolve_parent: Some(extern_block.syntax().text_range()), + }); + Some(()) +} + +pub(super) fn fn_hints( + acc: &mut Vec, + FamousDefs(_sema, _): &FamousDefs<'_, '_>, + _config: &InlayHintsConfig, + _file_id: EditionedFileId, + fn_: &ast::Fn, + extern_block: &ast::ExternBlock, +) -> Option<()> { + let implicit_unsafe = fn_.safe_token().is_none() && fn_.unsafe_token().is_none(); + if !implicit_unsafe { + return None; + } + let fn_ = fn_.fn_token()?; + acc.push(item_hint(extern_block, fn_)); + Some(()) +} + +pub(super) fn static_hints( + acc: &mut Vec, + FamousDefs(_sema, _): &FamousDefs<'_, '_>, + _config: &InlayHintsConfig, + _file_id: EditionedFileId, + static_: &ast::Static, + extern_block: &ast::ExternBlock, +) -> Option<()> { + let implicit_unsafe = static_.safe_token().is_none() && static_.unsafe_token().is_none(); + if !implicit_unsafe { + return None; + } + let static_ = static_.static_token()?; + acc.push(item_hint(extern_block, static_)); + Some(()) +} + +fn item_hint(extern_block: &ast::ExternBlock, token: SyntaxToken) -> InlayHint { + InlayHint { + range: token.text_range(), + position: crate::InlayHintPosition::Before, + pad_left: false, + pad_right: true, + kind: crate::InlayKind::ExternUnsafety, + label: crate::InlayHintLabel::from("unsafe"), + text_edit: { + let mut builder = TextEdit::builder(); + builder.insert(token.text_range().start(), "unsafe ".to_owned()); + if extern_block.unsafe_token().is_none() { + if let Some(abi) = extern_block.abi() { + builder.insert(abi.syntax().text_range().start(), "unsafe ".to_owned()); + } + } + Some(builder.finish()) + }, + resolve_parent: Some(extern_block.syntax().text_range()), + } +} + +#[cfg(test)] +mod tests { + use crate::inlay_hints::tests::{check_with_config, DISABLED_CONFIG}; + + #[test] + fn unadorned() { + check_with_config( + DISABLED_CONFIG, + r#" + extern "C" { +//^^^^^^^^^^ unsafe + static FOO: (); + // ^^^^^^ unsafe + pub static FOO: (); + // ^^^^^^unsafe + unsafe static FOO: (); + safe static FOO: (); + fn foo(); + // ^^ unsafe + pub fn foo(); + // ^^ unsafe + unsafe fn foo(); + safe fn foo(); +} +"#, + ); + } + + #[test] + fn adorned() { + check_with_config( + DISABLED_CONFIG, + r#" +unsafe extern "C" { + static FOO: (); + // ^^^^^^ unsafe + pub static FOO: (); + // ^^^^^^unsafe + unsafe static FOO: (); + safe static FOO: (); + fn foo(); + // ^^ unsafe + pub fn foo(); + // ^^ unsafe + unsafe fn foo(); + safe fn foo(); +} +"#, + ); + } +} From 5dc5107e9cdff77752dda04f88b80d9b7e740eed Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 6 Dec 2024 15:00:09 +0100 Subject: [PATCH 52/95] Improve heuristics for on typing semicolon insertion --- crates/ide/src/typing.rs | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs index 5840719ff6..cfa1d6ff03 100644 --- a/crates/ide/src/typing.rs +++ b/crates/ide/src/typing.rs @@ -15,6 +15,8 @@ mod on_enter; +use std::iter; + use ide_db::{base_db::SourceDatabase, FilePosition, RootDatabase}; use span::{Edition, EditionedFileId}; use syntax::{ @@ -120,7 +122,8 @@ fn on_opening_delimiter_typed( '(' => ( ')', SyntaxKind::L_PAREN, - &[ast::Expr::can_cast, ast::Pat::can_cast, ast::Type::can_cast] as &[FilterFn], + &[ast::Expr::can_cast as FilterFn, ast::Pat::can_cast, ast::Type::can_cast] + as &[FilterFn], ), '<' => ('>', SyntaxKind::L_ANGLE, &[ast::Type::can_cast as FilterFn] as &[FilterFn]), _ => return None, @@ -208,7 +211,18 @@ fn on_delimited_node_typed( /// this works when adding `let =`. // FIXME: use a snippet completion instead of this hack here. fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option { - if !stdx::always!(file.syntax().text().char_at(offset) == Some('=')) { + let text = file.syntax().text(); + if !stdx::always!(text.char_at(offset) == Some('=')) { + return None; + } + + let has_newline = iter::successors(Some(offset), |&offset| Some(offset + TextSize::new(1))) + .filter_map(|offset| text.char_at(offset)) + .find(|&c| !c.is_whitespace() || c == '\n') + == Some('n'); + // don't attempt to add `;` if there is a newline after the `=`, the intent is likely to write + // out the expression afterwards! + if has_newline { return None; } @@ -466,6 +480,15 @@ fn foo() { let foo =$0 let bar = 1; } +", + ); + type_char_noop( + '=', + r" +fn foo() { + let foo =$0 + 1 + 1 +} ", ); } From 54dbf1b4468715ee16ac99eea3ba2d270166c980 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 6 Dec 2024 15:35:13 +0100 Subject: [PATCH 53/95] Add typing handler for param list pipe --- crates/ide/src/lib.rs | 2 + crates/ide/src/typing.rs | 67 ++++++++++++++++---- crates/rust-analyzer/src/config.rs | 4 +- crates/rust-analyzer/src/lsp/capabilities.rs | 17 ++--- docs/user/generated_config.adoc | 4 +- editors/code/package.json | 4 +- 6 files changed, 69 insertions(+), 29 deletions(-) diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index b43685ffee..c960b88a3e 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -402,6 +402,8 @@ impl Analysis { self.with_db(|db| typing::on_enter(db, position)) } + pub const SUPPORTED_TRIGGER_CHARS: &'static str = typing::TRIGGER_CHARS; + /// Returns an edit which should be applied after a character was typed. /// /// This is useful for some on-the-fly fixups, like adding `;` to `let =` diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs index cfa1d6ff03..3d9146cc4c 100644 --- a/crates/ide/src/typing.rs +++ b/crates/ide/src/typing.rs @@ -32,7 +32,7 @@ use crate::SourceChange; pub(crate) use on_enter::on_enter; // Don't forget to add new trigger characters to `server_capabilities` in `caps.rs`. -pub(crate) const TRIGGER_CHARS: &str = ".=<>{("; +pub(crate) const TRIGGER_CHARS: &str = ".=<>{(|"; struct ExtendedTextEdit { edit: TextEdit, @@ -99,6 +99,7 @@ fn on_char_typed_( '=' => on_eq_typed(&file.tree(), offset), '>' => on_right_angle_typed(&file.tree(), offset), '{' | '(' | '<' => on_opening_delimiter_typed(file, offset, char_typed, edition), + '|' => on_pipe_typed(&file.tree(), offset), _ => None, } .map(conv) @@ -212,10 +213,6 @@ fn on_delimited_node_typed( // FIXME: use a snippet completion instead of this hack here. fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option { let text = file.syntax().text(); - if !stdx::always!(text.char_at(offset) == Some('=')) { - return None; - } - let has_newline = iter::successors(Some(offset), |&offset| Some(offset + TextSize::new(1))) .filter_map(|offset| text.char_at(offset)) .find(|&c| !c.is_whitespace() || c == '\n') @@ -308,9 +305,6 @@ fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option { /// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately. fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option { - if !stdx::always!(file.syntax().text().char_at(offset) == Some('.')) { - return None; - } let whitespace = file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?; @@ -380,7 +374,9 @@ fn on_left_angle_typed( if ancestors_at_offset(file.syntax(), offset) .take_while(|n| !ast::Item::can_cast(n.kind())) .any(|n| { - ast::GenericParamList::can_cast(n.kind()) || ast::GenericArgList::can_cast(n.kind()) + ast::GenericParamList::can_cast(n.kind()) + || ast::GenericArgList::can_cast(n.kind()) + || ast::UseBoundGenericArgs::can_cast(n.kind()) }) { // Insert the closing bracket right after @@ -390,12 +386,21 @@ fn on_left_angle_typed( } } +fn on_pipe_typed(file: &SourceFile, offset: TextSize) -> Option { + let pipe_token = file.syntax().token_at_offset(offset).right_biased()?; + if pipe_token.kind() != SyntaxKind::PIPE { + return None; + } + if pipe_token.parent().and_then(ast::ParamList::cast)?.r_paren_token().is_some() { + return None; + } + let after_lpipe = offset + TextSize::of('|'); + Some(TextEdit::insert(after_lpipe, "|".to_owned())) +} + /// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }` fn on_right_angle_typed(file: &SourceFile, offset: TextSize) -> Option { let file_text = file.syntax().text(); - if !stdx::always!(file_text.char_at(offset) == Some('>')) { - return None; - } let after_arrow = offset + TextSize::of('>'); if file_text.char_at(after_arrow) != Some('{') { return None; @@ -1527,6 +1532,44 @@ fn foo() { ) $0 } +"#, + ); + } + + #[test] + fn completes_pipe_param_list() { + type_char( + '|', + r#" +fn foo() { + $0 +} +"#, + r#" +fn foo() { + || +} +"#, + ); + type_char( + '|', + r#" +fn foo() { + $0 a +} +"#, + r#" +fn foo() { + || a +} +"#, + ); + type_char_noop( + '|', + r#" +fn foo() { + let $0 +} "#, ); } diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 392bfbf15f..0fdc48a0e7 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -308,8 +308,8 @@ config_data! { /// Show documentation. signatureInfo_documentation_enable: bool = true, - /// Specify the characters to exclude from triggering typing assists. The default trigger characters are `.`, `=`, `<`, `>`, `{`, and `(`. Setting this to a string will disable typing assists for the specified characters. - typing_excludeChars: Option = None, + /// Specify the characters to exclude from triggering typing assists. The default trigger characters are `.`, `=`, `<`, `>`, `{`, and `(`. + typing_excludeChars: Option = Some('<'.to_string()), /// Enables automatic discovery of projects using [`DiscoverWorkspaceConfig::command`]. diff --git a/crates/rust-analyzer/src/lsp/capabilities.rs b/crates/rust-analyzer/src/lsp/capabilities.rs index bd496e8ddc..82e6ae2b49 100644 --- a/crates/rust-analyzer/src/lsp/capabilities.rs +++ b/crates/rust-analyzer/src/lsp/capabilities.rs @@ -72,9 +72,12 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities { RustfmtConfig::Rustfmt { enable_range_formatting: true, .. } => Some(OneOf::Left(true)), _ => Some(OneOf::Left(false)), }, - document_on_type_formatting_provider: Some(DocumentOnTypeFormattingOptions { - first_trigger_character: "=".to_owned(), - more_trigger_character: Some(more_trigger_character(config)), + document_on_type_formatting_provider: Some({ + let mut chars = ide::Analysis::SUPPORTED_TRIGGER_CHARS.chars(); + DocumentOnTypeFormattingOptions { + first_trigger_character: chars.next().unwrap().to_string(), + more_trigger_character: Some(chars.map(|c| c.to_string()).collect()), + } }), selection_range_provider: Some(SelectionRangeProviderCapability::Simple(true)), folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)), @@ -528,11 +531,3 @@ impl ClientCapabilities { .unwrap_or_default() } } - -fn more_trigger_character(config: &Config) -> Vec { - let mut res = vec![".".to_owned(), ">".to_owned(), "{".to_owned(), "(".to_owned()]; - if config.snippet_cap().is_some() { - res.push("<".to_owned()); - } - res -} diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index a3172c7ca2..715f8d43ad 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -992,10 +992,10 @@ Show full signature of the callable. Only shows parameters if disabled. -- Show documentation. -- -[[rust-analyzer.typing.excludeChars]]rust-analyzer.typing.excludeChars (default: `null`):: +[[rust-analyzer.typing.excludeChars]]rust-analyzer.typing.excludeChars (default: `"<"`):: + -- -Specify the characters to exclude from triggering typing assists. The default trigger characters are `.`, `=`, `<`, `>`, `{`, and `(`. Setting this to a string will disable typing assists for the specified characters. +Specify the characters to exclude from triggering typing assists. The default trigger characters are `.`, `=`, `<`, `>`, `{`, and `(`. -- [[rust-analyzer.workspace.discoverConfig]]rust-analyzer.workspace.discoverConfig (default: `null`):: + diff --git a/editors/code/package.json b/editors/code/package.json index 68c61e4bf6..70d26c9707 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -2606,8 +2606,8 @@ "title": "typing", "properties": { "rust-analyzer.typing.excludeChars": { - "markdownDescription": "Specify the characters to exclude from triggering typing assists. The default trigger characters are `.`, `=`, `<`, `>`, `{`, and `(`. Setting this to a string will disable typing assists for the specified characters.", - "default": null, + "markdownDescription": "Specify the characters to exclude from triggering typing assists. The default trigger characters are `.`, `=`, `<`, `>`, `{`, and `(`.", + "default": "<", "type": [ "null", "string" From ff1124918e9035d401fb707f6307e99ace052f66 Mon Sep 17 00:00:00 2001 From: Giga Bowser <45986823+Giga-Bowser@users.noreply.github.com> Date: Sat, 16 Nov 2024 20:01:05 -0500 Subject: [PATCH 54/95] fix: Properly determine `SyntaxEditor` replacement intersection Bordering replacements should not be considered intersecting --- crates/syntax/src/syntax_editor/edit_algo.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/syntax/src/syntax_editor/edit_algo.rs b/crates/syntax/src/syntax_editor/edit_algo.rs index b769c94110..44a1fae23b 100644 --- a/crates/syntax/src/syntax_editor/edit_algo.rs +++ b/crates/syntax/src/syntax_editor/edit_algo.rs @@ -73,7 +73,7 @@ pub(super) fn apply_edits(editor: SyntaxEditor) -> SyntaxEdit { }) .all(|(l, r)| { get_node_depth(l.target_parent()) != get_node_depth(r.target_parent()) - || l.target_range().intersect(r.target_range()).is_none() + || (l.target_range().end() <= r.target_range().start()) }); if stdx::never!( From 5eb8affdc5a5f86ce60c9e112a3af257070c1798 Mon Sep 17 00:00:00 2001 From: Giga Bowser <45986823+Giga-Bowser@users.noreply.github.com> Date: Sun, 17 Nov 2024 15:36:57 -0500 Subject: [PATCH 55/95] fix: Don't produce `ChangedAncestor` for `SyntaxToken`s --- crates/syntax/src/syntax_editor.rs | 48 +++++++++++++++++++- crates/syntax/src/syntax_editor/edit_algo.rs | 14 ++---- 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/crates/syntax/src/syntax_editor.rs b/crates/syntax/src/syntax_editor.rs index 7e5d0f27e0..992a847663 100644 --- a/crates/syntax/src/syntax_editor.rs +++ b/crates/syntax/src/syntax_editor.rs @@ -327,7 +327,7 @@ mod tests { use crate::{ ast::{self, make, syntax_factory::SyntaxFactory}, - AstNode, + AstNode, SyntaxKind, }; use super::*; @@ -540,4 +540,50 @@ mod tests { }"#]]; expect.assert_eq(&edit.new_root.to_string()); } + + #[test] + fn test_replace_token_in_parent() { + let parent_fn = make::fn_( + None, + make::name("it"), + None, + None, + make::param_list(None, []), + make::block_expr([], Some(make::expr_unit())), + Some(make::ret_type(make::ty_unit())), + false, + false, + false, + false, + ); + + let mut editor = SyntaxEditor::new(parent_fn.syntax().clone()); + + if let Some(ret_ty) = parent_fn.ret_type() { + editor.delete(ret_ty.syntax().clone()); + + if let Some(SyntaxElement::Token(token)) = ret_ty.syntax().next_sibling_or_token() { + if token.kind().is_trivia() { + editor.delete(token); + } + } + } + + if let Some(tail) = parent_fn.body().unwrap().tail_expr() { + // FIXME: We do this because `xtask tidy` will not allow us to have trailing whitespace in the expect string. + if let Some(SyntaxElement::Token(token)) = tail.syntax().prev_sibling_or_token() { + if let SyntaxKind::WHITESPACE = token.kind() { + editor.delete(token); + } + } + editor.delete(tail.syntax().clone()); + } + + let edit = editor.finish(); + + let expect = expect![[r#" +fn it() { +}"#]]; + expect.assert_eq(&edit.new_root.to_string()); + } } diff --git a/crates/syntax/src/syntax_editor/edit_algo.rs b/crates/syntax/src/syntax_editor/edit_algo.rs index 44a1fae23b..71b69dbec1 100644 --- a/crates/syntax/src/syntax_editor/edit_algo.rs +++ b/crates/syntax/src/syntax_editor/edit_algo.rs @@ -128,13 +128,14 @@ pub(super) fn apply_edits(editor: SyntaxEditor) -> SyntaxEdit { // Add to changed ancestors, if applicable match change { - Change::Insert(_, _) | Change::InsertAll(_, _) => {} - Change::Replace(target, _) | Change::ReplaceWithMany(target, _) => { + Change::Replace(SyntaxElement::Node(target), _) + | Change::ReplaceWithMany(SyntaxElement::Node(target), _) => { changed_ancestors.push_back(ChangedAncestor::single(target, change_index)) } Change::ReplaceAll(range, _) => { changed_ancestors.push_back(ChangedAncestor::multiple(range, change_index)) } + _ => (), } } @@ -304,13 +305,8 @@ enum ChangedAncestorKind { } impl ChangedAncestor { - fn single(element: &SyntaxElement, change_index: usize) -> Self { - let kind = match element { - SyntaxElement::Node(node) => ChangedAncestorKind::Single { node: node.clone() }, - SyntaxElement::Token(token) => { - ChangedAncestorKind::Single { node: token.parent().unwrap() } - } - }; + fn single(node: &SyntaxNode, change_index: usize) -> Self { + let kind = ChangedAncestorKind::Single { node: node.clone() }; Self { kind, change_index } } From 64060486269b6ff5e54606796fe66cb17189186b Mon Sep 17 00:00:00 2001 From: Giga Bowser <45986823+Giga-Bowser@users.noreply.github.com> Date: Thu, 21 Nov 2024 13:34:12 -0500 Subject: [PATCH 56/95] minor: Add `token` constructor to `SyntaxFactory` --- crates/syntax/src/ast/syntax_factory/constructors.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/syntax/src/ast/syntax_factory/constructors.rs b/crates/syntax/src/ast/syntax_factory/constructors.rs index 8b06109407..0d8fe4cd23 100644 --- a/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -4,7 +4,7 @@ use itertools::Itertools; use crate::{ ast::{self, make, HasName, HasTypeBounds}, syntax_editor::SyntaxMappingBuilder, - AstNode, + AstNode, SyntaxKind, SyntaxToken, }; use super::SyntaxFactory; @@ -151,4 +151,8 @@ impl SyntaxFactory { ast } + + pub fn token(&self, kind: SyntaxKind) -> SyntaxToken { + make::token(kind) + } } From fbb392062ab202073eb19144ebae407be21c127b Mon Sep 17 00:00:00 2001 From: Giga Bowser <45986823+Giga-Bowser@users.noreply.github.com> Date: Thu, 21 Nov 2024 15:16:06 -0500 Subject: [PATCH 57/95] minor: Add `expr_bin` constructor to `SyntaxFactory` --- .../src/ast/syntax_factory/constructors.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/crates/syntax/src/ast/syntax_factory/constructors.rs b/crates/syntax/src/ast/syntax_factory/constructors.rs index 0d8fe4cd23..944724ff05 100644 --- a/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -80,6 +80,23 @@ impl SyntaxFactory { ast } + pub fn expr_bin(&self, lhs: ast::Expr, op: ast::BinaryOp, rhs: ast::Expr) -> ast::BinExpr { + let ast::Expr::BinExpr(ast) = + make::expr_bin_op(lhs.clone(), op, rhs.clone()).clone_for_update() + else { + unreachable!() + }; + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_node(lhs.syntax().clone(), ast.lhs().unwrap().syntax().clone()); + builder.map_node(rhs.syntax().clone(), ast.rhs().unwrap().syntax().clone()); + builder.finish(&mut mapping); + } + + ast + } + pub fn expr_path(&self, path: ast::Path) -> ast::Expr { let ast::Expr::PathExpr(ast) = make::expr_path(path.clone()).clone_for_update() else { unreachable!() From 09dee81412a6f5e152eae7ce78d96e1e06f45930 Mon Sep 17 00:00:00 2001 From: Giga Bowser <45986823+Giga-Bowser@users.noreply.github.com> Date: Thu, 21 Nov 2024 16:23:27 -0500 Subject: [PATCH 58/95] feat: Migrate `flip_binexpr` assist to `SyntaxEditor` --- .../ide-assists/src/handlers/flip_binexpr.rs | 53 ++++++++++--------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/crates/ide-assists/src/handlers/flip_binexpr.rs b/crates/ide-assists/src/handlers/flip_binexpr.rs index 8b46a23f9a..601fd29f8e 100644 --- a/crates/ide-assists/src/handlers/flip_binexpr.rs +++ b/crates/ide-assists/src/handlers/flip_binexpr.rs @@ -1,4 +1,7 @@ -use syntax::ast::{self, AstNode, BinExpr}; +use syntax::{ + ast::{self, syntax_factory::SyntaxFactory, AstNode, BinExpr}, + SyntaxKind, T, +}; use crate::{AssistContext, AssistId, AssistKind, Assists}; @@ -19,22 +22,17 @@ use crate::{AssistContext, AssistId, AssistKind, Assists}; // ``` pub(crate) fn flip_binexpr(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let expr = ctx.find_node_at_offset::()?; - let rhs = expr.rhs()?.syntax().clone(); - let lhs = expr.lhs()?.syntax().clone(); + let lhs = expr.lhs()?; + let rhs = expr.rhs()?; - let lhs = if let Some(bin_expr) = BinExpr::cast(lhs.clone()) { - if bin_expr.op_kind() == expr.op_kind() { - bin_expr.rhs()?.syntax().clone() - } else { - lhs - } - } else { - lhs + let lhs = match &lhs { + ast::Expr::BinExpr(bin_expr) if bin_expr.op_kind() == expr.op_kind() => bin_expr.rhs()?, + _ => lhs, }; - let op_range = expr.op_token()?.text_range(); + let op_token = expr.op_token()?; // The assist should be applied only if the cursor is on the operator - let cursor_in_range = op_range.contains_range(ctx.selection_trimmed()); + let cursor_in_range = op_token.text_range().contains_range(ctx.selection_trimmed()); if !cursor_in_range { return None; } @@ -47,13 +45,18 @@ pub(crate) fn flip_binexpr(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option acc.add( AssistId("flip_binexpr", AssistKind::RefactorRewrite), "Flip binary expression", - op_range, - |edit| { - if let FlipAction::FlipAndReplaceOp(new_op) = action { - edit.replace(op_range, new_op); - } - edit.replace(lhs.text_range(), rhs.text()); - edit.replace(rhs.text_range(), lhs.text()); + op_token.text_range(), + |builder| { + let mut editor = builder.make_editor(&expr.syntax().parent().unwrap()); + let make = SyntaxFactory::new(); + if let FlipAction::FlipAndReplaceOp(binary_op) = action { + editor.replace(op_token, make.token(binary_op)) + }; + // FIXME: remove `clone_for_update` when `SyntaxEditor` handles it for us + editor.replace(lhs.syntax(), rhs.syntax().clone_for_update()); + editor.replace(rhs.syntax(), lhs.syntax().clone_for_update()); + editor.add_mappings(make.finish_with_mappings()); + builder.add_file_edits(ctx.file_id(), editor); }, ) } @@ -62,7 +65,7 @@ enum FlipAction { // Flip the expression Flip, // Flip the expression and replace the operator with this string - FlipAndReplaceOp(&'static str), + FlipAndReplaceOp(SyntaxKind), // Do not flip the expression DontFlip, } @@ -73,10 +76,10 @@ impl From for FlipAction { ast::BinaryOp::Assignment { .. } => FlipAction::DontFlip, ast::BinaryOp::CmpOp(ast::CmpOp::Ord { ordering, strict }) => { let rev_op = match (ordering, strict) { - (ast::Ordering::Less, true) => ">", - (ast::Ordering::Less, false) => ">=", - (ast::Ordering::Greater, true) => "<", - (ast::Ordering::Greater, false) => "<=", + (ast::Ordering::Less, true) => T![>], + (ast::Ordering::Less, false) => T![>=], + (ast::Ordering::Greater, true) => T![<], + (ast::Ordering::Greater, false) => T![<=], }; FlipAction::FlipAndReplaceOp(rev_op) } From d55879d1cbf38f5b7f8b36a41164412e7b78617e Mon Sep 17 00:00:00 2001 From: Giga Bowser <45986823+Giga-Bowser@users.noreply.github.com> Date: Fri, 22 Nov 2024 09:49:19 -0500 Subject: [PATCH 59/95] feat: Migrate `flip_trait_bound` assist to `SyntaxEditor` --- .../ide-assists/src/handlers/flip_trait_bound.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/ide-assists/src/handlers/flip_trait_bound.rs b/crates/ide-assists/src/handlers/flip_trait_bound.rs index 70b5efcb64..03366bd861 100644 --- a/crates/ide-assists/src/handlers/flip_trait_bound.rs +++ b/crates/ide-assists/src/handlers/flip_trait_bound.rs @@ -23,11 +23,11 @@ pub(crate) fn flip_trait_bound(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op let plus = ctx.find_token_syntax_at_offset(T![+])?; // Make sure we're in a `TypeBoundList` - ast::TypeBoundList::cast(plus.parent()?)?; + let parent = ast::TypeBoundList::cast(plus.parent()?)?; let (before, after) = ( - non_trivia_sibling(plus.clone().into(), Direction::Prev)?, - non_trivia_sibling(plus.clone().into(), Direction::Next)?, + non_trivia_sibling(plus.clone().into(), Direction::Prev)?.into_node()?, + non_trivia_sibling(plus.clone().into(), Direction::Next)?.into_node()?, ); let target = plus.text_range(); @@ -35,9 +35,11 @@ pub(crate) fn flip_trait_bound(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op AssistId("flip_trait_bound", AssistKind::RefactorRewrite), "Flip trait bounds", target, - |edit| { - edit.replace(before.text_range(), after.to_string()); - edit.replace(after.text_range(), before.to_string()); + |builder| { + let mut editor = builder.make_editor(parent.syntax()); + editor.replace(before.clone(), after.clone_for_update()); + editor.replace(after.clone(), before.clone_for_update()); + builder.add_file_edits(ctx.file_id(), editor); }, ) } From 8fd7790eb51b546b18cbe209f910c396917bd88e Mon Sep 17 00:00:00 2001 From: Giga Bowser <45986823+Giga-Bowser@users.noreply.github.com> Date: Sat, 23 Nov 2024 12:41:48 -0500 Subject: [PATCH 60/95] minor: Add `token_tree` constructor to `SyntaxFactory` --- .../src/ast/syntax_factory/constructors.rs | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/crates/syntax/src/ast/syntax_factory/constructors.rs b/crates/syntax/src/ast/syntax_factory/constructors.rs index 944724ff05..54f17bd721 100644 --- a/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -4,7 +4,7 @@ use itertools::Itertools; use crate::{ ast::{self, make, HasName, HasTypeBounds}, syntax_editor::SyntaxMappingBuilder, - AstNode, SyntaxKind, SyntaxToken, + AstNode, NodeOrToken, SyntaxKind, SyntaxNode, SyntaxToken, }; use super::SyntaxFactory; @@ -169,6 +169,32 @@ impl SyntaxFactory { ast } + pub fn token_tree( + &self, + delimiter: SyntaxKind, + tt: Vec>, + ) -> ast::TokenTree { + let tt: Vec<_> = tt.into_iter().collect(); + let input: Vec<_> = tt.iter().cloned().filter_map(only_nodes).collect(); + + let ast = make::token_tree(delimiter, tt).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_children( + input.into_iter(), + ast.token_trees_and_tokens().filter_map(only_nodes), + ); + builder.finish(&mut mapping); + } + + return ast; + + fn only_nodes(element: NodeOrToken) -> Option { + element.as_node().map(|it| it.syntax().clone()) + } + } + pub fn token(&self, kind: SyntaxKind) -> SyntaxToken { make::token(kind) } From d329329b76dc4a15d9803a5e5adec45e48f940fd Mon Sep 17 00:00:00 2001 From: Giga Bowser <45986823+Giga-Bowser@users.noreply.github.com> Date: Sat, 23 Nov 2024 12:42:49 -0500 Subject: [PATCH 61/95] feat: Migrate `flip_comma` assist to `SyntaxEditor` --- crates/ide-assists/src/handlers/flip_comma.rs | 120 ++++++++++++------ 1 file changed, 78 insertions(+), 42 deletions(-) diff --git a/crates/ide-assists/src/handlers/flip_comma.rs b/crates/ide-assists/src/handlers/flip_comma.rs index af2c2c759e..490a9ee3c0 100644 --- a/crates/ide-assists/src/handlers/flip_comma.rs +++ b/crates/ide-assists/src/handlers/flip_comma.rs @@ -1,7 +1,8 @@ -use ide_db::base_db::SourceDatabase; -use syntax::TextSize; use syntax::{ - algo::non_trivia_sibling, ast, AstNode, Direction, SyntaxKind, SyntaxToken, TextRange, T, + algo::non_trivia_sibling, + ast::{self, syntax_factory::SyntaxFactory}, + syntax_editor::{Element, SyntaxMapping}, + AstNode, Direction, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxToken, T, }; use crate::{AssistContext, AssistId, AssistKind, Assists}; @@ -25,8 +26,6 @@ pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<( let comma = ctx.find_token_syntax_at_offset(T![,])?; let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?; let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?; - let (mut prev_text, mut next_text) = (prev.to_string(), next.to_string()); - let (mut prev_range, mut next_range) = (prev.text_range(), next.text_range()); // Don't apply a "flip" in case of a last comma // that typically comes before punctuation @@ -40,53 +39,85 @@ pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<( return None; } - if let Some(parent) = comma.parent().and_then(ast::TokenTree::cast) { - // An attribute. It often contains a path followed by a token tree (e.g. `align(2)`), so we have - // to be smarter. - let prev_start = - match comma.siblings_with_tokens(Direction::Prev).skip(1).find(|it| it.kind() == T![,]) - { - Some(it) => position_after_token(it.as_token().unwrap()), - None => position_after_token(&parent.left_delimiter_token()?), - }; - let prev_end = prev.text_range().end(); - let next_start = next.text_range().start(); - let next_end = - match comma.siblings_with_tokens(Direction::Next).skip(1).find(|it| it.kind() == T![,]) - { - Some(it) => position_before_token(it.as_token().unwrap()), - None => position_before_token(&parent.right_delimiter_token()?), - }; - prev_range = TextRange::new(prev_start, prev_end); - next_range = TextRange::new(next_start, next_end); - let file_text = ctx.db().file_text(ctx.file_id().file_id()); - prev_text = file_text[prev_range].to_owned(); - next_text = file_text[next_range].to_owned(); - } + // FIXME: remove `clone_for_update` when `SyntaxEditor` handles it for us + let prev = match prev { + SyntaxElement::Node(node) => node.clone_for_update().syntax_element(), + _ => prev, + }; + let next = match next { + SyntaxElement::Node(node) => node.clone_for_update().syntax_element(), + _ => next, + }; acc.add( AssistId("flip_comma", AssistKind::RefactorRewrite), "Flip comma", comma.text_range(), - |edit| { - edit.replace(prev_range, next_text); - edit.replace(next_range, prev_text); + |builder| { + let parent = comma.parent().unwrap(); + let mut editor = builder.make_editor(&parent); + + if let Some(parent) = ast::TokenTree::cast(parent) { + // An attribute. It often contains a path followed by a + // token tree (e.g. `align(2)`), so we have to be smarter. + let (new_tree, mapping) = flip_tree(parent.clone(), comma); + editor.replace(parent.syntax(), new_tree.syntax()); + editor.add_mappings(mapping); + } else { + editor.replace(prev.clone(), next.clone()); + editor.replace(next.clone(), prev.clone()); + } + + builder.add_file_edits(ctx.file_id(), editor); }, ) } -fn position_before_token(token: &SyntaxToken) -> TextSize { - match non_trivia_sibling(token.clone().into(), Direction::Prev) { - Some(prev_token) => prev_token.text_range().end(), - None => token.text_range().start(), - } -} +fn flip_tree(tree: ast::TokenTree, comma: SyntaxToken) -> (ast::TokenTree, SyntaxMapping) { + let mut tree_iter = tree.token_trees_and_tokens(); + let before: Vec<_> = + tree_iter.by_ref().take_while(|it| it.as_token() != Some(&comma)).collect(); + let after: Vec<_> = tree_iter.collect(); -fn position_after_token(token: &SyntaxToken) -> TextSize { - match non_trivia_sibling(token.clone().into(), Direction::Next) { - Some(prev_token) => prev_token.text_range().start(), - None => token.text_range().end(), - } + let not_ws = |element: &NodeOrToken<_, SyntaxToken>| match element { + NodeOrToken::Token(token) => token.kind() != SyntaxKind::WHITESPACE, + NodeOrToken::Node(_) => true, + }; + + let is_comma = |element: &NodeOrToken<_, SyntaxToken>| match element { + NodeOrToken::Token(token) => token.kind() == T![,], + NodeOrToken::Node(_) => false, + }; + + let prev_start_untrimmed = match before.iter().rposition(is_comma) { + Some(pos) => pos + 1, + None => 1, + }; + let prev_end = 1 + before.iter().rposition(not_ws).unwrap(); + let prev_start = prev_start_untrimmed + + before[prev_start_untrimmed..prev_end].iter().position(not_ws).unwrap(); + + let next_start = after.iter().position(not_ws).unwrap(); + let next_end_untrimmed = match after.iter().position(is_comma) { + Some(pos) => pos, + None => after.len() - 1, + }; + let next_end = 1 + after[..next_end_untrimmed].iter().rposition(not_ws).unwrap(); + + let result = [ + &before[1..prev_start], + &after[next_start..next_end], + &before[prev_end..], + &[NodeOrToken::Token(comma)], + &after[..next_start], + &before[prev_start..prev_end], + &after[next_end..after.len() - 1], + ] + .concat(); + + let make = SyntaxFactory::new(); + let new_token_tree = make.token_tree(tree.left_delimiter_token().unwrap().kind(), result); + (new_token_tree, make.finish_with_mappings()) } #[cfg(test)] @@ -147,4 +178,9 @@ mod tests { r#"#[foo(bar, qux, baz(1 + 1), other)] struct Foo;"#, ); } + + #[test] + fn flip_comma_attribute_incomplete() { + check_assist_not_applicable(flip_comma, r#"#[repr(align(2),$0)] struct Foo;"#); + } } From bf6547a77ef85d4f6c1ee1427a82e5fa9201edcc Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 6 Dec 2024 16:26:00 +0100 Subject: [PATCH 62/95] Highlight right angle as part of fat arrow in macro rules arm --- crates/ide/src/syntax_highlighting/highlight.rs | 8 ++++++++ .../test_data/highlight_block_mod_items.html | 2 +- .../test_data/highlight_const.html | 4 ++-- .../test_data/highlight_doctest.html | 4 ++-- .../test_data/highlight_keywords_2015.html | 2 +- .../test_data/highlight_keywords_2018.html | 2 +- .../test_data/highlight_keywords_2021.html | 2 +- .../test_data/highlight_keywords_2024.html | 2 +- .../test_data/highlight_macros.html | 12 ++++++------ .../test_data/highlight_strings.html | 8 ++++---- .../test_data/highlight_unsafe.html | 4 ++-- 11 files changed, 29 insertions(+), 21 deletions(-) diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs index 96375937a1..3767a3917c 100644 --- a/crates/ide/src/syntax_highlighting/highlight.rs +++ b/crates/ide/src/syntax_highlighting/highlight.rs @@ -151,6 +151,14 @@ fn punctuation( T!['['] | T![']'] => HlPunct::Bracket, T!['{'] | T!['}'] => HlPunct::Brace, T!['('] | T![')'] => HlPunct::Parenthesis, + T![>] + if parent + .as_ref() + .and_then(SyntaxNode::parent) + .map_or(false, |it| it.kind() == MACRO_RULES) => + { + return HlOperator::Other.into() + } T![<] | T![>] => HlPunct::Angle, T![,] => HlPunct::Comma, T![:] => HlPunct::Colon, diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_block_mod_items.html b/crates/ide/src/syntax_highlighting/test_data/highlight_block_mod_items.html index 70f2d7203e..edd9639768 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_block_mod_items.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_block_mod_items.html @@ -46,7 +46,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
macro_rules! foo {
-    ($foo:ident) => {
+    ($foo:ident) => {
         mod y {
             pub struct $foo;
         }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_const.html b/crates/ide/src/syntax_highlighting/test_data/highlight_const.html
index a14f2cc88c..05289cfe3f 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_const.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_const.html
@@ -46,7 +46,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .unresolved_reference    { color: #FC5555; text-decoration: wavy underline; }
 
 
macro_rules! id {
-    ($($tt:tt)*) => {
+    ($($tt:tt)*) => {
         $($tt)*
     };
 }
@@ -79,7 +79,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 }
 
 macro_rules! unsafe_deref {
-    () => {
+    () => {
         *(&() as *const ())
     };
 }
\ No newline at end of file diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html index 35650bbe87..aa9d23250c 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html @@ -147,11 +147,11 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd } /// ``` -/// macro_rules! noop { ($expr:expr) => { $expr }} +/// macro_rules! noop { ($expr:expr) => { $expr }} /// noop!(1); /// ``` macro_rules! noop { - ($expr:expr) => { + ($expr:expr) => { $expr } } diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2015.html b/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2015.html index a790b38578..c2bf94fd9b 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2015.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2015.html @@ -54,7 +54,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd } macro_rules! void { - ($($tt:tt)*) => {} + ($($tt:tt)*) => {} } struct __ where Self:; diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2018.html b/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2018.html index 6dac066bfa..a30d16d532 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2018.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2018.html @@ -54,7 +54,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd } macro_rules! void { - ($($tt:tt)*) => {} + ($($tt:tt)*) => {} } struct __ where Self:; diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2021.html b/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2021.html index 6dac066bfa..a30d16d532 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2021.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2021.html @@ -54,7 +54,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd } macro_rules! void { - ($($tt:tt)*) => {} + ($($tt:tt)*) => {} } struct __ where Self:; diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2024.html b/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2024.html index 4ccc407990..b82a3f9f81 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2024.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_keywords_2024.html @@ -54,7 +54,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd } macro_rules! void { - ($($tt:tt)*) => {} + ($($tt:tt)*) => {} } struct __ where Self:; diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html b/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html index 196552020a..06673d1a73 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html @@ -54,7 +54,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd } Foo struct } macro_rules! def_fn { - ($($tt:tt)*) => {$($tt)*} + ($($tt:tt)*) => {$($tt)*} } def_fn! { @@ -64,24 +64,24 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd } macro_rules! dont_color_me_braces { - () => {0} + () => {0} } macro_rules! noop { - ($expr:expr) => { + ($expr:expr) => { $expr } } /// textually shadow previous definition macro_rules! noop { - ($expr:expr) => { + ($expr:expr) => { $expr } } macro_rules! keyword_frag { - ($type:ty) => ($type) + ($type:ty) => ($type) } macro with_args($i:ident) { @@ -95,7 +95,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd } macro_rules! id { - ($($tt:tt)*) => { + ($($tt:tt)*) => { $($tt)* }; } diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html index 5594a36e73..1385ae0510 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html @@ -46,7 +46,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
macro_rules! println {
-    ($($arg:tt)*) => ({
+    ($($arg:tt)*) => ({
         $crate::io::_print(format_args_nl!($($arg)*));
     })
 }
@@ -74,12 +74,12 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 }
 
 macro_rules! toho {
-    () => ($crate::panic!("not yet implemented"));
-    ($($arg:tt)+) => ($crate::panic!("not yet implemented: {}", format_args!($($arg)+)));
+    () => ($crate::panic!("not yet implemented"));
+    ($($arg:tt)+) => ($crate::panic!("not yet implemented: {}", format_args!($($arg)+)));
 }
 
 macro_rules! reuse_twice {
-    ($literal:literal) => {{stringify!($literal); format_args!($literal)}};
+    ($literal:literal) => {{stringify!($literal); format_args!($literal)}};
 }
 
 fn main() {
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
index be6176894b..4e69c82f3d 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
@@ -46,12 +46,12 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .unresolved_reference    { color: #FC5555; text-decoration: wavy underline; }
 
 
macro_rules! id {
-    ($($tt:tt)*) => {
+    ($($tt:tt)*) => {
         $($tt)*
     };
 }
 macro_rules! unsafe_deref {
-    () => {
+    () => {
         *(&() as *const ())
     };
 }

From c6208c86a586d745b50b65d047f13ed1c4e55206 Mon Sep 17 00:00:00 2001
From: Lukas Wirth 
Date: Fri, 6 Dec 2024 17:06:18 +0100
Subject: [PATCH 63/95] Temporarily disable completion resolve support for
 helix

---
 crates/rust-analyzer/src/bin/main.rs         | 12 ++-----
 crates/rust-analyzer/src/config.rs           | 34 +++++++++++++++-----
 crates/rust-analyzer/src/lsp/capabilities.rs |  6 +++-
 3 files changed, 33 insertions(+), 19 deletions(-)

diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs
index e872585c57..eac33be566 100644
--- a/crates/rust-analyzer/src/bin/main.rs
+++ b/crates/rust-analyzer/src/bin/main.rs
@@ -20,7 +20,6 @@ use rust_analyzer::{
     config::{Config, ConfigChange, ConfigErrors},
     from_json,
 };
-use semver::Version;
 use tracing_subscriber::fmt::writer::BoxMakeWriter;
 use vfs::AbsPathBuf;
 
@@ -204,18 +203,12 @@ fn run_server() -> anyhow::Result<()> {
         }
     };
 
-    let mut visual_studio_code_version = None;
-    if let Some(client_info) = client_info {
+    if let Some(client_info) = &client_info {
         tracing::info!(
             "Client '{}' {}",
             client_info.name,
             client_info.version.as_deref().unwrap_or_default()
         );
-        visual_studio_code_version = client_info
-            .name
-            .starts_with("Visual Studio Code")
-            .then(|| client_info.version.as_deref().map(Version::parse).and_then(Result::ok))
-            .flatten();
     }
 
     let workspace_roots = workspace_folders
@@ -230,8 +223,7 @@ fn run_server() -> anyhow::Result<()> {
         })
         .filter(|workspaces| !workspaces.is_empty())
         .unwrap_or_else(|| vec![root_path.clone()]);
-    let mut config =
-        Config::new(root_path, capabilities, workspace_roots, visual_studio_code_version);
+    let mut config = Config::new(root_path, capabilities, workspace_roots, client_info);
     if let Some(json) = initialization_options {
         let mut change = ConfigChange::default();
         change.change_client_config(json);
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 0fdc48a0e7..a4f9246a58 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -730,6 +730,12 @@ enum RatomlFile {
     Crate(LocalConfigInput),
 }
 
+#[derive(Clone, Debug)]
+struct ClientInfo {
+    name: String,
+    version: Option,
+}
+
 #[derive(Clone)]
 pub struct Config {
     /// Projects that have a Cargo.toml or a rust-project.json in a
@@ -744,7 +750,7 @@ pub struct Config {
     caps: ClientCapabilities,
     root_path: AbsPathBuf,
     snippets: Vec,
-    visual_studio_code_version: Option,
+    client_info: Option,
 
     default_config: &'static DefaultConfigData,
     /// Config node that obtains its initial value during the server initialization and
@@ -777,7 +783,7 @@ impl fmt::Debug for Config {
             .field("caps", &self.caps)
             .field("root_path", &self.root_path)
             .field("snippets", &self.snippets)
-            .field("visual_studio_code_version", &self.visual_studio_code_version)
+            .field("client_info", &self.client_info)
             .field("client_config", &self.client_config)
             .field("user_config", &self.user_config)
             .field("ratoml_file", &self.ratoml_file)
@@ -1335,7 +1341,7 @@ impl Config {
         root_path: AbsPathBuf,
         caps: lsp_types::ClientCapabilities,
         workspace_roots: Vec,
-        visual_studio_code_version: Option,
+        client_info: Option,
     ) -> Self {
         static DEFAULT_CONFIG_DATA: OnceLock<&'static DefaultConfigData> = OnceLock::new();
 
@@ -1346,7 +1352,10 @@ impl Config {
             root_path,
             snippets: Default::default(),
             workspace_roots,
-            visual_studio_code_version,
+            client_info: client_info.map(|it| ClientInfo {
+                name: it.name,
+                version: it.version.as_deref().map(Version::parse).and_then(Result::ok),
+            }),
             client_config: (FullConfigInput::default(), ConfigErrors(vec![])),
             default_config: DEFAULT_CONFIG_DATA.get_or_init(|| Box::leak(Box::default())),
             source_root_parent_map: Arc::new(FxHashMap::default()),
@@ -1446,9 +1455,11 @@ impl Config {
             limit: self.completion_limit(source_root).to_owned(),
             enable_term_search: self.completion_termSearch_enable(source_root).to_owned(),
             term_search_fuel: self.completion_termSearch_fuel(source_root).to_owned() as u64,
-            fields_to_resolve: CompletionFieldsToResolve::from_client_capabilities(
-                &client_capability_fields,
-            ),
+            fields_to_resolve: if self.client_is_helix() {
+                CompletionFieldsToResolve::empty()
+            } else {
+                CompletionFieldsToResolve::from_client_capabilities(&client_capability_fields)
+            },
         }
     }
 
@@ -2163,7 +2174,14 @@ impl Config {
     // VSCode is our reference implementation, so we allow ourselves to work around issues by
     // special casing certain versions
     pub fn visual_studio_code_version(&self) -> Option<&Version> {
-        self.visual_studio_code_version.as_ref()
+        self.client_info
+            .as_ref()
+            .filter(|it| it.name.starts_with("Visual Studio Code"))
+            .and_then(|it| it.version.as_ref())
+    }
+
+    pub fn client_is_helix(&self) -> bool {
+        self.client_info.as_ref().map(|it| it.name == "helix").unwrap_or_default()
     }
 }
 // Deserialization definitions
diff --git a/crates/rust-analyzer/src/lsp/capabilities.rs b/crates/rust-analyzer/src/lsp/capabilities.rs
index 82e6ae2b49..0362821209 100644
--- a/crates/rust-analyzer/src/lsp/capabilities.rs
+++ b/crates/rust-analyzer/src/lsp/capabilities.rs
@@ -41,7 +41,11 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities {
         })),
         hover_provider: Some(HoverProviderCapability::Simple(true)),
         completion_provider: Some(CompletionOptions {
-            resolve_provider: Some(config.caps().completions_resolve_provider()),
+            resolve_provider: if config.client_is_helix() {
+                None
+            } else {
+                Some(config.caps().completions_resolve_provider())
+            },
             trigger_characters: Some(vec![
                 ":".to_owned(),
                 ".".to_owned(),

From ad01392756adfed1b5f16029907450cc39a7676c Mon Sep 17 00:00:00 2001
From: Lukas Wirth 
Date: Fri, 6 Dec 2024 17:10:24 +0100
Subject: [PATCH 64/95] Temporarily disable completion resolve support for
 neovim

---
 crates/rust-analyzer/src/config.rs           | 6 +++++-
 crates/rust-analyzer/src/lsp/capabilities.rs | 4 ++--
 2 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index a4f9246a58..dd7351bcf2 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -1455,7 +1455,7 @@ impl Config {
             limit: self.completion_limit(source_root).to_owned(),
             enable_term_search: self.completion_termSearch_enable(source_root).to_owned(),
             term_search_fuel: self.completion_termSearch_fuel(source_root).to_owned() as u64,
-            fields_to_resolve: if self.client_is_helix() {
+            fields_to_resolve: if self.client_is_helix() || self.client_is_neovim() {
                 CompletionFieldsToResolve::empty()
             } else {
                 CompletionFieldsToResolve::from_client_capabilities(&client_capability_fields)
@@ -2183,6 +2183,10 @@ impl Config {
     pub fn client_is_helix(&self) -> bool {
         self.client_info.as_ref().map(|it| it.name == "helix").unwrap_or_default()
     }
+
+    pub fn client_is_neovim(&self) -> bool {
+        self.client_info.as_ref().map(|it| it.name == "Neovim").unwrap_or_default()
+    }
 }
 // Deserialization definitions
 
diff --git a/crates/rust-analyzer/src/lsp/capabilities.rs b/crates/rust-analyzer/src/lsp/capabilities.rs
index 0362821209..6d73319e67 100644
--- a/crates/rust-analyzer/src/lsp/capabilities.rs
+++ b/crates/rust-analyzer/src/lsp/capabilities.rs
@@ -41,8 +41,8 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities {
         })),
         hover_provider: Some(HoverProviderCapability::Simple(true)),
         completion_provider: Some(CompletionOptions {
-            resolve_provider: if config.client_is_helix() {
-                None
+            resolve_provider: if config.client_is_helix() || config.client_is_neovim() {
+                config.completion_item_edit_resolve().then_some(true)
             } else {
                 Some(config.caps().completions_resolve_provider())
             },

From 4fcecbb55ecdeea90d21019c8007e5a74aaf62cb Mon Sep 17 00:00:00 2001
From: Ali Bektas 
Date: Sun, 25 Aug 2024 13:28:07 +0200
Subject: [PATCH 65/95] Watch for user config ratoml

---
 crates/load-cargo/src/lib.rs                  | 24 +++++++++-
 crates/rust-analyzer/src/config.rs            | 14 ++----
 crates/rust-analyzer/src/global_state.rs      | 11 ++++-
 crates/rust-analyzer/src/reload.rs            |  8 +++-
 .../rust-analyzer/tests/slow-tests/ratoml.rs  | 44 ++++++++++++-------
 .../rust-analyzer/tests/slow-tests/support.rs | 30 +++++++++++--
 6 files changed, 94 insertions(+), 37 deletions(-)

diff --git a/crates/load-cargo/src/lib.rs b/crates/load-cargo/src/lib.rs
index 0e1606a699..ab8a780363 100644
--- a/crates/load-cargo/src/lib.rs
+++ b/crates/load-cargo/src/lib.rs
@@ -123,7 +123,7 @@ pub fn load_workspace(
             .collect()
     };
 
-    let project_folders = ProjectFolders::new(std::slice::from_ref(&ws), &[]);
+    let project_folders = ProjectFolders::new(std::slice::from_ref(&ws), &[], None);
     loader.set_config(vfs::loader::Config {
         load: project_folders.load,
         watch: vec![],
@@ -153,7 +153,11 @@ pub struct ProjectFolders {
 }
 
 impl ProjectFolders {
-    pub fn new(workspaces: &[ProjectWorkspace], global_excludes: &[AbsPathBuf]) -> ProjectFolders {
+    pub fn new(
+        workspaces: &[ProjectWorkspace],
+        global_excludes: &[AbsPathBuf],
+        user_config_dir_path: Option<&'static AbsPath>,
+    ) -> ProjectFolders {
         let mut res = ProjectFolders::default();
         let mut fsc = FileSetConfig::builder();
         let mut local_filesets = vec![];
@@ -291,6 +295,22 @@ impl ProjectFolders {
             }
         }
 
+        if let Some(user_config_path) = user_config_dir_path {
+            let ratoml_path = {
+                let mut p = user_config_path.to_path_buf();
+                p.push("rust-analyzer.toml");
+                p
+            };
+
+            let file_set_roots: Vec = vec![VfsPath::from(ratoml_path.to_owned())];
+            let entry = vfs::loader::Entry::Files(vec![ratoml_path.to_owned()]);
+
+            res.watch.push(res.load.len());
+            res.load.push(entry);
+            local_filesets.push(fsc.len() as u64);
+            fsc.add_file_set(file_set_roots)
+        }
+
         let fsc = fsc.build();
         res.source_root_config = SourceRootConfig { fsc, local_filesets };
 
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index dd7351bcf2..921c9b991c 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -804,22 +804,14 @@ impl std::ops::Deref for Config {
 }
 
 impl Config {
-    /// Path to the root configuration file. This can be seen as a generic way to define what would be `$XDG_CONFIG_HOME/rust-analyzer/rust-analyzer.toml` in Linux.
-    /// This path is equal to:
-    ///
-    /// |Platform | Value                                 | Example                                  |
-    /// | ------- | ------------------------------------- | ---------------------------------------- |
-    /// | Linux   | `$XDG_CONFIG_HOME` or `$HOME`/.config | /home/alice/.config                      |
-    /// | macOS   | `$HOME`/Library/Application Support   | /Users/Alice/Library/Application Support |
-    /// | Windows | `{FOLDERID_RoamingAppData}`           | C:\Users\Alice\AppData\Roaming           |
-    pub fn user_config_path() -> Option<&'static AbsPath> {
+    /// Path to the user configuration dir. This can be seen as a generic way to define what would be `$XDG_CONFIG_HOME/rust-analyzer` in Linux.
+    pub fn user_config_dir_path() -> Option<&'static AbsPath> {
         static USER_CONFIG_PATH: LazyLock> = LazyLock::new(|| {
             let user_config_path = if let Some(path) = env::var_os("__TEST_RA_USER_CONFIG_DIR") {
                 std::path::PathBuf::from(path)
             } else {
                 dirs::config_dir()?.join("rust-analyzer")
-            }
-            .join("rust-analyzer.toml");
+            };
             Some(AbsPathBuf::assert_utf8(user_config_path))
         });
         USER_CONFIG_PATH.as_deref()
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index 7fbeaa4e3e..8d5536e1b0 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -392,7 +392,14 @@ impl GlobalState {
             || !self.config.same_source_root_parent_map(&self.local_roots_parent_map)
         {
             let config_change = {
-                let user_config_path = Config::user_config_path();
+                let user_config_path = {
+                    let mut p = Config::user_config_dir_path().unwrap().to_path_buf();
+                    p.push("rust-analyzer.toml");
+                    p
+                };
+
+                let user_config_abs_path = Some(user_config_path.as_path());
+
                 let mut change = ConfigChange::default();
                 let db = self.analysis_host.raw_database();
 
@@ -411,7 +418,7 @@ impl GlobalState {
                     .collect_vec();
 
                 for (file_id, (_change_kind, vfs_path)) in modified_ratoml_files {
-                    if vfs_path.as_path() == user_config_path {
+                    if vfs_path.as_path() == user_config_abs_path {
                         change.change_user_config(Some(db.file_text(file_id)));
                         continue;
                     }
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index bc85afa0e4..b0b6220352 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -590,7 +590,7 @@ impl GlobalState {
             }
 
             watchers.extend(
-                iter::once(Config::user_config_path())
+                iter::once(Config::user_config_dir_path())
                     .chain(self.workspaces.iter().map(|ws| ws.manifest().map(ManifestPath::as_ref)))
                     .flatten()
                     .map(|glob_pattern| lsp_types::FileSystemWatcher {
@@ -613,7 +613,11 @@ impl GlobalState {
         }
 
         let files_config = self.config.files();
-        let project_folders = ProjectFolders::new(&self.workspaces, &files_config.exclude);
+        let project_folders = ProjectFolders::new(
+            &self.workspaces,
+            &files_config.exclude,
+            Config::user_config_dir_path().to_owned(),
+        );
 
         if (self.proc_macro_clients.is_empty() || !same_workspaces)
             && self.config.expand_proc_macros()
diff --git a/crates/rust-analyzer/tests/slow-tests/ratoml.rs b/crates/rust-analyzer/tests/slow-tests/ratoml.rs
index a857e0c296..623a9f76d1 100644
--- a/crates/rust-analyzer/tests/slow-tests/ratoml.rs
+++ b/crates/rust-analyzer/tests/slow-tests/ratoml.rs
@@ -30,6 +30,23 @@ impl RatomlTest {
         fixtures: Vec<&str>,
         roots: Vec<&str>,
         client_config: Option,
+    ) -> Self {
+        RatomlTest::new_with_lock(fixtures, roots, client_config, false)
+    }
+
+    fn new_locked(
+        fixtures: Vec<&str>,
+        roots: Vec<&str>,
+        client_config: Option,
+    ) -> Self {
+        RatomlTest::new_with_lock(fixtures, roots, client_config, true)
+    }
+
+    fn new_with_lock(
+        fixtures: Vec<&str>,
+        roots: Vec<&str>,
+        client_config: Option,
+        prelock: bool,
     ) -> Self {
         let tmp_dir = TestDir::new();
         let tmp_path = tmp_dir.path().to_owned();
@@ -46,7 +63,7 @@ impl RatomlTest {
             project = project.with_config(client_config);
         }
 
-        let server = project.server().wait_until_workspace_is_loaded();
+        let server = project.server_with_lock(prelock).wait_until_workspace_is_loaded();
 
         let mut case = Self { urls: vec![], server, tmp_path };
         let urls = fixtures.iter().map(|fixture| case.fixture_path(fixture)).collect::>();
@@ -72,7 +89,7 @@ impl RatomlTest {
         let mut spl = spl.into_iter();
         if let Some(first) = spl.next() {
             if first == "$$CONFIG_DIR$$" {
-                path = Config::user_config_path().unwrap().to_path_buf().into();
+                path = Config::user_config_dir_path().unwrap().to_path_buf().into();
             } else {
                 path = path.join(first);
             }
@@ -285,16 +302,15 @@ enum Value {
 //     }
 
 #[test]
-#[ignore = "the user config is currently not being watched on startup, fix this"]
 fn ratoml_user_config_detected() {
     if skip_slow_tests() {
         return;
     }
 
-    let server = RatomlTest::new(
+    let server = RatomlTest::new_locked(
         vec![
             r#"
-//- /$$CONFIG_DIR$$/rust-analyzer/rust-analyzer.toml
+//- /$$CONFIG_DIR$$/rust-analyzer.toml
 assist.emitMustUse = true
 "#,
             r#"
@@ -322,13 +338,12 @@ enum Value {
 }
 
 #[test]
-#[ignore = "the user config is currently not being watched on startup, fix this"]
 fn ratoml_create_user_config() {
     if skip_slow_tests() {
         return;
     }
 
-    let mut server = RatomlTest::new(
+    let mut server = RatomlTest::new_locked(
         vec![
             r#"
 //- /p1/Cargo.toml
@@ -353,10 +368,7 @@ enum Value {
         1,
         InternalTestingFetchConfigResponse::AssistEmitMustUse(false),
     );
-    server.create(
-        "//- /$$CONFIG_DIR$$/rust-analyzer/rust-analyzer.toml",
-        RatomlTest::EMIT_MUST_USE.to_owned(),
-    );
+    server.create("//- /$$CONFIG_DIR$$/rust-analyzer.toml", RatomlTest::EMIT_MUST_USE.to_owned());
     server.query(
         InternalTestingFetchConfigOption::AssistEmitMustUse,
         1,
@@ -365,13 +377,12 @@ enum Value {
 }
 
 #[test]
-#[ignore = "the user config is currently not being watched on startup, fix this"]
 fn ratoml_modify_user_config() {
     if skip_slow_tests() {
         return;
     }
 
-    let mut server = RatomlTest::new(
+    let mut server = RatomlTest::new_locked(
         vec![
             r#"
 //- /p1/Cargo.toml
@@ -386,7 +397,7 @@ enum Value {
     Text(String),
 }"#,
             r#"
-//- /$$CONFIG_DIR$$/rust-analyzer/rust-analyzer.toml
+//- /$$CONFIG_DIR$$/rust-analyzer.toml
 assist.emitMustUse = true"#,
         ],
         vec!["p1"],
@@ -407,13 +418,12 @@ assist.emitMustUse = true"#,
 }
 
 #[test]
-#[ignore = "the user config is currently not being watched on startup, fix this"]
 fn ratoml_delete_user_config() {
     if skip_slow_tests() {
         return;
     }
 
-    let mut server = RatomlTest::new(
+    let mut server = RatomlTest::new_locked(
         vec![
             r#"
 //- /p1/Cargo.toml
@@ -428,7 +438,7 @@ enum Value {
     Text(String),
 }"#,
             r#"
-//- /$$CONFIG_DIR$$/rust-analyzer/rust-analyzer.toml
+//- /$$CONFIG_DIR$$/rust-analyzer.toml
 assist.emitMustUse = true"#,
         ],
         vec!["p1"],
diff --git a/crates/rust-analyzer/tests/slow-tests/support.rs b/crates/rust-analyzer/tests/slow-tests/support.rs
index 78572e37a9..86137e3f2c 100644
--- a/crates/rust-analyzer/tests/slow-tests/support.rs
+++ b/crates/rust-analyzer/tests/slow-tests/support.rs
@@ -1,6 +1,6 @@
 use std::{
     cell::{Cell, RefCell},
-    fs,
+    env, fs,
     sync::Once,
     time::Duration,
 };
@@ -127,7 +127,30 @@ impl Project<'_> {
     }
 
     pub(crate) fn server(self) -> Server {
+        Project::server_with_lock(self, false)
+    }
+
+    /// `prelock` : Forcefully acquire a lock that will maintain the path to the config dir throughout the whole test.
+    ///
+    /// When testing we set the user config dir by setting an envvar `__TEST_RA_USER_CONFIG_DIR`.
+    /// This value must be maintained until the end of a test case. When tests run in parallel
+    /// this value may change thus making the tests flaky. As such, we use a `MutexGuard` that locks
+    /// the process until `Server` is dropped. To optimize parallelization we use a lock only when it is
+    /// needed, that is when a test uses config directory to do stuff. Our naive approach is to use a lock
+    /// if there is a path to config dir in the test fixture. However, in certain cases we create a
+    /// file in the config dir after server is run, something where our naive approach comes short.
+    /// Using a `prelock` allows us to force a lock when we know we need it.
+    pub(crate) fn server_with_lock(self, prelock: bool) -> Server {
         static CONFIG_DIR_LOCK: Mutex<()> = Mutex::new(());
+
+        let mut config_dir_guard = if prelock {
+            let v = Some(CONFIG_DIR_LOCK.lock());
+            env::set_var("__TEST_RA_USER_CONFIG_DIR", TestDir::new().path());
+            v
+        } else {
+            None
+        };
+
         let tmp_dir = self.tmp_dir.unwrap_or_else(|| {
             if self.root_dir_contains_symlink {
                 TestDir::new_symlink()
@@ -160,13 +183,14 @@ impl Project<'_> {
         assert!(mini_core.is_none());
         assert!(toolchain.is_none());
 
-        let mut config_dir_guard = None;
         for entry in fixture {
             if let Some(pth) = entry.path.strip_prefix("/$$CONFIG_DIR$$") {
                 if config_dir_guard.is_none() {
                     config_dir_guard = Some(CONFIG_DIR_LOCK.lock());
+                    env::set_var("__TEST_RA_USER_CONFIG_DIR", TestDir::new().path());
                 }
-                let path = Config::user_config_path().unwrap().join(&pth['/'.len_utf8()..]);
+
+                let path = Config::user_config_dir_path().unwrap().join(&pth['/'.len_utf8()..]);
                 fs::create_dir_all(path.parent().unwrap()).unwrap();
                 fs::write(path.as_path(), entry.text.as_bytes()).unwrap();
             } else {

From a9e015f8eeea28f7c9e5286c12c1776fc439b899 Mon Sep 17 00:00:00 2001
From: Chayim Refael Friedman 
Date: Sat, 7 Dec 2024 19:16:00 +0200
Subject: [PATCH 66/95] Coerce two `FnDef`s to fn pointers even if they are the
 same, if they are subtypes

That's because they can be the same function but still different substs, which mandates them to coerce to fn pointers in order to unify.
---
 crates/hir-ty/src/infer/coerce.rs   |  6 +++++-
 crates/hir-ty/src/tests/coercion.rs | 16 ++++++++++++++++
 2 files changed, 21 insertions(+), 1 deletion(-)

diff --git a/crates/hir-ty/src/infer/coerce.rs b/crates/hir-ty/src/infer/coerce.rs
index 366c3cb0f1..2fe90a8a92 100644
--- a/crates/hir-ty/src/infer/coerce.rs
+++ b/crates/hir-ty/src/infer/coerce.rs
@@ -125,7 +125,11 @@ impl CoerceMany {
         // pointers to have a chance at getting a match. See
         // https://github.com/rust-lang/rust/blob/7b805396bf46dce972692a6846ce2ad8481c5f85/src/librustc_typeck/check/coercion.rs#L877-L916
         let sig = match (self.merged_ty().kind(Interner), expr_ty.kind(Interner)) {
-            (TyKind::FnDef(x, _), TyKind::FnDef(y, _)) if x == y => None,
+            (TyKind::FnDef(x, _), TyKind::FnDef(y, _))
+                if x == y && ctx.table.unify(&self.merged_ty(), &expr_ty) =>
+            {
+                None
+            }
             (TyKind::Closure(x, _), TyKind::Closure(y, _)) if x == y => None,
             (TyKind::FnDef(..) | TyKind::Closure(..), TyKind::FnDef(..) | TyKind::Closure(..)) => {
                 // FIXME: we're ignoring safety here. To be more correct, if we have one FnDef and one Closure,
diff --git a/crates/hir-ty/src/tests/coercion.rs b/crates/hir-ty/src/tests/coercion.rs
index 273571901a..7992f1feee 100644
--- a/crates/hir-ty/src/tests/coercion.rs
+++ b/crates/hir-ty/src/tests/coercion.rs
@@ -942,3 +942,19 @@ fn main() {
 "#,
     )
 }
+
+#[test]
+fn regression_18626() {
+    check_no_mismatches(
+        r#"
+fn f() {
+    trait T {
+        fn f() {}
+    }
+    impl T for i32 {}
+    impl T for u32 {}
+    &[i32::f, u32::f] as &[fn()];
+}
+    "#,
+    );
+}

From 26333a467ec370c1618c40da9da462842658bcbe Mon Sep 17 00:00:00 2001
From: Chayim Refael Friedman 
Date: Sun, 8 Dec 2024 15:02:14 +0200
Subject: [PATCH 67/95] Fix a test that didn't test what it should

That is, fix the helper function and disable the tests for now.
---
 .../hir-def/src/nameres/tests/incremental.rs  | 90 ++++++++++---------
 1 file changed, 46 insertions(+), 44 deletions(-)

diff --git a/crates/hir-def/src/nameres/tests/incremental.rs b/crates/hir-def/src/nameres/tests/incremental.rs
index d920c10826..e54b0fb78f 100644
--- a/crates/hir-def/src/nameres/tests/incremental.rs
+++ b/crates/hir-def/src/nameres/tests/incremental.rs
@@ -8,7 +8,8 @@ fn check_def_map_is_not_recomputed(ra_fixture_initial: &str, ra_fixture_change:
     let krate = {
         let crate_graph = db.crate_graph();
         // Some of these tests use minicore/proc-macros which will be injected as the first crate
-        crate_graph.iter().last().unwrap()
+        let krate = crate_graph.iter().next().unwrap();
+        krate
     };
     {
         let events = db.log_executed(|| {
@@ -120,28 +121,29 @@ fn f() { foo }
     );
 }
 
-#[test]
-fn typing_inside_an_attribute_arg_should_not_invalidate_def_map() {
-    check_def_map_is_not_recomputed(
-        r"
-//- proc_macros: identity
-//- /lib.rs
-mod foo;
+// #[test]
+// fn typing_inside_an_attribute_arg_should_not_invalidate_def_map() {
+//     check_def_map_is_not_recomputed(
+//         r"
+// //- proc_macros: identity
+// //- /lib.rs
+// mod foo;
 
-//- /foo/mod.rs
-pub mod bar;
+// //- /foo/mod.rs
+// pub mod bar;
+
+// //- /foo/bar.rs
+// $0
+// #[proc_macros::identity]
+// fn f() {}
+// ",
+//         r"
+// #[proc_macros::identity(foo)]
+// fn f() {}
+// ",
+//     );
+// }
 
-//- /foo/bar.rs
-$0
-#[proc_macros::identity]
-fn f() {}
-",
-        r"
-#[proc_macros::identity(foo)]
-fn f() {}
-",
-    );
-}
 #[test]
 fn typing_inside_macro_heavy_file_should_not_invalidate_def_map() {
     check_def_map_is_not_recomputed(
@@ -198,31 +200,31 @@ pub struct S {}
     );
 }
 
-#[test]
-fn typing_inside_a_derive_should_not_invalidate_def_map() {
-    check_def_map_is_not_recomputed(
-        r"
-//- proc_macros: derive_identity
-//- minicore:derive
-//- /lib.rs
-mod foo;
+// #[test]
+// fn typing_inside_a_derive_should_not_invalidate_def_map() {
+//     check_def_map_is_not_recomputed(
+//         r"
+// //- proc_macros: derive_identity
+// //- minicore:derive
+// //- /lib.rs
+// mod foo;
 
-//- /foo/mod.rs
-pub mod bar;
+// //- /foo/mod.rs
+// pub mod bar;
 
-//- /foo/bar.rs
-$0
-#[derive(proc_macros::DeriveIdentity)]
-#[allow()]
-struct S;
-",
-        r"
-#[derive(proc_macros::DeriveIdentity)]
-#[allow(dead_code)]
-struct S;
-",
-    );
-}
+// //- /foo/bar.rs
+// $0
+// #[derive(proc_macros::DeriveIdentity)]
+// #[allow()]
+// struct S;
+// ",
+//         r"
+// #[derive(proc_macros::DeriveIdentity)]
+// #[allow(dead_code)]
+// struct S;
+// ",
+//     );
+// }
 
 #[test]
 fn typing_inside_a_function_should_not_invalidate_item_expansions() {

From e66b4f336dfb402ac28445d695d21e8709edc4d4 Mon Sep 17 00:00:00 2001
From: Giga Bowser <45986823+Giga-Bowser@users.noreply.github.com>
Date: Mon, 18 Nov 2024 18:11:13 -0500
Subject: [PATCH 68/95] fix: Map new replacement nodes to their mutable
 equivalents in `SyntaxEditor`

---
 crates/ide-assists/src/handlers/flip_binexpr.rs       | 5 ++---
 crates/ide-assists/src/handlers/flip_comma.rs         | 5 ++---
 crates/ide-assists/src/handlers/flip_trait_bound.rs   | 4 ++--
 crates/ide-assists/src/handlers/reorder_fields.rs     | 7 +++----
 crates/ide-assists/src/handlers/reorder_impl_items.rs | 8 ++++----
 crates/ide-assists/src/handlers/sort_items.rs         | 7 +++----
 crates/syntax/src/syntax_editor/edit_algo.rs          | 6 ++++++
 7 files changed, 22 insertions(+), 20 deletions(-)

diff --git a/crates/ide-assists/src/handlers/flip_binexpr.rs b/crates/ide-assists/src/handlers/flip_binexpr.rs
index 601fd29f8e..818a868fe3 100644
--- a/crates/ide-assists/src/handlers/flip_binexpr.rs
+++ b/crates/ide-assists/src/handlers/flip_binexpr.rs
@@ -52,9 +52,8 @@ pub(crate) fn flip_binexpr(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
             if let FlipAction::FlipAndReplaceOp(binary_op) = action {
                 editor.replace(op_token, make.token(binary_op))
             };
-            // FIXME: remove `clone_for_update` when `SyntaxEditor` handles it for us
-            editor.replace(lhs.syntax(), rhs.syntax().clone_for_update());
-            editor.replace(rhs.syntax(), lhs.syntax().clone_for_update());
+            editor.replace(lhs.syntax(), rhs.syntax());
+            editor.replace(rhs.syntax(), lhs.syntax());
             editor.add_mappings(make.finish_with_mappings());
             builder.add_file_edits(ctx.file_id(), editor);
         },
diff --git a/crates/ide-assists/src/handlers/flip_comma.rs b/crates/ide-assists/src/handlers/flip_comma.rs
index 490a9ee3c0..95e035c053 100644
--- a/crates/ide-assists/src/handlers/flip_comma.rs
+++ b/crates/ide-assists/src/handlers/flip_comma.rs
@@ -39,13 +39,12 @@ pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<(
         return None;
     }
 
-    // FIXME: remove `clone_for_update` when `SyntaxEditor` handles it for us
     let prev = match prev {
-        SyntaxElement::Node(node) => node.clone_for_update().syntax_element(),
+        SyntaxElement::Node(node) => node.syntax_element(),
         _ => prev,
     };
     let next = match next {
-        SyntaxElement::Node(node) => node.clone_for_update().syntax_element(),
+        SyntaxElement::Node(node) => node.syntax_element(),
         _ => next,
     };
 
diff --git a/crates/ide-assists/src/handlers/flip_trait_bound.rs b/crates/ide-assists/src/handlers/flip_trait_bound.rs
index 03366bd861..298e5bd82c 100644
--- a/crates/ide-assists/src/handlers/flip_trait_bound.rs
+++ b/crates/ide-assists/src/handlers/flip_trait_bound.rs
@@ -37,8 +37,8 @@ pub(crate) fn flip_trait_bound(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
         target,
         |builder| {
             let mut editor = builder.make_editor(parent.syntax());
-            editor.replace(before.clone(), after.clone_for_update());
-            editor.replace(after.clone(), before.clone_for_update());
+            editor.replace(before.clone(), after.clone());
+            editor.replace(after, before);
             builder.add_file_edits(ctx.file_id(), editor);
         },
     )
diff --git a/crates/ide-assists/src/handlers/reorder_fields.rs b/crates/ide-assists/src/handlers/reorder_fields.rs
index 4d3e85ab1b..972303c2a0 100644
--- a/crates/ide-assists/src/handlers/reorder_fields.rs
+++ b/crates/ide-assists/src/handlers/reorder_fields.rs
@@ -92,10 +92,9 @@ fn replace(
     fields: impl Iterator,
     sorted_fields: impl IntoIterator,
 ) {
-    fields.zip(sorted_fields).for_each(|(field, sorted_field)| {
-        // FIXME: remove `clone_for_update` when `SyntaxEditor` handles it for us
-        editor.replace(field.syntax(), sorted_field.syntax().clone_for_update())
-    });
+    fields
+        .zip(sorted_fields)
+        .for_each(|(field, sorted_field)| editor.replace(field.syntax(), sorted_field.syntax()));
 }
 
 fn compute_fields_ranks(
diff --git a/crates/ide-assists/src/handlers/reorder_impl_items.rs b/crates/ide-assists/src/handlers/reorder_impl_items.rs
index d7fa882612..eb1d538f87 100644
--- a/crates/ide-assists/src/handlers/reorder_impl_items.rs
+++ b/crates/ide-assists/src/handlers/reorder_impl_items.rs
@@ -101,10 +101,10 @@ pub(crate) fn reorder_impl_items(acc: &mut Assists, ctx: &AssistContext<'_>) ->
         |builder| {
             let mut editor = builder.make_editor(&parent_node);
 
-            assoc_items.into_iter().zip(sorted).for_each(|(old, new)| {
-                // FIXME: remove `clone_for_update` when `SyntaxEditor` handles it for us
-                editor.replace(old.syntax(), new.clone_for_update().syntax())
-            });
+            assoc_items
+                .into_iter()
+                .zip(sorted)
+                .for_each(|(old, new)| editor.replace(old.syntax(), new.syntax()));
 
             builder.add_file_edits(ctx.file_id(), editor);
         },
diff --git a/crates/ide-assists/src/handlers/sort_items.rs b/crates/ide-assists/src/handlers/sort_items.rs
index 7307325e49..54e16d4d80 100644
--- a/crates/ide-assists/src/handlers/sort_items.rs
+++ b/crates/ide-assists/src/handlers/sort_items.rs
@@ -133,10 +133,9 @@ impl AddRewrite for Assists {
             |builder| {
                 let mut editor = builder.make_editor(target);
 
-                old.into_iter().zip(new).for_each(|(old, new)| {
-                    // FIXME: remove `clone_for_update` when `SyntaxEditor` handles it for us
-                    editor.replace(old.syntax(), new.clone_for_update().syntax())
-                });
+                old.into_iter()
+                    .zip(new)
+                    .for_each(|(old, new)| editor.replace(old.syntax(), new.syntax()));
 
                 builder.add_file_edits(builder.file_id, editor)
             },
diff --git a/crates/syntax/src/syntax_editor/edit_algo.rs b/crates/syntax/src/syntax_editor/edit_algo.rs
index 71b69dbec1..57ecbe5701 100644
--- a/crates/syntax/src/syntax_editor/edit_algo.rs
+++ b/crates/syntax/src/syntax_editor/edit_algo.rs
@@ -155,6 +155,12 @@ pub(super) fn apply_edits(editor: SyntaxEditor) -> SyntaxEdit {
                     }
                 };
             }
+            Change::Replace(SyntaxElement::Node(target), Some(SyntaxElement::Node(new_target))) => {
+                *target = tree_mutator.make_syntax_mut(target);
+                if new_target.ancestors().any(|node| node == tree_mutator.immutable) {
+                    *new_target = new_target.clone_for_update();
+                }
+            }
             Change::Replace(target, _) | Change::ReplaceWithMany(target, _) => {
                 *target = tree_mutator.make_element_mut(target);
             }

From 10a07a443d26e9cc9d02dee8d431acd1e631eb7b Mon Sep 17 00:00:00 2001
From: Lukas Wirth 
Date: Wed, 30 Oct 2024 12:03:08 +0100
Subject: [PATCH 69/95] Fix config guard lock for ratoml tests

---
 crates/load-cargo/src/lib.rs                  |  4 +-
 crates/rust-analyzer/src/config.rs            | 25 ++++------
 crates/rust-analyzer/src/global_state.rs      | 10 ++--
 crates/rust-analyzer/src/reload.rs            |  4 +-
 .../rust-analyzer/tests/slow-tests/ratoml.rs  | 29 +++--------
 .../rust-analyzer/tests/slow-tests/support.rs | 48 +++++++++++++------
 6 files changed, 58 insertions(+), 62 deletions(-)

diff --git a/crates/load-cargo/src/lib.rs b/crates/load-cargo/src/lib.rs
index ab8a780363..cf26845b11 100644
--- a/crates/load-cargo/src/lib.rs
+++ b/crates/load-cargo/src/lib.rs
@@ -156,7 +156,7 @@ impl ProjectFolders {
     pub fn new(
         workspaces: &[ProjectWorkspace],
         global_excludes: &[AbsPathBuf],
-        user_config_dir_path: Option<&'static AbsPath>,
+        user_config_dir_path: Option<&AbsPath>,
     ) -> ProjectFolders {
         let mut res = ProjectFolders::default();
         let mut fsc = FileSetConfig::builder();
@@ -302,7 +302,7 @@ impl ProjectFolders {
                 p
             };
 
-            let file_set_roots: Vec = vec![VfsPath::from(ratoml_path.to_owned())];
+            let file_set_roots = vec![VfsPath::from(ratoml_path.to_owned())];
             let entry = vfs::loader::Entry::Files(vec![ratoml_path.to_owned()]);
 
             res.watch.push(res.load.len());
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 921c9b991c..694748f82f 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -3,11 +3,7 @@
 //! Of particular interest is the `feature_flags` hash map: while other fields
 //! configure the server itself, feature flags are passed into analysis, and
 //! tweak things like automatic insertion of `()` in completions.
-use std::{
-    env, fmt, iter,
-    ops::Not,
-    sync::{LazyLock, OnceLock},
-};
+use std::{env, fmt, iter, ops::Not, sync::OnceLock};
 
 use cfg::{CfgAtom, CfgDiff};
 use hir::Symbol;
@@ -805,16 +801,13 @@ impl std::ops::Deref for Config {
 
 impl Config {
     /// Path to the user configuration dir. This can be seen as a generic way to define what would be `$XDG_CONFIG_HOME/rust-analyzer` in Linux.
-    pub fn user_config_dir_path() -> Option<&'static AbsPath> {
-        static USER_CONFIG_PATH: LazyLock> = LazyLock::new(|| {
-            let user_config_path = if let Some(path) = env::var_os("__TEST_RA_USER_CONFIG_DIR") {
-                std::path::PathBuf::from(path)
-            } else {
-                dirs::config_dir()?.join("rust-analyzer")
-            };
-            Some(AbsPathBuf::assert_utf8(user_config_path))
-        });
-        USER_CONFIG_PATH.as_deref()
+    pub fn user_config_dir_path() -> Option {
+        let user_config_path = if let Some(path) = env::var_os("__TEST_RA_USER_CONFIG_DIR") {
+            std::path::PathBuf::from(path)
+        } else {
+            dirs::config_dir()?.join("rust-analyzer")
+        };
+        Some(AbsPathBuf::assert_utf8(user_config_path))
     }
 
     pub fn same_source_root_parent_map(
@@ -1254,7 +1247,7 @@ pub struct NotificationsConfig {
     pub cargo_toml_not_found: bool,
 }
 
-#[derive(Deserialize, Serialize, Debug, Clone)]
+#[derive(Debug, Clone)]
 pub enum RustfmtConfig {
     Rustfmt { extra_args: Vec, enable_range_formatting: bool },
     CustomCommand { command: String, args: Vec },
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index 8d5536e1b0..5f83570284 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -392,13 +392,13 @@ impl GlobalState {
             || !self.config.same_source_root_parent_map(&self.local_roots_parent_map)
         {
             let config_change = {
-                let user_config_path = {
-                    let mut p = Config::user_config_dir_path().unwrap().to_path_buf();
+                let user_config_path = (|| {
+                    let mut p = Config::user_config_dir_path()?;
                     p.push("rust-analyzer.toml");
-                    p
-                };
+                    Some(p)
+                })();
 
-                let user_config_abs_path = Some(user_config_path.as_path());
+                let user_config_abs_path = user_config_path.as_deref();
 
                 let mut change = ConfigChange::default();
                 let db = self.analysis_host.raw_database();
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index b0b6220352..4549735fef 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -590,7 +590,7 @@ impl GlobalState {
             }
 
             watchers.extend(
-                iter::once(Config::user_config_dir_path())
+                iter::once(Config::user_config_dir_path().as_deref())
                     .chain(self.workspaces.iter().map(|ws| ws.manifest().map(ManifestPath::as_ref)))
                     .flatten()
                     .map(|glob_pattern| lsp_types::FileSystemWatcher {
@@ -616,7 +616,7 @@ impl GlobalState {
         let project_folders = ProjectFolders::new(
             &self.workspaces,
             &files_config.exclude,
-            Config::user_config_dir_path().to_owned(),
+            Config::user_config_dir_path().as_deref(),
         );
 
         if (self.proc_macro_clients.is_empty() || !same_workspaces)
diff --git a/crates/rust-analyzer/tests/slow-tests/ratoml.rs b/crates/rust-analyzer/tests/slow-tests/ratoml.rs
index 623a9f76d1..5dfaf0d365 100644
--- a/crates/rust-analyzer/tests/slow-tests/ratoml.rs
+++ b/crates/rust-analyzer/tests/slow-tests/ratoml.rs
@@ -30,23 +30,6 @@ impl RatomlTest {
         fixtures: Vec<&str>,
         roots: Vec<&str>,
         client_config: Option,
-    ) -> Self {
-        RatomlTest::new_with_lock(fixtures, roots, client_config, false)
-    }
-
-    fn new_locked(
-        fixtures: Vec<&str>,
-        roots: Vec<&str>,
-        client_config: Option,
-    ) -> Self {
-        RatomlTest::new_with_lock(fixtures, roots, client_config, true)
-    }
-
-    fn new_with_lock(
-        fixtures: Vec<&str>,
-        roots: Vec<&str>,
-        client_config: Option,
-        prelock: bool,
     ) -> Self {
         let tmp_dir = TestDir::new();
         let tmp_path = tmp_dir.path().to_owned();
@@ -63,7 +46,7 @@ impl RatomlTest {
             project = project.with_config(client_config);
         }
 
-        let server = project.server_with_lock(prelock).wait_until_workspace_is_loaded();
+        let server = project.server_with_lock(true).wait_until_workspace_is_loaded();
 
         let mut case = Self { urls: vec![], server, tmp_path };
         let urls = fixtures.iter().map(|fixture| case.fixture_path(fixture)).collect::>();
@@ -89,7 +72,7 @@ impl RatomlTest {
         let mut spl = spl.into_iter();
         if let Some(first) = spl.next() {
             if first == "$$CONFIG_DIR$$" {
-                path = Config::user_config_dir_path().unwrap().to_path_buf().into();
+                path = Config::user_config_dir_path().unwrap().into();
             } else {
                 path = path.join(first);
             }
@@ -307,7 +290,7 @@ fn ratoml_user_config_detected() {
         return;
     }
 
-    let server = RatomlTest::new_locked(
+    let server = RatomlTest::new(
         vec![
             r#"
 //- /$$CONFIG_DIR$$/rust-analyzer.toml
@@ -343,7 +326,7 @@ fn ratoml_create_user_config() {
         return;
     }
 
-    let mut server = RatomlTest::new_locked(
+    let mut server = RatomlTest::new(
         vec![
             r#"
 //- /p1/Cargo.toml
@@ -382,7 +365,7 @@ fn ratoml_modify_user_config() {
         return;
     }
 
-    let mut server = RatomlTest::new_locked(
+    let mut server = RatomlTest::new(
         vec![
             r#"
 //- /p1/Cargo.toml
@@ -423,7 +406,7 @@ fn ratoml_delete_user_config() {
         return;
     }
 
-    let mut server = RatomlTest::new_locked(
+    let mut server = RatomlTest::new(
         vec![
             r#"
 //- /p1/Cargo.toml
diff --git a/crates/rust-analyzer/tests/slow-tests/support.rs b/crates/rust-analyzer/tests/slow-tests/support.rs
index 86137e3f2c..5a88a5515c 100644
--- a/crates/rust-analyzer/tests/slow-tests/support.rs
+++ b/crates/rust-analyzer/tests/slow-tests/support.rs
@@ -1,7 +1,7 @@
 use std::{
     cell::{Cell, RefCell},
     env, fs,
-    sync::Once,
+    sync::{Once, OnceLock},
     time::Duration,
 };
 
@@ -140,13 +140,36 @@ impl Project<'_> {
     /// if there is a path to config dir in the test fixture. However, in certain cases we create a
     /// file in the config dir after server is run, something where our naive approach comes short.
     /// Using a `prelock` allows us to force a lock when we know we need it.
-    pub(crate) fn server_with_lock(self, prelock: bool) -> Server {
-        static CONFIG_DIR_LOCK: Mutex<()> = Mutex::new(());
+    pub(crate) fn server_with_lock(self, config_lock: bool) -> Server {
+        static CONFIG_DIR_LOCK: OnceLock<(Utf8PathBuf, Mutex<()>)> = OnceLock::new();
 
-        let mut config_dir_guard = if prelock {
-            let v = Some(CONFIG_DIR_LOCK.lock());
-            env::set_var("__TEST_RA_USER_CONFIG_DIR", TestDir::new().path());
-            v
+        let config_dir_guard = if config_lock {
+            Some({
+                let (path, mutex) = CONFIG_DIR_LOCK.get_or_init(|| {
+                    let value = TestDir::new().keep().path().to_owned();
+                    env::set_var("__TEST_RA_USER_CONFIG_DIR", &value);
+                    (value, Mutex::new(()))
+                });
+                #[allow(dyn_drop)]
+                (mutex.lock(), {
+                    Box::new({
+                        struct Dropper(Utf8PathBuf);
+                        impl Drop for Dropper {
+                            fn drop(&mut self) {
+                                for entry in fs::read_dir(&self.0).unwrap() {
+                                    let path = entry.unwrap().path();
+                                    if path.is_file() {
+                                        fs::remove_file(path).unwrap();
+                                    } else if path.is_dir() {
+                                        fs::remove_dir_all(path).unwrap();
+                                    }
+                                }
+                            }
+                        }
+                        Dropper(path.clone())
+                    }) as Box
+                })
+            })
         } else {
             None
         };
@@ -185,11 +208,6 @@ impl Project<'_> {
 
         for entry in fixture {
             if let Some(pth) = entry.path.strip_prefix("/$$CONFIG_DIR$$") {
-                if config_dir_guard.is_none() {
-                    config_dir_guard = Some(CONFIG_DIR_LOCK.lock());
-                    env::set_var("__TEST_RA_USER_CONFIG_DIR", TestDir::new().path());
-                }
-
                 let path = Config::user_config_dir_path().unwrap().join(&pth['/'.len_utf8()..]);
                 fs::create_dir_all(path.parent().unwrap()).unwrap();
                 fs::write(path.as_path(), entry.text.as_bytes()).unwrap();
@@ -293,12 +311,14 @@ pub(crate) struct Server {
     client: Connection,
     /// XXX: remove the tempdir last
     dir: TestDir,
-    _config_dir_guard: Option>,
+    #[allow(dyn_drop)]
+    _config_dir_guard: Option<(MutexGuard<'static, ()>, Box)>,
 }
 
 impl Server {
+    #[allow(dyn_drop)]
     fn new(
-        config_dir_guard: Option>,
+        config_dir_guard: Option<(MutexGuard<'static, ()>, Box)>,
         dir: TestDir,
         config: Config,
     ) -> Server {

From d5b96b7998d78ea2e533e9fa852cf2224d0fdaaf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Lauren=C8=9Biu=20Nicola?= 
Date: Mon, 9 Dec 2024 11:17:17 +0200
Subject: [PATCH 70/95] Remove unstable attributes in minicore

---
 crates/test-utils/src/minicore.rs | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs
index f5c8466cb9..99dfabe174 100644
--- a/crates/test-utils/src/minicore.rs
+++ b/crates/test-utils/src/minicore.rs
@@ -728,7 +728,6 @@ pub mod ops {
                 }
             }
 
-            #[unstable(feature = "async_fn_traits", issue = "none")]
             impl AsyncFnMut for &F
             where
                 F: AsyncFn,
@@ -746,7 +745,6 @@ pub mod ops {
                 }
             }
 
-            #[unstable(feature = "async_fn_traits", issue = "none")]
             impl<'a, A: Tuple, F: ?Sized> AsyncFnOnce for &'a F
             where
                 F: AsyncFn,
@@ -759,7 +757,6 @@ pub mod ops {
                 }
             }
 
-            #[unstable(feature = "async_fn_traits", issue = "none")]
             impl AsyncFnMut for &mut F
             where
                 F: AsyncFnMut,
@@ -777,7 +774,6 @@ pub mod ops {
                 }
             }
 
-            #[unstable(feature = "async_fn_traits", issue = "none")]
             impl<'a, A: Tuple, F: ?Sized> AsyncFnOnce for &'a mut F
             where
                 F: AsyncFnMut,

From 956ff4d8c5d86a5d5eb867599cefcef7cc124496 Mon Sep 17 00:00:00 2001
From: Lukas Wirth 
Date: Mon, 9 Dec 2024 09:48:54 +0100
Subject: [PATCH 71/95] Rename test fixture crates to ra_test_fixture

---
 crates/hir-def/src/body/tests.rs              |   3 +-
 .../hir-def/src/macro_expansion_tests/mod.rs  |   3 +-
 crates/hir-def/src/nameres/tests.rs           |   4 +-
 .../hir-def/src/nameres/tests/incremental.rs  |  13 +-
 crates/hir-def/src/test_db.rs                 |  13 +
 .../generate_documentation_template.rs        |  20 +-
 crates/ide-assists/src/tests/generated.rs     |   2 +-
 crates/ide/src/hover/tests.rs                 | 744 +++++++++---------
 crates/test-fixture/src/lib.rs                |   2 +-
 9 files changed, 407 insertions(+), 397 deletions(-)

diff --git a/crates/hir-def/src/body/tests.rs b/crates/hir-def/src/body/tests.rs
index 82d46d2e49..8f01091584 100644
--- a/crates/hir-def/src/body/tests.rs
+++ b/crates/hir-def/src/body/tests.rs
@@ -1,6 +1,5 @@
 mod block;
 
-use base_db::SourceDatabase;
 use expect_test::{expect, Expect};
 use test_fixture::WithFixture;
 
@@ -11,7 +10,7 @@ use super::*;
 fn lower(ra_fixture: &str) -> (TestDB, Arc, DefWithBodyId) {
     let db = TestDB::with_files(ra_fixture);
 
-    let krate = db.crate_graph().iter().next().unwrap();
+    let krate = db.fetch_test_crate();
     let def_map = db.crate_def_map(krate);
     let mut fn_def = None;
     'outer: for (_, module) in def_map.modules() {
diff --git a/crates/hir-def/src/macro_expansion_tests/mod.rs b/crates/hir-def/src/macro_expansion_tests/mod.rs
index d5b94f0ae4..0475e40c5b 100644
--- a/crates/hir-def/src/macro_expansion_tests/mod.rs
+++ b/crates/hir-def/src/macro_expansion_tests/mod.rs
@@ -16,7 +16,6 @@ mod proc_macros;
 
 use std::{iter, ops::Range, sync};
 
-use base_db::SourceDatabase;
 use expect_test::Expect;
 use hir_expand::{
     db::ExpandDatabase,
@@ -63,7 +62,7 @@ pub fn identity_when_valid(_attr: TokenStream, item: TokenStream) -> TokenStream
         },
     )];
     let db = TestDB::with_files_extra_proc_macros(ra_fixture, extra_proc_macros);
-    let krate = db.crate_graph().iter().next().unwrap();
+    let krate = db.fetch_test_crate();
     let def_map = db.crate_def_map(krate);
     let local_id = DefMap::ROOT;
     let module = def_map.module_id(local_id);
diff --git a/crates/hir-def/src/nameres/tests.rs b/crates/hir-def/src/nameres/tests.rs
index e1e30e5cec..32c158415b 100644
--- a/crates/hir-def/src/nameres/tests.rs
+++ b/crates/hir-def/src/nameres/tests.rs
@@ -13,13 +13,13 @@ use crate::{db::DefDatabase, nameres::DefMap, test_db::TestDB};
 
 fn compute_crate_def_map(ra_fixture: &str) -> Arc {
     let db = TestDB::with_files(ra_fixture);
-    let krate = db.crate_graph().iter().next().unwrap();
+    let krate = db.fetch_test_crate();
     db.crate_def_map(krate)
 }
 
 fn render_crate_def_map(ra_fixture: &str) -> String {
     let db = TestDB::with_files(ra_fixture);
-    let krate = db.crate_graph().iter().next().unwrap();
+    let krate = db.fetch_test_crate();
     db.crate_def_map(krate).dump(&db)
 }
 
diff --git a/crates/hir-def/src/nameres/tests/incremental.rs b/crates/hir-def/src/nameres/tests/incremental.rs
index e54b0fb78f..1cfbabca28 100644
--- a/crates/hir-def/src/nameres/tests/incremental.rs
+++ b/crates/hir-def/src/nameres/tests/incremental.rs
@@ -1,16 +1,11 @@
-use base_db::{SourceDatabase, SourceDatabaseFileInputExt as _};
+use base_db::SourceDatabaseFileInputExt as _;
 use test_fixture::WithFixture;
 
 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);
-    let krate = {
-        let crate_graph = db.crate_graph();
-        // Some of these tests use minicore/proc-macros which will be injected as the first crate
-        let krate = crate_graph.iter().next().unwrap();
-        krate
-    };
+    let krate = db.fetch_test_crate();
     {
         let events = db.log_executed(|| {
             db.crate_def_map(krate);
@@ -121,6 +116,8 @@ fn f() { foo }
     );
 }
 
+// Would be nice if this was the case, but as attribute inputs are stored in the item tree, this is
+// not currently the case.
 // #[test]
 // fn typing_inside_an_attribute_arg_should_not_invalidate_def_map() {
 //     check_def_map_is_not_recomputed(
@@ -200,6 +197,8 @@ pub struct S {}
     );
 }
 
+// Would be nice if this was the case, but as attribute inputs are stored in the item tree, this is
+// not currently the case.
 // #[test]
 // fn typing_inside_a_derive_should_not_invalidate_def_map() {
 //     check_def_map_is_not_recomputed(
diff --git a/crates/hir-def/src/test_db.rs b/crates/hir-def/src/test_db.rs
index 0c36c88fb0..54e6c1fd20 100644
--- a/crates/hir-def/src/test_db.rs
+++ b/crates/hir-def/src/test_db.rs
@@ -78,6 +78,19 @@ impl FileLoader for TestDB {
 }
 
 impl TestDB {
+    pub(crate) fn fetch_test_crate(&self) -> CrateId {
+        let crate_graph = self.crate_graph();
+        let it = crate_graph
+            .iter()
+            .find(|&idx| {
+                crate_graph[idx].display_name.as_ref().map(|it| it.canonical_name().as_str())
+                    == Some("ra_test_fixture")
+            })
+            .or_else(|| crate_graph.iter().next())
+            .unwrap();
+        it
+    }
+
     pub(crate) fn module_for_file(&self, file_id: FileId) -> ModuleId {
         for &krate in self.relevant_crates(file_id).iter() {
             let crate_def_map = self.crate_def_map(krate);
diff --git a/crates/ide-assists/src/handlers/generate_documentation_template.rs b/crates/ide-assists/src/handlers/generate_documentation_template.rs
index c5c70c9f8e..862be791d1 100644
--- a/crates/ide-assists/src/handlers/generate_documentation_template.rs
+++ b/crates/ide-assists/src/handlers/generate_documentation_template.rs
@@ -88,7 +88,7 @@ pub(crate) fn generate_documentation_template(
 // /// # Examples
 // ///
 // /// ```
-// /// use test::add;
+// /// use ra_test_fixture::add;
 // ///
 // /// assert_eq!(add(a, b), );
 // /// ```
@@ -596,7 +596,7 @@ pub fn noop_with_param(_a: i32) {}
 /// # Examples
 ///
 /// ```
-/// use test::noop_with_param;
+/// use ra_test_fixture::noop_with_param;
 ///
 /// noop_with_param(_a);
 /// ```
@@ -641,7 +641,7 @@ pub unsafe fn noop_unsafe() {}
 /// # Examples
 ///
 /// ```
-/// use test::noop_unsafe;
+/// use ra_test_fixture::noop_unsafe;
 ///
 /// unsafe { noop_unsafe() };
 /// ```
@@ -758,7 +758,7 @@ pub fn returns_a_value$0() -> i32 {
 /// # Examples
 ///
 /// ```
-/// use test::returns_a_value;
+/// use ra_test_fixture::returns_a_value;
 ///
 /// assert_eq!(returns_a_value(), );
 /// ```
@@ -807,7 +807,7 @@ pub fn modifies_a_value$0(a: &mut i32) {
 /// # Examples
 ///
 /// ```
-/// use test::modifies_a_value;
+/// use ra_test_fixture::modifies_a_value;
 ///
 /// let mut a = ;
 /// modifies_a_value(&mut a);
@@ -836,7 +836,7 @@ pub fn sum3$0(a: i32, b: i32, c: i32) -> i32 {
 /// # Examples
 ///
 /// ```
-/// use test::sum3;
+/// use ra_test_fixture::sum3;
 ///
 /// let result = sum3(a, b, c);
 /// assert_eq!(result, );
@@ -868,7 +868,7 @@ pub mod a {
         /// # Examples
         ///
         /// ```
-        /// use test::a::b::noop;
+        /// use ra_test_fixture::a::b::noop;
         ///
         /// noop();
         /// ```
@@ -898,7 +898,7 @@ impl MyStruct {
     /// # Examples
     ///
     /// ```
-    /// use test::MyStruct;
+    /// use ra_test_fixture::MyStruct;
     ///
     /// MyStruct::noop();
     /// ```
@@ -1169,7 +1169,7 @@ impl MyGenericStruct {
     /// # Examples
     ///
     /// ```
-    /// use test::MyGenericStruct;
+    /// use ra_test_fixture::MyGenericStruct;
     ///
     /// let my_generic_struct = ;
     /// my_generic_struct.consume();
@@ -1199,7 +1199,7 @@ impl MyGenericStruct {
     /// # Examples
     ///
     /// ```
-    /// use test::MyGenericStruct;
+    /// use ra_test_fixture::MyGenericStruct;
     ///
     /// let mut my_generic_struct = ;
     /// my_generic_struct.modify(new_value);
diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs
index eef4da55e9..69ea200db1 100644
--- a/crates/ide-assists/src/tests/generated.rs
+++ b/crates/ide-assists/src/tests/generated.rs
@@ -1392,7 +1392,7 @@ pub fn add(a: i32, b: i32) -> i32 { a + b }
 /// # Examples
 ///
 /// ```
-/// use test::add;
+/// use ra_test_fixture::add;
 ///
 /// assert_eq!(add(a, b), );
 /// ```
diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs
index 1c08514a67..ea18b89c5c 100644
--- a/crates/ide/src/hover/tests.rs
+++ b/crates/ide/src/hover/tests.rs
@@ -288,7 +288,7 @@ m!(ab$0c);
             *abc*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -301,7 +301,7 @@ m!(ab$0c);
             ---
 
             ```rust
-            test::module
+            ra_test_fixture::module
             ```
 
             ```rust
@@ -327,7 +327,7 @@ fn main() {
 "#,
         expect![[r#"
             *foo*
-            test
+            ra_test_fixture
 
             pub fn foo() -> u32
         "#]],
@@ -487,7 +487,7 @@ fn main() {
                 GoToType(
                     [
                         HoverGotoTypeData {
-                            mod_path: "test::S2",
+                            mod_path: "ra_test_fixture::S2",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -500,7 +500,7 @@ fn main() {
                             },
                         },
                         HoverGotoTypeData {
-                            mod_path: "test::S",
+                            mod_path: "ra_test_fixture::S",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -583,7 +583,7 @@ fn main() { let foo_test = fo$0o(); }
             *foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -605,7 +605,7 @@ fn main() { f$0oo(); }
             *foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -627,7 +627,7 @@ fn main() { m::f$0oo(); }
             *foo*
 
             ```rust
-            test::m
+            ra_test_fixture::m
             ```
 
             ```rust
@@ -649,7 +649,7 @@ fn main() { fo$0o(); }
             *foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -667,7 +667,7 @@ fn main() { fo$0o(); }
             *foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -691,7 +691,7 @@ fn main() { let foo_test = fo$0o(); }
             *foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -715,7 +715,7 @@ fn main() { }
             *foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -743,7 +743,7 @@ fn main() { }
             *foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -776,7 +776,7 @@ fn main() { }
             *foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -801,7 +801,7 @@ struct Foo { fiel$0d_a: u8, field_b: i32, field_c: i16 }
             *field_a*
 
             ```rust
-            test::Foo
+            ra_test_fixture::Foo
             ```
 
             ```rust
@@ -830,7 +830,7 @@ fn main() {
             *field_a*
 
             ```rust
-            test::Foo
+            ra_test_fixture::Foo
             ```
 
             ```rust
@@ -852,7 +852,7 @@ fn main() {
             *field_a*
 
             ```rust
-            test::Foo
+            ra_test_fixture::Foo
             ```
 
             ```rust
@@ -880,7 +880,7 @@ fn main() {
             *0*
 
             ```rust
-            test::Foo
+            ra_test_fixture::Foo
             ```
 
             ```rust
@@ -900,7 +900,7 @@ fn foo(foo: Foo) {
             *0*
 
             ```rust
-            test::Foo
+            ra_test_fixture::Foo
             ```
 
             ```rust
@@ -920,7 +920,7 @@ struct Foo$0(pub u32) where u32: Copy;
             *Foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -946,7 +946,7 @@ struct Foo$0 { field: u32 }
             *Foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -968,7 +968,7 @@ struct Foo$0 where u32: Copy { field: u32 }
             *Foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -998,7 +998,7 @@ fn hover_record_struct_limit() {
             *Foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -1023,7 +1023,7 @@ fn hover_record_struct_limit() {
             *Foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -1046,7 +1046,7 @@ fn hover_record_struct_limit() {
             *Foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -1072,7 +1072,7 @@ fn hover_record_struct_limit() {
             *Foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -1093,7 +1093,7 @@ fn hover_record_struct_limit() {
             *Foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -1116,7 +1116,7 @@ fn hover_record_struct_limit() {
             *Foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -1141,7 +1141,7 @@ fn hover_record_variant_limit() {
             *A*
 
             ```rust
-            test::Foo
+            ra_test_fixture::Foo
             ```
 
             ```rust
@@ -1162,7 +1162,7 @@ fn hover_record_variant_limit() {
             *A*
 
             ```rust
-            test::Foo
+            ra_test_fixture::Foo
             ```
 
             ```rust
@@ -1183,7 +1183,7 @@ fn hover_record_variant_limit() {
             *A*
 
             ```rust
-            test::Foo
+            ra_test_fixture::Foo
             ```
 
             ```rust
@@ -1204,7 +1204,7 @@ fn hover_record_variant_limit() {
             *A*
 
             ```rust
-            test::Foo
+            ra_test_fixture::Foo
             ```
 
             ```rust
@@ -1225,7 +1225,7 @@ fn hover_record_variant_limit() {
             *A*
 
             ```rust
-            test::Foo
+            ra_test_fixture::Foo
             ```
 
             ```rust
@@ -1248,7 +1248,7 @@ fn hover_enum_limit() {
             *Foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -1270,7 +1270,7 @@ fn hover_enum_limit() {
             *Foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -1292,7 +1292,7 @@ fn hover_enum_limit() {
             *Foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -1311,7 +1311,7 @@ fn hover_enum_limit() {
             *Foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -1339,7 +1339,7 @@ fn hover_enum_limit() {
             *Enum*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -1371,7 +1371,7 @@ fn hover_union_limit() {
             *Foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -1393,7 +1393,7 @@ fn hover_union_limit() {
             *Foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -1415,7 +1415,7 @@ fn hover_union_limit() {
             *Foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -1434,7 +1434,7 @@ fn hover_union_limit() {
             *Foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -1458,7 +1458,7 @@ struct Foo$0 where u32: Copy;
             *Foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -1484,7 +1484,7 @@ type Fo$0o: Trait = S where T: Trait;
             *Foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -1504,7 +1504,7 @@ fn hover_const_static() {
             *foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -1522,7 +1522,7 @@ const foo$0: u32 = {
             *foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -1540,7 +1540,7 @@ const foo$0: u32 = {
             *foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -1555,7 +1555,7 @@ const foo$0: u32 = {
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -1573,7 +1573,7 @@ const foo$0: u32 = {
             *BAR*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -1591,7 +1591,7 @@ fn hover_unsigned_max_const() {
             *A*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -1612,7 +1612,7 @@ fn hover_eval_complex_constants() {
             *foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -1667,16 +1667,16 @@ use Option::Some;
 fn main() { So$0me(12); }
 "#,
         expect![[r#"
-                *Some*
+            *Some*
 
-                ```rust
-                test::Option
-                ```
+            ```rust
+            ra_test_fixture::Option
+            ```
 
-                ```rust
-                Some(T)
-                ```
-            "#]],
+            ```rust
+            Some(T)
+            ```
+        "#]],
     );
 
     check(
@@ -1711,20 +1711,20 @@ enum Option {
 }
 "#,
         expect![[r#"
-                *None*
+            *None*
 
-                ```rust
-                test::Option
-                ```
+            ```rust
+            ra_test_fixture::Option
+            ```
 
-                ```rust
-                None
-                ```
+            ```rust
+            None
+            ```
 
-                ---
+            ---
 
-                The None variant
-            "#]],
+            The None variant
+        "#]],
     );
 
     check(
@@ -1738,20 +1738,20 @@ fn main() {
 }
 "#,
         expect![[r#"
-                *Some*
+            *Some*
 
-                ```rust
-                test::Option
-                ```
+            ```rust
+            ra_test_fixture::Option
+            ```
 
-                ```rust
-                Some(T)
-                ```
+            ```rust
+            Some(T)
+            ```
 
-                ---
+            ---
 
-                The Some variant
-            "#]],
+            The Some variant
+        "#]],
     );
 }
 
@@ -1885,7 +1885,7 @@ fn main() { let foo_test = wrapper::Thing::new$0(); }
             *new*
 
             ```rust
-            test::wrapper::Thing
+            ra_test_fixture::wrapper::Thing
             ```
 
             ```rust
@@ -1916,7 +1916,7 @@ fn main() {
             *C*
 
             ```rust
-            test::X
+            ra_test_fixture::X
             ```
 
             ```rust
@@ -1936,18 +1936,18 @@ impl Thing {
 }
 "#,
         expect![[r#"
-                *Self*
+            *Self*
 
-                ```rust
-                test
-                ```
+            ```rust
+            ra_test_fixture
+            ```
 
-                ```rust
-                struct Thing {
-                    x: u32,
-                }
-                ```
-            "#]],
+            ```rust
+            struct Thing {
+                x: u32,
+            }
+            ```
+        "#]],
     );
     check_hover_fields_limit(
         None,
@@ -1958,16 +1958,16 @@ impl Thing {
 }
 "#,
         expect![[r#"
-                *Self*
+            *Self*
 
-                ```rust
-                test
-                ```
+            ```rust
+            ra_test_fixture
+            ```
 
-                ```rust
-                struct Thing
-                ```
-            "#]],
+            ```rust
+            struct Thing
+            ```
+        "#]],
     );
     check(
         r#"
@@ -1977,18 +1977,18 @@ impl Thing {
 }
 "#,
         expect![[r#"
-                *Self*
+            *Self*
 
-                ```rust
-                test
-                ```
+            ```rust
+            ra_test_fixture
+            ```
 
-                ```rust
-                struct Thing {
-                    x: u32,
-                }
-                ```
-            "#]],
+            ```rust
+            struct Thing {
+                x: u32,
+            }
+            ```
+        "#]],
     );
     check(
         r#"
@@ -2001,7 +2001,7 @@ impl Thing {
             *Self*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -2022,7 +2022,7 @@ impl Thing {
             *Self*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -2042,7 +2042,7 @@ impl usize {
             *Self*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -2060,7 +2060,7 @@ impl fn() -> usize {
             *Self*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -2103,7 +2103,7 @@ fn f() { fo$0o!(); }
             *foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -2128,7 +2128,7 @@ fn f() { fo$0o!(); }
             *foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -2172,7 +2172,7 @@ id! {
             *foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -2194,7 +2194,7 @@ fn foo$0() {}
             *foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -2252,7 +2252,7 @@ fn foo() { let a = id!([0u32, bar$0()] ); }
             *bar*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -2278,7 +2278,7 @@ fn foo() {
             *bar*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -2302,7 +2302,7 @@ fn foo(Foo { b$0ar }: &Foo) {}
                 GoToType(
                     [
                         HoverGotoTypeData {
-                            mod_path: "test::Bar",
+                            mod_path: "ra_test_fixture::Bar",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -2334,7 +2334,7 @@ fn bar() { fo$0o(); }
             *foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -2356,7 +2356,7 @@ fn test_hover_function_show_qualifiers() {
             *foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -2370,7 +2370,7 @@ fn test_hover_function_show_qualifiers() {
             *foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -2385,7 +2385,7 @@ fn test_hover_function_show_qualifiers() {
             *foo*
 
             ```rust
-            test::m
+            ra_test_fixture::m
             ```
 
             ```rust
@@ -2403,7 +2403,7 @@ fn test_hover_function_show_types() {
             *foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -2426,7 +2426,7 @@ fn main() { foo$0; }
             *foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -2446,7 +2446,7 @@ fn main() { foo$0; }
             *foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -2464,7 +2464,7 @@ fn test_hover_function_pointer_show_identifiers() {
             *foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -2486,7 +2486,7 @@ fn test_hover_function_pointer_no_identifier() {
             *foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -2601,16 +2601,16 @@ mod my { pub struct Bar; }
 fn my() {}
 "#,
         expect![[r#"
-                *my*
+            *my*
 
-                ```rust
-                test
-                ```
+            ```rust
+            ra_test_fixture
+            ```
 
-                ```rust
-                mod my
-                ```
-            "#]],
+            ```rust
+            mod my
+            ```
+        "#]],
     );
 }
 
@@ -2636,7 +2636,7 @@ fn foo() { let bar = Ba$0r; }
             *Bar*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -2672,7 +2672,7 @@ fn foo() { let bar = Ba$0r; }
             *Bar*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -2701,7 +2701,7 @@ fn foo() { let bar = Ba$0r; }
             *Bar*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -2729,7 +2729,7 @@ pub struct B$0ar
             *Bar*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -2760,7 +2760,7 @@ pub struct B$0ar
             *Bar*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -2809,7 +2809,7 @@ pub fn fo$0o() {}
             *foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -2853,7 +2853,7 @@ fn test_hover_layout_of_variant() {
             *Variant1*
 
             ```rust
-            test::Foo
+            ra_test_fixture::Foo
             ```
 
             ```rust
@@ -2878,7 +2878,7 @@ fn test_hover_layout_of_variant_generic() {
             *None*
 
             ```rust
-            test::Option
+            ra_test_fixture::Option
             ```
 
             ```rust
@@ -2899,7 +2899,7 @@ struct S$0(core::marker::PhantomData);
             *S*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -2924,7 +2924,7 @@ fn test_hover_layout_of_enum() {
             *Foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -2949,7 +2949,7 @@ fn test_hover_no_memory_layout() {
             *field_a*
 
             ```rust
-            test::Foo
+            ra_test_fixture::Foo
             ```
 
             ```rust
@@ -3003,7 +3003,7 @@ fn foo() { let bar = Bar; bar.fo$0o(); }
             *foo*
 
             ```rust
-            test::Bar
+            ra_test_fixture::Bar
             ```
 
             ```rust
@@ -3041,7 +3041,7 @@ fn foo() { let bar = Bar; bar.fo$0o(); }
             *foo*
 
             ```rust
-            test::Bar
+            ra_test_fixture::Bar
             ```
 
             ```rust
@@ -3069,7 +3069,7 @@ fn main() { let foo_test = unsafe { fo$0o(1, 2, 3); } }
             *foo*
 
             ```rust
-            test::
+            ra_test_fixture::
             ```
 
             ```rust
@@ -3267,7 +3267,7 @@ fn main() { let s$0t = S{ f1:0 }; }
                 GoToType(
                     [
                         HoverGotoTypeData {
-                            mod_path: "test::S",
+                            mod_path: "ra_test_fixture::S",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -3300,7 +3300,7 @@ fn main() { let s$0t = S{ f1:Arg(0) }; }
                 GoToType(
                     [
                         HoverGotoTypeData {
-                            mod_path: "test::Arg",
+                            mod_path: "ra_test_fixture::Arg",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -3313,7 +3313,7 @@ fn main() { let s$0t = S{ f1:Arg(0) }; }
                             },
                         },
                         HoverGotoTypeData {
-                            mod_path: "test::S",
+                            mod_path: "ra_test_fixture::S",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -3359,7 +3359,7 @@ fn main() { let s$0t = S{ f1: S{ f1: Arg(0) } }; }
                 GoToType(
                     [
                         HoverGotoTypeData {
-                            mod_path: "test::Arg",
+                            mod_path: "ra_test_fixture::Arg",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -3372,7 +3372,7 @@ fn main() { let s$0t = S{ f1: S{ f1: Arg(0) } }; }
                             },
                         },
                         HoverGotoTypeData {
-                            mod_path: "test::S",
+                            mod_path: "ra_test_fixture::S",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -3408,7 +3408,7 @@ fn main() { let s$0t = (A(1), B(2), M::C(3) ); }
                 GoToType(
                     [
                         HoverGotoTypeData {
-                            mod_path: "test::A",
+                            mod_path: "ra_test_fixture::A",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -3421,7 +3421,7 @@ fn main() { let s$0t = (A(1), B(2), M::C(3) ); }
                             },
                         },
                         HoverGotoTypeData {
-                            mod_path: "test::B",
+                            mod_path: "ra_test_fixture::B",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -3434,7 +3434,7 @@ fn main() { let s$0t = (A(1), B(2), M::C(3) ); }
                             },
                         },
                         HoverGotoTypeData {
-                            mod_path: "test::M::C",
+                            mod_path: "ra_test_fixture::M::C",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -3468,7 +3468,7 @@ fn main() { let s$0t = foo(); }
                 GoToType(
                     [
                         HoverGotoTypeData {
-                            mod_path: "test::Foo",
+                            mod_path: "ra_test_fixture::Foo",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -3502,7 +3502,7 @@ fn main() { let s$0t = foo(); }
                 GoToType(
                     [
                         HoverGotoTypeData {
-                            mod_path: "test::Foo",
+                            mod_path: "ra_test_fixture::Foo",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -3515,7 +3515,7 @@ fn main() { let s$0t = foo(); }
                             },
                         },
                         HoverGotoTypeData {
-                            mod_path: "test::S",
+                            mod_path: "ra_test_fixture::S",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -3549,7 +3549,7 @@ fn main() { let s$0t = foo(); }
                 GoToType(
                     [
                         HoverGotoTypeData {
-                            mod_path: "test::Bar",
+                            mod_path: "ra_test_fixture::Bar",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -3562,7 +3562,7 @@ fn main() { let s$0t = foo(); }
                             },
                         },
                         HoverGotoTypeData {
-                            mod_path: "test::Foo",
+                            mod_path: "ra_test_fixture::Foo",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -3599,7 +3599,7 @@ fn main() { let s$0t = foo(); }
                 GoToType(
                     [
                         HoverGotoTypeData {
-                            mod_path: "test::Bar",
+                            mod_path: "ra_test_fixture::Bar",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -3612,7 +3612,7 @@ fn main() { let s$0t = foo(); }
                             },
                         },
                         HoverGotoTypeData {
-                            mod_path: "test::Foo",
+                            mod_path: "ra_test_fixture::Foo",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -3625,7 +3625,7 @@ fn main() { let s$0t = foo(); }
                             },
                         },
                         HoverGotoTypeData {
-                            mod_path: "test::S1",
+                            mod_path: "ra_test_fixture::S1",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -3638,7 +3638,7 @@ fn main() { let s$0t = foo(); }
                             },
                         },
                         HoverGotoTypeData {
-                            mod_path: "test::S2",
+                            mod_path: "ra_test_fixture::S2",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -3669,7 +3669,7 @@ fn foo(ar$0g: &impl Foo) {}
                 GoToType(
                     [
                         HoverGotoTypeData {
-                            mod_path: "test::Foo",
+                            mod_path: "ra_test_fixture::Foo",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -3703,7 +3703,7 @@ fn foo(ar$0g: &impl Foo + Bar) {}
                 GoToType(
                     [
                         HoverGotoTypeData {
-                            mod_path: "test::Bar",
+                            mod_path: "ra_test_fixture::Bar",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -3716,7 +3716,7 @@ fn foo(ar$0g: &impl Foo + Bar) {}
                             },
                         },
                         HoverGotoTypeData {
-                            mod_path: "test::Foo",
+                            mod_path: "ra_test_fixture::Foo",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -3729,7 +3729,7 @@ fn foo(ar$0g: &impl Foo + Bar) {}
                             },
                         },
                         HoverGotoTypeData {
-                            mod_path: "test::S",
+                            mod_path: "ra_test_fixture::S",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -3816,7 +3816,7 @@ fn foo(ar$0g: &impl Foo) {}
                 GoToType(
                     [
                         HoverGotoTypeData {
-                            mod_path: "test::Foo",
+                            mod_path: "ra_test_fixture::Foo",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -3829,7 +3829,7 @@ fn foo(ar$0g: &impl Foo) {}
                             },
                         },
                         HoverGotoTypeData {
-                            mod_path: "test::S",
+                            mod_path: "ra_test_fixture::S",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -3866,7 +3866,7 @@ fn main() { let s$0t = foo(); }
                 GoToType(
                     [
                         HoverGotoTypeData {
-                            mod_path: "test::B",
+                            mod_path: "ra_test_fixture::B",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -3879,7 +3879,7 @@ fn main() { let s$0t = foo(); }
                             },
                         },
                         HoverGotoTypeData {
-                            mod_path: "test::Foo",
+                            mod_path: "ra_test_fixture::Foo",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -3892,7 +3892,7 @@ fn main() { let s$0t = foo(); }
                             },
                         },
                         HoverGotoTypeData {
-                            mod_path: "test::S",
+                            mod_path: "ra_test_fixture::S",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -3923,7 +3923,7 @@ fn foo(ar$0g: &dyn Foo) {}
                 GoToType(
                     [
                         HoverGotoTypeData {
-                            mod_path: "test::Foo",
+                            mod_path: "ra_test_fixture::Foo",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -3955,7 +3955,7 @@ fn foo(ar$0g: &dyn Foo) {}
                 GoToType(
                     [
                         HoverGotoTypeData {
-                            mod_path: "test::Foo",
+                            mod_path: "ra_test_fixture::Foo",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -3968,7 +3968,7 @@ fn foo(ar$0g: &dyn Foo) {}
                             },
                         },
                         HoverGotoTypeData {
-                            mod_path: "test::S",
+                            mod_path: "ra_test_fixture::S",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -4003,7 +4003,7 @@ fn foo(a$0rg: &impl ImplTrait>>>) {}
                 GoToType(
                     [
                         HoverGotoTypeData {
-                            mod_path: "test::B",
+                            mod_path: "ra_test_fixture::B",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -4016,7 +4016,7 @@ fn foo(a$0rg: &impl ImplTrait>>>) {}
                             },
                         },
                         HoverGotoTypeData {
-                            mod_path: "test::DynTrait",
+                            mod_path: "ra_test_fixture::DynTrait",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -4029,7 +4029,7 @@ fn foo(a$0rg: &impl ImplTrait>>>) {}
                             },
                         },
                         HoverGotoTypeData {
-                            mod_path: "test::ImplTrait",
+                            mod_path: "ra_test_fixture::ImplTrait",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -4042,7 +4042,7 @@ fn foo(a$0rg: &impl ImplTrait>>>) {}
                             },
                         },
                         HoverGotoTypeData {
-                            mod_path: "test::S",
+                            mod_path: "ra_test_fixture::S",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -4084,7 +4084,7 @@ fn main() { let s$0t = test().get(); }
                 GoToType(
                     [
                         HoverGotoTypeData {
-                            mod_path: "test::Foo",
+                            mod_path: "ra_test_fixture::Foo",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -4117,7 +4117,7 @@ impl Foo {}
                 GoToType(
                     [
                         HoverGotoTypeData {
-                            mod_path: "test::Bar",
+                            mod_path: "ra_test_fixture::Bar",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -4149,7 +4149,7 @@ fn foo(t: T$0){}
                 GoToType(
                     [
                         HoverGotoTypeData {
-                            mod_path: "test::Foo",
+                            mod_path: "ra_test_fixture::Foo",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -4182,7 +4182,7 @@ impl Foo {
                 GoToType(
                     [
                         HoverGotoTypeData {
-                            mod_path: "test::Foo",
+                            mod_path: "ra_test_fixture::Foo",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -4257,7 +4257,7 @@ fn main() {
             ---
 
             ```rust
-            test::S
+            ra_test_fixture::S
             ```
 
             ```rust
@@ -4282,7 +4282,7 @@ struct S$0T(T);
             *ST*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -4307,7 +4307,7 @@ struct S$0T(T);
             *ST*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -4333,7 +4333,7 @@ struct S$0T(T);
             *ST*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -4527,21 +4527,21 @@ mod Foo$0 {
 }
 "#,
         expect![[r#"
-                *Foo*
+            *Foo*
 
-                ```rust
-                test
-                ```
+            ```rust
+            ra_test_fixture
+            ```
 
-                ```rust
-                mod Foo
-                ```
+            ```rust
+            mod Foo
+            ```
 
-                ---
+            ---
 
-                Be quick;
-                time is mana
-            "#]],
+            Be quick;
+            time is mana
+        "#]],
     );
 }
 
@@ -4558,21 +4558,21 @@ mod Foo$0 {
 }
 "#,
         expect![[r#"
-                *Foo*
+            *Foo*
 
-                ```rust
-                test
-                ```
+            ```rust
+            ra_test_fixture
+            ```
 
-                ```rust
-                mod Foo
-                ```
+            ```rust
+            mod Foo
+            ```
 
-                ---
+            ---
 
-                Be quick;
-                time is mana
-            "#]],
+            Be quick;
+            time is mana
+        "#]],
     );
 }
 
@@ -4592,7 +4592,7 @@ fn foo$0() {}
             *foo*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -4902,7 +4902,7 @@ type Fo$0o2 = Foo<2>;
             *Foo2*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -4948,7 +4948,7 @@ enum E {
             *A*
 
             ```rust
-            test::E
+            ra_test_fixture::E
             ```
 
             ```rust
@@ -4977,7 +4977,7 @@ enum E {
             *A*
 
             ```rust
-            test::E
+            ra_test_fixture::E
             ```
 
             ```rust
@@ -5007,7 +5007,7 @@ enum E {
             *B*
 
             ```rust
-            test::E
+            ra_test_fixture::E
             ```
 
             ```rust
@@ -5037,7 +5037,7 @@ enum E {
             *B*
 
             ```rust
-            test::E
+            ra_test_fixture::E
             ```
 
             ```rust
@@ -5074,7 +5074,7 @@ fn main() {
             *B*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5111,7 +5111,7 @@ fn main() {
             *AA*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5138,7 +5138,7 @@ fn main() {
             *B*
 
             ```rust
-            test::T
+            ra_test_fixture::T
             ```
 
             ```rust
@@ -5167,7 +5167,7 @@ fn main() {
             *B*
 
             ```rust
-            test::T
+            ra_test_fixture::T
             ```
 
             ```rust
@@ -5199,7 +5199,7 @@ fn main() {
             *B*
 
             ```rust
-            test::T
+            ra_test_fixture::T
             ```
 
             ```rust
@@ -5222,7 +5222,7 @@ const FOO$0: usize = 1 << 3;
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5243,7 +5243,7 @@ const FOO$0: usize = (1 << 3) + (1 << 2);
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5265,7 +5265,7 @@ const FOO$0: usize = 2 - 3;
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5287,7 +5287,7 @@ const FOO$0: i32 = 2 - 3;
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5308,7 +5308,7 @@ const FOO$0: &str = "bar";
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5330,7 +5330,7 @@ const FOO$0: char = 'a';
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5352,7 +5352,7 @@ const FOO$0: char = '\x61';
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5374,7 +5374,7 @@ const FOO$0: u8 = b'a';
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5396,7 +5396,7 @@ const FOO$0: u8 = b'\x61';
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5418,7 +5418,7 @@ const FOO$0: u8 = b'\x61';
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5440,7 +5440,7 @@ const FOO$0: f32 = 1f32;
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5462,7 +5462,7 @@ const FOO$0: &i32 = &2;
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5484,7 +5484,7 @@ const FOO$0: f64 = 1.0f64;
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5512,7 +5512,7 @@ const FOO$0: f64 = expf64(1.2);
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5530,7 +5530,7 @@ const FOO$0: f32 = 1.9999999403953552_f32;
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5552,7 +5552,7 @@ const FOO$0: f16 = -1.0f16;
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5573,7 +5573,7 @@ const FOO$0: f128 = -1.0f128;
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5604,7 +5604,7 @@ const FOO$0: Enum = VX;
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5621,7 +5621,7 @@ const FOO$0: Option = Some(2);
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5638,7 +5638,7 @@ const FOO$0: Option<&i32> = Some(2).as_ref();
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5661,7 +5661,7 @@ const FOO$0: &dyn Debug = &2i32;
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5682,7 +5682,7 @@ const FOO$0: &[i32] = &[1, 2, 3 + 4];
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5699,7 +5699,7 @@ const FOO$0: &[i32; 5] = &[12; 5];
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5720,7 +5720,7 @@ const FOO$0: (&i32, &[i32], &i32) = {
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5743,7 +5743,7 @@ const FOO$0: Tree = {
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5763,7 +5763,7 @@ const FOO$0: &S<[u8]> = core::mem::transmute::<&[u8], _>(&[1, 2, 3]);
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5783,7 +5783,7 @@ const FOO$0: &str = "foo";
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5806,7 +5806,7 @@ const FOO$0: X = X {
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5825,7 +5825,7 @@ const FOO$0: (&str, &str) = {
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5856,7 +5856,7 @@ fn test() {
             *FOO*
 
             ```rust
-            test::S
+            ra_test_fixture::S
             ```
 
             ```rust
@@ -5883,7 +5883,7 @@ fn foo() {
             *FOO*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5912,7 +5912,7 @@ fn foo(e: E) {
             *A*
 
             ```rust
-            test::E
+            ra_test_fixture::E
             ```
 
             ```rust
@@ -5942,7 +5942,7 @@ pub fn the_function() -> AA {
             *CONST*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -5984,20 +5984,20 @@ mod foo$0;
 //! For the horde!
 "#,
         expect![[r#"
-                *foo*
+            *foo*
 
-                ```rust
-                test
-                ```
+            ```rust
+            ra_test_fixture
+            ```
 
-                ```rust
-                mod foo
-                ```
+            ```rust
+            mod foo
+            ```
 
-                ---
+            ---
 
-                For the horde!
-            "#]],
+            For the horde!
+        "#]],
     );
 }
 
@@ -6013,20 +6013,20 @@ mod foo {
 use foo::bar::{self$0};
 "#,
         expect![[r#"
-                *self*
+            *self*
 
-                ```rust
-                test::foo
-                ```
+            ```rust
+            ra_test_fixture::foo
+            ```
 
-                ```rust
-                mod bar
-                ```
+            ```rust
+            mod bar
+            ```
 
-                ---
+            ---
 
-                But this should appear
-            "#]],
+            But this should appear
+        "#]],
     )
 }
 
@@ -6166,7 +6166,7 @@ fn main() {
             *bar*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -6200,7 +6200,7 @@ pub fn gimme() -> theitem::TheItem {
             *[`TheItem`]*
 
             ```rust
-            test::theitem
+            ra_test_fixture::theitem
             ```
 
             ```rust
@@ -6248,7 +6248,7 @@ impl T1 for Foo {
             *Bar*
 
             ```rust
-            test::t2::T2
+            ra_test_fixture::t2::T2
             ```
 
             ```rust
@@ -6270,7 +6270,7 @@ trait A {
             *Assoc*
 
             ```rust
-            test::A
+            ra_test_fixture::A
             ```
 
             ```rust
@@ -6291,7 +6291,7 @@ trait A {
             *Assoc*
 
             ```rust
-            test::A
+            ra_test_fixture::A
             ```
 
             ```rust
@@ -6310,7 +6310,7 @@ trait A where
             *Assoc*
 
             ```rust
-            test::A
+            ra_test_fixture::A
             ```
 
             ```rust
@@ -6572,12 +6572,12 @@ fn hover_rename() {
 use self as foo$0;
 "#,
         expect![[r#"
-                *foo*
+            *foo*
 
-                ```rust
-                extern crate test
-                ```
-            "#]],
+            ```rust
+            extern crate ra_test_fixture
+            ```
+        "#]],
     );
     check(
         r#"
@@ -6585,16 +6585,16 @@ mod bar {}
 use bar::{self as foo$0};
 "#,
         expect![[r#"
-                *foo*
+            *foo*
 
-                ```rust
-                test
-                ```
+            ```rust
+            ra_test_fixture
+            ```
 
-                ```rust
-                mod bar
-                ```
-            "#]],
+            ```rust
+            mod bar
+            ```
+        "#]],
     );
     check(
         r#"
@@ -6603,24 +6603,24 @@ mod bar {
 }
 "#,
         expect![[r#"
-                *foo*
+            *foo*
 
-                ```rust
-                extern crate test
-                ```
-            "#]],
+            ```rust
+            extern crate ra_test_fixture
+            ```
+        "#]],
     );
     check(
         r#"
 use crate as foo$0;
 "#,
         expect![[r#"
-                *foo*
+            *foo*
 
-                ```rust
-                extern crate test
-                ```
-            "#]],
+            ```rust
+            extern crate ra_test_fixture
+            ```
+        "#]],
     );
 }
 
@@ -6645,7 +6645,7 @@ identity!{
             *Copy*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -6669,7 +6669,7 @@ struct Foo;
             *Copy*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -6691,7 +6691,7 @@ struct Foo;
             *Copy*
 
             ```rust
-            test::foo
+            ra_test_fixture::foo
             ```
 
             ```rust
@@ -6910,7 +6910,7 @@ fn foo() {
                 GoToType(
                     [
                         HoverGotoTypeData {
-                            mod_path: "test::Foo",
+                            mod_path: "ra_test_fixture::Foo",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -7110,7 +7110,7 @@ foo_macro!(
             *[`Foo`]*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -7119,7 +7119,7 @@ foo_macro!(
 
             ---
 
-            Doc comment for [`Foo`](https://docs.rs/test/*/test/struct.Foo.html)
+            Doc comment for [`Foo`](https://docs.rs/ra_test_fixture/*/ra_test_fixture/struct.Foo.html)
         "#]],
     );
 }
@@ -7135,7 +7135,7 @@ pub struct Foo(i32);
             *[`Foo`]*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -7144,7 +7144,7 @@ pub struct Foo(i32);
 
             ---
 
-            Doc comment for [`Foo`](https://docs.rs/test/*/test/struct.Foo.html)
+            Doc comment for [`Foo`](https://docs.rs/ra_test_fixture/*/ra_test_fixture/struct.Foo.html)
         "#]],
     );
 }
@@ -7160,7 +7160,7 @@ pub struct Foo(T);
             *[`Foo`]*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -7169,7 +7169,7 @@ pub struct Foo(T);
 
             ---
 
-            Doc comment for [`Foo`](https://docs.rs/test/*/test/struct.Foo.html)
+            Doc comment for [`Foo`](https://docs.rs/ra_test_fixture/*/ra_test_fixture/struct.Foo.html)
         "#]],
     );
 }
@@ -7259,7 +7259,7 @@ enum Enum {
             *RecordV*
 
             ```rust
-            test::Enum
+            ra_test_fixture::Enum
             ```
 
             ```rust
@@ -7285,7 +7285,7 @@ enum Enum {
             *field*
 
             ```rust
-            test::RecordV
+            ra_test_fixture::RecordV
             ```
 
             ```rust
@@ -7315,7 +7315,7 @@ impl T for () {
             *func*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -7341,7 +7341,7 @@ impl T$0 for () {}
             *T*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -7360,7 +7360,7 @@ impl T$0 for () {}
             *T*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -7383,7 +7383,7 @@ impl T$0 for () {}
             *T*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -7406,7 +7406,7 @@ impl T$0 for () {}
             *T*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -7433,7 +7433,7 @@ impl T$0 for () {}
             *T*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -7460,7 +7460,7 @@ impl T$0 for () {}
             *T*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -7529,7 +7529,7 @@ fn f() {
             ***
 
             ```rust
-            test::Struct
+            ra_test_fixture::Struct
             ```
 
             ```rust
@@ -7558,7 +7558,7 @@ fn main() { $0V; }
             *V*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -7585,7 +7585,7 @@ fn main() { $0V; }
             *V*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -7765,7 +7765,7 @@ fn test() {
             *foo*
 
             ```rust
-            test::S
+            ra_test_fixture::S
             ```
 
             ```rust
@@ -7794,7 +7794,7 @@ fn test() {
             *foo*
 
             ```rust
-            test::S
+            ra_test_fixture::S
             ```
 
             ```rust
@@ -7824,7 +7824,7 @@ mod m {
             *foo*
 
             ```rust
-            test::S
+            ra_test_fixture::S
             ```
 
             ```rust
@@ -7854,7 +7854,7 @@ fn test() {
             *A*
 
             ```rust
-            test::S
+            ra_test_fixture::S
             ```
 
             ```rust
@@ -7883,7 +7883,7 @@ fn test() {
             *A*
 
             ```rust
-            test::S
+            ra_test_fixture::S
             ```
 
             ```rust
@@ -7913,7 +7913,7 @@ mod m {
             *A*
 
             ```rust
-            test::S
+            ra_test_fixture::S
             ```
 
             ```rust
@@ -7936,7 +7936,7 @@ fn test() {
             *f*
 
             ```rust
-            test::S
+            ra_test_fixture::S
             ```
 
             ```rust
@@ -8091,7 +8091,7 @@ fn main() {
             *foo*
 
             ```rust
-            test::S
+            ra_test_fixture::S
             ```
 
             ```rust
@@ -8573,7 +8573,7 @@ impl Iterator for S {
             *S*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -8595,7 +8595,7 @@ extern "C" {
             *STATIC*
 
             ```rust
-            test::
+            ra_test_fixture::
             ```
 
             ```rust
@@ -8613,7 +8613,7 @@ extern "C" {
             *fun*
 
             ```rust
-            test::
+            ra_test_fixture::
             ```
 
             ```rust
@@ -8631,7 +8631,7 @@ extern "C" {
             *Ty*
 
             ```rust
-            test::
+            ra_test_fixture::
             ```
 
             ```rust
@@ -8731,7 +8731,7 @@ impl Iterator for S {
                             },
                         },
                         HoverGotoTypeData {
-                            mod_path: "test::Notable",
+                            mod_path: "ra_test_fixture::Notable",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -8744,7 +8744,7 @@ impl Iterator for S {
                             },
                         },
                         HoverGotoTypeData {
-                            mod_path: "test::S2",
+                            mod_path: "ra_test_fixture::S2",
                             nav: NavigationTarget {
                                 file_id: FileId(
                                     0,
@@ -8775,7 +8775,7 @@ struct Pedro$0<'a> {
             *Pedro*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -8962,7 +8962,7 @@ fn test_hover_function_with_pat_param() {
             *test_1*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -8978,7 +8978,7 @@ fn test_hover_function_with_pat_param() {
             *test_2*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -8994,7 +8994,7 @@ fn test_hover_function_with_pat_param() {
             *test_3*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -9010,7 +9010,7 @@ fn test_hover_function_with_pat_param() {
             *test_4*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -9026,7 +9026,7 @@ fn test_hover_function_with_pat_param() {
             *test_5*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -9042,7 +9042,7 @@ fn test_hover_function_with_pat_param() {
             *test_6*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -9058,7 +9058,7 @@ fn test_hover_function_with_pat_param() {
             *test_7*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -9074,7 +9074,7 @@ fn test_hover_function_with_pat_param() {
             *test_8*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -9090,7 +9090,7 @@ fn test_hover_function_with_pat_param() {
             *test_9*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -9106,7 +9106,7 @@ fn test_hover_function_with_pat_param() {
             *test_10*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -9122,7 +9122,7 @@ fn test_hover_function_with_pat_param() {
             *test_10*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -9148,7 +9148,7 @@ mod m {
             *C*
 
             ```rust
-            test::m::m2
+            ra_test_fixture::m::m2
             ```
 
             ```rust
@@ -9173,17 +9173,17 @@ macro_rules! foo {
 foo!(BAR_$0);
 "#,
         expect![[r#"
-        *BAR_*
+            *BAR_*
 
-        ```rust
-        test
-        ```
+            ```rust
+            ra_test_fixture
+            ```
 
-        ```rust
-        pub static BAR_: {error} = Foo::new(||{
-            crate;
-        })
-        ```
+            ```rust
+            pub static BAR_: {error} = Foo::new(||{
+                crate;
+            })
+            ```
         "#]],
     );
 }
@@ -9202,7 +9202,7 @@ type A$0 = B;
             *A*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -9235,7 +9235,7 @@ type A$0 = B;
             *A*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -9269,7 +9269,7 @@ type A$0 = B;
             *A*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -9301,7 +9301,7 @@ type A$0 = B;
             *A*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -9362,7 +9362,7 @@ trait Compat$0 {}
             *Compat*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -9384,7 +9384,7 @@ trait UnCompat$0 {
             *UnCompat*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
@@ -9407,7 +9407,7 @@ fn f
             *UnCompat*
 
             ```rust
-            test
+            ra_test_fixture
             ```
 
             ```rust
diff --git a/crates/test-fixture/src/lib.rs b/crates/test-fixture/src/lib.rs
index 593e31c2fb..889a7d10ad 100644
--- a/crates/test-fixture/src/lib.rs
+++ b/crates/test-fixture/src/lib.rs
@@ -236,7 +236,7 @@ impl ChangeFixture {
             crate_graph.add_crate_root(
                 crate_root,
                 Edition::CURRENT,
-                Some(CrateName::new("test").unwrap().into()),
+                Some(CrateName::new("ra_test_fixture").unwrap().into()),
                 None,
                 From::from(default_cfg.clone()),
                 Some(From::from(default_cfg)),

From 708532818593c1d526da5c68182f46f6624b084c Mon Sep 17 00:00:00 2001
From: Lukas Wirth 
Date: Mon, 9 Dec 2024 11:42:51 +0100
Subject: [PATCH 72/95] Remove patch sysroot cfg-if hack

---
 crates/base-db/src/input.rs           | 23 -------------
 crates/project-model/src/workspace.rs | 48 ++++++++++-----------------
 2 files changed, 18 insertions(+), 53 deletions(-)

diff --git a/crates/base-db/src/input.rs b/crates/base-db/src/input.rs
index 57522d6932..e86944eeb3 100644
--- a/crates/base-db/src/input.rs
+++ b/crates/base-db/src/input.rs
@@ -547,29 +547,6 @@ impl CrateGraph {
         None
     }
 
-    // Work around for https://github.com/rust-lang/rust-analyzer/issues/6038.
-    // As hacky as it gets.
-    pub fn patch_cfg_if(&mut self) -> bool {
-        // we stupidly max by version in an attempt to have all duplicated std's depend on the same cfg_if so that deduplication still works
-        let cfg_if =
-            self.hacky_find_crate("cfg_if").max_by_key(|&it| self.arena[it].version.clone());
-        let std = self.hacky_find_crate("std").next();
-        match (cfg_if, std) {
-            (Some(cfg_if), Some(std)) => {
-                self.arena[cfg_if].dependencies.clear();
-                self.arena[std]
-                    .dependencies
-                    .push(Dependency::new(CrateName::new("cfg_if").unwrap(), cfg_if));
-                true
-            }
-            _ => false,
-        }
-    }
-
-    fn hacky_find_crate<'a>(&'a self, display_name: &'a str) -> impl Iterator + 'a {
-        self.iter().filter(move |it| self[*it].display_name.as_deref() == Some(display_name))
-    }
-
     /// Removes all crates from this crate graph except for the ones in `to_keep` and fixes up the dependencies.
     /// Returns a mapping from old crate ids to new crate ids.
     pub fn remove_crates_except(&mut self, to_keep: &[CrateId]) -> Vec> {
diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs
index f5ba71fcd0..988eff9be4 100644
--- a/crates/project-model/src/workspace.rs
+++ b/crates/project-model/src/workspace.rs
@@ -747,17 +747,14 @@ impl ProjectWorkspace {
         let _p = tracing::info_span!("ProjectWorkspace::to_crate_graph").entered();
 
         let Self { kind, sysroot, cfg_overrides, rustc_cfg, .. } = self;
-        let ((mut crate_graph, proc_macros), sysroot) = match kind {
-            ProjectWorkspaceKind::Json(project) => (
-                project_json_to_crate_graph(
-                    rustc_cfg.clone(),
-                    load,
-                    project,
-                    sysroot,
-                    extra_env,
-                    cfg_overrides,
-                ),
+        let (crate_graph, proc_macros) = match kind {
+            ProjectWorkspaceKind::Json(project) => project_json_to_crate_graph(
+                rustc_cfg.clone(),
+                load,
+                project,
                 sysroot,
+                extra_env,
+                cfg_overrides,
             ),
             ProjectWorkspaceKind::Cargo {
                 cargo,
@@ -766,20 +763,17 @@ impl ProjectWorkspace {
                 cargo_config_extra_env: _,
                 error: _,
                 set_test,
-            } => (
-                cargo_to_crate_graph(
-                    load,
-                    rustc.as_ref().map(|a| a.as_ref()).ok(),
-                    cargo,
-                    sysroot,
-                    rustc_cfg.clone(),
-                    cfg_overrides,
-                    build_scripts,
-                    *set_test,
-                ),
+            } => cargo_to_crate_graph(
+                load,
+                rustc.as_ref().map(|a| a.as_ref()).ok(),
+                cargo,
                 sysroot,
+                rustc_cfg.clone(),
+                cfg_overrides,
+                build_scripts,
+                *set_test,
             ),
-            ProjectWorkspaceKind::DetachedFile { file, cargo: cargo_script, set_test, .. } => (
+            ProjectWorkspaceKind::DetachedFile { file, cargo: cargo_script, set_test, .. } => {
                 if let Some((cargo, build_scripts, _)) = cargo_script {
                     cargo_to_crate_graph(
                         &mut |path| load(path),
@@ -800,16 +794,10 @@ impl ProjectWorkspace {
                         cfg_overrides,
                         *set_test,
                     )
-                },
-                sysroot,
-            ),
+                }
+            }
         };
 
-        if matches!(sysroot.mode(), SysrootMode::Stitched(_)) && crate_graph.patch_cfg_if() {
-            debug!("Patched std to depend on cfg-if")
-        } else {
-            debug!("Did not patch std to depend on cfg-if")
-        }
         (crate_graph, proc_macros)
     }
 

From 55a7ee806523848902604d84f94e2ea924ddb49e Mon Sep 17 00:00:00 2001
From: Lukas Wirth 
Date: Mon, 9 Dec 2024 14:12:22 +0100
Subject: [PATCH 73/95] fix: Non-exhaustive structs may be empty

---
 .../diagnostics/match_check/pat_analysis.rs   |  6 +-----
 crates/hir-ty/src/inhabitedness.rs            |  8 +-------
 .../src/handlers/missing_match_arms.rs        | 19 +++++++++++++++++++
 3 files changed, 21 insertions(+), 12 deletions(-)

diff --git a/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs b/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs
index 58de19ba81..5452f5c680 100644
--- a/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs
+++ b/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs
@@ -383,9 +383,6 @@ impl<'db> PatCx for MatchCheckCtx<'db> {
                     } else {
                         let variant = Self::variant_id_for_adt(self.db, ctor, adt).unwrap();
 
-                        // Whether we must not match the fields of this variant exhaustively.
-                        let is_non_exhaustive =
-                            LazyCell::new(|| self.is_foreign_non_exhaustive(adt));
                         let visibilities = LazyCell::new(|| self.db.field_visibilities(variant));
 
                         self.list_variant_fields(ty, variant)
@@ -396,8 +393,7 @@ impl<'db> PatCx for MatchCheckCtx<'db> {
                                             .is_visible_from(self.db.upcast(), self.module)
                                 };
                                 let is_uninhabited = self.is_uninhabited(&ty);
-                                let private_uninhabited =
-                                    is_uninhabited && (!is_visible() || *is_non_exhaustive);
+                                let private_uninhabited = is_uninhabited && !is_visible();
                                 (ty, PrivateUninhabitedField(private_uninhabited))
                             })
                             .collect()
diff --git a/crates/hir-ty/src/inhabitedness.rs b/crates/hir-ty/src/inhabitedness.rs
index c0a781b17e..56b549436c 100644
--- a/crates/hir-ty/src/inhabitedness.rs
+++ b/crates/hir-ty/src/inhabitedness.rs
@@ -5,8 +5,7 @@ use chalk_ir::{
     visit::{TypeSuperVisitable, TypeVisitable, TypeVisitor},
     DebruijnIndex,
 };
-use hir_def::{visibility::Visibility, AdtId, EnumVariantId, HasModule, ModuleId, VariantId};
-use intern::sym;
+use hir_def::{visibility::Visibility, AdtId, EnumVariantId, ModuleId, VariantId};
 use rustc_hash::FxHashSet;
 
 use crate::{
@@ -118,11 +117,6 @@ impl UninhabitedFrom<'_> {
         variant: VariantId,
         subst: &Substitution,
     ) -> ControlFlow {
-        let is_local = variant.krate(self.db.upcast()) == self.target_mod.krate();
-        if !is_local && self.db.attrs(variant.into()).by_key(&sym::non_exhaustive).exists() {
-            return CONTINUE_OPAQUELY_INHABITED;
-        }
-
         let variant_data = self.db.variant_data(variant);
         let fields = variant_data.fields();
         if fields.is_empty() {
diff --git a/crates/ide-diagnostics/src/handlers/missing_match_arms.rs b/crates/ide-diagnostics/src/handlers/missing_match_arms.rs
index f39738f2fb..08e6e7dced 100644
--- a/crates/ide-diagnostics/src/handlers/missing_match_arms.rs
+++ b/crates/ide-diagnostics/src/handlers/missing_match_arms.rs
@@ -1114,6 +1114,25 @@ fn test(x: Option) {
         }
     }
 
+    #[test]
+    fn non_exhaustive_may_be_empty() {
+        check_diagnostics_no_bails(
+            r"
+//- /main.rs crate:main deps:dep
+// In a different crate
+fn empty_match_on_empty_struct(x: dep::UninhabitedStruct) -> T {
+    match x {}
+}
+//- /dep.rs crate:dep
+#[non_exhaustive]
+pub struct UninhabitedStruct {
+    pub never: !,
+    // other fields
+}
+",
+        );
+    }
+
     mod false_negatives {
         //! The implementation of match checking here is a work in progress. As we roll this out, we
         //! prefer false negatives to false positives (ideally there would be no false positives). This

From f021ec3b966e5a1008b17aced2d868db844c361c Mon Sep 17 00:00:00 2001
From: Lukas Wirth 
Date: Mon, 9 Dec 2024 15:52:04 +0100
Subject: [PATCH 74/95] Disable pipe on typing handler

---
 crates/rust-analyzer/src/config.rs | 2 +-
 docs/user/generated_config.adoc    | 2 +-
 editors/code/package.json          | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index dd7351bcf2..fd4c8d0e68 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -309,7 +309,7 @@ config_data! {
         signatureInfo_documentation_enable: bool                       = true,
 
         /// Specify the characters to exclude from triggering typing assists. The default trigger characters are `.`, `=`, `<`, `>`, `{`, and `(`.
-        typing_excludeChars: Option = Some('<'.to_string()),
+        typing_excludeChars: Option = Some("|<".to_owned()),
 
 
         /// Enables automatic discovery of projects using [`DiscoverWorkspaceConfig::command`].
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index 715f8d43ad..1195a85cf7 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -992,7 +992,7 @@ Show full signature of the callable. Only shows parameters if disabled.
 --
 Show documentation.
 --
-[[rust-analyzer.typing.excludeChars]]rust-analyzer.typing.excludeChars (default: `"<"`)::
+[[rust-analyzer.typing.excludeChars]]rust-analyzer.typing.excludeChars (default: `"|<"`)::
 +
 --
 Specify the characters to exclude from triggering typing assists. The default trigger characters are `.`, `=`, `<`, `>`, `{`, and `(`.
diff --git a/editors/code/package.json b/editors/code/package.json
index 70d26c9707..469c1b458d 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -2607,7 +2607,7 @@
                 "properties": {
                     "rust-analyzer.typing.excludeChars": {
                         "markdownDescription": "Specify the characters to exclude from triggering typing assists. The default trigger characters are `.`, `=`, `<`, `>`, `{`, and `(`.",
-                        "default": "<",
+                        "default": "|<",
                         "type": [
                             "null",
                             "string"

From 9c03cbb49945efbde60d0d46daa4e087aa469d86 Mon Sep 17 00:00:00 2001
From: roife 
Date: Tue, 10 Dec 2024 02:47:52 +0800
Subject: [PATCH 75/95] minor: enhance name suggestion for `Arc` and `Rc`

---
 .../ide-db/src/syntax_helpers/suggest_name.rs | 28 ++++++++++++++++++-
 1 file changed, 27 insertions(+), 1 deletion(-)

diff --git a/crates/ide-db/src/syntax_helpers/suggest_name.rs b/crates/ide-db/src/syntax_helpers/suggest_name.rs
index 2679cbef61..b3ecc26cb2 100644
--- a/crates/ide-db/src/syntax_helpers/suggest_name.rs
+++ b/crates/ide-db/src/syntax_helpers/suggest_name.rs
@@ -29,7 +29,7 @@ const USELESS_NAME_PREFIXES: &[&str] = &["from_", "with_", "into_"];
 /// # Examples
 /// `Option` -> `Name`
 /// `Result` -> `User`
-const WRAPPER_TYPES: &[&str] = &["Box", "Option", "Result"];
+const WRAPPER_TYPES: &[&str] = &["Box", "Arc", "Rc", "Option", "Result"];
 
 /// Prefixes to strip from methods names
 ///
@@ -858,6 +858,32 @@ fn foo() { $0(bar())$0; }
         );
     }
 
+    #[test]
+    fn arc_value() {
+        check(
+            r#"
+struct Arc(*const T);
+struct Seed;
+fn bar() -> Arc {}
+fn foo() { $0(bar())$0; }
+"#,
+            "seed",
+        );
+    }
+
+    #[test]
+    fn rc_value() {
+        check(
+            r#"
+struct Rc(*const T);
+struct Seed;
+fn bar() -> Rc {}
+fn foo() { $0(bar())$0; }
+"#,
+            "seed",
+        );
+    }
+
     #[test]
     fn ref_call() {
         check(

From 62d97d9ba7c41b3670e8c77f0fa6735162a3bbf8 Mon Sep 17 00:00:00 2001
From: Kirill Bulatov 
Date: Mon, 9 Dec 2024 19:53:30 +0200
Subject: [PATCH 76/95] Draft completion hashing

---
 Cargo.lock                                   |  8 ++
 crates/ide-completion/src/item.rs            |  3 +-
 crates/ide-completion/src/lib.rs             |  1 +
 crates/rust-analyzer/Cargo.toml              | 16 ++--
 crates/rust-analyzer/src/handlers/request.rs | 26 ++++--
 crates/rust-analyzer/src/lib.rs              | 89 ++++++++++++++++++++
 crates/rust-analyzer/src/lsp/ext.rs          |  3 +-
 crates/rust-analyzer/src/lsp/to_proto.rs     | 18 +++-
 docs/dev/lsp-extensions.md                   |  2 +-
 9 files changed, 142 insertions(+), 24 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 5861833d53..b6f2c6faf8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1659,6 +1659,7 @@ dependencies = [
  "hir-def",
  "hir-ty",
  "ide",
+ "ide-completion",
  "ide-db",
  "ide-ssr",
  "intern",
@@ -1687,6 +1688,7 @@ dependencies = [
  "stdx",
  "syntax",
  "syntax-bridge",
+ "tenthash",
  "test-fixture",
  "test-utils",
  "tikv-jemallocator",
@@ -1990,6 +1992,12 @@ dependencies = [
  "tt",
 ]
 
+[[package]]
+name = "tenthash"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d67f9f3cf70e0852941d7bc3cb884b49b24b8ee956baf91ad0abae31f5ef11fb"
+
 [[package]]
 name = "test-fixture"
 version = "0.0.0"
diff --git a/crates/ide-completion/src/item.rs b/crates/ide-completion/src/item.rs
index 52f6bedaaa..8878fbbea3 100644
--- a/crates/ide-completion/src/item.rs
+++ b/crates/ide-completion/src/item.rs
@@ -346,8 +346,7 @@ pub enum CompletionItemKind {
 impl_from!(SymbolKind for CompletionItemKind);
 
 impl CompletionItemKind {
-    #[cfg(test)]
-    pub(crate) fn tag(self) -> &'static str {
+    pub fn tag(self) -> &'static str {
         match self {
             CompletionItemKind::SymbolKind(kind) => match kind {
                 SymbolKind::Attribute => "at",
diff --git a/crates/ide-completion/src/lib.rs b/crates/ide-completion/src/lib.rs
index cffdfa29f1..14f42b4005 100644
--- a/crates/ide-completion/src/lib.rs
+++ b/crates/ide-completion/src/lib.rs
@@ -34,6 +34,7 @@ pub use crate::{
     config::{CallableSnippets, CompletionConfig},
     item::{
         CompletionItem, CompletionItemKind, CompletionRelevance, CompletionRelevancePostfixMatch,
+        CompletionRelevanceReturnType, CompletionRelevanceTypeMatch,
     },
     snippet::{Snippet, SnippetScope},
 };
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml
index 2dd2f2242a..022b0a0ecf 100644
--- a/crates/rust-analyzer/Cargo.toml
+++ b/crates/rust-analyzer/Cargo.toml
@@ -24,6 +24,7 @@ anyhow.workspace = true
 crossbeam-channel.workspace = true
 dirs = "5.0.1"
 dissimilar.workspace = true
+ide-completion.workspace = true
 itertools.workspace = true
 scip = "0.5.1"
 lsp-types = { version = "=0.95.0", features = ["proposed"] }
@@ -34,6 +35,7 @@ rayon.workspace = true
 rustc-hash.workspace = true
 serde_json = { workspace = true, features = ["preserve_order"] }
 serde.workspace = true
+tenthash = "0.4.0"
 num_cpus = "1.15.0"
 mimalloc = { version = "0.1.30", default-features = false, optional = true }
 lsp-server.workspace = true
@@ -90,13 +92,13 @@ jemalloc = ["jemallocator", "profile/jemalloc"]
 force-always-assert = ["always-assert/force"]
 sysroot-abi = []
 in-rust-tree = [
-  "sysroot-abi",
-  "syntax/in-rust-tree",
-  "parser/in-rust-tree",
-  "hir/in-rust-tree",
-  "hir-def/in-rust-tree",
-  "hir-ty/in-rust-tree",
-  "load-cargo/in-rust-tree",
+    "sysroot-abi",
+    "syntax/in-rust-tree",
+    "parser/in-rust-tree",
+    "hir/in-rust-tree",
+    "hir-def/in-rust-tree",
+    "hir-ty/in-rust-tree",
+    "load-cargo/in-rust-tree",
 ]
 
 [lints]
diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs
index 0fadfa6c42..9dd6dc999b 100644
--- a/crates/rust-analyzer/src/handlers/request.rs
+++ b/crates/rust-analyzer/src/handlers/request.rs
@@ -36,6 +36,7 @@ use triomphe::Arc;
 use vfs::{AbsPath, AbsPathBuf, FileId, VfsPath};
 
 use crate::{
+    completion_item_hash,
     config::{Config, RustfmtConfig, WorkspaceSymbolConfig},
     diagnostics::convert_diagnostic,
     global_state::{FetchWorkspaceRequest, GlobalState, GlobalStateSnapshot},
@@ -1122,12 +1123,15 @@ pub(crate) fn handle_completion_resolve(
         return Ok(original_completion);
     };
     let source_root = snap.analysis.source_root_id(file_id)?;
+    let Some(completion_hash_for_resolve) = &resolve_data.completion_item_hash else {
+        return Ok(original_completion);
+    };
 
     let mut forced_resolve_completions_config = snap.config.completion(Some(source_root));
     forced_resolve_completions_config.fields_to_resolve = CompletionFieldsToResolve::empty();
 
     let position = FilePosition { file_id, offset };
-    let Some(resolved_completions) = snap.analysis.completions(
+    let Some(completions) = snap.analysis.completions(
         &forced_resolve_completions_config,
         position,
         resolve_data.trigger_character,
@@ -1135,6 +1139,14 @@ pub(crate) fn handle_completion_resolve(
     else {
         return Ok(original_completion);
     };
+
+    let Some(corresponding_completion) = completions.into_iter().find(|completion_item| {
+        let hash = completion_item_hash(&completion_item, resolve_data.for_ref);
+        &hash == completion_hash_for_resolve
+    }) else {
+        return Ok(original_completion);
+    };
+
     let mut resolved_completions = to_proto::completion_items(
         &snap.config,
         &forced_resolve_completions_config.fields_to_resolve,
@@ -1142,15 +1154,11 @@ pub(crate) fn handle_completion_resolve(
         snap.file_version(position.file_id),
         resolve_data.position,
         resolve_data.trigger_character,
-        resolved_completions,
+        vec![corresponding_completion],
     );
-
-    let mut resolved_completion =
-        if resolved_completions.get(resolve_data.completion_item_index).is_some() {
-            resolved_completions.swap_remove(resolve_data.completion_item_index)
-        } else {
-            return Ok(original_completion);
-        };
+    let Some(mut resolved_completion) = resolved_completions.pop() else {
+        return Ok(original_completion);
+    };
 
     if !resolve_data.imports.is_empty() {
         let additional_edits = snap
diff --git a/crates/rust-analyzer/src/lib.rs b/crates/rust-analyzer/src/lib.rs
index 234204695c..8f74e75d3d 100644
--- a/crates/rust-analyzer/src/lib.rs
+++ b/crates/rust-analyzer/src/lib.rs
@@ -47,7 +47,9 @@ use self::lsp::ext as lsp_ext;
 #[cfg(test)]
 mod integrated_benchmarks;
 
+use ide::{CompletionItem, CompletionRelevance, TextEdit, TextRange};
 use serde::de::DeserializeOwned;
+use tenthash::TentHasher;
 
 pub use crate::{
     lsp::capabilities::server_capabilities, main_loop::main_loop, reload::ws_to_crate_graph,
@@ -61,3 +63,90 @@ pub fn from_json(
     serde_json::from_value(json.clone())
         .map_err(|e| anyhow::format_err!("Failed to deserialize {what}: {e}; {json}"))
 }
+
+fn completion_item_hash(item: &CompletionItem, is_ref_completion: bool) -> [u8; 20] {
+    fn hash_text_range(hasher: &mut TentHasher, text_range: &TextRange) {
+        hasher.update(u32::from(text_range.start()).to_le_bytes());
+        hasher.update(u32::from(text_range.end()).to_le_bytes());
+    }
+
+    fn hash_text_edit(hasher: &mut TentHasher, edit: &TextEdit) {
+        for indel in edit.iter() {
+            hasher.update(&indel.insert);
+            hash_text_range(hasher, &indel.delete);
+        }
+    }
+
+    fn has_completion_relevance(hasher: &mut TentHasher, relevance: &CompletionRelevance) {
+        use ide_completion::{
+            CompletionRelevancePostfixMatch, CompletionRelevanceReturnType,
+            CompletionRelevanceTypeMatch,
+        };
+
+        if let Some(type_match) = &relevance.type_match {
+            let label = match type_match {
+                CompletionRelevanceTypeMatch::CouldUnify => "could_unify",
+                CompletionRelevanceTypeMatch::Exact => "exact",
+            };
+            hasher.update(label);
+        }
+        hasher.update(&[u8::from(relevance.exact_name_match), u8::from(relevance.is_local)]);
+        if let Some(trait_) = &relevance.trait_ {
+            hasher.update(&[u8::from(trait_.is_op_method), u8::from(trait_.notable_trait)]);
+        }
+        hasher.update(&[
+            u8::from(relevance.is_name_already_imported),
+            u8::from(relevance.requires_import),
+            u8::from(relevance.is_private_editable),
+        ]);
+        if let Some(postfix_match) = &relevance.postfix_match {
+            let label = match postfix_match {
+                CompletionRelevancePostfixMatch::NonExact => "non_exact",
+                CompletionRelevancePostfixMatch::Exact => "exact",
+            };
+            hasher.update(label);
+        }
+        if let Some(function) = &relevance.function {
+            hasher.update(&[u8::from(function.has_params), u8::from(function.has_self_param)]);
+            let label = match function.return_type {
+                CompletionRelevanceReturnType::Other => "other",
+                CompletionRelevanceReturnType::DirectConstructor => "direct_constructor",
+                CompletionRelevanceReturnType::Constructor => "constructor",
+                CompletionRelevanceReturnType::Builder => "builder",
+            };
+            hasher.update(label);
+        }
+    }
+
+    let mut hasher = TentHasher::new();
+    hasher.update(&[
+        u8::from(is_ref_completion),
+        u8::from(item.is_snippet),
+        u8::from(item.deprecated),
+        u8::from(item.trigger_call_info),
+    ]);
+    hasher.update(&item.label);
+    if let Some(label_detail) = &item.label_detail {
+        hasher.update(label_detail);
+    }
+    hash_text_range(&mut hasher, &item.source_range);
+    hash_text_edit(&mut hasher, &item.text_edit);
+    hasher.update(item.kind.tag());
+    hasher.update(&item.lookup);
+    if let Some(detail) = &item.detail {
+        hasher.update(detail);
+    }
+    if let Some(documentation) = &item.documentation {
+        hasher.update(documentation.as_str());
+    }
+    has_completion_relevance(&mut hasher, &item.relevance);
+    if let Some((mutability, text_size)) = &item.ref_match {
+        hasher.update(mutability.as_keyword_for_ref());
+        hasher.update(u32::from(*text_size).to_le_bytes());
+    }
+    for (import_path, import_name) in &item.import_to_add {
+        hasher.update(import_path);
+        hasher.update(import_name);
+    }
+    hasher.finalize()
+}
diff --git a/crates/rust-analyzer/src/lsp/ext.rs b/crates/rust-analyzer/src/lsp/ext.rs
index 6ddfe118d5..7d60ae703b 100644
--- a/crates/rust-analyzer/src/lsp/ext.rs
+++ b/crates/rust-analyzer/src/lsp/ext.rs
@@ -826,7 +826,8 @@ pub struct CompletionResolveData {
     pub imports: Vec,
     pub version: Option,
     pub trigger_character: Option,
-    pub completion_item_index: usize,
+    pub for_ref: bool,
+    pub completion_item_hash: Option<[u8; 20]>,
 }
 
 #[derive(Debug, Serialize, Deserialize)]
diff --git a/crates/rust-analyzer/src/lsp/to_proto.rs b/crates/rust-analyzer/src/lsp/to_proto.rs
index d444f90a13..97caed8f08 100644
--- a/crates/rust-analyzer/src/lsp/to_proto.rs
+++ b/crates/rust-analyzer/src/lsp/to_proto.rs
@@ -21,6 +21,7 @@ use serde_json::to_value;
 use vfs::AbsPath;
 
 use crate::{
+    completion_item_hash,
     config::{CallInfoConfig, Config},
     global_state::GlobalStateSnapshot,
     line_index::{LineEndings, LineIndex, PositionEncoding},
@@ -274,6 +275,11 @@ fn completion_item(
     completion_trigger_character: Option,
     item: CompletionItem,
 ) {
+    let original_completion_item = if fields_to_resolve == &CompletionFieldsToResolve::empty() {
+        None
+    } else {
+        Some(item.clone())
+    };
     let insert_replace_support = config.insert_replace_support().then_some(tdpp.position);
     let ref_match = item.ref_match();
 
@@ -393,16 +399,17 @@ fn completion_item(
             Vec::new()
         };
     let (ref_resolve_data, resolve_data) = if something_to_resolve || !imports.is_empty() {
-        let mut item_index = acc.len();
         let ref_resolve_data = if ref_match.is_some() {
             let ref_resolve_data = lsp_ext::CompletionResolveData {
                 position: tdpp.clone(),
                 imports: Vec::new(),
                 version,
                 trigger_character: completion_trigger_character,
-                completion_item_index: item_index,
+                for_ref: true,
+                completion_item_hash: original_completion_item
+                    .as_ref()
+                    .map(|item| completion_item_hash(item, true)),
             };
-            item_index += 1;
             Some(to_value(ref_resolve_data).unwrap())
         } else {
             None
@@ -412,7 +419,10 @@ fn completion_item(
             imports,
             version,
             trigger_character: completion_trigger_character,
-            completion_item_index: item_index,
+            for_ref: false,
+            completion_item_hash: original_completion_item
+                .as_ref()
+                .map(|item| completion_item_hash(item, false)),
         };
         (ref_resolve_data, Some(to_value(resolve_data).unwrap()))
     } else {
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index b7c536e027..0086382517 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -1,5 +1,5 @@