Merge pull request #20364 from Hmikihiro/migrate_convert_from_to_tryfrom
Migrate `convert_from_to_tryfrom` assist to use `SyntaxEditor`
diff --git a/crates/base-db/src/lib.rs b/crates/base-db/src/lib.rs
index ad17f17..b8eadb6 100644
--- a/crates/base-db/src/lib.rs
+++ b/crates/base-db/src/lib.rs
@@ -206,6 +206,7 @@
#[salsa_macros::input(debug)]
pub struct FileText {
+ #[returns(ref)]
pub text: Arc<str>,
pub file_id: vfs::FileId,
}
@@ -357,7 +358,7 @@
let _p = tracing::info_span!("parse", ?file_id).entered();
let (file_id, edition) = file_id.unpack(db.as_dyn_database());
let text = db.file_text(file_id).text(db);
- ast::SourceFile::parse(&text, edition)
+ ast::SourceFile::parse(text, edition)
}
fn parse_errors(db: &dyn RootQueryDb, file_id: EditionedFileId) -> Option<&[SyntaxError]> {
diff --git a/crates/hir-expand/src/builtin/fn_macro.rs b/crates/hir-expand/src/builtin/fn_macro.rs
index 58ab7f4..ec34461 100644
--- a/crates/hir-expand/src/builtin/fn_macro.rs
+++ b/crates/hir-expand/src/builtin/fn_macro.rs
@@ -890,7 +890,7 @@
};
let text = db.file_text(file_id.file_id(db));
- let text = &*text.text(db);
+ let text = &**text.text(db);
ExpandResult::ok(quote!(call_site =>#text))
}
diff --git a/crates/hir-expand/src/files.rs b/crates/hir-expand/src/files.rs
index 6730b33..a7f3e27 100644
--- a/crates/hir-expand/src/files.rs
+++ b/crates/hir-expand/src/files.rs
@@ -99,6 +99,16 @@
pub fn into_file_id(self, db: &dyn ExpandDatabase) -> FileRangeWrapper<FileId> {
FileRangeWrapper { file_id: self.file_id.file_id(db), range: self.range }
}
+
+ #[inline]
+ pub fn file_text(self, db: &dyn ExpandDatabase) -> &triomphe::Arc<str> {
+ db.file_text(self.file_id.file_id(db)).text(db)
+ }
+
+ #[inline]
+ pub fn text(self, db: &dyn ExpandDatabase) -> &str {
+ &self.file_text(db)[self.range]
+ }
}
/// `AstId` points to an AST node in any file.
diff --git a/crates/hir-ty/src/test_db.rs b/crates/hir-ty/src/test_db.rs
index b5de0e5..775136d 100644
--- a/crates/hir-ty/src/test_db.rs
+++ b/crates/hir-ty/src/test_db.rs
@@ -149,7 +149,7 @@
.into_iter()
.filter_map(|file_id| {
let text = self.file_text(file_id.file_id(self));
- let annotations = extract_annotations(&text.text(self));
+ let annotations = extract_annotations(text.text(self));
if annotations.is_empty() {
return None;
}
diff --git a/crates/ide-db/src/lib.rs b/crates/ide-db/src/lib.rs
index c94be7e..49f7f63 100644
--- a/crates/ide-db/src/lib.rs
+++ b/crates/ide-db/src/lib.rs
@@ -244,7 +244,7 @@
fn line_index(db: &dyn LineIndexDatabase, file_id: FileId) -> Arc<LineIndex> {
let text = db.file_text(file_id).text(db);
- Arc::new(LineIndex::new(&text))
+ Arc::new(LineIndex::new(text))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
diff --git a/crates/ide-db/src/search.rs b/crates/ide-db/src/search.rs
index 4dd6422..abd4dc8 100644
--- a/crates/ide-db/src/search.rs
+++ b/crates/ide-db/src/search.rs
@@ -487,9 +487,9 @@
scope.entries.iter().map(|(&file_id, &search_range)| {
let text = db.file_text(file_id.file_id(db)).text(db);
let search_range =
- search_range.unwrap_or_else(|| TextRange::up_to(TextSize::of(&*text)));
+ search_range.unwrap_or_else(|| TextRange::up_to(TextSize::of(&**text)));
- (text, file_id, search_range)
+ (text.clone(), file_id, search_range)
})
}
@@ -854,14 +854,7 @@
&finder,
name,
is_possibly_self.into_iter().map(|position| {
- (
- self.sema
- .db
- .file_text(position.file_id.file_id(self.sema.db))
- .text(self.sema.db),
- position.file_id,
- position.range,
- )
+ (position.file_text(self.sema.db).clone(), position.file_id, position.range)
}),
|path, name_position| {
let has_self = path
@@ -1067,12 +1060,12 @@
let file_text = sema.db.file_text(file_id.file_id(self.sema.db));
let text = file_text.text(sema.db);
let search_range =
- search_range.unwrap_or_else(|| TextRange::up_to(TextSize::of(&*text)));
+ search_range.unwrap_or_else(|| TextRange::up_to(TextSize::of(&**text)));
let tree = LazyCell::new(|| sema.parse(file_id).syntax().clone());
let finder = &Finder::new("self");
- for offset in Self::match_indices(&text, finder, search_range) {
+ for offset in Self::match_indices(text, finder, search_range) {
for name_ref in Self::find_nodes(sema, "self", file_id, &tree, offset)
.filter_map(ast::NameRef::cast)
{
diff --git a/crates/ide-diagnostics/src/tests.rs b/crates/ide-diagnostics/src/tests.rs
index 4e4bd47..1819931 100644
--- a/crates/ide-diagnostics/src/tests.rs
+++ b/crates/ide-diagnostics/src/tests.rs
@@ -229,7 +229,7 @@
let line_index = db.line_index(file_id);
let mut actual = annotations.remove(&file_id).unwrap_or_default();
- let mut expected = extract_annotations(&db.file_text(file_id).text(&db));
+ let mut expected = extract_annotations(db.file_text(file_id).text(&db));
expected.sort_by_key(|(range, s)| (range.start(), s.clone()));
actual.sort_by_key(|(range, s)| (range.start(), s.clone()));
// FIXME: We should panic on duplicates instead, but includes currently cause us to report
diff --git a/crates/ide-ssr/src/lib.rs b/crates/ide-ssr/src/lib.rs
index 138af22..43ad12c1 100644
--- a/crates/ide-ssr/src/lib.rs
+++ b/crates/ide-ssr/src/lib.rs
@@ -186,7 +186,7 @@
replacing::matches_to_edit(
self.sema.db,
&matches,
- &self.sema.db.file_text(file_id).text(self.sema.db),
+ self.sema.db.file_text(file_id).text(self.sema.db),
&self.rules,
),
)
@@ -228,7 +228,7 @@
let file = self.sema.parse(file_id);
let mut res = Vec::new();
let file_text = self.sema.db.file_text(file_id.file_id(self.sema.db)).text(self.sema.db);
- let mut remaining_text = &*file_text;
+ let mut remaining_text = &**file_text;
let mut base = 0;
let len = snippet.len() as u32;
while let Some(offset) = remaining_text.find(snippet) {
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index b3b8deb..9887748 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -299,7 +299,7 @@
/// Gets the text of the source file.
pub fn file_text(&self, file_id: FileId) -> Cancellable<Arc<str>> {
- self.with_db(|db| SourceDatabase::file_text(db, file_id).text(db))
+ self.with_db(|db| SourceDatabase::file_text(db, file_id).text(db).clone())
}
/// Gets the syntax tree of the file.
diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs
index 634edaa..aea4ae0 100644
--- a/crates/ide/src/rename.rs
+++ b/crates/ide/src/rename.rs
@@ -13,8 +13,11 @@
};
use itertools::Itertools;
use std::fmt::Write;
-use stdx::{always, never};
-use syntax::{AstNode, SyntaxKind, SyntaxNode, TextRange, TextSize, ast};
+use stdx::{always, format_to, never};
+use syntax::{
+ AstNode, SyntaxKind, SyntaxNode, TextRange, TextSize,
+ ast::{self, HasArgList, prec::ExprPrecedence},
+};
use ide_db::text_edit::TextEdit;
@@ -331,6 +334,85 @@
}
}
+fn transform_assoc_fn_into_method_call(
+ sema: &Semantics<'_, RootDatabase>,
+ source_change: &mut SourceChange,
+ f: hir::Function,
+) {
+ let calls = Definition::Function(f).usages(sema).all();
+ for (file_id, calls) in calls {
+ for call in calls {
+ let Some(fn_name) = call.name.as_name_ref() else { continue };
+ let Some(path) = fn_name.syntax().parent().and_then(ast::PathSegment::cast) else {
+ continue;
+ };
+ let path = path.parent_path();
+ // The `PathExpr` is the direct parent, above it is the `CallExpr`.
+ let Some(call) =
+ path.syntax().parent().and_then(|it| ast::CallExpr::cast(it.parent()?))
+ else {
+ continue;
+ };
+
+ let Some(arg_list) = call.arg_list() else { continue };
+ let mut args = arg_list.args();
+ let Some(mut self_arg) = args.next() else { continue };
+ let second_arg = args.next();
+
+ // Strip (de)references, as they will be taken automatically by auto(de)ref.
+ loop {
+ let self_ = match &self_arg {
+ ast::Expr::RefExpr(self_) => self_.expr(),
+ ast::Expr::ParenExpr(self_) => self_.expr(),
+ ast::Expr::PrefixExpr(self_)
+ if self_.op_kind() == Some(ast::UnaryOp::Deref) =>
+ {
+ self_.expr()
+ }
+ _ => break,
+ };
+ self_arg = match self_ {
+ Some(it) => it,
+ None => break,
+ };
+ }
+
+ let self_needs_parens =
+ self_arg.precedence().needs_parentheses_in(ExprPrecedence::Postfix);
+
+ let replace_start = path.syntax().text_range().start();
+ let replace_end = match second_arg {
+ Some(second_arg) => second_arg.syntax().text_range().start(),
+ None => arg_list
+ .r_paren_token()
+ .map(|it| it.text_range().start())
+ .unwrap_or_else(|| arg_list.syntax().text_range().end()),
+ };
+ let replace_range = TextRange::new(replace_start, replace_end);
+
+ let Some(macro_mapped_self) = sema.original_range_opt(self_arg.syntax()) else {
+ continue;
+ };
+ let mut replacement = String::new();
+ if self_needs_parens {
+ replacement.push('(');
+ }
+ replacement.push_str(macro_mapped_self.text(sema.db));
+ if self_needs_parens {
+ replacement.push(')');
+ }
+ replacement.push('.');
+ format_to!(replacement, "{fn_name}");
+ replacement.push('(');
+
+ source_change.insert_source_edit(
+ file_id.file_id(sema.db),
+ TextEdit::replace(replace_range, replacement),
+ );
+ }
+ }
+}
+
fn rename_to_self(
sema: &Semantics<'_, RootDatabase>,
local: hir::Local,
@@ -408,6 +490,7 @@
file_id.original_file(sema.db).file_id(sema.db),
TextEdit::replace(param_source.syntax().text_range(), String::from(self_param)),
);
+ transform_assoc_fn_into_method_call(sema, &mut source_change, fn_def);
Ok(source_change)
}
@@ -3412,4 +3495,78 @@
"#,
);
}
+
+ #[test]
+ fn rename_to_self_callers() {
+ check(
+ "self",
+ r#"
+//- minicore: add
+struct Foo;
+impl core::ops::Add for Foo {
+ type Target = Foo;
+ fn add(self, _: Self) -> Foo { Foo }
+}
+
+impl Foo {
+ fn foo(th$0is: &Self) {}
+}
+
+fn bar(v: &Foo) {
+ Foo::foo(v);
+}
+
+fn baz() {
+ Foo::foo(&Foo);
+ Foo::foo(Foo + Foo);
+}
+ "#,
+ r#"
+struct Foo;
+impl core::ops::Add for Foo {
+ type Target = Foo;
+ fn add(self, _: Self) -> Foo { Foo }
+}
+
+impl Foo {
+ fn foo(&self) {}
+}
+
+fn bar(v: &Foo) {
+ v.foo();
+}
+
+fn baz() {
+ Foo.foo();
+ (Foo + Foo).foo();
+}
+ "#,
+ );
+ // Multiple arguments:
+ check(
+ "self",
+ r#"
+struct Foo;
+
+impl Foo {
+ fn foo(th$0is: &Self, v: i32) {}
+}
+
+fn bar(v: Foo) {
+ Foo::foo(&v, 123);
+}
+ "#,
+ r#"
+struct Foo;
+
+impl Foo {
+ fn foo(&self, v: i32) {}
+}
+
+fn bar(v: Foo) {
+ v.foo(123);
+}
+ "#,
+ );
+ }
}
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 70d0448..1a00295 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -726,7 +726,9 @@
/// ```bash
/// cargo check --quiet --workspace --message-format=json --all-targets --keep-going
/// ```
- /// .
+ ///
+ /// Note: The option must be specified as an array of command line arguments, with
+ /// the first argument being the name of the command to run.
cargo_buildScripts_overrideCommand: Option<Vec<String>> = None,
/// Rerun proc-macros building/build-scripts running when proc-macro
/// or build-script sources change and are saved.
@@ -840,7 +842,9 @@
/// ```bash
/// cargo check --workspace --message-format=json --all-targets
/// ```
- /// .
+ ///
+ /// Note: The option must be specified as an array of command line arguments, with
+ /// the first argument being the name of the command to run.
check_overrideCommand | checkOnSave_overrideCommand: Option<Vec<String>> = None,
/// Check for specific targets. Defaults to `#rust-analyzer.cargo.target#` if empty.
///
@@ -890,6 +894,9 @@
/// not that of `cargo fmt`. The file contents will be passed on the
/// standard input and the formatted result will be read from the
/// standard output.
+ ///
+ /// Note: The option must be specified as an array of command line arguments, with
+ /// the first argument being the name of the command to run.
rustfmt_overrideCommand: Option<Vec<String>> = None,
/// Enables the use of rustfmt's unstable range formatting command for the
/// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index 3171bdd..2f1afba 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -448,7 +448,7 @@
tracing::info!(%vfs_path, ?change_kind, "Processing rust-analyzer.toml changes");
if vfs_path.as_path() == user_config_abs_path {
tracing::info!(%vfs_path, ?change_kind, "Use config rust-analyzer.toml changes");
- change.change_user_config(Some(db.file_text(file_id).text(db)));
+ change.change_user_config(Some(db.file_text(file_id).text(db).clone()));
}
// If change has been made to a ratoml file that
@@ -462,14 +462,14 @@
change.change_workspace_ratoml(
source_root_id,
vfs_path.clone(),
- Some(db.file_text(file_id).text(db)),
+ Some(db.file_text(file_id).text(db).clone()),
)
} else {
tracing::info!(%vfs_path, ?source_root_id, "crate rust-analyzer.toml changes");
change.change_ratoml(
source_root_id,
vfs_path.clone(),
- Some(db.file_text(file_id).text(db)),
+ Some(db.file_text(file_id).text(db).clone()),
)
};
diff --git a/docs/book/src/configuration_generated.md b/docs/book/src/configuration_generated.md
index 05299f1..99a30d8 100644
--- a/docs/book/src/configuration_generated.md
+++ b/docs/book/src/configuration_generated.md
@@ -104,7 +104,9 @@
```bash
cargo check --quiet --workspace --message-format=json --all-targets --keep-going
```
-.
+
+Note: The option must be specified as an array of command line arguments, with
+the first argument being the name of the command to run.
## rust-analyzer.cargo.buildScripts.rebuildOnSave {#cargo.buildScripts.rebuildOnSave}
@@ -331,7 +333,9 @@
```bash
cargo check --workspace --message-format=json --all-targets
```
-.
+
+Note: The option must be specified as an array of command line arguments, with
+the first argument being the name of the command to run.
## rust-analyzer.check.targets {#check.targets}
@@ -1343,6 +1347,9 @@
standard input and the formatted result will be read from the
standard output.
+Note: The option must be specified as an array of command line arguments, with
+the first argument being the name of the command to run.
+
## rust-analyzer.rustfmt.rangeFormatting.enable {#rustfmt.rangeFormatting.enable}
diff --git a/docs/book/src/contributing/style.md b/docs/book/src/contributing/style.md
index 5654e37..746f3eb 100644
--- a/docs/book/src/contributing/style.md
+++ b/docs/book/src/contributing/style.md
@@ -49,8 +49,8 @@
Changes of the third group should be pretty rare, so we don't specify any specific process for them.
That said, adding an innocent-looking `pub use` is a very simple way to break encapsulation, keep an eye on it!
-Note: if you enjoyed this abstract hand-waving about boundaries, you might appreciate
-https://www.tedinski.com/2018/02/06/system-boundaries.html
+Note: if you enjoyed this abstract hand-waving about boundaries, you might appreciate [this post](https://www.tedinski.com/2018/02/06/system-boundaries.html).
+
## Crates.io Dependencies
@@ -231,7 +231,7 @@
}
```
-In the "Not as good" version, the precondition that `1` is a valid char boundary is checked in `is_string_literal` and used in `foo`.
+In the "Bad" version, the precondition that `1` and `s.len() - 1` are valid string literal boundaries is checked in `is_string_literal` but used in `main`.
In the "Good" version, the precondition check and usage are checked in the same block, and then encoded in the types.
**Rationale:** non-local code properties degrade under change.
@@ -271,6 +271,8 @@
}
```
+See also [this post](https://matklad.github.io/2023/11/15/push-ifs-up-and-fors-down.html)
+
## Assertions
Assert liberally.
@@ -608,7 +610,7 @@
```rust
// GOOD
-fn frobnicate(f: impl FnMut()) {
+fn frobnicate(mut f: impl FnMut()) {
frobnicate_impl(&mut f)
}
fn frobnicate_impl(f: &mut dyn FnMut()) {
@@ -616,7 +618,7 @@
}
// BAD
-fn frobnicate(f: impl FnMut()) {
+fn frobnicate(mut f: impl FnMut()) {
// lots of code
}
```
@@ -975,7 +977,7 @@
**Rationale:** consistency & simplicity.
`ref` was required before [match ergonomics](https://github.com/rust-lang/rfcs/blob/master/text/2005-match-ergonomics.md).
Today, it is redundant.
-Between `ref` and mach ergonomics, the latter is more ergonomic in most cases, and is simpler (does not require a keyword).
+Between `ref` and match ergonomics, the latter is more ergonomic in most cases, and is simpler (does not require a keyword).
## Empty Match Arms
diff --git a/editors/code/package.json b/editors/code/package.json
index 8953a30..470db24 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -887,7 +887,7 @@
"title": "Cargo",
"properties": {
"rust-analyzer.cargo.buildScripts.overrideCommand": {
- "markdownDescription": "Override the command rust-analyzer uses to run build scripts and\nbuild procedural macros. The command is required to output json\nand should therefore include `--message-format=json` or a similar\noption.\n\nIf there are multiple linked projects/workspaces, this command is invoked for\neach of them, with the working directory being the workspace root\n(i.e., the folder containing the `Cargo.toml`). This can be overwritten\nby changing `#rust-analyzer.cargo.buildScripts.invocationStrategy#`.\n\nBy default, a cargo invocation will be constructed for the configured\ntargets and features, with the following base command line:\n\n```bash\ncargo check --quiet --workspace --message-format=json --all-targets --keep-going\n```\n.",
+ "markdownDescription": "Override the command rust-analyzer uses to run build scripts and\nbuild procedural macros. The command is required to output json\nand should therefore include `--message-format=json` or a similar\noption.\n\nIf there are multiple linked projects/workspaces, this command is invoked for\neach of them, with the working directory being the workspace root\n(i.e., the folder containing the `Cargo.toml`). This can be overwritten\nby changing `#rust-analyzer.cargo.buildScripts.invocationStrategy#`.\n\nBy default, a cargo invocation will be constructed for the configured\ntargets and features, with the following base command line:\n\n```bash\ncargo check --quiet --workspace --message-format=json --all-targets --keep-going\n```\n\nNote: The option must be specified as an array of command line arguments, with\nthe first argument being the name of the command to run.",
"default": null,
"type": [
"null",
@@ -1207,7 +1207,7 @@
"title": "Check",
"properties": {
"rust-analyzer.check.overrideCommand": {
- "markdownDescription": "Override the command rust-analyzer uses instead of `cargo check` for\ndiagnostics on save. The command is required to output json and\nshould therefore include `--message-format=json` or a similar option\n(if your client supports the `colorDiagnosticOutput` experimental\ncapability, you can use `--message-format=json-diagnostic-rendered-ansi`).\n\nIf you're changing this because you're using some tool wrapping\nCargo, you might also want to change\n`#rust-analyzer.cargo.buildScripts.overrideCommand#`.\n\nIf there are multiple linked projects/workspaces, this command is invoked for\neach of them, with the working directory being the workspace root\n(i.e., the folder containing the `Cargo.toml`). This can be overwritten\nby changing `#rust-analyzer.check.invocationStrategy#`.\n\nIf `$saved_file` is part of the command, rust-analyzer will pass\nthe absolute path of the saved file to the provided command. This is\nintended to be used with non-Cargo build systems.\nNote that `$saved_file` is experimental and may be removed in the future.\n\nAn example command would be:\n\n```bash\ncargo check --workspace --message-format=json --all-targets\n```\n.",
+ "markdownDescription": "Override the command rust-analyzer uses instead of `cargo check` for\ndiagnostics on save. The command is required to output json and\nshould therefore include `--message-format=json` or a similar option\n(if your client supports the `colorDiagnosticOutput` experimental\ncapability, you can use `--message-format=json-diagnostic-rendered-ansi`).\n\nIf you're changing this because you're using some tool wrapping\nCargo, you might also want to change\n`#rust-analyzer.cargo.buildScripts.overrideCommand#`.\n\nIf there are multiple linked projects/workspaces, this command is invoked for\neach of them, with the working directory being the workspace root\n(i.e., the folder containing the `Cargo.toml`). This can be overwritten\nby changing `#rust-analyzer.check.invocationStrategy#`.\n\nIf `$saved_file` is part of the command, rust-analyzer will pass\nthe absolute path of the saved file to the provided command. This is\nintended to be used with non-Cargo build systems.\nNote that `$saved_file` is experimental and may be removed in the future.\n\nAn example command would be:\n\n```bash\ncargo check --workspace --message-format=json --all-targets\n```\n\nNote: The option must be specified as an array of command line arguments, with\nthe first argument being the name of the command to run.",
"default": null,
"type": [
"null",
@@ -2808,7 +2808,7 @@
"title": "Rustfmt",
"properties": {
"rust-analyzer.rustfmt.overrideCommand": {
- "markdownDescription": "Advanced option, fully override the command rust-analyzer uses for\nformatting. This should be the equivalent of `rustfmt` here, and\nnot that of `cargo fmt`. The file contents will be passed on the\nstandard input and the formatted result will be read from the\nstandard output.",
+ "markdownDescription": "Advanced option, fully override the command rust-analyzer uses for\nformatting. This should be the equivalent of `rustfmt` here, and\nnot that of `cargo fmt`. The file contents will be passed on the\nstandard input and the formatted result will be read from the\nstandard output.\n\nNote: The option must be specified as an array of command line arguments, with\nthe first argument being the name of the command to run.",
"default": null,
"type": [
"null",