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

View file

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

View file

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

View file

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

View file

@ -10,11 +10,12 @@ use std::{
time::Duration,
};
use command_group::{CommandGroup, GroupChild};
use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
use paths::AbsPathBuf;
use rustc_hash::FxHashMap;
use serde::Deserialize;
use stdx::{process::streaming_output, JodChild};
use stdx::process::streaming_output;
pub use cargo_metadata::diagnostic::{
Applicability, Diagnostic, DiagnosticCode, DiagnosticLevel, DiagnosticSpan,
@ -39,7 +40,7 @@ pub enum InvocationLocation {
pub enum FlycheckConfig {
CargoCommand {
command: String,
target_triple: Option<String>,
target_triples: Vec<String>,
all_targets: bool,
no_default_features: bool,
all_features: bool,
@ -285,7 +286,7 @@ impl FlycheckActor {
let (mut cmd, args) = match &self.config {
FlycheckConfig::CargoCommand {
command,
target_triple,
target_triples,
no_default_features,
all_targets,
all_features,
@ -299,7 +300,7 @@ impl FlycheckActor {
cmd.args(&["--workspace", "--message-format=json", "--manifest-path"])
.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()]);
}
if *all_targets {
@ -359,10 +360,12 @@ impl FlycheckActor {
}
}
struct JodChild(GroupChild);
/// A handle to a cargo process used for fly-checking.
struct CargoHandle {
/// 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,
thread: jod_thread::JoinHandle<io::Result<(bool, String)>>,
receiver: Receiver<CargoMessage>,
@ -371,10 +374,10 @@ struct CargoHandle {
impl CargoHandle {
fn spawn(mut command: Command) -> std::io::Result<CargoHandle> {
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 stderr = child.stderr.take().unwrap();
let stdout = child.0.inner().stdout.take().unwrap();
let stderr = child.0.inner().stderr.take().unwrap();
let (sender, receiver) = unbounded();
let actor = CargoActor::new(sender, stdout, stderr);
@ -386,13 +389,13 @@ impl CargoHandle {
}
fn cancel(mut self) {
let _ = self.child.kill();
let _ = self.child.wait();
let _ = self.child.0.kill();
let _ = self.child.0.wait();
}
fn join(mut self) -> io::Result<()> {
let _ = self.child.kill();
let exit_status = self.child.wait()?;
let _ = self.child.0.kill();
let exit_status = self.child.0.wait()?;
let (read_at_least_one_message, error) = self.thread.join()?;
if read_at_least_one_message || exit_status.success() {
Ok(())

View file

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

View file

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

View file

@ -666,7 +666,8 @@ pub struct Trait {
pub generic_params: Interned<GenericParams>,
pub is_auto: 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>,
}

View file

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

View file

@ -375,12 +375,21 @@ impl<'a> Printer<'a> {
}
w!(self, "trait {}", name);
self.print_generic_params(generic_params);
match items {
Some(items) => {
self.print_where_clause_and_opening_brace(generic_params);
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, "}}");
}
ModItem::Impl(it) => {

View file

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

View file

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

View file

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

View file

@ -73,7 +73,10 @@ impl DefMap {
pub(crate) fn resolve_visibility(
&self,
db: &dyn DefDatabase,
// module to import to
original_module: LocalModuleId,
// pub(path)
// ^^^^ this
visibility: &RawVisibility,
) -> Option<Visibility> {
let mut vis = match visibility {
@ -115,6 +118,7 @@ impl DefMap {
&self,
db: &dyn DefDatabase,
mode: ResolveMode,
// module to import to
mut original_module: LocalModuleId,
path: &ModPath,
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))

View file

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

View file

@ -119,7 +119,7 @@ use foo::*;
use foo::bar::*;
//- /foo/mod.rs
mod bar;
pub mod bar;
fn Foo() {};
pub struct Foo {};
@ -132,6 +132,7 @@ pub(crate) struct PubCrateStruct;
crate
Foo: t
PubCrateStruct: t v
bar: t
foo: t
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
mod foo {
#[path = "baz.rs"]
mod bar;
pub mod bar;
}
use self::foo::bar::Baz;

View file

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

View file

@ -4,6 +4,7 @@ use std::mem;
use mbe::{SyntheticToken, SyntheticTokenId, TokenMap};
use rustc_hash::FxHashMap;
use smallvec::SmallVec;
use syntax::{
ast::{self, AstNode, HasLoopBody},
match_ast, SyntaxElement, SyntaxKind, SyntaxNode, TextRange,
@ -292,25 +293,34 @@ pub(crate) fn reverse_fixups(
token_map: &TokenMap,
undo_info: &SyntaxFixupUndoInfo,
) {
tt.token_trees.retain(|tt| match tt {
tt::TokenTree::Leaf(leaf) => {
token_map.synthetic_token_id(leaf.id()).is_none()
|| token_map.synthetic_token_id(leaf.id()) != Some(EMPTY_ID)
let tts = std::mem::take(&mut tt.token_trees);
tt.token_trees = tts
.into_iter()
.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| token_map.synthetic_token_id(d.id) != Some(EMPTY_ID))
}
})
.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::Subtree(st) => st.delimiter.map_or(true, |d| {
token_map.synthetic_token_id(d.id).is_none()
|| 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());
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)]
@ -319,6 +329,31 @@ mod tests {
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]
fn check(ra_fixture: &str, mut expect: Expect) {
let parsed = syntax::SourceFile::parse(ra_fixture);
@ -331,17 +366,15 @@ mod tests {
fixups.append,
);
let mut actual = tt.to_string();
actual.push('\n');
let actual = format!("{}\n", tt);
expect.indent(false);
expect.assert_eq(&actual);
// the fixed-up tree should be syntactically valid
let (parse, _) = mbe::token_tree_to_syntax_node(&tt, ::mbe::TopEntryPoint::MacroItems);
assert_eq!(
parse.errors(),
&[],
assert!(
parse.errors().is_empty(),
"parse has syntax errors. parse tree:\n{:#?}",
parse.syntax_node()
);
@ -349,9 +382,12 @@ mod tests {
reverse_fixups(&mut tt, &tmap, &fixups.undo_info);
// 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());
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]
@ -468,7 +504,7 @@ fn foo() {
}
"#,
expect![[r#"
fn foo () {a .__ra_fixup}
fn foo () {a . __ra_fixup}
"#]],
)
}
@ -482,7 +518,7 @@ fn foo() {
}
"#,
expect![[r#"
fn foo () {a .__ra_fixup ;}
fn foo () {a . __ra_fixup ;}
"#]],
)
}
@ -497,7 +533,7 @@ fn foo() {
}
"#,
expect![[r#"
fn foo () {a .__ra_fixup ; bar () ;}
fn foo () {a . __ra_fixup ; bar () ;}
"#]],
)
}
@ -525,7 +561,7 @@ fn foo() {
}
"#,
expect![[r#"
fn foo () {let x = a .__ra_fixup ;}
fn foo () {let x = a . __ra_fixup ;}
"#]],
)
}
@ -541,7 +577,7 @@ fn foo() {
}
"#,
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>> {
// 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() {
return Some(self.map(Clone::clone));
} 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>> {
// 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() {
return Some(self);
} else if !self.file_id.is_attr_macro(db) {

View file

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

View file

@ -53,7 +53,7 @@ pub use builder::{ParamKind, TyBuilder};
pub use chalk_ext::*;
pub use infer::{
could_coerce, could_unify, Adjust, Adjustment, AutoBorrow, BindingMode, InferenceDiagnostic,
InferenceResult,
InferenceResult, OverloadedDeref, PointerCast,
};
pub use interner::Interner;
pub use lower::{
@ -523,7 +523,7 @@ where
}
pub fn callable_sig_from_fnonce(
self_ty: &Canonical<Ty>,
self_ty: &Ty,
env: Arc<TraitEnvironment>,
db: &dyn HirDatabase,
) -> Option<CallableSig> {
@ -531,16 +531,16 @@ pub fn callable_sig_from_fnonce(
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 mut kinds = self_ty.binders.interned().to_vec();
let b = TyBuilder::trait_ref(db, fn_once_trait);
if b.remaining() != 2 {
return None;
}
let fn_once = b
.push(self_ty.value.clone())
.fill_with_bound_vars(DebruijnIndex::INNERMOST, kinds.len())
.build();
kinds.extend(fn_once.substitution.iter(Interner).skip(1).map(|x| {
let fn_once = b.push(self_ty.clone()).fill_with_bound_vars(DebruijnIndex::INNERMOST, 0).build();
let kinds = fn_once
.substitution
.iter(Interner)
.skip(1)
.map(|x| {
let vk = match x.data(Interner) {
chalk_ir::GenericArgData::Ty(_) => {
chalk_ir::VariableKind::Ty(chalk_ir::TyVariableKind::General)
@ -551,7 +551,8 @@ pub fn callable_sig_from_fnonce(
}
};
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
// `<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,
_ => 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) {
chalk_ir::TyKind::Tuple(_, subst) => {
subst.iter(Interner).filter_map(|arg| arg.ty(Interner).cloned()).collect::<Vec<_>>()
}
_ => return None,
};
if params.iter().any(|ty| ty.is_unknown()) {
return None;
}
let fn_once = TyBuilder::trait_ref(db, fn_once_trait)
.push(self_ty.value.clone())
.push(args.clone())
.build();
let fn_once =
TyBuilder::trait_ref(db, fn_once_trait).push(self_ty.clone()).push(args.clone()).build();
let projection =
TyBuilder::assoc_type_projection(db, output_assoc_type, Some(fn_once.substitution.clone()))
.build();

View file

@ -541,7 +541,7 @@ pub struct ReceiverAdjustments {
impl ReceiverAdjustments {
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();
for _ in 0..self.autoderefs {
match autoderef::autoderef_step(table, ty.clone()) {

View file

@ -164,16 +164,16 @@ fn infer_associated_method_with_modules() {
check_infer(
r#"
mod a {
struct A;
pub struct A;
impl A { pub fn thing() -> A { A {} }}
}
mod b {
struct B;
pub struct B;
impl B { pub fn thing() -> u32 { 99 }}
mod c {
struct C;
pub mod c {
pub struct C;
impl C { pub fn thing() -> C { C {} }}
}
}
@ -186,22 +186,22 @@ fn infer_associated_method_with_modules() {
}
"#,
expect![[r#"
55..63 '{ A {} }': A
57..61 'A {}': A
125..131 '{ 99 }': u32
127..129 '99': u32
201..209 '{ C {} }': C
203..207 'C {}': C
240..324 '{ ...g(); }': ()
250..251 'x': A
254..265 'a::A::thing': fn thing() -> A
254..267 'a::A::thing()': A
277..278 'y': u32
281..292 'b::B::thing': fn thing() -> u32
281..294 'b::B::thing()': u32
304..305 'z': C
308..319 'c::C::thing': fn thing() -> C
308..321 'c::C::thing()': C
59..67 '{ A {} }': A
61..65 'A {}': A
133..139 '{ 99 }': u32
135..137 '99': u32
217..225 '{ C {} }': C
219..223 'C {}': C
256..340 '{ ...g(); }': ()
266..267 'x': A
270..281 'a::A::thing': fn thing() -> A
270..283 'a::A::thing()': A
293..294 'y': u32
297..308 'b::B::thing': fn thing() -> u32
297..310 'b::B::thing()': u32
320..321 'z': C
324..335 'c::C::thing': fn 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 }
mod b {
fn c() -> u32 { 1 }
pub fn c() -> u32 { 1 }
}
fn test() {
@ -225,13 +225,13 @@ fn test() {
expect![[r#"
14..19 '{ 1 }': u32
16..17 '1': u32
47..52 '{ 1 }': u32
49..50 '1': u32
66..90 '{ ...c(); }': ()
72..73 'a': fn a() -> u32
72..75 'a()': u32
81..85 'b::c': fn c() -> u32
81..87 'b::c()': u32
51..56 '{ 1 }': u32
53..54 '1': u32
70..94 '{ ...c(); }': ()
76..77 'a': fn a() -> u32
76..79 'a()': u32
85..89 'b::c': fn c() -> u32
85..91 'b::c()': u32
"#]],
);
}
@ -1856,7 +1856,7 @@ fn not_shadowing_module_by_primitive() {
check_types(
r#"
//- /str.rs
fn foo() -> u32 {0}
pub fn foo() -> u32 {0}
//- /main.rs
mod str;

View file

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

View file

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

View file

@ -117,7 +117,7 @@ pub use {
name::{known, Name},
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
@ -2997,8 +2997,7 @@ impl Type {
TyKind::Function(_) => Callee::FnPtr,
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(&ty, self.env.clone(), db)?;
let sig = hir_ty::callable_sig_from_fnonce(&self.ty, self.env.clone(), db)?;
return Some(Callable {
ty: self.clone(),
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 {
fn visibility(&self, db: &dyn HirDatabase) -> Visibility;
fn is_visible_from(&self, db: &dyn HirDatabase, module: Module) -> bool {

View file

@ -29,9 +29,10 @@ use crate::{
db::HirDatabase,
semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx},
source_analyzer::{resolve_hir_path, SourceAnalyzer},
Access, BindingMode, BuiltinAttr, Callable, ConstParam, Crate, DeriveHelper, Field, Function,
HasSource, HirFileId, Impl, InFile, Label, LifetimeParam, Local, Macro, Module, ModuleDef,
Name, Path, ScopeDef, ToolModule, Trait, Type, TypeAlias, TypeParam, VariantDef,
Access, Adjust, AutoBorrow, BindingMode, BuiltinAttr, Callable, ConstParam, Crate,
DeriveHelper, Field, Function, HasSource, HirFileId, Impl, InFile, Label, LifetimeParam, Local,
Macro, Module, ModuleDef, Name, OverloadedDeref, Path, ScopeDef, ToolModule, Trait, Type,
TypeAlias, TypeParam, VariantDef,
};
#[derive(Debug, Clone, PartialEq, Eq)]
@ -333,9 +334,8 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
self.imp.resolve_trait(trait_)
}
// FIXME: Figure out a nice interface to inspect adjustments
pub fn is_implicit_reborrow(&self, expr: &ast::Expr) -> Option<Mutability> {
self.imp.is_implicit_reborrow(expr)
pub fn expr_adjustments(&self, expr: &ast::Expr) -> Option<Vec<Adjust>> {
self.imp.expr_adjustments(expr)
}
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> {
self.analyze(expr.syntax())?.is_implicit_reborrow(self.db, expr)
fn expr_adjustments(&self, expr: &ast::Expr) -> Option<Vec<Adjust>> {
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> {

View file

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

View file

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

View file

@ -196,6 +196,7 @@ trait Foo {
type Output;
const CONST: usize = 42;
const CONST_2: i32;
fn foo(&self);
fn bar(&self);
@ -213,6 +214,7 @@ trait Foo {
type Output;
const CONST: usize = 42;
const CONST_2: i32;
fn foo(&self);
fn bar(&self);
@ -226,7 +228,7 @@ impl Foo for S {
$0type Output;
const CONST: usize = 42;
const CONST_2: i32;
fn foo(&self) {
todo!()
@ -379,14 +381,14 @@ impl Foo for S {
r#"
mod foo {
pub struct Bar;
trait Foo { fn foo(&self, bar: Bar); }
pub trait Foo { fn foo(&self, bar: Bar); }
}
struct S;
impl foo::Foo for S { $0 }"#,
r#"
mod foo {
pub struct Bar;
trait Foo { fn foo(&self, bar: Bar); }
pub trait Foo { fn foo(&self, bar: Bar); }
}
struct S;
impl foo::Foo for S {
@ -439,14 +441,14 @@ impl bar::Foo for S {
r#"
mod foo {
pub struct Bar<T>;
trait Foo { fn foo(&self, bar: Bar<u32>); }
pub trait Foo { fn foo(&self, bar: Bar<u32>); }
}
struct S;
impl foo::Foo for S { $0 }"#,
r#"
mod foo {
pub struct Bar<T>;
trait Foo { fn foo(&self, bar: Bar<u32>); }
pub trait Foo { fn foo(&self, bar: Bar<u32>); }
}
struct S;
impl foo::Foo for S {
@ -464,14 +466,14 @@ impl foo::Foo for S {
r#"
mod foo {
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;
impl foo::Foo<u32> for S { $0 }"#,
r#"
mod foo {
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;
impl foo::Foo<u32> for S {
@ -489,7 +491,7 @@ impl foo::Foo<u32> for S {
add_missing_impl_members,
r#"
mod foo {
trait Foo<T> { fn foo(&self, bar: T); }
pub trait Foo<T> { fn foo(&self, bar: T); }
pub struct Param;
}
struct Param;
@ -497,7 +499,7 @@ struct S;
impl foo::Foo<Param> for S { $0 }"#,
r#"
mod foo {
trait Foo<T> { fn foo(&self, bar: T); }
pub trait Foo<T> { fn foo(&self, bar: T); }
pub struct Param;
}
struct Param;
@ -518,7 +520,7 @@ impl foo::Foo<Param> for S {
mod foo {
pub struct Bar<T>;
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;
impl foo::Foo for S { $0 }"#,
@ -526,7 +528,7 @@ impl foo::Foo for S { $0 }"#,
mod foo {
pub struct Bar<T>;
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;
impl foo::Foo for S {
@ -545,7 +547,7 @@ impl foo::Foo for S {
mod foo {
pub struct Bar<T>;
pub struct Baz;
trait Foo { fn foo(&self, bar: Bar<Baz>); }
pub trait Foo { fn foo(&self, bar: Bar<Baz>); }
}
struct S;
impl foo::Foo for S { $0 }"#,
@ -553,7 +555,7 @@ impl foo::Foo for S { $0 }"#,
mod foo {
pub struct Bar<T>;
pub struct Baz;
trait Foo { fn foo(&self, bar: Bar<Baz>); }
pub trait Foo { fn foo(&self, bar: Bar<Baz>); }
}
struct S;
impl foo::Foo for S {
@ -571,14 +573,14 @@ impl foo::Foo for S {
r#"
mod foo {
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;
impl foo::Foo for S { $0 }"#,
r#"
mod foo {
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;
impl foo::Foo for S {
@ -658,6 +660,7 @@ trait Foo {
type Output;
const CONST: usize = 42;
const CONST_2: i32;
fn valid(some: u32) -> bool { false }
fn foo(some: u32) -> bool;
@ -669,13 +672,16 @@ trait Foo {
type Output;
const CONST: usize = 42;
const CONST_2: i32;
fn valid(some: u32) -> bool { false }
fn foo(some: u32) -> bool;
}
struct 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 =
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 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));
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) {
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);
generate_impl_text(&adt, &fn_def).replace("{\n\n", "{")
}
@ -272,7 +273,7 @@ enum FunType {
}
/// Where to put extracted function definition
#[derive(Debug)]
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
enum Anchor {
/// Extract free function and put right after current top-level function
Freestanding,
@ -1245,6 +1246,14 @@ fn node_to_insert_after(body: &FunctionBody, anchor: Anchor) -> Option<SyntaxNod
while let Some(next_ancestor) = ancestors.next() {
match next_ancestor.kind() {
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 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
}
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 {
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]
fn closure_arguments() {
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 syntax::{
ast::{self, HasVisibility as _},
@ -18,7 +18,7 @@ use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
// fn frobnicate() {}
// }
// 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() {}
// }
// fn main() {
// m::frobnicate() {}
// m::frobnicate();
// }
// ```
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<()> {
let path: ast::Path = ctx.find_node_at_offset()?;
let path_res = ctx.sema.resolve_path(&path)?;
let def = match path_res {
PathResolution::Def(def) => def,
_ => return None,
};
let qualifier = path.qualifier()?;
let name_ref = path.segment()?.name_ref()?;
let qualifier_res = ctx.sema.resolve_path(&qualifier)?;
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 target_module = def.module(ctx.db())?;

View file

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

View file

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

View file

@ -92,7 +92,7 @@ pub(crate) fn move_format_string_arg(acc: &mut Assists, ctx: &AssistContext<'_>)
NodeOrToken::Node(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());
current_arg.clear();
},
@ -238,14 +238,14 @@ fn main() {
&add_macro_decl(
r#"
fn main() {
print!("{} {x + 1:b} {Struct(1, 2)}$0", 1);
print!("{:b} {x + 1:b} {Struct(1, 2)}$0", 1);
}
"#,
),
&add_macro_decl(
r#"
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 syntax::{
ast::{self, AstNode, AstToken},
match_ast, NodeOrToken, SyntaxElement, TextSize, T,
match_ast, NodeOrToken, SyntaxElement, TextRange, TextSize, T,
};
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<()> {
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 r_delim = NodeOrToken::Token(tt.right_delimiter_token()?);
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 parent = macro_expr.syntax().parent()?;
let (range, text) = match &*input_expressions {
Some(match &*input_expressions {
// dbg!()
[] => {
match_ast! {
@ -107,10 +136,6 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<(
}
// dbg!(expr0, expr1, ...)
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, 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 {
$0type Qux;
const Baz: usize = 42;
const Fez: usize;
fn foo() {

View file

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

View file

@ -119,6 +119,10 @@ pub fn filter_assoc_items(
(default_methods, def.body()),
(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,
})
.collect::<Vec<_>>()

View file

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

View file

@ -157,7 +157,7 @@ fn complete_trait_impl(
add_function_impl(acc, ctx, replacement_range, func, hir_impl)
}
(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) => {
add_const_impl(acc, ctx, replacement_range, const_, hir_impl)
@ -236,9 +236,7 @@ fn get_transformed_assoc_item(
);
transform.apply(assoc_item.syntax());
if let ast::AssocItem::Fn(func) = &assoc_item {
func.remove_attrs_and_docs();
}
assoc_item.remove_attrs_and_docs();
Some(assoc_item)
}
@ -247,24 +245,50 @@ fn add_type_alias_impl(
ctx: &CompletionContext<'_>,
replacement_range: TextRange,
type_alias: hir::TypeAlias,
impl_def: hir::Impl,
) {
let alias_name = type_alias.name(ctx.db);
let (alias_name, escaped_name) =
(alias_name.unescaped().to_smol_str(), alias_name.to_smol_str());
let alias_name = type_alias.name(ctx.db).unescaped().to_smol_str();
let label = format!("type {} =", alias_name);
let replacement = format!("type {} = ", escaped_name);
let mut item = CompletionItem::new(SymbolKind::TypeAlias, replacement_range, label);
item.lookup_by(format!("type {}", alias_name))
.set_documentation(type_alias.docs(ctx.db))
.set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() });
if let Some(source) = ctx.sema.source(type_alias) {
let assoc_item = ast::AssocItem::TypeAlias(source.value);
if let Some(transformed_item) = get_transformed_assoc_item(ctx, assoc_item, impl_def) {
let transformed_ty = match transformed_item {
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) => item
.snippet_edit(cap, TextEdit::replace(replacement_range, format!("{}$0;", replacement))),
None => item.text_edit(TextEdit::replace(replacement_range, replacement)),
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(
@ -309,7 +333,6 @@ fn add_const_impl(
}
fn make_const_compl_syntax(const_: &ast::Const, needs_whitespace: bool) -> String {
const_.remove_attrs_and_docs();
let const_ = if needs_whitespace {
insert_whitespace_into_node::insert_ws_into(const_.syntax().clone())
} 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 {
node.remove_attrs_and_docs();
let node = if needs_whitespace {
insert_whitespace_into_node::insert_ws_into(node.syntax().clone())
} else {
@ -350,9 +371,7 @@ fn function_declaration(node: &ast::Fn, needs_whitespace: bool) -> String {
.map_or(end, |f| f.text_range().start());
let len = end - start;
let range = TextRange::new(0.into(), len);
let syntax = node.text().slice(range).to_string();
let syntax = node.text().slice(..len).to_string();
syntax.trim_end().to_owned()
}
@ -1160,6 +1179,106 @@ impl Foo for Test {
$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::Fn(it) => it.body().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::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::TypeAlias(it) => it.ty().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"
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.57"
rust-version = "1.65"
[lib]
doctest = false

View file

@ -58,8 +58,11 @@ impl LineIndex {
let mut utf16_lines = NoHashHashMap::default();
let mut utf16_chars = Vec::new();
let mut newlines = vec![0.into()];
let mut curr_row @ mut curr_col = 0.into();
let mut newlines = Vec::with_capacity(16);
newlines.push(TextSize::from(0));
let mut curr_row = 0.into();
let mut curr_col = 0.into();
let mut line = 0;
for c in text.chars() {
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);
state = State::NotArg;
}
(State::MaybeArg, ':') => {
output.push(chr);
extracted_expressions.push(Arg::Placeholder);
state = State::FormatOpts;
}
(State::MaybeArg, _) => {
if matches!(chr, '\\' | '$') {
current_expr.push('\\');
@ -118,44 +123,41 @@ pub fn parse_format_exprs(input: &str) -> Result<(String, Vec<Arg>), ()> {
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(':')) => {
// path separator
state = State::Expr;
current_expr.push_str("::");
chars.next();
}
(State::Ident | State::Expr, ':') => {
(State::Ident | State::Expr, ':' | '}') => {
if inexpr_open_count == 0 {
// We're outside of braces, thus assume that it's a specifier, like "{Some(value):?}"
output.push(chr);
let trimmed = current_expr.trim();
if matches!(state, State::Expr) {
extracted_expressions.push(Arg::Expr(current_expr.trim().into()));
// if the expression consists of a single number, like "0" or "12", it can refer to
// 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 {
extracted_expressions.push(Arg::Ident(current_expr.trim().into()));
extracted_expressions.push(Arg::Ident(trimmed.into()));
}
current_expr = String::new();
state = State::FormatOpts;
output.push(chr);
current_expr.clear();
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.
current_expr.push(chr);
}
@ -219,6 +221,10 @@ mod tests {
("{expr} is {2 + 2}", expect![["{} is {}; expr, 2 + 2"]]),
("{expr:?}", expect![["{:?}; 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"]]),
("{malformed", expect![["-"]]),
("malformed}", expect![["-"]]),

View file

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

View file

@ -5,10 +5,7 @@ use crate::{Diagnostic, DiagnosticsContext};
// This diagnostic is shown for macro expansion errors.
pub(crate) fn macro_error(ctx: &DiagnosticsContext<'_>, d: &hir::MacroError) -> Diagnostic {
// Use more accurate position if available.
let display_range = d
.precise_location
.unwrap_or_else(|| ctx.sema.diagnostics_display_range(d.node.clone()).range);
let display_range = ctx.resolve_precise_location(&d.node, d.precise_location);
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.rs
struct Foo {
pub struct Foo {
bar: i32
}
"#,
r#"
struct Foo {
pub struct Foo {
bar: i32,
pub(crate) baz: bool
}

View file

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

View file

@ -1,5 +1,4 @@
use hir::db::DefDatabase;
use syntax::NodeOrToken;
use crate::{Diagnostic, DiagnosticsContext, Severity};
@ -19,16 +18,7 @@ pub(crate) fn unresolved_proc_macro(
proc_attr_macros_enabled: bool,
) -> Diagnostic {
// Use more accurate position if available.
let display_range = (|| {
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 display_range = ctx.resolve_precise_location(&d.node, d.precise_location);
let config_enabled = match d.kind {
hir::MacroKind::Attr => proc_macros_enabled && proc_attr_macros_enabled,

View file

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

View file

@ -182,6 +182,28 @@ struct DiagnosticsContext<'a> {
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(
db: &RootDatabase,
config: &DiagnosticsConfig,

View file

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

View file

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

View file

@ -289,10 +289,10 @@ mod b;
enum E { X(Foo$0) }
//- /a.rs
struct Foo;
pub struct Foo;
//^^^
//- /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
// equivalency is more important
let descended = if in_attr {

View file

@ -1,7 +1,10 @@
use std::fmt;
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::{
base_db::FileRange, famous_defs::FamousDefs, syntax_helpers::node_ext::walk_ty, FxHashMap,
RootDatabase,
@ -22,7 +25,7 @@ pub struct InlayHintsConfig {
pub type_hints: bool,
pub parameter_hints: bool,
pub chaining_hints: bool,
pub reborrow_hints: ReborrowHints,
pub adjustment_hints: AdjustmentHints,
pub closure_return_type_hints: ClosureReturnTypeHints,
pub binding_mode_hints: bool,
pub lifetime_elision_hints: LifetimeElisionHints,
@ -48,9 +51,9 @@ pub enum LifetimeElisionHints {
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ReborrowHints {
pub enum AdjustmentHints {
Always,
MutableOnly,
ReborrowOnly,
Never,
}
@ -61,7 +64,8 @@ pub enum InlayKind {
ClosingBraceHint,
ClosureReturnTypeHint,
GenericParamListHint,
ImplicitReborrowHint,
AdjustmentHint,
AdjustmentHintClosingParenthesis,
LifetimeHint,
ParameterHint,
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 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.parts.iter().map(|part| &part.text).format(""))
@ -180,7 +190,7 @@ impl fmt::Debug for InlayHintLabelPart {
pub(crate) fn inlay_hints(
db: &RootDatabase,
file_id: FileId,
range_limit: Option<FileRange>,
range_limit: Option<TextRange>,
config: &InlayHintsConfig,
) -> Vec<InlayHint> {
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);
match range_limit {
Some(FileRange { range, .. }) => match file.covering_element(range) {
Some(range) => match file.covering_element(range) {
NodeOrToken::Token(_) => return acc,
NodeOrToken::Node(n) => n
.descendants()
@ -221,6 +231,7 @@ fn hints(
match node {
ast::Expr(expr) => {
chaining_hints(hints, sema, &famous_defs, config, file_id, &expr);
adjustment_hints(hints, sema, config, &expr);
match expr {
ast::Expr::CallExpr(it) => param_name_hints(hints, sema, config, ast::Expr::from(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),
// 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
ast::Expr::PathExpr(_) => reborrow_hints(hints, sema, config, &expr),
// ast::Expr::PathExpr(_) => reborrow_hints(hints, sema, config, &expr),
_ => None,
}
},
@ -617,30 +628,95 @@ fn closure_ret_hints(
Some(())
}
fn reborrow_hints(
fn adjustment_hints(
acc: &mut Vec<InlayHint>,
sema: &Semantics<'_, RootDatabase>,
config: &InlayHintsConfig,
expr: &ast::Expr,
) -> Option<()> {
if config.reborrow_hints == ReborrowHints::Never {
if config.adjustment_hints == AdjustmentHints::Never {
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 desc_expr = descended.as_ref().unwrap_or(expr);
let mutability = sema.is_implicit_reborrow(desc_expr)?;
let label = match mutability {
hir::Mutability::Shared if config.reborrow_hints != ReborrowHints::MutableOnly => "&*",
hir::Mutability::Mut => "&mut *",
_ => return None,
let adjustments = sema.expr_adjustments(desc_expr).filter(|it| !it.is_empty())?;
let needs_parens = match parent {
Some(parent) => {
match parent {
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,
};
if needs_parens {
acc.push(InlayHint {
range: expr.syntax().text_range(),
kind: InlayKind::AdjustmentHint,
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::ImplicitReborrowHint,
label: label.to_string().into(),
tooltip: Some(InlayTooltip::String("Compiler inserted reborrow".into())),
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(())
}
@ -1213,12 +1289,11 @@ fn get_callable(
#[cfg(test)]
mod tests {
use expect_test::{expect, Expect};
use ide_db::base_db::FileRange;
use itertools::Itertools;
use syntax::{TextRange, TextSize};
use test_utils::extract_annotations;
use crate::inlay_hints::ReborrowHints;
use crate::inlay_hints::AdjustmentHints;
use crate::{fixture, inlay_hints::InlayHintsConfig, LifetimeElisionHints};
use super::ClosureReturnTypeHints;
@ -1230,7 +1305,7 @@ mod tests {
chaining_hints: false,
lifetime_elision_hints: LifetimeElisionHints::Never,
closure_return_type_hints: ClosureReturnTypeHints::Never,
reborrow_hints: ReborrowHints::Always,
adjustment_hints: AdjustmentHints::Never,
binding_mode_hints: false,
hide_named_constructor_hints: false,
hide_closure_initialization_hints: false,
@ -1242,7 +1317,6 @@ mod tests {
type_hints: true,
parameter_hints: true,
chaining_hints: true,
reborrow_hints: ReborrowHints::Always,
closure_return_type_hints: ClosureReturnTypeHints::WithBlock,
binding_mode_hints: true,
lifetime_elision_hints: LifetimeElisionHints::Always,
@ -1838,10 +1912,7 @@ fn main() {
.inlay_hints(
&InlayHintsConfig { type_hints: true, ..DISABLED_CONFIG },
file_id,
Some(FileRange {
file_id,
range: TextRange::new(TextSize::from(500), TextSize::from(600)),
}),
Some(TextRange::new(TextSize::from(500), TextSize::from(600))),
)
.unwrap();
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]
fn hints_binding_modes() {
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},
hover::{HoverAction, HoverConfig, HoverDocFormat, HoverGotoTypeData, HoverResult},
inlay_hints::{
ClosureReturnTypeHints, InlayHint, InlayHintLabel, InlayHintsConfig, InlayKind,
InlayTooltip, LifetimeElisionHints, ReborrowHints,
AdjustmentHints, ClosureReturnTypeHints, InlayHint, InlayHintLabel, InlayHintsConfig,
InlayKind, InlayTooltip, LifetimeElisionHints,
},
join_lines::JoinLinesConfig,
markup::Markup,
@ -367,7 +367,7 @@ impl Analysis {
&self,
config: &InlayHintsConfig,
file_id: FileId,
range: Option<FileRange>,
range: Option<TextRange>,
) -> Cancellable<Vec<InlayHint>> {
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)
//! for LSIF and LSP.
use hir::{db::DefDatabase, AsAssocItem, AssocItemContainer, Crate, Name, Semantics};
use hir::{AsAssocItem, AssocItemContainer, Crate, Name, Semantics};
use ide_db::{
base_db::{CrateOrigin, FileId, FileLoader, FilePosition, LangCrateOrigin},
base_db::{CrateOrigin, FilePosition, LangCrateOrigin},
defs::{Definition, IdentClass},
helpers::pick_best_token,
RootDatabase,
@ -11,7 +11,7 @@ use ide_db::{
use itertools::Itertools;
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)]
pub enum MonikerDescriptorKind {
@ -77,25 +77,13 @@ pub struct PackageInformation {
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(
db: &RootDatabase,
FilePosition { file_id, offset }: FilePosition,
) -> Option<RangeInfo<Vec<MonikerResult>>> {
let sema = &Semantics::new(db);
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 {
IDENT
| INT_NUMBER

View file

@ -16,6 +16,7 @@ use ide_db::{
search::{ReferenceCategory, SearchScope, UsageSearchResult},
RootDatabase,
};
use itertools::Itertools;
use stdx::hash::NoHashHashMap;
use syntax::{
algo::find_node_at_offset,
@ -86,6 +87,7 @@ pub(crate) fn find_all_refs(
file_id,
refs.into_iter()
.map(|file_ref| (file_ref.range, file_ref.category))
.unique()
.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::{
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,
TryToNav,
};
@ -99,7 +100,7 @@ fn all_modules(db: &dyn HirDatabase) -> Vec<Module> {
impl StaticIndex<'_> {
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 inlay_hints = self
.analysis
@ -111,7 +112,7 @@ impl StaticIndex<'_> {
chaining_hints: true,
closure_return_type_hints: crate::ClosureReturnTypeHints::WithBlock,
lifetime_elision_hints: crate::LifetimeElisionHints::Never,
reborrow_hints: crate::ReborrowHints::Never,
adjustment_hints: crate::AdjustmentHints::Never,
hide_named_constructor_hints: false,
hide_closure_initialization_hints: false,
param_names_for_lifetime_elision_hints: false,

View file

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

View file

@ -4,7 +4,7 @@ version = "0.0.0"
description = "TBD"
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.57"
rust-version = "1.65"
[lib]
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};
#[cfg(test)]
mod tests;
/// Convert the syntax node to a `TokenTree` (what macro
/// will consume).
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>>,
) -> (tt::Subtree, TokenMap, u32) {
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);
c.id_alloc.map.shrink_to_fit();
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;
}
let mut conv = RawConvertor {
let mut conv = RawConverter {
lexed,
pos: 0,
id_alloc: TokenIdAlloc {
@ -148,7 +151,7 @@ pub fn parse_exprs_with_sep(tt: &tt::Subtree, sep: char) -> Vec<tt::Subtree> {
res
}
fn convert_tokens<C: TokenConvertor>(conv: &mut C) -> tt::Subtree {
fn convert_tokens<C: TokenConverter>(conv: &mut C) -> tt::Subtree {
struct StackEntry {
subtree: tt::Subtree,
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)) {
Some(kind) if !kind.is_trivia() => tt::Spacing::Joint,
Some(kind) if is_single_token_op(kind) => tt::Spacing::Joint,
_ => tt::Spacing::Alone,
};
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
/// That is, strips leading `///` (or `/**`, etc)
/// and strips the ending `*/`
@ -425,8 +457,8 @@ impl TokenIdAlloc {
}
}
/// A raw token (straight from lexer) convertor
struct RawConvertor<'a> {
/// A raw token (straight from lexer) converter
struct RawConverter<'a> {
lexed: parser::LexedStr<'a>,
pos: usize,
id_alloc: TokenIdAlloc,
@ -442,7 +474,7 @@ trait SrcToken<Ctx>: std::fmt::Debug {
fn synthetic_id(&self, ctx: &Ctx) -> Option<SyntheticTokenId>;
}
trait TokenConvertor: Sized {
trait TokenConverter: Sized {
type Token: SrcToken<Self>;
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;
}
impl<'a> SrcToken<RawConvertor<'a>> for usize {
fn kind(&self, ctx: &RawConvertor<'a>) -> SyntaxKind {
impl<'a> SrcToken<RawConverter<'a>> for usize {
fn kind(&self, ctx: &RawConverter<'a>) -> SyntaxKind {
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()
}
fn to_text(&self, ctx: &RawConvertor<'_>) -> SmolStr {
fn to_text(&self, ctx: &RawConverter<'_>) -> SmolStr {
ctx.lexed.text(*self).into()
}
fn synthetic_id(&self, _ctx: &RawConvertor<'a>) -> Option<SyntheticTokenId> {
fn synthetic_id(&self, _ctx: &RawConverter<'a>) -> Option<SyntheticTokenId> {
None
}
}
impl<'a> TokenConvertor for RawConvertor<'a> {
impl<'a> TokenConverter for RawConverter<'a> {
type Token = usize;
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,
current: Option<SyntaxToken>,
current_synthetic: Vec<SyntheticToken>,
@ -515,7 +547,7 @@ struct Convertor {
punct_offset: Option<(SyntaxToken, TextSize)>,
}
impl Convertor {
impl Converter {
fn new(
node: &SyntaxNode,
global_offset: TextSize,
@ -523,11 +555,11 @@ impl Convertor {
next_id: u32,
mut replace: FxHashMap<SyntaxElement, Vec<SyntheticToken>>,
mut append: FxHashMap<SyntaxElement, Vec<SyntheticToken>>,
) -> Convertor {
) -> Converter {
let range = node.text_range();
let mut preorder = node.preorder_with_tokens();
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 } },
current: first,
current_synthetic: synthetic,
@ -590,15 +622,15 @@ impl SynToken {
}
}
impl SrcToken<Convertor> for SynToken {
fn kind(&self, _ctx: &Convertor) -> SyntaxKind {
impl SrcToken<Converter> for SynToken {
fn kind(&self, ctx: &Converter) -> SyntaxKind {
match self {
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,
}
}
fn to_char(&self, _ctx: &Convertor) -> Option<char> {
fn to_char(&self, _ctx: &Converter) -> Option<char> {
match self {
SynToken::Ordinary(_) => None,
SynToken::Punch(it, i) => it.text().chars().nth((*i).into()),
@ -606,7 +638,7 @@ impl SrcToken<Convertor> for SynToken {
SynToken::Synthetic(_) => None,
}
}
fn to_text(&self, _ctx: &Convertor) -> SmolStr {
fn to_text(&self, _ctx: &Converter) -> SmolStr {
match self {
SynToken::Ordinary(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 {
SynToken::Synthetic(token) => Some(token.id),
_ => None,
@ -622,7 +654,7 @@ impl SrcToken<Convertor> for SynToken {
}
}
impl TokenConvertor for Convertor {
impl TokenConverter for Converter {
type Token = SynToken;
fn convert_doc_comment(&self, token: &Self::Token) -> Option<Vec<tt::TokenTree>> {
convert_doc_comment(token.token()?)
@ -651,7 +683,7 @@ impl TokenConvertor for Convertor {
}
let curr = self.current.clone()?;
if !&self.range.contains_range(curr.text_range()) {
if !self.range.contains_range(curr.text_range()) {
return None;
}
let (new_current, new_synth) =
@ -809,12 +841,15 @@ impl<'a> TtTreeSink<'a> {
let next = last.bump();
if let (
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())
{
// 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.
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.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"
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.57"
rust-version = "1.65"
[lib]
doctest = false

View file

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

View file

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

View file

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

View file

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

View file

@ -117,7 +117,7 @@ impl Abi {
let inner = unsafe { Abi_1_63::from_lib(lib, symbol_name) }?;
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 {
Io(io::Error),
LibLoading(libloading::Error),
UnsupportedABI,
UnsupportedABI(String),
}
impl fmt::Display for LoadProcMacroDylibError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
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),
}
}

View file

@ -113,12 +113,12 @@ impl ProcMacroSrv {
fn expander(&mut self, path: &Path) -> Result<&dylib::Expander, String> {
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)) {
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(),
})

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -69,7 +69,7 @@ impl WorkspaceBuildScripts {
cmd.args(&["check", "--quiet", "--workspace", "--message-format=json"]);
// --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.
cmd.arg("--all-targets");

View file

@ -270,11 +270,7 @@ impl CargoWorkspace {
config: &CargoConfig,
progress: &dyn Fn(String),
) -> Result<cargo_metadata::Metadata> {
let target = config
.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 targets = find_list_of_build_targets(config, cargo_toml);
let mut meta = MetadataCommand::new();
meta.cargo_path(toolchain::cargo());
@ -294,8 +290,12 @@ impl CargoWorkspace {
}
meta.current_dir(current_dir.as_os_str());
if let Some(target) = target {
meta.other_options(vec![String::from("--filter-platform"), target]);
if !targets.is_empty() {
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
@ -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(
cargo_toml: &ManifestPath,
extra_env: &FxHashMap<String, String>,
@ -499,7 +512,7 @@ fn rustc_discover_host_triple(
fn cargo_config_build_target(
cargo_toml: &ManifestPath,
extra_env: &FxHashMap<String, String>,
) -> Option<String> {
) -> Vec<String> {
let mut cargo_config = Command::new(toolchain::cargo());
cargo_config.envs(extra_env);
cargo_config
@ -507,12 +520,21 @@ fn cargo_config_build_target(
.args(&["-Z", "unstable-options", "config", "get", "build.target"])
.env("RUSTC_BOOTSTRAP", "1");
// if successful we receive `build.target = "target-triple"`
// or `build.target = ["<target 1>", ..]`
tracing::debug!("Discovering cargo config target by {:?}", cargo_config);
match utf8_stdout(cargo_config) {
Ok(stdout) => stdout
.strip_prefix("build.target = \"")
.and_then(|stdout| stdout.strip_suffix('"'))
.map(ToOwned::to_owned),
Err(_) => None,
}
utf8_stdout(cargo_config).map(parse_output_cargo_config_build_target).unwrap_or_default()
}
fn parse_output_cargo_config_build_target(stdout: String) -> Vec<String> {
let trimmed = stdout.trim_start_matches("build.target = ").trim_matches('"');
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(core) = sysroot.by_name("core") {
sysroot.crates[alloc].deps.push(core);
for dep in ALLOC_DEPS.trim().lines() {
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(std) = sysroot.by_name("std") {
sysroot.crates[proc_macro].deps.push(std);
for dep in PROC_MACRO_DEPS.trim().lines() {
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 = "
alloc
backtrace
core
panic_abort
panic_unwind
@ -246,17 +251,19 @@ proc_macro
profiler_builtins
std
stdarch/crates/std_detect
term
test
unwind";
const ALLOC_DEPS: &str = "core";
const STD_DEPS: &str = "
alloc
core
panic_abort
panic_unwind
panic_abort
core
profiler_builtins
unwind
std_detect
term
test
unwind";
test";
const PROC_MACRO_DEPS: &str = "std";

View file

@ -1566,10 +1566,10 @@ fn rust_project_hello_world_project_model() {
},
Dependency {
crate_id: CrateId(
1,
3,
),
name: CrateName(
"core",
"panic_unwind",
),
prelude: true,
},
@ -1584,10 +1584,10 @@ fn rust_project_hello_world_project_model() {
},
Dependency {
crate_id: CrateId(
3,
1,
),
name: CrateName(
"panic_unwind",
"core",
),
prelude: true,
},
@ -1600,6 +1600,15 @@ fn rust_project_hello_world_project_model() {
),
prelude: true,
},
Dependency {
crate_id: CrateId(
9,
),
name: CrateName(
"unwind",
),
prelude: true,
},
Dependency {
crate_id: CrateId(
7,
@ -1613,29 +1622,11 @@ fn rust_project_hello_world_project_model() {
crate_id: CrateId(
8,
),
name: CrateName(
"term",
),
prelude: true,
},
Dependency {
crate_id: CrateId(
9,
),
name: CrateName(
"test",
),
prelude: true,
},
Dependency {
crate_id: CrateId(
10,
),
name: CrateName(
"unwind",
),
prelude: true,
},
],
proc_macro: Err(
"no proc macro loaded for sysroot crate",
@ -1687,40 +1678,6 @@ fn rust_project_hello_world_project_model() {
),
edition: Edition2018,
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(
CrateDisplayName {
crate_name: CrateName(
@ -1748,10 +1705,10 @@ fn rust_project_hello_world_project_model() {
is_proc_macro: false,
},
CrateId(
10,
9,
): CrateData {
root_file_id: FileId(
11,
10,
),
edition: Edition2018,
version: None,
@ -1782,10 +1739,10 @@ fn rust_project_hello_world_project_model() {
is_proc_macro: false,
},
CrateId(
11,
10,
): CrateData {
root_file_id: FileId(
12,
11,
),
edition: Edition2018,
version: None,
@ -1836,7 +1793,7 @@ fn rust_project_hello_world_project_model() {
},
Dependency {
crate_id: CrateId(
9,
8,
),
name: CrateName(
"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`
/// The return type contains the path and whether or not
/// the root is a member of the current workspace
@ -509,14 +524,14 @@ impl ProjectWorkspace {
build_scripts,
toolchain: _,
} => cargo_to_crate_graph(
rustc_cfg.clone(),
cfg_overrides,
load_proc_macro,
load,
cargo,
build_scripts,
sysroot.as_ref(),
rustc,
cargo,
sysroot.as_ref(),
rustc_cfg.clone(),
cfg_overrides,
build_scripts,
),
ProjectWorkspace::DetachedFiles { files, sysroot, rustc_cfg } => {
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() {
if let Some(&from) = crates.get(&from) {
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 let Some(proc_macro) = libproc_macro {
add_dep(
@ -626,14 +641,14 @@ fn project_json_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: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
cargo: &CargoWorkspace,
build_scripts: &WorkspaceBuildScripts,
sysroot: Option<&Sysroot>,
rustc: &Option<CargoWorkspace>,
cargo: &CargoWorkspace,
sysroot: Option<&Sysroot>,
rustc_cfg: Vec<CfgFlag>,
override_cfg: &CfgOverrides,
build_scripts: &WorkspaceBuildScripts,
) -> CrateGraph {
let _p = profile::span("cargo_to_crate_graph");
let mut crate_graph = CrateGraph::default();
@ -642,13 +657,15 @@ fn cargo_to_crate_graph(
None => (SysrootPublicDeps::default(), None),
};
let cfg_options = {
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();
cfg_options.insert_atom("debug_assertions".into());
let mut pkg_crates = FxHashMap::default();
// Does any crate signal to rust-analyzer that they need the rustc_private crates?
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
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.
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 to != from && kind != TargetKind::BuildScript {
@ -767,15 +784,16 @@ fn cargo_to_crate_graph(
if let Some(rustc_workspace) = rustc {
handle_rustc_crates(
&mut crate_graph,
rustc_workspace,
&mut pkg_to_lib_crate,
load,
load_proc_macro,
rustc_workspace,
cargo,
&public_deps,
libproc_macro,
&pkg_crates,
&cfg_options,
override_cfg,
load_proc_macro,
&mut pkg_to_lib_crate,
&public_deps,
cargo,
&pkg_crates,
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
}
fn handle_rustc_crates(
crate_graph: &mut CrateGraph,
rustc_workspace: &CargoWorkspace,
pkg_to_lib_crate: &mut FxHashMap<Package, CrateId>,
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,
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,
) {
let mut rustc_pkg_crates = FxHashMap::default();
// The root package of the rustc-dev component is rustc_driver, so we match that
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
// installed for the current toolchain) and `rustc_source` is set to discover.
if let Some(root_pkg) = root_pkg {
@ -901,7 +920,16 @@ fn handle_rustc_crates(
);
pkg_to_lib_crate.insert(pkg, crate_id);
// 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);
}
}
@ -1009,7 +1037,7 @@ struct SysrootPublicDeps {
impl SysrootPublicDeps {
/// 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 {
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"
autobins = false
edition = "2021"
rust-version = "1.57"
rust-version = "1.65"
[lib]
doctest = false

View file

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

View file

@ -60,24 +60,12 @@ pub fn load_workspace(
};
let proc_macro_client = if load_config.with_proc_macro {
let mut path = AbsPathBuf::assert(std::env::current_exe()?);
let mut args = vec!["proc-macro"];
let (server_path, args): (_, &[_]) = match ws.find_sysroot_proc_macro_srv() {
Some(server_path) => (server_path, &[]),
None => (AbsPathBuf::assert(std::env::current_exe()?), &["proc-macro"]),
};
if let ProjectWorkspace::Cargo { sysroot, .. } | ProjectWorkspace::Json { sysroot, .. } =
&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())
ProcMacroServer::spawn(server_path, args).map_err(|e| e.to_string())
} else {
Err("proc macro server disabled".to_owned())
};

View file

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

View file

@ -118,6 +118,8 @@ config_data! {
/// This option does not take effect until rust-analyzer is restarted.
cargo_sysroot: Option<String> = "\"discover\"",
/// 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",
/// Unsets `#[cfg(test)]` for the specified crates.
cargo_unsetTest: Vec<String> = "[\"core\"]",
@ -157,7 +159,7 @@ config_data! {
checkOnSave_noDefaultFeatures: Option<bool> = "null",
/// Override the command rust-analyzer uses instead of `cargo check` for
/// 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
/// Cargo, you might also want to change
@ -174,9 +176,13 @@ config_data! {
/// ```
/// .
checkOnSave_overrideCommand: Option<Vec<String>> = "null",
/// Check for a specific target. Defaults to
/// `#rust-analyzer.cargo.target#`.
checkOnSave_target: Option<String> = "null",
/// Check for specific targets. Defaults to `#rust-analyzer.cargo.target#` if empty.
///
/// 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.
/// 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> = "[]",
/// Controls file watching implementation.
files_watcher: FilesWatcherDef = "\"client\"",
/// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords.
highlightRelated_breakPoints_enable: bool = "true",
/// 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",
/// Whether to show inlay type hints for return types of closures.
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.
inlayHints_lifetimeElisionHints_enable: LifetimeElisionDef = "\"never\"",
/// 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
/// site.
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\"",
/// Whether to render leading colons for type hints, and trailing colons for parameter hints.
inlayHints_renderColons: bool = "true",
@ -1143,11 +1153,10 @@ impl Config {
}
Some(_) | None => FlycheckConfig::CargoCommand {
command: self.data.checkOnSave_command.clone(),
target_triple: self
.data
.checkOnSave_target
.clone()
.or_else(|| self.data.cargo_target.clone()),
target_triples: match &self.data.checkOnSave_target.0[..] {
[] => self.data.cargo_target.clone().into_iter().collect(),
targets => targets.into(),
},
all_targets: self.data.checkOnSave_allTargets,
no_default_features: self
.data
@ -1200,10 +1209,15 @@ impl Config {
hide_closure_initialization_hints: self
.data
.inlayHints_typeHints_hideClosureInitialization,
reborrow_hints: match self.data.inlayHints_reborrowHints_enable {
ReborrowHintsDef::Always => ide::ReborrowHints::Always,
ReborrowHintsDef::Never => ide::ReborrowHints::Never,
ReborrowHintsDef::Mutable => ide::ReborrowHints::MutableOnly,
adjustment_hints: match self.data.inlayHints_expressionAdjustmentHints_enable {
AdjustmentHintsDef::Always => ide::AdjustmentHints::Always,
AdjustmentHintsDef::Never => match self.data.inlayHints_reborrowHints_enable {
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,
param_names_for_lifetime_elision_hints: self
@ -1538,6 +1552,7 @@ mod de_unit_v {
named_unit_variant!(all);
named_unit_variant!(skip_trivial);
named_unit_variant!(mutable);
named_unit_variant!(reborrow);
named_unit_variant!(with_block);
}
@ -1647,6 +1662,9 @@ enum InvocationStrategy {
PerWorkspace,
}
#[derive(Deserialize, Debug, Clone)]
struct CheckOnSaveTargets(#[serde(deserialize_with = "single_or_array")] Vec<String>);
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
enum InvocationLocation {
@ -1687,6 +1705,17 @@ enum ReborrowHintsDef {
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)]
#[serde(rename_all = "snake_case")]
enum FilesWatcherDef {
@ -1996,6 +2025,19 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
"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! {
"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."
],
},
"CheckOnSaveTargets" => set! {
"anyOf": [
{
"type": "string",
},
{
"type": "array",
"items": { "type": "string" }
},
],
},
_ => panic!("missing entry for {}: {}", ty, default),
}

View file

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

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> {
let start = offset(line_index, range.start)?;
let end = offset(line_index, range.end)?;
let text_range = TextRange::new(start, end);
Ok(text_range)
match end < start {
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> {

View file

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

View file

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

View file

@ -27,10 +27,6 @@ pub(crate) enum LineEndings {
impl LineEndings {
/// Replaces `\r\n` with `\n` in-place in `src`.
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.
// 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
@ -39,10 +35,19 @@ impl LineEndings {
let mut buf = src.into_bytes();
let mut gap_len = 0;
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 {
let idx = match find_crlf(&tail[gap_len..]) {
None => tail.len(),
Some(idx) => idx + gap_len,
None if crlf_seen => tail.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 = &mut tail[idx - gap_len..];
@ -54,15 +59,48 @@ impl LineEndings {
// Account for removed `\r`.
// After `set_len`, `buf` is guaranteed to contain utf-8 again.
let new_len = buf.len() - gap_len;
let src = unsafe {
let new_len = buf.len() - gap_len;
buf.set_len(new_len);
String::from_utf8_unchecked(buf)
};
return (src, LineEndings::Dos);
fn find_crlf(src: &[u8]) -> Option<usize> {
src.windows(2).position(|it| it == b"\r\n")
}
(src, LineEndings::Dos)
}
}
#[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.
use std::{ops::Range, sync::Arc};
use std::{mem, ops::Range, sync::Arc};
use lsp_server::Notification;
@ -133,11 +133,37 @@ impl GlobalState {
}
pub(crate) fn apply_document_changes(
old_text: &mut String,
content_changes: Vec<lsp_types::TextDocumentContentChangeEvent>,
) {
file_contents: impl FnOnce() -> String,
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 {
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.
endings: LineEndings::Unix,
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
// remember the last valid line in the index and only rebuild it if needed.
// The VFS will normalize the end of lines to `\n`.
enum IndexValid {
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;
let mut index_valid = !0u32;
for change in content_changes {
match change.range {
Some(range) => {
if !index_valid.covers(range.end.line) {
line_index.index = Arc::new(ide::LineIndex::new(old_text));
// The None case can't happen as we have handled it above already
if let Some(range) = change.range {
if index_valid <= range.end.line {
*Arc::make_mut(&mut line_index.index) = ide::LineIndex::new(&text);
}
index_valid = IndexValid::UpToLineExclusive(range.start.line);
index_valid = 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 => {
*old_text = change.text;
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.
@ -242,11 +250,10 @@ mod tests {
};
}
let mut text = String::new();
apply_document_changes(&mut text, vec![]);
let text = apply_document_changes(|| String::new(), vec![]);
assert_eq!(text, "");
apply_document_changes(
&mut text,
let text = apply_document_changes(
|| text,
vec![TextDocumentContentChangeEvent {
range: None,
range_length: None,
@ -254,39 +261,39 @@ mod tests {
}],
);
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");
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");
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");
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");
apply_document_changes(
&mut text,
let text = apply_document_changes(
|| text,
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");
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");
apply_document_changes(
&mut text,
let text = apply_document_changes(
|| text,
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");
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");
text = String::from("❤️");
apply_document_changes(&mut text, c![0, 0; 0, 0 => "a"]);
let text = String::from("❤️");
let text = apply_document_changes(|| text, c![0, 0; 0, 0 => "a"]);
assert_eq!(text, "a❤");
text = String::from("a\nb");
apply_document_changes(&mut text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]);
let text = String::from("a\nb");
let text = apply_document_changes(|| text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]);
assert_eq!(text, "adcb");
text = String::from("a\nb");
apply_document_changes(&mut text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]);
let text = String::from("a\nb");
let text = apply_document_changes(|| text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]);
assert_eq!(text, "ațc\ncb");
}

View file

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