⬆️ rust-analyzer

This commit is contained in:
Laurențiu Nicola 2022-11-23 17:24:03 +02:00
parent 61c744d4fd
commit a2a1d99545
126 changed files with 2098 additions and 904 deletions

116
Cargo.lock generated
View file

@ -221,6 +221,16 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "command-group"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7a8a86f409b4a59df3a3e4bee2de0b83f1755fdd2a25e3a9684c396fc4bed2c"
dependencies = [
"nix",
"winapi",
]
[[package]] [[package]]
name = "countme" name = "countme"
version = "3.0.1" version = "3.0.1"
@ -300,7 +310,7 @@ dependencies = [
"hashbrown", "hashbrown",
"lock_api", "lock_api",
"once_cell", "once_cell",
"parking_lot_core 0.9.3", "parking_lot_core 0.9.4",
] ]
[[package]] [[package]]
@ -359,14 +369,14 @@ dependencies = [
[[package]] [[package]]
name = "filetime" name = "filetime"
version = "0.2.17" version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c" checksum = "4b9663d381d07ae25dc88dbdf27df458faa83a9b25336bcac83d5e452b5fc9d3"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"redox_syscall", "redox_syscall",
"windows-sys 0.36.1", "windows-sys 0.42.0",
] ]
[[package]] [[package]]
@ -390,6 +400,7 @@ name = "flycheck"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"cargo_metadata", "cargo_metadata",
"command-group",
"crossbeam-channel", "crossbeam-channel",
"jod-thread", "jod-thread",
"paths", "paths",
@ -963,11 +974,24 @@ dependencies = [
[[package]] [[package]]
name = "miow" name = "miow"
version = "0.4.0" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7377f7792b3afb6a3cba68daa54ca23c032137010460d667fda53a8d66be00e" checksum = "52ffbca2f655e33c08be35d87278e5b18b89550a37dbd598c20db92f6a471123"
dependencies = [ dependencies = [
"windows-sys 0.28.0", "windows-sys 0.42.0",
]
[[package]]
name = "nix"
version = "0.22.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf"
dependencies = [
"bitflags",
"cc",
"cfg-if",
"libc",
"memoffset",
] ]
[[package]] [[package]]
@ -1037,7 +1061,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [ dependencies = [
"lock_api", "lock_api",
"parking_lot_core 0.9.3", "parking_lot_core 0.9.4",
] ]
[[package]] [[package]]
@ -1056,15 +1080,15 @@ dependencies = [
[[package]] [[package]]
name = "parking_lot_core" name = "parking_lot_core"
version = "0.9.3" version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"redox_syscall", "redox_syscall",
"smallvec", "smallvec",
"windows-sys 0.36.1", "windows-sys 0.42.0",
] ]
[[package]] [[package]]
@ -1979,19 +2003,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82ca39602d5cbfa692c4b67e3bcbb2751477355141c1ed434c94da4186836ff6"
dependencies = [
"windows_aarch64_msvc 0.28.0",
"windows_i686_gnu 0.28.0",
"windows_i686_msvc 0.28.0",
"windows_x86_64_gnu 0.28.0",
"windows_x86_64_msvc 0.28.0",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.36.1" version = "0.36.1"
@ -2006,10 +2017,25 @@ dependencies = [
] ]
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows-sys"
version = "0.28.0" version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52695a41e536859d5308cc613b4a022261a274390b25bd29dfff4bf08505f3c2" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc 0.42.0",
"windows_i686_gnu 0.42.0",
"windows_i686_msvc 0.42.0",
"windows_x86_64_gnu 0.42.0",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc 0.42.0",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
@ -2018,10 +2044,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_aarch64_msvc"
version = "0.28.0" version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f54725ac23affef038fecb177de6c9bf065787c2f432f79e3c373da92f3e1d8a" checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
@ -2030,10 +2056,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_gnu"
version = "0.28.0" version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d5158a43cc43623c0729d1ad6647e62fa384a3d135fd15108d37c683461f64" checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
@ -2042,10 +2068,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_i686_msvc"
version = "0.28.0" version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc31f409f565611535130cfe7ee8e6655d3fa99c1c61013981e491921b5ce954" checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
@ -2054,10 +2080,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_gnu"
version = "0.28.0" version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f2b8c7cbd3bfdddd9ab98769f9746a7fad1bca236554cd032b78d768bc0e89f" checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
@ -2065,6 +2097,12 @@ version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
[[package]] [[package]]
name = "write-json" name = "write-json"
version = "0.1.2" version = "0.1.2"

View file

@ -4,7 +4,7 @@ version = "0.0.0"
description = "TBD" description = "TBD"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
rust-version = "1.57" rust-version = "1.65"
[lib] [lib]
doctest = false doctest = false

View file

@ -4,7 +4,7 @@ version = "0.0.0"
description = "TBD" description = "TBD"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
rust-version = "1.57" rust-version = "1.65"
[lib] [lib]
doctest = false doctest = false

View file

@ -4,7 +4,7 @@ version = "0.0.0"
description = "TBD" description = "TBD"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
rust-version = "1.57" rust-version = "1.65"
[lib] [lib]
doctest = false doctest = false
@ -17,6 +17,7 @@ rustc-hash = "1.1.0"
serde = { version = "1.0.137", features = ["derive"] } serde = { version = "1.0.137", features = ["derive"] }
serde_json = "1.0.86" serde_json = "1.0.86"
jod-thread = "0.1.2" jod-thread = "0.1.2"
command-group = "1.0.8"
toolchain = { path = "../toolchain", version = "0.0.0" } toolchain = { path = "../toolchain", version = "0.0.0" }
stdx = { path = "../stdx", version = "0.0.0" } stdx = { path = "../stdx", version = "0.0.0" }

View file

@ -10,11 +10,12 @@ use std::{
time::Duration, time::Duration,
}; };
use command_group::{CommandGroup, GroupChild};
use crossbeam_channel::{never, select, unbounded, Receiver, Sender}; use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
use paths::AbsPathBuf; use paths::AbsPathBuf;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use serde::Deserialize; use serde::Deserialize;
use stdx::{process::streaming_output, JodChild}; use stdx::process::streaming_output;
pub use cargo_metadata::diagnostic::{ pub use cargo_metadata::diagnostic::{
Applicability, Diagnostic, DiagnosticCode, DiagnosticLevel, DiagnosticSpan, Applicability, Diagnostic, DiagnosticCode, DiagnosticLevel, DiagnosticSpan,
@ -39,7 +40,7 @@ pub enum InvocationLocation {
pub enum FlycheckConfig { pub enum FlycheckConfig {
CargoCommand { CargoCommand {
command: String, command: String,
target_triple: Option<String>, target_triples: Vec<String>,
all_targets: bool, all_targets: bool,
no_default_features: bool, no_default_features: bool,
all_features: bool, all_features: bool,
@ -285,7 +286,7 @@ impl FlycheckActor {
let (mut cmd, args) = match &self.config { let (mut cmd, args) = match &self.config {
FlycheckConfig::CargoCommand { FlycheckConfig::CargoCommand {
command, command,
target_triple, target_triples,
no_default_features, no_default_features,
all_targets, all_targets,
all_features, all_features,
@ -299,7 +300,7 @@ impl FlycheckActor {
cmd.args(&["--workspace", "--message-format=json", "--manifest-path"]) cmd.args(&["--workspace", "--message-format=json", "--manifest-path"])
.arg(self.root.join("Cargo.toml").as_os_str()); .arg(self.root.join("Cargo.toml").as_os_str());
if let Some(target) = target_triple { for target in target_triples {
cmd.args(&["--target", target.as_str()]); cmd.args(&["--target", target.as_str()]);
} }
if *all_targets { if *all_targets {
@ -359,10 +360,12 @@ impl FlycheckActor {
} }
} }
struct JodChild(GroupChild);
/// A handle to a cargo process used for fly-checking. /// A handle to a cargo process used for fly-checking.
struct CargoHandle { struct CargoHandle {
/// The handle to the actual cargo process. As we cannot cancel directly from with /// The handle to the actual cargo process. As we cannot cancel directly from with
/// a read syscall dropping and therefor terminating the process is our best option. /// a read syscall dropping and therefore terminating the process is our best option.
child: JodChild, child: JodChild,
thread: jod_thread::JoinHandle<io::Result<(bool, String)>>, thread: jod_thread::JoinHandle<io::Result<(bool, String)>>,
receiver: Receiver<CargoMessage>, receiver: Receiver<CargoMessage>,
@ -371,10 +374,10 @@ struct CargoHandle {
impl CargoHandle { impl CargoHandle {
fn spawn(mut command: Command) -> std::io::Result<CargoHandle> { fn spawn(mut command: Command) -> std::io::Result<CargoHandle> {
command.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null()); command.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null());
let mut child = JodChild::spawn(command)?; let mut child = command.group_spawn().map(JodChild)?;
let stdout = child.stdout.take().unwrap(); let stdout = child.0.inner().stdout.take().unwrap();
let stderr = child.stderr.take().unwrap(); let stderr = child.0.inner().stderr.take().unwrap();
let (sender, receiver) = unbounded(); let (sender, receiver) = unbounded();
let actor = CargoActor::new(sender, stdout, stderr); let actor = CargoActor::new(sender, stdout, stderr);
@ -386,13 +389,13 @@ impl CargoHandle {
} }
fn cancel(mut self) { fn cancel(mut self) {
let _ = self.child.kill(); let _ = self.child.0.kill();
let _ = self.child.wait(); let _ = self.child.0.wait();
} }
fn join(mut self) -> io::Result<()> { fn join(mut self) -> io::Result<()> {
let _ = self.child.kill(); let _ = self.child.0.kill();
let exit_status = self.child.wait()?; let exit_status = self.child.0.wait()?;
let (read_at_least_one_message, error) = self.thread.join()?; let (read_at_least_one_message, error) = self.thread.join()?;
if read_at_least_one_message || exit_status.success() { if read_at_least_one_message || exit_status.success() {
Ok(()) Ok(())

View file

@ -4,7 +4,7 @@ version = "0.0.0"
description = "TBD" description = "TBD"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
rust-version = "1.57" rust-version = "1.65"
[lib] [lib]
doctest = false doctest = false

View file

@ -236,11 +236,19 @@ impl TraitData {
.by_key("rustc_skip_array_during_method_dispatch") .by_key("rustc_skip_array_during_method_dispatch")
.exists(); .exists();
let mut collector = let (items, attribute_calls, diagnostics) = match &tr_def.items {
AssocItemCollector::new(db, module_id, tree_id.file_id(), ItemContainerId::TraitId(tr)); Some(items) => {
collector.collect(&item_tree, tree_id.tree_id(), &tr_def.items); let mut collector = AssocItemCollector::new(
let (items, attribute_calls, diagnostics) = collector.finish(); db,
module_id,
tree_id.file_id(),
ItemContainerId::TraitId(tr),
);
collector.collect(&item_tree, tree_id.tree_id(), items);
collector.finish()
}
None => Default::default(),
};
( (
Arc::new(TraitData { Arc::new(TraitData {
name, name,

View file

@ -666,7 +666,8 @@ pub struct Trait {
pub generic_params: Interned<GenericParams>, pub generic_params: Interned<GenericParams>,
pub is_auto: bool, pub is_auto: bool,
pub is_unsafe: bool, pub is_unsafe: bool,
pub items: Box<[AssocItem]>, /// This is [`None`] if this Trait is a trait alias.
pub items: Option<Box<[AssocItem]>>,
pub ast_id: FileAstId<ast::Trait>, pub ast_id: FileAstId<ast::Trait>,
} }

View file

@ -451,15 +451,7 @@ impl<'a> Ctx<'a> {
.collect() .collect()
}); });
let ast_id = self.source_ast_id_map.ast_id(trait_def); let ast_id = self.source_ast_id_map.ast_id(trait_def);
let res = Trait { let res = Trait { name, visibility, generic_params, is_auto, is_unsafe, items, ast_id };
name,
visibility,
generic_params,
is_auto,
is_unsafe,
items: items.unwrap_or_default(),
ast_id,
};
Some(id(self.data().traits.alloc(res))) Some(id(self.data().traits.alloc(res)))
} }

View file

@ -375,12 +375,21 @@ impl<'a> Printer<'a> {
} }
w!(self, "trait {}", name); w!(self, "trait {}", name);
self.print_generic_params(generic_params); self.print_generic_params(generic_params);
self.print_where_clause_and_opening_brace(generic_params); match items {
self.indented(|this| { Some(items) => {
for item in &**items { self.print_where_clause_and_opening_brace(generic_params);
this.print_mod_item((*item).into()); self.indented(|this| {
for item in &**items {
this.print_mod_item((*item).into());
}
});
} }
}); None => {
w!(self, " = ");
// FIXME: Print the aliased traits
self.print_where_clause_and_opening_brace(generic_params);
}
}
wln!(self, "}}"); wln!(self, "}}");
} }
ModItem::Impl(it) => { ModItem::Impl(it) => {

View file

@ -94,11 +94,11 @@ macro_rules! m {
($($s:stmt)*) => (stringify!($($s |)*);) ($($s:stmt)*) => (stringify!($($s |)*);)
} }
stringify!(; stringify!(;
|; | ;
|92|; |92| ;
|let x = 92|; |let x = 92| ;
|loop {} |loop {}
|; | ;
|); |);
"#]], "#]],
); );
@ -118,7 +118,7 @@ m!(.. .. ..);
macro_rules! m { macro_rules! m {
($($p:pat)*) => (stringify!($($p |)*);) ($($p:pat)*) => (stringify!($($p |)*);)
} }
stringify!(.. .. ..|); stringify!(.. .. .. |);
"#]], "#]],
); );
} }

View file

@ -82,14 +82,14 @@ fn attribute_macro_syntax_completion_2() {
#[proc_macros::identity_when_valid] #[proc_macros::identity_when_valid]
fn foo() { bar.; blub } fn foo() { bar.; blub }
"#, "#,
expect![[r##" expect![[r#"
#[proc_macros::identity_when_valid] #[proc_macros::identity_when_valid]
fn foo() { bar.; blub } fn foo() { bar.; blub }
fn foo() { fn foo() {
bar.; bar. ;
blub blub
}"##]], }"#]],
); );
} }

View file

@ -212,6 +212,7 @@ impl Import {
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
struct ImportDirective { struct ImportDirective {
/// The module this import directive is in.
module_id: LocalModuleId, module_id: LocalModuleId,
import: Import, import: Import,
status: PartialResolvedImport, status: PartialResolvedImport,
@ -963,8 +964,10 @@ impl DefCollector<'_> {
fn update( fn update(
&mut self, &mut self,
// The module for which `resolutions` have been resolve
module_id: LocalModuleId, module_id: LocalModuleId,
resolutions: &[(Option<Name>, PerNs)], resolutions: &[(Option<Name>, PerNs)],
// Visibility this import will have
vis: Visibility, vis: Visibility,
import_type: ImportType, import_type: ImportType,
) { ) {
@ -974,6 +977,7 @@ impl DefCollector<'_> {
fn update_recursive( fn update_recursive(
&mut self, &mut self,
// The module for which `resolutions` have been resolve
module_id: LocalModuleId, module_id: LocalModuleId,
resolutions: &[(Option<Name>, PerNs)], resolutions: &[(Option<Name>, PerNs)],
// All resolutions are imported with this visibility; the visibilities in // All resolutions are imported with this visibility; the visibilities in

View file

@ -73,7 +73,10 @@ impl DefMap {
pub(crate) fn resolve_visibility( pub(crate) fn resolve_visibility(
&self, &self,
db: &dyn DefDatabase, db: &dyn DefDatabase,
// module to import to
original_module: LocalModuleId, original_module: LocalModuleId,
// pub(path)
// ^^^^ this
visibility: &RawVisibility, visibility: &RawVisibility,
) -> Option<Visibility> { ) -> Option<Visibility> {
let mut vis = match visibility { let mut vis = match visibility {
@ -115,6 +118,7 @@ impl DefMap {
&self, &self,
db: &dyn DefDatabase, db: &dyn DefDatabase,
mode: ResolveMode, mode: ResolveMode,
// module to import to
mut original_module: LocalModuleId, mut original_module: LocalModuleId,
path: &ModPath, path: &ModPath,
shadow: BuiltinShadowMode, shadow: BuiltinShadowMode,
@ -361,6 +365,9 @@ impl DefMap {
); );
} }
}; };
curr_per_ns = curr_per_ns
.filter_visibility(|vis| vis.is_visible_from_def_map(db, self, original_module));
} }
ResolvePathResult::with(curr_per_ns, ReachedFixedPoint::Yes, None, Some(self.krate)) ResolvePathResult::with(curr_per_ns, ReachedFixedPoint::Yes, None, Some(self.krate))

View file

@ -58,9 +58,9 @@ extern {
"#, "#,
expect![[r#" expect![[r#"
crate crate
E: t E: _
S: t v S: t v
V: t v V: _
foo: t foo: t
crate::foo crate::foo
@ -307,7 +307,7 @@ pub struct FromLib;
Bar: t v Bar: t v
crate::foo crate::foo
Bar: t v Bar: _
FromLib: t v FromLib: t v
"#]], "#]],
); );

View file

@ -119,7 +119,7 @@ use foo::*;
use foo::bar::*; use foo::bar::*;
//- /foo/mod.rs //- /foo/mod.rs
mod bar; pub mod bar;
fn Foo() {}; fn Foo() {};
pub struct Foo {}; pub struct Foo {};
@ -132,6 +132,7 @@ pub(crate) struct PubCrateStruct;
crate crate
Foo: t Foo: t
PubCrateStruct: t v PubCrateStruct: t v
bar: t
foo: t foo: t
crate::foo crate::foo
@ -336,3 +337,33 @@ mod d {
"#]], "#]],
); );
} }
#[test]
fn glob_name_collision_check_visibility() {
check(
r#"
mod event {
mod serenity {
pub fn Event() {}
}
use serenity::*;
pub struct Event {}
}
use event::Event;
"#,
expect![[r#"
crate
Event: t
event: t
crate::event
Event: t v
serenity: t
crate::event::serenity
Event: v
"#]],
);
}

View file

@ -580,7 +580,7 @@ fn module_resolution_decl_inside_inline_module_in_crate_root() {
//- /main.rs //- /main.rs
mod foo { mod foo {
#[path = "baz.rs"] #[path = "baz.rs"]
mod bar; pub mod bar;
} }
use self::foo::bar::Baz; use self::foo::bar::Baz;

View file

@ -4,7 +4,7 @@ version = "0.0.0"
description = "TBD" description = "TBD"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
rust-version = "1.57" rust-version = "1.65"
[lib] [lib]
doctest = false doctest = false

View file

@ -4,6 +4,7 @@ use std::mem;
use mbe::{SyntheticToken, SyntheticTokenId, TokenMap}; use mbe::{SyntheticToken, SyntheticTokenId, TokenMap};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use smallvec::SmallVec;
use syntax::{ use syntax::{
ast::{self, AstNode, HasLoopBody}, ast::{self, AstNode, HasLoopBody},
match_ast, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, match_ast, SyntaxElement, SyntaxKind, SyntaxNode, TextRange,
@ -292,25 +293,34 @@ pub(crate) fn reverse_fixups(
token_map: &TokenMap, token_map: &TokenMap,
undo_info: &SyntaxFixupUndoInfo, undo_info: &SyntaxFixupUndoInfo,
) { ) {
tt.token_trees.retain(|tt| match tt { let tts = std::mem::take(&mut tt.token_trees);
tt::TokenTree::Leaf(leaf) => { tt.token_trees = tts
token_map.synthetic_token_id(leaf.id()).is_none() .into_iter()
|| token_map.synthetic_token_id(leaf.id()) != Some(EMPTY_ID) .filter(|tt| match tt {
} tt::TokenTree::Leaf(leaf) => token_map.synthetic_token_id(leaf.id()) != Some(EMPTY_ID),
tt::TokenTree::Subtree(st) => st.delimiter.map_or(true, |d| { tt::TokenTree::Subtree(st) => {
token_map.synthetic_token_id(d.id).is_none() st.delimiter.map_or(true, |d| token_map.synthetic_token_id(d.id) != Some(EMPTY_ID))
|| token_map.synthetic_token_id(d.id) != Some(EMPTY_ID)
}),
});
tt.token_trees.iter_mut().for_each(|tt| match tt {
tt::TokenTree::Subtree(tt) => reverse_fixups(tt, token_map, undo_info),
tt::TokenTree::Leaf(leaf) => {
if let Some(id) = token_map.synthetic_token_id(leaf.id()) {
let original = &undo_info.original[id.0 as usize];
*tt = tt::TokenTree::Subtree(original.clone());
} }
} })
}); .flat_map(|tt| match tt {
tt::TokenTree::Subtree(mut tt) => {
reverse_fixups(&mut tt, token_map, undo_info);
SmallVec::from_const([tt.into()])
}
tt::TokenTree::Leaf(leaf) => {
if let Some(id) = token_map.synthetic_token_id(leaf.id()) {
let original = undo_info.original[id.0 as usize].clone();
if original.delimiter.is_none() {
original.token_trees.into()
} else {
SmallVec::from_const([original.into()])
}
} else {
SmallVec::from_const([leaf.into()])
}
}
})
.collect();
} }
#[cfg(test)] #[cfg(test)]
@ -319,6 +329,31 @@ mod tests {
use super::reverse_fixups; use super::reverse_fixups;
// The following three functions are only meant to check partial structural equivalence of
// `TokenTree`s, see the last assertion in `check()`.
fn check_leaf_eq(a: &tt::Leaf, b: &tt::Leaf) -> bool {
match (a, b) {
(tt::Leaf::Literal(a), tt::Leaf::Literal(b)) => a.text == b.text,
(tt::Leaf::Punct(a), tt::Leaf::Punct(b)) => a.char == b.char,
(tt::Leaf::Ident(a), tt::Leaf::Ident(b)) => a.text == b.text,
_ => false,
}
}
fn check_subtree_eq(a: &tt::Subtree, b: &tt::Subtree) -> bool {
a.delimiter.map(|it| it.kind) == b.delimiter.map(|it| it.kind)
&& a.token_trees.len() == b.token_trees.len()
&& a.token_trees.iter().zip(&b.token_trees).all(|(a, b)| check_tt_eq(a, b))
}
fn check_tt_eq(a: &tt::TokenTree, b: &tt::TokenTree) -> bool {
match (a, b) {
(tt::TokenTree::Leaf(a), tt::TokenTree::Leaf(b)) => check_leaf_eq(a, b),
(tt::TokenTree::Subtree(a), tt::TokenTree::Subtree(b)) => check_subtree_eq(a, b),
_ => false,
}
}
#[track_caller] #[track_caller]
fn check(ra_fixture: &str, mut expect: Expect) { fn check(ra_fixture: &str, mut expect: Expect) {
let parsed = syntax::SourceFile::parse(ra_fixture); let parsed = syntax::SourceFile::parse(ra_fixture);
@ -331,17 +366,15 @@ mod tests {
fixups.append, fixups.append,
); );
let mut actual = tt.to_string(); let actual = format!("{}\n", tt);
actual.push('\n');
expect.indent(false); expect.indent(false);
expect.assert_eq(&actual); expect.assert_eq(&actual);
// the fixed-up tree should be syntactically valid // the fixed-up tree should be syntactically valid
let (parse, _) = mbe::token_tree_to_syntax_node(&tt, ::mbe::TopEntryPoint::MacroItems); let (parse, _) = mbe::token_tree_to_syntax_node(&tt, ::mbe::TopEntryPoint::MacroItems);
assert_eq!( assert!(
parse.errors(), parse.errors().is_empty(),
&[],
"parse has syntax errors. parse tree:\n{:#?}", "parse has syntax errors. parse tree:\n{:#?}",
parse.syntax_node() parse.syntax_node()
); );
@ -349,9 +382,12 @@ mod tests {
reverse_fixups(&mut tt, &tmap, &fixups.undo_info); reverse_fixups(&mut tt, &tmap, &fixups.undo_info);
// the fixed-up + reversed version should be equivalent to the original input // the fixed-up + reversed version should be equivalent to the original input
// (but token IDs don't matter) // modulo token IDs and `Punct`s' spacing.
let (original_as_tt, _) = mbe::syntax_node_to_token_tree(&parsed.syntax_node()); let (original_as_tt, _) = mbe::syntax_node_to_token_tree(&parsed.syntax_node());
assert_eq!(tt.to_string(), original_as_tt.to_string()); assert!(
check_subtree_eq(&tt, &original_as_tt),
"different token tree: {tt:?}, {original_as_tt:?}"
);
} }
#[test] #[test]
@ -468,7 +504,7 @@ fn foo() {
} }
"#, "#,
expect![[r#" expect![[r#"
fn foo () {a .__ra_fixup} fn foo () {a . __ra_fixup}
"#]], "#]],
) )
} }
@ -482,7 +518,7 @@ fn foo() {
} }
"#, "#,
expect![[r#" expect![[r#"
fn foo () {a .__ra_fixup ;} fn foo () {a . __ra_fixup ;}
"#]], "#]],
) )
} }
@ -497,7 +533,7 @@ fn foo() {
} }
"#, "#,
expect![[r#" expect![[r#"
fn foo () {a .__ra_fixup ; bar () ;} fn foo () {a . __ra_fixup ; bar () ;}
"#]], "#]],
) )
} }
@ -525,7 +561,7 @@ fn foo() {
} }
"#, "#,
expect![[r#" expect![[r#"
fn foo () {let x = a .__ra_fixup ;} fn foo () {let x = a . __ra_fixup ;}
"#]], "#]],
) )
} }
@ -541,7 +577,7 @@ fn foo() {
} }
"#, "#,
expect![[r#" expect![[r#"
fn foo () {a .b ; bar () ;} fn foo () {a . b ; bar () ;}
"#]], "#]],
) )
} }

View file

@ -814,7 +814,7 @@ impl<'a> InFile<&'a SyntaxNode> {
pub fn original_syntax_node(self, db: &dyn db::AstDatabase) -> Option<InFile<SyntaxNode>> { pub fn original_syntax_node(self, db: &dyn db::AstDatabase) -> Option<InFile<SyntaxNode>> {
// This kind of upmapping can only be achieved in attribute expanded files, // This kind of upmapping can only be achieved in attribute expanded files,
// as we don't have node inputs otherwise and therefor can't find an `N` node in the input // as we don't have node inputs otherwise and therefore can't find an `N` node in the input
if !self.file_id.is_macro() { if !self.file_id.is_macro() {
return Some(self.map(Clone::clone)); return Some(self.map(Clone::clone));
} else if !self.file_id.is_attr_macro(db) { } else if !self.file_id.is_attr_macro(db) {
@ -926,7 +926,7 @@ impl<N: AstNode> InFile<N> {
pub fn original_ast_node(self, db: &dyn db::AstDatabase) -> Option<InFile<N>> { pub fn original_ast_node(self, db: &dyn db::AstDatabase) -> Option<InFile<N>> {
// This kind of upmapping can only be achieved in attribute expanded files, // This kind of upmapping can only be achieved in attribute expanded files,
// as we don't have node inputs otherwise and therefor can't find an `N` node in the input // as we don't have node inputs otherwise and therefore can't find an `N` node in the input
if !self.file_id.is_macro() { if !self.file_id.is_macro() {
return Some(self); return Some(self);
} else if !self.file_id.is_attr_macro(db) { } else if !self.file_id.is_attr_macro(db) {

View file

@ -4,7 +4,7 @@ version = "0.0.0"
description = "TBD" description = "TBD"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
rust-version = "1.57" rust-version = "1.65"
[lib] [lib]
doctest = false doctest = false

View file

@ -53,7 +53,7 @@ pub use builder::{ParamKind, TyBuilder};
pub use chalk_ext::*; pub use chalk_ext::*;
pub use infer::{ pub use infer::{
could_coerce, could_unify, Adjust, Adjustment, AutoBorrow, BindingMode, InferenceDiagnostic, could_coerce, could_unify, Adjust, Adjustment, AutoBorrow, BindingMode, InferenceDiagnostic,
InferenceResult, InferenceResult, OverloadedDeref, PointerCast,
}; };
pub use interner::Interner; pub use interner::Interner;
pub use lower::{ pub use lower::{
@ -523,7 +523,7 @@ where
} }
pub fn callable_sig_from_fnonce( pub fn callable_sig_from_fnonce(
self_ty: &Canonical<Ty>, self_ty: &Ty,
env: Arc<TraitEnvironment>, env: Arc<TraitEnvironment>,
db: &dyn HirDatabase, db: &dyn HirDatabase,
) -> Option<CallableSig> { ) -> Option<CallableSig> {
@ -531,27 +531,28 @@ pub fn callable_sig_from_fnonce(
let fn_once_trait = FnTrait::FnOnce.get_id(db, krate)?; let fn_once_trait = FnTrait::FnOnce.get_id(db, krate)?;
let output_assoc_type = db.trait_data(fn_once_trait).associated_type_by_name(&name![Output])?; let output_assoc_type = db.trait_data(fn_once_trait).associated_type_by_name(&name![Output])?;
let mut kinds = self_ty.binders.interned().to_vec();
let b = TyBuilder::trait_ref(db, fn_once_trait); let b = TyBuilder::trait_ref(db, fn_once_trait);
if b.remaining() != 2 { if b.remaining() != 2 {
return None; return None;
} }
let fn_once = b let fn_once = b.push(self_ty.clone()).fill_with_bound_vars(DebruijnIndex::INNERMOST, 0).build();
.push(self_ty.value.clone()) let kinds = fn_once
.fill_with_bound_vars(DebruijnIndex::INNERMOST, kinds.len()) .substitution
.build(); .iter(Interner)
kinds.extend(fn_once.substitution.iter(Interner).skip(1).map(|x| { .skip(1)
let vk = match x.data(Interner) { .map(|x| {
chalk_ir::GenericArgData::Ty(_) => { let vk = match x.data(Interner) {
chalk_ir::VariableKind::Ty(chalk_ir::TyVariableKind::General) chalk_ir::GenericArgData::Ty(_) => {
} chalk_ir::VariableKind::Ty(chalk_ir::TyVariableKind::General)
chalk_ir::GenericArgData::Lifetime(_) => chalk_ir::VariableKind::Lifetime, }
chalk_ir::GenericArgData::Const(c) => { chalk_ir::GenericArgData::Lifetime(_) => chalk_ir::VariableKind::Lifetime,
chalk_ir::VariableKind::Const(c.data(Interner).ty.clone()) chalk_ir::GenericArgData::Const(c) => {
} chalk_ir::VariableKind::Const(c.data(Interner).ty.clone())
}; }
chalk_ir::WithKind::new(vk, UniverseIndex::ROOT) };
})); chalk_ir::WithKind::new(vk, UniverseIndex::ROOT)
})
.collect::<Vec<_>>();
// FIXME: chalk refuses to solve `<Self as FnOnce<^0.0>>::Output == ^0.1`, so we first solve // FIXME: chalk refuses to solve `<Self as FnOnce<^0.0>>::Output == ^0.1`, so we first solve
// `<Self as FnOnce<^0.0>>` and then replace `^0.0` with the concrete argument tuple. // `<Self as FnOnce<^0.0>>` and then replace `^0.0` with the concrete argument tuple.
@ -563,21 +564,16 @@ pub fn callable_sig_from_fnonce(
Some(Solution::Unique(vars)) => vars.value.subst, Some(Solution::Unique(vars)) => vars.value.subst,
_ => return None, _ => return None,
}; };
let args = subst.at(Interner, self_ty.binders.interned().len()).ty(Interner)?; let args = subst.at(Interner, 0).ty(Interner)?;
let params = match args.kind(Interner) { let params = match args.kind(Interner) {
chalk_ir::TyKind::Tuple(_, subst) => { chalk_ir::TyKind::Tuple(_, subst) => {
subst.iter(Interner).filter_map(|arg| arg.ty(Interner).cloned()).collect::<Vec<_>>() subst.iter(Interner).filter_map(|arg| arg.ty(Interner).cloned()).collect::<Vec<_>>()
} }
_ => return None, _ => return None,
}; };
if params.iter().any(|ty| ty.is_unknown()) {
return None;
}
let fn_once = TyBuilder::trait_ref(db, fn_once_trait) let fn_once =
.push(self_ty.value.clone()) TyBuilder::trait_ref(db, fn_once_trait).push(self_ty.clone()).push(args.clone()).build();
.push(args.clone())
.build();
let projection = let projection =
TyBuilder::assoc_type_projection(db, output_assoc_type, Some(fn_once.substitution.clone())) TyBuilder::assoc_type_projection(db, output_assoc_type, Some(fn_once.substitution.clone()))
.build(); .build();

View file

@ -541,7 +541,7 @@ pub struct ReceiverAdjustments {
impl ReceiverAdjustments { impl ReceiverAdjustments {
pub(crate) fn apply(&self, table: &mut InferenceTable<'_>, ty: Ty) -> (Ty, Vec<Adjustment>) { pub(crate) fn apply(&self, table: &mut InferenceTable<'_>, ty: Ty) -> (Ty, Vec<Adjustment>) {
let mut ty = ty; let mut ty = table.resolve_ty_shallow(&ty);
let mut adjust = Vec::new(); let mut adjust = Vec::new();
for _ in 0..self.autoderefs { for _ in 0..self.autoderefs {
match autoderef::autoderef_step(table, ty.clone()) { match autoderef::autoderef_step(table, ty.clone()) {

View file

@ -164,16 +164,16 @@ fn infer_associated_method_with_modules() {
check_infer( check_infer(
r#" r#"
mod a { mod a {
struct A; pub struct A;
impl A { pub fn thing() -> A { A {} }} impl A { pub fn thing() -> A { A {} }}
} }
mod b { mod b {
struct B; pub struct B;
impl B { pub fn thing() -> u32 { 99 }} impl B { pub fn thing() -> u32 { 99 }}
mod c { pub mod c {
struct C; pub struct C;
impl C { pub fn thing() -> C { C {} }} impl C { pub fn thing() -> C { C {} }}
} }
} }
@ -186,22 +186,22 @@ fn infer_associated_method_with_modules() {
} }
"#, "#,
expect![[r#" expect![[r#"
55..63 '{ A {} }': A 59..67 '{ A {} }': A
57..61 'A {}': A 61..65 'A {}': A
125..131 '{ 99 }': u32 133..139 '{ 99 }': u32
127..129 '99': u32 135..137 '99': u32
201..209 '{ C {} }': C 217..225 '{ C {} }': C
203..207 'C {}': C 219..223 'C {}': C
240..324 '{ ...g(); }': () 256..340 '{ ...g(); }': ()
250..251 'x': A 266..267 'x': A
254..265 'a::A::thing': fn thing() -> A 270..281 'a::A::thing': fn thing() -> A
254..267 'a::A::thing()': A 270..283 'a::A::thing()': A
277..278 'y': u32 293..294 'y': u32
281..292 'b::B::thing': fn thing() -> u32 297..308 'b::B::thing': fn thing() -> u32
281..294 'b::B::thing()': u32 297..310 'b::B::thing()': u32
304..305 'z': C 320..321 'z': C
308..319 'c::C::thing': fn thing() -> C 324..335 'c::C::thing': fn thing() -> C
308..321 'c::C::thing()': C 324..337 'c::C::thing()': C
"#]], "#]],
); );
} }

View file

@ -1707,3 +1707,19 @@ impl<T, const N: usize> Trait for [T; N] {
"#, "#,
); );
} }
#[test]
fn unsize_array_with_inference_variable() {
check_types(
r#"
//- minicore: try, slice
use core::ops::ControlFlow;
fn foo() -> ControlFlow<(), [usize; 1]> { loop {} }
fn bar() -> ControlFlow<(), ()> {
let a = foo()?.len();
//^ usize
ControlFlow::Continue(())
}
"#,
);
}

View file

@ -214,7 +214,7 @@ fn infer_paths() {
fn a() -> u32 { 1 } fn a() -> u32 { 1 }
mod b { mod b {
fn c() -> u32 { 1 } pub fn c() -> u32 { 1 }
} }
fn test() { fn test() {
@ -225,13 +225,13 @@ fn test() {
expect![[r#" expect![[r#"
14..19 '{ 1 }': u32 14..19 '{ 1 }': u32
16..17 '1': u32 16..17 '1': u32
47..52 '{ 1 }': u32 51..56 '{ 1 }': u32
49..50 '1': u32 53..54 '1': u32
66..90 '{ ...c(); }': () 70..94 '{ ...c(); }': ()
72..73 'a': fn a() -> u32 76..77 'a': fn a() -> u32
72..75 'a()': u32 76..79 'a()': u32
81..85 'b::c': fn c() -> u32 85..89 'b::c': fn c() -> u32
81..87 'b::c()': u32 85..91 'b::c()': u32
"#]], "#]],
); );
} }
@ -1856,7 +1856,7 @@ fn not_shadowing_module_by_primitive() {
check_types( check_types(
r#" r#"
//- /str.rs //- /str.rs
fn foo() -> u32 {0} pub fn foo() -> u32 {0}
//- /main.rs //- /main.rs
mod str; mod str;

View file

@ -1706,7 +1706,7 @@ fn where_clause_trait_in_scope_for_method_resolution() {
check_types( check_types(
r#" r#"
mod foo { mod foo {
trait Trait { pub trait Trait {
fn foo(&self) -> u32 { 0 } fn foo(&self) -> u32 { 0 }
} }
} }
@ -1723,7 +1723,7 @@ fn super_trait_method_resolution() {
check_infer( check_infer(
r#" r#"
mod foo { mod foo {
trait SuperTrait { pub trait SuperTrait {
fn foo(&self) -> u32 {} fn foo(&self) -> u32 {}
} }
} }
@ -1735,15 +1735,15 @@ fn test<T: Trait1, U: Trait2>(x: T, y: U) {
y.foo(); y.foo();
}"#, }"#,
expect![[r#" expect![[r#"
49..53 'self': &Self 53..57 'self': &Self
62..64 '{}': u32 66..68 '{}': u32
181..182 'x': T 185..186 'x': T
187..188 'y': U 191..192 'y': U
193..222 '{ ...o(); }': () 197..226 '{ ...o(); }': ()
199..200 'x': T 203..204 'x': T
199..206 'x.foo()': u32 203..210 'x.foo()': u32
212..213 'y': U 216..217 'y': U
212..219 'y.foo()': u32 216..223 'y.foo()': u32
"#]], "#]],
); );
} }
@ -1754,7 +1754,7 @@ fn super_trait_impl_trait_method_resolution() {
r#" r#"
//- minicore: sized //- minicore: sized
mod foo { mod foo {
trait SuperTrait { pub trait SuperTrait {
fn foo(&self) -> u32 {} fn foo(&self) -> u32 {}
} }
} }
@ -1764,12 +1764,12 @@ fn test(x: &impl Trait1) {
x.foo(); x.foo();
}"#, }"#,
expect![[r#" expect![[r#"
49..53 'self': &Self 53..57 'self': &Self
62..64 '{}': u32 66..68 '{}': u32
115..116 'x': &impl Trait1 119..120 'x': &impl Trait1
132..148 '{ ...o(); }': () 136..152 '{ ...o(); }': ()
138..139 'x': &impl Trait1 142..143 'x': &impl Trait1
138..145 'x.foo()': u32 142..149 'x.foo()': u32
"#]], "#]],
); );
} }

View file

@ -4,7 +4,7 @@ version = "0.0.0"
description = "TBD" description = "TBD"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
rust-version = "1.57" rust-version = "1.65"
[lib] [lib]
doctest = false doctest = false

View file

@ -117,7 +117,7 @@ pub use {
name::{known, Name}, name::{known, Name},
ExpandResult, HirFileId, InFile, MacroFile, Origin, ExpandResult, HirFileId, InFile, MacroFile, Origin,
}, },
hir_ty::display::HirDisplay, hir_ty::{display::HirDisplay, PointerCast, Safety},
}; };
// These are negative re-exports: pub using these names is forbidden, they // These are negative re-exports: pub using these names is forbidden, they
@ -2997,8 +2997,7 @@ impl Type {
TyKind::Function(_) => Callee::FnPtr, TyKind::Function(_) => Callee::FnPtr,
TyKind::FnDef(..) => Callee::Def(self.ty.callable_def(db)?), TyKind::FnDef(..) => Callee::Def(self.ty.callable_def(db)?),
_ => { _ => {
let ty = hir_ty::replace_errors_with_variables(&self.ty); let sig = hir_ty::callable_sig_from_fnonce(&self.ty, self.env.clone(), db)?;
let sig = hir_ty::callable_sig_from_fnonce(&ty, self.env.clone(), db)?;
return Some(Callable { return Some(Callable {
ty: self.clone(), ty: self.clone(),
sig, sig,
@ -3651,6 +3650,28 @@ impl From<ItemInNs> for ScopeDef {
} }
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Adjust {
/// Go from ! to any type.
NeverToAny,
/// Dereference once, producing a place.
Deref(Option<OverloadedDeref>),
/// Take the address and produce either a `&` or `*` pointer.
Borrow(AutoBorrow),
Pointer(PointerCast),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum AutoBorrow {
/// Converts from T to &T.
Ref(Mutability),
/// Converts from T to *T.
RawPtr(Mutability),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct OverloadedDeref(pub Mutability);
pub trait HasVisibility { pub trait HasVisibility {
fn visibility(&self, db: &dyn HirDatabase) -> Visibility; fn visibility(&self, db: &dyn HirDatabase) -> Visibility;
fn is_visible_from(&self, db: &dyn HirDatabase, module: Module) -> bool { fn is_visible_from(&self, db: &dyn HirDatabase, module: Module) -> bool {

View file

@ -29,9 +29,10 @@ use crate::{
db::HirDatabase, db::HirDatabase,
semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx}, semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx},
source_analyzer::{resolve_hir_path, SourceAnalyzer}, source_analyzer::{resolve_hir_path, SourceAnalyzer},
Access, BindingMode, BuiltinAttr, Callable, ConstParam, Crate, DeriveHelper, Field, Function, Access, Adjust, AutoBorrow, BindingMode, BuiltinAttr, Callable, ConstParam, Crate,
HasSource, HirFileId, Impl, InFile, Label, LifetimeParam, Local, Macro, Module, ModuleDef, DeriveHelper, Field, Function, HasSource, HirFileId, Impl, InFile, Label, LifetimeParam, Local,
Name, Path, ScopeDef, ToolModule, Trait, Type, TypeAlias, TypeParam, VariantDef, Macro, Module, ModuleDef, Name, OverloadedDeref, Path, ScopeDef, ToolModule, Trait, Type,
TypeAlias, TypeParam, VariantDef,
}; };
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
@ -333,9 +334,8 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
self.imp.resolve_trait(trait_) self.imp.resolve_trait(trait_)
} }
// FIXME: Figure out a nice interface to inspect adjustments pub fn expr_adjustments(&self, expr: &ast::Expr) -> Option<Vec<Adjust>> {
pub fn is_implicit_reborrow(&self, expr: &ast::Expr) -> Option<Mutability> { self.imp.expr_adjustments(expr)
self.imp.is_implicit_reborrow(expr)
} }
pub fn type_of_expr(&self, expr: &ast::Expr) -> Option<TypeInfo> { pub fn type_of_expr(&self, expr: &ast::Expr) -> Option<TypeInfo> {
@ -1067,8 +1067,29 @@ impl<'db> SemanticsImpl<'db> {
} }
} }
fn is_implicit_reborrow(&self, expr: &ast::Expr) -> Option<Mutability> { fn expr_adjustments(&self, expr: &ast::Expr) -> Option<Vec<Adjust>> {
self.analyze(expr.syntax())?.is_implicit_reborrow(self.db, expr) let mutability = |m| match m {
hir_ty::Mutability::Not => Mutability::Shared,
hir_ty::Mutability::Mut => Mutability::Mut,
};
self.analyze(expr.syntax())?.expr_adjustments(self.db, expr).map(|it| {
it.iter()
.map(|adjust| match adjust.kind {
hir_ty::Adjust::NeverToAny => Adjust::NeverToAny,
hir_ty::Adjust::Deref(Some(hir_ty::OverloadedDeref(m))) => {
Adjust::Deref(Some(OverloadedDeref(mutability(m))))
}
hir_ty::Adjust::Deref(None) => Adjust::Deref(None),
hir_ty::Adjust::Borrow(hir_ty::AutoBorrow::RawPtr(m)) => {
Adjust::Borrow(AutoBorrow::RawPtr(mutability(m)))
}
hir_ty::Adjust::Borrow(hir_ty::AutoBorrow::Ref(m)) => {
Adjust::Borrow(AutoBorrow::Ref(mutability(m)))
}
hir_ty::Adjust::Pointer(pc) => Adjust::Pointer(pc),
})
.collect()
})
} }
fn type_of_expr(&self, expr: &ast::Expr) -> Option<TypeInfo> { fn type_of_expr(&self, expr: &ast::Expr) -> Option<TypeInfo> {

View file

@ -38,8 +38,7 @@ use hir_ty::{
UnsafeExpr, UnsafeExpr,
}, },
method_resolution::{self, lang_names_for_bin_op}, method_resolution::{self, lang_names_for_bin_op},
Adjust, Adjustment, AutoBorrow, InferenceResult, Interner, Substitution, Ty, TyExt, TyKind, Adjustment, InferenceResult, Interner, Substitution, Ty, TyExt, TyKind, TyLoweringContext,
TyLoweringContext,
}; };
use itertools::Itertools; use itertools::Itertools;
use smallvec::SmallVec; use smallvec::SmallVec;
@ -156,21 +155,14 @@ impl SourceAnalyzer {
Some(res) Some(res)
} }
pub(crate) fn is_implicit_reborrow( pub(crate) fn expr_adjustments(
&self, &self,
db: &dyn HirDatabase, db: &dyn HirDatabase,
expr: &ast::Expr, expr: &ast::Expr,
) -> Option<Mutability> { ) -> Option<&[Adjustment]> {
let expr_id = self.expr_id(db, expr)?; let expr_id = self.expr_id(db, expr)?;
let infer = self.infer.as_ref()?; let infer = self.infer.as_ref()?;
let adjustments = infer.expr_adjustments.get(&expr_id)?; infer.expr_adjustments.get(&expr_id).map(|v| &**v)
adjustments.windows(2).find_map(|slice| match slice {
&[Adjustment {kind: Adjust::Deref(None), ..}, Adjustment {kind: Adjust::Borrow(AutoBorrow::Ref(m)), ..}] => Some(match m {
hir_ty::Mutability::Mut => Mutability::Mut,
hir_ty::Mutability::Not => Mutability::Shared,
}),
_ => None,
})
} }
pub(crate) fn type_of_expr( pub(crate) fn type_of_expr(

View file

@ -4,7 +4,7 @@ version = "0.0.0"
description = "TBD" description = "TBD"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
rust-version = "1.57" rust-version = "1.65"
[lib] [lib]
doctest = false doctest = false

View file

@ -196,6 +196,7 @@ trait Foo {
type Output; type Output;
const CONST: usize = 42; const CONST: usize = 42;
const CONST_2: i32;
fn foo(&self); fn foo(&self);
fn bar(&self); fn bar(&self);
@ -213,6 +214,7 @@ trait Foo {
type Output; type Output;
const CONST: usize = 42; const CONST: usize = 42;
const CONST_2: i32;
fn foo(&self); fn foo(&self);
fn bar(&self); fn bar(&self);
@ -226,7 +228,7 @@ impl Foo for S {
$0type Output; $0type Output;
const CONST: usize = 42; const CONST_2: i32;
fn foo(&self) { fn foo(&self) {
todo!() todo!()
@ -379,14 +381,14 @@ impl Foo for S {
r#" r#"
mod foo { mod foo {
pub struct Bar; pub struct Bar;
trait Foo { fn foo(&self, bar: Bar); } pub trait Foo { fn foo(&self, bar: Bar); }
} }
struct S; struct S;
impl foo::Foo for S { $0 }"#, impl foo::Foo for S { $0 }"#,
r#" r#"
mod foo { mod foo {
pub struct Bar; pub struct Bar;
trait Foo { fn foo(&self, bar: Bar); } pub trait Foo { fn foo(&self, bar: Bar); }
} }
struct S; struct S;
impl foo::Foo for S { impl foo::Foo for S {
@ -439,14 +441,14 @@ impl bar::Foo for S {
r#" r#"
mod foo { mod foo {
pub struct Bar<T>; pub struct Bar<T>;
trait Foo { fn foo(&self, bar: Bar<u32>); } pub trait Foo { fn foo(&self, bar: Bar<u32>); }
} }
struct S; struct S;
impl foo::Foo for S { $0 }"#, impl foo::Foo for S { $0 }"#,
r#" r#"
mod foo { mod foo {
pub struct Bar<T>; pub struct Bar<T>;
trait Foo { fn foo(&self, bar: Bar<u32>); } pub trait Foo { fn foo(&self, bar: Bar<u32>); }
} }
struct S; struct S;
impl foo::Foo for S { impl foo::Foo for S {
@ -464,14 +466,14 @@ impl foo::Foo for S {
r#" r#"
mod foo { mod foo {
pub struct Bar<T>; pub struct Bar<T>;
trait Foo<T> { fn foo(&self, bar: Bar<T>); } pub trait Foo<T> { fn foo(&self, bar: Bar<T>); }
} }
struct S; struct S;
impl foo::Foo<u32> for S { $0 }"#, impl foo::Foo<u32> for S { $0 }"#,
r#" r#"
mod foo { mod foo {
pub struct Bar<T>; pub struct Bar<T>;
trait Foo<T> { fn foo(&self, bar: Bar<T>); } pub trait Foo<T> { fn foo(&self, bar: Bar<T>); }
} }
struct S; struct S;
impl foo::Foo<u32> for S { impl foo::Foo<u32> for S {
@ -489,7 +491,7 @@ impl foo::Foo<u32> for S {
add_missing_impl_members, add_missing_impl_members,
r#" r#"
mod foo { mod foo {
trait Foo<T> { fn foo(&self, bar: T); } pub trait Foo<T> { fn foo(&self, bar: T); }
pub struct Param; pub struct Param;
} }
struct Param; struct Param;
@ -497,7 +499,7 @@ struct S;
impl foo::Foo<Param> for S { $0 }"#, impl foo::Foo<Param> for S { $0 }"#,
r#" r#"
mod foo { mod foo {
trait Foo<T> { fn foo(&self, bar: T); } pub trait Foo<T> { fn foo(&self, bar: T); }
pub struct Param; pub struct Param;
} }
struct Param; struct Param;
@ -518,7 +520,7 @@ impl foo::Foo<Param> for S {
mod foo { mod foo {
pub struct Bar<T>; pub struct Bar<T>;
impl Bar<T> { type Assoc = u32; } impl Bar<T> { type Assoc = u32; }
trait Foo { fn foo(&self, bar: Bar<u32>::Assoc); } pub trait Foo { fn foo(&self, bar: Bar<u32>::Assoc); }
} }
struct S; struct S;
impl foo::Foo for S { $0 }"#, impl foo::Foo for S { $0 }"#,
@ -526,7 +528,7 @@ impl foo::Foo for S { $0 }"#,
mod foo { mod foo {
pub struct Bar<T>; pub struct Bar<T>;
impl Bar<T> { type Assoc = u32; } impl Bar<T> { type Assoc = u32; }
trait Foo { fn foo(&self, bar: Bar<u32>::Assoc); } pub trait Foo { fn foo(&self, bar: Bar<u32>::Assoc); }
} }
struct S; struct S;
impl foo::Foo for S { impl foo::Foo for S {
@ -545,7 +547,7 @@ impl foo::Foo for S {
mod foo { mod foo {
pub struct Bar<T>; pub struct Bar<T>;
pub struct Baz; pub struct Baz;
trait Foo { fn foo(&self, bar: Bar<Baz>); } pub trait Foo { fn foo(&self, bar: Bar<Baz>); }
} }
struct S; struct S;
impl foo::Foo for S { $0 }"#, impl foo::Foo for S { $0 }"#,
@ -553,7 +555,7 @@ impl foo::Foo for S { $0 }"#,
mod foo { mod foo {
pub struct Bar<T>; pub struct Bar<T>;
pub struct Baz; pub struct Baz;
trait Foo { fn foo(&self, bar: Bar<Baz>); } pub trait Foo { fn foo(&self, bar: Bar<Baz>); }
} }
struct S; struct S;
impl foo::Foo for S { impl foo::Foo for S {
@ -571,14 +573,14 @@ impl foo::Foo for S {
r#" r#"
mod foo { mod foo {
pub trait Fn<Args> { type Output; } pub trait Fn<Args> { type Output; }
trait Foo { fn foo(&self, bar: dyn Fn(u32) -> i32); } pub trait Foo { fn foo(&self, bar: dyn Fn(u32) -> i32); }
} }
struct S; struct S;
impl foo::Foo for S { $0 }"#, impl foo::Foo for S { $0 }"#,
r#" r#"
mod foo { mod foo {
pub trait Fn<Args> { type Output; } pub trait Fn<Args> { type Output; }
trait Foo { fn foo(&self, bar: dyn Fn(u32) -> i32); } pub trait Foo { fn foo(&self, bar: dyn Fn(u32) -> i32); }
} }
struct S; struct S;
impl foo::Foo for S { impl foo::Foo for S {
@ -658,6 +660,7 @@ trait Foo {
type Output; type Output;
const CONST: usize = 42; const CONST: usize = 42;
const CONST_2: i32;
fn valid(some: u32) -> bool { false } fn valid(some: u32) -> bool { false }
fn foo(some: u32) -> bool; fn foo(some: u32) -> bool;
@ -669,13 +672,16 @@ trait Foo {
type Output; type Output;
const CONST: usize = 42; const CONST: usize = 42;
const CONST_2: i32;
fn valid(some: u32) -> bool { false } fn valid(some: u32) -> bool { false }
fn foo(some: u32) -> bool; fn foo(some: u32) -> bool;
} }
struct S; struct S;
impl Foo for S { impl Foo for S {
$0fn valid(some: u32) -> bool { false } $0const CONST: usize = 42;
fn valid(some: u32) -> bool { false }
}"#, }"#,
) )
} }

View file

@ -109,8 +109,6 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
let params = let params =
body.extracted_function_params(ctx, &container_info, locals_used.iter().copied()); body.extracted_function_params(ctx, &container_info, locals_used.iter().copied());
let extracted_from_trait_impl = body.extracted_from_trait_impl();
let name = make_function_name(&semantics_scope); let name = make_function_name(&semantics_scope);
let fun = Function { let fun = Function {
@ -129,8 +127,11 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
builder.replace(target_range, make_call(ctx, &fun, old_indent)); builder.replace(target_range, make_call(ctx, &fun, old_indent));
let has_impl_wrapper =
insert_after.ancestors().any(|a| a.kind() == SyntaxKind::IMPL && a != insert_after);
let fn_def = match fun.self_param_adt(ctx) { let fn_def = match fun.self_param_adt(ctx) {
Some(adt) if extracted_from_trait_impl => { Some(adt) if anchor == Anchor::Method && !has_impl_wrapper => {
let fn_def = format_function(ctx, module, &fun, old_indent, new_indent + 1); let fn_def = format_function(ctx, module, &fun, old_indent, new_indent + 1);
generate_impl_text(&adt, &fn_def).replace("{\n\n", "{") generate_impl_text(&adt, &fn_def).replace("{\n\n", "{")
} }
@ -272,7 +273,7 @@ enum FunType {
} }
/// Where to put extracted function definition /// Where to put extracted function definition
#[derive(Debug)] #[derive(Debug, Eq, PartialEq, Clone, Copy)]
enum Anchor { enum Anchor {
/// Extract free function and put right after current top-level function /// Extract free function and put right after current top-level function
Freestanding, Freestanding,
@ -1245,6 +1246,14 @@ fn node_to_insert_after(body: &FunctionBody, anchor: Anchor) -> Option<SyntaxNod
while let Some(next_ancestor) = ancestors.next() { while let Some(next_ancestor) = ancestors.next() {
match next_ancestor.kind() { match next_ancestor.kind() {
SyntaxKind::SOURCE_FILE => break, SyntaxKind::SOURCE_FILE => break,
SyntaxKind::IMPL => {
if body.extracted_from_trait_impl() && matches!(anchor, Anchor::Method) {
let impl_node = find_non_trait_impl(&next_ancestor);
if let target_node @ Some(_) = impl_node.as_ref().and_then(last_impl_member) {
return target_node;
}
}
}
SyntaxKind::ITEM_LIST if !matches!(anchor, Anchor::Freestanding) => continue, SyntaxKind::ITEM_LIST if !matches!(anchor, Anchor::Freestanding) => continue,
SyntaxKind::ITEM_LIST => { SyntaxKind::ITEM_LIST => {
if ancestors.peek().map(SyntaxNode::kind) == Some(SyntaxKind::MODULE) { if ancestors.peek().map(SyntaxNode::kind) == Some(SyntaxKind::MODULE) {
@ -1265,6 +1274,29 @@ fn node_to_insert_after(body: &FunctionBody, anchor: Anchor) -> Option<SyntaxNod
last_ancestor last_ancestor
} }
fn find_non_trait_impl(trait_impl: &SyntaxNode) -> Option<ast::Impl> {
let as_impl = ast::Impl::cast(trait_impl.clone())?;
let impl_type = Some(impl_type_name(&as_impl)?);
let sibblings = trait_impl.parent()?.children();
sibblings
.filter_map(ast::Impl::cast)
.find(|s| impl_type_name(s) == impl_type && !is_trait_impl(s))
}
fn last_impl_member(impl_node: &ast::Impl) -> Option<SyntaxNode> {
let last_child = impl_node.assoc_item_list()?.assoc_items().last()?;
Some(last_child.syntax().clone())
}
fn is_trait_impl(node: &ast::Impl) -> bool {
node.trait_().is_some()
}
fn impl_type_name(impl_node: &ast::Impl) -> Option<String> {
Some(impl_node.self_ty()?.to_string())
}
fn make_call(ctx: &AssistContext<'_>, fun: &Function, indent: IndentLevel) -> String { fn make_call(ctx: &AssistContext<'_>, fun: &Function, indent: IndentLevel) -> String {
let ret_ty = fun.return_type(ctx); let ret_ty = fun.return_type(ctx);
@ -5051,6 +5083,236 @@ impl Struct {
); );
} }
#[test]
fn extract_method_from_trait_with_existing_non_empty_impl_block() {
check_assist(
extract_function,
r#"
struct Struct(i32);
trait Trait {
fn bar(&self) -> i32;
}
impl Struct {
fn foo() {}
}
impl Trait for Struct {
fn bar(&self) -> i32 {
$0self.0 + 2$0
}
}
"#,
r#"
struct Struct(i32);
trait Trait {
fn bar(&self) -> i32;
}
impl Struct {
fn foo() {}
fn $0fun_name(&self) -> i32 {
self.0 + 2
}
}
impl Trait for Struct {
fn bar(&self) -> i32 {
self.fun_name()
}
}
"#,
)
}
#[test]
fn extract_function_from_trait_with_existing_non_empty_impl_block() {
check_assist(
extract_function,
r#"
struct Struct(i32);
trait Trait {
fn bar(&self) -> i32;
}
impl Struct {
fn foo() {}
}
impl Trait for Struct {
fn bar(&self) -> i32 {
let three_squared = $03 * 3$0;
self.0 + three_squared
}
}
"#,
r#"
struct Struct(i32);
trait Trait {
fn bar(&self) -> i32;
}
impl Struct {
fn foo() {}
}
impl Trait for Struct {
fn bar(&self) -> i32 {
let three_squared = fun_name();
self.0 + three_squared
}
}
fn $0fun_name() -> i32 {
3 * 3
}
"#,
)
}
#[test]
fn extract_method_from_trait_with_multiple_existing_impl_blocks() {
check_assist(
extract_function,
r#"
struct Struct(i32);
struct StructBefore(i32);
struct StructAfter(i32);
trait Trait {
fn bar(&self) -> i32;
}
impl StructBefore {
fn foo(){}
}
impl Struct {
fn foo(){}
}
impl StructAfter {
fn foo(){}
}
impl Trait for Struct {
fn bar(&self) -> i32 {
$0self.0 + 2$0
}
}
"#,
r#"
struct Struct(i32);
struct StructBefore(i32);
struct StructAfter(i32);
trait Trait {
fn bar(&self) -> i32;
}
impl StructBefore {
fn foo(){}
}
impl Struct {
fn foo(){}
fn $0fun_name(&self) -> i32 {
self.0 + 2
}
}
impl StructAfter {
fn foo(){}
}
impl Trait for Struct {
fn bar(&self) -> i32 {
self.fun_name()
}
}
"#,
)
}
#[test]
fn extract_method_from_trait_with_multiple_existing_trait_impl_blocks() {
check_assist(
extract_function,
r#"
struct Struct(i32);
trait Trait {
fn bar(&self) -> i32;
}
trait TraitBefore {
fn before(&self) -> i32;
}
trait TraitAfter {
fn after(&self) -> i32;
}
impl TraitBefore for Struct {
fn before(&self) -> i32 {
42
}
}
impl Struct {
fn foo(){}
}
impl TraitAfter for Struct {
fn after(&self) -> i32 {
42
}
}
impl Trait for Struct {
fn bar(&self) -> i32 {
$0self.0 + 2$0
}
}
"#,
r#"
struct Struct(i32);
trait Trait {
fn bar(&self) -> i32;
}
trait TraitBefore {
fn before(&self) -> i32;
}
trait TraitAfter {
fn after(&self) -> i32;
}
impl TraitBefore for Struct {
fn before(&self) -> i32 {
42
}
}
impl Struct {
fn foo(){}
fn $0fun_name(&self) -> i32 {
self.0 + 2
}
}
impl TraitAfter for Struct {
fn after(&self) -> i32 {
42
}
}
impl Trait for Struct {
fn bar(&self) -> i32 {
self.fun_name()
}
}
"#,
)
}
#[test] #[test]
fn closure_arguments() { fn closure_arguments() {
check_assist( check_assist(

View file

@ -1,4 +1,4 @@
use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution}; use hir::{db::HirDatabase, HasSource, HasVisibility, ModuleDef, PathResolution, ScopeDef};
use ide_db::base_db::FileId; use ide_db::base_db::FileId;
use syntax::{ use syntax::{
ast::{self, HasVisibility as _}, ast::{self, HasVisibility as _},
@ -18,7 +18,7 @@ use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
// fn frobnicate() {} // fn frobnicate() {}
// } // }
// fn main() { // fn main() {
// m::frobnicate$0() {} // m::frobnicate$0();
// } // }
// ``` // ```
// -> // ->
@ -27,7 +27,7 @@ use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
// $0pub(crate) fn frobnicate() {} // $0pub(crate) fn frobnicate() {}
// } // }
// fn main() { // fn main() {
// m::frobnicate() {} // m::frobnicate();
// } // }
// ``` // ```
pub(crate) fn fix_visibility(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { pub(crate) fn fix_visibility(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
@ -37,11 +37,15 @@ pub(crate) fn fix_visibility(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
let path: ast::Path = ctx.find_node_at_offset()?; let path: ast::Path = ctx.find_node_at_offset()?;
let path_res = ctx.sema.resolve_path(&path)?; let qualifier = path.qualifier()?;
let def = match path_res { let name_ref = path.segment()?.name_ref()?;
PathResolution::Def(def) => def, let qualifier_res = ctx.sema.resolve_path(&qualifier)?;
_ => return None, let PathResolution::Def(ModuleDef::Module(module)) = qualifier_res else { return None; };
}; let (_, def) = module
.scope(ctx.db(), None)
.into_iter()
.find(|(name, _)| name.to_smol_str() == name_ref.text().as_str())?;
let ScopeDef::ModuleDef(def) = def else { return None; };
let current_module = ctx.sema.scope(path.syntax())?.module(); let current_module = ctx.sema.scope(path.syntax())?.module();
let target_module = def.module(ctx.db())?; let target_module = def.module(ctx.db())?;

View file

@ -261,12 +261,12 @@ fn main() {
} }
//- /foo.rs //- /foo.rs
enum Foo { pub enum Foo {
Bar, Bar,
} }
", ",
r" r"
enum Foo { pub enum Foo {
Bar, Bar,
Baz, Baz,
} }
@ -310,7 +310,7 @@ fn main() {
generate_enum_variant, generate_enum_variant,
r" r"
mod m { mod m {
enum Foo { pub enum Foo {
Bar, Bar,
} }
} }
@ -320,7 +320,7 @@ fn main() {
", ",
r" r"
mod m { mod m {
enum Foo { pub enum Foo {
Bar, Bar,
Baz, Baz,
} }
@ -516,10 +516,10 @@ mod foo;
use foo::Foo::Bar$0; use foo::Foo::Bar$0;
//- /foo.rs //- /foo.rs
enum Foo {} pub enum Foo {}
", ",
r" r"
enum Foo { pub enum Foo {
Bar, Bar,
} }
", ",

View file

@ -1324,7 +1324,7 @@ fn foo() {
generate_function, generate_function,
r" r"
mod bar { mod bar {
mod baz {} pub mod baz {}
} }
fn foo() { fn foo() {
@ -1333,7 +1333,7 @@ fn foo() {
", ",
r" r"
mod bar { mod bar {
mod baz { pub mod baz {
pub(crate) fn my_fn() { pub(crate) fn my_fn() {
${0:todo!()} ${0:todo!()}
} }

View file

@ -92,7 +92,7 @@ pub(crate) fn move_format_string_arg(acc: &mut Assists, ctx: &AssistContext<'_>)
NodeOrToken::Node(n) => { NodeOrToken::Node(n) => {
format_to!(current_arg, "{n}"); format_to!(current_arg, "{n}");
}, },
NodeOrToken::Token(t) if t.kind() == COMMA=> { NodeOrToken::Token(t) if t.kind() == COMMA => {
existing_args.push(current_arg.trim().into()); existing_args.push(current_arg.trim().into());
current_arg.clear(); current_arg.clear();
}, },
@ -238,14 +238,14 @@ fn main() {
&add_macro_decl( &add_macro_decl(
r#" r#"
fn main() { fn main() {
print!("{} {x + 1:b} {Struct(1, 2)}$0", 1); print!("{:b} {x + 1:b} {Struct(1, 2)}$0", 1);
} }
"#, "#,
), ),
&add_macro_decl( &add_macro_decl(
r#" r#"
fn main() { fn main() {
print!("{} {:b} {}"$0, 1, x + 1, Struct(1, 2)); print!("{:b} {:b} {}"$0, 1, x + 1, Struct(1, 2));
} }
"#, "#,
), ),

View file

@ -1,7 +1,7 @@
use itertools::Itertools; use itertools::Itertools;
use syntax::{ use syntax::{
ast::{self, AstNode, AstToken}, ast::{self, AstNode, AstToken},
match_ast, NodeOrToken, SyntaxElement, TextSize, T, match_ast, NodeOrToken, SyntaxElement, TextRange, TextSize, T,
}; };
use crate::{AssistContext, AssistId, AssistKind, Assists}; use crate::{AssistContext, AssistId, AssistKind, Assists};
@ -22,7 +22,36 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
// } // }
// ``` // ```
pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?; let macro_calls = if ctx.has_empty_selection() {
vec![ctx.find_node_at_offset::<ast::MacroCall>()?]
} else {
ctx.covering_element()
.as_node()?
.descendants()
.filter(|node| ctx.selection_trimmed().contains_range(node.text_range()))
.filter_map(ast::MacroCall::cast)
.collect()
};
let replacements =
macro_calls.into_iter().filter_map(compute_dbg_replacement).collect::<Vec<_>>();
if replacements.is_empty() {
return None;
}
acc.add(
AssistId("remove_dbg", AssistKind::Refactor),
"Remove dbg!()",
ctx.selection_trimmed(),
|builder| {
for (range, text) in replacements {
builder.replace(range, text);
}
},
)
}
fn compute_dbg_replacement(macro_call: ast::MacroCall) -> Option<(TextRange, String)> {
let tt = macro_call.token_tree()?; let tt = macro_call.token_tree()?;
let r_delim = NodeOrToken::Token(tt.right_delimiter_token()?); let r_delim = NodeOrToken::Token(tt.right_delimiter_token()?);
if macro_call.path()?.segment()?.name_ref()?.text() != "dbg" if macro_call.path()?.segment()?.name_ref()?.text() != "dbg"
@ -41,7 +70,7 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<(
let macro_expr = ast::MacroExpr::cast(macro_call.syntax().parent()?)?; let macro_expr = ast::MacroExpr::cast(macro_call.syntax().parent()?)?;
let parent = macro_expr.syntax().parent()?; let parent = macro_expr.syntax().parent()?;
let (range, text) = match &*input_expressions { Some(match &*input_expressions {
// dbg!() // dbg!()
[] => { [] => {
match_ast! { match_ast! {
@ -107,10 +136,6 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<(
} }
// dbg!(expr0, expr1, ...) // dbg!(expr0, expr1, ...)
exprs => (macro_call.syntax().text_range(), format!("({})", exprs.iter().format(", "))), exprs => (macro_call.syntax().text_range(), format!("({})", exprs.iter().format(", "))),
};
acc.add(AssistId("remove_dbg", AssistKind::Refactor), "Remove dbg!()", range, |builder| {
builder.replace(range, text);
}) })
} }
@ -238,4 +263,28 @@ fn foo() {
check(r#"$0dbg!(0, 1)"#, r#"(0, 1)"#); check(r#"$0dbg!(0, 1)"#, r#"(0, 1)"#);
check(r#"$0dbg!(0, (1, 2))"#, r#"(0, (1, 2))"#); check(r#"$0dbg!(0, (1, 2))"#, r#"(0, (1, 2))"#);
} }
#[test]
fn test_range() {
check(
r#"
fn f() {
dbg!(0) + $0dbg!(1);
dbg!(())$0
}
"#,
r#"
fn f() {
dbg!(0) + 1;
()
}
"#,
);
}
#[test]
fn test_range_partial() {
check_assist_not_applicable(remove_dbg, r#"$0dbg$0!(0)"#);
check_assist_not_applicable(remove_dbg, r#"$0dbg!(0$0)"#);
}
} }

View file

@ -1019,8 +1019,6 @@ struct Foo {
impl foo::Bar for Foo { impl foo::Bar for Foo {
$0type Qux; $0type Qux;
const Baz: usize = 42;
const Fez: usize; const Fez: usize;
fn foo() { fn foo() {

View file

@ -741,7 +741,7 @@ mod m {
fn frobnicate() {} fn frobnicate() {}
} }
fn main() { fn main() {
m::frobnicate$0() {} m::frobnicate$0();
} }
"#####, "#####,
r#####" r#####"
@ -749,7 +749,7 @@ mod m {
$0pub(crate) fn frobnicate() {} $0pub(crate) fn frobnicate() {}
} }
fn main() { fn main() {
m::frobnicate() {} m::frobnicate();
} }
"#####, "#####,
) )

View file

@ -119,6 +119,10 @@ pub fn filter_assoc_items(
(default_methods, def.body()), (default_methods, def.body()),
(DefaultMethods::Only, Some(_)) | (DefaultMethods::No, None) (DefaultMethods::Only, Some(_)) | (DefaultMethods::No, None)
), ),
ast::AssocItem::Const(def) => matches!(
(default_methods, def.body()),
(DefaultMethods::Only, Some(_)) | (DefaultMethods::No, None)
),
_ => default_methods == DefaultMethods::No, _ => default_methods == DefaultMethods::No,
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()

View file

@ -4,7 +4,7 @@ version = "0.0.0"
description = "TBD" description = "TBD"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
rust-version = "1.57" rust-version = "1.65"
[lib] [lib]
doctest = false doctest = false

View file

@ -157,7 +157,7 @@ fn complete_trait_impl(
add_function_impl(acc, ctx, replacement_range, func, hir_impl) add_function_impl(acc, ctx, replacement_range, func, hir_impl)
} }
(hir::AssocItem::TypeAlias(type_alias), All | TypeAlias) => { (hir::AssocItem::TypeAlias(type_alias), All | TypeAlias) => {
add_type_alias_impl(acc, ctx, replacement_range, type_alias) add_type_alias_impl(acc, ctx, replacement_range, type_alias, hir_impl)
} }
(hir::AssocItem::Const(const_), All | Const) => { (hir::AssocItem::Const(const_), All | Const) => {
add_const_impl(acc, ctx, replacement_range, const_, hir_impl) add_const_impl(acc, ctx, replacement_range, const_, hir_impl)
@ -236,9 +236,7 @@ fn get_transformed_assoc_item(
); );
transform.apply(assoc_item.syntax()); transform.apply(assoc_item.syntax());
if let ast::AssocItem::Fn(func) = &assoc_item { assoc_item.remove_attrs_and_docs();
func.remove_attrs_and_docs();
}
Some(assoc_item) Some(assoc_item)
} }
@ -247,24 +245,50 @@ fn add_type_alias_impl(
ctx: &CompletionContext<'_>, ctx: &CompletionContext<'_>,
replacement_range: TextRange, replacement_range: TextRange,
type_alias: hir::TypeAlias, type_alias: hir::TypeAlias,
impl_def: hir::Impl,
) { ) {
let alias_name = type_alias.name(ctx.db); let alias_name = type_alias.name(ctx.db).unescaped().to_smol_str();
let (alias_name, escaped_name) =
(alias_name.unescaped().to_smol_str(), alias_name.to_smol_str());
let label = format!("type {} =", alias_name); let label = format!("type {} =", alias_name);
let replacement = format!("type {} = ", escaped_name);
let mut item = CompletionItem::new(SymbolKind::TypeAlias, replacement_range, label); let mut item = CompletionItem::new(SymbolKind::TypeAlias, replacement_range, label);
item.lookup_by(format!("type {}", alias_name)) item.lookup_by(format!("type {}", alias_name))
.set_documentation(type_alias.docs(ctx.db)) .set_documentation(type_alias.docs(ctx.db))
.set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() }); .set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() });
match ctx.config.snippet_cap {
Some(cap) => item if let Some(source) = ctx.sema.source(type_alias) {
.snippet_edit(cap, TextEdit::replace(replacement_range, format!("{}$0;", replacement))), let assoc_item = ast::AssocItem::TypeAlias(source.value);
None => item.text_edit(TextEdit::replace(replacement_range, replacement)), if let Some(transformed_item) = get_transformed_assoc_item(ctx, assoc_item, impl_def) {
}; let transformed_ty = match transformed_item {
item.add_to(acc); ast::AssocItem::TypeAlias(ty) => ty,
_ => unreachable!(),
};
let start = transformed_ty.syntax().text_range().start();
let Some(end) = transformed_ty
.eq_token()
.map(|tok| tok.text_range().start())
.or(transformed_ty.semicolon_token().map(|tok| tok.text_range().start())) else { return };
let len = end - start;
let mut decl = transformed_ty.syntax().text().slice(..len).to_string();
if !decl.ends_with(' ') {
decl.push(' ');
}
decl.push_str("= ");
match ctx.config.snippet_cap {
Some(cap) => {
let snippet = format!("{}$0;", decl);
item.snippet_edit(cap, TextEdit::replace(replacement_range, snippet));
}
None => {
item.text_edit(TextEdit::replace(replacement_range, decl));
}
};
item.add_to(acc);
}
}
} }
fn add_const_impl( fn add_const_impl(
@ -309,7 +333,6 @@ fn add_const_impl(
} }
fn make_const_compl_syntax(const_: &ast::Const, needs_whitespace: bool) -> String { fn make_const_compl_syntax(const_: &ast::Const, needs_whitespace: bool) -> String {
const_.remove_attrs_and_docs();
let const_ = if needs_whitespace { let const_ = if needs_whitespace {
insert_whitespace_into_node::insert_ws_into(const_.syntax().clone()) insert_whitespace_into_node::insert_ws_into(const_.syntax().clone())
} else { } else {
@ -333,8 +356,6 @@ fn make_const_compl_syntax(const_: &ast::Const, needs_whitespace: bool) -> Strin
} }
fn function_declaration(node: &ast::Fn, needs_whitespace: bool) -> String { fn function_declaration(node: &ast::Fn, needs_whitespace: bool) -> String {
node.remove_attrs_and_docs();
let node = if needs_whitespace { let node = if needs_whitespace {
insert_whitespace_into_node::insert_ws_into(node.syntax().clone()) insert_whitespace_into_node::insert_ws_into(node.syntax().clone())
} else { } else {
@ -350,9 +371,7 @@ fn function_declaration(node: &ast::Fn, needs_whitespace: bool) -> String {
.map_or(end, |f| f.text_range().start()); .map_or(end, |f| f.text_range().start());
let len = end - start; let len = end - start;
let range = TextRange::new(0.into(), len); let syntax = node.text().slice(..len).to_string();
let syntax = node.text().slice(range).to_string();
syntax.trim_end().to_owned() syntax.trim_end().to_owned()
} }
@ -1160,6 +1179,106 @@ impl Foo for Test {
$0 $0
} }
} }
"#,
);
}
#[test]
fn includes_gat_generics() {
check_edit(
"type Ty",
r#"
trait Tr<'b> {
type Ty<'a: 'b, T: Copy, const C: usize>;
}
impl<'b> Tr<'b> for () {
$0
}
"#,
r#"
trait Tr<'b> {
type Ty<'a: 'b, T: Copy, const C: usize>;
}
impl<'b> Tr<'b> for () {
type Ty<'a: 'b, T: Copy, const C: usize> = $0;
}
"#,
);
}
#[test]
fn strips_comments() {
check_edit(
"fn func",
r#"
trait Tr {
/// docs
#[attr]
fn func();
}
impl Tr for () {
$0
}
"#,
r#"
trait Tr {
/// docs
#[attr]
fn func();
}
impl Tr for () {
fn func() {
$0
}
}
"#,
);
check_edit(
"const C",
r#"
trait Tr {
/// docs
#[attr]
const C: usize;
}
impl Tr for () {
$0
}
"#,
r#"
trait Tr {
/// docs
#[attr]
const C: usize;
}
impl Tr for () {
const C: usize = $0;
}
"#,
);
check_edit(
"type Item",
r#"
trait Tr {
/// docs
#[attr]
type Item;
}
impl Tr for () {
$0
}
"#,
r#"
trait Tr {
/// docs
#[attr]
type Item;
}
impl Tr for () {
type Item = $0;
}
"#, "#,
); );
} }

View file

@ -681,9 +681,13 @@ fn classify_name_ref(
ast::Item::ExternBlock(it) => it.extern_item_list().is_none(), ast::Item::ExternBlock(it) => it.extern_item_list().is_none(),
ast::Item::Fn(it) => it.body().is_none(), ast::Item::Fn(it) => it.body().is_none(),
ast::Item::Impl(it) => it.assoc_item_list().is_none(), ast::Item::Impl(it) => it.assoc_item_list().is_none(),
ast::Item::Module(it) => it.item_list().is_none(), ast::Item::Module(it) => {
it.item_list().is_none() && it.semicolon_token().is_none()
}
ast::Item::Static(it) => it.body().is_none(), ast::Item::Static(it) => it.body().is_none(),
ast::Item::Struct(it) => it.field_list().is_none(), ast::Item::Struct(it) => {
it.field_list().is_none() && it.semicolon_token().is_none()
}
ast::Item::Trait(it) => it.assoc_item_list().is_none(), ast::Item::Trait(it) => it.assoc_item_list().is_none(),
ast::Item::TypeAlias(it) => it.ty().is_none(), ast::Item::TypeAlias(it) => it.ty().is_none(),
ast::Item::Union(it) => it.record_field_list().is_none(), ast::Item::Union(it) => it.record_field_list().is_none(),

View file

@ -245,3 +245,35 @@ impl Test for () {
"#]], "#]],
); );
} }
#[test]
fn after_unit_struct() {
check(
r#"struct S; f$0"#,
expect![[r#"
ma makro!() macro_rules! makro
md module
kw const
kw crate::
kw enum
kw extern
kw fn
kw impl
kw mod
kw pub
kw pub(crate)
kw pub(super)
kw self::
kw static
kw struct
kw trait
kw type
kw union
kw unsafe
kw use
sn macro_rules
sn tfn (Test function)
sn tmod (Test module)
"#]],
);
}

View file

@ -4,7 +4,7 @@ version = "0.0.0"
description = "TBD" description = "TBD"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
rust-version = "1.57" rust-version = "1.65"
[lib] [lib]
doctest = false doctest = false

View file

@ -58,8 +58,11 @@ impl LineIndex {
let mut utf16_lines = NoHashHashMap::default(); let mut utf16_lines = NoHashHashMap::default();
let mut utf16_chars = Vec::new(); let mut utf16_chars = Vec::new();
let mut newlines = vec![0.into()]; let mut newlines = Vec::with_capacity(16);
let mut curr_row @ mut curr_col = 0.into(); newlines.push(TextSize::from(0));
let mut curr_row = 0.into();
let mut curr_col = 0.into();
let mut line = 0; let mut line = 0;
for c in text.chars() { for c in text.chars() {
let c_len = TextSize::of(c); let c_len = TextSize::of(c);

View file

@ -104,6 +104,11 @@ pub fn parse_format_exprs(input: &str) -> Result<(String, Vec<Arg>), ()> {
extracted_expressions.push(Arg::Placeholder); extracted_expressions.push(Arg::Placeholder);
state = State::NotArg; state = State::NotArg;
} }
(State::MaybeArg, ':') => {
output.push(chr);
extracted_expressions.push(Arg::Placeholder);
state = State::FormatOpts;
}
(State::MaybeArg, _) => { (State::MaybeArg, _) => {
if matches!(chr, '\\' | '$') { if matches!(chr, '\\' | '$') {
current_expr.push('\\'); current_expr.push('\\');
@ -118,44 +123,41 @@ pub fn parse_format_exprs(input: &str) -> Result<(String, Vec<Arg>), ()> {
state = State::Expr; state = State::Expr;
} }
} }
(State::Ident | State::Expr, '}') => {
if inexpr_open_count == 0 {
output.push(chr);
if matches!(state, State::Expr) {
extracted_expressions.push(Arg::Expr(current_expr.trim().into()));
} else {
extracted_expressions.push(Arg::Ident(current_expr.trim().into()));
}
current_expr = String::new();
state = State::NotArg;
} else {
// We're closing one brace met before inside of the expression.
current_expr.push(chr);
inexpr_open_count -= 1;
}
}
(State::Ident | State::Expr, ':') if matches!(chars.peek(), Some(':')) => { (State::Ident | State::Expr, ':') if matches!(chars.peek(), Some(':')) => {
// path separator // path separator
state = State::Expr; state = State::Expr;
current_expr.push_str("::"); current_expr.push_str("::");
chars.next(); chars.next();
} }
(State::Ident | State::Expr, ':') => { (State::Ident | State::Expr, ':' | '}') => {
if inexpr_open_count == 0 { if inexpr_open_count == 0 {
// We're outside of braces, thus assume that it's a specifier, like "{Some(value):?}" let trimmed = current_expr.trim();
output.push(chr);
if matches!(state, State::Expr) { // if the expression consists of a single number, like "0" or "12", it can refer to
extracted_expressions.push(Arg::Expr(current_expr.trim().into())); // format args in the order they are specified.
// see: https://doc.rust-lang.org/std/fmt/#positional-parameters
if trimmed.chars().fold(true, |only_num, c| c.is_ascii_digit() && only_num) {
output.push_str(trimmed);
} else if matches!(state, State::Expr) {
extracted_expressions.push(Arg::Expr(trimmed.into()));
} else { } else {
extracted_expressions.push(Arg::Ident(current_expr.trim().into())); extracted_expressions.push(Arg::Ident(trimmed.into()));
} }
current_expr = String::new(); output.push(chr);
state = State::FormatOpts; current_expr.clear();
} else { state = if chr == ':' {
State::FormatOpts
} else if chr == '}' {
State::NotArg
} else {
unreachable!()
};
} else if chr == '}' {
// We're closing one brace met before inside of the expression.
current_expr.push(chr);
inexpr_open_count -= 1;
} else if chr == ':' {
// We're inside of braced expression, assume that it's a struct field name/value delimiter. // We're inside of braced expression, assume that it's a struct field name/value delimiter.
current_expr.push(chr); current_expr.push(chr);
} }
@ -219,6 +221,10 @@ mod tests {
("{expr} is {2 + 2}", expect![["{} is {}; expr, 2 + 2"]]), ("{expr} is {2 + 2}", expect![["{} is {}; expr, 2 + 2"]]),
("{expr:?}", expect![["{:?}; expr"]]), ("{expr:?}", expect![["{:?}; expr"]]),
("{expr:1$}", expect![[r"{:1\$}; expr"]]), ("{expr:1$}", expect![[r"{:1\$}; expr"]]),
("{:1$}", expect![[r"{:1\$}; $1"]]),
("{:>padding$}", expect![[r"{:>padding\$}; $1"]]),
("{}, {}, {0}", expect![[r"{}, {}, {0}; $1, $2"]]),
("{}, {}, {0:b}", expect![[r"{}, {}, {0:b}; $1, $2"]]),
("{$0}", expect![[r"{}; \$0"]]), ("{$0}", expect![[r"{}; \$0"]]),
("{malformed", expect![["-"]]), ("{malformed", expect![["-"]]),
("malformed}", expect![["-"]]), ("malformed}", expect![["-"]]),

View file

@ -4,7 +4,7 @@ version = "0.0.0"
description = "TBD" description = "TBD"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
rust-version = "1.57" rust-version = "1.65"
[lib] [lib]
doctest = false doctest = false

View file

@ -5,10 +5,7 @@ use crate::{Diagnostic, DiagnosticsContext};
// This diagnostic is shown for macro expansion errors. // This diagnostic is shown for macro expansion errors.
pub(crate) fn macro_error(ctx: &DiagnosticsContext<'_>, d: &hir::MacroError) -> Diagnostic { pub(crate) fn macro_error(ctx: &DiagnosticsContext<'_>, d: &hir::MacroError) -> Diagnostic {
// Use more accurate position if available. // Use more accurate position if available.
let display_range = d let display_range = ctx.resolve_precise_location(&d.node, d.precise_location);
.precise_location
.unwrap_or_else(|| ctx.sema.diagnostics_display_range(d.node.clone()).range);
Diagnostic::new("macro-error", d.message.clone(), display_range).experimental() Diagnostic::new("macro-error", d.message.clone(), display_range).experimental()
} }

View file

@ -268,12 +268,12 @@ fn main() {
foo::Foo { bar: 3, $0baz: false}; foo::Foo { bar: 3, $0baz: false};
} }
//- /foo.rs //- /foo.rs
struct Foo { pub struct Foo {
bar: i32 bar: i32
} }
"#, "#,
r#" r#"
struct Foo { pub struct Foo {
bar: i32, bar: i32,
pub(crate) baz: bool pub(crate) baz: bool
} }

View file

@ -9,10 +9,7 @@ pub(crate) fn unresolved_macro_call(
d: &hir::UnresolvedMacroCall, d: &hir::UnresolvedMacroCall,
) -> Diagnostic { ) -> Diagnostic {
// Use more accurate position if available. // Use more accurate position if available.
let display_range = d let display_range = ctx.resolve_precise_location(&d.macro_call, d.precise_location);
.precise_location
.unwrap_or_else(|| ctx.sema.diagnostics_display_range(d.macro_call.clone()).range);
let bang = if d.is_bang { "!" } else { "" }; let bang = if d.is_bang { "!" } else { "" };
Diagnostic::new( Diagnostic::new(
"unresolved-macro-call", "unresolved-macro-call",

View file

@ -1,5 +1,4 @@
use hir::db::DefDatabase; use hir::db::DefDatabase;
use syntax::NodeOrToken;
use crate::{Diagnostic, DiagnosticsContext, Severity}; use crate::{Diagnostic, DiagnosticsContext, Severity};
@ -19,16 +18,7 @@ pub(crate) fn unresolved_proc_macro(
proc_attr_macros_enabled: bool, proc_attr_macros_enabled: bool,
) -> Diagnostic { ) -> Diagnostic {
// Use more accurate position if available. // Use more accurate position if available.
let display_range = (|| { let display_range = ctx.resolve_precise_location(&d.node, d.precise_location);
let precise_location = d.precise_location?;
let root = ctx.sema.parse_or_expand(d.node.file_id)?;
match root.covering_element(precise_location) {
NodeOrToken::Node(it) => Some(ctx.sema.original_range(&it)),
NodeOrToken::Token(it) => d.node.with_value(it).original_file_range_opt(ctx.sema.db),
}
})()
.unwrap_or_else(|| ctx.sema.diagnostics_display_range(d.node.clone()))
.range;
let config_enabled = match d.kind { let config_enabled = match d.kind {
hir::MacroKind::Attr => proc_macros_enabled && proc_attr_macros_enabled, hir::MacroKind::Attr => proc_macros_enabled && proc_attr_macros_enabled,

View file

@ -71,9 +71,9 @@ use a;
use a::{c, d::e}; use a::{c, d::e};
mod a { mod a {
mod c {} pub mod c {}
mod d { pub mod d {
mod e {} pub mod e {}
} }
} }
"#, "#,
@ -87,9 +87,9 @@ use a::{
}; };
mod a { mod a {
mod c {} pub mod c {}
mod d { pub mod d {
mod e {} pub mod e {}
} }
} }
"#, "#,
@ -116,11 +116,11 @@ use b;
); );
check_fix( check_fix(
r#" r#"
mod a { mod c {} } mod a { pub mod c {} }
use a::{c$0}; use a::{c$0};
"#, "#,
r#" r#"
mod a { mod c {} } mod a { pub mod c {} }
use a::c; use a::c;
"#, "#,
); );
@ -136,11 +136,11 @@ use a;
); );
check_fix( check_fix(
r#" r#"
mod a { mod c {} mod d { mod e {} } } mod a { pub mod c {} pub mod d { pub mod e {} } }
use a::{c, d::{e$0}}; use a::{c, d::{e$0}};
"#, "#,
r#" r#"
mod a { mod c {} mod d { mod e {} } } mod a { pub mod c {} pub mod d { pub mod e {} } }
use a::{c, d::e}; use a::{c, d::e};
"#, "#,
); );

View file

@ -182,6 +182,28 @@ struct DiagnosticsContext<'a> {
resolve: &'a AssistResolveStrategy, resolve: &'a AssistResolveStrategy,
} }
impl<'a> DiagnosticsContext<'a> {
fn resolve_precise_location(
&self,
node: &InFile<SyntaxNodePtr>,
precise_location: Option<TextRange>,
) -> TextRange {
let sema = &self.sema;
(|| {
let precise_location = precise_location?;
let root = sema.parse_or_expand(node.file_id)?;
match root.covering_element(precise_location) {
syntax::NodeOrToken::Node(it) => Some(sema.original_range(&it)),
syntax::NodeOrToken::Token(it) => {
node.with_value(it).original_file_range_opt(sema.db)
}
}
})()
.unwrap_or_else(|| sema.diagnostics_display_range(node.clone()))
.range
}
}
pub fn diagnostics( pub fn diagnostics(
db: &RootDatabase, db: &RootDatabase,
config: &DiagnosticsConfig, config: &DiagnosticsConfig,

View file

@ -5,7 +5,7 @@ description = "Structural search and replace of Rust code"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
repository = "https://github.com/rust-lang/rust-analyzer" repository = "https://github.com/rust-lang/rust-analyzer"
edition = "2021" edition = "2021"
rust-version = "1.57" rust-version = "1.65"
[lib] [lib]
doctest = false doctest = false

View file

@ -4,7 +4,7 @@ version = "0.0.0"
description = "TBD" description = "TBD"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
rust-version = "1.57" rust-version = "1.65"
[lib] [lib]
doctest = false doctest = false

View file

@ -289,10 +289,10 @@ mod b;
enum E { X(Foo$0) } enum E { X(Foo$0) }
//- /a.rs //- /a.rs
struct Foo; pub struct Foo;
//^^^ //^^^
//- /b.rs //- /b.rs
struct Foo; pub struct Foo;
"#, "#,
); );
} }

View file

@ -119,7 +119,14 @@ pub(crate) fn hover(
}); });
} }
let in_attr = matches!(original_token.parent().and_then(ast::TokenTree::cast), Some(tt) if tt.syntax().ancestors().any(|it| ast::Meta::can_cast(it.kind()))); let in_attr = original_token
.parent_ancestors()
.filter_map(ast::Item::cast)
.any(|item| sema.is_attr_macro_call(&item))
&& !matches!(
original_token.parent().and_then(ast::TokenTree::cast),
Some(tt) if tt.syntax().ancestors().any(|it| ast::Meta::can_cast(it.kind()))
);
// prefer descending the same token kind in attribute expansions, in normal macros text // prefer descending the same token kind in attribute expansions, in normal macros text
// equivalency is more important // equivalency is more important
let descended = if in_attr { let descended = if in_attr {

View file

@ -1,7 +1,10 @@
use std::fmt; use std::fmt;
use either::Either; use either::Either;
use hir::{known, Callable, HasVisibility, HirDisplay, Mutability, Semantics, TypeInfo}; use hir::{
known, Adjust, AutoBorrow, Callable, HasVisibility, HirDisplay, Mutability, OverloadedDeref,
PointerCast, Safety, Semantics, TypeInfo,
};
use ide_db::{ use ide_db::{
base_db::FileRange, famous_defs::FamousDefs, syntax_helpers::node_ext::walk_ty, FxHashMap, base_db::FileRange, famous_defs::FamousDefs, syntax_helpers::node_ext::walk_ty, FxHashMap,
RootDatabase, RootDatabase,
@ -22,7 +25,7 @@ pub struct InlayHintsConfig {
pub type_hints: bool, pub type_hints: bool,
pub parameter_hints: bool, pub parameter_hints: bool,
pub chaining_hints: bool, pub chaining_hints: bool,
pub reborrow_hints: ReborrowHints, pub adjustment_hints: AdjustmentHints,
pub closure_return_type_hints: ClosureReturnTypeHints, pub closure_return_type_hints: ClosureReturnTypeHints,
pub binding_mode_hints: bool, pub binding_mode_hints: bool,
pub lifetime_elision_hints: LifetimeElisionHints, pub lifetime_elision_hints: LifetimeElisionHints,
@ -48,9 +51,9 @@ pub enum LifetimeElisionHints {
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum ReborrowHints { pub enum AdjustmentHints {
Always, Always,
MutableOnly, ReborrowOnly,
Never, Never,
} }
@ -61,7 +64,8 @@ pub enum InlayKind {
ClosingBraceHint, ClosingBraceHint,
ClosureReturnTypeHint, ClosureReturnTypeHint,
GenericParamListHint, GenericParamListHint,
ImplicitReborrowHint, AdjustmentHint,
AdjustmentHintClosingParenthesis,
LifetimeHint, LifetimeHint,
ParameterHint, ParameterHint,
TypeHint, TypeHint,
@ -115,6 +119,12 @@ impl From<String> for InlayHintLabel {
} }
} }
impl From<&str> for InlayHintLabel {
fn from(s: &str) -> Self {
Self { parts: vec![InlayHintLabelPart { text: s.into(), linked_location: None }] }
}
}
impl fmt::Display for InlayHintLabel { impl fmt::Display for InlayHintLabel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.parts.iter().map(|part| &part.text).format("")) write!(f, "{}", self.parts.iter().map(|part| &part.text).format(""))
@ -180,7 +190,7 @@ impl fmt::Debug for InlayHintLabelPart {
pub(crate) fn inlay_hints( pub(crate) fn inlay_hints(
db: &RootDatabase, db: &RootDatabase,
file_id: FileId, file_id: FileId,
range_limit: Option<FileRange>, range_limit: Option<TextRange>,
config: &InlayHintsConfig, config: &InlayHintsConfig,
) -> Vec<InlayHint> { ) -> Vec<InlayHint> {
let _p = profile::span("inlay_hints"); let _p = profile::span("inlay_hints");
@ -195,7 +205,7 @@ pub(crate) fn inlay_hints(
let hints = |node| hints(&mut acc, &famous_defs, config, file_id, node); let hints = |node| hints(&mut acc, &famous_defs, config, file_id, node);
match range_limit { match range_limit {
Some(FileRange { range, .. }) => match file.covering_element(range) { Some(range) => match file.covering_element(range) {
NodeOrToken::Token(_) => return acc, NodeOrToken::Token(_) => return acc,
NodeOrToken::Node(n) => n NodeOrToken::Node(n) => n
.descendants() .descendants()
@ -221,6 +231,7 @@ fn hints(
match node { match node {
ast::Expr(expr) => { ast::Expr(expr) => {
chaining_hints(hints, sema, &famous_defs, config, file_id, &expr); chaining_hints(hints, sema, &famous_defs, config, file_id, &expr);
adjustment_hints(hints, sema, config, &expr);
match expr { match expr {
ast::Expr::CallExpr(it) => param_name_hints(hints, sema, config, ast::Expr::from(it)), ast::Expr::CallExpr(it) => param_name_hints(hints, sema, config, ast::Expr::from(it)),
ast::Expr::MethodCallExpr(it) => { ast::Expr::MethodCallExpr(it) => {
@ -229,7 +240,7 @@ fn hints(
ast::Expr::ClosureExpr(it) => closure_ret_hints(hints, sema, &famous_defs, config, file_id, it), ast::Expr::ClosureExpr(it) => closure_ret_hints(hints, sema, &famous_defs, config, file_id, it),
// We could show reborrows for all expressions, but usually that is just noise to the user // We could show reborrows for all expressions, but usually that is just noise to the user
// and the main point here is to show why "moving" a mutable reference doesn't necessarily move it // and the main point here is to show why "moving" a mutable reference doesn't necessarily move it
ast::Expr::PathExpr(_) => reborrow_hints(hints, sema, config, &expr), // ast::Expr::PathExpr(_) => reborrow_hints(hints, sema, config, &expr),
_ => None, _ => None,
} }
}, },
@ -617,30 +628,95 @@ fn closure_ret_hints(
Some(()) Some(())
} }
fn reborrow_hints( fn adjustment_hints(
acc: &mut Vec<InlayHint>, acc: &mut Vec<InlayHint>,
sema: &Semantics<'_, RootDatabase>, sema: &Semantics<'_, RootDatabase>,
config: &InlayHintsConfig, config: &InlayHintsConfig,
expr: &ast::Expr, expr: &ast::Expr,
) -> Option<()> { ) -> Option<()> {
if config.reborrow_hints == ReborrowHints::Never { if config.adjustment_hints == AdjustmentHints::Never {
return None; return None;
} }
if let ast::Expr::ParenExpr(_) = expr {
// These inherit from the inner expression which would result in duplicate hints
return None;
}
let parent = expr.syntax().parent().and_then(ast::Expr::cast);
let descended = sema.descend_node_into_attributes(expr.clone()).pop(); let descended = sema.descend_node_into_attributes(expr.clone()).pop();
let desc_expr = descended.as_ref().unwrap_or(expr); let desc_expr = descended.as_ref().unwrap_or(expr);
let mutability = sema.is_implicit_reborrow(desc_expr)?; let adjustments = sema.expr_adjustments(desc_expr).filter(|it| !it.is_empty())?;
let label = match mutability { let needs_parens = match parent {
hir::Mutability::Shared if config.reborrow_hints != ReborrowHints::MutableOnly => "&*", Some(parent) => {
hir::Mutability::Mut => "&mut *", match parent {
_ => return None, ast::Expr::AwaitExpr(_)
| ast::Expr::CallExpr(_)
| ast::Expr::CastExpr(_)
| ast::Expr::FieldExpr(_)
| ast::Expr::MethodCallExpr(_)
| ast::Expr::TryExpr(_) => true,
// FIXME: shorthands need special casing, though not sure if adjustments are even valid there
ast::Expr::RecordExpr(_) => false,
ast::Expr::IndexExpr(index) => index.base().as_ref() == Some(expr),
_ => false,
}
}
None => false,
}; };
acc.push(InlayHint { if needs_parens {
range: expr.syntax().text_range(), acc.push(InlayHint {
kind: InlayKind::ImplicitReborrowHint, range: expr.syntax().text_range(),
label: label.to_string().into(), kind: InlayKind::AdjustmentHint,
tooltip: Some(InlayTooltip::String("Compiler inserted reborrow".into())), label: "(".into(),
}); tooltip: None,
});
}
for adjustment in adjustments.into_iter().rev() {
// FIXME: Add some nicer tooltips to each of these
let text = match adjustment {
Adjust::NeverToAny if config.adjustment_hints == AdjustmentHints::Always => {
"<never-to-any>"
}
Adjust::Deref(None) => "*",
Adjust::Deref(Some(OverloadedDeref(Mutability::Mut))) => "*",
Adjust::Deref(Some(OverloadedDeref(Mutability::Shared))) => "*",
Adjust::Borrow(AutoBorrow::Ref(Mutability::Shared)) => "&",
Adjust::Borrow(AutoBorrow::Ref(Mutability::Mut)) => "&mut ",
Adjust::Borrow(AutoBorrow::RawPtr(Mutability::Shared)) => "&raw const ",
Adjust::Borrow(AutoBorrow::RawPtr(Mutability::Mut)) => "&raw mut ",
// some of these could be represented via `as` casts, but that's not too nice and
// handling everything as a prefix expr makes the `(` and `)` insertion easier
Adjust::Pointer(cast) if config.adjustment_hints == AdjustmentHints::Always => {
match cast {
PointerCast::ReifyFnPointer => "<fn-item-to-fn-pointer>",
PointerCast::UnsafeFnPointer => "<safe-fn-pointer-to-unsafe-fn-pointer>",
PointerCast::ClosureFnPointer(Safety::Unsafe) => {
"<closure-to-unsafe-fn-pointer>"
}
PointerCast::ClosureFnPointer(Safety::Safe) => "<closure-to-fn-pointer>",
PointerCast::MutToConstPointer => "<mut-ptr-to-const-ptr>",
PointerCast::ArrayToPointer => "<array-ptr-to-element-ptr>",
PointerCast::Unsize => "<unsize>",
}
}
_ => continue,
};
acc.push(InlayHint {
range: expr.syntax().text_range(),
kind: InlayKind::AdjustmentHint,
label: text.into(),
tooltip: None,
});
}
if needs_parens {
acc.push(InlayHint {
range: expr.syntax().text_range(),
kind: InlayKind::AdjustmentHintClosingParenthesis,
label: ")".into(),
tooltip: None,
});
}
Some(()) Some(())
} }
@ -1213,12 +1289,11 @@ fn get_callable(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use expect_test::{expect, Expect}; use expect_test::{expect, Expect};
use ide_db::base_db::FileRange;
use itertools::Itertools; use itertools::Itertools;
use syntax::{TextRange, TextSize}; use syntax::{TextRange, TextSize};
use test_utils::extract_annotations; use test_utils::extract_annotations;
use crate::inlay_hints::ReborrowHints; use crate::inlay_hints::AdjustmentHints;
use crate::{fixture, inlay_hints::InlayHintsConfig, LifetimeElisionHints}; use crate::{fixture, inlay_hints::InlayHintsConfig, LifetimeElisionHints};
use super::ClosureReturnTypeHints; use super::ClosureReturnTypeHints;
@ -1230,7 +1305,7 @@ mod tests {
chaining_hints: false, chaining_hints: false,
lifetime_elision_hints: LifetimeElisionHints::Never, lifetime_elision_hints: LifetimeElisionHints::Never,
closure_return_type_hints: ClosureReturnTypeHints::Never, closure_return_type_hints: ClosureReturnTypeHints::Never,
reborrow_hints: ReborrowHints::Always, adjustment_hints: AdjustmentHints::Never,
binding_mode_hints: false, binding_mode_hints: false,
hide_named_constructor_hints: false, hide_named_constructor_hints: false,
hide_closure_initialization_hints: false, hide_closure_initialization_hints: false,
@ -1242,7 +1317,6 @@ mod tests {
type_hints: true, type_hints: true,
parameter_hints: true, parameter_hints: true,
chaining_hints: true, chaining_hints: true,
reborrow_hints: ReborrowHints::Always,
closure_return_type_hints: ClosureReturnTypeHints::WithBlock, closure_return_type_hints: ClosureReturnTypeHints::WithBlock,
binding_mode_hints: true, binding_mode_hints: true,
lifetime_elision_hints: LifetimeElisionHints::Always, lifetime_elision_hints: LifetimeElisionHints::Always,
@ -1838,10 +1912,7 @@ fn main() {
.inlay_hints( .inlay_hints(
&InlayHintsConfig { type_hints: true, ..DISABLED_CONFIG }, &InlayHintsConfig { type_hints: true, ..DISABLED_CONFIG },
file_id, file_id,
Some(FileRange { Some(TextRange::new(TextSize::from(500), TextSize::from(600))),
file_id,
range: TextRange::new(TextSize::from(500), TextSize::from(600)),
}),
) )
.unwrap(); .unwrap();
let actual = let actual =
@ -2845,48 +2916,6 @@ impl () {
); );
} }
#[test]
fn hints_implicit_reborrow() {
check_with_config(
InlayHintsConfig {
reborrow_hints: ReborrowHints::Always,
parameter_hints: true,
..DISABLED_CONFIG
},
r#"
fn __() {
let unique = &mut ();
let r_mov = unique;
let foo: &mut _ = unique;
//^^^^^^ &mut *
ref_mut_id(unique);
//^^^^^^ mut_ref
//^^^^^^ &mut *
let shared = ref_id(unique);
//^^^^^^ shared_ref
//^^^^^^ &*
let mov = shared;
let r_mov: &_ = shared;
ref_id(shared);
//^^^^^^ shared_ref
identity(unique);
identity(shared);
}
fn identity<T>(t: T) -> T {
t
}
fn ref_mut_id(mut_ref: &mut ()) -> &mut () {
mut_ref
//^^^^^^^ &mut *
}
fn ref_id(shared_ref: &()) -> &() {
shared_ref
}
"#,
);
}
#[test] #[test]
fn hints_binding_modes() { fn hints_binding_modes() {
check_with_config( check_with_config(
@ -2994,4 +3023,76 @@ fn f() {
"#, "#,
); );
} }
#[test]
fn adjustment_hints() {
check_with_config(
InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG },
r#"
//- minicore: coerce_unsized
fn main() {
let _: u32 = loop {};
//^^^^^^^<never-to-any>
let _: &u32 = &mut 0;
//^^^^^^&
//^^^^^^*
let _: &mut u32 = &mut 0;
//^^^^^^&mut $
//^^^^^^*
let _: *const u32 = &mut 0;
//^^^^^^&raw const $
//^^^^^^*
let _: *mut u32 = &mut 0;
//^^^^^^&raw mut $
//^^^^^^*
let _: fn() = main;
//^^^^<fn-item-to-fn-pointer>
let _: unsafe fn() = main;
//^^^^<safe-fn-pointer-to-unsafe-fn-pointer>
//^^^^<fn-item-to-fn-pointer>
let _: unsafe fn() = main as fn();
//^^^^^^^^^^^^<safe-fn-pointer-to-unsafe-fn-pointer>
let _: fn() = || {};
//^^^^^<closure-to-fn-pointer>
let _: unsafe fn() = || {};
//^^^^^<closure-to-unsafe-fn-pointer>
let _: *const u32 = &mut 0u32 as *mut u32;
//^^^^^^^^^^^^^^^^^^^^^<mut-ptr-to-const-ptr>
let _: &mut [_] = &mut [0; 0];
//^^^^^^^^^^^<unsize>
//^^^^^^^^^^^&mut $
//^^^^^^^^^^^*
Struct.consume();
Struct.by_ref();
//^^^^^^(
//^^^^^^&
//^^^^^^)
Struct.by_ref_mut();
//^^^^^^(
//^^^^^^&mut $
//^^^^^^)
(&Struct).consume();
//^^^^^^^*
(&Struct).by_ref();
(&mut Struct).consume();
//^^^^^^^^^^^*
(&mut Struct).by_ref();
//^^^^^^^^^^^&
//^^^^^^^^^^^*
(&mut Struct).by_ref_mut();
}
#[derive(Copy, Clone)]
struct Struct;
impl Struct {
fn consume(self) {}
fn by_ref(&self) {}
fn by_ref_mut(&mut self) {}
}
"#,
)
}
} }

View file

@ -81,8 +81,8 @@ pub use crate::{
highlight_related::{HighlightRelatedConfig, HighlightedRange}, highlight_related::{HighlightRelatedConfig, HighlightedRange},
hover::{HoverAction, HoverConfig, HoverDocFormat, HoverGotoTypeData, HoverResult}, hover::{HoverAction, HoverConfig, HoverDocFormat, HoverGotoTypeData, HoverResult},
inlay_hints::{ inlay_hints::{
ClosureReturnTypeHints, InlayHint, InlayHintLabel, InlayHintsConfig, InlayKind, AdjustmentHints, ClosureReturnTypeHints, InlayHint, InlayHintLabel, InlayHintsConfig,
InlayTooltip, LifetimeElisionHints, ReborrowHints, InlayKind, InlayTooltip, LifetimeElisionHints,
}, },
join_lines::JoinLinesConfig, join_lines::JoinLinesConfig,
markup::Markup, markup::Markup,
@ -367,7 +367,7 @@ impl Analysis {
&self, &self,
config: &InlayHintsConfig, config: &InlayHintsConfig,
file_id: FileId, file_id: FileId,
range: Option<FileRange>, range: Option<TextRange>,
) -> Cancellable<Vec<InlayHint>> { ) -> Cancellable<Vec<InlayHint>> {
self.with_db(|db| inlay_hints::inlay_hints(db, file_id, range, config)) self.with_db(|db| inlay_hints::inlay_hints(db, file_id, range, config))
} }

View file

@ -1,9 +1,9 @@
//! This module generates [moniker](https://microsoft.github.io/language-server-protocol/specifications/lsif/0.6.0/specification/#exportsImports) //! This module generates [moniker](https://microsoft.github.io/language-server-protocol/specifications/lsif/0.6.0/specification/#exportsImports)
//! for LSIF and LSP. //! for LSIF and LSP.
use hir::{db::DefDatabase, AsAssocItem, AssocItemContainer, Crate, Name, Semantics}; use hir::{AsAssocItem, AssocItemContainer, Crate, Name, Semantics};
use ide_db::{ use ide_db::{
base_db::{CrateOrigin, FileId, FileLoader, FilePosition, LangCrateOrigin}, base_db::{CrateOrigin, FilePosition, LangCrateOrigin},
defs::{Definition, IdentClass}, defs::{Definition, IdentClass},
helpers::pick_best_token, helpers::pick_best_token,
RootDatabase, RootDatabase,
@ -11,7 +11,7 @@ use ide_db::{
use itertools::Itertools; use itertools::Itertools;
use syntax::{AstNode, SyntaxKind::*, T}; use syntax::{AstNode, SyntaxKind::*, T};
use crate::{doc_links::token_as_doc_comment, RangeInfo}; use crate::{doc_links::token_as_doc_comment, parent_module::crates_for, RangeInfo};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum MonikerDescriptorKind { pub enum MonikerDescriptorKind {
@ -77,25 +77,13 @@ pub struct PackageInformation {
pub version: Option<String>, pub version: Option<String>,
} }
pub(crate) fn crate_for_file(db: &RootDatabase, file_id: FileId) -> Option<Crate> {
for &krate in db.relevant_crates(file_id).iter() {
let crate_def_map = db.crate_def_map(krate);
for (_, data) in crate_def_map.modules() {
if data.origin.file_id() == Some(file_id) {
return Some(krate.into());
}
}
}
None
}
pub(crate) fn moniker( pub(crate) fn moniker(
db: &RootDatabase, db: &RootDatabase,
FilePosition { file_id, offset }: FilePosition, FilePosition { file_id, offset }: FilePosition,
) -> Option<RangeInfo<Vec<MonikerResult>>> { ) -> Option<RangeInfo<Vec<MonikerResult>>> {
let sema = &Semantics::new(db); let sema = &Semantics::new(db);
let file = sema.parse(file_id).syntax().clone(); let file = sema.parse(file_id).syntax().clone();
let current_crate = crate_for_file(db, file_id)?; let current_crate: hir::Crate = crates_for(db, file_id).pop()?.into();
let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind { let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
IDENT IDENT
| INT_NUMBER | INT_NUMBER

View file

@ -16,6 +16,7 @@ use ide_db::{
search::{ReferenceCategory, SearchScope, UsageSearchResult}, search::{ReferenceCategory, SearchScope, UsageSearchResult},
RootDatabase, RootDatabase,
}; };
use itertools::Itertools;
use stdx::hash::NoHashHashMap; use stdx::hash::NoHashHashMap;
use syntax::{ use syntax::{
algo::find_node_at_offset, algo::find_node_at_offset,
@ -86,6 +87,7 @@ pub(crate) fn find_all_refs(
file_id, file_id,
refs.into_iter() refs.into_iter()
.map(|file_ref| (file_ref.range, file_ref.category)) .map(|file_ref| (file_ref.range, file_ref.category))
.unique()
.collect(), .collect(),
) )
}) })

View file

@ -1345,5 +1345,36 @@ fn f<F: FnOnce(u8, u16) -> i32>(f: F) {
^^ --- ^^ ---
"#]], "#]],
); );
check(
r#"
fn f<T, F: FnOnce(&T, u16) -> &T>(f: F) {
f($0)
}
"#,
expect![[r#"
(&T, u16) -> &T
^^ ---
"#]],
);
}
#[test]
fn regression_13579() {
check(
r#"
fn f() {
take(2)($0);
}
fn take<C, Error>(
count: C
) -> impl Fn() -> C {
move || count
}
"#,
expect![[r#"
() -> i32
"#]],
);
} }
} }

View file

@ -13,7 +13,8 @@ use syntax::{AstNode, SyntaxKind::*, SyntaxToken, TextRange, T};
use crate::{ use crate::{
hover::hover_for_definition, hover::hover_for_definition,
moniker::{crate_for_file, def_to_moniker, MonikerResult}, moniker::{def_to_moniker, MonikerResult},
parent_module::crates_for,
Analysis, Fold, HoverConfig, HoverDocFormat, HoverResult, InlayHint, InlayHintsConfig, Analysis, Fold, HoverConfig, HoverDocFormat, HoverResult, InlayHint, InlayHintsConfig,
TryToNav, TryToNav,
}; };
@ -99,7 +100,7 @@ fn all_modules(db: &dyn HirDatabase) -> Vec<Module> {
impl StaticIndex<'_> { impl StaticIndex<'_> {
fn add_file(&mut self, file_id: FileId) { fn add_file(&mut self, file_id: FileId) {
let current_crate = crate_for_file(self.db, file_id); let current_crate = crates_for(self.db, file_id).pop().map(Into::into);
let folds = self.analysis.folding_ranges(file_id).unwrap(); let folds = self.analysis.folding_ranges(file_id).unwrap();
let inlay_hints = self let inlay_hints = self
.analysis .analysis
@ -111,7 +112,7 @@ impl StaticIndex<'_> {
chaining_hints: true, chaining_hints: true,
closure_return_type_hints: crate::ClosureReturnTypeHints::WithBlock, closure_return_type_hints: crate::ClosureReturnTypeHints::WithBlock,
lifetime_elision_hints: crate::LifetimeElisionHints::Never, lifetime_elision_hints: crate::LifetimeElisionHints::Never,
reborrow_hints: crate::ReborrowHints::Never, adjustment_hints: crate::AdjustmentHints::Never,
hide_named_constructor_hints: false, hide_named_constructor_hints: false,
hide_closure_initialization_hints: false, hide_closure_initialization_hints: false,
param_names_for_lifetime_elision_hints: false, param_names_for_lifetime_elision_hints: false,

View file

@ -4,7 +4,7 @@ version = "0.0.0"
description = "TBD" description = "TBD"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
rust-version = "1.57" rust-version = "1.65"
[features] [features]
tracking = [] tracking = []

View file

@ -4,7 +4,7 @@ version = "0.0.0"
description = "TBD" description = "TBD"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
rust-version = "1.57" rust-version = "1.65"
[lib] [lib]
doctest = false doctest = false

View file

@ -12,6 +12,9 @@ use tt::buffer::{Cursor, TokenBuffer};
use crate::{to_parser_input::to_parser_input, tt_iter::TtIter, TokenMap}; use crate::{to_parser_input::to_parser_input, tt_iter::TtIter, TokenMap};
#[cfg(test)]
mod tests;
/// Convert the syntax node to a `TokenTree` (what macro /// Convert the syntax node to a `TokenTree` (what macro
/// will consume). /// will consume).
pub fn syntax_node_to_token_tree(node: &SyntaxNode) -> (tt::Subtree, TokenMap) { pub fn syntax_node_to_token_tree(node: &SyntaxNode) -> (tt::Subtree, TokenMap) {
@ -35,7 +38,7 @@ pub fn syntax_node_to_token_tree_with_modifications(
append: FxHashMap<SyntaxElement, Vec<SyntheticToken>>, append: FxHashMap<SyntaxElement, Vec<SyntheticToken>>,
) -> (tt::Subtree, TokenMap, u32) { ) -> (tt::Subtree, TokenMap, u32) {
let global_offset = node.text_range().start(); let global_offset = node.text_range().start();
let mut c = Convertor::new(node, global_offset, existing_token_map, next_id, replace, append); let mut c = Converter::new(node, global_offset, existing_token_map, next_id, replace, append);
let subtree = convert_tokens(&mut c); let subtree = convert_tokens(&mut c);
c.id_alloc.map.shrink_to_fit(); c.id_alloc.map.shrink_to_fit();
always!(c.replace.is_empty(), "replace: {:?}", c.replace); always!(c.replace.is_empty(), "replace: {:?}", c.replace);
@ -100,7 +103,7 @@ pub fn parse_to_token_tree(text: &str) -> Option<(tt::Subtree, TokenMap)> {
return None; return None;
} }
let mut conv = RawConvertor { let mut conv = RawConverter {
lexed, lexed,
pos: 0, pos: 0,
id_alloc: TokenIdAlloc { id_alloc: TokenIdAlloc {
@ -148,7 +151,7 @@ pub fn parse_exprs_with_sep(tt: &tt::Subtree, sep: char) -> Vec<tt::Subtree> {
res res
} }
fn convert_tokens<C: TokenConvertor>(conv: &mut C) -> tt::Subtree { fn convert_tokens<C: TokenConverter>(conv: &mut C) -> tt::Subtree {
struct StackEntry { struct StackEntry {
subtree: tt::Subtree, subtree: tt::Subtree,
idx: usize, idx: usize,
@ -228,7 +231,7 @@ fn convert_tokens<C: TokenConvertor>(conv: &mut C) -> tt::Subtree {
} }
let spacing = match conv.peek().map(|next| next.kind(conv)) { let spacing = match conv.peek().map(|next| next.kind(conv)) {
Some(kind) if !kind.is_trivia() => tt::Spacing::Joint, Some(kind) if is_single_token_op(kind) => tt::Spacing::Joint,
_ => tt::Spacing::Alone, _ => tt::Spacing::Alone,
}; };
let char = match token.to_char(conv) { let char = match token.to_char(conv) {
@ -307,6 +310,35 @@ fn convert_tokens<C: TokenConvertor>(conv: &mut C) -> tt::Subtree {
} }
} }
fn is_single_token_op(kind: SyntaxKind) -> bool {
matches!(
kind,
EQ | L_ANGLE
| R_ANGLE
| BANG
| AMP
| PIPE
| TILDE
| AT
| DOT
| COMMA
| SEMICOLON
| COLON
| POUND
| DOLLAR
| QUESTION
| PLUS
| MINUS
| STAR
| SLASH
| PERCENT
| CARET
// LIFETIME_IDENT will be split into a sequence of `'` (a single quote) and an
// identifier.
| LIFETIME_IDENT
)
}
/// Returns the textual content of a doc comment block as a quoted string /// Returns the textual content of a doc comment block as a quoted string
/// That is, strips leading `///` (or `/**`, etc) /// That is, strips leading `///` (or `/**`, etc)
/// and strips the ending `*/` /// and strips the ending `*/`
@ -425,8 +457,8 @@ impl TokenIdAlloc {
} }
} }
/// A raw token (straight from lexer) convertor /// A raw token (straight from lexer) converter
struct RawConvertor<'a> { struct RawConverter<'a> {
lexed: parser::LexedStr<'a>, lexed: parser::LexedStr<'a>,
pos: usize, pos: usize,
id_alloc: TokenIdAlloc, id_alloc: TokenIdAlloc,
@ -442,7 +474,7 @@ trait SrcToken<Ctx>: std::fmt::Debug {
fn synthetic_id(&self, ctx: &Ctx) -> Option<SyntheticTokenId>; fn synthetic_id(&self, ctx: &Ctx) -> Option<SyntheticTokenId>;
} }
trait TokenConvertor: Sized { trait TokenConverter: Sized {
type Token: SrcToken<Self>; type Token: SrcToken<Self>;
fn convert_doc_comment(&self, token: &Self::Token) -> Option<Vec<tt::TokenTree>>; fn convert_doc_comment(&self, token: &Self::Token) -> Option<Vec<tt::TokenTree>>;
@ -454,25 +486,25 @@ trait TokenConvertor: Sized {
fn id_alloc(&mut self) -> &mut TokenIdAlloc; fn id_alloc(&mut self) -> &mut TokenIdAlloc;
} }
impl<'a> SrcToken<RawConvertor<'a>> for usize { impl<'a> SrcToken<RawConverter<'a>> for usize {
fn kind(&self, ctx: &RawConvertor<'a>) -> SyntaxKind { fn kind(&self, ctx: &RawConverter<'a>) -> SyntaxKind {
ctx.lexed.kind(*self) ctx.lexed.kind(*self)
} }
fn to_char(&self, ctx: &RawConvertor<'a>) -> Option<char> { fn to_char(&self, ctx: &RawConverter<'a>) -> Option<char> {
ctx.lexed.text(*self).chars().next() ctx.lexed.text(*self).chars().next()
} }
fn to_text(&self, ctx: &RawConvertor<'_>) -> SmolStr { fn to_text(&self, ctx: &RawConverter<'_>) -> SmolStr {
ctx.lexed.text(*self).into() ctx.lexed.text(*self).into()
} }
fn synthetic_id(&self, _ctx: &RawConvertor<'a>) -> Option<SyntheticTokenId> { fn synthetic_id(&self, _ctx: &RawConverter<'a>) -> Option<SyntheticTokenId> {
None None
} }
} }
impl<'a> TokenConvertor for RawConvertor<'a> { impl<'a> TokenConverter for RawConverter<'a> {
type Token = usize; type Token = usize;
fn convert_doc_comment(&self, &token: &usize) -> Option<Vec<tt::TokenTree>> { fn convert_doc_comment(&self, &token: &usize) -> Option<Vec<tt::TokenTree>> {
@ -504,7 +536,7 @@ impl<'a> TokenConvertor for RawConvertor<'a> {
} }
} }
struct Convertor { struct Converter {
id_alloc: TokenIdAlloc, id_alloc: TokenIdAlloc,
current: Option<SyntaxToken>, current: Option<SyntaxToken>,
current_synthetic: Vec<SyntheticToken>, current_synthetic: Vec<SyntheticToken>,
@ -515,7 +547,7 @@ struct Convertor {
punct_offset: Option<(SyntaxToken, TextSize)>, punct_offset: Option<(SyntaxToken, TextSize)>,
} }
impl Convertor { impl Converter {
fn new( fn new(
node: &SyntaxNode, node: &SyntaxNode,
global_offset: TextSize, global_offset: TextSize,
@ -523,11 +555,11 @@ impl Convertor {
next_id: u32, next_id: u32,
mut replace: FxHashMap<SyntaxElement, Vec<SyntheticToken>>, mut replace: FxHashMap<SyntaxElement, Vec<SyntheticToken>>,
mut append: FxHashMap<SyntaxElement, Vec<SyntheticToken>>, mut append: FxHashMap<SyntaxElement, Vec<SyntheticToken>>,
) -> Convertor { ) -> Converter {
let range = node.text_range(); let range = node.text_range();
let mut preorder = node.preorder_with_tokens(); let mut preorder = node.preorder_with_tokens();
let (first, synthetic) = Self::next_token(&mut preorder, &mut replace, &mut append); let (first, synthetic) = Self::next_token(&mut preorder, &mut replace, &mut append);
Convertor { Converter {
id_alloc: { TokenIdAlloc { map: existing_token_map, global_offset, next_id } }, id_alloc: { TokenIdAlloc { map: existing_token_map, global_offset, next_id } },
current: first, current: first,
current_synthetic: synthetic, current_synthetic: synthetic,
@ -590,15 +622,15 @@ impl SynToken {
} }
} }
impl SrcToken<Convertor> for SynToken { impl SrcToken<Converter> for SynToken {
fn kind(&self, _ctx: &Convertor) -> SyntaxKind { fn kind(&self, ctx: &Converter) -> SyntaxKind {
match self { match self {
SynToken::Ordinary(token) => token.kind(), SynToken::Ordinary(token) => token.kind(),
SynToken::Punch(token, _) => token.kind(), SynToken::Punch(..) => SyntaxKind::from_char(self.to_char(ctx).unwrap()).unwrap(),
SynToken::Synthetic(token) => token.kind, SynToken::Synthetic(token) => token.kind,
} }
} }
fn to_char(&self, _ctx: &Convertor) -> Option<char> { fn to_char(&self, _ctx: &Converter) -> Option<char> {
match self { match self {
SynToken::Ordinary(_) => None, SynToken::Ordinary(_) => None,
SynToken::Punch(it, i) => it.text().chars().nth((*i).into()), SynToken::Punch(it, i) => it.text().chars().nth((*i).into()),
@ -606,7 +638,7 @@ impl SrcToken<Convertor> for SynToken {
SynToken::Synthetic(_) => None, SynToken::Synthetic(_) => None,
} }
} }
fn to_text(&self, _ctx: &Convertor) -> SmolStr { fn to_text(&self, _ctx: &Converter) -> SmolStr {
match self { match self {
SynToken::Ordinary(token) => token.text().into(), SynToken::Ordinary(token) => token.text().into(),
SynToken::Punch(token, _) => token.text().into(), SynToken::Punch(token, _) => token.text().into(),
@ -614,7 +646,7 @@ impl SrcToken<Convertor> for SynToken {
} }
} }
fn synthetic_id(&self, _ctx: &Convertor) -> Option<SyntheticTokenId> { fn synthetic_id(&self, _ctx: &Converter) -> Option<SyntheticTokenId> {
match self { match self {
SynToken::Synthetic(token) => Some(token.id), SynToken::Synthetic(token) => Some(token.id),
_ => None, _ => None,
@ -622,7 +654,7 @@ impl SrcToken<Convertor> for SynToken {
} }
} }
impl TokenConvertor for Convertor { impl TokenConverter for Converter {
type Token = SynToken; type Token = SynToken;
fn convert_doc_comment(&self, token: &Self::Token) -> Option<Vec<tt::TokenTree>> { fn convert_doc_comment(&self, token: &Self::Token) -> Option<Vec<tt::TokenTree>> {
convert_doc_comment(token.token()?) convert_doc_comment(token.token()?)
@ -651,7 +683,7 @@ impl TokenConvertor for Convertor {
} }
let curr = self.current.clone()?; let curr = self.current.clone()?;
if !&self.range.contains_range(curr.text_range()) { if !self.range.contains_range(curr.text_range()) {
return None; return None;
} }
let (new_current, new_synth) = let (new_current, new_synth) =
@ -809,12 +841,15 @@ impl<'a> TtTreeSink<'a> {
let next = last.bump(); let next = last.bump();
if let ( if let (
Some(tt::buffer::TokenTreeRef::Leaf(tt::Leaf::Punct(curr), _)), Some(tt::buffer::TokenTreeRef::Leaf(tt::Leaf::Punct(curr), _)),
Some(tt::buffer::TokenTreeRef::Leaf(tt::Leaf::Punct(_), _)), Some(tt::buffer::TokenTreeRef::Leaf(tt::Leaf::Punct(next), _)),
) = (last.token_tree(), next.token_tree()) ) = (last.token_tree(), next.token_tree())
{ {
// Note: We always assume the semi-colon would be the last token in // Note: We always assume the semi-colon would be the last token in
// other parts of RA such that we don't add whitespace here. // other parts of RA such that we don't add whitespace here.
if curr.spacing == tt::Spacing::Alone && curr.char != ';' { //
// When `next` is a `Punct` of `'`, that's a part of a lifetime identifier so we don't
// need to add whitespace either.
if curr.spacing == tt::Spacing::Alone && curr.char != ';' && next.char != '\'' {
self.inner.token(WHITESPACE, " "); self.inner.token(WHITESPACE, " ");
self.text_pos += TextSize::of(' '); self.text_pos += TextSize::of(' ');
} }

View file

@ -0,0 +1,93 @@
use std::collections::HashMap;
use syntax::{ast, AstNode};
use test_utils::extract_annotations;
use tt::{
buffer::{TokenBuffer, TokenTreeRef},
Leaf, Punct, Spacing,
};
use super::syntax_node_to_token_tree;
fn check_punct_spacing(fixture: &str) {
let source_file = ast::SourceFile::parse(fixture).ok().unwrap();
let (subtree, token_map) = syntax_node_to_token_tree(source_file.syntax());
let mut annotations: HashMap<_, _> = extract_annotations(fixture)
.into_iter()
.map(|(range, annotation)| {
let token = token_map.token_by_range(range).expect("no token found");
let spacing = match annotation.as_str() {
"Alone" => Spacing::Alone,
"Joint" => Spacing::Joint,
a => panic!("unknown annotation: {}", a),
};
(token, spacing)
})
.collect();
let buf = TokenBuffer::from_subtree(&subtree);
let mut cursor = buf.begin();
while !cursor.eof() {
while let Some(token_tree) = cursor.token_tree() {
if let TokenTreeRef::Leaf(Leaf::Punct(Punct { spacing, id, .. }), _) = token_tree {
if let Some(expected) = annotations.remove(&id) {
assert_eq!(expected, *spacing);
}
}
cursor = cursor.bump_subtree();
}
cursor = cursor.bump();
}
assert!(annotations.is_empty(), "unchecked annotations: {:?}", annotations);
}
#[test]
fn punct_spacing() {
check_punct_spacing(
r#"
fn main() {
0+0;
//^ Alone
0+(0);
//^ Alone
0<=0;
//^ Joint
// ^ Alone
0<=(0);
// ^ Alone
a=0;
//^ Alone
a=(0);
//^ Alone
a+=0;
//^ Joint
// ^ Alone
a+=(0);
// ^ Alone
a&&b;
//^ Joint
// ^ Alone
a&&(b);
// ^ Alone
foo::bar;
// ^ Joint
// ^ Alone
use foo::{bar,baz,};
// ^ Alone
// ^ Alone
// ^ Alone
struct Struct<'a> {};
// ^ Joint
// ^ Joint
Struct::<0>;
// ^ Alone
Struct::<{0}>;
// ^ Alone
;;
//^ Joint
// ^ Alone
}
"#,
);
}

View file

@ -4,7 +4,7 @@ version = "0.0.0"
description = "TBD" description = "TBD"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
rust-version = "1.57" rust-version = "1.65"
[lib] [lib]
doctest = false doctest = false

View file

@ -4,7 +4,7 @@ version = "0.0.0"
description = "TBD" description = "TBD"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
rust-version = "1.57" rust-version = "1.65"
[lib] [lib]
doctest = false doctest = false

View file

@ -4,7 +4,7 @@ version = "0.0.0"
description = "TBD" description = "TBD"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
rust-version = "1.57" rust-version = "1.65"
[lib] [lib]
doctest = false doctest = false

View file

@ -4,7 +4,7 @@ version = "0.0.0"
description = "TBD" description = "TBD"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
rust-version = "1.57" rust-version = "1.65"
[dependencies] [dependencies]
proc-macro-srv = { version = "0.0.0", path = "../proc-macro-srv" } proc-macro-srv = { version = "0.0.0", path = "../proc-macro-srv" }

View file

@ -4,7 +4,7 @@ version = "0.0.0"
description = "TBD" description = "TBD"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
rust-version = "1.57" rust-version = "1.65"
[lib] [lib]
doctest = false doctest = false

View file

@ -117,7 +117,7 @@ impl Abi {
let inner = unsafe { Abi_1_63::from_lib(lib, symbol_name) }?; let inner = unsafe { Abi_1_63::from_lib(lib, symbol_name) }?;
Ok(Abi::Abi1_63(inner)) Ok(Abi::Abi1_63(inner))
} }
_ => Err(LoadProcMacroDylibError::UnsupportedABI), _ => Err(LoadProcMacroDylibError::UnsupportedABI(info.version_string.clone())),
} }
} }

View file

@ -80,14 +80,14 @@ fn load_library(file: &Path) -> Result<Library, libloading::Error> {
pub enum LoadProcMacroDylibError { pub enum LoadProcMacroDylibError {
Io(io::Error), Io(io::Error),
LibLoading(libloading::Error), LibLoading(libloading::Error),
UnsupportedABI, UnsupportedABI(String),
} }
impl fmt::Display for LoadProcMacroDylibError { impl fmt::Display for LoadProcMacroDylibError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::Io(e) => e.fmt(f), Self::Io(e) => e.fmt(f),
Self::UnsupportedABI => write!(f, "unsupported ABI version"), Self::UnsupportedABI(v) => write!(f, "unsupported ABI `{v}`"),
Self::LibLoading(e) => e.fmt(f), Self::LibLoading(e) => e.fmt(f),
} }
} }

View file

@ -113,12 +113,12 @@ impl ProcMacroSrv {
fn expander(&mut self, path: &Path) -> Result<&dylib::Expander, String> { fn expander(&mut self, path: &Path) -> Result<&dylib::Expander, String> {
let time = fs::metadata(path).and_then(|it| it.modified()).map_err(|err| { let time = fs::metadata(path).and_then(|it| it.modified()).map_err(|err| {
format!("Failed to get file metadata for {}: {:?}", path.display(), err) format!("Failed to get file metadata for {}: {}", path.display(), err)
})?; })?;
Ok(match self.expanders.entry((path.to_path_buf(), time)) { Ok(match self.expanders.entry((path.to_path_buf(), time)) {
Entry::Vacant(v) => v.insert(dylib::Expander::new(path).map_err(|err| { Entry::Vacant(v) => v.insert(dylib::Expander::new(path).map_err(|err| {
format!("Cannot create expander for {}: {:?}", path.display(), err) format!("Cannot create expander for {}: {}", path.display(), err)
})?), })?),
Entry::Occupied(e) => e.into_mut(), Entry::Occupied(e) => e.into_mut(),
}) })

View file

@ -19,7 +19,7 @@ fn test_derive_error() {
expect![[r##" expect![[r##"
SUBTREE $ SUBTREE $
IDENT compile_error 4294967295 IDENT compile_error 4294967295
PUNCH ! [joint] 4294967295 PUNCH ! [alone] 4294967295
SUBTREE () 4294967295 SUBTREE () 4294967295
LITERAL "#[derive(DeriveError)] struct S ;" 4294967295 LITERAL "#[derive(DeriveError)] struct S ;" 4294967295
PUNCH ; [alone] 4294967295"##]], PUNCH ; [alone] 4294967295"##]],
@ -109,7 +109,7 @@ fn test_fn_like_macro_clone_literals() {
PUNCH , [alone] 4294967295 PUNCH , [alone] 4294967295
LITERAL 2_u32 4294967295 LITERAL 2_u32 4294967295
PUNCH , [alone] 4294967295 PUNCH , [alone] 4294967295
PUNCH - [joint] 4294967295 PUNCH - [alone] 4294967295
LITERAL 4i64 4294967295 LITERAL 4i64 4294967295
PUNCH , [alone] 4294967295 PUNCH , [alone] 4294967295
LITERAL 3.14f32 4294967295 LITERAL 3.14f32 4294967295
@ -130,7 +130,7 @@ fn test_attr_macro() {
expect![[r##" expect![[r##"
SUBTREE $ SUBTREE $
IDENT compile_error 4294967295 IDENT compile_error 4294967295
PUNCH ! [joint] 4294967295 PUNCH ! [alone] 4294967295
SUBTREE () 4294967295 SUBTREE () 4294967295
LITERAL "#[attr_error(some arguments)] mod m {}" 4294967295 LITERAL "#[attr_error(some arguments)] mod m {}" 4294967295
PUNCH ; [alone] 4294967295"##]], PUNCH ; [alone] 4294967295"##]],

View file

@ -3,7 +3,7 @@ name = "proc-macro-test"
version = "0.0.0" version = "0.0.0"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
rust-version = "1.57" rust-version = "1.65"
publish = false publish = false
[lib] [lib]

View file

@ -3,7 +3,7 @@ name = "proc-macro-test-impl"
version = "0.0.0" version = "0.0.0"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
rust-version = "1.57" rust-version = "1.65"
publish = false publish = false
[lib] [lib]

View file

@ -4,7 +4,7 @@ version = "0.0.0"
description = "TBD" description = "TBD"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
rust-version = "1.57" rust-version = "1.65"
[lib] [lib]
doctest = false doctest = false

View file

@ -4,7 +4,7 @@ version = "0.0.0"
description = "TBD" description = "TBD"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
rust-version = "1.57" rust-version = "1.65"
[lib] [lib]
doctest = false doctest = false

View file

@ -69,7 +69,7 @@ impl WorkspaceBuildScripts {
cmd.args(&["check", "--quiet", "--workspace", "--message-format=json"]); cmd.args(&["check", "--quiet", "--workspace", "--message-format=json"]);
// --all-targets includes tests, benches and examples in addition to the // --all-targets includes tests, benches and examples in addition to the
// default lib and bins. This is an independent concept from the --targets // default lib and bins. This is an independent concept from the --target
// flag below. // flag below.
cmd.arg("--all-targets"); cmd.arg("--all-targets");

View file

@ -270,11 +270,7 @@ impl CargoWorkspace {
config: &CargoConfig, config: &CargoConfig,
progress: &dyn Fn(String), progress: &dyn Fn(String),
) -> Result<cargo_metadata::Metadata> { ) -> Result<cargo_metadata::Metadata> {
let target = config let targets = find_list_of_build_targets(config, cargo_toml);
.target
.clone()
.or_else(|| cargo_config_build_target(cargo_toml, &config.extra_env))
.or_else(|| rustc_discover_host_triple(cargo_toml, &config.extra_env));
let mut meta = MetadataCommand::new(); let mut meta = MetadataCommand::new();
meta.cargo_path(toolchain::cargo()); meta.cargo_path(toolchain::cargo());
@ -294,8 +290,12 @@ impl CargoWorkspace {
} }
meta.current_dir(current_dir.as_os_str()); meta.current_dir(current_dir.as_os_str());
if let Some(target) = target { if !targets.is_empty() {
meta.other_options(vec![String::from("--filter-platform"), target]); let other_options: Vec<_> = targets
.into_iter()
.flat_map(|target| ["--filter-platform".to_string(), target])
.collect();
meta.other_options(other_options);
} }
// FIXME: Fetching metadata is a slow process, as it might require // FIXME: Fetching metadata is a slow process, as it might require
@ -469,6 +469,19 @@ impl CargoWorkspace {
} }
} }
fn find_list_of_build_targets(config: &CargoConfig, cargo_toml: &ManifestPath) -> Vec<String> {
if let Some(target) = &config.target {
return [target.into()].to_vec();
}
let build_targets = cargo_config_build_target(cargo_toml, &config.extra_env);
if !build_targets.is_empty() {
return build_targets;
}
rustc_discover_host_triple(cargo_toml, &config.extra_env).into_iter().collect()
}
fn rustc_discover_host_triple( fn rustc_discover_host_triple(
cargo_toml: &ManifestPath, cargo_toml: &ManifestPath,
extra_env: &FxHashMap<String, String>, extra_env: &FxHashMap<String, String>,
@ -499,7 +512,7 @@ fn rustc_discover_host_triple(
fn cargo_config_build_target( fn cargo_config_build_target(
cargo_toml: &ManifestPath, cargo_toml: &ManifestPath,
extra_env: &FxHashMap<String, String>, extra_env: &FxHashMap<String, String>,
) -> Option<String> { ) -> Vec<String> {
let mut cargo_config = Command::new(toolchain::cargo()); let mut cargo_config = Command::new(toolchain::cargo());
cargo_config.envs(extra_env); cargo_config.envs(extra_env);
cargo_config cargo_config
@ -507,12 +520,21 @@ fn cargo_config_build_target(
.args(&["-Z", "unstable-options", "config", "get", "build.target"]) .args(&["-Z", "unstable-options", "config", "get", "build.target"])
.env("RUSTC_BOOTSTRAP", "1"); .env("RUSTC_BOOTSTRAP", "1");
// if successful we receive `build.target = "target-triple"` // if successful we receive `build.target = "target-triple"`
// or `build.target = ["<target 1>", ..]`
tracing::debug!("Discovering cargo config target by {:?}", cargo_config); tracing::debug!("Discovering cargo config target by {:?}", cargo_config);
match utf8_stdout(cargo_config) { utf8_stdout(cargo_config).map(parse_output_cargo_config_build_target).unwrap_or_default()
Ok(stdout) => stdout }
.strip_prefix("build.target = \"")
.and_then(|stdout| stdout.strip_suffix('"')) fn parse_output_cargo_config_build_target(stdout: String) -> Vec<String> {
.map(ToOwned::to_owned), let trimmed = stdout.trim_start_matches("build.target = ").trim_matches('"');
Err(_) => None,
} if !trimmed.starts_with('[') {
return [trimmed.to_string()].to_vec();
}
let res = serde_json::from_str(trimmed);
if let Err(e) = &res {
tracing::warn!("Failed to parse `build.target` as an array of target: {}`", e);
}
res.unwrap_or_default()
} }

View file

@ -128,14 +128,18 @@ impl Sysroot {
} }
if let Some(alloc) = sysroot.by_name("alloc") { if let Some(alloc) = sysroot.by_name("alloc") {
if let Some(core) = sysroot.by_name("core") { for dep in ALLOC_DEPS.trim().lines() {
sysroot.crates[alloc].deps.push(core); if let Some(dep) = sysroot.by_name(dep) {
sysroot.crates[alloc].deps.push(dep)
}
} }
} }
if let Some(proc_macro) = sysroot.by_name("proc_macro") { if let Some(proc_macro) = sysroot.by_name("proc_macro") {
if let Some(std) = sysroot.by_name("std") { for dep in PROC_MACRO_DEPS.trim().lines() {
sysroot.crates[proc_macro].deps.push(std); if let Some(dep) = sysroot.by_name(dep) {
sysroot.crates[proc_macro].deps.push(dep)
}
} }
} }
@ -239,6 +243,7 @@ fn get_rust_src(sysroot_path: &AbsPath) -> Option<AbsPathBuf> {
const SYSROOT_CRATES: &str = " const SYSROOT_CRATES: &str = "
alloc alloc
backtrace
core core
panic_abort panic_abort
panic_unwind panic_unwind
@ -246,17 +251,19 @@ proc_macro
profiler_builtins profiler_builtins
std std
stdarch/crates/std_detect stdarch/crates/std_detect
term
test test
unwind"; unwind";
const ALLOC_DEPS: &str = "core";
const STD_DEPS: &str = " const STD_DEPS: &str = "
alloc alloc
core
panic_abort
panic_unwind panic_unwind
panic_abort
core
profiler_builtins profiler_builtins
unwind
std_detect std_detect
term test";
test
unwind"; const PROC_MACRO_DEPS: &str = "std";

View file

@ -1566,10 +1566,10 @@ fn rust_project_hello_world_project_model() {
}, },
Dependency { Dependency {
crate_id: CrateId( crate_id: CrateId(
1, 3,
), ),
name: CrateName( name: CrateName(
"core", "panic_unwind",
), ),
prelude: true, prelude: true,
}, },
@ -1584,10 +1584,10 @@ fn rust_project_hello_world_project_model() {
}, },
Dependency { Dependency {
crate_id: CrateId( crate_id: CrateId(
3, 1,
), ),
name: CrateName( name: CrateName(
"panic_unwind", "core",
), ),
prelude: true, prelude: true,
}, },
@ -1600,6 +1600,15 @@ fn rust_project_hello_world_project_model() {
), ),
prelude: true, prelude: true,
}, },
Dependency {
crate_id: CrateId(
9,
),
name: CrateName(
"unwind",
),
prelude: true,
},
Dependency { Dependency {
crate_id: CrateId( crate_id: CrateId(
7, 7,
@ -1613,29 +1622,11 @@ fn rust_project_hello_world_project_model() {
crate_id: CrateId( crate_id: CrateId(
8, 8,
), ),
name: CrateName(
"term",
),
prelude: true,
},
Dependency {
crate_id: CrateId(
9,
),
name: CrateName( name: CrateName(
"test", "test",
), ),
prelude: true, prelude: true,
}, },
Dependency {
crate_id: CrateId(
10,
),
name: CrateName(
"unwind",
),
prelude: true,
},
], ],
proc_macro: Err( proc_macro: Err(
"no proc macro loaded for sysroot crate", "no proc macro loaded for sysroot crate",
@ -1687,40 +1678,6 @@ fn rust_project_hello_world_project_model() {
), ),
edition: Edition2018, edition: Edition2018,
version: None, version: None,
display_name: Some(
CrateDisplayName {
crate_name: CrateName(
"term",
),
canonical_name: "term",
},
),
cfg_options: CfgOptions(
[],
),
potential_cfg_options: CfgOptions(
[],
),
env: Env {
entries: {},
},
dependencies: [],
proc_macro: Err(
"no proc macro loaded for sysroot crate",
),
origin: Lang(
Other,
),
is_proc_macro: false,
},
CrateId(
9,
): CrateData {
root_file_id: FileId(
10,
),
edition: Edition2018,
version: None,
display_name: Some( display_name: Some(
CrateDisplayName { CrateDisplayName {
crate_name: CrateName( crate_name: CrateName(
@ -1748,10 +1705,10 @@ fn rust_project_hello_world_project_model() {
is_proc_macro: false, is_proc_macro: false,
}, },
CrateId( CrateId(
10, 9,
): CrateData { ): CrateData {
root_file_id: FileId( root_file_id: FileId(
11, 10,
), ),
edition: Edition2018, edition: Edition2018,
version: None, version: None,
@ -1782,10 +1739,10 @@ fn rust_project_hello_world_project_model() {
is_proc_macro: false, is_proc_macro: false,
}, },
CrateId( CrateId(
11, 10,
): CrateData { ): CrateData {
root_file_id: FileId( root_file_id: FileId(
12, 11,
), ),
edition: Edition2018, edition: Edition2018,
version: None, version: None,
@ -1836,7 +1793,7 @@ fn rust_project_hello_world_project_model() {
}, },
Dependency { Dependency {
crate_id: CrateId( crate_id: CrateId(
9, 8,
), ),
name: CrateName( name: CrateName(
"test", "test",

View file

@ -377,6 +377,21 @@ impl ProjectWorkspace {
} }
} }
pub fn find_sysroot_proc_macro_srv(&self) -> Option<AbsPathBuf> {
match self {
ProjectWorkspace::Cargo { sysroot: Some(sysroot), .. }
| ProjectWorkspace::Json { sysroot: Some(sysroot), .. } => {
let standalone_server_name =
format!("rust-analyzer-proc-macro-srv{}", std::env::consts::EXE_SUFFIX);
["libexec", "lib"]
.into_iter()
.map(|segment| sysroot.root().join(segment).join(&standalone_server_name))
.find(|server_path| std::fs::metadata(&server_path).is_ok())
}
_ => None,
}
}
/// Returns the roots for the current `ProjectWorkspace` /// Returns the roots for the current `ProjectWorkspace`
/// The return type contains the path and whether or not /// The return type contains the path and whether or not
/// the root is a member of the current workspace /// the root is a member of the current workspace
@ -509,14 +524,14 @@ impl ProjectWorkspace {
build_scripts, build_scripts,
toolchain: _, toolchain: _,
} => cargo_to_crate_graph( } => cargo_to_crate_graph(
rustc_cfg.clone(),
cfg_overrides,
load_proc_macro, load_proc_macro,
load, load,
cargo,
build_scripts,
sysroot.as_ref(),
rustc, rustc,
cargo,
sysroot.as_ref(),
rustc_cfg.clone(),
cfg_overrides,
build_scripts,
), ),
ProjectWorkspace::DetachedFiles { files, sysroot, rustc_cfg } => { ProjectWorkspace::DetachedFiles { files, sysroot, rustc_cfg } => {
detached_files_to_crate_graph(rustc_cfg.clone(), load, files, sysroot) detached_files_to_crate_graph(rustc_cfg.clone(), load, files, sysroot)
@ -602,7 +617,7 @@ fn project_json_to_crate_graph(
for (from, krate) in project.crates() { for (from, krate) in project.crates() {
if let Some(&from) = crates.get(&from) { if let Some(&from) = crates.get(&from) {
if let Some((public_deps, libproc_macro)) = &sysroot_deps { if let Some((public_deps, libproc_macro)) = &sysroot_deps {
public_deps.add(from, &mut crate_graph); public_deps.add_to_crate_graph(&mut crate_graph, from);
if krate.is_proc_macro { if krate.is_proc_macro {
if let Some(proc_macro) = libproc_macro { if let Some(proc_macro) = libproc_macro {
add_dep( add_dep(
@ -626,14 +641,14 @@ fn project_json_to_crate_graph(
} }
fn cargo_to_crate_graph( fn cargo_to_crate_graph(
rustc_cfg: Vec<CfgFlag>,
override_cfg: &CfgOverrides,
load_proc_macro: &mut dyn FnMut(&str, &AbsPath) -> ProcMacroLoadResult, load_proc_macro: &mut dyn FnMut(&str, &AbsPath) -> ProcMacroLoadResult,
load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
cargo: &CargoWorkspace,
build_scripts: &WorkspaceBuildScripts,
sysroot: Option<&Sysroot>,
rustc: &Option<CargoWorkspace>, rustc: &Option<CargoWorkspace>,
cargo: &CargoWorkspace,
sysroot: Option<&Sysroot>,
rustc_cfg: Vec<CfgFlag>,
override_cfg: &CfgOverrides,
build_scripts: &WorkspaceBuildScripts,
) -> CrateGraph { ) -> CrateGraph {
let _p = profile::span("cargo_to_crate_graph"); let _p = profile::span("cargo_to_crate_graph");
let mut crate_graph = CrateGraph::default(); let mut crate_graph = CrateGraph::default();
@ -642,13 +657,15 @@ fn cargo_to_crate_graph(
None => (SysrootPublicDeps::default(), None), None => (SysrootPublicDeps::default(), None),
}; };
let mut cfg_options = CfgOptions::default(); let cfg_options = {
cfg_options.extend(rustc_cfg); let mut cfg_options = CfgOptions::default();
cfg_options.extend(rustc_cfg);
cfg_options.insert_atom("debug_assertions".into());
cfg_options
};
let mut pkg_to_lib_crate = FxHashMap::default(); let mut pkg_to_lib_crate = FxHashMap::default();
cfg_options.insert_atom("debug_assertions".into());
let mut pkg_crates = FxHashMap::default(); let mut pkg_crates = FxHashMap::default();
// Does any crate signal to rust-analyzer that they need the rustc_private crates? // Does any crate signal to rust-analyzer that they need the rustc_private crates?
let mut has_private = false; let mut has_private = false;
@ -723,7 +740,7 @@ fn cargo_to_crate_graph(
// Set deps to the core, std and to the lib target of the current package // Set deps to the core, std and to the lib target of the current package
for &(from, kind) in pkg_crates.get(&pkg).into_iter().flatten() { for &(from, kind) in pkg_crates.get(&pkg).into_iter().flatten() {
// Add sysroot deps first so that a lib target named `core` etc. can overwrite them. // Add sysroot deps first so that a lib target named `core` etc. can overwrite them.
public_deps.add(from, &mut crate_graph); public_deps.add_to_crate_graph(&mut crate_graph, from);
if let Some((to, name)) = lib_tgt.clone() { if let Some((to, name)) = lib_tgt.clone() {
if to != from && kind != TargetKind::BuildScript { if to != from && kind != TargetKind::BuildScript {
@ -767,15 +784,16 @@ fn cargo_to_crate_graph(
if let Some(rustc_workspace) = rustc { if let Some(rustc_workspace) = rustc {
handle_rustc_crates( handle_rustc_crates(
&mut crate_graph, &mut crate_graph,
rustc_workspace, &mut pkg_to_lib_crate,
load, load,
load_proc_macro,
rustc_workspace,
cargo,
&public_deps,
libproc_macro,
&pkg_crates,
&cfg_options, &cfg_options,
override_cfg, override_cfg,
load_proc_macro,
&mut pkg_to_lib_crate,
&public_deps,
cargo,
&pkg_crates,
build_scripts, build_scripts,
); );
} }
@ -825,28 +843,29 @@ fn detached_files_to_crate_graph(
}, },
); );
public_deps.add(detached_file_crate, &mut crate_graph); public_deps.add_to_crate_graph(&mut crate_graph, detached_file_crate);
} }
crate_graph crate_graph
} }
fn handle_rustc_crates( fn handle_rustc_crates(
crate_graph: &mut CrateGraph, crate_graph: &mut CrateGraph,
rustc_workspace: &CargoWorkspace, pkg_to_lib_crate: &mut FxHashMap<Package, CrateId>,
load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
load_proc_macro: &mut dyn FnMut(&str, &AbsPath) -> ProcMacroLoadResult,
rustc_workspace: &CargoWorkspace,
cargo: &CargoWorkspace,
public_deps: &SysrootPublicDeps,
libproc_macro: Option<CrateId>,
pkg_crates: &FxHashMap<Package, Vec<(CrateId, TargetKind)>>,
cfg_options: &CfgOptions, cfg_options: &CfgOptions,
override_cfg: &CfgOverrides, override_cfg: &CfgOverrides,
load_proc_macro: &mut dyn FnMut(&str, &AbsPath) -> ProcMacroLoadResult,
pkg_to_lib_crate: &mut FxHashMap<Package, CrateId>,
public_deps: &SysrootPublicDeps,
cargo: &CargoWorkspace,
pkg_crates: &FxHashMap<Package, Vec<(CrateId, TargetKind)>>,
build_scripts: &WorkspaceBuildScripts, build_scripts: &WorkspaceBuildScripts,
) { ) {
let mut rustc_pkg_crates = FxHashMap::default(); let mut rustc_pkg_crates = FxHashMap::default();
// The root package of the rustc-dev component is rustc_driver, so we match that // The root package of the rustc-dev component is rustc_driver, so we match that
let root_pkg = let root_pkg =
rustc_workspace.packages().find(|package| rustc_workspace[*package].name == "rustc_driver"); rustc_workspace.packages().find(|&package| rustc_workspace[package].name == "rustc_driver");
// The rustc workspace might be incomplete (such as if rustc-dev is not // The rustc workspace might be incomplete (such as if rustc-dev is not
// installed for the current toolchain) and `rustc_source` is set to discover. // installed for the current toolchain) and `rustc_source` is set to discover.
if let Some(root_pkg) = root_pkg { if let Some(root_pkg) = root_pkg {
@ -901,7 +920,16 @@ fn handle_rustc_crates(
); );
pkg_to_lib_crate.insert(pkg, crate_id); pkg_to_lib_crate.insert(pkg, crate_id);
// Add dependencies on core / std / alloc for this crate // Add dependencies on core / std / alloc for this crate
public_deps.add(crate_id, crate_graph); public_deps.add_to_crate_graph(crate_graph, crate_id);
if let Some(proc_macro) = libproc_macro {
add_dep_with_prelude(
crate_graph,
crate_id,
CrateName::new("proc_macro").unwrap(),
proc_macro,
rustc_workspace[tgt].is_proc_macro,
);
}
rustc_pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id); rustc_pkg_crates.entry(pkg).or_insert_with(Vec::new).push(crate_id);
} }
} }
@ -1009,7 +1037,7 @@ struct SysrootPublicDeps {
impl SysrootPublicDeps { impl SysrootPublicDeps {
/// Makes `from` depend on the public sysroot crates. /// Makes `from` depend on the public sysroot crates.
fn add(&self, from: CrateId, crate_graph: &mut CrateGraph) { fn add_to_crate_graph(&self, crate_graph: &mut CrateGraph, from: CrateId) {
for (name, krate, prelude) in &self.deps { for (name, krate, prelude) in &self.deps {
add_dep_with_prelude(crate_graph, from, name.clone(), *krate, *prelude); add_dep_with_prelude(crate_graph, from, name.clone(), *krate, *prelude);
} }

View file

@ -8,7 +8,7 @@ documentation = "https://rust-analyzer.github.io/manual.html"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
autobins = false autobins = false
edition = "2021" edition = "2021"
rust-version = "1.57" rust-version = "1.65"
[lib] [lib]
doctest = false doctest = false

View file

@ -3,11 +3,11 @@
use std::mem; use std::mem;
use cfg::{CfgAtom, CfgExpr}; use cfg::{CfgAtom, CfgExpr};
use ide::{FileId, RunnableKind, TestId}; use ide::{Cancellable, FileId, RunnableKind, TestId};
use project_model::{self, CargoFeatures, ManifestPath, TargetKind}; use project_model::{self, CargoFeatures, ManifestPath, TargetKind};
use vfs::AbsPathBuf; use vfs::AbsPathBuf;
use crate::{global_state::GlobalStateSnapshot, Result}; use crate::global_state::GlobalStateSnapshot;
/// Abstract representation of Cargo target. /// Abstract representation of Cargo target.
/// ///
@ -29,7 +29,7 @@ impl CargoTargetSpec {
spec: Option<CargoTargetSpec>, spec: Option<CargoTargetSpec>,
kind: &RunnableKind, kind: &RunnableKind,
cfg: &Option<CfgExpr>, cfg: &Option<CfgExpr>,
) -> Result<(Vec<String>, Vec<String>)> { ) -> (Vec<String>, Vec<String>) {
let mut args = Vec::new(); let mut args = Vec::new();
let mut extra_args = Vec::new(); let mut extra_args = Vec::new();
@ -111,13 +111,13 @@ impl CargoTargetSpec {
} }
} }
} }
Ok((args, extra_args)) (args, extra_args)
} }
pub(crate) fn for_file( pub(crate) fn for_file(
global_state_snapshot: &GlobalStateSnapshot, global_state_snapshot: &GlobalStateSnapshot,
file_id: FileId, file_id: FileId,
) -> Result<Option<CargoTargetSpec>> { ) -> Cancellable<Option<CargoTargetSpec>> {
let crate_id = match &*global_state_snapshot.analysis.crates_for(file_id)? { let crate_id = match &*global_state_snapshot.analysis.crates_for(file_id)? {
&[crate_id, ..] => crate_id, &[crate_id, ..] => crate_id,
_ => return Ok(None), _ => return Ok(None),

View file

@ -60,24 +60,12 @@ pub fn load_workspace(
}; };
let proc_macro_client = if load_config.with_proc_macro { let proc_macro_client = if load_config.with_proc_macro {
let mut path = AbsPathBuf::assert(std::env::current_exe()?); let (server_path, args): (_, &[_]) = match ws.find_sysroot_proc_macro_srv() {
let mut args = vec!["proc-macro"]; Some(server_path) => (server_path, &[]),
None => (AbsPathBuf::assert(std::env::current_exe()?), &["proc-macro"]),
};
if let ProjectWorkspace::Cargo { sysroot, .. } | ProjectWorkspace::Json { sysroot, .. } = ProcMacroServer::spawn(server_path, args).map_err(|e| e.to_string())
&ws
{
if let Some(sysroot) = sysroot.as_ref() {
let standalone_server_name =
format!("rust-analyzer-proc-macro-srv{}", std::env::consts::EXE_SUFFIX);
let server_path = sysroot.root().join("libexec").join(&standalone_server_name);
if std::fs::metadata(&server_path).is_ok() {
path = server_path;
args = vec![];
}
}
}
ProcMacroServer::spawn(path.clone(), args.clone()).map_err(|e| e.to_string())
} else { } else {
Err("proc macro server disabled".to_owned()) Err("proc macro server disabled".to_owned())
}; };

View file

@ -47,30 +47,27 @@ impl flags::Scip {
let si = StaticIndex::compute(&analysis); let si = StaticIndex::compute(&analysis);
let mut index = scip_types::Index { let metadata = scip_types::Metadata {
metadata: Some(scip_types::Metadata { version: scip_types::ProtocolVersion::UnspecifiedProtocolVersion.into(),
version: scip_types::ProtocolVersion::UnspecifiedProtocolVersion.into(), tool_info: Some(scip_types::ToolInfo {
tool_info: Some(scip_types::ToolInfo { name: "rust-analyzer".to_owned(),
name: "rust-analyzer".to_owned(), version: "0.1".to_owned(),
version: "0.1".to_owned(), arguments: vec![],
arguments: vec![], special_fields: Default::default(),
..Default::default()
})
.into(),
project_root: format!(
"file://{}",
path.normalize()
.as_os_str()
.to_str()
.ok_or(anyhow::anyhow!("Unable to normalize project_root path"))?
.to_string()
),
text_document_encoding: scip_types::TextEncoding::UTF8.into(),
..Default::default()
}) })
.into(), .into(),
..Default::default() project_root: format!(
"file://{}",
path.normalize()
.as_os_str()
.to_str()
.ok_or(anyhow::anyhow!("Unable to normalize project_root path"))?
.to_string()
),
text_document_encoding: scip_types::TextEncoding::UTF8.into(),
special_fields: Default::default(),
}; };
let mut documents = Vec::new();
let mut symbols_emitted: HashSet<TokenId> = HashSet::default(); let mut symbols_emitted: HashSet<TokenId> = HashSet::default();
let mut tokens_to_symbol: HashMap<TokenId, String> = HashMap::new(); let mut tokens_to_symbol: HashMap<TokenId, String> = HashMap::new();
@ -95,18 +92,14 @@ impl flags::Scip {
endings: LineEndings::Unix, endings: LineEndings::Unix,
}; };
let mut doc = scip_types::Document { let mut occurrences = Vec::new();
relative_path, let mut symbols = Vec::new();
language: "rust".to_string(),
..Default::default()
};
tokens.into_iter().for_each(|(range, id)| { tokens.into_iter().for_each(|(text_range, id)| {
let token = si.tokens.get(id).unwrap(); let token = si.tokens.get(id).unwrap();
let mut occurrence = scip_types::Occurrence::default(); let range = text_range_to_scip_range(&line_index, text_range);
occurrence.range = text_range_to_scip_range(&line_index, range); let symbol = tokens_to_symbol
occurrence.symbol = tokens_to_symbol
.entry(id) .entry(id)
.or_insert_with(|| { .or_insert_with(|| {
let symbol = token_to_symbol(&token).unwrap_or_else(&mut new_local_symbol); let symbol = token_to_symbol(&token).unwrap_or_else(&mut new_local_symbol);
@ -114,34 +107,62 @@ impl flags::Scip {
}) })
.clone(); .clone();
let mut symbol_roles = Default::default();
if let Some(def) = token.definition { if let Some(def) = token.definition {
if def.range == range { if def.range == text_range {
occurrence.symbol_roles |= scip_types::SymbolRole::Definition as i32; symbol_roles |= scip_types::SymbolRole::Definition as i32;
} }
if symbols_emitted.insert(id) { if symbols_emitted.insert(id) {
let mut symbol_info = scip_types::SymbolInformation::default(); let documentation = token
symbol_info.symbol = occurrence.symbol.clone(); .hover
if let Some(hover) = &token.hover { .as_ref()
if !hover.markup.as_str().is_empty() { .map(|hover| hover.markup.as_str())
symbol_info.documentation = vec![hover.markup.as_str().to_string()]; .filter(|it| !it.is_empty())
} .map(|it| vec![it.to_owned()]);
} let symbol_info = scip_types::SymbolInformation {
symbol: symbol.clone(),
documentation: documentation.unwrap_or_default(),
relationships: Vec::new(),
special_fields: Default::default(),
};
doc.symbols.push(symbol_info) symbols.push(symbol_info)
} }
} }
doc.occurrences.push(occurrence); occurrences.push(scip_types::Occurrence {
range,
symbol,
symbol_roles,
override_documentation: Vec::new(),
syntax_kind: Default::default(),
diagnostics: Vec::new(),
special_fields: Default::default(),
});
}); });
if doc.occurrences.is_empty() { if occurrences.is_empty() {
continue; continue;
} }
index.documents.push(doc); documents.push(scip_types::Document {
relative_path,
language: "rust".to_string(),
occurrences,
symbols,
special_fields: Default::default(),
});
} }
let index = scip_types::Index {
metadata: Some(metadata).into(),
documents,
external_symbols: Vec::new(),
special_fields: Default::default(),
};
scip::write_message_to_file("index.scip", index) scip::write_message_to_file("index.scip", index)
.map_err(|err| anyhow::anyhow!("Failed to write scip to file: {}", err))?; .map_err(|err| anyhow::anyhow!("Failed to write scip to file: {}", err))?;
@ -181,7 +202,7 @@ fn new_descriptor_str(
name: name.to_string(), name: name.to_string(),
disambiguator: "".to_string(), disambiguator: "".to_string(),
suffix: suffix.into(), suffix: suffix.into(),
..Default::default() special_fields: Default::default(),
} }
} }
@ -232,11 +253,11 @@ fn token_to_symbol(token: &TokenStaticData) -> Option<scip_types::Symbol> {
manager: "cargo".to_string(), manager: "cargo".to_string(),
name: package_name, name: package_name,
version: version.unwrap_or_else(|| ".".to_string()), version: version.unwrap_or_else(|| ".".to_string()),
..Default::default() special_fields: Default::default(),
}) })
.into(), .into(),
descriptors, descriptors,
..Default::default() special_fields: Default::default(),
}) })
} }

View file

@ -118,9 +118,11 @@ config_data! {
/// This option does not take effect until rust-analyzer is restarted. /// This option does not take effect until rust-analyzer is restarted.
cargo_sysroot: Option<String> = "\"discover\"", cargo_sysroot: Option<String> = "\"discover\"",
/// Compilation target override (target triple). /// Compilation target override (target triple).
// FIXME(@poliorcetics): move to multiple targets here too, but this will need more work
// than `checkOnSave_target`
cargo_target: Option<String> = "null", cargo_target: Option<String> = "null",
/// Unsets `#[cfg(test)]` for the specified crates. /// Unsets `#[cfg(test)]` for the specified crates.
cargo_unsetTest: Vec<String> = "[\"core\"]", cargo_unsetTest: Vec<String> = "[\"core\"]",
/// Check all targets and tests (`--all-targets`). /// Check all targets and tests (`--all-targets`).
checkOnSave_allTargets: bool = "true", checkOnSave_allTargets: bool = "true",
@ -157,7 +159,7 @@ config_data! {
checkOnSave_noDefaultFeatures: Option<bool> = "null", checkOnSave_noDefaultFeatures: Option<bool> = "null",
/// Override the command rust-analyzer uses instead of `cargo check` for /// Override the command rust-analyzer uses instead of `cargo check` for
/// diagnostics on save. The command is required to output json and /// diagnostics on save. The command is required to output json and
/// should therefor include `--message-format=json` or a similar option. /// should therefore include `--message-format=json` or a similar option.
/// ///
/// If you're changing this because you're using some tool wrapping /// If you're changing this because you're using some tool wrapping
/// Cargo, you might also want to change /// Cargo, you might also want to change
@ -174,9 +176,13 @@ config_data! {
/// ``` /// ```
/// . /// .
checkOnSave_overrideCommand: Option<Vec<String>> = "null", checkOnSave_overrideCommand: Option<Vec<String>> = "null",
/// Check for a specific target. Defaults to /// Check for specific targets. Defaults to `#rust-analyzer.cargo.target#` if empty.
/// `#rust-analyzer.cargo.target#`. ///
checkOnSave_target: Option<String> = "null", /// Can be a single target, e.g. `"x86_64-unknown-linux-gnu"` or a list of targets, e.g.
/// `["aarch64-apple-darwin", "x86_64-apple-darwin"]`.
///
/// Aliased as `"checkOnSave.targets"`.
checkOnSave_target | checkOnSave_targets: CheckOnSaveTargets = "[]",
/// Toggles the additional completions that automatically add imports when completed. /// Toggles the additional completions that automatically add imports when completed.
/// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled. /// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled.
@ -261,6 +267,7 @@ config_data! {
files_excludeDirs: Vec<PathBuf> = "[]", files_excludeDirs: Vec<PathBuf> = "[]",
/// Controls file watching implementation. /// Controls file watching implementation.
files_watcher: FilesWatcherDef = "\"client\"", files_watcher: FilesWatcherDef = "\"client\"",
/// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords. /// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords.
highlightRelated_breakPoints_enable: bool = "true", highlightRelated_breakPoints_enable: bool = "true",
/// Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`). /// Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`).
@ -320,6 +327,8 @@ config_data! {
inlayHints_closingBraceHints_minLines: usize = "25", inlayHints_closingBraceHints_minLines: usize = "25",
/// Whether to show inlay type hints for return types of closures. /// Whether to show inlay type hints for return types of closures.
inlayHints_closureReturnTypeHints_enable: ClosureReturnTypeHintsDef = "\"never\"", inlayHints_closureReturnTypeHints_enable: ClosureReturnTypeHintsDef = "\"never\"",
/// Whether to show inlay hints for type adjustments.
inlayHints_expressionAdjustmentHints_enable: AdjustmentHintsDef = "\"never\"",
/// Whether to show inlay type hints for elided lifetimes in function signatures. /// Whether to show inlay type hints for elided lifetimes in function signatures.
inlayHints_lifetimeElisionHints_enable: LifetimeElisionDef = "\"never\"", inlayHints_lifetimeElisionHints_enable: LifetimeElisionDef = "\"never\"",
/// Whether to prefer using parameter names as the name for elided lifetime hints if possible. /// Whether to prefer using parameter names as the name for elided lifetime hints if possible.
@ -329,7 +338,8 @@ config_data! {
/// Whether to show function parameter name inlay hints at the call /// Whether to show function parameter name inlay hints at the call
/// site. /// site.
inlayHints_parameterHints_enable: bool = "true", inlayHints_parameterHints_enable: bool = "true",
/// Whether to show inlay type hints for compiler inserted reborrows. /// Whether to show inlay hints for compiler inserted reborrows.
/// This setting is deprecated in favor of #rust-analyzer.inlayHints.expressionAdjustmentHints.enable#.
inlayHints_reborrowHints_enable: ReborrowHintsDef = "\"never\"", inlayHints_reborrowHints_enable: ReborrowHintsDef = "\"never\"",
/// Whether to render leading colons for type hints, and trailing colons for parameter hints. /// Whether to render leading colons for type hints, and trailing colons for parameter hints.
inlayHints_renderColons: bool = "true", inlayHints_renderColons: bool = "true",
@ -1143,11 +1153,10 @@ impl Config {
} }
Some(_) | None => FlycheckConfig::CargoCommand { Some(_) | None => FlycheckConfig::CargoCommand {
command: self.data.checkOnSave_command.clone(), command: self.data.checkOnSave_command.clone(),
target_triple: self target_triples: match &self.data.checkOnSave_target.0[..] {
.data [] => self.data.cargo_target.clone().into_iter().collect(),
.checkOnSave_target targets => targets.into(),
.clone() },
.or_else(|| self.data.cargo_target.clone()),
all_targets: self.data.checkOnSave_allTargets, all_targets: self.data.checkOnSave_allTargets,
no_default_features: self no_default_features: self
.data .data
@ -1200,10 +1209,15 @@ impl Config {
hide_closure_initialization_hints: self hide_closure_initialization_hints: self
.data .data
.inlayHints_typeHints_hideClosureInitialization, .inlayHints_typeHints_hideClosureInitialization,
reborrow_hints: match self.data.inlayHints_reborrowHints_enable { adjustment_hints: match self.data.inlayHints_expressionAdjustmentHints_enable {
ReborrowHintsDef::Always => ide::ReborrowHints::Always, AdjustmentHintsDef::Always => ide::AdjustmentHints::Always,
ReborrowHintsDef::Never => ide::ReborrowHints::Never, AdjustmentHintsDef::Never => match self.data.inlayHints_reborrowHints_enable {
ReborrowHintsDef::Mutable => ide::ReborrowHints::MutableOnly, ReborrowHintsDef::Always | ReborrowHintsDef::Mutable => {
ide::AdjustmentHints::ReborrowOnly
}
ReborrowHintsDef::Never => ide::AdjustmentHints::Never,
},
AdjustmentHintsDef::Reborrow => ide::AdjustmentHints::ReborrowOnly,
}, },
binding_mode_hints: self.data.inlayHints_bindingModeHints_enable, binding_mode_hints: self.data.inlayHints_bindingModeHints_enable,
param_names_for_lifetime_elision_hints: self param_names_for_lifetime_elision_hints: self
@ -1538,6 +1552,7 @@ mod de_unit_v {
named_unit_variant!(all); named_unit_variant!(all);
named_unit_variant!(skip_trivial); named_unit_variant!(skip_trivial);
named_unit_variant!(mutable); named_unit_variant!(mutable);
named_unit_variant!(reborrow);
named_unit_variant!(with_block); named_unit_variant!(with_block);
} }
@ -1647,6 +1662,9 @@ enum InvocationStrategy {
PerWorkspace, PerWorkspace,
} }
#[derive(Deserialize, Debug, Clone)]
struct CheckOnSaveTargets(#[serde(deserialize_with = "single_or_array")] Vec<String>);
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
enum InvocationLocation { enum InvocationLocation {
@ -1687,6 +1705,17 @@ enum ReborrowHintsDef {
Mutable, Mutable,
} }
#[derive(Deserialize, Debug, Clone)]
#[serde(untagged)]
enum AdjustmentHintsDef {
#[serde(deserialize_with = "true_or_always")]
Always,
#[serde(deserialize_with = "false_or_never")]
Never,
#[serde(deserialize_with = "de_unit_v::reborrow")]
Reborrow,
}
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
enum FilesWatcherDef { enum FilesWatcherDef {
@ -1996,6 +2025,19 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
"Only show mutable reborrow hints." "Only show mutable reborrow hints."
] ]
}, },
"AdjustmentHintsDef" => set! {
"type": "string",
"enum": [
"always",
"never",
"reborrow"
],
"enumDescriptions": [
"Always show all adjustment hints.",
"Never show adjustment hints.",
"Only show auto borrow and dereference adjustment hints."
]
},
"CargoFeaturesDef" => set! { "CargoFeaturesDef" => set! {
"anyOf": [ "anyOf": [
{ {
@ -2084,6 +2126,17 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
"The command will be executed in the project root." "The command will be executed in the project root."
], ],
}, },
"CheckOnSaveTargets" => set! {
"anyOf": [
{
"type": "string",
},
{
"type": "array",
"items": { "type": "string" }
},
],
},
_ => panic!("missing entry for {}: {}", ty, default), _ => panic!("missing entry for {}: {}", ty, default),
} }

View file

@ -359,14 +359,15 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
.iter() .iter()
.flat_map(|primary_span| { .flat_map(|primary_span| {
let primary_location = primary_location(config, workspace_root, primary_span, snap); let primary_location = primary_location(config, workspace_root, primary_span, snap);
let message = {
let mut message = message.clone(); let mut message = message.clone();
if needs_primary_span_label { if needs_primary_span_label {
if let Some(primary_span_label) = &primary_span.label { if let Some(primary_span_label) = &primary_span.label {
format_to!(message, "\n{}", primary_span_label); format_to!(message, "\n{}", primary_span_label);
}
} }
} message
};
// Each primary diagnostic span may result in multiple LSP diagnostics. // Each primary diagnostic span may result in multiple LSP diagnostics.
let mut diagnostics = Vec::new(); let mut diagnostics = Vec::new();
@ -417,7 +418,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
message: message.clone(), message: message.clone(),
related_information: Some(information_for_additional_diagnostic), related_information: Some(information_for_additional_diagnostic),
tags: if tags.is_empty() { None } else { Some(tags.clone()) }, tags: if tags.is_empty() { None } else { Some(tags.clone()) },
data: None, data: Some(serde_json::json!({ "rendered": rd.rendered })),
}; };
diagnostics.push(MappedRustDiagnostic { diagnostics.push(MappedRustDiagnostic {
url: secondary_location.uri, url: secondary_location.uri,
@ -449,7 +450,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
} }
}, },
tags: if tags.is_empty() { None } else { Some(tags.clone()) }, tags: if tags.is_empty() { None } else { Some(tags.clone()) },
data: None, data: Some(serde_json::json!({ "rendered": rd.rendered })),
}, },
fix: None, fix: None,
}); });
@ -534,7 +535,8 @@ mod tests {
Config::new(workspace_root.to_path_buf(), ClientCapabilities::default()), Config::new(workspace_root.to_path_buf(), ClientCapabilities::default()),
); );
let snap = state.snapshot(); let snap = state.snapshot();
let actual = map_rust_diagnostic_to_lsp(&config, &diagnostic, workspace_root, &snap); let mut actual = map_rust_diagnostic_to_lsp(&config, &diagnostic, workspace_root, &snap);
actual.iter_mut().for_each(|diag| diag.diagnostic.data = None);
expect.assert_debug_eq(&actual) expect.assert_debug_eq(&actual)
} }

View file

@ -42,8 +42,10 @@ pub(crate) fn offset(line_index: &LineIndex, position: lsp_types::Position) -> R
pub(crate) fn text_range(line_index: &LineIndex, range: lsp_types::Range) -> Result<TextRange> { pub(crate) fn text_range(line_index: &LineIndex, range: lsp_types::Range) -> Result<TextRange> {
let start = offset(line_index, range.start)?; let start = offset(line_index, range.start)?;
let end = offset(line_index, range.end)?; let end = offset(line_index, range.end)?;
let text_range = TextRange::new(start, end); match end < start {
Ok(text_range) true => Err(format_err!("Invalid Range").into()),
false => Ok(TextRange::new(start, end)),
}
} }
pub(crate) fn file_id(snap: &GlobalStateSnapshot, url: &lsp_types::Url) -> Result<FileId> { pub(crate) fn file_id(snap: &GlobalStateSnapshot, url: &lsp_types::Url) -> Result<FileId> {

View file

@ -100,7 +100,7 @@ pub(crate) struct GlobalState {
/// the user just adds comments or whitespace to Cargo.toml, we do not want /// the user just adds comments or whitespace to Cargo.toml, we do not want
/// to invalidate any salsa caches. /// to invalidate any salsa caches.
pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>, pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>,
pub(crate) fetch_workspaces_queue: OpQueue<Vec<anyhow::Result<ProjectWorkspace>>>, pub(crate) fetch_workspaces_queue: OpQueue<Option<Vec<anyhow::Result<ProjectWorkspace>>>>,
pub(crate) fetch_build_data_queue: pub(crate) fetch_build_data_queue:
OpQueue<(Arc<Vec<ProjectWorkspace>>, Vec<anyhow::Result<WorkspaceBuildScripts>>)>, OpQueue<(Arc<Vec<ProjectWorkspace>>, Vec<anyhow::Result<WorkspaceBuildScripts>>)>,

View file

@ -9,9 +9,9 @@ use std::{
use anyhow::Context; use anyhow::Context;
use ide::{ use ide::{
AnnotationConfig, AssistKind, AssistResolveStrategy, FileId, FilePosition, FileRange, AnnotationConfig, AssistKind, AssistResolveStrategy, Cancellable, FileId, FilePosition,
HoverAction, HoverGotoTypeData, Query, RangeInfo, ReferenceCategory, Runnable, RunnableKind, FileRange, HoverAction, HoverGotoTypeData, Query, RangeInfo, ReferenceCategory, Runnable,
SingleResolve, SourceChange, TextEdit, RunnableKind, SingleResolve, SourceChange, TextEdit,
}; };
use ide_db::SymbolKind; use ide_db::SymbolKind;
use lsp_server::ErrorCode; use lsp_server::ErrorCode;
@ -556,7 +556,7 @@ pub(crate) fn handle_will_rename_files(
if source_change.source_file_edits.is_empty() { if source_change.source_file_edits.is_empty() {
Ok(None) Ok(None)
} else { } else {
to_proto::workspace_edit(&snap, source_change).map(Some) Ok(Some(to_proto::workspace_edit(&snap, source_change)?))
} }
} }
@ -1313,7 +1313,7 @@ pub(crate) fn handle_ssr(
position, position,
selections, selections,
)??; )??;
to_proto::workspace_edit(&snap, source_change) to_proto::workspace_edit(&snap, source_change).map_err(Into::into)
} }
pub(crate) fn publish_diagnostics( pub(crate) fn publish_diagnostics(
@ -1354,13 +1354,12 @@ pub(crate) fn handle_inlay_hints(
) -> Result<Option<Vec<InlayHint>>> { ) -> Result<Option<Vec<InlayHint>>> {
let _p = profile::span("handle_inlay_hints"); let _p = profile::span("handle_inlay_hints");
let document_uri = &params.text_document.uri; let document_uri = &params.text_document.uri;
let file_id = from_proto::file_id(&snap, document_uri)?; let FileRange { file_id, range } = from_proto::file_range(
let line_index = snap.file_line_index(file_id)?;
let range = from_proto::file_range(
&snap, &snap,
TextDocumentIdentifier::new(document_uri.to_owned()), TextDocumentIdentifier::new(document_uri.to_owned()),
params.range, params.range,
)?; )?;
let line_index = snap.file_line_index(file_id)?;
let inlay_hints_config = snap.config.inlay_hints(); let inlay_hints_config = snap.config.inlay_hints();
Ok(Some( Ok(Some(
snap.analysis snap.analysis
@ -1369,7 +1368,7 @@ pub(crate) fn handle_inlay_hints(
.map(|it| { .map(|it| {
to_proto::inlay_hint(&snap, &line_index, inlay_hints_config.render_colons, it) to_proto::inlay_hint(&snap, &line_index, inlay_hints_config.render_colons, it)
}) })
.collect::<Result<Vec<_>>>()?, .collect::<Cancellable<Vec<_>>>()?,
)) ))
} }
@ -1426,7 +1425,7 @@ pub(crate) fn handle_call_hierarchy_prepare(
.into_iter() .into_iter()
.filter(|it| it.kind == Some(SymbolKind::Function)) .filter(|it| it.kind == Some(SymbolKind::Function))
.map(|it| to_proto::call_hierarchy_item(&snap, it)) .map(|it| to_proto::call_hierarchy_item(&snap, it))
.collect::<Result<Vec<_>>>()?; .collect::<Cancellable<Vec<_>>>()?;
Ok(Some(res)) Ok(Some(res))
} }

View file

@ -27,10 +27,6 @@ pub(crate) enum LineEndings {
impl LineEndings { impl LineEndings {
/// Replaces `\r\n` with `\n` in-place in `src`. /// Replaces `\r\n` with `\n` in-place in `src`.
pub(crate) fn normalize(src: String) -> (String, LineEndings) { pub(crate) fn normalize(src: String) -> (String, LineEndings) {
if !src.as_bytes().contains(&b'\r') {
return (src, LineEndings::Unix);
}
// We replace `\r\n` with `\n` in-place, which doesn't break utf-8 encoding. // We replace `\r\n` with `\n` in-place, which doesn't break utf-8 encoding.
// While we *can* call `as_mut_vec` and do surgery on the live string // While we *can* call `as_mut_vec` and do surgery on the live string
// directly, let's rather steal the contents of `src`. This makes the code // directly, let's rather steal the contents of `src`. This makes the code
@ -39,10 +35,19 @@ impl LineEndings {
let mut buf = src.into_bytes(); let mut buf = src.into_bytes();
let mut gap_len = 0; let mut gap_len = 0;
let mut tail = buf.as_mut_slice(); let mut tail = buf.as_mut_slice();
let mut crlf_seen = false;
let find_crlf = |src: &[u8]| src.windows(2).position(|it| it == b"\r\n");
loop { loop {
let idx = match find_crlf(&tail[gap_len..]) { let idx = match find_crlf(&tail[gap_len..]) {
None => tail.len(), None if crlf_seen => tail.len(),
Some(idx) => idx + gap_len, // SAFETY: buf is unchanged and therefore still contains utf8 data
None => return (unsafe { String::from_utf8_unchecked(buf) }, LineEndings::Unix),
Some(idx) => {
crlf_seen = true;
idx + gap_len
}
}; };
tail.copy_within(gap_len..idx, 0); tail.copy_within(gap_len..idx, 0);
tail = &mut tail[idx - gap_len..]; tail = &mut tail[idx - gap_len..];
@ -54,15 +59,48 @@ impl LineEndings {
// Account for removed `\r`. // Account for removed `\r`.
// After `set_len`, `buf` is guaranteed to contain utf-8 again. // After `set_len`, `buf` is guaranteed to contain utf-8 again.
let new_len = buf.len() - gap_len;
let src = unsafe { let src = unsafe {
let new_len = buf.len() - gap_len;
buf.set_len(new_len); buf.set_len(new_len);
String::from_utf8_unchecked(buf) String::from_utf8_unchecked(buf)
}; };
return (src, LineEndings::Dos); (src, LineEndings::Dos)
}
fn find_crlf(src: &[u8]) -> Option<usize> { }
src.windows(2).position(|it| it == b"\r\n")
} #[cfg(test)]
mod tests {
use super::*;
#[test]
fn unix() {
let src = "a\nb\nc\n\n\n\n";
let (res, endings) = LineEndings::normalize(src.into());
assert_eq!(endings, LineEndings::Unix);
assert_eq!(res, src);
}
#[test]
fn dos() {
let src = "\r\na\r\n\r\nb\r\nc\r\n\r\n\r\n\r\n";
let (res, endings) = LineEndings::normalize(src.into());
assert_eq!(endings, LineEndings::Dos);
assert_eq!(res, "\na\n\nb\nc\n\n\n\n");
}
#[test]
fn mixed() {
let src = "a\r\nb\r\nc\r\n\n\r\n\n";
let (res, endings) = LineEndings::normalize(src.into());
assert_eq!(endings, LineEndings::Dos);
assert_eq!(res, "a\nb\nc\n\n\n\n");
}
#[test]
fn none() {
let src = "abc";
let (res, endings) = LineEndings::normalize(src.into());
assert_eq!(endings, LineEndings::Unix);
assert_eq!(res, src);
} }
} }

View file

@ -1,5 +1,5 @@
//! Utilities for LSP-related boilerplate code. //! Utilities for LSP-related boilerplate code.
use std::{ops::Range, sync::Arc}; use std::{mem, ops::Range, sync::Arc};
use lsp_server::Notification; use lsp_server::Notification;
@ -133,11 +133,37 @@ impl GlobalState {
} }
pub(crate) fn apply_document_changes( pub(crate) fn apply_document_changes(
old_text: &mut String, file_contents: impl FnOnce() -> String,
content_changes: Vec<lsp_types::TextDocumentContentChangeEvent>, mut content_changes: Vec<lsp_types::TextDocumentContentChangeEvent>,
) { ) -> String {
// Skip to the last full document change, as it invalidates all previous changes anyways.
let mut start = content_changes
.iter()
.rev()
.position(|change| change.range.is_none())
.map(|idx| content_changes.len() - idx - 1)
.unwrap_or(0);
let mut text: String = match content_changes.get_mut(start) {
// peek at the first content change as an optimization
Some(lsp_types::TextDocumentContentChangeEvent { range: None, text, .. }) => {
let text = mem::take(text);
start += 1;
// The only change is a full document update
if start == content_changes.len() {
return text;
}
text
}
Some(_) => file_contents(),
// we received no content changes
None => return file_contents(),
};
let mut line_index = LineIndex { let mut line_index = LineIndex {
index: Arc::new(ide::LineIndex::new(old_text)), // the index will be overwritten in the bottom loop's first iteration
index: Arc::new(ide::LineIndex::new(&text)),
// We don't care about line endings or offset encoding here. // We don't care about line endings or offset encoding here.
endings: LineEndings::Unix, endings: LineEndings::Unix,
encoding: PositionEncoding::Utf16, encoding: PositionEncoding::Utf16,
@ -148,38 +174,20 @@ pub(crate) fn apply_document_changes(
// Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we // Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we
// remember the last valid line in the index and only rebuild it if needed. // remember the last valid line in the index and only rebuild it if needed.
// The VFS will normalize the end of lines to `\n`. // The VFS will normalize the end of lines to `\n`.
enum IndexValid { let mut index_valid = !0u32;
All,
UpToLineExclusive(u32),
}
impl IndexValid {
fn covers(&self, line: u32) -> bool {
match *self {
IndexValid::UpToLineExclusive(to) => to > line,
_ => true,
}
}
}
let mut index_valid = IndexValid::All;
for change in content_changes { for change in content_changes {
match change.range { // The None case can't happen as we have handled it above already
Some(range) => { if let Some(range) = change.range {
if !index_valid.covers(range.end.line) { if index_valid <= range.end.line {
line_index.index = Arc::new(ide::LineIndex::new(old_text)); *Arc::make_mut(&mut line_index.index) = ide::LineIndex::new(&text);
}
index_valid = IndexValid::UpToLineExclusive(range.start.line);
if let Ok(range) = from_proto::text_range(&line_index, range) {
old_text.replace_range(Range::<usize>::from(range), &change.text);
}
} }
None => { index_valid = range.start.line;
*old_text = change.text; if let Ok(range) = from_proto::text_range(&line_index, range) {
index_valid = IndexValid::UpToLineExclusive(0); text.replace_range(Range::<usize>::from(range), &change.text);
} }
} }
} }
text
} }
/// Checks that the edits inside the completion and the additional edits do not overlap. /// Checks that the edits inside the completion and the additional edits do not overlap.
@ -242,11 +250,10 @@ mod tests {
}; };
} }
let mut text = String::new(); let text = apply_document_changes(|| String::new(), vec![]);
apply_document_changes(&mut text, vec![]);
assert_eq!(text, ""); assert_eq!(text, "");
apply_document_changes( let text = apply_document_changes(
&mut text, || text,
vec![TextDocumentContentChangeEvent { vec![TextDocumentContentChangeEvent {
range: None, range: None,
range_length: None, range_length: None,
@ -254,39 +261,39 @@ mod tests {
}], }],
); );
assert_eq!(text, "the"); assert_eq!(text, "the");
apply_document_changes(&mut text, c![0, 3; 0, 3 => " quick"]); let text = apply_document_changes(|| text, c![0, 3; 0, 3 => " quick"]);
assert_eq!(text, "the quick"); assert_eq!(text, "the quick");
apply_document_changes(&mut text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]); let text = apply_document_changes(|| text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]);
assert_eq!(text, "quick foxes"); assert_eq!(text, "quick foxes");
apply_document_changes(&mut text, c![0, 11; 0, 11 => "\ndream"]); let text = apply_document_changes(|| text, c![0, 11; 0, 11 => "\ndream"]);
assert_eq!(text, "quick foxes\ndream"); assert_eq!(text, "quick foxes\ndream");
apply_document_changes(&mut text, c![1, 0; 1, 0 => "have "]); let text = apply_document_changes(|| text, c![1, 0; 1, 0 => "have "]);
assert_eq!(text, "quick foxes\nhave dream"); assert_eq!(text, "quick foxes\nhave dream");
apply_document_changes( let text = apply_document_changes(
&mut text, || text,
c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"], c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"],
); );
assert_eq!(text, "the quick foxes\nhave quiet dreams\n"); assert_eq!(text, "the quick foxes\nhave quiet dreams\n");
apply_document_changes(&mut text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]); let text = apply_document_changes(|| text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]);
assert_eq!(text, "the quick foxes\n\nhave quiet dreams\n\n"); assert_eq!(text, "the quick foxes\n\nhave quiet dreams\n\n");
apply_document_changes( let text = apply_document_changes(
&mut text, || text,
c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"], c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"],
); );
assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n"); assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n");
apply_document_changes(&mut text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]); let text = apply_document_changes(|| text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]);
assert_eq!(text, "the quick \nthey have quiet dreams\n"); assert_eq!(text, "the quick \nthey have quiet dreams\n");
text = String::from("❤️"); let text = String::from("❤️");
apply_document_changes(&mut text, c![0, 0; 0, 0 => "a"]); let text = apply_document_changes(|| text, c![0, 0; 0, 0 => "a"]);
assert_eq!(text, "a❤"); assert_eq!(text, "a❤");
text = String::from("a\nb"); let text = String::from("a\nb");
apply_document_changes(&mut text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]); let text = apply_document_changes(|| text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]);
assert_eq!(text, "adcb"); assert_eq!(text, "adcb");
text = String::from("a\nb"); let text = String::from("a\nb");
apply_document_changes(&mut text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]); let text = apply_document_changes(|| text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]);
assert_eq!(text, "ațc\ncb"); assert_eq!(text, "ațc\ncb");
} }

View file

@ -451,7 +451,7 @@ impl GlobalState {
ProjectWorkspaceProgress::Begin => (Progress::Begin, None), ProjectWorkspaceProgress::Begin => (Progress::Begin, None),
ProjectWorkspaceProgress::Report(msg) => (Progress::Report, Some(msg)), ProjectWorkspaceProgress::Report(msg) => (Progress::Report, Some(msg)),
ProjectWorkspaceProgress::End(workspaces) => { ProjectWorkspaceProgress::End(workspaces) => {
self.fetch_workspaces_queue.op_completed(workspaces); self.fetch_workspaces_queue.op_completed(Some(workspaces));
let old = Arc::clone(&self.workspaces); let old = Arc::clone(&self.workspaces);
self.switch_workspaces("fetched workspace".to_string()); self.switch_workspaces("fetched workspace".to_string());
@ -759,8 +759,10 @@ impl GlobalState {
let vfs = &mut this.vfs.write().0; let vfs = &mut this.vfs.write().0;
let file_id = vfs.file_id(&path).unwrap(); let file_id = vfs.file_id(&path).unwrap();
let mut text = String::from_utf8(vfs.file_contents(file_id).to_vec()).unwrap(); let text = apply_document_changes(
apply_document_changes(&mut text, params.content_changes); || std::str::from_utf8(vfs.file_contents(file_id)).unwrap().into(),
params.content_changes,
);
vfs.set_file_contents(path, Some(text.into_bytes())); vfs.set_file_contents(path, Some(text.into_bytes()));
} }

Some files were not shown because too many files have changed in this diff Show more