Merge branch 'master' into keyword_completion
# Conflicts: # docs/user/generated_features.adoc
This commit is contained in:
commit
16bbf4ab7f
175 changed files with 7416 additions and 3911 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -8,3 +8,5 @@ crates/*/target
|
|||
*.iml
|
||||
.vscode/settings.json
|
||||
*.html
|
||||
generated_assists.adoc
|
||||
generated_features.adoc
|
||||
|
|
|
|||
83
Cargo.lock
generated
83
Cargo.lock
generated
|
|
@ -113,8 +113,9 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
|||
|
||||
[[package]]
|
||||
name = "chalk-derive"
|
||||
version = "0.10.1-dev"
|
||||
source = "git+https://github.com/rust-lang/chalk.git?rev=329b7f3fdd2431ed6f6778cde53f22374c7d094c#329b7f3fdd2431ed6f6778cde53f22374c7d094c"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b9bd01eab87277d973183a1d2e56bace1c11f8242c52c20636fb7dddf343ac9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -124,8 +125,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "chalk-engine"
|
||||
version = "0.10.1-dev"
|
||||
source = "git+https://github.com/rust-lang/chalk.git?rev=329b7f3fdd2431ed6f6778cde53f22374c7d094c#329b7f3fdd2431ed6f6778cde53f22374c7d094c"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c7a637c3d17ed555aef16e16952a5d1e127bd55178cc30be22afeb92da90c7d"
|
||||
dependencies = [
|
||||
"chalk-derive",
|
||||
"chalk-ir",
|
||||
|
|
@ -134,8 +136,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "chalk-ir"
|
||||
version = "0.10.1-dev"
|
||||
source = "git+https://github.com/rust-lang/chalk.git?rev=329b7f3fdd2431ed6f6778cde53f22374c7d094c#329b7f3fdd2431ed6f6778cde53f22374c7d094c"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "595e5735ded16c3f3dc348f7b15bbb2521a0080b1863cac38ad5271589944670"
|
||||
dependencies = [
|
||||
"chalk-derive",
|
||||
"lazy_static",
|
||||
|
|
@ -143,8 +146,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "chalk-solve"
|
||||
version = "0.10.1-dev"
|
||||
source = "git+https://github.com/rust-lang/chalk.git?rev=329b7f3fdd2431ed6f6778cde53f22374c7d094c#329b7f3fdd2431ed6f6778cde53f22374c7d094c"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d9d938139db425867a30cc0cfec0269406d8238d0571d829041eaa7a8455d11"
|
||||
dependencies = [
|
||||
"chalk-derive",
|
||||
"chalk-engine",
|
||||
|
|
@ -243,12 +247,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.2.2"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab6bffe714b6bb07e42f201352c34f51fefd355ace793f9e638ebd52d23f98d2"
|
||||
checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"maybe-uninit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -462,18 +467,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.3.2"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292"
|
||||
checksum = "c398b2b113b55809ceb9ee3e753fcbac793f1956663f3c36549c1346015c2afe"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.7.0"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24e40d6fd5d64e2082e0c796495c8ef5ad667a96d03e5aaa0becfd9d47bcbfb8"
|
||||
checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"inotify-sys",
|
||||
|
|
@ -561,9 +566,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "jod-thread"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4022656272c3e564a7cdebcaaba6518d844b0d0c1836597196efb5bfeb98bb49"
|
||||
checksum = "8b23360e99b8717f20aaa4598f5a6541efbe30630039fbc7706cf954a87947ae"
|
||||
|
||||
[[package]]
|
||||
name = "kernel32-sys"
|
||||
|
|
@ -809,9 +814,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "0.1.15"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d53181dcd37421c08d3b69f887784956674d09c3f9a47a04fece2b130a5b346b"
|
||||
checksum = "d508492eeb1e5c38ee696371bf7b9fc33c83d46a7d451606b96458fbbbdc2dec"
|
||||
dependencies = [
|
||||
"paste-impl",
|
||||
"proc-macro-hack",
|
||||
|
|
@ -819,9 +824,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "paste-impl"
|
||||
version = "0.1.15"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05ca490fa1c034a71412b4d1edcb904ec5a0981a4426c9eb2128c0fda7a68d17"
|
||||
checksum = "84f328a6a63192b333fce5fbb4be79db6758a4d518dfac6d54412f1492f72d32"
|
||||
dependencies = [
|
||||
"proc-macro-hack",
|
||||
"proc-macro2",
|
||||
|
|
@ -871,18 +876,18 @@ checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4"
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.17"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1502d12e458c49a4c9cbff560d0fe0060c252bc29799ed94ca2ed4bb665a0101"
|
||||
checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.6"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea"
|
||||
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
|
@ -977,7 +982,10 @@ dependencies = [
|
|||
"anymap",
|
||||
"drop_bomb",
|
||||
"either",
|
||||
"fst",
|
||||
"indexmap",
|
||||
"insta",
|
||||
"itertools",
|
||||
"log",
|
||||
"once_cell",
|
||||
"ra_arena",
|
||||
|
|
@ -1006,6 +1014,7 @@ dependencies = [
|
|||
"ra_prof",
|
||||
"ra_syntax",
|
||||
"ra_tt",
|
||||
"rustc-hash",
|
||||
"test_utils",
|
||||
]
|
||||
|
||||
|
|
@ -1119,6 +1128,7 @@ dependencies = [
|
|||
"memmap",
|
||||
"ra_mbe",
|
||||
"ra_proc_macro",
|
||||
"ra_toolchain",
|
||||
"ra_tt",
|
||||
"serde_derive",
|
||||
"test_utils",
|
||||
|
|
@ -1400,9 +1410,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
|||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.4"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1"
|
||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||
|
||||
[[package]]
|
||||
name = "salsa"
|
||||
|
|
@ -1511,9 +1521,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.53"
|
||||
version = "1.0.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "993948e75b189211a9b31a7528f950c6adc21f9720b6438ff80a7fa2f864cea2"
|
||||
checksum = "ec2c5d7e739bc07a3e73381a39d61fdb5f671c60c1df26a130690665803d8226"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
|
|
@ -1533,9 +1543,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.8.12"
|
||||
version = "0.8.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16c7a592a1ec97c9c1c68d75b6e537dcbf60c7618e038e7841e00af1d9ccf0c4"
|
||||
checksum = "ae3e2dd40a7cdc18ca80db804b7f461a39bb721160a85c9a1fa30134bf3c02a5"
|
||||
dependencies = [
|
||||
"dtoa",
|
||||
"linked-hash-map",
|
||||
|
|
@ -1576,9 +1586,9 @@ checksum = "ab16ced94dbd8a46c82fd81e3ed9a8727dac2977ea869d217bcc4ea1f122e81f"
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.29"
|
||||
version = "1.0.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb37da98a55b1d08529362d9cbb863be17556873df2585904ab9d2bc951291d0"
|
||||
checksum = "b5304cfdf27365b7585c25d4af91b35016ed21ef88f17ced89c7093b43dba8b6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -1587,9 +1597,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.12.3"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545"
|
||||
checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -1639,6 +1649,7 @@ dependencies = [
|
|||
"relative-path",
|
||||
"rustc-hash",
|
||||
"serde_json",
|
||||
"stdx",
|
||||
"text-size",
|
||||
]
|
||||
|
||||
|
|
@ -1797,9 +1808,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
version = "0.4.3"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d"
|
||||
checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d"
|
||||
dependencies = [
|
||||
"linked-hash-map",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
//! See `AssistContext`
|
||||
|
||||
use std::mem;
|
||||
|
||||
use algo::find_covering_element;
|
||||
use hir::Semantics;
|
||||
use ra_db::{FileId, FileRange};
|
||||
|
|
@ -170,13 +172,32 @@ impl Assists {
|
|||
|
||||
pub(crate) struct AssistBuilder {
|
||||
edit: TextEditBuilder,
|
||||
file: FileId,
|
||||
file_id: FileId,
|
||||
is_snippet: bool,
|
||||
edits: Vec<SourceFileEdit>,
|
||||
}
|
||||
|
||||
impl AssistBuilder {
|
||||
pub(crate) fn new(file: FileId) -> AssistBuilder {
|
||||
AssistBuilder { edit: TextEditBuilder::default(), file, is_snippet: false }
|
||||
pub(crate) fn new(file_id: FileId) -> AssistBuilder {
|
||||
AssistBuilder {
|
||||
edit: TextEditBuilder::default(),
|
||||
file_id,
|
||||
is_snippet: false,
|
||||
edits: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn edit_file(&mut self, file_id: FileId) {
|
||||
self.file_id = file_id;
|
||||
}
|
||||
|
||||
fn commit(&mut self) {
|
||||
let edit = mem::take(&mut self.edit).finish();
|
||||
if !edit.is_empty() {
|
||||
let new_edit = SourceFileEdit { file_id: self.file_id, edit };
|
||||
assert!(!self.edits.iter().any(|it| it.file_id == new_edit.file_id));
|
||||
self.edits.push(new_edit);
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove specified `range` of text.
|
||||
|
|
@ -234,21 +255,15 @@ impl AssistBuilder {
|
|||
algo::diff(&node, &new).into_text_edit(&mut self.edit)
|
||||
}
|
||||
|
||||
// FIXME: better API
|
||||
pub(crate) fn set_file(&mut self, assist_file: FileId) {
|
||||
self.file = assist_file;
|
||||
}
|
||||
|
||||
// FIXME: kill this API
|
||||
/// Get access to the raw `TextEditBuilder`.
|
||||
pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder {
|
||||
&mut self.edit
|
||||
}
|
||||
|
||||
fn finish(self) -> SourceChange {
|
||||
let edit = self.edit.finish();
|
||||
let source_file_edit = SourceFileEdit { file_id: self.file, edit };
|
||||
let mut res: SourceChange = source_file_edit.into();
|
||||
fn finish(mut self) -> SourceChange {
|
||||
self.commit();
|
||||
let mut res: SourceChange = mem::take(&mut self.edits).into();
|
||||
if self.is_snippet {
|
||||
res.is_snippet = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@ struct Test<K, T = u8> {
|
|||
}
|
||||
|
||||
fn main() {
|
||||
let test<|> = Test { t: 23, k: 33 };
|
||||
let test<|> = Test { t: 23u8, k: 33 };
|
||||
}"#,
|
||||
r#"
|
||||
struct Test<K, T = u8> {
|
||||
|
|
@ -204,7 +204,7 @@ struct Test<K, T = u8> {
|
|||
}
|
||||
|
||||
fn main() {
|
||||
let test: Test<i32> = Test { t: 23, k: 33 };
|
||||
let test: Test<i32> = Test { t: 23u8, k: 33 };
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ pub(crate) fn add_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
|
|||
let target = call.syntax().text_range();
|
||||
acc.add(AssistId("add_function"), "Add function", target, |builder| {
|
||||
let function_template = function_builder.render();
|
||||
builder.set_file(function_template.file);
|
||||
builder.edit_file(function_template.file);
|
||||
let new_fn = function_template.to_string(ctx.config.snippet_cap);
|
||||
match ctx.config.snippet_cap {
|
||||
Some(cap) => builder.insert_snippet(cap, function_template.insert_offset, new_fn),
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ impl AutoImportAssets {
|
|||
fn search_for_imports(&self, db: &RootDatabase) -> BTreeSet<ModPath> {
|
||||
let _p = profile("auto_import::search_for_imports");
|
||||
let current_crate = self.module_with_name_to_import.krate();
|
||||
ImportsLocator::new(db)
|
||||
ImportsLocator::new(db, current_crate)
|
||||
.find_imports(&self.get_search_query())
|
||||
.into_iter()
|
||||
.filter_map(|candidate| match &self.import_candidate {
|
||||
|
|
@ -841,4 +841,105 @@ fn main() {
|
|||
",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dep_import() {
|
||||
check_assist(
|
||||
auto_import,
|
||||
r"
|
||||
//- /lib.rs crate:dep
|
||||
pub struct Struct;
|
||||
|
||||
//- /main.rs crate:main deps:dep
|
||||
fn main() {
|
||||
Struct<|>
|
||||
}",
|
||||
r"use dep::Struct;
|
||||
|
||||
fn main() {
|
||||
Struct
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn whole_segment() {
|
||||
// Tests that only imports whose last segment matches the identifier get suggested.
|
||||
check_assist(
|
||||
auto_import,
|
||||
r"
|
||||
//- /lib.rs crate:dep
|
||||
pub mod fmt {
|
||||
pub trait Display {}
|
||||
}
|
||||
|
||||
pub fn panic_fmt() {}
|
||||
|
||||
//- /main.rs crate:main deps:dep
|
||||
struct S;
|
||||
|
||||
impl f<|>mt::Display for S {}",
|
||||
r"use dep::fmt;
|
||||
|
||||
struct S;
|
||||
impl fmt::Display for S {}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macro_generated() {
|
||||
// Tests that macro-generated items are suggested from external crates.
|
||||
check_assist(
|
||||
auto_import,
|
||||
r"
|
||||
//- /lib.rs crate:dep
|
||||
|
||||
macro_rules! mac {
|
||||
() => {
|
||||
pub struct Cheese;
|
||||
};
|
||||
}
|
||||
|
||||
mac!();
|
||||
|
||||
//- /main.rs crate:main deps:dep
|
||||
|
||||
fn main() {
|
||||
Cheese<|>;
|
||||
}",
|
||||
r"use dep::Cheese;
|
||||
|
||||
fn main() {
|
||||
Cheese;
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn casing() {
|
||||
// Tests that differently cased names don't interfere and we only suggest the matching one.
|
||||
check_assist(
|
||||
auto_import,
|
||||
r"
|
||||
//- /lib.rs crate:dep
|
||||
|
||||
pub struct FMT;
|
||||
pub struct fmt;
|
||||
|
||||
//- /main.rs crate:main deps:dep
|
||||
|
||||
fn main() {
|
||||
FMT<|>;
|
||||
}",
|
||||
r"use dep::FMT;
|
||||
|
||||
fn main() {
|
||||
FMT;
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext)
|
|||
parent_block: &ast::BlockExpr,
|
||||
if_expr: &ast::IfExpr,
|
||||
) -> SyntaxNode {
|
||||
let then_block_items = then_block.dedent(IndentLevel::from(1));
|
||||
let then_block_items = then_block.dedent(IndentLevel(1));
|
||||
let end_of_then = then_block_items.syntax().last_child_or_token().unwrap();
|
||||
let end_of_then =
|
||||
if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,320 @@
|
|||
use hir::{EnumVariant, Module, ModuleDef, Name};
|
||||
use ra_db::FileId;
|
||||
use ra_fmt::leading_indent;
|
||||
use ra_ide_db::{defs::Definition, search::Reference, RootDatabase};
|
||||
use ra_syntax::{
|
||||
algo::find_node_at_offset,
|
||||
ast::{self, ArgListOwner, AstNode, NameOwner, VisibilityOwner},
|
||||
SourceFile, SyntaxNode, TextRange, TextSize,
|
||||
};
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use crate::{
|
||||
assist_context::AssistBuilder, utils::insert_use_statement, AssistContext, AssistId, Assists,
|
||||
};
|
||||
|
||||
// Assist: extract_struct_from_enum_variant
|
||||
//
|
||||
// Extracts a struct from enum variant.
|
||||
//
|
||||
// ```
|
||||
// enum A { <|>One(u32, u32) }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// struct One(pub u32, pub u32);
|
||||
//
|
||||
// enum A { One(One) }
|
||||
// ```
|
||||
pub(crate) fn extract_struct_from_enum_variant(
|
||||
acc: &mut Assists,
|
||||
ctx: &AssistContext,
|
||||
) -> Option<()> {
|
||||
let variant = ctx.find_node_at_offset::<ast::EnumVariant>()?;
|
||||
let field_list = match variant.kind() {
|
||||
ast::StructKind::Tuple(field_list) => field_list,
|
||||
_ => return None,
|
||||
};
|
||||
let variant_name = variant.name()?.to_string();
|
||||
let variant_hir = ctx.sema.to_def(&variant)?;
|
||||
if existing_struct_def(ctx.db, &variant_name, &variant_hir) {
|
||||
return None;
|
||||
}
|
||||
let enum_ast = variant.parent_enum();
|
||||
let visibility = enum_ast.visibility();
|
||||
let enum_hir = ctx.sema.to_def(&enum_ast)?;
|
||||
let variant_hir_name = variant_hir.name(ctx.db);
|
||||
let enum_module_def = ModuleDef::from(enum_hir);
|
||||
let current_module = enum_hir.module(ctx.db);
|
||||
let target = variant.syntax().text_range();
|
||||
acc.add(
|
||||
AssistId("extract_struct_from_enum_variant"),
|
||||
"Extract struct from enum variant",
|
||||
target,
|
||||
|builder| {
|
||||
let definition = Definition::ModuleDef(ModuleDef::EnumVariant(variant_hir));
|
||||
let res = definition.find_usages(&ctx.db, None);
|
||||
let start_offset = variant.parent_enum().syntax().text_range().start();
|
||||
let mut visited_modules_set = FxHashSet::default();
|
||||
visited_modules_set.insert(current_module);
|
||||
for reference in res {
|
||||
let source_file = ctx.sema.parse(reference.file_range.file_id);
|
||||
update_reference(
|
||||
ctx,
|
||||
builder,
|
||||
reference,
|
||||
&source_file,
|
||||
&enum_module_def,
|
||||
&variant_hir_name,
|
||||
&mut visited_modules_set,
|
||||
);
|
||||
}
|
||||
extract_struct_def(
|
||||
builder,
|
||||
enum_ast.syntax(),
|
||||
&variant_name,
|
||||
&field_list.to_string(),
|
||||
start_offset,
|
||||
ctx.frange.file_id,
|
||||
&visibility,
|
||||
);
|
||||
let list_range = field_list.syntax().text_range();
|
||||
update_variant(builder, &variant_name, ctx.frange.file_id, list_range);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn existing_struct_def(db: &RootDatabase, variant_name: &str, variant: &EnumVariant) -> bool {
|
||||
variant
|
||||
.parent_enum(db)
|
||||
.module(db)
|
||||
.scope(db, None)
|
||||
.into_iter()
|
||||
.any(|(name, _)| name.to_string() == variant_name.to_string())
|
||||
}
|
||||
|
||||
fn insert_import(
|
||||
ctx: &AssistContext,
|
||||
builder: &mut AssistBuilder,
|
||||
path: &ast::PathExpr,
|
||||
module: &Module,
|
||||
enum_module_def: &ModuleDef,
|
||||
variant_hir_name: &Name,
|
||||
) -> Option<()> {
|
||||
let db = ctx.db;
|
||||
let mod_path = module.find_use_path(db, enum_module_def.clone());
|
||||
if let Some(mut mod_path) = mod_path {
|
||||
mod_path.segments.pop();
|
||||
mod_path.segments.push(variant_hir_name.clone());
|
||||
insert_use_statement(path.syntax(), &mod_path, ctx, builder.text_edit_builder());
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn extract_struct_def(
|
||||
builder: &mut AssistBuilder,
|
||||
enum_ast: &SyntaxNode,
|
||||
variant_name: &str,
|
||||
variant_list: &str,
|
||||
start_offset: TextSize,
|
||||
file_id: FileId,
|
||||
visibility: &Option<ast::Visibility>,
|
||||
) -> Option<()> {
|
||||
let visibility_string = if let Some(visibility) = visibility {
|
||||
format!("{} ", visibility.to_string())
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
let indent = if let Some(indent) = leading_indent(enum_ast) {
|
||||
indent.to_string()
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
let struct_def = format!(
|
||||
r#"{}struct {}{};
|
||||
|
||||
{}"#,
|
||||
visibility_string,
|
||||
variant_name,
|
||||
list_with_visibility(variant_list),
|
||||
indent
|
||||
);
|
||||
builder.edit_file(file_id);
|
||||
builder.insert(start_offset, struct_def);
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn update_variant(
|
||||
builder: &mut AssistBuilder,
|
||||
variant_name: &str,
|
||||
file_id: FileId,
|
||||
list_range: TextRange,
|
||||
) -> Option<()> {
|
||||
let inside_variant_range = TextRange::new(
|
||||
list_range.start().checked_add(TextSize::from(1))?,
|
||||
list_range.end().checked_sub(TextSize::from(1))?,
|
||||
);
|
||||
builder.edit_file(file_id);
|
||||
builder.replace(inside_variant_range, variant_name);
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn update_reference(
|
||||
ctx: &AssistContext,
|
||||
builder: &mut AssistBuilder,
|
||||
reference: Reference,
|
||||
source_file: &SourceFile,
|
||||
enum_module_def: &ModuleDef,
|
||||
variant_hir_name: &Name,
|
||||
visited_modules_set: &mut FxHashSet<Module>,
|
||||
) -> Option<()> {
|
||||
let path_expr: ast::PathExpr = find_node_at_offset::<ast::PathExpr>(
|
||||
source_file.syntax(),
|
||||
reference.file_range.range.start(),
|
||||
)?;
|
||||
let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
|
||||
let list = call.arg_list()?;
|
||||
let segment = path_expr.path()?.segment()?;
|
||||
let module = ctx.sema.scope(&path_expr.syntax()).module()?;
|
||||
let list_range = list.syntax().text_range();
|
||||
let inside_list_range = TextRange::new(
|
||||
list_range.start().checked_add(TextSize::from(1))?,
|
||||
list_range.end().checked_sub(TextSize::from(1))?,
|
||||
);
|
||||
builder.edit_file(reference.file_range.file_id);
|
||||
if !visited_modules_set.contains(&module) {
|
||||
if insert_import(ctx, builder, &path_expr, &module, enum_module_def, variant_hir_name)
|
||||
.is_some()
|
||||
{
|
||||
visited_modules_set.insert(module);
|
||||
}
|
||||
}
|
||||
builder.replace(inside_list_range, format!("{}{}", segment, list));
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn list_with_visibility(list: &str) -> String {
|
||||
list.split(',')
|
||||
.map(|part| {
|
||||
let index = if part.chars().next().unwrap() == '(' { 1usize } else { 0 };
|
||||
let mut mod_part = part.trim().to_string();
|
||||
mod_part.insert_str(index, "pub ");
|
||||
mod_part
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::{
|
||||
tests::{check_assist, check_assist_not_applicable},
|
||||
utils::FamousDefs,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_extract_struct_several_fields() {
|
||||
check_assist(
|
||||
extract_struct_from_enum_variant,
|
||||
"enum A { <|>One(u32, u32) }",
|
||||
r#"struct One(pub u32, pub u32);
|
||||
|
||||
enum A { One(One) }"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_struct_one_field() {
|
||||
check_assist(
|
||||
extract_struct_from_enum_variant,
|
||||
"enum A { <|>One(u32) }",
|
||||
r#"struct One(pub u32);
|
||||
|
||||
enum A { One(One) }"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_struct_pub_visibility() {
|
||||
check_assist(
|
||||
extract_struct_from_enum_variant,
|
||||
"pub enum A { <|>One(u32, u32) }",
|
||||
r#"pub struct One(pub u32, pub u32);
|
||||
|
||||
pub enum A { One(One) }"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_struct_with_complex_imports() {
|
||||
check_assist(
|
||||
extract_struct_from_enum_variant,
|
||||
r#"mod my_mod {
|
||||
fn another_fn() {
|
||||
let m = my_other_mod::MyEnum::MyField(1, 1);
|
||||
}
|
||||
|
||||
pub mod my_other_mod {
|
||||
fn another_fn() {
|
||||
let m = MyEnum::MyField(1, 1);
|
||||
}
|
||||
|
||||
pub enum MyEnum {
|
||||
<|>MyField(u8, u8),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn another_fn() {
|
||||
let m = my_mod::my_other_mod::MyEnum::MyField(1, 1);
|
||||
}"#,
|
||||
r#"use my_mod::my_other_mod::MyField;
|
||||
|
||||
mod my_mod {
|
||||
use my_other_mod::MyField;
|
||||
|
||||
fn another_fn() {
|
||||
let m = my_other_mod::MyEnum::MyField(MyField(1, 1));
|
||||
}
|
||||
|
||||
pub mod my_other_mod {
|
||||
fn another_fn() {
|
||||
let m = MyEnum::MyField(MyField(1, 1));
|
||||
}
|
||||
|
||||
pub struct MyField(pub u8, pub u8);
|
||||
|
||||
pub enum MyEnum {
|
||||
MyField(MyField),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn another_fn() {
|
||||
let m = my_mod::my_other_mod::MyEnum::MyField(MyField(1, 1));
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
fn check_not_applicable(ra_fixture: &str) {
|
||||
let fixture =
|
||||
format!("//- main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
|
||||
check_assist_not_applicable(extract_struct_from_enum_variant, &fixture)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_enum_not_applicable_for_element_with_no_fields() {
|
||||
check_not_applicable("enum A { <|>One }");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_enum_not_applicable_if_struct_exists() {
|
||||
check_not_applicable(
|
||||
r#"struct One;
|
||||
enum A { <|>One(u8) }"#,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -63,7 +63,7 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext) -> O
|
|||
};
|
||||
|
||||
acc.add(AssistId("fix_visibility"), assist_label, target, |builder| {
|
||||
builder.set_file(target_file);
|
||||
builder.edit_file(target_file);
|
||||
match ctx.config.snippet_cap {
|
||||
Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
|
||||
None => builder.insert(offset, format!("{} ", missing_visibility)),
|
||||
|
|
@ -106,7 +106,7 @@ fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) ->
|
|||
format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility);
|
||||
|
||||
acc.add(AssistId("fix_visibility"), assist_label, target, |builder| {
|
||||
builder.set_file(target_file);
|
||||
builder.edit_file(target_file);
|
||||
match ctx.config.snippet_cap {
|
||||
Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
|
||||
None => builder.insert(offset, format!("{} ", missing_visibility)),
|
||||
|
|
|
|||
|
|
@ -41,8 +41,6 @@ pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -
|
|||
if let Some(fn_def) = lifetime_token.ancestors().find_map(ast::FnDef::cast) {
|
||||
generate_fn_def_assist(acc, &fn_def, lifetime_token.text_range())
|
||||
} else if let Some(impl_def) = lifetime_token.ancestors().find_map(ast::ImplDef::cast) {
|
||||
// only allow naming the last anonymous lifetime
|
||||
lifetime_token.next_token().filter(|tok| tok.kind() == SyntaxKind::R_ANGLE)?;
|
||||
generate_impl_def_assist(acc, &impl_def, lifetime_token.text_range())
|
||||
} else {
|
||||
None
|
||||
|
|
@ -190,6 +188,23 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_impl_with_other_type_param() {
|
||||
check_assist(
|
||||
introduce_named_lifetime,
|
||||
"impl<I> fmt::Display for SepByBuilder<'_<|>, I>
|
||||
where
|
||||
I: Iterator,
|
||||
I::Item: fmt::Display,
|
||||
{",
|
||||
"impl<I, 'a> fmt::Display for SepByBuilder<'a, I>
|
||||
where
|
||||
I: Iterator,
|
||||
I::Item: fmt::Display,
|
||||
{",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_example_case_cursor_before_tick() {
|
||||
check_assist(
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext)
|
|||
acc.add(AssistId("replace_if_let_with_match"), "Replace with match", target, move |edit| {
|
||||
let match_expr = {
|
||||
let then_arm = {
|
||||
let then_block = then_block.reset_indent().indent(IndentLevel(1));
|
||||
let then_expr = unwrap_trivial_block(then_block);
|
||||
make::match_arm(vec![pat.clone()], then_expr)
|
||||
};
|
||||
|
|
@ -64,8 +65,8 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext)
|
|||
let else_expr = unwrap_trivial_block(else_block);
|
||||
make::match_arm(vec![pattern], else_expr)
|
||||
};
|
||||
make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]))
|
||||
.indent(IndentLevel::from_node(if_expr.syntax()))
|
||||
let match_expr = make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]));
|
||||
match_expr.indent(IndentLevel::from_node(if_expr.syntax()))
|
||||
};
|
||||
|
||||
edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr);
|
||||
|
|
@ -213,4 +214,36 @@ fn foo(x: Result<i32, ()>) {
|
|||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_indent() {
|
||||
check_assist(
|
||||
replace_if_let_with_match,
|
||||
r#"
|
||||
fn main() {
|
||||
if true {
|
||||
<|>if let Ok(rel_path) = path.strip_prefix(root_path) {
|
||||
let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
|
||||
Some((*id, rel_path))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
if true {
|
||||
match path.strip_prefix(root_path) {
|
||||
Ok(rel_path) => {
|
||||
let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
|
||||
Some((*id, rel_path))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
use ra_fmt::unwrap_trivial_block;
|
||||
use ra_syntax::{
|
||||
ast::{self, ElseBranch, Expr, LoopBodyOwner},
|
||||
match_ast, AstNode, TextRange, T,
|
||||
ast::{
|
||||
self,
|
||||
edit::{AstNodeEdit, IndentLevel},
|
||||
},
|
||||
AstNode, TextRange, T,
|
||||
};
|
||||
|
||||
use crate::{AssistContext, AssistId, Assists};
|
||||
|
|
@ -24,94 +27,73 @@ use crate::{AssistContext, AssistId, Assists};
|
|||
// }
|
||||
// ```
|
||||
pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let l_curly_token = ctx.find_token_at_offset(T!['{'])?;
|
||||
let block = ast::BlockExpr::cast(l_curly_token.parent())?;
|
||||
let parent = block.syntax().parent()?;
|
||||
let assist_id = AssistId("unwrap_block");
|
||||
let assist_label = "Unwrap block";
|
||||
|
||||
let (expr, expr_to_unwrap) = match_ast! {
|
||||
match parent {
|
||||
ast::ForExpr(for_expr) => {
|
||||
let block_expr = for_expr.loop_body()?;
|
||||
let expr_to_unwrap = extract_expr(ctx.frange.range, block_expr)?;
|
||||
(ast::Expr::ForExpr(for_expr), expr_to_unwrap)
|
||||
},
|
||||
ast::WhileExpr(while_expr) => {
|
||||
let block_expr = while_expr.loop_body()?;
|
||||
let expr_to_unwrap = extract_expr(ctx.frange.range, block_expr)?;
|
||||
(ast::Expr::WhileExpr(while_expr), expr_to_unwrap)
|
||||
},
|
||||
ast::LoopExpr(loop_expr) => {
|
||||
let block_expr = loop_expr.loop_body()?;
|
||||
let expr_to_unwrap = extract_expr(ctx.frange.range, block_expr)?;
|
||||
(ast::Expr::LoopExpr(loop_expr), expr_to_unwrap)
|
||||
},
|
||||
ast::IfExpr(if_expr) => {
|
||||
let mut resp = None;
|
||||
let l_curly_token = ctx.find_token_at_offset(T!['{'])?;
|
||||
let mut block = ast::BlockExpr::cast(l_curly_token.parent())?;
|
||||
let mut parent = block.syntax().parent()?;
|
||||
if ast::MatchArm::can_cast(parent.kind()) {
|
||||
parent = parent.ancestors().find(|it| ast::MatchExpr::can_cast(it.kind()))?
|
||||
}
|
||||
|
||||
let then_branch = if_expr.then_branch()?;
|
||||
if then_branch.l_curly_token()?.text_range().contains_range(ctx.frange.range) {
|
||||
if let Some(ancestor) = if_expr.syntax().parent().and_then(ast::IfExpr::cast) {
|
||||
// For `else if` blocks
|
||||
let ancestor_then_branch = ancestor.then_branch()?;
|
||||
let l_curly_token = then_branch.l_curly_token()?;
|
||||
let parent = ast::Expr::cast(parent)?;
|
||||
|
||||
let target = then_branch.syntax().text_range();
|
||||
return acc.add(assist_id, assist_label, target, |edit| {
|
||||
let range_to_del_else_if = TextRange::new(ancestor_then_branch.syntax().text_range().end(), l_curly_token.text_range().start());
|
||||
let range_to_del_rest = TextRange::new(then_branch.syntax().text_range().end(), if_expr.syntax().text_range().end());
|
||||
match parent.clone() {
|
||||
ast::Expr::ForExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::LoopExpr(_) => (),
|
||||
ast::Expr::MatchExpr(_) => block = block.dedent(IndentLevel(1)),
|
||||
ast::Expr::IfExpr(if_expr) => {
|
||||
let then_branch = if_expr.then_branch()?;
|
||||
if then_branch == block {
|
||||
if let Some(ancestor) = if_expr.syntax().parent().and_then(ast::IfExpr::cast) {
|
||||
// For `else if` blocks
|
||||
let ancestor_then_branch = ancestor.then_branch()?;
|
||||
|
||||
edit.delete(range_to_del_rest);
|
||||
edit.delete(range_to_del_else_if);
|
||||
edit.replace(target, update_expr_string(then_branch.to_string(), &[' ', '{']));
|
||||
});
|
||||
} else {
|
||||
resp = Some((ast::Expr::IfExpr(if_expr.clone()), Expr::BlockExpr(then_branch)));
|
||||
}
|
||||
} else if let Some(else_branch) = if_expr.else_branch() {
|
||||
match else_branch {
|
||||
ElseBranch::Block(else_block) => {
|
||||
let l_curly_token = else_block.l_curly_token()?;
|
||||
if l_curly_token.text_range().contains_range(ctx.frange.range) {
|
||||
let target = else_block.syntax().text_range();
|
||||
return acc.add(assist_id, assist_label, target, |edit| {
|
||||
let range_to_del = TextRange::new(then_branch.syntax().text_range().end(), l_curly_token.text_range().start());
|
||||
let target = then_branch.syntax().text_range();
|
||||
return acc.add(assist_id, assist_label, target, |edit| {
|
||||
let range_to_del_else_if = TextRange::new(
|
||||
ancestor_then_branch.syntax().text_range().end(),
|
||||
l_curly_token.text_range().start(),
|
||||
);
|
||||
let range_to_del_rest = TextRange::new(
|
||||
then_branch.syntax().text_range().end(),
|
||||
if_expr.syntax().text_range().end(),
|
||||
);
|
||||
|
||||
edit.delete(range_to_del);
|
||||
edit.replace(target, update_expr_string(else_block.to_string(), &[' ', '{']));
|
||||
});
|
||||
}
|
||||
},
|
||||
ElseBranch::IfExpr(_) => {},
|
||||
}
|
||||
edit.delete(range_to_del_rest);
|
||||
edit.delete(range_to_del_else_if);
|
||||
edit.replace(
|
||||
target,
|
||||
update_expr_string(then_branch.to_string(), &[' ', '{']),
|
||||
);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
let target = block.syntax().text_range();
|
||||
return acc.add(assist_id, assist_label, target, |edit| {
|
||||
let range_to_del = TextRange::new(
|
||||
then_branch.syntax().text_range().end(),
|
||||
l_curly_token.text_range().start(),
|
||||
);
|
||||
|
||||
resp?
|
||||
},
|
||||
_ => return None,
|
||||
edit.delete(range_to_del);
|
||||
edit.replace(target, update_expr_string(block.to_string(), &[' ', '{']));
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let target = expr_to_unwrap.syntax().text_range();
|
||||
acc.add(assist_id, assist_label, target, |edit| {
|
||||
edit.replace(
|
||||
expr.syntax().text_range(),
|
||||
update_expr_string(expr_to_unwrap.to_string(), &[' ', '{', '\n']),
|
||||
let unwrapped = unwrap_trivial_block(block);
|
||||
let target = unwrapped.syntax().text_range();
|
||||
acc.add(assist_id, assist_label, target, |builder| {
|
||||
builder.replace(
|
||||
parent.syntax().text_range(),
|
||||
update_expr_string(unwrapped.to_string(), &[' ', '{', '\n']),
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_expr(cursor_range: TextRange, block: ast::BlockExpr) -> Option<ast::Expr> {
|
||||
let cursor_in_range = block.l_curly_token()?.text_range().contains_range(cursor_range);
|
||||
|
||||
if cursor_in_range {
|
||||
Some(unwrap_trivial_block(block))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn update_expr_string(expr_str: String, trim_start_pat: &[char]) -> String {
|
||||
let expr_string = expr_str.trim_start_matches(trim_start_pat);
|
||||
let mut expr_string_lines: Vec<&str> = expr_string.lines().collect();
|
||||
|
|
@ -489,6 +471,30 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unwrap_match_arm() {
|
||||
check_assist(
|
||||
unwrap_block,
|
||||
r#"
|
||||
fn main() {
|
||||
match rel_path {
|
||||
Ok(rel_path) => {<|>
|
||||
let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
|
||||
Some((*id, rel_path))
|
||||
}
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
|
||||
Some((*id, rel_path))
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_if_in_while_bad_cursor_position() {
|
||||
check_assist_not_applicable(
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ mod handlers {
|
|||
mod change_return_type_to_result;
|
||||
mod change_visibility;
|
||||
mod early_return;
|
||||
mod extract_struct_from_enum_variant;
|
||||
mod fill_match_arms;
|
||||
mod fix_visibility;
|
||||
mod flip_binexpr;
|
||||
|
|
@ -155,6 +156,7 @@ mod handlers {
|
|||
change_return_type_to_result::change_return_type_to_result,
|
||||
change_visibility::change_visibility,
|
||||
early_return::convert_to_guarded_return,
|
||||
extract_struct_from_enum_variant::extract_struct_from_enum_variant,
|
||||
fill_match_arms::fill_match_arms,
|
||||
fix_visibility::fix_visibility,
|
||||
flip_binexpr::flip_binexpr,
|
||||
|
|
|
|||
|
|
@ -337,6 +337,21 @@ fn main() {
|
|||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_extract_struct_from_enum_variant() {
|
||||
check_doc_test(
|
||||
"extract_struct_from_enum_variant",
|
||||
r#####"
|
||||
enum A { <|>One(u32, u32) }
|
||||
"#####,
|
||||
r#####"
|
||||
struct One(pub u32, pub u32);
|
||||
|
||||
enum A { One(One) }
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_fill_match_arms() {
|
||||
check_doc_test(
|
||||
|
|
|
|||
|
|
@ -15,12 +15,10 @@ use std::{
|
|||
|
||||
use ra_cfg::CfgOptions;
|
||||
use ra_syntax::SmolStr;
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustc_hash::FxHashSet;
|
||||
use ra_tt::TokenExpander;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use crate::{RelativePath, RelativePathBuf};
|
||||
use fmt::Display;
|
||||
use ra_tt::TokenExpander;
|
||||
|
||||
/// `FileId` is an integer which uniquely identifies a file. File paths are
|
||||
/// messy and system-dependent, so most of the code should work directly with
|
||||
|
|
@ -111,7 +109,7 @@ impl CrateName {
|
|||
}
|
||||
}
|
||||
|
||||
impl Display for CrateName {
|
||||
impl fmt::Display for CrateName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
|
|
@ -337,15 +335,11 @@ impl Env {
|
|||
}
|
||||
|
||||
impl ExternSource {
|
||||
pub fn extern_path(&self, path: impl AsRef<Path>) -> Option<(ExternSourceId, RelativePathBuf)> {
|
||||
let path = path.as_ref();
|
||||
pub fn extern_path(&self, path: &Path) -> Option<(ExternSourceId, RelativePathBuf)> {
|
||||
self.extern_paths.iter().find_map(|(root_path, id)| {
|
||||
if let Ok(rel_path) = path.strip_prefix(root_path) {
|
||||
let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
|
||||
Some((*id, rel_path))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
let rel_path = path.strip_prefix(root_path).ok()?;
|
||||
let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
|
||||
Some((*id, rel_path))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,12 +7,13 @@ use std::{panic, sync::Arc};
|
|||
|
||||
use ra_prof::profile;
|
||||
use ra_syntax::{ast, Parse, SourceFile, TextRange, TextSize};
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
pub use crate::{
|
||||
cancellation::Canceled,
|
||||
input::{
|
||||
CrateGraph, CrateId, CrateName, Dependency, Edition, Env, ExternSource, ExternSourceId,
|
||||
FileId, ProcMacroId, SourceRoot, SourceRootId,
|
||||
CrateData, CrateGraph, CrateId, CrateName, Dependency, Edition, Env, ExternSource,
|
||||
ExternSourceId, FileId, ProcMacroId, SourceRoot, SourceRootId,
|
||||
},
|
||||
};
|
||||
pub use relative_path::{RelativePath, RelativePathBuf};
|
||||
|
|
@ -89,15 +90,13 @@ pub const DEFAULT_LRU_CAP: usize = 128;
|
|||
pub trait FileLoader {
|
||||
/// Text of the file.
|
||||
fn file_text(&self, file_id: FileId) -> Arc<String>;
|
||||
fn resolve_relative_path(&self, anchor: FileId, relative_path: &RelativePath)
|
||||
-> Option<FileId>;
|
||||
fn relevant_crates(&self, file_id: FileId) -> Arc<Vec<CrateId>>;
|
||||
|
||||
fn resolve_extern_path(
|
||||
&self,
|
||||
extern_id: ExternSourceId,
|
||||
relative_path: &RelativePath,
|
||||
) -> Option<FileId>;
|
||||
/// Note that we intentionally accept a `&str` and not a `&Path` here. This
|
||||
/// method exists to handle `#[path = "/some/path.rs"] mod foo;` and such,
|
||||
/// so the input is guaranteed to be utf-8 string. We might introduce
|
||||
/// `struct StrPath(str)` for clarity some day, but it's a bit messy, so we
|
||||
/// get by with a `&str` for the time being.
|
||||
fn resolve_path(&self, anchor: FileId, path: &str) -> Option<FileId>;
|
||||
fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>>;
|
||||
}
|
||||
|
||||
/// Database which stores all significant input facts: source code and project
|
||||
|
|
@ -135,16 +134,21 @@ pub trait SourceDatabaseExt: SourceDatabase {
|
|||
#[salsa::input]
|
||||
fn source_root(&self, id: SourceRootId) -> Arc<SourceRoot>;
|
||||
|
||||
fn source_root_crates(&self, id: SourceRootId) -> Arc<Vec<CrateId>>;
|
||||
fn source_root_crates(&self, id: SourceRootId) -> Arc<FxHashSet<CrateId>>;
|
||||
}
|
||||
|
||||
fn source_root_crates(
|
||||
db: &(impl SourceDatabaseExt + SourceDatabase),
|
||||
id: SourceRootId,
|
||||
) -> Arc<Vec<CrateId>> {
|
||||
let root = db.source_root(id);
|
||||
) -> Arc<FxHashSet<CrateId>> {
|
||||
let graph = db.crate_graph();
|
||||
let res = root.walk().filter_map(|it| graph.crate_id_for_crate_root(it)).collect::<Vec<_>>();
|
||||
let res = graph
|
||||
.iter()
|
||||
.filter(|&krate| {
|
||||
let root_file = graph[krate].root_file_id;
|
||||
db.file_source_root(root_file) == id
|
||||
})
|
||||
.collect::<FxHashSet<_>>();
|
||||
Arc::new(res)
|
||||
}
|
||||
|
||||
|
|
@ -155,33 +159,30 @@ impl<T: SourceDatabaseExt> FileLoader for FileLoaderDelegate<&'_ T> {
|
|||
fn file_text(&self, file_id: FileId) -> Arc<String> {
|
||||
SourceDatabaseExt::file_text(self.0, file_id)
|
||||
}
|
||||
fn resolve_relative_path(
|
||||
&self,
|
||||
anchor: FileId,
|
||||
relative_path: &RelativePath,
|
||||
) -> Option<FileId> {
|
||||
let path = {
|
||||
let mut path = self.0.file_relative_path(anchor);
|
||||
assert!(path.pop());
|
||||
path.push(relative_path);
|
||||
path.normalize()
|
||||
};
|
||||
let source_root = self.0.file_source_root(anchor);
|
||||
let source_root = self.0.source_root(source_root);
|
||||
source_root.file_by_relative_path(&path)
|
||||
fn resolve_path(&self, anchor: FileId, path: &str) -> Option<FileId> {
|
||||
// FIXME: this *somehow* should be platform agnostic...
|
||||
if std::path::Path::new(path).is_absolute() {
|
||||
let krate = *self.relevant_crates(anchor).iter().next()?;
|
||||
let (extern_source_id, relative_file) =
|
||||
self.0.crate_graph()[krate].extern_source.extern_path(path.as_ref())?;
|
||||
|
||||
let source_root = self.0.source_root(SourceRootId(extern_source_id.0));
|
||||
source_root.file_by_relative_path(&relative_file)
|
||||
} else {
|
||||
let rel_path = {
|
||||
let mut rel_path = self.0.file_relative_path(anchor);
|
||||
assert!(rel_path.pop());
|
||||
rel_path.push(path);
|
||||
rel_path.normalize()
|
||||
};
|
||||
let source_root = self.0.file_source_root(anchor);
|
||||
let source_root = self.0.source_root(source_root);
|
||||
source_root.file_by_relative_path(&rel_path)
|
||||
}
|
||||
}
|
||||
|
||||
fn relevant_crates(&self, file_id: FileId) -> Arc<Vec<CrateId>> {
|
||||
fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>> {
|
||||
let source_root = self.0.file_source_root(file_id);
|
||||
self.0.source_root_crates(source_root)
|
||||
}
|
||||
|
||||
fn resolve_extern_path(
|
||||
&self,
|
||||
extern_id: ExternSourceId,
|
||||
relative_path: &RelativePath,
|
||||
) -> Option<FileId> {
|
||||
let source_root = self.0.source_root(SourceRootId(extern_id.0));
|
||||
source_root.file_by_relative_path(&relative_path)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,17 @@ pub use cargo_metadata::diagnostic::{
|
|||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum FlycheckConfig {
|
||||
CargoCommand { command: String, all_targets: bool, all_features: bool, extra_args: Vec<String> },
|
||||
CustomCommand { command: String, args: Vec<String> },
|
||||
CargoCommand {
|
||||
command: String,
|
||||
all_targets: bool,
|
||||
all_features: bool,
|
||||
features: Vec<String>,
|
||||
extra_args: Vec<String>,
|
||||
},
|
||||
CustomCommand {
|
||||
command: String,
|
||||
args: Vec<String>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Flycheck wraps the shared state and communication machinery used for
|
||||
|
|
@ -188,7 +197,13 @@ impl FlycheckThread {
|
|||
self.check_process = None;
|
||||
|
||||
let mut cmd = match &self.config {
|
||||
FlycheckConfig::CargoCommand { command, all_targets, all_features, extra_args } => {
|
||||
FlycheckConfig::CargoCommand {
|
||||
command,
|
||||
all_targets,
|
||||
all_features,
|
||||
extra_args,
|
||||
features,
|
||||
} => {
|
||||
let mut cmd = Command::new(ra_toolchain::cargo());
|
||||
cmd.arg(command);
|
||||
cmd.args(&["--workspace", "--message-format=json", "--manifest-path"])
|
||||
|
|
@ -198,6 +213,9 @@ impl FlycheckThread {
|
|||
}
|
||||
if *all_features {
|
||||
cmd.arg("--all-features");
|
||||
} else if !features.is_empty() {
|
||||
cmd.arg("--features");
|
||||
cmd.arg(features.join(" "));
|
||||
}
|
||||
cmd.args(extra_args);
|
||||
cmd
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use hir_def::{
|
|||
builtin_type::BuiltinType,
|
||||
docs::Documentation,
|
||||
expr::{BindingAnnotation, Pat, PatId},
|
||||
import_map,
|
||||
per_ns::PerNs,
|
||||
resolver::{HasResolver, Resolver},
|
||||
type_ref::{Mutability, TypeRef},
|
||||
|
|
@ -98,6 +99,23 @@ impl Crate {
|
|||
db.crate_graph()[self.id].display_name.as_ref().cloned()
|
||||
}
|
||||
|
||||
pub fn query_external_importables(
|
||||
self,
|
||||
db: &dyn DefDatabase,
|
||||
query: &str,
|
||||
) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> {
|
||||
import_map::search_dependencies(
|
||||
db,
|
||||
self.into(),
|
||||
import_map::Query::new(query).anchor_end().case_sensitive().limit(40),
|
||||
)
|
||||
.into_iter()
|
||||
.map(|item| match item {
|
||||
ItemInNs::Types(mod_id) | ItemInNs::Values(mod_id) => Either::Left(mod_id.into()),
|
||||
ItemInNs::Macros(mac_id) => Either::Right(mac_id.into()),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn all(db: &dyn HirDatabase) -> Vec<Crate> {
|
||||
db.crate_graph().iter().map(|id| Crate { id }).collect()
|
||||
}
|
||||
|
|
@ -637,6 +655,10 @@ impl Function {
|
|||
db.function_data(self.id).params.clone()
|
||||
}
|
||||
|
||||
pub fn is_unsafe(self, db: &dyn HirDatabase) -> bool {
|
||||
db.function_data(self.id).is_unsafe
|
||||
}
|
||||
|
||||
pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
|
||||
let _p = profile("Function::diagnostics");
|
||||
let infer = db.infer(self.id.into());
|
||||
|
|
@ -1190,6 +1212,10 @@ impl Type {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn is_raw_ptr(&self) -> bool {
|
||||
matches!(&self.ty.value, Ty::Apply(ApplicationTy { ctor: TypeCtor::RawPtr(..), .. }))
|
||||
}
|
||||
|
||||
pub fn contains_unknown(&self) -> bool {
|
||||
return go(&self.ty.value);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@
|
|||
pub use hir_def::db::{
|
||||
AttrsQuery, BodyQuery, BodyWithSourceMapQuery, ConstDataQuery, CrateDefMapQueryQuery,
|
||||
CrateLangItemsQuery, DefDatabase, DefDatabaseStorage, DocumentationQuery, EnumDataQuery,
|
||||
ExprScopesQuery, FunctionDataQuery, GenericParamsQuery, ImplDataQuery, InternConstQuery,
|
||||
InternDatabase, InternDatabaseStorage, InternEnumQuery, InternFunctionQuery, InternImplQuery,
|
||||
InternStaticQuery, InternStructQuery, InternTraitQuery, InternTypeAliasQuery, InternUnionQuery,
|
||||
LangItemQuery, ModuleLangItemsQuery, RawItemsQuery, StaticDataQuery, StructDataQuery,
|
||||
TraitDataQuery, TypeAliasDataQuery, UnionDataQuery,
|
||||
ExprScopesQuery, FunctionDataQuery, GenericParamsQuery, ImplDataQuery, ImportMapQuery,
|
||||
InternConstQuery, InternDatabase, InternDatabaseStorage, InternEnumQuery, InternFunctionQuery,
|
||||
InternImplQuery, InternStaticQuery, InternStructQuery, InternTraitQuery, InternTypeAliasQuery,
|
||||
InternUnionQuery, LangItemQuery, ModuleLangItemsQuery, RawItemsQuery, StaticDataQuery,
|
||||
StructDataQuery, TraitDataQuery, TypeAliasDataQuery, UnionDataQuery,
|
||||
};
|
||||
pub use hir_expand::db::{
|
||||
AstDatabase, AstDatabaseStorage, AstIdMapQuery, InternEagerExpansionQuery, InternMacroQuery,
|
||||
|
|
@ -18,8 +18,8 @@ pub use hir_ty::db::{
|
|||
GenericDefaultsQuery, GenericPredicatesForParamQuery, GenericPredicatesQuery, HirDatabase,
|
||||
HirDatabaseStorage, ImplDatumQuery, ImplSelfTyQuery, ImplTraitQuery, ImplsForTraitQuery,
|
||||
ImplsInCrateQuery, InferQueryQuery, InternAssocTyValueQuery, InternChalkImplQuery,
|
||||
InternTypeCtorQuery, InternTypeParamIdQuery, StructDatumQuery, TraitDatumQuery,
|
||||
TraitSolveQuery, TyQuery, ValueTyQuery,
|
||||
InternTypeCtorQuery, InternTypeParamIdQuery, ReturnTypeImplTraitsQuery, StructDatumQuery,
|
||||
TraitDatumQuery, TraitSolveQuery, TyQuery, ValueTyQuery,
|
||||
};
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -122,8 +122,9 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
|
|||
let macro_call =
|
||||
self.find_file(actual_macro_call.syntax().clone()).with_value(actual_macro_call);
|
||||
let sa = self.analyze2(macro_call.map(|it| it.syntax()), None);
|
||||
let krate = sa.resolver.krate()?;
|
||||
let macro_call_id = macro_call
|
||||
.as_call_id(self.db, |path| sa.resolver.resolve_path_as_macro(self.db, &path))?;
|
||||
.as_call_id(self.db, krate, |path| sa.resolver.resolve_path_as_macro(self.db, &path))?;
|
||||
hir_expand::db::expand_hypothetical(self.db, macro_call_id, hypothetical_args, token_to_map)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -307,7 +307,8 @@ impl SourceAnalyzer {
|
|||
db: &dyn HirDatabase,
|
||||
macro_call: InFile<&ast::MacroCall>,
|
||||
) -> Option<HirFileId> {
|
||||
let macro_call_id = macro_call.as_call_id(db.upcast(), |path| {
|
||||
let krate = self.resolver.krate()?;
|
||||
let macro_call_id = macro_call.as_call_id(db.upcast(), krate, |path| {
|
||||
self.resolver.resolve_path_as_macro(db.upcast(), &path)
|
||||
})?;
|
||||
Some(macro_call_id.as_file())
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ rustc-hash = "1.1.0"
|
|||
either = "1.5.3"
|
||||
anymap = "0.12.1"
|
||||
drop_bomb = "0.1.4"
|
||||
fst = { version = "0.4", default-features = false }
|
||||
itertools = "0.9.0"
|
||||
indexmap = "1.4.0"
|
||||
|
||||
stdx = { path = "../stdx" }
|
||||
|
||||
|
|
|
|||
|
|
@ -87,12 +87,18 @@ impl Attrs {
|
|||
}
|
||||
|
||||
pub(crate) fn new(owner: &dyn AttrsOwner, hygiene: &Hygiene) -> Attrs {
|
||||
let docs = ast::CommentIter::from_syntax_node(owner.syntax()).doc_comment_text().map(
|
||||
|docs_text| Attr {
|
||||
input: Some(AttrInput::Literal(SmolStr::new(docs_text))),
|
||||
path: ModPath::from(hir_expand::name!(doc)),
|
||||
},
|
||||
);
|
||||
let mut attrs = owner.attrs().peekable();
|
||||
let entries = if attrs.peek().is_none() {
|
||||
// Avoid heap allocation
|
||||
None
|
||||
} else {
|
||||
Some(attrs.flat_map(|ast| Attr::from_src(ast, hygiene)).collect())
|
||||
Some(attrs.flat_map(|ast| Attr::from_src(ast, hygiene)).chain(docs).collect())
|
||||
};
|
||||
Attrs { entries }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ impl Expander {
|
|||
|
||||
let macro_call = InFile::new(self.current_file_id, ¯o_call);
|
||||
|
||||
if let Some(call_id) = macro_call.as_call_id(db, |path| {
|
||||
if let Some(call_id) = macro_call.as_call_id(db, self.crate_def_map.krate, |path| {
|
||||
if let Some(local_scope) = local_scope {
|
||||
if let Some(def) = path.as_ident().and_then(|n| local_scope.get_legacy_macro(n)) {
|
||||
return Some(def);
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ pub struct FunctionData {
|
|||
/// True if the first param is `self`. This is relevant to decide whether this
|
||||
/// can be called as a method.
|
||||
pub has_self_param: bool,
|
||||
pub is_unsafe: bool,
|
||||
pub visibility: RawVisibility,
|
||||
}
|
||||
|
||||
|
|
@ -85,17 +86,20 @@ impl FunctionData {
|
|||
ret_type
|
||||
};
|
||||
|
||||
let is_unsafe = src.value.unsafe_token().is_some();
|
||||
|
||||
let vis_default = RawVisibility::default_for_container(loc.container);
|
||||
let visibility =
|
||||
RawVisibility::from_ast_with_default(db, vis_default, src.map(|s| s.visibility()));
|
||||
|
||||
let sig = FunctionData { name, params, ret_type, has_self_param, visibility, attrs };
|
||||
let sig =
|
||||
FunctionData { name, params, ret_type, has_self_param, is_unsafe, visibility, attrs };
|
||||
Arc::new(sig)
|
||||
}
|
||||
}
|
||||
|
||||
fn desugar_future_path(orig: TypeRef) -> Path {
|
||||
let path = path![std::future::Future];
|
||||
let path = path![core::future::Future];
|
||||
let mut generic_args: Vec<_> = std::iter::repeat(None).take(path.segments.len() - 1).collect();
|
||||
let mut last = GenericArgs::empty();
|
||||
last.bindings.push(AssociatedTypeBinding {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//! Defines database & queries for name resolution.
|
||||
use std::sync::Arc;
|
||||
|
||||
use hir_expand::{db::AstDatabase, name::Name, HirFileId};
|
||||
use hir_expand::{db::AstDatabase, HirFileId};
|
||||
use ra_db::{salsa, CrateId, SourceDatabase, Upcast};
|
||||
use ra_prof::profile;
|
||||
use ra_syntax::SmolStr;
|
||||
|
|
@ -12,13 +12,10 @@ use crate::{
|
|||
body::{scope::ExprScopes, Body, BodySourceMap},
|
||||
data::{ConstData, FunctionData, ImplData, StaticData, TraitData, TypeAliasData},
|
||||
docs::Documentation,
|
||||
find_path,
|
||||
generics::GenericParams,
|
||||
item_scope::ItemInNs,
|
||||
import_map::ImportMap,
|
||||
lang_item::{LangItemTarget, LangItems},
|
||||
nameres::{raw::RawItems, CrateDefMap},
|
||||
path::ModPath,
|
||||
visibility::Visibility,
|
||||
AttrDefId, ConstId, ConstLoc, DefWithBodyId, EnumId, EnumLoc, FunctionId, FunctionLoc,
|
||||
GenericDefId, ImplId, ImplLoc, ModuleId, StaticId, StaticLoc, StructId, StructLoc, TraitId,
|
||||
TraitLoc, TypeAliasId, TypeAliasLoc, UnionId, UnionLoc,
|
||||
|
|
@ -113,15 +110,8 @@ pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> {
|
|||
#[salsa::invoke(Documentation::documentation_query)]
|
||||
fn documentation(&self, def: AttrDefId) -> Option<Documentation>;
|
||||
|
||||
#[salsa::invoke(find_path::importable_locations_of_query)]
|
||||
fn importable_locations_of(
|
||||
&self,
|
||||
item: ItemInNs,
|
||||
krate: CrateId,
|
||||
) -> Arc<[(ModuleId, Name, Visibility)]>;
|
||||
|
||||
#[salsa::invoke(find_path::find_path_inner_query)]
|
||||
fn find_path_inner(&self, item: ItemInNs, from: ModuleId, max_len: usize) -> Option<ModPath>;
|
||||
#[salsa::invoke(ImportMap::import_map_query)]
|
||||
fn import_map(&self, krate: CrateId) -> Arc<ImportMap>;
|
||||
}
|
||||
|
||||
fn crate_def_map_wait(db: &impl DefDatabase, krate: CrateId) -> Arc<CrateDefMap> {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,13 @@ impl Documentation {
|
|||
Documentation(s.into())
|
||||
}
|
||||
|
||||
pub fn from_ast<N>(node: &N) -> Option<Documentation>
|
||||
where
|
||||
N: ast::DocCommentsOwner + ast::AttrsOwner,
|
||||
{
|
||||
docs_from_ast(node)
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&*self.0
|
||||
}
|
||||
|
|
@ -70,6 +77,45 @@ impl Documentation {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn docs_from_ast(node: &impl ast::DocCommentsOwner) -> Option<Documentation> {
|
||||
node.doc_comment_text().map(|it| Documentation::new(&it))
|
||||
pub(crate) fn docs_from_ast<N>(node: &N) -> Option<Documentation>
|
||||
where
|
||||
N: ast::DocCommentsOwner + ast::AttrsOwner,
|
||||
{
|
||||
let doc_comment_text = node.doc_comment_text();
|
||||
let doc_attr_text = expand_doc_attrs(node);
|
||||
let docs = merge_doc_comments_and_attrs(doc_comment_text, doc_attr_text);
|
||||
docs.map(|it| Documentation::new(&it))
|
||||
}
|
||||
|
||||
fn merge_doc_comments_and_attrs(
|
||||
doc_comment_text: Option<String>,
|
||||
doc_attr_text: Option<String>,
|
||||
) -> Option<String> {
|
||||
match (doc_comment_text, doc_attr_text) {
|
||||
(Some(mut comment_text), Some(attr_text)) => {
|
||||
comment_text.push_str("\n\n");
|
||||
comment_text.push_str(&attr_text);
|
||||
Some(comment_text)
|
||||
}
|
||||
(Some(comment_text), None) => Some(comment_text),
|
||||
(None, Some(attr_text)) => Some(attr_text),
|
||||
(None, None) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn expand_doc_attrs(owner: &dyn ast::AttrsOwner) -> Option<String> {
|
||||
let mut docs = String::new();
|
||||
for attr in owner.attrs() {
|
||||
if let Some(("doc", value)) =
|
||||
attr.as_simple_key_value().as_ref().map(|(k, v)| (k.as_str(), v.as_str()))
|
||||
{
|
||||
docs.push_str(value);
|
||||
docs.push_str("\n\n");
|
||||
}
|
||||
}
|
||||
if docs.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(docs.trim_end_matches("\n\n").to_owned())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
//! An algorithm to find a path to refer to a certain item.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use hir_expand::name::{known, AsName, Name};
|
||||
use ra_prof::profile;
|
||||
use rustc_hash::FxHashSet;
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{
|
||||
|
|
@ -11,7 +10,7 @@ use crate::{
|
|||
item_scope::ItemInNs,
|
||||
path::{ModPath, PathKind},
|
||||
visibility::Visibility,
|
||||
CrateId, ModuleDefId, ModuleId,
|
||||
ModuleDefId, ModuleId,
|
||||
};
|
||||
|
||||
// FIXME: handle local items
|
||||
|
|
@ -20,7 +19,7 @@ use crate::{
|
|||
/// *from where* you're referring to the item, hence the `from` parameter.
|
||||
pub fn find_path(db: &dyn DefDatabase, item: ItemInNs, from: ModuleId) -> Option<ModPath> {
|
||||
let _p = profile("find_path");
|
||||
db.find_path_inner(item, from, MAX_PATH_LEN)
|
||||
find_path_inner(db, item, from, MAX_PATH_LEN)
|
||||
}
|
||||
|
||||
const MAX_PATH_LEN: usize = 15;
|
||||
|
|
@ -36,20 +35,9 @@ impl ModPath {
|
|||
let first_segment = self.segments.first();
|
||||
first_segment == Some(&known::alloc) || first_segment == Some(&known::core)
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.segments.len()
|
||||
+ match self.kind {
|
||||
PathKind::Plain => 0,
|
||||
PathKind::Super(i) => i as usize,
|
||||
PathKind::Crate => 1,
|
||||
PathKind::Abs => 0,
|
||||
PathKind::DollarCrate(_) => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn find_path_inner_query(
|
||||
fn find_path_inner(
|
||||
db: &dyn DefDatabase,
|
||||
item: ItemInNs,
|
||||
from: ModuleId,
|
||||
|
|
@ -133,31 +121,67 @@ pub(crate) fn find_path_inner_query(
|
|||
}
|
||||
|
||||
// - otherwise, look for modules containing (reexporting) it and import it from one of those
|
||||
|
||||
let crate_root = ModuleId { local_id: def_map.root, krate: from.krate };
|
||||
let crate_attrs = db.attrs(crate_root.into());
|
||||
let prefer_no_std = crate_attrs.by_key("no_std").exists();
|
||||
let importable_locations = find_importable_locations(db, item, from);
|
||||
let mut best_path = None;
|
||||
let mut best_path_len = max_len;
|
||||
for (module_id, name) in importable_locations {
|
||||
let mut path = match db.find_path_inner(
|
||||
ItemInNs::Types(ModuleDefId::ModuleId(module_id)),
|
||||
from,
|
||||
best_path_len - 1,
|
||||
) {
|
||||
None => continue,
|
||||
Some(path) => path,
|
||||
};
|
||||
path.segments.push(name);
|
||||
|
||||
let new_path = if let Some(best_path) = best_path {
|
||||
select_best_path(best_path, path, prefer_no_std)
|
||||
} else {
|
||||
path
|
||||
};
|
||||
best_path_len = new_path.len();
|
||||
best_path = Some(new_path);
|
||||
if item.krate(db) == Some(from.krate) {
|
||||
// Item was defined in the same crate that wants to import it. It cannot be found in any
|
||||
// dependency in this case.
|
||||
|
||||
let local_imports = find_local_import_locations(db, item, from);
|
||||
for (module_id, name) in local_imports {
|
||||
if let Some(mut path) = find_path_inner(
|
||||
db,
|
||||
ItemInNs::Types(ModuleDefId::ModuleId(module_id)),
|
||||
from,
|
||||
best_path_len - 1,
|
||||
) {
|
||||
path.segments.push(name);
|
||||
|
||||
let new_path = if let Some(best_path) = best_path {
|
||||
select_best_path(best_path, path, prefer_no_std)
|
||||
} else {
|
||||
path
|
||||
};
|
||||
best_path_len = new_path.len();
|
||||
best_path = Some(new_path);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Item was defined in some upstream crate. This means that it must be exported from one,
|
||||
// too (unless we can't name it at all). It could *also* be (re)exported by the same crate
|
||||
// that wants to import it here, but we always prefer to use the external path here.
|
||||
|
||||
let crate_graph = db.crate_graph();
|
||||
let extern_paths = crate_graph[from.krate].dependencies.iter().filter_map(|dep| {
|
||||
let import_map = db.import_map(dep.crate_id);
|
||||
import_map.import_info_for(item).and_then(|info| {
|
||||
// Determine best path for containing module and append last segment from `info`.
|
||||
let mut path = find_path_inner(
|
||||
db,
|
||||
ItemInNs::Types(ModuleDefId::ModuleId(info.container)),
|
||||
from,
|
||||
best_path_len - 1,
|
||||
)?;
|
||||
path.segments.push(info.path.segments.last().unwrap().clone());
|
||||
Some(path)
|
||||
})
|
||||
});
|
||||
|
||||
for path in extern_paths {
|
||||
let new_path = if let Some(best_path) = best_path {
|
||||
select_best_path(best_path, path, prefer_no_std)
|
||||
} else {
|
||||
path
|
||||
};
|
||||
best_path = Some(new_path);
|
||||
}
|
||||
}
|
||||
|
||||
best_path
|
||||
}
|
||||
|
||||
|
|
@ -185,69 +209,86 @@ fn select_best_path(old_path: ModPath, new_path: ModPath, prefer_no_std: bool) -
|
|||
}
|
||||
}
|
||||
|
||||
fn find_importable_locations(
|
||||
/// Finds locations in `from.krate` from which `item` can be imported by `from`.
|
||||
fn find_local_import_locations(
|
||||
db: &dyn DefDatabase,
|
||||
item: ItemInNs,
|
||||
from: ModuleId,
|
||||
) -> Vec<(ModuleId, Name)> {
|
||||
let crate_graph = db.crate_graph();
|
||||
let mut result = Vec::new();
|
||||
// We only look in the crate from which we are importing, and the direct
|
||||
// dependencies. We cannot refer to names from transitive dependencies
|
||||
// directly (only through reexports in direct dependencies).
|
||||
for krate in Some(from.krate)
|
||||
.into_iter()
|
||||
.chain(crate_graph[from.krate].dependencies.iter().map(|dep| dep.crate_id))
|
||||
{
|
||||
result.extend(
|
||||
db.importable_locations_of(item, krate)
|
||||
.iter()
|
||||
.filter(|(_, _, vis)| vis.is_visible_from(db, from))
|
||||
.map(|(m, n, _)| (*m, n.clone())),
|
||||
);
|
||||
}
|
||||
result
|
||||
}
|
||||
let _p = profile("find_local_import_locations");
|
||||
|
||||
// `from` can import anything below `from` with visibility of at least `from`, and anything
|
||||
// above `from` with any visibility. That means we do not need to descend into private siblings
|
||||
// of `from` (and similar).
|
||||
|
||||
let def_map = db.crate_def_map(from.krate);
|
||||
|
||||
// Compute the initial worklist. We start with all direct child modules of `from` as well as all
|
||||
// of its (recursive) parent modules.
|
||||
let data = &def_map.modules[from.local_id];
|
||||
let mut worklist = data
|
||||
.children
|
||||
.values()
|
||||
.map(|child| ModuleId { krate: from.krate, local_id: *child })
|
||||
.collect::<Vec<_>>();
|
||||
let mut parent = data.parent;
|
||||
while let Some(p) = parent {
|
||||
worklist.push(ModuleId { krate: from.krate, local_id: p });
|
||||
parent = def_map.modules[p].parent;
|
||||
}
|
||||
|
||||
let mut seen: FxHashSet<_> = FxHashSet::default();
|
||||
|
||||
let mut locations = Vec::new();
|
||||
while let Some(module) = worklist.pop() {
|
||||
if !seen.insert(module) {
|
||||
continue; // already processed this module
|
||||
}
|
||||
|
||||
let ext_def_map;
|
||||
let data = if module.krate == from.krate {
|
||||
&def_map[module.local_id]
|
||||
} else {
|
||||
// The crate might reexport a module defined in another crate.
|
||||
ext_def_map = db.crate_def_map(module.krate);
|
||||
&ext_def_map[module.local_id]
|
||||
};
|
||||
|
||||
/// Collects all locations from which we might import the item in a particular
|
||||
/// crate. These include the original definition of the item, and any
|
||||
/// non-private `use`s.
|
||||
///
|
||||
/// Note that the crate doesn't need to be the one in which the item is defined;
|
||||
/// it might be re-exported in other crates.
|
||||
pub(crate) fn importable_locations_of_query(
|
||||
db: &dyn DefDatabase,
|
||||
item: ItemInNs,
|
||||
krate: CrateId,
|
||||
) -> Arc<[(ModuleId, Name, Visibility)]> {
|
||||
let _p = profile("importable_locations_of_query");
|
||||
let def_map = db.crate_def_map(krate);
|
||||
let mut result = Vec::new();
|
||||
for (local_id, data) in def_map.modules.iter() {
|
||||
if let Some((name, vis)) = data.scope.name_of(item) {
|
||||
let is_private = if let Visibility::Module(private_to) = vis {
|
||||
private_to.local_id == local_id
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let is_original_def = if let Some(module_def_id) = item.as_module_def_id() {
|
||||
data.scope.declarations().any(|it| it == module_def_id)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if is_private && !is_original_def {
|
||||
if vis.is_visible_from(db, from) {
|
||||
let is_private = if let Visibility::Module(private_to) = vis {
|
||||
private_to.local_id == module.local_id
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let is_original_def = if let Some(module_def_id) = item.as_module_def_id() {
|
||||
data.scope.declarations().any(|it| it == module_def_id)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// Ignore private imports. these could be used if we are
|
||||
// in a submodule of this module, but that's usually not
|
||||
// what the user wants; and if this module can import
|
||||
// the item and we're a submodule of it, so can we.
|
||||
// Also this keeps the cached data smaller.
|
||||
continue;
|
||||
if !is_private || is_original_def {
|
||||
locations.push((module, name.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Descend into all modules visible from `from`.
|
||||
for (_, per_ns) in data.scope.entries() {
|
||||
if let Some((ModuleDefId::ModuleId(module), vis)) = per_ns.take_types_vis() {
|
||||
if vis.is_visible_from(db, from) {
|
||||
worklist.push(module);
|
||||
}
|
||||
}
|
||||
result.push((ModuleId { krate, local_id }, name.clone(), vis));
|
||||
}
|
||||
}
|
||||
|
||||
Arc::from(result)
|
||||
locations
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -264,8 +305,8 @@ mod tests {
|
|||
/// `code` needs to contain a cursor marker; checks that `find_path` for the
|
||||
/// item the `path` refers to returns that same path when called from the
|
||||
/// module the cursor is in.
|
||||
fn check_found_path(code: &str, path: &str) {
|
||||
let (db, pos) = TestDB::with_position(code);
|
||||
fn check_found_path(ra_fixture: &str, path: &str) {
|
||||
let (db, pos) = TestDB::with_position(ra_fixture);
|
||||
let module = db.module_for_file(pos.file_id);
|
||||
let parsed_path_file = ra_syntax::SourceFile::parse(&format!("use {};", path));
|
||||
let ast_path = parsed_path_file
|
||||
|
|
@ -395,6 +436,44 @@ mod tests {
|
|||
check_found_path(code, "std_renamed::S");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partially_imported() {
|
||||
// Tests that short paths are used even for external items, when parts of the path are
|
||||
// already in scope.
|
||||
check_found_path(
|
||||
r#"
|
||||
//- /main.rs crate:main deps:ra_syntax
|
||||
|
||||
use ra_syntax::ast;
|
||||
<|>
|
||||
|
||||
//- /lib.rs crate:ra_syntax
|
||||
pub mod ast {
|
||||
pub enum ModuleItem {
|
||||
A, B, C,
|
||||
}
|
||||
}
|
||||
"#,
|
||||
"ast::ModuleItem",
|
||||
);
|
||||
|
||||
check_found_path(
|
||||
r#"
|
||||
//- /main.rs crate:main deps:ra_syntax
|
||||
|
||||
<|>
|
||||
|
||||
//- /lib.rs crate:ra_syntax
|
||||
pub mod ast {
|
||||
pub enum ModuleItem {
|
||||
A, B, C,
|
||||
}
|
||||
}
|
||||
"#,
|
||||
"ra_syntax::ast::ModuleItem",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_crate_reexport() {
|
||||
let code = r#"
|
||||
|
|
|
|||
679
crates/ra_hir_def/src/import_map.rs
Normal file
679
crates/ra_hir_def/src/import_map.rs
Normal file
|
|
@ -0,0 +1,679 @@
|
|||
//! A map of all publicly exported items in a crate.
|
||||
|
||||
use std::{cmp::Ordering, fmt, hash::BuildHasherDefault, sync::Arc};
|
||||
|
||||
use fst::{self, Streamer};
|
||||
use indexmap::{map::Entry, IndexMap};
|
||||
use ra_db::CrateId;
|
||||
use rustc_hash::FxHasher;
|
||||
|
||||
use crate::{
|
||||
db::DefDatabase,
|
||||
item_scope::ItemInNs,
|
||||
path::{ModPath, PathKind},
|
||||
visibility::Visibility,
|
||||
ModuleDefId, ModuleId,
|
||||
};
|
||||
|
||||
type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>;
|
||||
|
||||
/// Item import details stored in the `ImportMap`.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct ImportInfo {
|
||||
/// A path that can be used to import the item, relative to the crate's root.
|
||||
pub path: ModPath,
|
||||
/// The module containing this item.
|
||||
pub container: ModuleId,
|
||||
}
|
||||
|
||||
/// A map from publicly exported items to the path needed to import/name them from a downstream
|
||||
/// crate.
|
||||
///
|
||||
/// Reexports of items are taken into account, ie. if something is exported under multiple
|
||||
/// names, the one with the shortest import path will be used.
|
||||
///
|
||||
/// Note that all paths are relative to the containing crate's root, so the crate name still needs
|
||||
/// to be prepended to the `ModPath` before the path is valid.
|
||||
pub struct ImportMap {
|
||||
map: FxIndexMap<ItemInNs, ImportInfo>,
|
||||
|
||||
/// List of keys stored in `map`, sorted lexicographically by their `ModPath`. Indexed by the
|
||||
/// values returned by running `fst`.
|
||||
///
|
||||
/// Since a path can refer to multiple items due to namespacing, we store all items with the
|
||||
/// same path right after each other. This allows us to find all items after the FST gives us
|
||||
/// the index of the first one.
|
||||
importables: Vec<ItemInNs>,
|
||||
fst: fst::Map<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl ImportMap {
|
||||
pub fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<Self> {
|
||||
let _p = ra_prof::profile("import_map_query");
|
||||
let def_map = db.crate_def_map(krate);
|
||||
let mut import_map = FxIndexMap::with_capacity_and_hasher(64, Default::default());
|
||||
|
||||
// We look only into modules that are public(ly reexported), starting with the crate root.
|
||||
let empty = ModPath { kind: PathKind::Plain, segments: vec![] };
|
||||
let root = ModuleId { krate, local_id: def_map.root };
|
||||
let mut worklist = vec![(root, empty)];
|
||||
while let Some((module, mod_path)) = worklist.pop() {
|
||||
let ext_def_map;
|
||||
let mod_data = if module.krate == krate {
|
||||
&def_map[module.local_id]
|
||||
} else {
|
||||
// The crate might reexport a module defined in another crate.
|
||||
ext_def_map = db.crate_def_map(module.krate);
|
||||
&ext_def_map[module.local_id]
|
||||
};
|
||||
|
||||
let visible_items = mod_data.scope.entries().filter_map(|(name, per_ns)| {
|
||||
let per_ns = per_ns.filter_visibility(|vis| vis == Visibility::Public);
|
||||
if per_ns.is_none() {
|
||||
None
|
||||
} else {
|
||||
Some((name, per_ns))
|
||||
}
|
||||
});
|
||||
|
||||
for (name, per_ns) in visible_items {
|
||||
let mk_path = || {
|
||||
let mut path = mod_path.clone();
|
||||
path.segments.push(name.clone());
|
||||
path
|
||||
};
|
||||
|
||||
for item in per_ns.iter_items() {
|
||||
let path = mk_path();
|
||||
match import_map.entry(item) {
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(ImportInfo { path, container: module });
|
||||
}
|
||||
Entry::Occupied(mut entry) => {
|
||||
// If the new path is shorter, prefer that one.
|
||||
if path.len() < entry.get().path.len() {
|
||||
*entry.get_mut() = ImportInfo { path, container: module };
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we've just added a path to a module, descend into it. We might traverse
|
||||
// modules multiple times, but only if the new path to it is shorter than the
|
||||
// first (else we `continue` above).
|
||||
if let Some(ModuleDefId::ModuleId(mod_id)) = item.as_module_def_id() {
|
||||
worklist.push((mod_id, mk_path()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut importables = import_map.iter().collect::<Vec<_>>();
|
||||
|
||||
importables.sort_by(cmp);
|
||||
|
||||
// Build the FST, taking care not to insert duplicate values.
|
||||
|
||||
let mut builder = fst::MapBuilder::memory();
|
||||
let mut last_batch_start = 0;
|
||||
|
||||
for idx in 0..importables.len() {
|
||||
if let Some(next_item) = importables.get(idx + 1) {
|
||||
if cmp(&importables[last_batch_start], next_item) == Ordering::Equal {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let start = last_batch_start;
|
||||
last_batch_start = idx + 1;
|
||||
|
||||
let key = fst_path(&importables[start].1.path);
|
||||
|
||||
builder.insert(key, start as u64).unwrap();
|
||||
}
|
||||
|
||||
let fst = fst::Map::new(builder.into_inner().unwrap()).unwrap();
|
||||
let importables = importables.iter().map(|(item, _)| **item).collect();
|
||||
|
||||
Arc::new(Self { map: import_map, fst, importables })
|
||||
}
|
||||
|
||||
/// Returns the `ModPath` needed to import/mention `item`, relative to this crate's root.
|
||||
pub fn path_of(&self, item: ItemInNs) -> Option<&ModPath> {
|
||||
Some(&self.map.get(&item)?.path)
|
||||
}
|
||||
|
||||
pub fn import_info_for(&self, item: ItemInNs) -> Option<&ImportInfo> {
|
||||
self.map.get(&item)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for ImportMap {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
// `fst` and `importables` are built from `map`, so we don't need to compare them.
|
||||
self.map == other.map
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for ImportMap {}
|
||||
|
||||
impl fmt::Debug for ImportMap {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut importable_paths: Vec<_> = self
|
||||
.map
|
||||
.iter()
|
||||
.map(|(item, info)| {
|
||||
let ns = match item {
|
||||
ItemInNs::Types(_) => "t",
|
||||
ItemInNs::Values(_) => "v",
|
||||
ItemInNs::Macros(_) => "m",
|
||||
};
|
||||
format!("- {} ({})", info.path, ns)
|
||||
})
|
||||
.collect();
|
||||
|
||||
importable_paths.sort();
|
||||
f.write_str(&importable_paths.join("\n"))
|
||||
}
|
||||
}
|
||||
|
||||
fn fst_path(path: &ModPath) -> String {
|
||||
let mut s = path.to_string();
|
||||
s.make_ascii_lowercase();
|
||||
s
|
||||
}
|
||||
|
||||
fn cmp((_, lhs): &(&ItemInNs, &ImportInfo), (_, rhs): &(&ItemInNs, &ImportInfo)) -> Ordering {
|
||||
let lhs_str = fst_path(&lhs.path);
|
||||
let rhs_str = fst_path(&rhs.path);
|
||||
lhs_str.cmp(&rhs_str)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Query {
|
||||
query: String,
|
||||
lowercased: String,
|
||||
anchor_end: bool,
|
||||
case_sensitive: bool,
|
||||
limit: usize,
|
||||
}
|
||||
|
||||
impl Query {
|
||||
pub fn new(query: &str) -> Self {
|
||||
Self {
|
||||
lowercased: query.to_lowercase(),
|
||||
query: query.to_string(),
|
||||
anchor_end: false,
|
||||
case_sensitive: false,
|
||||
limit: usize::max_value(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Only returns items whose paths end with the (case-insensitive) query string as their last
|
||||
/// segment.
|
||||
pub fn anchor_end(self) -> Self {
|
||||
Self { anchor_end: true, ..self }
|
||||
}
|
||||
|
||||
/// Limits the returned number of items to `limit`.
|
||||
pub fn limit(self, limit: usize) -> Self {
|
||||
Self { limit, ..self }
|
||||
}
|
||||
|
||||
/// Respect casing of the query string when matching.
|
||||
pub fn case_sensitive(self) -> Self {
|
||||
Self { case_sensitive: true, ..self }
|
||||
}
|
||||
}
|
||||
|
||||
/// Searches dependencies of `krate` for an importable path matching `query`.
|
||||
///
|
||||
/// This returns a list of items that could be imported from dependencies of `krate`.
|
||||
pub fn search_dependencies<'a>(
|
||||
db: &'a dyn DefDatabase,
|
||||
krate: CrateId,
|
||||
query: Query,
|
||||
) -> Vec<ItemInNs> {
|
||||
let _p = ra_prof::profile("search_dependencies").detail(|| format!("{:?}", query));
|
||||
|
||||
let graph = db.crate_graph();
|
||||
let import_maps: Vec<_> =
|
||||
graph[krate].dependencies.iter().map(|dep| db.import_map(dep.crate_id)).collect();
|
||||
|
||||
let automaton = fst::automaton::Subsequence::new(&query.lowercased);
|
||||
|
||||
let mut op = fst::map::OpBuilder::new();
|
||||
for map in &import_maps {
|
||||
op = op.add(map.fst.search(&automaton));
|
||||
}
|
||||
|
||||
let mut stream = op.union();
|
||||
let mut res = Vec::new();
|
||||
while let Some((_, indexed_values)) = stream.next() {
|
||||
for indexed_value in indexed_values {
|
||||
let import_map = &import_maps[indexed_value.index];
|
||||
let importables = &import_map.importables[indexed_value.value as usize..];
|
||||
|
||||
// Path shared by the importable items in this group.
|
||||
let path = &import_map.map[&importables[0]].path;
|
||||
|
||||
if query.anchor_end {
|
||||
// Last segment must match query.
|
||||
let last = path.segments.last().unwrap().to_string();
|
||||
if last.to_lowercase() != query.lowercased {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the items from this `ModPath` group. Those are all subsequent items in
|
||||
// `importables` whose paths match `path`.
|
||||
let iter = importables.iter().copied().take_while(|item| {
|
||||
let item_path = &import_map.map[item].path;
|
||||
fst_path(item_path) == fst_path(path)
|
||||
});
|
||||
|
||||
if query.case_sensitive {
|
||||
// FIXME: This does not do a subsequence match.
|
||||
res.extend(iter.filter(|item| {
|
||||
let item_path = &import_map.map[item].path;
|
||||
item_path.to_string().contains(&query.query)
|
||||
}));
|
||||
} else {
|
||||
res.extend(iter);
|
||||
}
|
||||
|
||||
if res.len() >= query.limit {
|
||||
res.truncate(query.limit);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test_db::TestDB;
|
||||
use insta::assert_snapshot;
|
||||
use itertools::Itertools;
|
||||
use ra_db::fixture::WithFixture;
|
||||
use ra_db::{SourceDatabase, Upcast};
|
||||
|
||||
fn import_map(ra_fixture: &str) -> String {
|
||||
let db = TestDB::with_files(ra_fixture);
|
||||
let crate_graph = db.crate_graph();
|
||||
|
||||
let s = crate_graph
|
||||
.iter()
|
||||
.filter_map(|krate| {
|
||||
let cdata = &crate_graph[krate];
|
||||
let name = cdata.display_name.as_ref()?;
|
||||
|
||||
let map = db.import_map(krate);
|
||||
|
||||
Some(format!("{}:\n{:?}", name, map))
|
||||
})
|
||||
.join("\n");
|
||||
s
|
||||
}
|
||||
|
||||
fn search_dependencies_of(ra_fixture: &str, krate_name: &str, query: Query) -> String {
|
||||
let db = TestDB::with_files(ra_fixture);
|
||||
let crate_graph = db.crate_graph();
|
||||
let krate = crate_graph
|
||||
.iter()
|
||||
.find(|krate| {
|
||||
crate_graph[*krate].display_name.as_ref().map(|n| n.to_string())
|
||||
== Some(krate_name.to_string())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
search_dependencies(db.upcast(), krate, query)
|
||||
.into_iter()
|
||||
.filter_map(|item| {
|
||||
let mark = match item {
|
||||
ItemInNs::Types(_) => "t",
|
||||
ItemInNs::Values(_) => "v",
|
||||
ItemInNs::Macros(_) => "m",
|
||||
};
|
||||
item.krate(db.upcast()).map(|krate| {
|
||||
let map = db.import_map(krate);
|
||||
let path = map.path_of(item).unwrap();
|
||||
format!(
|
||||
"{}::{} ({})",
|
||||
crate_graph[krate].display_name.as_ref().unwrap(),
|
||||
path,
|
||||
mark
|
||||
)
|
||||
})
|
||||
})
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smoke() {
|
||||
let map = import_map(
|
||||
r"
|
||||
//- /main.rs crate:main deps:lib
|
||||
|
||||
mod private {
|
||||
pub use lib::Pub;
|
||||
pub struct InPrivateModule;
|
||||
}
|
||||
|
||||
pub mod publ1 {
|
||||
use lib::Pub;
|
||||
}
|
||||
|
||||
pub mod real_pub {
|
||||
pub use lib::Pub;
|
||||
}
|
||||
pub mod real_pu2 { // same path length as above
|
||||
pub use lib::Pub;
|
||||
}
|
||||
|
||||
//- /lib.rs crate:lib
|
||||
pub struct Pub {}
|
||||
pub struct Pub2; // t + v
|
||||
struct Priv;
|
||||
",
|
||||
);
|
||||
|
||||
assert_snapshot!(map, @r###"
|
||||
main:
|
||||
- publ1 (t)
|
||||
- real_pu2 (t)
|
||||
- real_pub (t)
|
||||
- real_pub::Pub (t)
|
||||
lib:
|
||||
- Pub (t)
|
||||
- Pub2 (t)
|
||||
- Pub2 (v)
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prefers_shortest_path() {
|
||||
let map = import_map(
|
||||
r"
|
||||
//- /main.rs crate:main
|
||||
|
||||
pub mod sub {
|
||||
pub mod subsub {
|
||||
pub struct Def {}
|
||||
}
|
||||
|
||||
pub use super::sub::subsub::Def;
|
||||
}
|
||||
",
|
||||
);
|
||||
|
||||
assert_snapshot!(map, @r###"
|
||||
main:
|
||||
- sub (t)
|
||||
- sub::Def (t)
|
||||
- sub::subsub (t)
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_reexport_cross_crate() {
|
||||
// Reexports need to be visible from a crate, even if the original crate exports the item
|
||||
// at a shorter path.
|
||||
let map = import_map(
|
||||
r"
|
||||
//- /main.rs crate:main deps:lib
|
||||
pub mod m {
|
||||
pub use lib::S;
|
||||
}
|
||||
//- /lib.rs crate:lib
|
||||
pub struct S;
|
||||
",
|
||||
);
|
||||
|
||||
assert_snapshot!(map, @r###"
|
||||
main:
|
||||
- m (t)
|
||||
- m::S (t)
|
||||
- m::S (v)
|
||||
lib:
|
||||
- S (t)
|
||||
- S (v)
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macro_reexport() {
|
||||
let map = import_map(
|
||||
r"
|
||||
//- /main.rs crate:main deps:lib
|
||||
pub mod m {
|
||||
pub use lib::pub_macro;
|
||||
}
|
||||
//- /lib.rs crate:lib
|
||||
#[macro_export]
|
||||
macro_rules! pub_macro {
|
||||
() => {};
|
||||
}
|
||||
",
|
||||
);
|
||||
|
||||
assert_snapshot!(map, @r###"
|
||||
main:
|
||||
- m (t)
|
||||
- m::pub_macro (m)
|
||||
lib:
|
||||
- pub_macro (m)
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_reexport() {
|
||||
// Reexporting modules from a dependency adds all contents to the import map.
|
||||
let map = import_map(
|
||||
r"
|
||||
//- /main.rs crate:main deps:lib
|
||||
pub use lib::module as reexported_module;
|
||||
//- /lib.rs crate:lib
|
||||
pub mod module {
|
||||
pub struct S;
|
||||
}
|
||||
",
|
||||
);
|
||||
|
||||
assert_snapshot!(map, @r###"
|
||||
main:
|
||||
- reexported_module (t)
|
||||
- reexported_module::S (t)
|
||||
- reexported_module::S (v)
|
||||
lib:
|
||||
- module (t)
|
||||
- module::S (t)
|
||||
- module::S (v)
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cyclic_module_reexport() {
|
||||
// A cyclic reexport does not hang.
|
||||
let map = import_map(
|
||||
r"
|
||||
//- /lib.rs crate:lib
|
||||
pub mod module {
|
||||
pub struct S;
|
||||
pub use super::sub::*;
|
||||
}
|
||||
|
||||
pub mod sub {
|
||||
pub use super::module;
|
||||
}
|
||||
",
|
||||
);
|
||||
|
||||
assert_snapshot!(map, @r###"
|
||||
lib:
|
||||
- module (t)
|
||||
- module::S (t)
|
||||
- module::S (v)
|
||||
- sub (t)
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_macro() {
|
||||
let map = import_map(
|
||||
r"
|
||||
//- /lib.rs crate:lib
|
||||
macro_rules! private_macro {
|
||||
() => {};
|
||||
}
|
||||
",
|
||||
);
|
||||
|
||||
assert_snapshot!(map, @r###"
|
||||
lib:
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn namespacing() {
|
||||
let map = import_map(
|
||||
r"
|
||||
//- /lib.rs crate:lib
|
||||
pub struct Thing; // t + v
|
||||
#[macro_export]
|
||||
macro_rules! Thing { // m
|
||||
() => {};
|
||||
}
|
||||
",
|
||||
);
|
||||
|
||||
assert_snapshot!(map, @r###"
|
||||
lib:
|
||||
- Thing (m)
|
||||
- Thing (t)
|
||||
- Thing (v)
|
||||
"###);
|
||||
|
||||
let map = import_map(
|
||||
r"
|
||||
//- /lib.rs crate:lib
|
||||
pub mod Thing {} // t
|
||||
#[macro_export]
|
||||
macro_rules! Thing { // m
|
||||
() => {};
|
||||
}
|
||||
",
|
||||
);
|
||||
|
||||
assert_snapshot!(map, @r###"
|
||||
lib:
|
||||
- Thing (m)
|
||||
- Thing (t)
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search() {
|
||||
let ra_fixture = r#"
|
||||
//- /main.rs crate:main deps:dep
|
||||
//- /dep.rs crate:dep deps:tdep
|
||||
use tdep::fmt as fmt_dep;
|
||||
pub mod fmt {
|
||||
pub trait Display {
|
||||
fn fmt();
|
||||
}
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! Fmt {
|
||||
() => {};
|
||||
}
|
||||
pub struct Fmt;
|
||||
|
||||
pub fn format() {}
|
||||
pub fn no() {}
|
||||
|
||||
//- /tdep.rs crate:tdep
|
||||
pub mod fmt {
|
||||
pub struct NotImportableFromMain;
|
||||
}
|
||||
"#;
|
||||
|
||||
let res = search_dependencies_of(ra_fixture, "main", Query::new("fmt"));
|
||||
assert_snapshot!(res, @r###"
|
||||
dep::fmt (t)
|
||||
dep::Fmt (t)
|
||||
dep::Fmt (v)
|
||||
dep::Fmt (m)
|
||||
dep::fmt::Display (t)
|
||||
dep::format (v)
|
||||
"###);
|
||||
|
||||
let res = search_dependencies_of(ra_fixture, "main", Query::new("fmt").anchor_end());
|
||||
assert_snapshot!(res, @r###"
|
||||
dep::fmt (t)
|
||||
dep::Fmt (t)
|
||||
dep::Fmt (v)
|
||||
dep::Fmt (m)
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_casing() {
|
||||
let ra_fixture = r#"
|
||||
//- /main.rs crate:main deps:dep
|
||||
//- /dep.rs crate:dep
|
||||
|
||||
pub struct fmt;
|
||||
pub struct FMT;
|
||||
"#;
|
||||
|
||||
let res = search_dependencies_of(ra_fixture, "main", Query::new("FMT"));
|
||||
|
||||
assert_snapshot!(res, @r###"
|
||||
dep::fmt (t)
|
||||
dep::fmt (v)
|
||||
dep::FMT (t)
|
||||
dep::FMT (v)
|
||||
"###);
|
||||
|
||||
let res = search_dependencies_of(ra_fixture, "main", Query::new("FMT").case_sensitive());
|
||||
|
||||
assert_snapshot!(res, @r###"
|
||||
dep::FMT (t)
|
||||
dep::FMT (v)
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_limit() {
|
||||
let res = search_dependencies_of(
|
||||
r#"
|
||||
//- /main.rs crate:main deps:dep
|
||||
//- /dep.rs crate:dep
|
||||
pub mod fmt {
|
||||
pub trait Display {
|
||||
fn fmt();
|
||||
}
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! Fmt {
|
||||
() => {};
|
||||
}
|
||||
pub struct Fmt;
|
||||
|
||||
pub fn format() {}
|
||||
pub fn no() {}
|
||||
"#,
|
||||
"main",
|
||||
Query::new("").limit(2),
|
||||
);
|
||||
assert_snapshot!(res, @r###"
|
||||
dep::fmt (t)
|
||||
dep::Fmt (t)
|
||||
"###);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,11 +3,12 @@
|
|||
|
||||
use hir_expand::name::Name;
|
||||
use once_cell::sync::Lazy;
|
||||
use ra_db::CrateId;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::{
|
||||
per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, ImplId, MacroDefId, ModuleDefId,
|
||||
TraitId,
|
||||
db::DefDatabase, per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, HasModule, ImplId,
|
||||
Lookup, MacroDefId, ModuleDefId, TraitId,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
|
|
@ -203,4 +204,22 @@ impl ItemInNs {
|
|||
ItemInNs::Macros(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the crate defining this item (or `None` if `self` is built-in).
|
||||
pub fn krate(&self, db: &dyn DefDatabase) -> Option<CrateId> {
|
||||
Some(match self {
|
||||
ItemInNs::Types(did) | ItemInNs::Values(did) => match did {
|
||||
ModuleDefId::ModuleId(id) => id.krate,
|
||||
ModuleDefId::FunctionId(id) => id.lookup(db).module(db).krate,
|
||||
ModuleDefId::AdtId(id) => id.module(db).krate,
|
||||
ModuleDefId::EnumVariantId(id) => id.parent.lookup(db).container.module(db).krate,
|
||||
ModuleDefId::ConstId(id) => id.lookup(db).container.module(db).krate,
|
||||
ModuleDefId::StaticId(id) => id.lookup(db).container.module(db).krate,
|
||||
ModuleDefId::TraitId(id) => id.lookup(db).container.module(db).krate,
|
||||
ModuleDefId::TypeAliasId(id) => id.lookup(db).module(db).krate,
|
||||
ModuleDefId::BuiltinType(_) => return None,
|
||||
},
|
||||
ItemInNs::Macros(id) => return id.krate,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ pub mod child_by_source;
|
|||
|
||||
pub mod visibility;
|
||||
pub mod find_path;
|
||||
pub mod import_map;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_db;
|
||||
|
|
@ -416,6 +417,7 @@ pub trait AsMacroCall {
|
|||
fn as_call_id(
|
||||
&self,
|
||||
db: &dyn db::DefDatabase,
|
||||
krate: CrateId,
|
||||
resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
|
||||
) -> Option<MacroCallId>;
|
||||
}
|
||||
|
|
@ -424,13 +426,14 @@ impl AsMacroCall for InFile<&ast::MacroCall> {
|
|||
fn as_call_id(
|
||||
&self,
|
||||
db: &dyn db::DefDatabase,
|
||||
krate: CrateId,
|
||||
resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
|
||||
) -> Option<MacroCallId> {
|
||||
let ast_id = AstId::new(self.file_id, db.ast_id_map(self.file_id).ast_id(self.value));
|
||||
let h = Hygiene::new(db.upcast(), self.file_id);
|
||||
let path = path::ModPath::from_src(self.value.path()?, &h)?;
|
||||
|
||||
AstIdWithPath::new(ast_id.file_id, ast_id.value, path).as_call_id(db, resolver)
|
||||
AstIdWithPath::new(ast_id.file_id, ast_id.value, path).as_call_id(db, krate, resolver)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -451,6 +454,7 @@ impl AsMacroCall for AstIdWithPath<ast::MacroCall> {
|
|||
fn as_call_id(
|
||||
&self,
|
||||
db: &dyn db::DefDatabase,
|
||||
krate: CrateId,
|
||||
resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
|
||||
) -> Option<MacroCallId> {
|
||||
let def: MacroDefId = resolver(self.path.clone())?;
|
||||
|
|
@ -460,13 +464,13 @@ impl AsMacroCall for AstIdWithPath<ast::MacroCall> {
|
|||
let hygiene = Hygiene::new(db.upcast(), self.ast_id.file_id);
|
||||
|
||||
Some(
|
||||
expand_eager_macro(db.upcast(), macro_call, def, &|path: ast::Path| {
|
||||
expand_eager_macro(db.upcast(), krate, macro_call, def, &|path: ast::Path| {
|
||||
resolver(path::ModPath::from_src(path, &hygiene)?)
|
||||
})?
|
||||
.into(),
|
||||
)
|
||||
} else {
|
||||
Some(def.as_lazy_macro(db.upcast(), MacroCallKind::FnLike(self.ast_id)).into())
|
||||
Some(def.as_lazy_macro(db.upcast(), krate, MacroCallKind::FnLike(self.ast_id)).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -475,12 +479,14 @@ impl AsMacroCall for AstIdWithPath<ast::ModuleItem> {
|
|||
fn as_call_id(
|
||||
&self,
|
||||
db: &dyn db::DefDatabase,
|
||||
krate: CrateId,
|
||||
resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
|
||||
) -> Option<MacroCallId> {
|
||||
let def = resolver(self.path.clone())?;
|
||||
Some(
|
||||
def.as_lazy_macro(
|
||||
db.upcast(),
|
||||
krate,
|
||||
MacroCallKind::Attr(self.ast_id, self.path.segments.last()?.to_string()),
|
||||
)
|
||||
.into(),
|
||||
|
|
|
|||
|
|
@ -571,16 +571,18 @@ impl DefCollector<'_> {
|
|||
return false;
|
||||
}
|
||||
|
||||
if let Some(call_id) = directive.ast_id.as_call_id(self.db, |path| {
|
||||
let resolved_res = self.def_map.resolve_path_fp_with_macro(
|
||||
self.db,
|
||||
ResolveMode::Other,
|
||||
directive.module_id,
|
||||
&path,
|
||||
BuiltinShadowMode::Module,
|
||||
);
|
||||
resolved_res.resolved_def.take_macros()
|
||||
}) {
|
||||
if let Some(call_id) =
|
||||
directive.ast_id.as_call_id(self.db, self.def_map.krate, |path| {
|
||||
let resolved_res = self.def_map.resolve_path_fp_with_macro(
|
||||
self.db,
|
||||
ResolveMode::Other,
|
||||
directive.module_id,
|
||||
&path,
|
||||
BuiltinShadowMode::Module,
|
||||
);
|
||||
resolved_res.resolved_def.take_macros()
|
||||
})
|
||||
{
|
||||
resolved.push((directive.module_id, call_id, directive.depth));
|
||||
res = ReachedFixedPoint::No;
|
||||
return false;
|
||||
|
|
@ -589,9 +591,10 @@ impl DefCollector<'_> {
|
|||
true
|
||||
});
|
||||
attribute_macros.retain(|directive| {
|
||||
if let Some(call_id) = directive
|
||||
.ast_id
|
||||
.as_call_id(self.db, |path| self.resolve_attribute_macro(&directive, &path))
|
||||
if let Some(call_id) =
|
||||
directive.ast_id.as_call_id(self.db, self.def_map.krate, |path| {
|
||||
self.resolve_attribute_macro(&directive, &path)
|
||||
})
|
||||
{
|
||||
resolved.push((directive.module_id, call_id, 0));
|
||||
res = ReachedFixedPoint::No;
|
||||
|
|
@ -957,11 +960,13 @@ impl ModCollector<'_, '_> {
|
|||
}
|
||||
|
||||
// Case 2: try to resolve in legacy scope and expand macro_rules
|
||||
if let Some(macro_call_id) = ast_id.as_call_id(self.def_collector.db, |path| {
|
||||
path.as_ident().and_then(|name| {
|
||||
self.def_collector.def_map[self.module_id].scope.get_legacy_macro(&name)
|
||||
if let Some(macro_call_id) =
|
||||
ast_id.as_call_id(self.def_collector.db, self.def_collector.def_map.krate, |path| {
|
||||
path.as_ident().and_then(|name| {
|
||||
self.def_collector.def_map[self.module_id].scope.get_legacy_macro(&name)
|
||||
})
|
||||
})
|
||||
}) {
|
||||
{
|
||||
self.def_collector.unexpanded_macros.push(MacroDirective {
|
||||
module_id: self.module_id,
|
||||
ast_id,
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ impl ModDir {
|
|||
};
|
||||
|
||||
for candidate in candidate_files.iter() {
|
||||
if let Some(file_id) = db.resolve_relative_path(file_id, candidate) {
|
||||
if let Some(file_id) = db.resolve_path(file_id, candidate.as_str()) {
|
||||
let mut root_non_dir_owner = false;
|
||||
let mut mod_path = RelativePathBuf::new();
|
||||
if !(candidate.ends_with("mod.rs") || attr_path.is_some()) {
|
||||
|
|
|
|||
|
|
@ -76,6 +76,19 @@ impl ModPath {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns the number of segments in the path (counting special segments like `$crate` and
|
||||
/// `super`).
|
||||
pub fn len(&self) -> usize {
|
||||
self.segments.len()
|
||||
+ match self.kind {
|
||||
PathKind::Plain => 0,
|
||||
PathKind::Super(i) => i as usize,
|
||||
PathKind::Crate => 1,
|
||||
PathKind::Abs => 0,
|
||||
PathKind::DollarCrate(_) => 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_ident(&self) -> bool {
|
||||
self.kind == PathKind::Plain && self.segments.len() == 1
|
||||
}
|
||||
|
|
@ -273,7 +286,7 @@ impl From<Name> for ModPath {
|
|||
impl Display for ModPath {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut first_segment = true;
|
||||
let mut add_segment = |s| {
|
||||
let mut add_segment = |s| -> fmt::Result {
|
||||
if !first_segment {
|
||||
f.write_str("::")?;
|
||||
}
|
||||
|
|
@ -310,16 +323,16 @@ pub use hir_expand::name as __name;
|
|||
|
||||
#[macro_export]
|
||||
macro_rules! __known_path {
|
||||
(std::iter::IntoIterator) => {};
|
||||
(std::result::Result) => {};
|
||||
(std::ops::Range) => {};
|
||||
(std::ops::RangeFrom) => {};
|
||||
(std::ops::RangeFull) => {};
|
||||
(std::ops::RangeTo) => {};
|
||||
(std::ops::RangeToInclusive) => {};
|
||||
(std::ops::RangeInclusive) => {};
|
||||
(std::future::Future) => {};
|
||||
(std::ops::Try) => {};
|
||||
(core::iter::IntoIterator) => {};
|
||||
(core::result::Result) => {};
|
||||
(core::ops::Range) => {};
|
||||
(core::ops::RangeFrom) => {};
|
||||
(core::ops::RangeFull) => {};
|
||||
(core::ops::RangeTo) => {};
|
||||
(core::ops::RangeToInclusive) => {};
|
||||
(core::ops::RangeInclusive) => {};
|
||||
(core::future::Future) => {};
|
||||
(core::ops::Try) => {};
|
||||
($path:path) => {
|
||||
compile_error!("Please register your known path in the path module")
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
use hir_expand::MacroDefId;
|
||||
|
||||
use crate::{visibility::Visibility, ModuleDefId};
|
||||
use crate::{item_scope::ItemInNs, visibility::Visibility, ModuleDefId};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct PerNs {
|
||||
|
|
@ -84,4 +84,12 @@ impl PerNs {
|
|||
macros: self.macros.or(other.macros),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter_items(self) -> impl Iterator<Item = ItemInNs> {
|
||||
self.types
|
||||
.map(|it| ItemInNs::Types(it.0))
|
||||
.into_iter()
|
||||
.chain(self.values.map(|it| ItemInNs::Values(it.0)).into_iter())
|
||||
.chain(self.macros.map(|it| ItemInNs::Macros(it.0)).into_iter())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,9 +6,8 @@ use std::{
|
|||
};
|
||||
|
||||
use hir_expand::db::AstDatabase;
|
||||
use ra_db::{
|
||||
salsa, CrateId, ExternSourceId, FileId, FileLoader, FileLoaderDelegate, RelativePath, Upcast,
|
||||
};
|
||||
use ra_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, Upcast};
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use crate::db::DefDatabase;
|
||||
|
||||
|
|
@ -58,24 +57,12 @@ impl FileLoader for TestDB {
|
|||
fn file_text(&self, file_id: FileId) -> Arc<String> {
|
||||
FileLoaderDelegate(self).file_text(file_id)
|
||||
}
|
||||
fn resolve_relative_path(
|
||||
&self,
|
||||
anchor: FileId,
|
||||
relative_path: &RelativePath,
|
||||
) -> Option<FileId> {
|
||||
FileLoaderDelegate(self).resolve_relative_path(anchor, relative_path)
|
||||
fn resolve_path(&self, anchor: FileId, path: &str) -> Option<FileId> {
|
||||
FileLoaderDelegate(self).resolve_path(anchor, path)
|
||||
}
|
||||
fn relevant_crates(&self, file_id: FileId) -> Arc<Vec<CrateId>> {
|
||||
fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>> {
|
||||
FileLoaderDelegate(self).relevant_crates(file_id)
|
||||
}
|
||||
|
||||
fn resolve_extern_path(
|
||||
&self,
|
||||
extern_id: ExternSourceId,
|
||||
relative_path: &RelativePath,
|
||||
) -> Option<FileId> {
|
||||
FileLoaderDelegate(self).resolve_extern_path(extern_id, relative_path)
|
||||
}
|
||||
}
|
||||
|
||||
impl TestDB {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ doctest = false
|
|||
[dependencies]
|
||||
log = "0.4.8"
|
||||
either = "1.5.3"
|
||||
rustc-hash = "1.0.0"
|
||||
|
||||
ra_arena = { path = "../ra_arena" }
|
||||
ra_db = { path = "../ra_db" }
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@ use ra_syntax::{
|
|||
match_ast,
|
||||
};
|
||||
|
||||
use crate::db::AstDatabase;
|
||||
use crate::{name, quote, LazyMacroId, MacroCallId, MacroDefId, MacroDefKind};
|
||||
use crate::{db::AstDatabase, name, quote, LazyMacroId, MacroDefId, MacroDefKind};
|
||||
|
||||
macro_rules! register_builtin {
|
||||
( $($trait:ident => $expand:ident),* ) => {
|
||||
|
|
@ -156,23 +155,13 @@ fn expand_simple_derive(
|
|||
fn find_builtin_crate(db: &dyn AstDatabase, id: LazyMacroId) -> tt::TokenTree {
|
||||
// FIXME: make hygiene works for builtin derive macro
|
||||
// such that $crate can be used here.
|
||||
|
||||
let m: MacroCallId = id.into();
|
||||
let file_id = m.as_file().original_file(db);
|
||||
let cg = db.crate_graph();
|
||||
let krates = db.relevant_crates(file_id);
|
||||
let krate = match krates.get(0) {
|
||||
Some(krate) => krate,
|
||||
None => {
|
||||
let tt = quote! { core };
|
||||
return tt.token_trees[0].clone();
|
||||
}
|
||||
};
|
||||
let krate = db.lookup_intern_macro(id).krate;
|
||||
|
||||
// XXX
|
||||
// All crates except core itself should have a dependency on core,
|
||||
// We detect `core` by seeing whether it doesn't have such a dependency.
|
||||
let tt = if cg[*krate].dependencies.iter().any(|dep| dep.name == "core") {
|
||||
let tt = if cg[krate].dependencies.iter().any(|dep| dep.name == "core") {
|
||||
quote! { core }
|
||||
} else {
|
||||
quote! { crate }
|
||||
|
|
@ -264,10 +253,12 @@ fn partial_ord_expand(
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{test_db::TestDB, AstId, MacroCallId, MacroCallKind, MacroCallLoc};
|
||||
use name::{known, Name};
|
||||
use ra_db::{fixture::WithFixture, SourceDatabase};
|
||||
use ra_db::{fixture::WithFixture, CrateId, SourceDatabase};
|
||||
|
||||
use crate::{test_db::TestDB, AstId, MacroCallId, MacroCallKind, MacroCallLoc};
|
||||
|
||||
use super::*;
|
||||
|
||||
fn expand_builtin_derive(s: &str, name: Name) -> String {
|
||||
let def = find_builtin_derive(&name).unwrap();
|
||||
|
|
@ -291,7 +282,11 @@ mod tests {
|
|||
|
||||
let attr_id = AstId::new(file_id.into(), ast_id_map.ast_id(&items[0]));
|
||||
|
||||
let loc = MacroCallLoc { def, kind: MacroCallKind::Attr(attr_id, name.to_string()) };
|
||||
let loc = MacroCallLoc {
|
||||
def,
|
||||
krate: CrateId(0),
|
||||
kind: MacroCallKind::Attr(attr_id, name.to_string()),
|
||||
};
|
||||
|
||||
let id: MacroCallId = db.intern_macro(loc).into();
|
||||
let parsed = db.parse_or_expand(id.as_file()).unwrap();
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
//! Builtin macro
|
||||
use crate::db::AstDatabase;
|
||||
use crate::{
|
||||
ast::{self, AstToken, HasStringValue},
|
||||
name, AstId, CrateId, MacroDefId, MacroDefKind, TextSize,
|
||||
db::AstDatabase, name, quote, AstId, CrateId, EagerMacroId, LazyMacroId, MacroCallId,
|
||||
MacroDefId, MacroDefKind, TextSize,
|
||||
};
|
||||
|
||||
use crate::{quote, EagerMacroId, LazyMacroId, MacroCallId};
|
||||
use either::Either;
|
||||
use mbe::parse_to_token_tree;
|
||||
use ra_db::{FileId, RelativePath};
|
||||
use ra_db::FileId;
|
||||
use ra_parser::FragmentKind;
|
||||
use ra_syntax::ast::{self, AstToken, HasStringValue};
|
||||
|
||||
macro_rules! register_builtin {
|
||||
( LAZY: $(($name:ident, $kind: ident) => $expand:ident),* , EAGER: $(($e_name:ident, $e_kind: ident) => $e_expand:ident),* ) => {
|
||||
|
|
@ -295,19 +294,13 @@ fn concat_expand(
|
|||
|
||||
fn relative_file(db: &dyn AstDatabase, call_id: MacroCallId, path: &str) -> Option<FileId> {
|
||||
let call_site = call_id.as_file().original_file(db);
|
||||
|
||||
// Handle trivial case
|
||||
if let Some(res) = db.resolve_relative_path(call_site, &RelativePath::new(&path)) {
|
||||
// Prevent include itself
|
||||
return if res == call_site { None } else { Some(res) };
|
||||
let res = db.resolve_path(call_site, path)?;
|
||||
// Prevent include itself
|
||||
if res == call_site {
|
||||
None
|
||||
} else {
|
||||
Some(res)
|
||||
}
|
||||
|
||||
// Extern paths ?
|
||||
let krate = *db.relevant_crates(call_site).get(0)?;
|
||||
let (extern_source_id, relative_file) =
|
||||
db.crate_graph()[krate].extern_source.extern_path(path)?;
|
||||
|
||||
db.resolve_extern_path(extern_source_id, &relative_file)
|
||||
}
|
||||
|
||||
fn parse_string(tt: &tt::Subtree) -> Result<String, mbe::ExpandError> {
|
||||
|
|
@ -339,10 +332,7 @@ fn include_expand(
|
|||
}
|
||||
|
||||
fn get_env_inner(db: &dyn AstDatabase, arg_id: EagerMacroId, key: &str) -> Option<String> {
|
||||
let call_id: MacroCallId = arg_id.into();
|
||||
let original_file = call_id.as_file().original_file(db);
|
||||
|
||||
let krate = *db.relevant_crates(original_file).get(0)?;
|
||||
let krate = db.lookup_intern_eager_expansion(arg_id).krate;
|
||||
db.crate_graph()[krate].env.get(key)
|
||||
}
|
||||
|
||||
|
|
@ -401,6 +391,7 @@ mod tests {
|
|||
|
||||
let expander = find_by_name(¯o_calls[0].name().unwrap().as_name()).unwrap();
|
||||
|
||||
let krate = CrateId(0);
|
||||
let file_id = match expander {
|
||||
Either::Left(expander) => {
|
||||
// the first one should be a macro_rules
|
||||
|
|
@ -413,6 +404,7 @@ mod tests {
|
|||
|
||||
let loc = MacroCallLoc {
|
||||
def,
|
||||
krate,
|
||||
kind: MacroCallKind::FnLike(AstId::new(
|
||||
file_id.into(),
|
||||
ast_id_map.ast_id(¯o_calls[1]),
|
||||
|
|
@ -425,7 +417,7 @@ mod tests {
|
|||
Either::Right(expander) => {
|
||||
// the first one should be a macro_rules
|
||||
let def = MacroDefId {
|
||||
krate: Some(CrateId(0)),
|
||||
krate: Some(krate),
|
||||
ast_id: Some(AstId::new(file_id.into(), ast_id_map.ast_id(¯o_calls[0]))),
|
||||
kind: MacroDefKind::BuiltInEager(expander),
|
||||
local_inner: false,
|
||||
|
|
@ -439,6 +431,7 @@ mod tests {
|
|||
def,
|
||||
fragment: FragmentKind::Expr,
|
||||
subtree: Arc::new(parsed_args.clone()),
|
||||
krate,
|
||||
file_id: file_id.into(),
|
||||
}
|
||||
});
|
||||
|
|
@ -448,6 +441,7 @@ mod tests {
|
|||
def,
|
||||
fragment,
|
||||
subtree: Arc::new(subtree),
|
||||
krate,
|
||||
file_id: file_id.into(),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -25,12 +25,14 @@ use crate::{
|
|||
EagerCallLoc, EagerMacroId, InFile, MacroCallId, MacroCallKind, MacroDefId, MacroDefKind,
|
||||
};
|
||||
|
||||
use ra_db::CrateId;
|
||||
use ra_parser::FragmentKind;
|
||||
use ra_syntax::{algo::SyntaxRewriter, SyntaxNode};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub fn expand_eager_macro(
|
||||
db: &dyn AstDatabase,
|
||||
krate: CrateId,
|
||||
macro_call: InFile<ast::MacroCall>,
|
||||
def: MacroDefId,
|
||||
resolver: &dyn Fn(ast::Path) -> Option<MacroDefId>,
|
||||
|
|
@ -47,6 +49,7 @@ pub fn expand_eager_macro(
|
|||
def,
|
||||
fragment: FragmentKind::Expr,
|
||||
subtree: Arc::new(parsed_args.clone()),
|
||||
krate,
|
||||
file_id: macro_call.file_id,
|
||||
}
|
||||
});
|
||||
|
|
@ -56,14 +59,20 @@ pub fn expand_eager_macro(
|
|||
let result = eager_macro_recur(
|
||||
db,
|
||||
InFile::new(arg_file_id.as_file(), parsed_args.syntax_node()),
|
||||
krate,
|
||||
resolver,
|
||||
)?;
|
||||
let subtree = to_subtree(&result)?;
|
||||
|
||||
if let MacroDefKind::BuiltInEager(eager) = def.kind {
|
||||
let (subtree, fragment) = eager.expand(db, arg_id, &subtree).ok()?;
|
||||
let eager =
|
||||
EagerCallLoc { def, fragment, subtree: Arc::new(subtree), file_id: macro_call.file_id };
|
||||
let eager = EagerCallLoc {
|
||||
def,
|
||||
fragment,
|
||||
subtree: Arc::new(subtree),
|
||||
krate,
|
||||
file_id: macro_call.file_id,
|
||||
};
|
||||
|
||||
Some(db.intern_eager_expansion(eager))
|
||||
} else {
|
||||
|
|
@ -81,11 +90,12 @@ fn lazy_expand(
|
|||
db: &dyn AstDatabase,
|
||||
def: &MacroDefId,
|
||||
macro_call: InFile<ast::MacroCall>,
|
||||
krate: CrateId,
|
||||
) -> Option<InFile<SyntaxNode>> {
|
||||
let ast_id = db.ast_id_map(macro_call.file_id).ast_id(¯o_call.value);
|
||||
|
||||
let id: MacroCallId =
|
||||
def.as_lazy_macro(db, MacroCallKind::FnLike(macro_call.with_value(ast_id))).into();
|
||||
def.as_lazy_macro(db, krate, MacroCallKind::FnLike(macro_call.with_value(ast_id))).into();
|
||||
|
||||
db.parse_or_expand(id.as_file()).map(|node| InFile::new(id.as_file(), node))
|
||||
}
|
||||
|
|
@ -93,6 +103,7 @@ fn lazy_expand(
|
|||
fn eager_macro_recur(
|
||||
db: &dyn AstDatabase,
|
||||
curr: InFile<SyntaxNode>,
|
||||
krate: CrateId,
|
||||
macro_resolver: &dyn Fn(ast::Path) -> Option<MacroDefId>,
|
||||
) -> Option<SyntaxNode> {
|
||||
let original = curr.value.clone();
|
||||
|
|
@ -105,18 +116,23 @@ fn eager_macro_recur(
|
|||
let def: MacroDefId = macro_resolver(child.path()?)?;
|
||||
let insert = match def.kind {
|
||||
MacroDefKind::BuiltInEager(_) => {
|
||||
let id: MacroCallId =
|
||||
expand_eager_macro(db, curr.with_value(child.clone()), def, macro_resolver)?
|
||||
.into();
|
||||
let id: MacroCallId = expand_eager_macro(
|
||||
db,
|
||||
krate,
|
||||
curr.with_value(child.clone()),
|
||||
def,
|
||||
macro_resolver,
|
||||
)?
|
||||
.into();
|
||||
db.parse_or_expand(id.as_file())?
|
||||
}
|
||||
MacroDefKind::Declarative
|
||||
| MacroDefKind::BuiltIn(_)
|
||||
| MacroDefKind::BuiltInDerive(_)
|
||||
| MacroDefKind::CustomDerive(_) => {
|
||||
let expanded = lazy_expand(db, &def, curr.with_value(child.clone()))?;
|
||||
let expanded = lazy_expand(db, &def, curr.with_value(child.clone()), krate)?;
|
||||
// replace macro inside
|
||||
eager_macro_recur(db, expanded, macro_resolver)?
|
||||
eager_macro_recur(db, expanded, krate, macro_resolver)?
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -209,8 +209,13 @@ pub struct MacroDefId {
|
|||
}
|
||||
|
||||
impl MacroDefId {
|
||||
pub fn as_lazy_macro(self, db: &dyn db::AstDatabase, kind: MacroCallKind) -> LazyMacroId {
|
||||
db.intern_macro(MacroCallLoc { def: self, kind })
|
||||
pub fn as_lazy_macro(
|
||||
self,
|
||||
db: &dyn db::AstDatabase,
|
||||
krate: CrateId,
|
||||
kind: MacroCallKind,
|
||||
) -> LazyMacroId {
|
||||
db.intern_macro(MacroCallLoc { def: self, krate, kind })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -227,6 +232,7 @@ pub enum MacroDefKind {
|
|||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct MacroCallLoc {
|
||||
pub(crate) def: MacroDefId,
|
||||
pub(crate) krate: CrateId,
|
||||
pub(crate) kind: MacroCallKind,
|
||||
}
|
||||
|
||||
|
|
@ -274,6 +280,7 @@ pub struct EagerCallLoc {
|
|||
pub(crate) def: MacroDefId,
|
||||
pub(crate) fragment: FragmentKind,
|
||||
pub(crate) subtree: Arc<tt::Subtree>,
|
||||
pub(crate) krate: CrateId,
|
||||
pub(crate) file_id: HirFileId,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ pub mod known {
|
|||
str,
|
||||
// Special names
|
||||
macro_rules,
|
||||
doc,
|
||||
// Components of known path (value or mod name)
|
||||
std,
|
||||
core,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ use std::{
|
|||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use ra_db::{salsa, CrateId, ExternSourceId, FileId, FileLoader, FileLoaderDelegate, RelativePath};
|
||||
use ra_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate};
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
#[salsa::database(
|
||||
ra_db::SourceDatabaseExtStorage,
|
||||
|
|
@ -41,21 +42,10 @@ impl FileLoader for TestDB {
|
|||
fn file_text(&self, file_id: FileId) -> Arc<String> {
|
||||
FileLoaderDelegate(self).file_text(file_id)
|
||||
}
|
||||
fn resolve_relative_path(
|
||||
&self,
|
||||
anchor: FileId,
|
||||
relative_path: &RelativePath,
|
||||
) -> Option<FileId> {
|
||||
FileLoaderDelegate(self).resolve_relative_path(anchor, relative_path)
|
||||
fn resolve_path(&self, anchor: FileId, path: &str) -> Option<FileId> {
|
||||
FileLoaderDelegate(self).resolve_path(anchor, path)
|
||||
}
|
||||
fn relevant_crates(&self, file_id: FileId) -> Arc<Vec<CrateId>> {
|
||||
fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>> {
|
||||
FileLoaderDelegate(self).relevant_crates(file_id)
|
||||
}
|
||||
fn resolve_extern_path(
|
||||
&self,
|
||||
anchor: ExternSourceId,
|
||||
relative_path: &RelativePath,
|
||||
) -> Option<FileId> {
|
||||
FileLoaderDelegate(self).resolve_extern_path(anchor, relative_path)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ test_utils = { path = "../test_utils" }
|
|||
|
||||
scoped-tls = "1"
|
||||
|
||||
chalk-solve = { git = "https://github.com/rust-lang/chalk.git", rev = "329b7f3fdd2431ed6f6778cde53f22374c7d094c" }
|
||||
chalk-ir = { git = "https://github.com/rust-lang/chalk.git", rev = "329b7f3fdd2431ed6f6778cde53f22374c7d094c" }
|
||||
chalk-solve = "0.11"
|
||||
chalk-ir = "0.11"
|
||||
|
||||
[dev-dependencies]
|
||||
insta = "0.16.0"
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use hir_def::{
|
||||
db::DefDatabase, DefWithBodyId, GenericDefId, ImplId, LocalFieldId, TraitId, TypeParamId,
|
||||
VariantId,
|
||||
db::DefDatabase, DefWithBodyId, FunctionId, GenericDefId, ImplId, LocalFieldId, TraitId,
|
||||
TypeParamId, VariantId,
|
||||
};
|
||||
use ra_arena::map::ArenaMap;
|
||||
use ra_db::{impl_intern_key, salsa, CrateId, Upcast};
|
||||
|
|
@ -13,8 +13,8 @@ use ra_prof::profile;
|
|||
use crate::{
|
||||
method_resolution::{CrateImplDefs, TyFingerprint},
|
||||
traits::{chalk, AssocTyValue, Impl},
|
||||
Binders, CallableDef, GenericPredicate, InferenceResult, PolyFnSig, Substs, TraitRef, Ty,
|
||||
TyDefId, TypeCtor, ValueTyDefId,
|
||||
Binders, CallableDef, GenericPredicate, InferenceResult, OpaqueTyId, PolyFnSig,
|
||||
ReturnTypeImplTraits, Substs, TraitRef, Ty, TyDefId, TypeCtor, ValueTyDefId,
|
||||
};
|
||||
use hir_expand::name::Name;
|
||||
|
||||
|
|
@ -48,6 +48,12 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
|
|||
#[salsa::invoke(crate::callable_item_sig)]
|
||||
fn callable_item_signature(&self, def: CallableDef) -> PolyFnSig;
|
||||
|
||||
#[salsa::invoke(crate::lower::return_type_impl_traits)]
|
||||
fn return_type_impl_traits(
|
||||
&self,
|
||||
def: FunctionId,
|
||||
) -> Option<Arc<Binders<ReturnTypeImplTraits>>>;
|
||||
|
||||
#[salsa::invoke(crate::lower::generic_predicates_for_param_query)]
|
||||
#[salsa::cycle(crate::lower::generic_predicates_for_param_recover)]
|
||||
fn generic_predicates_for_param(
|
||||
|
|
@ -80,6 +86,8 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
|
|||
#[salsa::interned]
|
||||
fn intern_type_param_id(&self, param_id: TypeParamId) -> GlobalTypeParamId;
|
||||
#[salsa::interned]
|
||||
fn intern_impl_trait_id(&self, id: OpaqueTyId) -> InternedOpaqueTyId;
|
||||
#[salsa::interned]
|
||||
fn intern_chalk_impl(&self, impl_: Impl) -> crate::traits::GlobalImplId;
|
||||
#[salsa::interned]
|
||||
fn intern_assoc_ty_value(&self, assoc_ty_value: AssocTyValue) -> crate::traits::AssocTyValueId;
|
||||
|
|
@ -142,3 +150,7 @@ fn hir_database_is_object_safe() {
|
|||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct GlobalTypeParamId(salsa::InternId);
|
||||
impl_intern_key!(GlobalTypeParamId);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct InternedOpaqueTyId(salsa::InternId);
|
||||
impl_intern_key!(InternedOpaqueTyId);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use std::fmt;
|
|||
|
||||
use crate::{
|
||||
db::HirDatabase, utils::generics, ApplicationTy, CallableDef, FnSig, GenericPredicate,
|
||||
Obligation, ProjectionTy, Substs, TraitRef, Ty, TypeCtor,
|
||||
Obligation, OpaqueTyId, ProjectionTy, Substs, TraitRef, Ty, TypeCtor,
|
||||
};
|
||||
use hir_def::{
|
||||
find_path, generics::TypeParamProvenance, item_scope::ItemInNs, AdtId, AssocContainerId,
|
||||
|
|
@ -359,6 +359,21 @@ impl HirDisplay for ApplicationTy {
|
|||
write!(f, ">")?;
|
||||
}
|
||||
}
|
||||
TypeCtor::OpaqueType(opaque_ty_id) => {
|
||||
let bounds = match opaque_ty_id {
|
||||
OpaqueTyId::ReturnTypeImplTrait(func, idx) => {
|
||||
let datas =
|
||||
f.db.return_type_impl_traits(func).expect("impl trait id without data");
|
||||
let data = (*datas)
|
||||
.as_ref()
|
||||
.map(|rpit| rpit.impl_traits[idx as usize].bounds.clone());
|
||||
data.clone().subst(&self.parameters)
|
||||
}
|
||||
};
|
||||
write!(f, "impl ")?;
|
||||
write_bounds_like_dyn_trait(&bounds.value, f)?;
|
||||
// FIXME: it would maybe be good to distinguish this from the alias type (when debug printing), and to show the substitution
|
||||
}
|
||||
TypeCtor::Closure { .. } => {
|
||||
let sig = self.parameters[0].callable_sig(f.db);
|
||||
if let Some(sig) = sig {
|
||||
|
|
@ -427,14 +442,24 @@ impl HirDisplay for Ty {
|
|||
}
|
||||
}
|
||||
Ty::Bound(idx) => write!(f, "?{}.{}", idx.debruijn.depth(), idx.index)?,
|
||||
Ty::Dyn(predicates) | Ty::Opaque(predicates) => {
|
||||
match self {
|
||||
Ty::Dyn(_) => write!(f, "dyn ")?,
|
||||
Ty::Opaque(_) => write!(f, "impl ")?,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
Ty::Dyn(predicates) => {
|
||||
write!(f, "dyn ")?;
|
||||
write_bounds_like_dyn_trait(predicates, f)?;
|
||||
}
|
||||
Ty::Opaque(opaque_ty) => {
|
||||
let bounds = match opaque_ty.opaque_ty_id {
|
||||
OpaqueTyId::ReturnTypeImplTrait(func, idx) => {
|
||||
let datas =
|
||||
f.db.return_type_impl_traits(func).expect("impl trait id without data");
|
||||
let data = (*datas)
|
||||
.as_ref()
|
||||
.map(|rpit| rpit.impl_traits[idx as usize].bounds.clone());
|
||||
data.clone().subst(&opaque_ty.parameters)
|
||||
}
|
||||
};
|
||||
write!(f, "impl ")?;
|
||||
write_bounds_like_dyn_trait(&bounds.value, f)?;
|
||||
}
|
||||
Ty::Unknown => write!(f, "{{unknown}}")?,
|
||||
Ty::Infer(..) => write!(f, "_")?,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -226,17 +226,19 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
|
|||
None => return,
|
||||
};
|
||||
|
||||
let std_result_path = path![std::result::Result];
|
||||
let core_result_path = path![core::result::Result];
|
||||
|
||||
let resolver = self.func.resolver(db.upcast());
|
||||
let std_result_enum = match resolver.resolve_known_enum(db.upcast(), &std_result_path) {
|
||||
let core_result_enum = match resolver.resolve_known_enum(db.upcast(), &core_result_path) {
|
||||
Some(it) => it,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let std_result_ctor = TypeCtor::Adt(AdtId::EnumId(std_result_enum));
|
||||
let core_result_ctor = TypeCtor::Adt(AdtId::EnumId(core_result_enum));
|
||||
let params = match &mismatch.expected {
|
||||
Ty::Apply(ApplicationTy { ctor, parameters }) if ctor == &std_result_ctor => parameters,
|
||||
Ty::Apply(ApplicationTy { ctor, parameters }) if ctor == &core_result_ctor => {
|
||||
parameters
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -39,8 +39,7 @@ use ra_syntax::SmolStr;
|
|||
use super::{
|
||||
primitive::{FloatTy, IntTy},
|
||||
traits::{Guidance, Obligation, ProjectionPredicate, Solution},
|
||||
ApplicationTy, GenericPredicate, InEnvironment, ProjectionTy, Substs, TraitEnvironment,
|
||||
TraitRef, Ty, TypeCtor, TypeWalk, Uncertain,
|
||||
InEnvironment, ProjectionTy, Substs, TraitEnvironment, TraitRef, Ty, TypeCtor, TypeWalk,
|
||||
};
|
||||
use crate::{
|
||||
db::HirDatabase, infer::diagnostics::InferenceDiagnostic, lower::ImplTraitLoweringMode,
|
||||
|
|
@ -312,12 +311,6 @@ impl<'a> InferenceContext<'a> {
|
|||
fn insert_type_vars_shallow(&mut self, ty: Ty) -> Ty {
|
||||
match ty {
|
||||
Ty::Unknown => self.table.new_type_var(),
|
||||
Ty::Apply(ApplicationTy { ctor: TypeCtor::Int(Uncertain::Unknown), .. }) => {
|
||||
self.table.new_integer_var()
|
||||
}
|
||||
Ty::Apply(ApplicationTy { ctor: TypeCtor::Float(Uncertain::Unknown), .. }) => {
|
||||
self.table.new_float_var()
|
||||
}
|
||||
_ => ty,
|
||||
}
|
||||
}
|
||||
|
|
@ -383,25 +376,6 @@ impl<'a> InferenceContext<'a> {
|
|||
) -> Ty {
|
||||
match assoc_ty {
|
||||
Some(res_assoc_ty) => {
|
||||
// FIXME:
|
||||
// Check if inner_ty is is `impl Trait` and contained input TypeAlias id
|
||||
// this is a workaround while Chalk assoc type projection doesn't always work yet,
|
||||
// but once that is fixed I don't think we should keep this
|
||||
// (we'll probably change how associated types are resolved anyway)
|
||||
if let Ty::Opaque(ref predicates) = inner_ty {
|
||||
for p in predicates.iter() {
|
||||
if let GenericPredicate::Projection(projection) = p {
|
||||
if projection.projection_ty.associated_ty == res_assoc_ty {
|
||||
if let ty_app!(_, params) = &projection.ty {
|
||||
if params.len() == 0 {
|
||||
return projection.ty.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ty = self.table.new_type_var();
|
||||
let builder = Substs::build_for_def(self.db, res_assoc_ty)
|
||||
.push(inner_ty)
|
||||
|
|
@ -458,13 +432,13 @@ impl<'a> InferenceContext<'a> {
|
|||
};
|
||||
return match resolution {
|
||||
TypeNs::AdtId(AdtId::StructId(strukt)) => {
|
||||
let substs = Ty::substs_from_path(&ctx, path, strukt.into());
|
||||
let substs = Ty::substs_from_path(&ctx, path, strukt.into(), true);
|
||||
let ty = self.db.ty(strukt.into());
|
||||
let ty = self.insert_type_vars(ty.subst(&substs));
|
||||
forbid_unresolved_segments((ty, Some(strukt.into())), unresolved)
|
||||
}
|
||||
TypeNs::EnumVariantId(var) => {
|
||||
let substs = Ty::substs_from_path(&ctx, path, var.into());
|
||||
let substs = Ty::substs_from_path(&ctx, path, var.into(), true);
|
||||
let ty = self.db.ty(var.parent.into());
|
||||
let ty = self.insert_type_vars(ty.subst(&substs));
|
||||
forbid_unresolved_segments((ty, Some(var.into())), unresolved)
|
||||
|
|
@ -581,13 +555,13 @@ impl<'a> InferenceContext<'a> {
|
|||
}
|
||||
|
||||
fn resolve_into_iter_item(&self) -> Option<TypeAliasId> {
|
||||
let path = path![std::iter::IntoIterator];
|
||||
let path = path![core::iter::IntoIterator];
|
||||
let trait_ = self.resolver.resolve_known_trait(self.db.upcast(), &path)?;
|
||||
self.db.trait_data(trait_).associated_type_by_name(&name![Item])
|
||||
}
|
||||
|
||||
fn resolve_ops_try_ok(&self) -> Option<TypeAliasId> {
|
||||
let path = path![std::ops::Try];
|
||||
let path = path![core::ops::Try];
|
||||
let trait_ = self.resolver.resolve_known_trait(self.db.upcast(), &path)?;
|
||||
self.db.trait_data(trait_).associated_type_by_name(&name![Ok])
|
||||
}
|
||||
|
|
@ -613,37 +587,37 @@ impl<'a> InferenceContext<'a> {
|
|||
}
|
||||
|
||||
fn resolve_range_full(&self) -> Option<AdtId> {
|
||||
let path = path![std::ops::RangeFull];
|
||||
let path = path![core::ops::RangeFull];
|
||||
let struct_ = self.resolver.resolve_known_struct(self.db.upcast(), &path)?;
|
||||
Some(struct_.into())
|
||||
}
|
||||
|
||||
fn resolve_range(&self) -> Option<AdtId> {
|
||||
let path = path![std::ops::Range];
|
||||
let path = path![core::ops::Range];
|
||||
let struct_ = self.resolver.resolve_known_struct(self.db.upcast(), &path)?;
|
||||
Some(struct_.into())
|
||||
}
|
||||
|
||||
fn resolve_range_inclusive(&self) -> Option<AdtId> {
|
||||
let path = path![std::ops::RangeInclusive];
|
||||
let path = path![core::ops::RangeInclusive];
|
||||
let struct_ = self.resolver.resolve_known_struct(self.db.upcast(), &path)?;
|
||||
Some(struct_.into())
|
||||
}
|
||||
|
||||
fn resolve_range_from(&self) -> Option<AdtId> {
|
||||
let path = path![std::ops::RangeFrom];
|
||||
let path = path![core::ops::RangeFrom];
|
||||
let struct_ = self.resolver.resolve_known_struct(self.db.upcast(), &path)?;
|
||||
Some(struct_.into())
|
||||
}
|
||||
|
||||
fn resolve_range_to(&self) -> Option<AdtId> {
|
||||
let path = path![std::ops::RangeTo];
|
||||
let path = path![core::ops::RangeTo];
|
||||
let struct_ = self.resolver.resolve_known_struct(self.db.upcast(), &path)?;
|
||||
Some(struct_.into())
|
||||
}
|
||||
|
||||
fn resolve_range_to_inclusive(&self) -> Option<AdtId> {
|
||||
let path = path![std::ops::RangeToInclusive];
|
||||
let path = path![core::ops::RangeToInclusive];
|
||||
let struct_ = self.resolver.resolve_known_struct(self.db.upcast(), &path)?;
|
||||
Some(struct_.into())
|
||||
}
|
||||
|
|
@ -683,8 +657,8 @@ impl InferTy {
|
|||
fn fallback_value(self) -> Ty {
|
||||
match self {
|
||||
InferTy::TypeVar(..) => Ty::Unknown,
|
||||
InferTy::IntVar(..) => Ty::simple(TypeCtor::Int(Uncertain::Known(IntTy::i32()))),
|
||||
InferTy::FloatVar(..) => Ty::simple(TypeCtor::Float(Uncertain::Known(FloatTy::f64()))),
|
||||
InferTy::IntVar(..) => Ty::simple(TypeCtor::Int(IntTy::i32())),
|
||||
InferTy::FloatVar(..) => Ty::simple(TypeCtor::Float(FloatTy::f64())),
|
||||
InferTy::MaybeNeverTypeVar(..) => Ty::simple(TypeCtor::Never),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ use crate::{
|
|||
traits::InEnvironment,
|
||||
utils::{generics, variant_data, Generics},
|
||||
ApplicationTy, Binders, CallableDef, InferTy, IntTy, Mutability, Obligation, Rawness, Substs,
|
||||
TraitRef, Ty, TypeCtor, Uncertain,
|
||||
TraitRef, Ty, TypeCtor,
|
||||
};
|
||||
|
||||
use super::{
|
||||
|
|
@ -426,15 +426,7 @@ impl<'a> InferenceContext<'a> {
|
|||
match &inner_ty {
|
||||
// Fast path for builtins
|
||||
Ty::Apply(ApplicationTy {
|
||||
ctor:
|
||||
TypeCtor::Int(Uncertain::Known(IntTy {
|
||||
signedness: Signedness::Signed,
|
||||
..
|
||||
})),
|
||||
..
|
||||
})
|
||||
| Ty::Apply(ApplicationTy {
|
||||
ctor: TypeCtor::Int(Uncertain::Unknown),
|
||||
ctor: TypeCtor::Int(IntTy { signedness: Signedness::Signed, .. }),
|
||||
..
|
||||
})
|
||||
| Ty::Apply(ApplicationTy { ctor: TypeCtor::Float(_), .. })
|
||||
|
|
@ -577,9 +569,7 @@ impl<'a> InferenceContext<'a> {
|
|||
);
|
||||
self.infer_expr(
|
||||
*repeat,
|
||||
&Expectation::has_type(Ty::simple(TypeCtor::Int(Uncertain::Known(
|
||||
IntTy::usize(),
|
||||
)))),
|
||||
&Expectation::has_type(Ty::simple(TypeCtor::Int(IntTy::usize()))),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -592,13 +582,19 @@ impl<'a> InferenceContext<'a> {
|
|||
Ty::apply_one(TypeCtor::Ref(Mutability::Shared), Ty::simple(TypeCtor::Str))
|
||||
}
|
||||
Literal::ByteString(..) => {
|
||||
let byte_type = Ty::simple(TypeCtor::Int(Uncertain::Known(IntTy::u8())));
|
||||
let byte_type = Ty::simple(TypeCtor::Int(IntTy::u8()));
|
||||
let array_type = Ty::apply_one(TypeCtor::Array, byte_type);
|
||||
Ty::apply_one(TypeCtor::Ref(Mutability::Shared), array_type)
|
||||
}
|
||||
Literal::Char(..) => Ty::simple(TypeCtor::Char),
|
||||
Literal::Int(_v, ty) => Ty::simple(TypeCtor::Int((*ty).into())),
|
||||
Literal::Float(_v, ty) => Ty::simple(TypeCtor::Float((*ty).into())),
|
||||
Literal::Int(_v, ty) => match ty {
|
||||
Some(int_ty) => Ty::simple(TypeCtor::Int((*int_ty).into())),
|
||||
None => self.table.new_integer_var(),
|
||||
},
|
||||
Literal::Float(_v, ty) => match ty {
|
||||
Some(float_ty) => Ty::simple(TypeCtor::Float((*float_ty).into())),
|
||||
None => self.table.new_float_var(),
|
||||
},
|
||||
},
|
||||
};
|
||||
// use a new type variable if we got Ty::Unknown here
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ impl<'a> InferenceContext<'a> {
|
|||
// self_subst is just for the parent
|
||||
let parent_substs = self_subst.unwrap_or_else(Substs::empty);
|
||||
let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver);
|
||||
let substs = Ty::substs_from_path(&ctx, path, typable);
|
||||
let substs = Ty::substs_from_path(&ctx, path, typable, true);
|
||||
let full_substs = Substs::builder(substs.len())
|
||||
.use_parent_substs(&parent_substs)
|
||||
.fill(substs.0[parent_substs.len()..].iter().cloned())
|
||||
|
|
@ -141,6 +141,7 @@ impl<'a> InferenceContext<'a> {
|
|||
def,
|
||||
resolved_segment,
|
||||
remaining_segments_for_ty,
|
||||
true,
|
||||
);
|
||||
if let Ty::Unknown = ty {
|
||||
return None;
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ use ra_db::{impl_intern_key, salsa, CrateId};
|
|||
|
||||
use crate::{
|
||||
db::HirDatabase,
|
||||
primitive::{FloatTy, IntTy, Uncertain},
|
||||
primitive::{FloatTy, IntTy},
|
||||
utils::{generics, make_mut_slice, Generics},
|
||||
};
|
||||
use display::HirDisplay;
|
||||
|
|
@ -87,10 +87,10 @@ pub enum TypeCtor {
|
|||
Char,
|
||||
|
||||
/// A primitive integer type. For example, `i32`.
|
||||
Int(Uncertain<IntTy>),
|
||||
Int(IntTy),
|
||||
|
||||
/// A primitive floating-point type. For example, `f64`.
|
||||
Float(Uncertain<FloatTy>),
|
||||
Float(FloatTy),
|
||||
|
||||
/// Structures, enumerations and unions.
|
||||
Adt(AdtId),
|
||||
|
|
@ -147,6 +147,12 @@ pub enum TypeCtor {
|
|||
/// an **application type** like `(Iterator::Item)<T>`.
|
||||
AssociatedType(TypeAliasId),
|
||||
|
||||
/// This represents a placeholder for an opaque type in situations where we
|
||||
/// don't know the hidden type (i.e. currently almost always). This is
|
||||
/// analogous to the `AssociatedType` type constructor. As with that one,
|
||||
/// these are only produced by Chalk.
|
||||
OpaqueType(OpaqueTyId),
|
||||
|
||||
/// The type of a specific closure.
|
||||
///
|
||||
/// The closure signature is stored in a `FnPtr` type in the first type
|
||||
|
|
@ -194,6 +200,14 @@ impl TypeCtor {
|
|||
let generic_params = generics(db.upcast(), type_alias.into());
|
||||
generic_params.len()
|
||||
}
|
||||
TypeCtor::OpaqueType(opaque_ty_id) => {
|
||||
match opaque_ty_id {
|
||||
OpaqueTyId::ReturnTypeImplTrait(func, _) => {
|
||||
let generic_params = generics(db.upcast(), func.into());
|
||||
generic_params.len()
|
||||
}
|
||||
}
|
||||
}
|
||||
TypeCtor::FnPtr { num_args } => num_args as usize + 1,
|
||||
TypeCtor::Tuple { cardinality } => cardinality as usize,
|
||||
}
|
||||
|
|
@ -220,6 +234,11 @@ impl TypeCtor {
|
|||
TypeCtor::AssociatedType(type_alias) => {
|
||||
Some(type_alias.lookup(db.upcast()).module(db.upcast()).krate)
|
||||
}
|
||||
TypeCtor::OpaqueType(opaque_ty_id) => match opaque_ty_id {
|
||||
OpaqueTyId::ReturnTypeImplTrait(func, _) => {
|
||||
Some(func.lookup(db.upcast()).module(db.upcast()).krate)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -241,6 +260,7 @@ impl TypeCtor {
|
|||
TypeCtor::Adt(adt) => Some(adt.into()),
|
||||
TypeCtor::FnDef(callable) => Some(callable.into()),
|
||||
TypeCtor::AssociatedType(type_alias) => Some(type_alias.into()),
|
||||
TypeCtor::OpaqueType(_impl_trait_id) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -254,6 +274,12 @@ pub struct ApplicationTy {
|
|||
pub parameters: Substs,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub struct OpaqueTy {
|
||||
pub opaque_ty_id: OpaqueTyId,
|
||||
pub parameters: Substs,
|
||||
}
|
||||
|
||||
/// A "projection" type corresponds to an (unnormalized)
|
||||
/// projection like `<P0 as Trait<P1..Pn>>::Foo`. Note that the
|
||||
/// trait and all its parameters are fully known.
|
||||
|
|
@ -308,6 +334,12 @@ pub enum Ty {
|
|||
/// trait and all its parameters are fully known.
|
||||
Projection(ProjectionTy),
|
||||
|
||||
/// An opaque type (`impl Trait`).
|
||||
///
|
||||
/// This is currently only used for return type impl trait; each instance of
|
||||
/// `impl Trait` in a return type gets its own ID.
|
||||
Opaque(OpaqueTy),
|
||||
|
||||
/// A placeholder for a type parameter; for example, `T` in `fn f<T>(x: T)
|
||||
/// {}` when we're type-checking the body of that function. In this
|
||||
/// situation, we know this stands for *some* type, but don't know the exact
|
||||
|
|
@ -332,12 +364,6 @@ pub enum Ty {
|
|||
/// didn't seem worth the overhead yet.
|
||||
Dyn(Arc<[GenericPredicate]>),
|
||||
|
||||
/// An opaque type (`impl Trait`).
|
||||
///
|
||||
/// The predicates are quantified over the `Self` type; see `Ty::Dyn` for
|
||||
/// more.
|
||||
Opaque(Arc<[GenericPredicate]>),
|
||||
|
||||
/// A placeholder for a type which could not be computed; this is propagated
|
||||
/// to avoid useless error messages. Doubles as a placeholder where type
|
||||
/// variables are inserted before type checking, since we want to try to
|
||||
|
|
@ -490,7 +516,7 @@ impl Deref for Substs {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub struct Binders<T> {
|
||||
pub num_binders: usize,
|
||||
pub value: T,
|
||||
|
|
@ -534,6 +560,20 @@ impl<T: TypeWalk> Binders<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: TypeWalk> TypeWalk for Binders<T> {
|
||||
fn walk(&self, f: &mut impl FnMut(&Ty)) {
|
||||
self.value.walk(f);
|
||||
}
|
||||
|
||||
fn walk_mut_binders(
|
||||
&mut self,
|
||||
f: &mut impl FnMut(&mut Ty, DebruijnIndex),
|
||||
binders: DebruijnIndex,
|
||||
) {
|
||||
self.value.walk_mut_binders(f, binders.shifted_in())
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait with type parameters. This includes the `Self`, so this represents a concrete type implementing the trait.
|
||||
/// Name to be bikeshedded: TraitBound? TraitImplements?
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
|
||||
|
|
@ -947,11 +987,16 @@ impl TypeWalk for Ty {
|
|||
t.walk(f);
|
||||
}
|
||||
}
|
||||
Ty::Dyn(predicates) | Ty::Opaque(predicates) => {
|
||||
Ty::Dyn(predicates) => {
|
||||
for p in predicates.iter() {
|
||||
p.walk(f);
|
||||
}
|
||||
}
|
||||
Ty::Opaque(o_ty) => {
|
||||
for t in o_ty.parameters.iter() {
|
||||
t.walk(f);
|
||||
}
|
||||
}
|
||||
Ty::Placeholder { .. } | Ty::Bound(_) | Ty::Infer(_) | Ty::Unknown => {}
|
||||
}
|
||||
f(self);
|
||||
|
|
@ -969,13 +1014,48 @@ impl TypeWalk for Ty {
|
|||
Ty::Projection(p_ty) => {
|
||||
p_ty.parameters.walk_mut_binders(f, binders);
|
||||
}
|
||||
Ty::Dyn(predicates) | Ty::Opaque(predicates) => {
|
||||
Ty::Dyn(predicates) => {
|
||||
for p in make_mut_slice(predicates) {
|
||||
p.walk_mut_binders(f, binders.shifted_in());
|
||||
}
|
||||
}
|
||||
Ty::Opaque(o_ty) => {
|
||||
o_ty.parameters.walk_mut_binders(f, binders);
|
||||
}
|
||||
Ty::Placeholder { .. } | Ty::Bound(_) | Ty::Infer(_) | Ty::Unknown => {}
|
||||
}
|
||||
f(self, binders);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TypeWalk> TypeWalk for Vec<T> {
|
||||
fn walk(&self, f: &mut impl FnMut(&Ty)) {
|
||||
for t in self {
|
||||
t.walk(f);
|
||||
}
|
||||
}
|
||||
fn walk_mut_binders(
|
||||
&mut self,
|
||||
f: &mut impl FnMut(&mut Ty, DebruijnIndex),
|
||||
binders: DebruijnIndex,
|
||||
) {
|
||||
for t in self {
|
||||
t.walk_mut_binders(f, binders);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub enum OpaqueTyId {
|
||||
ReturnTypeImplTrait(hir_def::FunctionId, u16),
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub struct ReturnTypeImplTraits {
|
||||
pub(crate) impl_traits: Vec<ReturnTypeImplTrait>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub(crate) struct ReturnTypeImplTrait {
|
||||
pub(crate) bounds: Binders<Vec<GenericPredicate>>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,10 @@ use hir_def::{
|
|||
HasModule, ImplId, LocalFieldId, Lookup, StaticId, StructId, TraitId, TypeAliasId, TypeParamId,
|
||||
UnionId, VariantId,
|
||||
};
|
||||
use hir_expand::name::Name;
|
||||
use ra_arena::map::ArenaMap;
|
||||
use ra_db::CrateId;
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{
|
||||
db::HirDatabase,
|
||||
|
|
@ -31,10 +33,10 @@ use crate::{
|
|||
all_super_trait_refs, associated_type_by_name_including_super_traits, generics,
|
||||
make_mut_slice, variant_data,
|
||||
},
|
||||
Binders, BoundVar, DebruijnIndex, FnSig, GenericPredicate, PolyFnSig, ProjectionPredicate,
|
||||
ProjectionTy, Substs, TraitEnvironment, TraitRef, Ty, TypeCtor, TypeWalk,
|
||||
Binders, BoundVar, DebruijnIndex, FnSig, GenericPredicate, OpaqueTy, OpaqueTyId, PolyFnSig,
|
||||
ProjectionPredicate, ProjectionTy, ReturnTypeImplTrait, ReturnTypeImplTraits, Substs,
|
||||
TraitEnvironment, TraitRef, Ty, TypeCtor, TypeWalk,
|
||||
};
|
||||
use hir_expand::name::Name;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TyLoweringContext<'a> {
|
||||
|
|
@ -47,7 +49,16 @@ pub struct TyLoweringContext<'a> {
|
|||
/// possible currently, so this should be fine for now.
|
||||
pub type_param_mode: TypeParamLoweringMode,
|
||||
pub impl_trait_mode: ImplTraitLoweringMode,
|
||||
pub impl_trait_counter: std::cell::Cell<u16>,
|
||||
impl_trait_counter: std::cell::Cell<u16>,
|
||||
/// When turning `impl Trait` into opaque types, we have to collect the
|
||||
/// bounds at the same time to get the IDs correct (without becoming too
|
||||
/// complicated). I don't like using interior mutability (as for the
|
||||
/// counter), but I've tried and failed to make the lifetimes work for
|
||||
/// passing around a `&mut TyLoweringContext`. The core problem is that
|
||||
/// we're grouping the mutable data (the counter and this field) together
|
||||
/// with the immutable context (the references to the DB and resolver).
|
||||
/// Splitting this up would be a possible fix.
|
||||
opaque_type_data: std::cell::RefCell<Vec<ReturnTypeImplTrait>>,
|
||||
}
|
||||
|
||||
impl<'a> TyLoweringContext<'a> {
|
||||
|
|
@ -56,7 +67,34 @@ impl<'a> TyLoweringContext<'a> {
|
|||
let impl_trait_mode = ImplTraitLoweringMode::Disallowed;
|
||||
let type_param_mode = TypeParamLoweringMode::Placeholder;
|
||||
let in_binders = DebruijnIndex::INNERMOST;
|
||||
Self { db, resolver, in_binders, impl_trait_mode, impl_trait_counter, type_param_mode }
|
||||
let opaque_type_data = std::cell::RefCell::new(Vec::new());
|
||||
Self {
|
||||
db,
|
||||
resolver,
|
||||
in_binders,
|
||||
impl_trait_mode,
|
||||
impl_trait_counter,
|
||||
type_param_mode,
|
||||
opaque_type_data,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_debruijn<T>(
|
||||
&self,
|
||||
debruijn: DebruijnIndex,
|
||||
f: impl FnOnce(&TyLoweringContext) -> T,
|
||||
) -> T {
|
||||
let opaque_ty_data_vec = self.opaque_type_data.replace(Vec::new());
|
||||
let new_ctx = Self {
|
||||
in_binders: debruijn,
|
||||
impl_trait_counter: std::cell::Cell::new(self.impl_trait_counter.get()),
|
||||
opaque_type_data: std::cell::RefCell::new(opaque_ty_data_vec),
|
||||
..*self
|
||||
};
|
||||
let result = f(&new_ctx);
|
||||
self.impl_trait_counter.set(new_ctx.impl_trait_counter.get());
|
||||
self.opaque_type_data.replace(new_ctx.opaque_type_data.into_inner());
|
||||
result
|
||||
}
|
||||
|
||||
pub fn with_shifted_in<T>(
|
||||
|
|
@ -64,18 +102,7 @@ impl<'a> TyLoweringContext<'a> {
|
|||
debruijn: DebruijnIndex,
|
||||
f: impl FnOnce(&TyLoweringContext) -> T,
|
||||
) -> T {
|
||||
let new_ctx = Self {
|
||||
in_binders: self.in_binders.shifted_in_from(debruijn),
|
||||
impl_trait_counter: std::cell::Cell::new(self.impl_trait_counter.get()),
|
||||
..*self
|
||||
};
|
||||
let result = f(&new_ctx);
|
||||
self.impl_trait_counter.set(new_ctx.impl_trait_counter.get());
|
||||
result
|
||||
}
|
||||
|
||||
pub fn shifted_in(self, debruijn: DebruijnIndex) -> Self {
|
||||
Self { in_binders: self.in_binders.shifted_in_from(debruijn), ..self }
|
||||
self.with_debruijn(self.in_binders.shifted_in_from(debruijn), f)
|
||||
}
|
||||
|
||||
pub fn with_impl_trait_mode(self, impl_trait_mode: ImplTraitLoweringMode) -> Self {
|
||||
|
|
@ -167,20 +194,44 @@ impl Ty {
|
|||
TypeRef::ImplTrait(bounds) => {
|
||||
match ctx.impl_trait_mode {
|
||||
ImplTraitLoweringMode::Opaque => {
|
||||
let self_ty = Ty::Bound(BoundVar::new(DebruijnIndex::INNERMOST, 0));
|
||||
let predicates = ctx.with_shifted_in(DebruijnIndex::ONE, |ctx| {
|
||||
bounds
|
||||
.iter()
|
||||
.flat_map(|b| {
|
||||
GenericPredicate::from_type_bound(ctx, b, self_ty.clone())
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
Ty::Opaque(predicates)
|
||||
let idx = ctx.impl_trait_counter.get();
|
||||
ctx.impl_trait_counter.set(idx + 1);
|
||||
|
||||
assert!(idx as usize == ctx.opaque_type_data.borrow().len());
|
||||
// this dance is to make sure the data is in the right
|
||||
// place even if we encounter more opaque types while
|
||||
// lowering the bounds
|
||||
ctx.opaque_type_data
|
||||
.borrow_mut()
|
||||
.push(ReturnTypeImplTrait { bounds: Binders::new(1, Vec::new()) });
|
||||
// We don't want to lower the bounds inside the binders
|
||||
// we're currently in, because they don't end up inside
|
||||
// those binders. E.g. when we have `impl Trait<impl
|
||||
// OtherTrait<T>>`, the `impl OtherTrait<T>` can't refer
|
||||
// to the self parameter from `impl Trait`, and the
|
||||
// bounds aren't actually stored nested within each
|
||||
// other, but separately. So if the `T` refers to a type
|
||||
// parameter of the outer function, it's just one binder
|
||||
// away instead of two.
|
||||
let actual_opaque_type_data = ctx
|
||||
.with_debruijn(DebruijnIndex::INNERMOST, |ctx| {
|
||||
ReturnTypeImplTrait::from_hir(ctx, &bounds)
|
||||
});
|
||||
ctx.opaque_type_data.borrow_mut()[idx as usize] = actual_opaque_type_data;
|
||||
|
||||
let func = match ctx.resolver.generic_def() {
|
||||
Some(GenericDefId::FunctionId(f)) => f,
|
||||
_ => panic!("opaque impl trait lowering in non-function"),
|
||||
};
|
||||
let impl_trait_id = OpaqueTyId::ReturnTypeImplTrait(func, idx);
|
||||
let generics = generics(ctx.db.upcast(), func.into());
|
||||
let parameters = Substs::bound_vars(&generics, ctx.in_binders);
|
||||
Ty::Opaque(OpaqueTy { opaque_ty_id: impl_trait_id, parameters })
|
||||
}
|
||||
ImplTraitLoweringMode::Param => {
|
||||
let idx = ctx.impl_trait_counter.get();
|
||||
ctx.impl_trait_counter.set(idx + 1);
|
||||
// FIXME we're probably doing something wrong here
|
||||
ctx.impl_trait_counter.set(idx + count_impl_traits(type_ref) as u16);
|
||||
if let Some(def) = ctx.resolver.generic_def() {
|
||||
let generics = generics(ctx.db.upcast(), def);
|
||||
let param = generics
|
||||
|
|
@ -197,7 +248,8 @@ impl Ty {
|
|||
}
|
||||
ImplTraitLoweringMode::Variable => {
|
||||
let idx = ctx.impl_trait_counter.get();
|
||||
ctx.impl_trait_counter.set(idx + 1);
|
||||
// FIXME we're probably doing something wrong here
|
||||
ctx.impl_trait_counter.set(idx + count_impl_traits(type_ref) as u16);
|
||||
let (parent_params, self_params, list_params, _impl_trait_params) =
|
||||
if let Some(def) = ctx.resolver.generic_def() {
|
||||
let generics = generics(ctx.db.upcast(), def);
|
||||
|
|
@ -271,6 +323,7 @@ impl Ty {
|
|||
resolution: TypeNs,
|
||||
resolved_segment: PathSegment<'_>,
|
||||
remaining_segments: PathSegments<'_>,
|
||||
infer_args: bool,
|
||||
) -> (Ty, Option<TypeNs>) {
|
||||
let ty = match resolution {
|
||||
TypeNs::TraitId(trait_) => {
|
||||
|
|
@ -348,9 +401,15 @@ impl Ty {
|
|||
ctx.db.ty(adt.into()).subst(&substs)
|
||||
}
|
||||
|
||||
TypeNs::AdtId(it) => Ty::from_hir_path_inner(ctx, resolved_segment, it.into()),
|
||||
TypeNs::BuiltinType(it) => Ty::from_hir_path_inner(ctx, resolved_segment, it.into()),
|
||||
TypeNs::TypeAliasId(it) => Ty::from_hir_path_inner(ctx, resolved_segment, it.into()),
|
||||
TypeNs::AdtId(it) => {
|
||||
Ty::from_hir_path_inner(ctx, resolved_segment, it.into(), infer_args)
|
||||
}
|
||||
TypeNs::BuiltinType(it) => {
|
||||
Ty::from_hir_path_inner(ctx, resolved_segment, it.into(), infer_args)
|
||||
}
|
||||
TypeNs::TypeAliasId(it) => {
|
||||
Ty::from_hir_path_inner(ctx, resolved_segment, it.into(), infer_args)
|
||||
}
|
||||
// FIXME: report error
|
||||
TypeNs::EnumVariantId(_) => return (Ty::Unknown, None),
|
||||
};
|
||||
|
|
@ -376,7 +435,13 @@ impl Ty {
|
|||
),
|
||||
Some(i) => (path.segments().get(i - 1).unwrap(), path.segments().skip(i)),
|
||||
};
|
||||
Ty::from_partly_resolved_hir_path(ctx, resolution, resolved_segment, remaining_segments)
|
||||
Ty::from_partly_resolved_hir_path(
|
||||
ctx,
|
||||
resolution,
|
||||
resolved_segment,
|
||||
remaining_segments,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
fn select_associated_type(
|
||||
|
|
@ -422,13 +487,14 @@ impl Ty {
|
|||
ctx: &TyLoweringContext<'_>,
|
||||
segment: PathSegment<'_>,
|
||||
typable: TyDefId,
|
||||
infer_args: bool,
|
||||
) -> Ty {
|
||||
let generic_def = match typable {
|
||||
TyDefId::BuiltinType(_) => None,
|
||||
TyDefId::AdtId(it) => Some(it.into()),
|
||||
TyDefId::TypeAliasId(it) => Some(it.into()),
|
||||
};
|
||||
let substs = substs_from_path_segment(ctx, segment, generic_def, false);
|
||||
let substs = substs_from_path_segment(ctx, segment, generic_def, infer_args);
|
||||
ctx.db.ty(typable).subst(&substs)
|
||||
}
|
||||
|
||||
|
|
@ -441,6 +507,7 @@ impl Ty {
|
|||
// `ValueTyDefId` is just a convenient way to pass generics and
|
||||
// special-case enum variants
|
||||
resolved: ValueTyDefId,
|
||||
infer_args: bool,
|
||||
) -> Substs {
|
||||
let last = path.segments().last().expect("path should have at least one segment");
|
||||
let (segment, generic_def) = match resolved {
|
||||
|
|
@ -463,22 +530,27 @@ impl Ty {
|
|||
(segment, Some(var.parent.into()))
|
||||
}
|
||||
};
|
||||
substs_from_path_segment(ctx, segment, generic_def, false)
|
||||
substs_from_path_segment(ctx, segment, generic_def, infer_args)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn substs_from_path_segment(
|
||||
fn substs_from_path_segment(
|
||||
ctx: &TyLoweringContext<'_>,
|
||||
segment: PathSegment<'_>,
|
||||
def_generic: Option<GenericDefId>,
|
||||
_add_self_param: bool,
|
||||
infer_args: bool,
|
||||
) -> Substs {
|
||||
let mut substs = Vec::new();
|
||||
let def_generics = def_generic.map(|def| generics(ctx.db.upcast(), def));
|
||||
|
||||
let (parent_params, self_params, type_params, impl_trait_params) =
|
||||
def_generics.map_or((0, 0, 0, 0), |g| g.provenance_split());
|
||||
let total_len = parent_params + self_params + type_params + impl_trait_params;
|
||||
|
||||
substs.extend(iter::repeat(Ty::Unknown).take(parent_params));
|
||||
|
||||
let mut had_explicit_args = false;
|
||||
|
||||
if let Some(generic_args) = &segment.args_and_bindings {
|
||||
if !generic_args.has_self_type {
|
||||
substs.extend(iter::repeat(Ty::Unknown).take(self_params));
|
||||
|
|
@ -490,31 +562,35 @@ pub(super) fn substs_from_path_segment(
|
|||
for arg in generic_args.args.iter().skip(skip).take(expected_num) {
|
||||
match arg {
|
||||
GenericArg::Type(type_ref) => {
|
||||
had_explicit_args = true;
|
||||
let ty = Ty::from_hir(ctx, type_ref);
|
||||
substs.push(ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let total_len = parent_params + self_params + type_params + impl_trait_params;
|
||||
|
||||
// handle defaults. In expression or pattern path segments without
|
||||
// explicitly specified type arguments, missing type arguments are inferred
|
||||
// (i.e. defaults aren't used).
|
||||
if !infer_args || had_explicit_args {
|
||||
if let Some(def_generic) = def_generic {
|
||||
let default_substs = ctx.db.generic_defaults(def_generic);
|
||||
assert_eq!(total_len, default_substs.len());
|
||||
|
||||
for default_ty in default_substs.iter().skip(substs.len()) {
|
||||
substs.push(default_ty.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add placeholders for args that were not provided
|
||||
// FIXME: emit diagnostics in contexts where this is not allowed
|
||||
for _ in substs.len()..total_len {
|
||||
substs.push(Ty::Unknown);
|
||||
}
|
||||
assert_eq!(substs.len(), total_len);
|
||||
|
||||
// handle defaults
|
||||
if let Some(def_generic) = def_generic {
|
||||
let default_substs = ctx.db.generic_defaults(def_generic);
|
||||
assert_eq!(substs.len(), default_substs.len());
|
||||
|
||||
for (i, default_ty) in default_substs.iter().enumerate() {
|
||||
if substs[i] == Ty::Unknown {
|
||||
substs[i] = default_ty.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Substs(substs.into())
|
||||
}
|
||||
|
||||
|
|
@ -563,9 +639,7 @@ impl TraitRef {
|
|||
segment: PathSegment<'_>,
|
||||
resolved: TraitId,
|
||||
) -> Substs {
|
||||
let has_self_param =
|
||||
segment.args_and_bindings.as_ref().map(|a| a.has_self_type).unwrap_or(false);
|
||||
substs_from_path_segment(ctx, segment, Some(resolved.into()), !has_self_param)
|
||||
substs_from_path_segment(ctx, segment, Some(resolved.into()), false)
|
||||
}
|
||||
|
||||
pub(crate) fn from_type_bound(
|
||||
|
|
@ -663,6 +737,30 @@ fn assoc_type_bindings_from_type_bound<'a>(
|
|||
})
|
||||
}
|
||||
|
||||
impl ReturnTypeImplTrait {
|
||||
fn from_hir(ctx: &TyLoweringContext, bounds: &[TypeBound]) -> Self {
|
||||
mark::hit!(lower_rpit);
|
||||
let self_ty = Ty::Bound(BoundVar::new(DebruijnIndex::INNERMOST, 0));
|
||||
let predicates = ctx.with_shifted_in(DebruijnIndex::ONE, |ctx| {
|
||||
bounds
|
||||
.iter()
|
||||
.flat_map(|b| GenericPredicate::from_type_bound(ctx, b, self_ty.clone()))
|
||||
.collect()
|
||||
});
|
||||
ReturnTypeImplTrait { bounds: Binders::new(1, predicates) }
|
||||
}
|
||||
}
|
||||
|
||||
fn count_impl_traits(type_ref: &TypeRef) -> usize {
|
||||
let mut count = 0;
|
||||
type_ref.walk(&mut |type_ref| {
|
||||
if matches!(type_ref, TypeRef::ImplTrait(_)) {
|
||||
count += 1;
|
||||
}
|
||||
});
|
||||
count
|
||||
}
|
||||
|
||||
/// Build the signature of a callable item (function, struct or enum variant).
|
||||
pub fn callable_item_sig(db: &dyn HirDatabase, def: CallableDef) -> PolyFnSig {
|
||||
match def {
|
||||
|
|
@ -864,7 +962,9 @@ fn fn_sig_for_fn(db: &dyn HirDatabase, def: FunctionId) -> PolyFnSig {
|
|||
.with_impl_trait_mode(ImplTraitLoweringMode::Variable)
|
||||
.with_type_param_mode(TypeParamLoweringMode::Variable);
|
||||
let params = data.params.iter().map(|tr| Ty::from_hir(&ctx_params, tr)).collect::<Vec<_>>();
|
||||
let ctx_ret = ctx_params.with_impl_trait_mode(ImplTraitLoweringMode::Opaque);
|
||||
let ctx_ret = TyLoweringContext::new(db, &resolver)
|
||||
.with_impl_trait_mode(ImplTraitLoweringMode::Opaque)
|
||||
.with_type_param_mode(TypeParamLoweringMode::Variable);
|
||||
let ret = Ty::from_hir(&ctx_ret, &data.ret_type);
|
||||
let generics = generics(db.upcast(), def.into());
|
||||
let num_binders = generics.len();
|
||||
|
|
@ -1084,3 +1184,25 @@ pub(crate) fn impl_trait_query(db: &dyn HirDatabase, impl_id: ImplId) -> Option<
|
|||
TraitRef::from_hir(&ctx, target_trait, Some(self_ty.value))?,
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn return_type_impl_traits(
|
||||
db: &impl HirDatabase,
|
||||
def: hir_def::FunctionId,
|
||||
) -> Option<Arc<Binders<ReturnTypeImplTraits>>> {
|
||||
// FIXME unify with fn_sig_for_fn instead of doing lowering twice, maybe
|
||||
let data = db.function_data(def);
|
||||
let resolver = def.resolver(db.upcast());
|
||||
let ctx_ret = TyLoweringContext::new(db, &resolver)
|
||||
.with_impl_trait_mode(ImplTraitLoweringMode::Opaque)
|
||||
.with_type_param_mode(TypeParamLoweringMode::Variable);
|
||||
let _ret = Ty::from_hir(&ctx_ret, &data.ret_type);
|
||||
let generics = generics(db.upcast(), def.into());
|
||||
let num_binders = generics.len();
|
||||
let return_type_impl_traits =
|
||||
ReturnTypeImplTraits { impl_traits: ctx_ret.opaque_type_data.into_inner() };
|
||||
if return_type_impl_traits.impl_traits.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Arc::new(Binders::new(num_binders, return_type_impl_traits)))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,12 +16,8 @@ use rustc_hash::{FxHashMap, FxHashSet};
|
|||
|
||||
use super::Substs;
|
||||
use crate::{
|
||||
autoderef,
|
||||
db::HirDatabase,
|
||||
primitive::{FloatBitness, Uncertain},
|
||||
utils::all_super_traits,
|
||||
ApplicationTy, Canonical, DebruijnIndex, InEnvironment, TraitEnvironment, TraitRef, Ty,
|
||||
TypeCtor, TypeWalk,
|
||||
autoderef, db::HirDatabase, primitive::FloatBitness, utils::all_super_traits, ApplicationTy,
|
||||
Canonical, DebruijnIndex, InEnvironment, TraitEnvironment, TraitRef, Ty, TypeCtor, TypeWalk,
|
||||
};
|
||||
|
||||
/// This is used as a key for indexing impls.
|
||||
|
|
@ -147,12 +143,12 @@ impl Ty {
|
|||
}
|
||||
TypeCtor::Bool => lang_item_crate!("bool"),
|
||||
TypeCtor::Char => lang_item_crate!("char"),
|
||||
TypeCtor::Float(Uncertain::Known(f)) => match f.bitness {
|
||||
TypeCtor::Float(f) => match f.bitness {
|
||||
// There are two lang items: one in libcore (fXX) and one in libstd (fXX_runtime)
|
||||
FloatBitness::X32 => lang_item_crate!("f32", "f32_runtime"),
|
||||
FloatBitness::X64 => lang_item_crate!("f64", "f64_runtime"),
|
||||
},
|
||||
TypeCtor::Int(Uncertain::Known(i)) => lang_item_crate!(i.ty_to_string()),
|
||||
TypeCtor::Int(i) => lang_item_crate!(i.ty_to_string()),
|
||||
TypeCtor::Str => lang_item_crate!("str_alloc", "str"),
|
||||
TypeCtor::Slice => lang_item_crate!("slice_alloc", "slice"),
|
||||
TypeCtor::RawPtr(Mutability::Shared) => lang_item_crate!("const_ptr"),
|
||||
|
|
|
|||
|
|
@ -7,42 +7,6 @@ use std::fmt;
|
|||
|
||||
pub use hir_def::builtin_type::{BuiltinFloat, BuiltinInt, FloatBitness, IntBitness, Signedness};
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)]
|
||||
pub enum Uncertain<T> {
|
||||
Unknown,
|
||||
Known(T),
|
||||
}
|
||||
|
||||
impl From<IntTy> for Uncertain<IntTy> {
|
||||
fn from(ty: IntTy) -> Self {
|
||||
Uncertain::Known(ty)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Uncertain<IntTy> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Uncertain::Unknown => write!(f, "{{integer}}"),
|
||||
Uncertain::Known(ty) => write!(f, "{}", ty),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FloatTy> for Uncertain<FloatTy> {
|
||||
fn from(ty: FloatTy) -> Self {
|
||||
Uncertain::Known(ty)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Uncertain<FloatTy> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Uncertain::Unknown => write!(f, "{{float}}"),
|
||||
Uncertain::Known(ty) => write!(f, "{}", ty),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct IntTy {
|
||||
pub signedness: Signedness,
|
||||
|
|
@ -173,21 +137,3 @@ impl From<BuiltinFloat> for FloatTy {
|
|||
FloatTy { bitness: t.bitness }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<BuiltinInt>> for Uncertain<IntTy> {
|
||||
fn from(t: Option<BuiltinInt>) -> Self {
|
||||
match t {
|
||||
None => Uncertain::Unknown,
|
||||
Some(t) => Uncertain::Known(t.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<BuiltinFloat>> for Uncertain<FloatTy> {
|
||||
fn from(t: Option<BuiltinFloat>) -> Self {
|
||||
match t {
|
||||
None => Uncertain::Unknown,
|
||||
Some(t) => Uncertain::Known(t.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,8 @@ use std::{
|
|||
|
||||
use hir_def::{db::DefDatabase, AssocItemId, ModuleDefId, ModuleId};
|
||||
use hir_expand::{db::AstDatabase, diagnostics::DiagnosticSink};
|
||||
use ra_db::{
|
||||
salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, RelativePath, SourceDatabase, Upcast,
|
||||
};
|
||||
use ra_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, SourceDatabase, Upcast};
|
||||
use rustc_hash::FxHashSet;
|
||||
use stdx::format_to;
|
||||
|
||||
use crate::{db::HirDatabase, diagnostics::Diagnostic, expr::ExprValidator};
|
||||
|
|
@ -72,23 +71,12 @@ impl FileLoader for TestDB {
|
|||
fn file_text(&self, file_id: FileId) -> Arc<String> {
|
||||
FileLoaderDelegate(self).file_text(file_id)
|
||||
}
|
||||
fn resolve_relative_path(
|
||||
&self,
|
||||
anchor: FileId,
|
||||
relative_path: &RelativePath,
|
||||
) -> Option<FileId> {
|
||||
FileLoaderDelegate(self).resolve_relative_path(anchor, relative_path)
|
||||
fn resolve_path(&self, anchor: FileId, path: &str) -> Option<FileId> {
|
||||
FileLoaderDelegate(self).resolve_path(anchor, path)
|
||||
}
|
||||
fn relevant_crates(&self, file_id: FileId) -> Arc<Vec<CrateId>> {
|
||||
fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>> {
|
||||
FileLoaderDelegate(self).relevant_crates(file_id)
|
||||
}
|
||||
fn resolve_extern_path(
|
||||
&self,
|
||||
extern_id: ra_db::ExternSourceId,
|
||||
relative_path: &RelativePath,
|
||||
) -> Option<FileId> {
|
||||
FileLoaderDelegate(self).resolve_extern_path(extern_id, relative_path)
|
||||
}
|
||||
}
|
||||
|
||||
impl TestDB {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ fn omit_default_type_parameters() {
|
|||
//- /main.rs
|
||||
struct Foo<T = u8> { t: T }
|
||||
fn main() {
|
||||
let foo = Foo { t: 5 };
|
||||
let foo = Foo { t: 5u8 };
|
||||
foo<|>;
|
||||
}
|
||||
",
|
||||
|
|
@ -41,7 +41,7 @@ fn omit_default_type_parameters() {
|
|||
//- /main.rs
|
||||
struct Foo<K, T = u8> { k: K, t: T }
|
||||
fn main() {
|
||||
let foo = Foo { k: 400, t: 5 };
|
||||
let foo = Foo { k: 400, t: 5u8 };
|
||||
foo<|>;
|
||||
}
|
||||
",
|
||||
|
|
|
|||
|
|
@ -183,60 +183,6 @@ fn test() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infer_associated_method_generics_with_default_param() {
|
||||
assert_snapshot!(
|
||||
infer(r#"
|
||||
struct Gen<T=u32> {
|
||||
val: T
|
||||
}
|
||||
|
||||
impl<T> Gen<T> {
|
||||
pub fn make() -> Gen<T> {
|
||||
loop { }
|
||||
}
|
||||
}
|
||||
|
||||
fn test() {
|
||||
let a = Gen::make();
|
||||
}
|
||||
"#),
|
||||
@r###"
|
||||
80..104 '{ ... }': Gen<T>
|
||||
90..98 'loop { }': !
|
||||
95..98 '{ }': ()
|
||||
118..146 '{ ...e(); }': ()
|
||||
128..129 'a': Gen<u32>
|
||||
132..141 'Gen::make': fn make<u32>() -> Gen<u32>
|
||||
132..143 'Gen::make()': Gen<u32>
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infer_associated_method_generics_with_default_tuple_param() {
|
||||
let t = type_at(
|
||||
r#"
|
||||
//- /main.rs
|
||||
struct Gen<T=()> {
|
||||
val: T
|
||||
}
|
||||
|
||||
impl<T> Gen<T> {
|
||||
pub fn make() -> Gen<T> {
|
||||
loop { }
|
||||
}
|
||||
}
|
||||
|
||||
fn test() {
|
||||
let a = Gen::make();
|
||||
a.val<|>;
|
||||
}
|
||||
"#,
|
||||
);
|
||||
assert_eq!(t, "()");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infer_associated_method_generics_without_args() {
|
||||
assert_snapshot!(
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ fn foo() {
|
|||
fn infer_ranges() {
|
||||
let (db, pos) = TestDB::with_position(
|
||||
r#"
|
||||
//- /main.rs crate:main deps:std
|
||||
//- /main.rs crate:main deps:core
|
||||
fn test() {
|
||||
let a = ..;
|
||||
let b = 1..;
|
||||
|
|
@ -108,7 +108,7 @@ fn test() {
|
|||
t<|>;
|
||||
}
|
||||
|
||||
//- /std.rs crate:std
|
||||
//- /core.rs crate:core
|
||||
#[prelude_import] use prelude::*;
|
||||
mod prelude {}
|
||||
|
||||
|
|
@ -1997,3 +1997,111 @@ fn foo() {
|
|||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generic_default() {
|
||||
assert_snapshot!(
|
||||
infer(r#"
|
||||
struct Thing<T = ()> { t: T }
|
||||
enum OtherThing<T = ()> {
|
||||
One { t: T },
|
||||
Two(T),
|
||||
}
|
||||
|
||||
fn test(t1: Thing, t2: OtherThing, t3: Thing<i32>, t4: OtherThing<i32>) {
|
||||
t1.t;
|
||||
t3.t;
|
||||
match t2 {
|
||||
OtherThing::One { t } => { t; },
|
||||
OtherThing::Two(t) => { t; },
|
||||
}
|
||||
match t4 {
|
||||
OtherThing::One { t } => { t; },
|
||||
OtherThing::Two(t) => { t; },
|
||||
}
|
||||
}
|
||||
"#),
|
||||
@r###"
|
||||
98..100 't1': Thing<()>
|
||||
109..111 't2': OtherThing<()>
|
||||
125..127 't3': Thing<i32>
|
||||
141..143 't4': OtherThing<i32>
|
||||
162..385 '{ ... } }': ()
|
||||
168..170 't1': Thing<()>
|
||||
168..172 't1.t': ()
|
||||
178..180 't3': Thing<i32>
|
||||
178..182 't3.t': i32
|
||||
188..283 'match ... }': ()
|
||||
194..196 't2': OtherThing<()>
|
||||
207..228 'OtherT... { t }': OtherThing<()>
|
||||
225..226 't': ()
|
||||
232..238 '{ t; }': ()
|
||||
234..235 't': ()
|
||||
248..266 'OtherT...Two(t)': OtherThing<()>
|
||||
264..265 't': ()
|
||||
270..276 '{ t; }': ()
|
||||
272..273 't': ()
|
||||
288..383 'match ... }': ()
|
||||
294..296 't4': OtherThing<i32>
|
||||
307..328 'OtherT... { t }': OtherThing<i32>
|
||||
325..326 't': i32
|
||||
332..338 '{ t; }': ()
|
||||
334..335 't': i32
|
||||
348..366 'OtherT...Two(t)': OtherThing<i32>
|
||||
364..365 't': i32
|
||||
370..376 '{ t; }': ()
|
||||
372..373 't': i32
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generic_default_in_struct_literal() {
|
||||
assert_snapshot!(
|
||||
infer(r#"
|
||||
struct Thing<T = ()> { t: T }
|
||||
enum OtherThing<T = ()> {
|
||||
One { t: T },
|
||||
Two(T),
|
||||
}
|
||||
|
||||
fn test() {
|
||||
let x = Thing { t: loop {} };
|
||||
let y = Thing { t: () };
|
||||
let z = Thing { t: 1i32 };
|
||||
if let Thing { t } = z {
|
||||
t;
|
||||
}
|
||||
|
||||
let a = OtherThing::One { t: 1i32 };
|
||||
let b = OtherThing::Two(1i32);
|
||||
}
|
||||
"#),
|
||||
@r###"
|
||||
100..320 '{ ...32); }': ()
|
||||
110..111 'x': Thing<!>
|
||||
114..134 'Thing ...p {} }': Thing<!>
|
||||
125..132 'loop {}': !
|
||||
130..132 '{}': ()
|
||||
144..145 'y': Thing<()>
|
||||
148..163 'Thing { t: () }': Thing<()>
|
||||
159..161 '()': ()
|
||||
173..174 'z': Thing<i32>
|
||||
177..194 'Thing ...1i32 }': Thing<i32>
|
||||
188..192 '1i32': i32
|
||||
200..241 'if let... }': ()
|
||||
207..218 'Thing { t }': Thing<i32>
|
||||
215..216 't': i32
|
||||
221..222 'z': Thing<i32>
|
||||
223..241 '{ ... }': ()
|
||||
233..234 't': i32
|
||||
251..252 'a': OtherThing<i32>
|
||||
255..282 'OtherT...1i32 }': OtherThing<i32>
|
||||
276..280 '1i32': i32
|
||||
292..293 'b': OtherThing<i32>
|
||||
296..311 'OtherThing::Two': Two<i32>(i32) -> OtherThing<i32>
|
||||
296..317 'OtherT...(1i32)': OtherThing<i32>
|
||||
312..316 '1i32': i32
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use super::{infer, infer_with_mismatches, type_at, type_at_pos};
|
|||
fn infer_await() {
|
||||
let (db, pos) = TestDB::with_position(
|
||||
r#"
|
||||
//- /main.rs crate:main deps:std
|
||||
//- /main.rs crate:main deps:core
|
||||
|
||||
struct IntFuture;
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ fn test() {
|
|||
v<|>;
|
||||
}
|
||||
|
||||
//- /std.rs crate:std
|
||||
//- /core.rs crate:core
|
||||
#[prelude_import] use future::*;
|
||||
mod future {
|
||||
#[lang = "future_trait"]
|
||||
|
|
@ -42,7 +42,7 @@ mod future {
|
|||
fn infer_async() {
|
||||
let (db, pos) = TestDB::with_position(
|
||||
r#"
|
||||
//- /main.rs crate:main deps:std
|
||||
//- /main.rs crate:main deps:core
|
||||
|
||||
async fn foo() -> u64 {
|
||||
128
|
||||
|
|
@ -54,7 +54,7 @@ fn test() {
|
|||
v<|>;
|
||||
}
|
||||
|
||||
//- /std.rs crate:std
|
||||
//- /core.rs crate:core
|
||||
#[prelude_import] use future::*;
|
||||
mod future {
|
||||
#[lang = "future_trait"]
|
||||
|
|
@ -72,7 +72,7 @@ mod future {
|
|||
fn infer_desugar_async() {
|
||||
let (db, pos) = TestDB::with_position(
|
||||
r#"
|
||||
//- /main.rs crate:main deps:std
|
||||
//- /main.rs crate:main deps:core
|
||||
|
||||
async fn foo() -> u64 {
|
||||
128
|
||||
|
|
@ -83,7 +83,7 @@ fn test() {
|
|||
r<|>;
|
||||
}
|
||||
|
||||
//- /std.rs crate:std
|
||||
//- /core.rs crate:core
|
||||
#[prelude_import] use future::*;
|
||||
mod future {
|
||||
trait Future {
|
||||
|
|
@ -100,7 +100,7 @@ mod future {
|
|||
fn infer_try() {
|
||||
let (db, pos) = TestDB::with_position(
|
||||
r#"
|
||||
//- /main.rs crate:main deps:std
|
||||
//- /main.rs crate:main deps:core
|
||||
|
||||
fn test() {
|
||||
let r: Result<i32, u64> = Result::Ok(1);
|
||||
|
|
@ -108,7 +108,7 @@ fn test() {
|
|||
v<|>;
|
||||
}
|
||||
|
||||
//- /std.rs crate:std
|
||||
//- /core.rs crate:core
|
||||
|
||||
#[prelude_import] use ops::*;
|
||||
mod ops {
|
||||
|
|
@ -140,9 +140,9 @@ mod result {
|
|||
fn infer_for_loop() {
|
||||
let (db, pos) = TestDB::with_position(
|
||||
r#"
|
||||
//- /main.rs crate:main deps:std
|
||||
//- /main.rs crate:main deps:core,alloc
|
||||
|
||||
use std::collections::Vec;
|
||||
use alloc::collections::Vec;
|
||||
|
||||
fn test() {
|
||||
let v = Vec::new();
|
||||
|
|
@ -152,7 +152,7 @@ fn test() {
|
|||
}
|
||||
}
|
||||
|
||||
//- /std.rs crate:std
|
||||
//- /core.rs crate:core
|
||||
|
||||
#[prelude_import] use iter::*;
|
||||
mod iter {
|
||||
|
|
@ -161,6 +161,8 @@ mod iter {
|
|||
}
|
||||
}
|
||||
|
||||
//- /alloc.rs crate:alloc deps:core
|
||||
|
||||
mod collections {
|
||||
struct Vec<T> {}
|
||||
impl<T> Vec<T> {
|
||||
|
|
@ -168,7 +170,7 @@ mod collections {
|
|||
fn push(&mut self, t: T) { }
|
||||
}
|
||||
|
||||
impl<T> crate::iter::IntoIterator for Vec<T> {
|
||||
impl<T> IntoIterator for Vec<T> {
|
||||
type Item=T;
|
||||
}
|
||||
}
|
||||
|
|
@ -1110,7 +1112,6 @@ fn test() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn impl_trait() {
|
||||
assert_snapshot!(
|
||||
infer(r#"
|
||||
|
|
@ -1160,6 +1161,95 @@ fn test(x: impl Trait<u64>, y: &impl Trait<u64>) {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_return_pos_impl_trait() {
|
||||
mark::check!(lower_rpit);
|
||||
assert_snapshot!(
|
||||
infer(r#"
|
||||
trait Trait<T> {
|
||||
fn foo(&self) -> T;
|
||||
}
|
||||
fn bar() -> impl Trait<u64> { loop {} }
|
||||
|
||||
fn test() {
|
||||
let a = bar();
|
||||
a.foo();
|
||||
}
|
||||
"#),
|
||||
@r###"
|
||||
30..34 'self': &Self
|
||||
72..83 '{ loop {} }': !
|
||||
74..81 'loop {}': !
|
||||
79..81 '{}': ()
|
||||
95..130 '{ ...o(); }': ()
|
||||
105..106 'a': impl Trait<u64>
|
||||
109..112 'bar': fn bar() -> impl Trait<u64>
|
||||
109..114 'bar()': impl Trait<u64>
|
||||
120..121 'a': impl Trait<u64>
|
||||
120..127 'a.foo()': u64
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn more_return_pos_impl_trait() {
|
||||
assert_snapshot!(
|
||||
infer(r#"
|
||||
trait Iterator {
|
||||
type Item;
|
||||
fn next(&mut self) -> Self::Item;
|
||||
}
|
||||
trait Trait<T> {
|
||||
fn foo(&self) -> T;
|
||||
}
|
||||
fn bar() -> (impl Iterator<Item = impl Trait<u32>>, impl Trait<u64>) { loop {} }
|
||||
fn baz<T>(t: T) -> (impl Iterator<Item = impl Trait<T>>, impl Trait<T>) { loop {} }
|
||||
|
||||
fn test() {
|
||||
let (a, b) = bar();
|
||||
a.next().foo();
|
||||
b.foo();
|
||||
let (c, d) = baz(1u128);
|
||||
c.next().foo();
|
||||
d.foo();
|
||||
}
|
||||
"#),
|
||||
@r###"
|
||||
50..54 'self': &mut Self
|
||||
102..106 'self': &Self
|
||||
185..196 '{ loop {} }': ({unknown}, {unknown})
|
||||
187..194 'loop {}': !
|
||||
192..194 '{}': ()
|
||||
207..208 't': T
|
||||
269..280 '{ loop {} }': ({unknown}, {unknown})
|
||||
271..278 'loop {}': !
|
||||
276..278 '{}': ()
|
||||
292..414 '{ ...o(); }': ()
|
||||
302..308 '(a, b)': (impl Iterator<Item = impl Trait<u32>>, impl Trait<u64>)
|
||||
303..304 'a': impl Iterator<Item = impl Trait<u32>>
|
||||
306..307 'b': impl Trait<u64>
|
||||
311..314 'bar': fn bar() -> (impl Iterator<Item = impl Trait<u32>>, impl Trait<u64>)
|
||||
311..316 'bar()': (impl Iterator<Item = impl Trait<u32>>, impl Trait<u64>)
|
||||
322..323 'a': impl Iterator<Item = impl Trait<u32>>
|
||||
322..330 'a.next()': impl Trait<u32>
|
||||
322..336 'a.next().foo()': u32
|
||||
342..343 'b': impl Trait<u64>
|
||||
342..349 'b.foo()': u64
|
||||
359..365 '(c, d)': (impl Iterator<Item = impl Trait<u128>>, impl Trait<u128>)
|
||||
360..361 'c': impl Iterator<Item = impl Trait<u128>>
|
||||
363..364 'd': impl Trait<u128>
|
||||
368..371 'baz': fn baz<u128>(u128) -> (impl Iterator<Item = impl Trait<u128>>, impl Trait<u128>)
|
||||
368..378 'baz(1u128)': (impl Iterator<Item = impl Trait<u128>>, impl Trait<u128>)
|
||||
372..377 '1u128': u128
|
||||
384..385 'c': impl Iterator<Item = impl Trait<u128>>
|
||||
384..392 'c.next()': impl Trait<u128>
|
||||
384..398 'c.next().foo()': u128
|
||||
404..405 'd': impl Trait<u128>
|
||||
404..411 'd.foo()': u128
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dyn_trait() {
|
||||
assert_snapshot!(
|
||||
|
|
@ -1718,33 +1808,33 @@ fn test() {
|
|||
}
|
||||
"#),
|
||||
@r###"
|
||||
65..69 'self': &Self
|
||||
166..170 'self': Self
|
||||
172..176 'args': Args
|
||||
240..244 'self': &Foo
|
||||
255..257 '{}': ()
|
||||
335..336 'f': F
|
||||
355..357 '{}': ()
|
||||
444..690 '{ ...o(); }': ()
|
||||
454..459 'lazy1': Lazy<Foo, fn() -> T>
|
||||
476..485 'Lazy::new': fn new<Foo, fn() -> T>(fn() -> T) -> Lazy<Foo, fn() -> T>
|
||||
476..493 'Lazy::...| Foo)': Lazy<Foo, fn() -> T>
|
||||
486..492 '|| Foo': || -> T
|
||||
489..492 'Foo': Foo
|
||||
503..505 'r1': {unknown}
|
||||
508..513 'lazy1': Lazy<Foo, fn() -> T>
|
||||
508..519 'lazy1.foo()': {unknown}
|
||||
561..576 'make_foo_fn_ptr': fn() -> Foo
|
||||
592..603 'make_foo_fn': fn make_foo_fn() -> Foo
|
||||
613..618 'lazy2': Lazy<Foo, fn() -> T>
|
||||
635..644 'Lazy::new': fn new<Foo, fn() -> T>(fn() -> T) -> Lazy<Foo, fn() -> T>
|
||||
635..661 'Lazy::...n_ptr)': Lazy<Foo, fn() -> T>
|
||||
645..660 'make_foo_fn_ptr': fn() -> Foo
|
||||
671..673 'r2': {unknown}
|
||||
676..681 'lazy2': Lazy<Foo, fn() -> T>
|
||||
676..687 'lazy2.foo()': {unknown}
|
||||
550..552 '{}': ()
|
||||
"###
|
||||
65..69 'self': &Self
|
||||
166..170 'self': Self
|
||||
172..176 'args': Args
|
||||
240..244 'self': &Foo
|
||||
255..257 '{}': ()
|
||||
335..336 'f': F
|
||||
355..357 '{}': ()
|
||||
444..690 '{ ...o(); }': ()
|
||||
454..459 'lazy1': Lazy<Foo, || -> Foo>
|
||||
476..485 'Lazy::new': fn new<Foo, || -> Foo>(|| -> Foo) -> Lazy<Foo, || -> Foo>
|
||||
476..493 'Lazy::...| Foo)': Lazy<Foo, || -> Foo>
|
||||
486..492 '|| Foo': || -> Foo
|
||||
489..492 'Foo': Foo
|
||||
503..505 'r1': usize
|
||||
508..513 'lazy1': Lazy<Foo, || -> Foo>
|
||||
508..519 'lazy1.foo()': usize
|
||||
561..576 'make_foo_fn_ptr': fn() -> Foo
|
||||
592..603 'make_foo_fn': fn make_foo_fn() -> Foo
|
||||
613..618 'lazy2': Lazy<Foo, fn() -> Foo>
|
||||
635..644 'Lazy::new': fn new<Foo, fn() -> Foo>(fn() -> Foo) -> Lazy<Foo, fn() -> Foo>
|
||||
635..661 'Lazy::...n_ptr)': Lazy<Foo, fn() -> Foo>
|
||||
645..660 'make_foo_fn_ptr': fn() -> Foo
|
||||
671..673 'r2': {unknown}
|
||||
676..681 'lazy2': Lazy<Foo, fn() -> Foo>
|
||||
676..687 'lazy2.foo()': {unknown}
|
||||
550..552 '{}': ()
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -2758,12 +2848,12 @@ fn test() {
|
|||
fn integer_range_iterate() {
|
||||
let t = type_at(
|
||||
r#"
|
||||
//- /main.rs crate:main deps:std
|
||||
//- /main.rs crate:main deps:core
|
||||
fn test() {
|
||||
for x in 0..100 { x<|>; }
|
||||
}
|
||||
|
||||
//- /std.rs crate:std
|
||||
//- /core.rs crate:core
|
||||
pub mod ops {
|
||||
pub struct Range<Idx> {
|
||||
pub start: Idx,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use std::sync::Arc;
|
|||
use log::debug;
|
||||
|
||||
use chalk_ir::{fold::shift::Shift, GenericArg, TypeName};
|
||||
use chalk_solve::rust_ir::{self, WellKnownTrait};
|
||||
use chalk_solve::rust_ir::{self, OpaqueTyDatumBound, WellKnownTrait};
|
||||
|
||||
use hir_def::{
|
||||
lang_item::{lang_attr, LangItemTarget},
|
||||
|
|
@ -100,6 +100,7 @@ impl<'a> chalk_solve::RustIrDatabase<Interner> for ChalkContext<'a> {
|
|||
fn associated_ty_value(&self, id: AssociatedTyValueId) -> Arc<AssociatedTyValue> {
|
||||
self.db.associated_ty_value(self.krate, id)
|
||||
}
|
||||
|
||||
fn custom_clauses(&self) -> Vec<chalk_ir::ProgramClause<Interner>> {
|
||||
vec![]
|
||||
}
|
||||
|
|
@ -130,11 +131,34 @@ impl<'a> chalk_solve::RustIrDatabase<Interner> for ChalkContext<'a> {
|
|||
self.db.program_clauses_for_chalk_env(self.krate, environment.clone())
|
||||
}
|
||||
|
||||
fn opaque_ty_data(
|
||||
&self,
|
||||
_id: chalk_ir::OpaqueTyId<Interner>,
|
||||
) -> Arc<rust_ir::OpaqueTyDatum<Interner>> {
|
||||
unimplemented!()
|
||||
fn opaque_ty_data(&self, id: chalk_ir::OpaqueTyId<Interner>) -> Arc<OpaqueTyDatum> {
|
||||
let interned_id = crate::db::InternedOpaqueTyId::from(id);
|
||||
let full_id = self.db.lookup_intern_impl_trait_id(interned_id);
|
||||
let (func, idx) = match full_id {
|
||||
crate::OpaqueTyId::ReturnTypeImplTrait(func, idx) => (func, idx),
|
||||
};
|
||||
let datas =
|
||||
self.db.return_type_impl_traits(func).expect("impl trait id without impl traits");
|
||||
let data = &datas.value.impl_traits[idx as usize];
|
||||
let bound = OpaqueTyDatumBound {
|
||||
bounds: make_binders(
|
||||
data.bounds
|
||||
.value
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter(|b| !b.is_error())
|
||||
.map(|b| b.to_chalk(self.db))
|
||||
.collect(),
|
||||
1,
|
||||
),
|
||||
};
|
||||
let num_vars = datas.num_binders;
|
||||
Arc::new(OpaqueTyDatum { opaque_ty_id: id, bound: make_binders(bound, num_vars) })
|
||||
}
|
||||
|
||||
fn hidden_opaque_type(&self, _id: chalk_ir::OpaqueTyId<Interner>) -> chalk_ir::Ty<Interner> {
|
||||
// FIXME: actually provide the hidden type; it is relevant for auto traits
|
||||
Ty::Unknown.to_chalk(self.db)
|
||||
}
|
||||
|
||||
fn force_impl_for(
|
||||
|
|
@ -150,10 +174,6 @@ impl<'a> chalk_solve::RustIrDatabase<Interner> for ChalkContext<'a> {
|
|||
// FIXME: implement actual object safety
|
||||
true
|
||||
}
|
||||
|
||||
fn hidden_opaque_type(&self, _id: chalk_ir::OpaqueTyId<Interner>) -> chalk_ir::Ty<Interner> {
|
||||
Ty::Unknown.to_chalk(self.db)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn program_clauses_for_chalk_env_query(
|
||||
|
|
@ -460,6 +480,18 @@ impl From<crate::traits::GlobalImplId> for ImplId {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<OpaqueTyId> for crate::db::InternedOpaqueTyId {
|
||||
fn from(id: OpaqueTyId) -> Self {
|
||||
InternKey::from_intern_id(id.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::db::InternedOpaqueTyId> for OpaqueTyId {
|
||||
fn from(id: crate::db::InternedOpaqueTyId) -> Self {
|
||||
chalk_ir::OpaqueTyId(id.as_intern_id())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<rust_ir::AssociatedTyValueId<Interner>> for crate::traits::AssocTyValueId {
|
||||
fn from(id: rust_ir::AssociatedTyValueId<Interner>) -> Self {
|
||||
Self::from_intern_id(id.0)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ pub type AssociatedTyValueId = chalk_solve::rust_ir::AssociatedTyValueId<Interne
|
|||
pub type AssociatedTyValue = chalk_solve::rust_ir::AssociatedTyValue<Interner>;
|
||||
pub type FnDefId = chalk_ir::FnDefId<Interner>;
|
||||
pub type FnDefDatum = chalk_solve::rust_ir::FnDefDatum<Interner>;
|
||||
pub type OpaqueTyId = chalk_ir::OpaqueTyId<Interner>;
|
||||
pub type OpaqueTyDatum = chalk_solve::rust_ir::OpaqueTyDatum<Interner>;
|
||||
|
||||
impl chalk_ir::interner::Interner for Interner {
|
||||
type InternedType = Box<chalk_ir::TyData<Self>>; // FIXME use Arc?
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@ use ra_db::salsa::InternKey;
|
|||
|
||||
use crate::{
|
||||
db::HirDatabase,
|
||||
primitive::{FloatBitness, FloatTy, IntBitness, IntTy, Signedness, Uncertain},
|
||||
primitive::{FloatBitness, FloatTy, IntBitness, IntTy, Signedness},
|
||||
traits::{builtin, AssocTyValue, Canonical, Impl, Obligation},
|
||||
ApplicationTy, CallableDef, GenericPredicate, InEnvironment, ProjectionPredicate, ProjectionTy,
|
||||
Substs, TraitEnvironment, TraitRef, Ty, TypeCtor,
|
||||
ApplicationTy, CallableDef, GenericPredicate, InEnvironment, OpaqueTy, OpaqueTyId,
|
||||
ProjectionPredicate, ProjectionTy, Substs, TraitEnvironment, TraitRef, Ty, TypeCtor,
|
||||
};
|
||||
|
||||
use super::interner::*;
|
||||
|
|
@ -68,7 +68,16 @@ impl ToChalk for Ty {
|
|||
let bounded_ty = chalk_ir::DynTy { bounds: make_binders(where_clauses, 1) };
|
||||
chalk_ir::TyData::Dyn(bounded_ty).intern(&Interner)
|
||||
}
|
||||
Ty::Opaque(_) | Ty::Unknown => {
|
||||
Ty::Opaque(opaque_ty) => {
|
||||
let opaque_ty_id = opaque_ty.opaque_ty_id.to_chalk(db);
|
||||
let substitution = opaque_ty.parameters.to_chalk(db);
|
||||
chalk_ir::TyData::Alias(chalk_ir::AliasTy::Opaque(chalk_ir::OpaqueTy {
|
||||
opaque_ty_id,
|
||||
substitution,
|
||||
}))
|
||||
.intern(&Interner)
|
||||
}
|
||||
Ty::Unknown => {
|
||||
let substitution = chalk_ir::Substitution::empty(&Interner);
|
||||
let name = TypeName::Error;
|
||||
chalk_ir::ApplicationTy { name, substitution }.cast(&Interner).intern(&Interner)
|
||||
|
|
@ -98,7 +107,11 @@ impl ToChalk for Ty {
|
|||
let parameters = from_chalk(db, proj.substitution);
|
||||
Ty::Projection(ProjectionTy { associated_ty, parameters })
|
||||
}
|
||||
chalk_ir::TyData::Alias(chalk_ir::AliasTy::Opaque(_)) => unimplemented!(),
|
||||
chalk_ir::TyData::Alias(chalk_ir::AliasTy::Opaque(opaque_ty)) => {
|
||||
let impl_trait_id = from_chalk(db, opaque_ty.opaque_ty_id);
|
||||
let parameters = from_chalk(db, opaque_ty.substitution);
|
||||
Ty::Opaque(OpaqueTy { opaque_ty_id: impl_trait_id, parameters })
|
||||
}
|
||||
chalk_ir::TyData::Function(chalk_ir::Fn { num_binders: _, substitution }) => {
|
||||
let parameters: Substs = from_chalk(db, substitution);
|
||||
Ty::Apply(ApplicationTy {
|
||||
|
|
@ -204,6 +217,21 @@ impl ToChalk for hir_def::TraitId {
|
|||
}
|
||||
}
|
||||
|
||||
impl ToChalk for OpaqueTyId {
|
||||
type Chalk = chalk_ir::OpaqueTyId<Interner>;
|
||||
|
||||
fn to_chalk(self, db: &dyn HirDatabase) -> chalk_ir::OpaqueTyId<Interner> {
|
||||
db.intern_impl_trait_id(self).into()
|
||||
}
|
||||
|
||||
fn from_chalk(
|
||||
db: &dyn HirDatabase,
|
||||
opaque_ty_id: chalk_ir::OpaqueTyId<Interner>,
|
||||
) -> OpaqueTyId {
|
||||
db.lookup_intern_impl_trait_id(opaque_ty_id.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToChalk for TypeCtor {
|
||||
type Chalk = TypeName<Interner>;
|
||||
|
||||
|
|
@ -214,13 +242,18 @@ impl ToChalk for TypeCtor {
|
|||
TypeName::AssociatedType(type_id)
|
||||
}
|
||||
|
||||
TypeCtor::OpaqueType(impl_trait_id) => {
|
||||
let id = impl_trait_id.to_chalk(db);
|
||||
TypeName::OpaqueType(id)
|
||||
}
|
||||
|
||||
TypeCtor::Bool => TypeName::Scalar(Scalar::Bool),
|
||||
TypeCtor::Char => TypeName::Scalar(Scalar::Char),
|
||||
TypeCtor::Int(Uncertain::Known(int_ty)) => TypeName::Scalar(int_ty_to_chalk(int_ty)),
|
||||
TypeCtor::Float(Uncertain::Known(FloatTy { bitness: FloatBitness::X32 })) => {
|
||||
TypeCtor::Int(int_ty) => TypeName::Scalar(int_ty_to_chalk(int_ty)),
|
||||
TypeCtor::Float(FloatTy { bitness: FloatBitness::X32 }) => {
|
||||
TypeName::Scalar(Scalar::Float(chalk_ir::FloatTy::F32))
|
||||
}
|
||||
TypeCtor::Float(Uncertain::Known(FloatTy { bitness: FloatBitness::X64 })) => {
|
||||
TypeCtor::Float(FloatTy { bitness: FloatBitness::X64 }) => {
|
||||
TypeName::Scalar(Scalar::Float(chalk_ir::FloatTy::F64))
|
||||
}
|
||||
|
||||
|
|
@ -235,9 +268,7 @@ impl ToChalk for TypeCtor {
|
|||
}
|
||||
TypeCtor::Never => TypeName::Never,
|
||||
|
||||
TypeCtor::Int(Uncertain::Unknown)
|
||||
| TypeCtor::Float(Uncertain::Unknown)
|
||||
| TypeCtor::Adt(_)
|
||||
TypeCtor::Adt(_)
|
||||
| TypeCtor::Array
|
||||
| TypeCtor::FnPtr { .. }
|
||||
| TypeCtor::Closure { .. } => {
|
||||
|
|
@ -252,23 +283,25 @@ impl ToChalk for TypeCtor {
|
|||
match type_name {
|
||||
TypeName::Adt(struct_id) => db.lookup_intern_type_ctor(struct_id.into()),
|
||||
TypeName::AssociatedType(type_id) => TypeCtor::AssociatedType(from_chalk(db, type_id)),
|
||||
TypeName::OpaqueType(_) => unreachable!(),
|
||||
TypeName::OpaqueType(opaque_type_id) => {
|
||||
TypeCtor::OpaqueType(from_chalk(db, opaque_type_id))
|
||||
}
|
||||
|
||||
TypeName::Scalar(Scalar::Bool) => TypeCtor::Bool,
|
||||
TypeName::Scalar(Scalar::Char) => TypeCtor::Char,
|
||||
TypeName::Scalar(Scalar::Int(int_ty)) => TypeCtor::Int(Uncertain::Known(IntTy {
|
||||
TypeName::Scalar(Scalar::Int(int_ty)) => TypeCtor::Int(IntTy {
|
||||
signedness: Signedness::Signed,
|
||||
bitness: bitness_from_chalk_int(int_ty),
|
||||
})),
|
||||
TypeName::Scalar(Scalar::Uint(uint_ty)) => TypeCtor::Int(Uncertain::Known(IntTy {
|
||||
}),
|
||||
TypeName::Scalar(Scalar::Uint(uint_ty)) => TypeCtor::Int(IntTy {
|
||||
signedness: Signedness::Unsigned,
|
||||
bitness: bitness_from_chalk_uint(uint_ty),
|
||||
})),
|
||||
}),
|
||||
TypeName::Scalar(Scalar::Float(chalk_ir::FloatTy::F32)) => {
|
||||
TypeCtor::Float(Uncertain::Known(FloatTy { bitness: FloatBitness::X32 }))
|
||||
TypeCtor::Float(FloatTy { bitness: FloatBitness::X32 })
|
||||
}
|
||||
TypeName::Scalar(Scalar::Float(chalk_ir::FloatTy::F64)) => {
|
||||
TypeCtor::Float(Uncertain::Known(FloatTy { bitness: FloatBitness::X64 }))
|
||||
TypeCtor::Float(FloatTy { bitness: FloatBitness::X64 })
|
||||
}
|
||||
TypeName::Tuple(cardinality) => TypeCtor::Tuple { cardinality: cardinality as u16 },
|
||||
TypeName::Raw(mutability) => TypeCtor::RawPtr(from_chalk(db, mutability)),
|
||||
|
|
@ -447,6 +480,11 @@ impl ToChalk for GenericPredicate {
|
|||
let ty = from_chalk(db, projection_eq.ty);
|
||||
GenericPredicate::Projection(ProjectionPredicate { projection_ty, ty })
|
||||
}
|
||||
|
||||
chalk_ir::WhereClause::LifetimeOutlives(_) => {
|
||||
// we shouldn't get these from Chalk
|
||||
panic!("encountered LifetimeOutlives from Chalk")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,11 @@ impl DebugContext<'_> {
|
|||
let name = self.0.type_alias_data(type_alias).name.clone();
|
||||
write!(f, "{}::{}", trait_name, name)?;
|
||||
}
|
||||
TypeCtor::OpaqueType(opaque_ty_id) => match opaque_ty_id {
|
||||
crate::OpaqueTyId::ReturnTypeImplTrait(func, idx) => {
|
||||
write!(f, "{{impl trait {} of {:?}}}", idx, func)?;
|
||||
}
|
||||
},
|
||||
TypeCtor::Closure { def, expr } => {
|
||||
write!(f, "{{closure {:?} in ", expr.into_raw())?;
|
||||
match def {
|
||||
|
|
|
|||
|
|
@ -126,3 +126,81 @@ pub(crate) fn completions(
|
|||
|
||||
Some(acc)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::completion::completion_config::CompletionConfig;
|
||||
use crate::mock_analysis::analysis_and_position;
|
||||
|
||||
struct DetailAndDocumentation<'a> {
|
||||
detail: &'a str,
|
||||
documentation: &'a str,
|
||||
}
|
||||
|
||||
fn check_detail_and_documentation(fixture: &str, expected: DetailAndDocumentation) {
|
||||
let (analysis, position) = analysis_and_position(fixture);
|
||||
let config = CompletionConfig::default();
|
||||
let completions = analysis.completions(&config, position).unwrap().unwrap();
|
||||
for item in completions {
|
||||
if item.detail() == Some(expected.detail) {
|
||||
let opt = item.documentation();
|
||||
let doc = opt.as_ref().map(|it| it.as_str());
|
||||
assert_eq!(doc, Some(expected.documentation));
|
||||
return;
|
||||
}
|
||||
}
|
||||
panic!("completion detail not found: {}", expected.detail)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_completion_detail_from_macro_generated_struct_fn_doc_attr() {
|
||||
check_detail_and_documentation(
|
||||
r#"
|
||||
//- /lib.rs
|
||||
macro_rules! bar {
|
||||
() => {
|
||||
struct Bar;
|
||||
impl Bar {
|
||||
#[doc = "Do the foo"]
|
||||
fn foo(&self) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bar!();
|
||||
|
||||
fn foo() {
|
||||
let bar = Bar;
|
||||
bar.fo<|>;
|
||||
}
|
||||
"#,
|
||||
DetailAndDocumentation { detail: "fn foo(&self)", documentation: "Do the foo" },
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_completion_detail_from_macro_generated_struct_fn_doc_comment() {
|
||||
check_detail_and_documentation(
|
||||
r#"
|
||||
//- /lib.rs
|
||||
macro_rules! bar {
|
||||
() => {
|
||||
struct Bar;
|
||||
impl Bar {
|
||||
/// Do the foo
|
||||
fn foo(&self) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bar!();
|
||||
|
||||
fn foo() {
|
||||
let bar = Bar;
|
||||
bar.fo<|>;
|
||||
}
|
||||
"#,
|
||||
DetailAndDocumentation { detail: "fn foo(&self)", documentation: " Do the foo" },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ use ra_syntax::{
|
|||
};
|
||||
use ra_text_edit::{TextEdit, TextEditBuilder};
|
||||
|
||||
use crate::{Diagnostic, FileId, FileSystemEdit, Fix, SourceChange, SourceFileEdit};
|
||||
use crate::{Diagnostic, FileId, FileSystemEdit, Fix, SourceFileEdit};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Severity {
|
||||
|
|
@ -115,7 +115,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
|
|||
let node = d.ast(db);
|
||||
let replacement = format!("Ok({})", node.syntax());
|
||||
let edit = TextEdit::replace(node.syntax().text_range(), replacement);
|
||||
let source_change = SourceChange::source_file_edit_from(file_id, edit);
|
||||
let source_change = SourceFileEdit { file_id, edit }.into();
|
||||
let fix = Fix::new("Wrap with ok", source_change);
|
||||
res.borrow_mut().push(Diagnostic {
|
||||
range: sema.diagnostics_range(d).range,
|
||||
|
|
@ -187,7 +187,8 @@ fn check_struct_shorthand_initialization(
|
|||
if let (Some(name_ref), Some(expr)) = (record_field.name_ref(), record_field.expr()) {
|
||||
let field_name = name_ref.syntax().text().to_string();
|
||||
let field_expr = expr.syntax().text().to_string();
|
||||
if field_name == field_expr {
|
||||
let field_name_is_tup_index = name_ref.as_tuple_field().is_some();
|
||||
if field_name == field_expr && !field_name_is_tup_index {
|
||||
let mut edit_builder = TextEditBuilder::default();
|
||||
edit_builder.delete(record_field.syntax().text_range());
|
||||
edit_builder.insert(record_field.syntax().text_range().start(), field_name);
|
||||
|
|
@ -321,29 +322,26 @@ mod tests {
|
|||
fn test_wrap_return_type() {
|
||||
let before = r#"
|
||||
//- /main.rs
|
||||
use std::{string::String, result::Result::{self, Ok, Err}};
|
||||
use core::result::Result::{self, Ok, Err};
|
||||
|
||||
fn div(x: i32, y: i32) -> Result<i32, String> {
|
||||
fn div(x: i32, y: i32) -> Result<i32, ()> {
|
||||
if y == 0 {
|
||||
return Err("div by zero".into());
|
||||
return Err(());
|
||||
}
|
||||
x / y<|>
|
||||
}
|
||||
|
||||
//- /std/lib.rs
|
||||
pub mod string {
|
||||
pub struct String { }
|
||||
}
|
||||
//- /core/lib.rs
|
||||
pub mod result {
|
||||
pub enum Result<T, E> { Ok(T), Err(E) }
|
||||
}
|
||||
"#;
|
||||
let after = r#"
|
||||
use std::{string::String, result::Result::{self, Ok, Err}};
|
||||
use core::result::Result::{self, Ok, Err};
|
||||
|
||||
fn div(x: i32, y: i32) -> Result<i32, String> {
|
||||
fn div(x: i32, y: i32) -> Result<i32, ()> {
|
||||
if y == 0 {
|
||||
return Err("div by zero".into());
|
||||
return Err(());
|
||||
}
|
||||
Ok(x / y)
|
||||
}
|
||||
|
|
@ -355,7 +353,7 @@ mod tests {
|
|||
fn test_wrap_return_type_handles_generic_functions() {
|
||||
let before = r#"
|
||||
//- /main.rs
|
||||
use std::result::Result::{self, Ok, Err};
|
||||
use core::result::Result::{self, Ok, Err};
|
||||
|
||||
fn div<T>(x: T) -> Result<T, i32> {
|
||||
if x == 0 {
|
||||
|
|
@ -364,13 +362,13 @@ mod tests {
|
|||
<|>x
|
||||
}
|
||||
|
||||
//- /std/lib.rs
|
||||
//- /core/lib.rs
|
||||
pub mod result {
|
||||
pub enum Result<T, E> { Ok(T), Err(E) }
|
||||
}
|
||||
"#;
|
||||
let after = r#"
|
||||
use std::result::Result::{self, Ok, Err};
|
||||
use core::result::Result::{self, Ok, Err};
|
||||
|
||||
fn div<T>(x: T) -> Result<T, i32> {
|
||||
if x == 0 {
|
||||
|
|
@ -386,32 +384,29 @@ mod tests {
|
|||
fn test_wrap_return_type_handles_type_aliases() {
|
||||
let before = r#"
|
||||
//- /main.rs
|
||||
use std::{string::String, result::Result::{self, Ok, Err}};
|
||||
use core::result::Result::{self, Ok, Err};
|
||||
|
||||
type MyResult<T> = Result<T, String>;
|
||||
type MyResult<T> = Result<T, ()>;
|
||||
|
||||
fn div(x: i32, y: i32) -> MyResult<i32> {
|
||||
if y == 0 {
|
||||
return Err("div by zero".into());
|
||||
return Err(());
|
||||
}
|
||||
x <|>/ y
|
||||
}
|
||||
|
||||
//- /std/lib.rs
|
||||
pub mod string {
|
||||
pub struct String { }
|
||||
}
|
||||
//- /core/lib.rs
|
||||
pub mod result {
|
||||
pub enum Result<T, E> { Ok(T), Err(E) }
|
||||
}
|
||||
"#;
|
||||
let after = r#"
|
||||
use std::{string::String, result::Result::{self, Ok, Err}};
|
||||
use core::result::Result::{self, Ok, Err};
|
||||
|
||||
type MyResult<T> = Result<T, String>;
|
||||
type MyResult<T> = Result<T, ()>;
|
||||
fn div(x: i32, y: i32) -> MyResult<i32> {
|
||||
if y == 0 {
|
||||
return Err("div by zero".into());
|
||||
return Err(());
|
||||
}
|
||||
Ok(x / y)
|
||||
}
|
||||
|
|
@ -423,16 +418,13 @@ mod tests {
|
|||
fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() {
|
||||
let content = r#"
|
||||
//- /main.rs
|
||||
use std::{string::String, result::Result::{self, Ok, Err}};
|
||||
use core::result::Result::{self, Ok, Err};
|
||||
|
||||
fn foo() -> Result<String, i32> {
|
||||
fn foo() -> Result<(), i32> {
|
||||
0<|>
|
||||
}
|
||||
|
||||
//- /std/lib.rs
|
||||
pub mod string {
|
||||
pub struct String { }
|
||||
}
|
||||
//- /core/lib.rs
|
||||
pub mod result {
|
||||
pub enum Result<T, E> { Ok(T), Err(E) }
|
||||
}
|
||||
|
|
@ -444,7 +436,7 @@ mod tests {
|
|||
fn test_wrap_return_type_not_applicable_when_return_type_is_not_result() {
|
||||
let content = r#"
|
||||
//- /main.rs
|
||||
use std::{string::String, result::Result::{self, Ok, Err}};
|
||||
use core::result::Result::{self, Ok, Err};
|
||||
|
||||
enum SomeOtherEnum {
|
||||
Ok(i32),
|
||||
|
|
@ -455,10 +447,7 @@ mod tests {
|
|||
0<|>
|
||||
}
|
||||
|
||||
//- /std/lib.rs
|
||||
pub mod string {
|
||||
pub struct String { }
|
||||
}
|
||||
//- /core/lib.rs
|
||||
pub mod result {
|
||||
pub enum Result<T, E> { Ok(T), Err(E) }
|
||||
}
|
||||
|
|
@ -731,6 +720,18 @@ mod tests {
|
|||
"#,
|
||||
check_struct_shorthand_initialization,
|
||||
);
|
||||
check_not_applicable(
|
||||
r#"
|
||||
struct A(usize);
|
||||
|
||||
fn main() {
|
||||
A {
|
||||
0: 0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
check_struct_shorthand_initialization,
|
||||
);
|
||||
|
||||
check_apply(
|
||||
r#"
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use std::{
|
|||
use hir::{Docs, Documentation, HasSource, HirDisplay};
|
||||
use ra_ide_db::RootDatabase;
|
||||
use ra_syntax::ast::{self, AstNode, NameOwner, VisibilityOwner};
|
||||
use stdx::SepBy;
|
||||
use stdx::{split1, SepBy};
|
||||
|
||||
use crate::display::{generic_parameters, where_predicates};
|
||||
|
||||
|
|
@ -207,7 +207,16 @@ impl From<&'_ ast::FnDef> for FunctionSignature {
|
|||
res.push(raw_param);
|
||||
}
|
||||
|
||||
res.extend(param_list.params().map(|param| param.syntax().text().to_string()));
|
||||
// macro-generated functions are missing whitespace
|
||||
fn fmt_param(param: ast::Param) -> String {
|
||||
let text = param.syntax().text().to_string();
|
||||
match split1(&text, ':') {
|
||||
Some((left, right)) => format!("{}: {}", left.trim(), right.trim()),
|
||||
_ => text,
|
||||
}
|
||||
}
|
||||
|
||||
res.extend(param_list.params().map(fmt_param));
|
||||
res_types.extend(param_list.params().map(|param| {
|
||||
let param_text = param.syntax().text().to_string();
|
||||
match param_text.split(':').nth(1).and_then(|it| it.get(1..)) {
|
||||
|
|
|
|||
|
|
@ -92,15 +92,16 @@ impl NavigationTarget {
|
|||
let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default();
|
||||
if let Some(src) = module.declaration_source(db) {
|
||||
let frange = original_range(db, src.as_ref().map(|it| it.syntax()));
|
||||
return NavigationTarget::from_syntax(
|
||||
let mut res = NavigationTarget::from_syntax(
|
||||
frange.file_id,
|
||||
name,
|
||||
None,
|
||||
frange.range,
|
||||
src.value.syntax().kind(),
|
||||
src.value.doc_comment_text(),
|
||||
src.value.short_label(),
|
||||
);
|
||||
res.docs = src.value.doc_comment_text();
|
||||
res.description = src.value.short_label();
|
||||
return res;
|
||||
}
|
||||
module.to_nav(db)
|
||||
}
|
||||
|
|
@ -130,11 +131,9 @@ impl NavigationTarget {
|
|||
}
|
||||
|
||||
/// Allows `NavigationTarget` to be created from a `NameOwner`
|
||||
fn from_named(
|
||||
pub(crate) fn from_named(
|
||||
db: &RootDatabase,
|
||||
node: InFile<&dyn ast::NameOwner>,
|
||||
docs: Option<String>,
|
||||
description: Option<String>,
|
||||
) -> NavigationTarget {
|
||||
//FIXME: use `_` instead of empty string
|
||||
let name = node.value.name().map(|it| it.text().clone()).unwrap_or_default();
|
||||
|
|
@ -148,8 +147,6 @@ impl NavigationTarget {
|
|||
focus_range,
|
||||
frange.range,
|
||||
node.value.syntax().kind(),
|
||||
docs,
|
||||
description,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -159,8 +156,6 @@ impl NavigationTarget {
|
|||
focus_range: Option<TextRange>,
|
||||
full_range: TextRange,
|
||||
kind: SyntaxKind,
|
||||
docs: Option<String>,
|
||||
description: Option<String>,
|
||||
) -> NavigationTarget {
|
||||
NavigationTarget {
|
||||
file_id,
|
||||
|
|
@ -169,8 +164,8 @@ impl NavigationTarget {
|
|||
full_range,
|
||||
focus_range,
|
||||
container_name: None,
|
||||
description,
|
||||
docs,
|
||||
description: None,
|
||||
docs: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -238,12 +233,11 @@ where
|
|||
{
|
||||
fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
|
||||
let src = self.source(db);
|
||||
NavigationTarget::from_named(
|
||||
db,
|
||||
src.as_ref().map(|it| it as &dyn ast::NameOwner),
|
||||
src.value.doc_comment_text(),
|
||||
src.value.short_label(),
|
||||
)
|
||||
let mut res =
|
||||
NavigationTarget::from_named(db, src.as_ref().map(|it| it as &dyn ast::NameOwner));
|
||||
res.docs = src.value.doc_comment_text();
|
||||
res.description = src.value.short_label();
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -258,15 +252,7 @@ impl ToNav for hir::Module {
|
|||
}
|
||||
};
|
||||
let frange = original_range(db, src.with_value(syntax));
|
||||
NavigationTarget::from_syntax(
|
||||
frange.file_id,
|
||||
name,
|
||||
focus,
|
||||
frange.range,
|
||||
syntax.kind(),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
NavigationTarget::from_syntax(frange.file_id, name, focus, frange.range, syntax.kind())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -285,8 +271,6 @@ impl ToNav for hir::ImplDef {
|
|||
None,
|
||||
frange.range,
|
||||
src.value.syntax().kind(),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -296,12 +280,12 @@ impl ToNav for hir::Field {
|
|||
let src = self.source(db);
|
||||
|
||||
match &src.value {
|
||||
FieldSource::Named(it) => NavigationTarget::from_named(
|
||||
db,
|
||||
src.with_value(it),
|
||||
it.doc_comment_text(),
|
||||
it.short_label(),
|
||||
),
|
||||
FieldSource::Named(it) => {
|
||||
let mut res = NavigationTarget::from_named(db, src.with_value(it));
|
||||
res.docs = it.doc_comment_text();
|
||||
res.description = it.short_label();
|
||||
res
|
||||
}
|
||||
FieldSource::Pos(it) => {
|
||||
let frange = original_range(db, src.with_value(it.syntax()));
|
||||
NavigationTarget::from_syntax(
|
||||
|
|
@ -310,8 +294,6 @@ impl ToNav for hir::Field {
|
|||
None,
|
||||
frange.range,
|
||||
it.syntax().kind(),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -322,12 +304,10 @@ impl ToNav for hir::MacroDef {
|
|||
fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
|
||||
let src = self.source(db);
|
||||
log::debug!("nav target {:#?}", src.value.syntax());
|
||||
NavigationTarget::from_named(
|
||||
db,
|
||||
src.as_ref().map(|it| it as &dyn ast::NameOwner),
|
||||
src.value.doc_comment_text(),
|
||||
None,
|
||||
)
|
||||
let mut res =
|
||||
NavigationTarget::from_named(db, src.as_ref().map(|it| it as &dyn ast::NameOwner));
|
||||
res.docs = src.value.doc_comment_text();
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use hir::Semantics;
|
||||
use ra_ide_db::{
|
||||
defs::{classify_name, classify_name_ref},
|
||||
defs::{classify_name, classify_name_ref, NameClass},
|
||||
symbol_index, RootDatabase,
|
||||
};
|
||||
use ra_syntax::{
|
||||
|
|
@ -39,7 +39,10 @@ pub(crate) fn goto_definition(
|
|||
reference_definition(&sema, &name_ref).to_vec()
|
||||
},
|
||||
ast::Name(name) => {
|
||||
let def = classify_name(&sema, &name)?.definition();
|
||||
let def = match classify_name(&sema, &name)? {
|
||||
NameClass::Definition(def) | NameClass::ConstReference(def) => def,
|
||||
NameClass::FieldShorthand { local: _, field } => field,
|
||||
};
|
||||
let nav = def.try_to_nav(sema.db)?;
|
||||
vec![nav]
|
||||
},
|
||||
|
|
@ -886,4 +889,23 @@ mod tests {
|
|||
"x",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_for_enum_variant_field() {
|
||||
check_goto(
|
||||
"
|
||||
//- /lib.rs
|
||||
enum Foo {
|
||||
Bar { x: i32 }
|
||||
}
|
||||
fn baz(foo: Foo) {
|
||||
match foo {
|
||||
Foo::Bar { x<|> } => x
|
||||
};
|
||||
}
|
||||
",
|
||||
"x RECORD_FIELD_DEF FileId(1) 21..27 21..22",
|
||||
"x: i32|x",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use std::iter::once;
|
||||
|
||||
use hir::{
|
||||
Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef,
|
||||
ModuleSource, Semantics,
|
||||
Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay,
|
||||
ModuleDef, ModuleSource, Semantics,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use ra_db::SourceDatabase;
|
||||
|
|
@ -10,22 +10,55 @@ use ra_ide_db::{
|
|||
defs::{classify_name, classify_name_ref, Definition},
|
||||
RootDatabase,
|
||||
};
|
||||
use ra_syntax::{
|
||||
ast::{self, DocCommentsOwner},
|
||||
match_ast, AstNode,
|
||||
SyntaxKind::*,
|
||||
SyntaxToken, TokenAtOffset,
|
||||
};
|
||||
use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset};
|
||||
|
||||
use crate::{
|
||||
display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel},
|
||||
FilePosition, RangeInfo,
|
||||
display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel, ToNav},
|
||||
runnables::runnable,
|
||||
FileId, FilePosition, NavigationTarget, RangeInfo, Runnable,
|
||||
};
|
||||
use test_utils::mark;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct HoverConfig {
|
||||
pub implementations: bool,
|
||||
pub run: bool,
|
||||
pub debug: bool,
|
||||
}
|
||||
|
||||
impl Default for HoverConfig {
|
||||
fn default() -> Self {
|
||||
Self { implementations: true, run: true, debug: true }
|
||||
}
|
||||
}
|
||||
|
||||
impl HoverConfig {
|
||||
pub const NO_ACTIONS: Self = Self { implementations: false, run: false, debug: false };
|
||||
|
||||
pub fn any(&self) -> bool {
|
||||
self.implementations || self.runnable()
|
||||
}
|
||||
|
||||
pub fn none(&self) -> bool {
|
||||
!self.any()
|
||||
}
|
||||
|
||||
pub fn runnable(&self) -> bool {
|
||||
self.run || self.debug
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum HoverAction {
|
||||
Runnable(Runnable),
|
||||
Implementaion(FilePosition),
|
||||
}
|
||||
|
||||
/// Contains the results when hovering over an item
|
||||
#[derive(Debug, Default)]
|
||||
pub struct HoverResult {
|
||||
results: Vec<String>,
|
||||
actions: Vec<HoverAction>,
|
||||
}
|
||||
|
||||
impl HoverResult {
|
||||
|
|
@ -53,10 +86,20 @@ impl HoverResult {
|
|||
&self.results
|
||||
}
|
||||
|
||||
pub fn actions(&self) -> &[HoverAction] {
|
||||
&self.actions
|
||||
}
|
||||
|
||||
pub fn push_action(&mut self, action: HoverAction) {
|
||||
self.actions.push(action);
|
||||
}
|
||||
|
||||
/// Returns the results converted into markup
|
||||
/// for displaying in a UI
|
||||
///
|
||||
/// Does not process actions!
|
||||
pub fn to_markup(&self) -> String {
|
||||
self.results.join("\n\n---\n")
|
||||
self.results.join("\n\n___\n")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -87,6 +130,14 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
|
|||
res.extend(hover_text_from_name_kind(db, name_kind));
|
||||
|
||||
if !res.is_empty() {
|
||||
if let Some(action) = show_implementations_action(db, name_kind) {
|
||||
res.push_action(action);
|
||||
}
|
||||
|
||||
if let Some(action) = runnable_action(&sema, name_kind, position.file_id) {
|
||||
res.push_action(action);
|
||||
}
|
||||
|
||||
return Some(RangeInfo::new(range, res));
|
||||
}
|
||||
}
|
||||
|
|
@ -117,6 +168,56 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
|
|||
Some(RangeInfo::new(range, res))
|
||||
}
|
||||
|
||||
fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
|
||||
fn to_action(nav_target: NavigationTarget) -> HoverAction {
|
||||
HoverAction::Implementaion(FilePosition {
|
||||
file_id: nav_target.file_id(),
|
||||
offset: nav_target.range().start(),
|
||||
})
|
||||
}
|
||||
|
||||
match def {
|
||||
Definition::ModuleDef(it) => match it {
|
||||
ModuleDef::Adt(Adt::Struct(it)) => Some(to_action(it.to_nav(db))),
|
||||
ModuleDef::Adt(Adt::Union(it)) => Some(to_action(it.to_nav(db))),
|
||||
ModuleDef::Adt(Adt::Enum(it)) => Some(to_action(it.to_nav(db))),
|
||||
ModuleDef::Trait(it) => Some(to_action(it.to_nav(db))),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn runnable_action(
|
||||
sema: &Semantics<RootDatabase>,
|
||||
def: Definition,
|
||||
file_id: FileId,
|
||||
) -> Option<HoverAction> {
|
||||
match def {
|
||||
Definition::ModuleDef(it) => match it {
|
||||
ModuleDef::Module(it) => match it.definition_source(sema.db).value {
|
||||
ModuleSource::Module(it) => runnable(&sema, it.syntax().clone(), file_id)
|
||||
.map(|it| HoverAction::Runnable(it)),
|
||||
_ => None,
|
||||
},
|
||||
ModuleDef::Function(it) => {
|
||||
let src = it.source(sema.db);
|
||||
if src.file_id != file_id.into() {
|
||||
mark::hit!(hover_macro_generated_struct_fn_doc_comment);
|
||||
mark::hit!(hover_macro_generated_struct_fn_doc_attr);
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
runnable(&sema, src.value.syntax().clone(), file_id)
|
||||
.map(|it| HoverAction::Runnable(it))
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn hover_text(
|
||||
docs: Option<String>,
|
||||
desc: Option<String>,
|
||||
|
|
@ -169,13 +270,15 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin
|
|||
return match def {
|
||||
Definition::Macro(it) => {
|
||||
let src = it.source(db);
|
||||
hover_text(src.value.doc_comment_text(), Some(macro_label(&src.value)), mod_path)
|
||||
let docs = Documentation::from_ast(&src.value).map(Into::into);
|
||||
hover_text(docs, Some(macro_label(&src.value)), mod_path)
|
||||
}
|
||||
Definition::Field(it) => {
|
||||
let src = it.source(db);
|
||||
match src.value {
|
||||
FieldSource::Named(it) => {
|
||||
hover_text(it.doc_comment_text(), it.short_label(), mod_path)
|
||||
let docs = Documentation::from_ast(&it).map(Into::into);
|
||||
hover_text(docs, it.short_label(), mod_path)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
|
|
@ -183,7 +286,8 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin
|
|||
Definition::ModuleDef(it) => match it {
|
||||
ModuleDef::Module(it) => match it.definition_source(db).value {
|
||||
ModuleSource::Module(it) => {
|
||||
hover_text(it.doc_comment_text(), it.short_label(), mod_path)
|
||||
let docs = Documentation::from_ast(&it).map(Into::into);
|
||||
hover_text(docs, it.short_label(), mod_path)
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
|
|
@ -208,10 +312,11 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin
|
|||
fn from_def_source<A, D>(db: &RootDatabase, def: D, mod_path: Option<String>) -> Option<String>
|
||||
where
|
||||
D: HasSource<Ast = A>,
|
||||
A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel,
|
||||
A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel + ast::AttrsOwner,
|
||||
{
|
||||
let src = def.source(db);
|
||||
hover_text(src.value.doc_comment_text(), src.value.short_label(), mod_path)
|
||||
let docs = Documentation::from_ast(&src.value).map(Into::into);
|
||||
hover_text(docs, src.value.short_label(), mod_path)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -229,6 +334,9 @@ fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use insta::assert_debug_snapshot;
|
||||
|
||||
use ra_db::FileLoader;
|
||||
use ra_syntax::TextRange;
|
||||
|
||||
|
|
@ -242,7 +350,15 @@ mod tests {
|
|||
s.map(trim_markup)
|
||||
}
|
||||
|
||||
fn check_hover_result(fixture: &str, expected: &[&str]) -> String {
|
||||
fn assert_impl_action(action: &HoverAction, position: u32) {
|
||||
let offset = match action {
|
||||
HoverAction::Implementaion(pos) => pos.offset,
|
||||
it => panic!("Unexpected hover action: {:#?}", it),
|
||||
};
|
||||
assert_eq!(offset, position.into());
|
||||
}
|
||||
|
||||
fn check_hover_result(fixture: &str, expected: &[&str]) -> (String, Vec<HoverAction>) {
|
||||
let (analysis, position) = analysis_and_position(fixture);
|
||||
let hover = analysis.hover(position).unwrap().unwrap();
|
||||
let mut results = Vec::from(hover.info.results());
|
||||
|
|
@ -257,7 +373,7 @@ mod tests {
|
|||
assert_eq!(hover.info.len(), expected.len());
|
||||
|
||||
let content = analysis.db.file_text(position.file_id);
|
||||
content[hover.range].to_string()
|
||||
(content[hover.range].to_string(), hover.info.actions().to_vec())
|
||||
}
|
||||
|
||||
fn check_hover_no_result(fixture: &str) {
|
||||
|
|
@ -458,7 +574,7 @@ struct Test<K, T = u8> {
|
|||
}
|
||||
|
||||
fn main() {
|
||||
let zz<|> = Test { t: 23, k: 33 };
|
||||
let zz<|> = Test { t: 23u8, k: 33 };
|
||||
}"#,
|
||||
&["Test<i32, u8>"],
|
||||
);
|
||||
|
|
@ -747,7 +863,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
|
|||
|
||||
#[test]
|
||||
fn test_hover_through_macro() {
|
||||
let hover_on = check_hover_result(
|
||||
let (hover_on, _) = check_hover_result(
|
||||
"
|
||||
//- /lib.rs
|
||||
macro_rules! id {
|
||||
|
|
@ -768,7 +884,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
|
|||
|
||||
#[test]
|
||||
fn test_hover_through_expr_in_macro() {
|
||||
let hover_on = check_hover_result(
|
||||
let (hover_on, _) = check_hover_result(
|
||||
"
|
||||
//- /lib.rs
|
||||
macro_rules! id {
|
||||
|
|
@ -786,7 +902,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
|
|||
|
||||
#[test]
|
||||
fn test_hover_through_expr_in_macro_recursive() {
|
||||
let hover_on = check_hover_result(
|
||||
let (hover_on, _) = check_hover_result(
|
||||
"
|
||||
//- /lib.rs
|
||||
macro_rules! id_deep {
|
||||
|
|
@ -807,7 +923,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
|
|||
|
||||
#[test]
|
||||
fn test_hover_through_func_in_macro_recursive() {
|
||||
let hover_on = check_hover_result(
|
||||
let (hover_on, _) = check_hover_result(
|
||||
"
|
||||
//- /lib.rs
|
||||
macro_rules! id_deep {
|
||||
|
|
@ -831,7 +947,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
|
|||
|
||||
#[test]
|
||||
fn test_hover_through_literal_string_in_macro() {
|
||||
let hover_on = check_hover_result(
|
||||
let (hover_on, _) = check_hover_result(
|
||||
r#"
|
||||
//- /lib.rs
|
||||
macro_rules! arr {
|
||||
|
|
@ -850,7 +966,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
|
|||
|
||||
#[test]
|
||||
fn test_hover_through_assert_macro() {
|
||||
let hover_on = check_hover_result(
|
||||
let (hover_on, _) = check_hover_result(
|
||||
r#"
|
||||
//- /lib.rs
|
||||
#[rustc_builtin_macro]
|
||||
|
|
@ -926,13 +1042,14 @@ fn func(foo: i32) { if true { <|>foo; }; }
|
|||
|
||||
#[test]
|
||||
fn test_hover_trait_show_qualifiers() {
|
||||
check_hover_result(
|
||||
let (_, actions) = check_hover_result(
|
||||
"
|
||||
//- /lib.rs
|
||||
unsafe trait foo<|>() {}
|
||||
",
|
||||
&["unsafe trait foo"],
|
||||
);
|
||||
assert_impl_action(&actions[0], 13);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -951,4 +1068,246 @@ fn func(foo: i32) { if true { <|>foo; }; }
|
|||
&["mod my"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hover_struct_doc_comment() {
|
||||
check_hover_result(
|
||||
r#"
|
||||
//- /lib.rs
|
||||
/// bar docs
|
||||
struct Bar;
|
||||
|
||||
fn foo() {
|
||||
let bar = Ba<|>r;
|
||||
}
|
||||
"#,
|
||||
&["struct Bar\n```\n___\n\nbar docs"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hover_struct_doc_attr() {
|
||||
check_hover_result(
|
||||
r#"
|
||||
//- /lib.rs
|
||||
#[doc = "bar docs"]
|
||||
struct Bar;
|
||||
|
||||
fn foo() {
|
||||
let bar = Ba<|>r;
|
||||
}
|
||||
"#,
|
||||
&["struct Bar\n```\n___\n\nbar docs"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hover_struct_doc_attr_multiple_and_mixed() {
|
||||
check_hover_result(
|
||||
r#"
|
||||
//- /lib.rs
|
||||
/// bar docs 0
|
||||
#[doc = "bar docs 1"]
|
||||
#[doc = "bar docs 2"]
|
||||
struct Bar;
|
||||
|
||||
fn foo() {
|
||||
let bar = Ba<|>r;
|
||||
}
|
||||
"#,
|
||||
&["struct Bar\n```\n___\n\nbar docs 0\n\nbar docs 1\n\nbar docs 2"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hover_macro_generated_struct_fn_doc_comment() {
|
||||
mark::check!(hover_macro_generated_struct_fn_doc_comment);
|
||||
|
||||
check_hover_result(
|
||||
r#"
|
||||
//- /lib.rs
|
||||
macro_rules! bar {
|
||||
() => {
|
||||
struct Bar;
|
||||
impl Bar {
|
||||
/// Do the foo
|
||||
fn foo(&self) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bar!();
|
||||
|
||||
fn foo() {
|
||||
let bar = Bar;
|
||||
bar.fo<|>o();
|
||||
}
|
||||
"#,
|
||||
&["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\n Do the foo"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hover_macro_generated_struct_fn_doc_attr() {
|
||||
mark::check!(hover_macro_generated_struct_fn_doc_attr);
|
||||
|
||||
check_hover_result(
|
||||
r#"
|
||||
//- /lib.rs
|
||||
macro_rules! bar {
|
||||
() => {
|
||||
struct Bar;
|
||||
impl Bar {
|
||||
#[doc = "Do the foo"]
|
||||
fn foo(&self) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bar!();
|
||||
|
||||
fn foo() {
|
||||
let bar = Bar;
|
||||
bar.fo<|>o();
|
||||
}
|
||||
"#,
|
||||
&["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\nDo the foo"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hover_trait_has_impl_action() {
|
||||
let (_, actions) = check_hover_result(
|
||||
"
|
||||
//- /lib.rs
|
||||
trait foo<|>() {}
|
||||
",
|
||||
&["trait foo"],
|
||||
);
|
||||
assert_impl_action(&actions[0], 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hover_struct_has_impl_action() {
|
||||
let (_, actions) = check_hover_result(
|
||||
"
|
||||
//- /lib.rs
|
||||
struct foo<|>() {}
|
||||
",
|
||||
&["struct foo"],
|
||||
);
|
||||
assert_impl_action(&actions[0], 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hover_union_has_impl_action() {
|
||||
let (_, actions) = check_hover_result(
|
||||
"
|
||||
//- /lib.rs
|
||||
union foo<|>() {}
|
||||
",
|
||||
&["union foo"],
|
||||
);
|
||||
assert_impl_action(&actions[0], 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hover_enum_has_impl_action() {
|
||||
let (_, actions) = check_hover_result(
|
||||
"
|
||||
//- /lib.rs
|
||||
enum foo<|>() {
|
||||
A,
|
||||
B
|
||||
}
|
||||
",
|
||||
&["enum foo"],
|
||||
);
|
||||
assert_impl_action(&actions[0], 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hover_test_has_action() {
|
||||
let (_, actions) = check_hover_result(
|
||||
"
|
||||
//- /lib.rs
|
||||
#[test]
|
||||
fn foo_<|>test() {}
|
||||
",
|
||||
&["fn foo_test()"],
|
||||
);
|
||||
assert_debug_snapshot!(actions,
|
||||
@r###"
|
||||
[
|
||||
Runnable(
|
||||
Runnable {
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 0..24,
|
||||
name: "foo_test",
|
||||
kind: FN_DEF,
|
||||
focus_range: Some(
|
||||
11..19,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: Test {
|
||||
test_id: Path(
|
||||
"foo_test",
|
||||
),
|
||||
attr: TestAttr {
|
||||
ignore: false,
|
||||
},
|
||||
},
|
||||
cfg_exprs: [],
|
||||
},
|
||||
),
|
||||
]
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hover_test_mod_has_action() {
|
||||
let (_, actions) = check_hover_result(
|
||||
"
|
||||
//- /lib.rs
|
||||
mod tests<|> {
|
||||
#[test]
|
||||
fn foo_test() {}
|
||||
}
|
||||
",
|
||||
&["mod tests"],
|
||||
);
|
||||
assert_debug_snapshot!(actions,
|
||||
@r###"
|
||||
[
|
||||
Runnable(
|
||||
Runnable {
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 0..46,
|
||||
name: "tests",
|
||||
kind: MODULE,
|
||||
focus_range: Some(
|
||||
4..9,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: TestMod {
|
||||
path: "tests",
|
||||
},
|
||||
cfg_exprs: [],
|
||||
},
|
||||
),
|
||||
]
|
||||
"###);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -149,11 +149,10 @@ fn get_param_name_hints(
|
|||
ast::Expr::MethodCallExpr(expr) => expr.arg_list()?.args(),
|
||||
_ => return None,
|
||||
};
|
||||
let args_count = args.clone().count();
|
||||
|
||||
let fn_signature = get_fn_signature(sema, &expr)?;
|
||||
let n_params_to_skip =
|
||||
if fn_signature.has_self_param && fn_signature.parameter_names.len() > args_count {
|
||||
if fn_signature.has_self_param && matches!(&expr, ast::Expr::MethodCallExpr(_)) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
|
|
@ -416,7 +415,7 @@ struct Test<K, T = u8> {
|
|||
}
|
||||
|
||||
fn main() {
|
||||
let zz = Test { t: 23, k: 33 };
|
||||
let zz = Test { t: 23u8, k: 33 };
|
||||
let zz_ref = &zz;
|
||||
}"#,
|
||||
);
|
||||
|
|
@ -429,7 +428,7 @@ fn main() {
|
|||
label: "Test<i32>",
|
||||
},
|
||||
InlayHint {
|
||||
range: 105..111,
|
||||
range: 107..113,
|
||||
kind: TypeHint,
|
||||
label: "&Test<i32>",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ pub use crate::{
|
|||
display::{file_structure, FunctionSignature, NavigationTarget, StructureNode},
|
||||
expand_macro::ExpandedMacro,
|
||||
folding_ranges::{Fold, FoldKind},
|
||||
hover::HoverResult,
|
||||
hover::{HoverAction, HoverConfig, HoverResult},
|
||||
inlay_hints::{InlayHint, InlayHintsConfig, InlayKind},
|
||||
references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult},
|
||||
runnables::{Runnable, RunnableKind, TestId},
|
||||
|
|
@ -77,7 +77,7 @@ pub use crate::{
|
|||
};
|
||||
|
||||
pub use hir::Documentation;
|
||||
pub use ra_assists::{AssistConfig, AssistId};
|
||||
pub use ra_assists::{Assist, AssistConfig, AssistId, ResolvedAssist};
|
||||
pub use ra_db::{
|
||||
Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId,
|
||||
};
|
||||
|
|
@ -142,14 +142,6 @@ pub struct AnalysisHost {
|
|||
db: RootDatabase,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Assist {
|
||||
pub id: AssistId,
|
||||
pub label: String,
|
||||
pub group_label: Option<String>,
|
||||
pub source_change: SourceChange,
|
||||
}
|
||||
|
||||
impl AnalysisHost {
|
||||
pub fn new(lru_capacity: Option<usize>) -> AnalysisHost {
|
||||
AnalysisHost { db: RootDatabase::new(lru_capacity) }
|
||||
|
|
@ -470,20 +462,23 @@ impl Analysis {
|
|||
self.with_db(|db| completion::completions(db, config, position).map(Into::into))
|
||||
}
|
||||
|
||||
/// Computes assists (aka code actions aka intentions) for the given
|
||||
/// Computes resolved assists with source changes for the given position.
|
||||
pub fn resolved_assists(
|
||||
&self,
|
||||
config: &AssistConfig,
|
||||
frange: FileRange,
|
||||
) -> Cancelable<Vec<ResolvedAssist>> {
|
||||
self.with_db(|db| ra_assists::Assist::resolved(db, config, frange))
|
||||
}
|
||||
|
||||
/// Computes unresolved assists (aka code actions aka intentions) for the given
|
||||
/// position.
|
||||
pub fn assists(&self, config: &AssistConfig, frange: FileRange) -> Cancelable<Vec<Assist>> {
|
||||
self.with_db(|db| {
|
||||
ra_assists::Assist::resolved(db, config, frange)
|
||||
.into_iter()
|
||||
.map(|assist| Assist {
|
||||
id: assist.assist.id,
|
||||
label: assist.assist.label,
|
||||
group_label: assist.assist.group.map(|it| it.0),
|
||||
source_change: assist.source_change,
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
pub fn unresolved_assists(
|
||||
&self,
|
||||
config: &AssistConfig,
|
||||
frange: FileRange,
|
||||
) -> Cancelable<Vec<Assist>> {
|
||||
self.with_db(|db| Assist::unresolved(db, config, frange))
|
||||
}
|
||||
|
||||
/// Computes the set of diagnostics for the given file.
|
||||
|
|
@ -508,7 +503,7 @@ impl Analysis {
|
|||
) -> Cancelable<Result<SourceChange, SsrError>> {
|
||||
self.with_db(|db| {
|
||||
let edits = ssr::parse_search_replace(query, parse_only, db)?;
|
||||
Ok(SourceChange::source_file_edits(edits))
|
||||
Ok(SourceChange::from(edits))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo
|
|||
),
|
||||
});
|
||||
|
||||
Some(RangeInfo::new(range, SourceChange::source_file_edits(edits)))
|
||||
Some(RangeInfo::new(range, SourceChange::from(edits)))
|
||||
}
|
||||
|
||||
fn text_edit_from_self_param(
|
||||
|
|
@ -234,7 +234,7 @@ fn rename_self_to_param(
|
|||
let range = ast::SelfParam::cast(self_token.parent())
|
||||
.map_or(self_token.text_range(), |p| p.syntax().text_range());
|
||||
|
||||
Some(RangeInfo::new(range, SourceChange::source_file_edits(edits)))
|
||||
Some(RangeInfo::new(range, SourceChange::from(edits)))
|
||||
}
|
||||
|
||||
fn rename_reference(
|
||||
|
|
@ -253,7 +253,7 @@ fn rename_reference(
|
|||
return None;
|
||||
}
|
||||
|
||||
Some(RangeInfo::new(range, SourceChange::source_file_edits(edit)))
|
||||
Some(RangeInfo::new(range, SourceChange::from(edit)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -1,31 +1,31 @@
|
|||
use std::fmt;
|
||||
|
||||
use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics};
|
||||
use itertools::Itertools;
|
||||
use ra_cfg::CfgExpr;
|
||||
use ra_ide_db::RootDatabase;
|
||||
use ra_syntax::{
|
||||
ast::{self, AstNode, AttrsOwner, ModuleItemOwner, NameOwner},
|
||||
match_ast, SyntaxNode, TextRange,
|
||||
ast::{self, AstNode, AttrsOwner, DocCommentsOwner, ModuleItemOwner, NameOwner},
|
||||
match_ast, SyntaxNode,
|
||||
};
|
||||
|
||||
use crate::FileId;
|
||||
use ast::DocCommentsOwner;
|
||||
use ra_cfg::CfgExpr;
|
||||
use std::fmt::Display;
|
||||
use crate::{display::ToNav, FileId, NavigationTarget};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Runnable {
|
||||
pub range: TextRange,
|
||||
pub nav: NavigationTarget,
|
||||
pub kind: RunnableKind,
|
||||
pub cfg_exprs: Vec<CfgExpr>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TestId {
|
||||
Name(String),
|
||||
Path(String),
|
||||
}
|
||||
|
||||
impl Display for TestId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
impl fmt::Display for TestId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
TestId::Name(name) => write!(f, "{}", name),
|
||||
TestId::Path(path) => write!(f, "{}", path),
|
||||
|
|
@ -33,7 +33,7 @@ impl Display for TestId {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum RunnableKind {
|
||||
Test { test_id: TestId, attr: TestAttr },
|
||||
TestMod { path: String },
|
||||
|
|
@ -42,6 +42,42 @@ pub enum RunnableKind {
|
|||
Bin,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct RunnableAction {
|
||||
pub run_title: &'static str,
|
||||
pub debugee: bool,
|
||||
}
|
||||
|
||||
const TEST: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run Test", debugee: true };
|
||||
const DOCTEST: RunnableAction =
|
||||
RunnableAction { run_title: "▶\u{fe0e} Run Doctest", debugee: false };
|
||||
const BENCH: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run Bench", debugee: true };
|
||||
const BIN: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run", debugee: true };
|
||||
|
||||
impl Runnable {
|
||||
// test package::module::testname
|
||||
pub fn label(&self, target: Option<String>) -> String {
|
||||
match &self.kind {
|
||||
RunnableKind::Test { test_id, .. } => format!("test {}", test_id),
|
||||
RunnableKind::TestMod { path } => format!("test-mod {}", path),
|
||||
RunnableKind::Bench { test_id } => format!("bench {}", test_id),
|
||||
RunnableKind::DocTest { test_id, .. } => format!("doctest {}", test_id),
|
||||
RunnableKind::Bin => {
|
||||
target.map_or_else(|| "run binary".to_string(), |t| format!("run {}", t))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn action(&self) -> &'static RunnableAction {
|
||||
match &self.kind {
|
||||
RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => &TEST,
|
||||
RunnableKind::DocTest { .. } => &DOCTEST,
|
||||
RunnableKind::Bench { .. } => &BENCH,
|
||||
RunnableKind::Bin => &BIN,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Feature: Run
|
||||
//
|
||||
// Shows a popup suggesting to run a test/benchmark/binary **at the current cursor
|
||||
|
|
@ -59,7 +95,11 @@ pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
|
|||
source_file.syntax().descendants().filter_map(|i| runnable(&sema, i, file_id)).collect()
|
||||
}
|
||||
|
||||
fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode, file_id: FileId) -> Option<Runnable> {
|
||||
pub(crate) fn runnable(
|
||||
sema: &Semantics<RootDatabase>,
|
||||
item: SyntaxNode,
|
||||
file_id: FileId,
|
||||
) -> Option<Runnable> {
|
||||
match_ast! {
|
||||
match item {
|
||||
ast::FnDef(it) => runnable_fn(sema, it, file_id),
|
||||
|
|
@ -131,10 +171,11 @@ fn runnable_fn(
|
|||
let cfg_exprs =
|
||||
attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect();
|
||||
|
||||
Some(Runnable { range: fn_def.syntax().text_range(), kind, cfg_exprs })
|
||||
let nav = NavigationTarget::from_named(sema.db, InFile::new(file_id.into(), &fn_def));
|
||||
Some(Runnable { nav, kind, cfg_exprs })
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct TestAttr {
|
||||
pub ignore: bool,
|
||||
}
|
||||
|
|
@ -183,7 +224,6 @@ fn runnable_mod(
|
|||
if !has_test_function {
|
||||
return None;
|
||||
}
|
||||
let range = module.syntax().text_range();
|
||||
let module_def = sema.to_def(&module)?;
|
||||
|
||||
let path = module_def
|
||||
|
|
@ -197,7 +237,8 @@ fn runnable_mod(
|
|||
let cfg_exprs =
|
||||
attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect();
|
||||
|
||||
Some(Runnable { range, kind: RunnableKind::TestMod { path }, cfg_exprs })
|
||||
let nav = module_def.to_nav(sema.db);
|
||||
Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg_exprs })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -206,6 +247,15 @@ mod tests {
|
|||
|
||||
use crate::mock_analysis::analysis_and_position;
|
||||
|
||||
use super::{Runnable, RunnableAction, BENCH, BIN, DOCTEST, TEST};
|
||||
|
||||
fn assert_actions(runnables: &[Runnable], actions: &[&RunnableAction]) {
|
||||
assert_eq!(
|
||||
actions,
|
||||
runnables.into_iter().map(|it| it.action()).collect::<Vec<_>>().as_slice()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_runnables() {
|
||||
let (analysis, pos) = analysis_and_position(
|
||||
|
|
@ -220,6 +270,9 @@ mod tests {
|
|||
#[test]
|
||||
#[ignore]
|
||||
fn test_foo() {}
|
||||
|
||||
#[bench]
|
||||
fn bench() {}
|
||||
"#,
|
||||
);
|
||||
let runnables = analysis.runnables(pos.file_id).unwrap();
|
||||
|
|
@ -227,12 +280,38 @@ mod tests {
|
|||
@r###"
|
||||
[
|
||||
Runnable {
|
||||
range: 1..21,
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 1..21,
|
||||
name: "main",
|
||||
kind: FN_DEF,
|
||||
focus_range: Some(
|
||||
12..16,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: Bin,
|
||||
cfg_exprs: [],
|
||||
},
|
||||
Runnable {
|
||||
range: 22..46,
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 22..46,
|
||||
name: "test_foo",
|
||||
kind: FN_DEF,
|
||||
focus_range: Some(
|
||||
33..41,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: Test {
|
||||
test_id: Path(
|
||||
"test_foo",
|
||||
|
|
@ -244,7 +323,20 @@ mod tests {
|
|||
cfg_exprs: [],
|
||||
},
|
||||
Runnable {
|
||||
range: 47..81,
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 47..81,
|
||||
name: "test_foo",
|
||||
kind: FN_DEF,
|
||||
focus_range: Some(
|
||||
68..76,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: Test {
|
||||
test_id: Path(
|
||||
"test_foo",
|
||||
|
|
@ -255,9 +347,32 @@ mod tests {
|
|||
},
|
||||
cfg_exprs: [],
|
||||
},
|
||||
Runnable {
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 82..104,
|
||||
name: "bench",
|
||||
kind: FN_DEF,
|
||||
focus_range: Some(
|
||||
94..99,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: Bench {
|
||||
test_id: Path(
|
||||
"bench",
|
||||
),
|
||||
},
|
||||
cfg_exprs: [],
|
||||
},
|
||||
]
|
||||
"###
|
||||
);
|
||||
assert_actions(&runnables, &[&BIN, &TEST, &TEST, &BENCH]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -279,12 +394,38 @@ mod tests {
|
|||
@r###"
|
||||
[
|
||||
Runnable {
|
||||
range: 1..21,
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 1..21,
|
||||
name: "main",
|
||||
kind: FN_DEF,
|
||||
focus_range: Some(
|
||||
12..16,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: Bin,
|
||||
cfg_exprs: [],
|
||||
},
|
||||
Runnable {
|
||||
range: 22..64,
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 22..64,
|
||||
name: "foo",
|
||||
kind: FN_DEF,
|
||||
focus_range: Some(
|
||||
56..59,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: DocTest {
|
||||
test_id: Path(
|
||||
"foo",
|
||||
|
|
@ -295,6 +436,7 @@ mod tests {
|
|||
]
|
||||
"###
|
||||
);
|
||||
assert_actions(&runnables, &[&BIN, &DOCTEST]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -319,12 +461,38 @@ mod tests {
|
|||
@r###"
|
||||
[
|
||||
Runnable {
|
||||
range: 1..21,
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 1..21,
|
||||
name: "main",
|
||||
kind: FN_DEF,
|
||||
focus_range: Some(
|
||||
12..16,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: Bin,
|
||||
cfg_exprs: [],
|
||||
},
|
||||
Runnable {
|
||||
range: 51..105,
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 51..105,
|
||||
name: "foo",
|
||||
kind: FN_DEF,
|
||||
focus_range: Some(
|
||||
97..100,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: DocTest {
|
||||
test_id: Path(
|
||||
"Data::foo",
|
||||
|
|
@ -335,6 +503,7 @@ mod tests {
|
|||
]
|
||||
"###
|
||||
);
|
||||
assert_actions(&runnables, &[&BIN, &DOCTEST]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -354,14 +523,40 @@ mod tests {
|
|||
@r###"
|
||||
[
|
||||
Runnable {
|
||||
range: 1..59,
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 1..59,
|
||||
name: "test_mod",
|
||||
kind: MODULE,
|
||||
focus_range: Some(
|
||||
13..21,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: TestMod {
|
||||
path: "test_mod",
|
||||
},
|
||||
cfg_exprs: [],
|
||||
},
|
||||
Runnable {
|
||||
range: 28..57,
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 28..57,
|
||||
name: "test_foo1",
|
||||
kind: FN_DEF,
|
||||
focus_range: Some(
|
||||
43..52,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: Test {
|
||||
test_id: Path(
|
||||
"test_mod::test_foo1",
|
||||
|
|
@ -375,6 +570,7 @@ mod tests {
|
|||
]
|
||||
"###
|
||||
);
|
||||
assert_actions(&runnables, &[&TEST, &TEST]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -396,14 +592,40 @@ mod tests {
|
|||
@r###"
|
||||
[
|
||||
Runnable {
|
||||
range: 23..85,
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 23..85,
|
||||
name: "test_mod",
|
||||
kind: MODULE,
|
||||
focus_range: Some(
|
||||
27..35,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: TestMod {
|
||||
path: "foo::test_mod",
|
||||
},
|
||||
cfg_exprs: [],
|
||||
},
|
||||
Runnable {
|
||||
range: 46..79,
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 46..79,
|
||||
name: "test_foo1",
|
||||
kind: FN_DEF,
|
||||
focus_range: Some(
|
||||
65..74,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: Test {
|
||||
test_id: Path(
|
||||
"foo::test_mod::test_foo1",
|
||||
|
|
@ -417,6 +639,7 @@ mod tests {
|
|||
]
|
||||
"###
|
||||
);
|
||||
assert_actions(&runnables, &[&TEST, &TEST]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -440,14 +663,40 @@ mod tests {
|
|||
@r###"
|
||||
[
|
||||
Runnable {
|
||||
range: 41..115,
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 41..115,
|
||||
name: "test_mod",
|
||||
kind: MODULE,
|
||||
focus_range: Some(
|
||||
45..53,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: TestMod {
|
||||
path: "foo::bar::test_mod",
|
||||
},
|
||||
cfg_exprs: [],
|
||||
},
|
||||
Runnable {
|
||||
range: 68..105,
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 68..105,
|
||||
name: "test_foo1",
|
||||
kind: FN_DEF,
|
||||
focus_range: Some(
|
||||
91..100,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: Test {
|
||||
test_id: Path(
|
||||
"foo::bar::test_mod::test_foo1",
|
||||
|
|
@ -461,6 +710,7 @@ mod tests {
|
|||
]
|
||||
"###
|
||||
);
|
||||
assert_actions(&runnables, &[&TEST, &TEST]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -479,7 +729,20 @@ mod tests {
|
|||
@r###"
|
||||
[
|
||||
Runnable {
|
||||
range: 1..58,
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 1..58,
|
||||
name: "test_foo1",
|
||||
kind: FN_DEF,
|
||||
focus_range: Some(
|
||||
44..53,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: Test {
|
||||
test_id: Path(
|
||||
"test_foo1",
|
||||
|
|
@ -498,6 +761,7 @@ mod tests {
|
|||
]
|
||||
"###
|
||||
);
|
||||
assert_actions(&runnables, &[&TEST]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -516,7 +780,20 @@ mod tests {
|
|||
@r###"
|
||||
[
|
||||
Runnable {
|
||||
range: 1..80,
|
||||
nav: NavigationTarget {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
full_range: 1..80,
|
||||
name: "test_foo1",
|
||||
kind: FN_DEF,
|
||||
focus_range: Some(
|
||||
66..75,
|
||||
),
|
||||
container_name: None,
|
||||
description: None,
|
||||
docs: None,
|
||||
},
|
||||
kind: Test {
|
||||
test_id: Path(
|
||||
"test_foo1",
|
||||
|
|
@ -543,6 +820,7 @@ mod tests {
|
|||
]
|
||||
"###
|
||||
);
|
||||
assert_actions(&runnables, &[&TEST]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
71
crates/ra_ide/src/snapshots/highlight_doctest.html
Normal file
71
crates/ra_ide/src/snapshots/highlight_doctest.html
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
|
||||
<style>
|
||||
body { margin: 0; }
|
||||
pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
|
||||
|
||||
.lifetime { color: #DFAF8F; font-style: italic; }
|
||||
.comment { color: #7F9F7F; }
|
||||
.struct, .enum { color: #7CB8BB; }
|
||||
.enum_variant { color: #BDE0F3; }
|
||||
.string_literal { color: #CC9393; }
|
||||
.field { color: #94BFF3; }
|
||||
.function { color: #93E0E3; }
|
||||
.function.unsafe { color: #BC8383; }
|
||||
.operator.unsafe { color: #BC8383; }
|
||||
.parameter { color: #94BFF3; }
|
||||
.text { color: #DCDCCC; }
|
||||
.type { color: #7CB8BB; }
|
||||
.builtin_type { color: #8CD0D3; }
|
||||
.type_param { color: #DFAF8F; }
|
||||
.attribute { color: #94BFF3; }
|
||||
.numeric_literal { color: #BFEBBF; }
|
||||
.bool_literal { color: #BFE6EB; }
|
||||
.macro { color: #94BFF3; }
|
||||
.module { color: #AFD8AF; }
|
||||
.variable { color: #DCDCCC; }
|
||||
.format_specifier { color: #CC696B; }
|
||||
.mutable { text-decoration: underline; }
|
||||
|
||||
.keyword { color: #F0DFAF; font-weight: bold; }
|
||||
.keyword.unsafe { color: #BC8383; font-weight: bold; }
|
||||
.control { font-style: italic; }
|
||||
</style>
|
||||
<pre><code><span class="keyword">impl</span> <span class="unresolved_reference">Foo</span> {
|
||||
<span class="comment">/// Constructs a new `Foo`.</span>
|
||||
<span class="comment">///</span>
|
||||
<span class="comment">/// # Examples</span>
|
||||
<span class="comment">///</span>
|
||||
<span class="comment">/// ```</span>
|
||||
<span class="comment">/// #</span> <span class="attribute">#![</span><span class="function attribute">allow</span><span class="attribute">(unused_mut)]</span>
|
||||
<span class="comment">/// </span><span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">foo</span>: <span class="unresolved_reference">Foo</span> = <span class="unresolved_reference">Foo</span>::<span class="unresolved_reference">new</span>();
|
||||
<span class="comment">/// ```</span>
|
||||
<span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration">new</span>() -> <span class="unresolved_reference">Foo</span> {
|
||||
<span class="unresolved_reference">Foo</span> { }
|
||||
}
|
||||
|
||||
<span class="comment">/// `bar` method on `Foo`.</span>
|
||||
<span class="comment">///</span>
|
||||
<span class="comment">/// # Examples</span>
|
||||
<span class="comment">///</span>
|
||||
<span class="comment">/// ```</span>
|
||||
<span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">foo</span> = <span class="unresolved_reference">Foo</span>::<span class="unresolved_reference">new</span>();
|
||||
<span class="comment">///</span>
|
||||
<span class="comment">/// </span><span class="comment">// calls bar on foo</span>
|
||||
<span class="comment">/// </span><span class="macro">assert!</span>(foo.bar());
|
||||
<span class="comment">///</span>
|
||||
<span class="comment">/// </span><span class="comment">/* multi-line
|
||||
</span><span class="comment">/// </span><span class="comment"> comment */</span>
|
||||
<span class="comment">///</span>
|
||||
<span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">multi_line_string</span> = <span class="string_literal">"Foo
|
||||
</span><span class="comment">/// </span><span class="string_literal"> bar
|
||||
</span><span class="comment">/// </span><span class="string_literal"> "</span>;
|
||||
<span class="comment">///</span>
|
||||
<span class="comment">/// ```</span>
|
||||
<span class="comment">///</span>
|
||||
<span class="comment">/// ```</span>
|
||||
<span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">foobar</span> = <span class="unresolved_reference">Foo</span>::<span class="unresolved_reference">new</span>().<span class="unresolved_reference">bar</span>();
|
||||
<span class="comment">/// ```</span>
|
||||
<span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">foo</span>(&<span class="self_keyword">self</span>) -> <span class="builtin_type">bool</span> {
|
||||
<span class="bool_literal">true</span>
|
||||
}
|
||||
}</code></pre>
|
||||
|
|
@ -10,6 +10,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
|||
.string_literal { color: #CC9393; }
|
||||
.field { color: #94BFF3; }
|
||||
.function { color: #93E0E3; }
|
||||
.function.unsafe { color: #BC8383; }
|
||||
.operator.unsafe { color: #BC8383; }
|
||||
.parameter { color: #94BFF3; }
|
||||
.text { color: #DCDCCC; }
|
||||
.type { color: #7CB8BB; }
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
|||
.string_literal { color: #CC9393; }
|
||||
.field { color: #94BFF3; }
|
||||
.function { color: #93E0E3; }
|
||||
.function.unsafe { color: #BC8383; }
|
||||
.operator.unsafe { color: #BC8383; }
|
||||
.parameter { color: #94BFF3; }
|
||||
.text { color: #DCDCCC; }
|
||||
.type { color: #7CB8BB; }
|
||||
|
|
@ -52,6 +54,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
|||
<span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">argument</span><span class="format_specifier">}</span><span class="string_literal">"</span>, argument = <span class="string_literal">"test"</span>); <span class="comment">// => "test"</span>
|
||||
<span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">name</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">"</span>, <span class="numeric_literal">1</span>, name = <span class="numeric_literal">2</span>); <span class="comment">// => "2 1"</span>
|
||||
<span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">a</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="variable">c</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="variable">b</span><span class="format_specifier">}</span><span class="string_literal">"</span>, a=<span class="string_literal">"a"</span>, b=<span class="char_literal">'b'</span>, c=<span class="numeric_literal">3</span>); <span class="comment">// => "a 3 b"</span>
|
||||
<span class="macro">println!</span>(<span class="string_literal">"{{</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">}}"</span>, <span class="numeric_literal">2</span>); <span class="comment">// => "{2}"</span>
|
||||
<span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
|
||||
<span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">1</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">5</span>);
|
||||
<span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="numeric_literal">1</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>, <span class="string_literal">"x"</span>);
|
||||
|
|
@ -61,7 +64,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
|||
<span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">^</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
|
||||
<span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">></span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
|
||||
<span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">+</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>);
|
||||
<span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">#</span><span class="variable">x</span><span class="string_literal">}!"</span>, <span class="numeric_literal">27</span>);
|
||||
<span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">#</span><span class="variable">x</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">27</span>);
|
||||
<span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>);
|
||||
<span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, -<span class="numeric_literal">5</span>);
|
||||
<span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">#</span><span class="numeric_literal">0</span><span class="numeric_literal">10</span><span class="variable">x</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">27</span>);
|
||||
|
|
|
|||
49
crates/ra_ide/src/snapshots/highlight_unsafe.html
Normal file
49
crates/ra_ide/src/snapshots/highlight_unsafe.html
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
|
||||
<style>
|
||||
body { margin: 0; }
|
||||
pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
|
||||
|
||||
.lifetime { color: #DFAF8F; font-style: italic; }
|
||||
.comment { color: #7F9F7F; }
|
||||
.struct, .enum { color: #7CB8BB; }
|
||||
.enum_variant { color: #BDE0F3; }
|
||||
.string_literal { color: #CC9393; }
|
||||
.field { color: #94BFF3; }
|
||||
.function { color: #93E0E3; }
|
||||
.function.unsafe { color: #BC8383; }
|
||||
.operator.unsafe { color: #BC8383; }
|
||||
.parameter { color: #94BFF3; }
|
||||
.text { color: #DCDCCC; }
|
||||
.type { color: #7CB8BB; }
|
||||
.builtin_type { color: #8CD0D3; }
|
||||
.type_param { color: #DFAF8F; }
|
||||
.attribute { color: #94BFF3; }
|
||||
.numeric_literal { color: #BFEBBF; }
|
||||
.bool_literal { color: #BFE6EB; }
|
||||
.macro { color: #94BFF3; }
|
||||
.module { color: #AFD8AF; }
|
||||
.variable { color: #DCDCCC; }
|
||||
.format_specifier { color: #CC696B; }
|
||||
.mutable { text-decoration: underline; }
|
||||
|
||||
.keyword { color: #F0DFAF; font-weight: bold; }
|
||||
.keyword.unsafe { color: #BC8383; font-weight: bold; }
|
||||
.control { font-style: italic; }
|
||||
</style>
|
||||
<pre><code><span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function declaration unsafe">unsafe_fn</span>() {}
|
||||
|
||||
<span class="keyword">struct</span> <span class="struct declaration">HasUnsafeFn</span>;
|
||||
|
||||
<span class="keyword">impl</span> <span class="struct">HasUnsafeFn</span> {
|
||||
<span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function declaration unsafe">unsafe_method</span>(&<span class="self_keyword">self</span>) {}
|
||||
}
|
||||
|
||||
<span class="keyword">fn</span> <span class="function declaration">main</span>() {
|
||||
<span class="keyword">let</span> <span class="variable declaration">x</span> = &<span class="numeric_literal">5</span> <span class="keyword">as</span> *<span class="keyword">const</span> <span class="builtin_type">usize</span>;
|
||||
<span class="keyword unsafe">unsafe</span> {
|
||||
<span class="function unsafe">unsafe_fn</span>();
|
||||
<span class="struct">HasUnsafeFn</span>.<span class="function unsafe">unsafe_method</span>();
|
||||
<span class="keyword">let</span> <span class="variable declaration">y</span> = <span class="operator unsafe">*</span>(<span class="variable">x</span>);
|
||||
<span class="keyword">let</span> <span class="variable declaration">z</span> = -<span class="variable">x</span>;
|
||||
}
|
||||
}</code></pre>
|
||||
|
|
@ -10,6 +10,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
|||
.string_literal { color: #CC9393; }
|
||||
.field { color: #94BFF3; }
|
||||
.function { color: #93E0E3; }
|
||||
.function.unsafe { color: #BC8383; }
|
||||
.operator.unsafe { color: #BC8383; }
|
||||
.parameter { color: #94BFF3; }
|
||||
.text { color: #DCDCCC; }
|
||||
.type { color: #7CB8BB; }
|
||||
|
|
@ -82,7 +84,9 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
|||
<span class="keyword">let</span> <span class="variable declaration mutable">y</span> = &<span class="keyword">mut</span> <span class="variable mutable">x</span>;
|
||||
<span class="keyword">let</span> <span class="variable declaration">z</span> = &<span class="variable mutable">y</span>;
|
||||
|
||||
<span class="variable mutable">y</span>;
|
||||
<span class="keyword">let</span> <span class="struct">Foo</span> { <span class="field">x</span>: <span class="variable declaration">z</span>, <span class="field">y</span> } = <span class="struct">Foo</span> { <span class="field">x</span>: <span class="variable">z</span>, <span class="field">y</span> };
|
||||
|
||||
<span class="variable">y</span>;
|
||||
}
|
||||
|
||||
<span class="keyword">enum</span> <span class="enum declaration">Option</span><<span class="type_param declaration">T</span>> {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
|||
.string_literal { color: #CC9393; }
|
||||
.field { color: #94BFF3; }
|
||||
.function { color: #93E0E3; }
|
||||
.function.unsafe { color: #BC8383; }
|
||||
.operator.unsafe { color: #BC8383; }
|
||||
.parameter { color: #94BFF3; }
|
||||
.text { color: #DCDCCC; }
|
||||
.type { color: #7CB8BB; }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
mod tags;
|
||||
mod html;
|
||||
mod injection;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
|
|
@ -10,14 +11,14 @@ use ra_ide_db::{
|
|||
};
|
||||
use ra_prof::profile;
|
||||
use ra_syntax::{
|
||||
ast::{self, HasFormatSpecifier, HasQuotes, HasStringValue},
|
||||
ast::{self, HasFormatSpecifier},
|
||||
AstNode, AstToken, Direction, NodeOrToken, SyntaxElement,
|
||||
SyntaxKind::*,
|
||||
SyntaxToken, TextRange, WalkEvent, T,
|
||||
TextRange, WalkEvent, T,
|
||||
};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::{call_info::ActiveParameter, Analysis, FileId};
|
||||
use crate::FileId;
|
||||
|
||||
use ast::FormatSpecifier;
|
||||
pub(crate) use html::highlight_as_html;
|
||||
|
|
@ -123,6 +124,23 @@ pub(crate) fn highlight(
|
|||
_ => (),
|
||||
}
|
||||
|
||||
// Check for Rust code in documentation
|
||||
match &event {
|
||||
WalkEvent::Leave(NodeOrToken::Node(node)) => {
|
||||
if let Some((doctest, range_mapping, new_comments)) =
|
||||
injection::extract_doc_comments(node)
|
||||
{
|
||||
injection::highlight_doc_comment(
|
||||
doctest,
|
||||
range_mapping,
|
||||
new_comments,
|
||||
&mut stack,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let element = match event {
|
||||
WalkEvent::Enter(it) => it,
|
||||
WalkEvent::Leave(_) => continue,
|
||||
|
|
@ -173,7 +191,7 @@ pub(crate) fn highlight(
|
|||
|
||||
if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) {
|
||||
let expanded = element_to_highlight.as_token().unwrap().clone();
|
||||
if highlight_injection(&mut stack, &sema, token, expanded).is_some() {
|
||||
if injection::highlight_injection(&mut stack, &sema, token, expanded).is_some() {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
@ -259,9 +277,8 @@ impl HighlightedRangeStack {
|
|||
let mut parent = prev.pop().unwrap();
|
||||
for ele in children {
|
||||
assert!(parent.range.contains_range(ele.range));
|
||||
let mut cloned = parent.clone();
|
||||
parent.range = TextRange::new(parent.range.start(), ele.range.start());
|
||||
cloned.range = TextRange::new(ele.range.end(), cloned.range.end());
|
||||
|
||||
let cloned = Self::intersect(&mut parent, &ele);
|
||||
if !parent.range.is_empty() {
|
||||
prev.push(parent);
|
||||
}
|
||||
|
|
@ -274,6 +291,62 @@ impl HighlightedRangeStack {
|
|||
}
|
||||
}
|
||||
|
||||
/// Intersects the `HighlightedRange` `parent` with `child`.
|
||||
/// `parent` is mutated in place, becoming the range before `child`.
|
||||
/// Returns the range (of the same type as `parent`) *after* `child`.
|
||||
fn intersect(parent: &mut HighlightedRange, child: &HighlightedRange) -> HighlightedRange {
|
||||
assert!(parent.range.contains_range(child.range));
|
||||
|
||||
let mut cloned = parent.clone();
|
||||
parent.range = TextRange::new(parent.range.start(), child.range.start());
|
||||
cloned.range = TextRange::new(child.range.end(), cloned.range.end());
|
||||
|
||||
cloned
|
||||
}
|
||||
|
||||
/// Similar to `pop`, but can modify arbitrary prior ranges (where `pop`)
|
||||
/// can only modify the last range currently on the stack.
|
||||
/// Can be used to do injections that span multiple ranges, like the
|
||||
/// doctest injection below.
|
||||
/// If `delete` is set to true, the parent range is deleted instead of
|
||||
/// intersected.
|
||||
///
|
||||
/// Note that `pop` can be simulated by `pop_and_inject(false)` but the
|
||||
/// latter is computationally more expensive.
|
||||
fn pop_and_inject(&mut self, delete: bool) {
|
||||
let mut children = self.stack.pop().unwrap();
|
||||
let prev = self.stack.last_mut().unwrap();
|
||||
children.sort_by_key(|range| range.range.start());
|
||||
prev.sort_by_key(|range| range.range.start());
|
||||
|
||||
for child in children {
|
||||
if let Some(idx) =
|
||||
prev.iter().position(|parent| parent.range.contains_range(child.range))
|
||||
{
|
||||
let cloned = Self::intersect(&mut prev[idx], &child);
|
||||
let insert_idx = if delete || prev[idx].range.is_empty() {
|
||||
prev.remove(idx);
|
||||
idx
|
||||
} else {
|
||||
idx + 1
|
||||
};
|
||||
prev.insert(insert_idx, child);
|
||||
if !delete && !cloned.range.is_empty() {
|
||||
prev.insert(insert_idx + 1, cloned);
|
||||
}
|
||||
} else if let Some(_idx) =
|
||||
prev.iter().position(|parent| parent.range.contains(child.range.start()))
|
||||
{
|
||||
unreachable!("child range should be completely contained in parent range");
|
||||
} else {
|
||||
let idx = prev
|
||||
.binary_search_by_key(&child.range.start(), |range| range.range.start())
|
||||
.unwrap_or_else(|x| x);
|
||||
prev.insert(idx, child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add(&mut self, range: HighlightedRange) {
|
||||
self.stack
|
||||
.last_mut()
|
||||
|
|
@ -363,6 +436,7 @@ fn highlight_element(
|
|||
highlight_name(db, def) | HighlightModifier::Definition
|
||||
}
|
||||
Some(NameClass::ConstReference(def)) => highlight_name(db, def),
|
||||
Some(NameClass::FieldShorthand { .. }) => HighlightTag::Field.into(),
|
||||
None => highlight_name_by_syntax(name) | HighlightModifier::Definition,
|
||||
}
|
||||
}
|
||||
|
|
@ -406,6 +480,19 @@ fn highlight_element(
|
|||
_ => h,
|
||||
}
|
||||
}
|
||||
T![*] => {
|
||||
let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?;
|
||||
|
||||
let expr = prefix_expr.expr()?;
|
||||
let ty = sema.type_of_expr(&expr)?;
|
||||
if !ty.is_raw_ptr() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut h = Highlight::new(HighlightTag::Operator);
|
||||
h |= HighlightModifier::Unsafe;
|
||||
h
|
||||
}
|
||||
|
||||
k if k.is_keyword() => {
|
||||
let h = Highlight::new(HighlightTag::Keyword);
|
||||
|
|
@ -458,7 +545,13 @@ fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight {
|
|||
Definition::Field(_) => HighlightTag::Field,
|
||||
Definition::ModuleDef(def) => match def {
|
||||
hir::ModuleDef::Module(_) => HighlightTag::Module,
|
||||
hir::ModuleDef::Function(_) => HighlightTag::Function,
|
||||
hir::ModuleDef::Function(func) => {
|
||||
let mut h = HighlightTag::Function.into();
|
||||
if func.is_unsafe(db) {
|
||||
h |= HighlightModifier::Unsafe;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct,
|
||||
hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Enum,
|
||||
hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Union,
|
||||
|
|
@ -516,42 +609,3 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
|
|||
|
||||
tag.into()
|
||||
}
|
||||
|
||||
fn highlight_injection(
|
||||
acc: &mut HighlightedRangeStack,
|
||||
sema: &Semantics<RootDatabase>,
|
||||
literal: ast::RawString,
|
||||
expanded: SyntaxToken,
|
||||
) -> Option<()> {
|
||||
let active_parameter = ActiveParameter::at_token(&sema, expanded)?;
|
||||
if !active_parameter.name.starts_with("ra_fixture") {
|
||||
return None;
|
||||
}
|
||||
let value = literal.value()?;
|
||||
let (analysis, tmp_file_id) = Analysis::from_single_file(value);
|
||||
|
||||
if let Some(range) = literal.open_quote_text_range() {
|
||||
acc.add(HighlightedRange {
|
||||
range,
|
||||
highlight: HighlightTag::StringLiteral.into(),
|
||||
binding_hash: None,
|
||||
})
|
||||
}
|
||||
|
||||
for mut h in analysis.highlight(tmp_file_id).unwrap() {
|
||||
if let Some(r) = literal.map_range_up(h.range) {
|
||||
h.range = r;
|
||||
acc.add(h)
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(range) = literal.close_quote_text_range() {
|
||||
acc.add(HighlightedRange {
|
||||
range,
|
||||
highlight: HighlightTag::StringLiteral.into(),
|
||||
binding_hash: None,
|
||||
})
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
|||
.string_literal { color: #CC9393; }
|
||||
.field { color: #94BFF3; }
|
||||
.function { color: #93E0E3; }
|
||||
.function.unsafe { color: #BC8383; }
|
||||
.operator.unsafe { color: #BC8383; }
|
||||
.parameter { color: #94BFF3; }
|
||||
.text { color: #DCDCCC; }
|
||||
.type { color: #7CB8BB; }
|
||||
|
|
|
|||
168
crates/ra_ide/src/syntax_highlighting/injection.rs
Normal file
168
crates/ra_ide/src/syntax_highlighting/injection.rs
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
//! Syntax highlighting injections such as highlighting of documentation tests.
|
||||
|
||||
use std::{collections::BTreeMap, convert::TryFrom};
|
||||
|
||||
use ast::{HasQuotes, HasStringValue};
|
||||
use hir::Semantics;
|
||||
use ra_syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize};
|
||||
use stdx::SepBy;
|
||||
|
||||
use crate::{call_info::ActiveParameter, Analysis, HighlightTag, HighlightedRange, RootDatabase};
|
||||
|
||||
use super::HighlightedRangeStack;
|
||||
|
||||
pub(super) fn highlight_injection(
|
||||
acc: &mut HighlightedRangeStack,
|
||||
sema: &Semantics<RootDatabase>,
|
||||
literal: ast::RawString,
|
||||
expanded: SyntaxToken,
|
||||
) -> Option<()> {
|
||||
let active_parameter = ActiveParameter::at_token(&sema, expanded)?;
|
||||
if !active_parameter.name.starts_with("ra_fixture") {
|
||||
return None;
|
||||
}
|
||||
let value = literal.value()?;
|
||||
let (analysis, tmp_file_id) = Analysis::from_single_file(value);
|
||||
|
||||
if let Some(range) = literal.open_quote_text_range() {
|
||||
acc.add(HighlightedRange {
|
||||
range,
|
||||
highlight: HighlightTag::StringLiteral.into(),
|
||||
binding_hash: None,
|
||||
})
|
||||
}
|
||||
|
||||
for mut h in analysis.highlight(tmp_file_id).unwrap() {
|
||||
if let Some(r) = literal.map_range_up(h.range) {
|
||||
h.range = r;
|
||||
acc.add(h)
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(range) = literal.close_quote_text_range() {
|
||||
acc.add(HighlightedRange {
|
||||
range,
|
||||
highlight: HighlightTag::StringLiteral.into(),
|
||||
binding_hash: None,
|
||||
})
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// Mapping from extracted documentation code to original code
|
||||
type RangesMap = BTreeMap<TextSize, TextSize>;
|
||||
|
||||
/// Extracts Rust code from documentation comments as well as a mapping from
|
||||
/// the extracted source code back to the original source ranges.
|
||||
/// Lastly, a vector of new comment highlight ranges (spanning only the
|
||||
/// comment prefix) is returned which is used in the syntax highlighting
|
||||
/// injection to replace the previous (line-spanning) comment ranges.
|
||||
pub(super) fn extract_doc_comments(
|
||||
node: &SyntaxNode,
|
||||
) -> Option<(String, RangesMap, Vec<HighlightedRange>)> {
|
||||
// wrap the doctest into function body to get correct syntax highlighting
|
||||
let prefix = "fn doctest() {\n";
|
||||
let suffix = "}\n";
|
||||
// Mapping from extracted documentation code to original code
|
||||
let mut range_mapping: RangesMap = BTreeMap::new();
|
||||
let mut line_start = TextSize::try_from(prefix.len()).unwrap();
|
||||
let mut is_doctest = false;
|
||||
// Replace the original, line-spanning comment ranges by new, only comment-prefix
|
||||
// spanning comment ranges.
|
||||
let mut new_comments = Vec::new();
|
||||
let doctest = node
|
||||
.children_with_tokens()
|
||||
.filter_map(|el| el.into_token().and_then(ast::Comment::cast))
|
||||
.filter(|comment| comment.kind().doc.is_some())
|
||||
.filter(|comment| {
|
||||
if comment.text().contains("```") {
|
||||
is_doctest = !is_doctest;
|
||||
false
|
||||
} else {
|
||||
is_doctest
|
||||
}
|
||||
})
|
||||
.map(|comment| {
|
||||
let prefix_len = comment.prefix().len();
|
||||
let line: &str = comment.text().as_str();
|
||||
let range = comment.syntax().text_range();
|
||||
|
||||
// whitespace after comment is ignored
|
||||
let pos = if let Some(ws) = line.chars().nth(prefix_len).filter(|c| c.is_whitespace()) {
|
||||
prefix_len + ws.len_utf8()
|
||||
} else {
|
||||
prefix_len
|
||||
};
|
||||
|
||||
// lines marked with `#` should be ignored in output, we skip the `#` char
|
||||
let pos = if let Some(ws) = line.chars().nth(pos).filter(|&c| c == '#') {
|
||||
pos + ws.len_utf8()
|
||||
} else {
|
||||
pos
|
||||
};
|
||||
|
||||
range_mapping.insert(line_start, range.start() + TextSize::try_from(pos).unwrap());
|
||||
new_comments.push(HighlightedRange {
|
||||
range: TextRange::new(
|
||||
range.start(),
|
||||
range.start() + TextSize::try_from(pos).unwrap(),
|
||||
),
|
||||
highlight: HighlightTag::Comment.into(),
|
||||
binding_hash: None,
|
||||
});
|
||||
line_start += range.len() - TextSize::try_from(pos).unwrap();
|
||||
line_start += TextSize::try_from('\n'.len_utf8()).unwrap();
|
||||
|
||||
line[pos..].to_owned()
|
||||
})
|
||||
.sep_by("\n")
|
||||
.to_string();
|
||||
|
||||
if doctest.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let doctest = format!("{}{}{}", prefix, doctest, suffix);
|
||||
Some((doctest, range_mapping, new_comments))
|
||||
}
|
||||
|
||||
/// Injection of syntax highlighting of doctests.
|
||||
pub(super) fn highlight_doc_comment(
|
||||
text: String,
|
||||
range_mapping: RangesMap,
|
||||
new_comments: Vec<HighlightedRange>,
|
||||
stack: &mut HighlightedRangeStack,
|
||||
) {
|
||||
let (analysis, tmp_file_id) = Analysis::from_single_file(text);
|
||||
|
||||
stack.push();
|
||||
for mut h in analysis.highlight(tmp_file_id).unwrap() {
|
||||
// Determine start offset and end offset in case of multi-line ranges
|
||||
let mut start_offset = None;
|
||||
let mut end_offset = None;
|
||||
for (line_start, orig_line_start) in range_mapping.range(..h.range.end()).rev() {
|
||||
if line_start <= &h.range.start() {
|
||||
start_offset.get_or_insert(orig_line_start - line_start);
|
||||
break;
|
||||
} else {
|
||||
end_offset.get_or_insert(orig_line_start - line_start);
|
||||
}
|
||||
}
|
||||
if let Some(start_offset) = start_offset {
|
||||
h.range = TextRange::new(
|
||||
h.range.start() + start_offset,
|
||||
h.range.end() + end_offset.unwrap_or(start_offset),
|
||||
);
|
||||
stack.add(h);
|
||||
}
|
||||
}
|
||||
|
||||
// Inject the comment prefix highlight ranges
|
||||
stack.push();
|
||||
for comment in new_comments {
|
||||
stack.add(comment);
|
||||
}
|
||||
stack.pop_and_inject(false);
|
||||
stack.pop_and_inject(true);
|
||||
}
|
||||
|
|
@ -24,12 +24,14 @@ pub enum HighlightTag {
|
|||
Enum,
|
||||
EnumVariant,
|
||||
Field,
|
||||
FormatSpecifier,
|
||||
Function,
|
||||
Keyword,
|
||||
Lifetime,
|
||||
Macro,
|
||||
Module,
|
||||
NumericLiteral,
|
||||
Operator,
|
||||
SelfKeyword,
|
||||
SelfType,
|
||||
Static,
|
||||
|
|
@ -41,8 +43,6 @@ pub enum HighlightTag {
|
|||
Union,
|
||||
Local,
|
||||
UnresolvedReference,
|
||||
FormatSpecifier,
|
||||
Operator,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
|
|
@ -72,12 +72,14 @@ impl HighlightTag {
|
|||
HighlightTag::Enum => "enum",
|
||||
HighlightTag::EnumVariant => "enum_variant",
|
||||
HighlightTag::Field => "field",
|
||||
HighlightTag::FormatSpecifier => "format_specifier",
|
||||
HighlightTag::Function => "function",
|
||||
HighlightTag::Keyword => "keyword",
|
||||
HighlightTag::Lifetime => "lifetime",
|
||||
HighlightTag::Macro => "macro",
|
||||
HighlightTag::Module => "module",
|
||||
HighlightTag::NumericLiteral => "numeric_literal",
|
||||
HighlightTag::Operator => "operator",
|
||||
HighlightTag::SelfKeyword => "self_keyword",
|
||||
HighlightTag::SelfType => "self_type",
|
||||
HighlightTag::Static => "static",
|
||||
|
|
@ -89,8 +91,6 @@ impl HighlightTag {
|
|||
HighlightTag::Union => "union",
|
||||
HighlightTag::Local => "variable",
|
||||
HighlightTag::UnresolvedReference => "unresolved_reference",
|
||||
HighlightTag::FormatSpecifier => "format_specifier",
|
||||
HighlightTag::Operator => "operator",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use crate::{
|
|||
|
||||
#[test]
|
||||
fn test_highlighting() {
|
||||
let (analysis, file_id) = single_file(
|
||||
check_highlighting(
|
||||
r#"
|
||||
#[derive(Clone, Debug)]
|
||||
struct Foo {
|
||||
|
|
@ -65,6 +65,8 @@ fn main() {
|
|||
let y = &mut x;
|
||||
let z = &y;
|
||||
|
||||
let Foo { x: z, y } = Foo { x: z, y };
|
||||
|
||||
y;
|
||||
}
|
||||
|
||||
|
|
@ -84,17 +86,14 @@ impl<T> Option<T> {
|
|||
}
|
||||
"#
|
||||
.trim(),
|
||||
"crates/ra_ide/src/snapshots/highlighting.html",
|
||||
false,
|
||||
);
|
||||
let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlighting.html");
|
||||
let actual_html = &analysis.highlight_as_html(file_id, false).unwrap();
|
||||
let expected_html = &read_text(&dst_file);
|
||||
fs::write(dst_file, &actual_html).unwrap();
|
||||
assert_eq_text!(expected_html, actual_html);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rainbow_highlighting() {
|
||||
let (analysis, file_id) = single_file(
|
||||
check_highlighting(
|
||||
r#"
|
||||
fn main() {
|
||||
let hello = "hello";
|
||||
|
|
@ -110,12 +109,9 @@ fn bar() {
|
|||
}
|
||||
"#
|
||||
.trim(),
|
||||
"crates/ra_ide/src/snapshots/rainbow_highlighting.html",
|
||||
true,
|
||||
);
|
||||
let dst_file = project_dir().join("crates/ra_ide/src/snapshots/rainbow_highlighting.html");
|
||||
let actual_html = &analysis.highlight_as_html(file_id, true).unwrap();
|
||||
let expected_html = &read_text(&dst_file);
|
||||
fs::write(dst_file, &actual_html).unwrap();
|
||||
assert_eq_text!(expected_html, actual_html);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -153,7 +149,7 @@ fn test_ranges() {
|
|||
|
||||
#[test]
|
||||
fn test_flattening() {
|
||||
let (analysis, file_id) = single_file(
|
||||
check_highlighting(
|
||||
r##"
|
||||
fn fixture(ra_fixture: &str) {}
|
||||
|
||||
|
|
@ -167,13 +163,9 @@ fn main() {
|
|||
);
|
||||
}"##
|
||||
.trim(),
|
||||
"crates/ra_ide/src/snapshots/highlight_injection.html",
|
||||
false,
|
||||
);
|
||||
|
||||
let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlight_injection.html");
|
||||
let actual_html = &analysis.highlight_as_html(file_id, false).unwrap();
|
||||
let expected_html = &read_text(&dst_file);
|
||||
fs::write(dst_file, &actual_html).unwrap();
|
||||
assert_eq_text!(expected_html, actual_html);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -192,7 +184,7 @@ macro_rules! test {}
|
|||
fn test_string_highlighting() {
|
||||
// The format string detection is based on macro-expansion,
|
||||
// thus, we have to copy the macro definition from `std`
|
||||
let (analysis, file_id) = single_file(
|
||||
check_highlighting(
|
||||
r#"
|
||||
macro_rules! println {
|
||||
($($arg:tt)*) => ({
|
||||
|
|
@ -218,6 +210,7 @@ fn main() {
|
|||
println!("{argument}", argument = "test"); // => "test"
|
||||
println!("{name} {}", 1, name = 2); // => "2 1"
|
||||
println!("{a} {c} {b}", a="a", b='b', c=3); // => "a 3 b"
|
||||
println!("{{{}}}", 2); // => "{2}"
|
||||
println!("Hello {:5}!", "x");
|
||||
println!("Hello {:1$}!", "x", 5);
|
||||
println!("Hello {1:0$}!", 5, "x");
|
||||
|
|
@ -249,10 +242,96 @@ fn main() {
|
|||
println!("{ничоси}", ничоси = 92);
|
||||
}"#
|
||||
.trim(),
|
||||
"crates/ra_ide/src/snapshots/highlight_strings.html",
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlight_strings.html");
|
||||
let actual_html = &analysis.highlight_as_html(file_id, false).unwrap();
|
||||
#[test]
|
||||
fn test_unsafe_highlighting() {
|
||||
check_highlighting(
|
||||
r#"
|
||||
unsafe fn unsafe_fn() {}
|
||||
|
||||
struct HasUnsafeFn;
|
||||
|
||||
impl HasUnsafeFn {
|
||||
unsafe fn unsafe_method(&self) {}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let x = &5 as *const usize;
|
||||
unsafe {
|
||||
unsafe_fn();
|
||||
HasUnsafeFn.unsafe_method();
|
||||
let y = *(x);
|
||||
let z = -x;
|
||||
}
|
||||
}
|
||||
"#
|
||||
.trim(),
|
||||
"crates/ra_ide/src/snapshots/highlight_unsafe.html",
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_highlight_doctest() {
|
||||
check_highlighting(
|
||||
r#"
|
||||
impl Foo {
|
||||
/// Constructs a new `Foo`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #![allow(unused_mut)]
|
||||
/// let mut foo: Foo = Foo::new();
|
||||
/// ```
|
||||
pub const fn new() -> Foo {
|
||||
Foo { }
|
||||
}
|
||||
|
||||
/// `bar` method on `Foo`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// let foo = Foo::new();
|
||||
///
|
||||
/// // calls bar on foo
|
||||
/// assert!(foo.bar());
|
||||
///
|
||||
/// /* multi-line
|
||||
/// comment */
|
||||
///
|
||||
/// let multi_line_string = "Foo
|
||||
/// bar
|
||||
/// ";
|
||||
///
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// let foobar = Foo::new().bar();
|
||||
/// ```
|
||||
pub fn foo(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
"#
|
||||
.trim(),
|
||||
"crates/ra_ide/src/snapshots/highlight_doctest.html",
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
/// Highlights the code given by the `ra_fixture` argument, renders the
|
||||
/// result as HTML, and compares it with the HTML file given as `snapshot`.
|
||||
/// Note that the `snapshot` file is overwritten by the rendered HTML.
|
||||
fn check_highlighting(ra_fixture: &str, snapshot: &str, rainbow: bool) {
|
||||
let (analysis, file_id) = single_file(ra_fixture);
|
||||
let dst_file = project_dir().join(snapshot);
|
||||
let actual_html = &analysis.highlight_as_html(file_id, rainbow).unwrap();
|
||||
let expected_html = &read_text(&dst_file);
|
||||
fs::write(dst_file, &actual_html).unwrap();
|
||||
assert_eq_text!(expected_html, actual_html);
|
||||
|
|
|
|||
|
|
@ -17,11 +17,13 @@ mod on_enter;
|
|||
|
||||
use ra_db::{FilePosition, SourceDatabase};
|
||||
use ra_fmt::leading_indent;
|
||||
use ra_ide_db::RootDatabase;
|
||||
use ra_ide_db::{source_change::SourceFileEdit, RootDatabase};
|
||||
use ra_syntax::{
|
||||
algo::find_node_at_offset,
|
||||
ast::{self, AstToken},
|
||||
AstNode, SourceFile, TextRange, TextSize,
|
||||
AstNode, SourceFile,
|
||||
SyntaxKind::{FIELD_EXPR, METHOD_CALL_EXPR},
|
||||
TextRange, TextSize,
|
||||
};
|
||||
|
||||
use ra_text_edit::TextEdit;
|
||||
|
|
@ -47,8 +49,8 @@ pub(crate) fn on_char_typed(
|
|||
assert!(TRIGGER_CHARS.contains(char_typed));
|
||||
let file = &db.parse(position.file_id).tree();
|
||||
assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed));
|
||||
let text_edit = on_char_typed_inner(file, position.offset, char_typed)?;
|
||||
Some(SourceChange::source_file_edit_from(position.file_id, text_edit))
|
||||
let edit = on_char_typed_inner(file, position.offset, char_typed)?;
|
||||
Some(SourceFileEdit { file_id: position.file_id, edit }.into())
|
||||
}
|
||||
|
||||
fn on_char_typed_inner(file: &SourceFile, offset: TextSize, char_typed: char) -> Option<TextEdit> {
|
||||
|
|
@ -98,9 +100,12 @@ fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
|
|||
};
|
||||
let current_indent_len = TextSize::of(current_indent);
|
||||
|
||||
let parent = whitespace.syntax().parent();
|
||||
// Make sure dot is a part of call chain
|
||||
let field_expr = ast::FieldExpr::cast(whitespace.syntax().parent())?;
|
||||
let prev_indent = leading_indent(field_expr.syntax())?;
|
||||
if !matches!(parent.kind(), FIELD_EXPR | METHOD_CALL_EXPR) {
|
||||
return None;
|
||||
}
|
||||
let prev_indent = leading_indent(&parent)?;
|
||||
let target_indent = format!(" {}", prev_indent);
|
||||
let target_indent_len = TextSize::of(&target_indent);
|
||||
if current_indent_len == target_indent_len {
|
||||
|
|
@ -143,11 +148,11 @@ mod tests {
|
|||
})
|
||||
}
|
||||
|
||||
fn type_char(char_typed: char, before: &str, after: &str) {
|
||||
let actual = do_type_char(char_typed, before)
|
||||
fn type_char(char_typed: char, ra_fixture_before: &str, ra_fixture_after: &str) {
|
||||
let actual = do_type_char(char_typed, ra_fixture_before)
|
||||
.unwrap_or_else(|| panic!("typing `{}` did nothing", char_typed));
|
||||
|
||||
assert_eq_text!(after, &actual);
|
||||
assert_eq_text!(ra_fixture_after, &actual);
|
||||
}
|
||||
|
||||
fn type_char_noop(char_typed: char, before: &str) {
|
||||
|
|
@ -248,6 +253,27 @@ fn foo() {
|
|||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn indents_new_chain_call_with_let() {
|
||||
type_char(
|
||||
'.',
|
||||
r#"
|
||||
fn main() {
|
||||
let _ = foo
|
||||
<|>
|
||||
bar()
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
let _ = foo
|
||||
.
|
||||
bar()
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn indents_continued_chain_call() {
|
||||
type_char(
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ use rustc_hash::FxHashMap;
|
|||
|
||||
use crate::{
|
||||
symbol_index::{SymbolIndex, SymbolsDatabase},
|
||||
DebugData, RootDatabase,
|
||||
RootDatabase,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -26,7 +26,6 @@ pub struct AnalysisChange {
|
|||
files_changed: Vec<(FileId, Arc<String>)>,
|
||||
libraries_added: Vec<LibraryData>,
|
||||
crate_graph: Option<CrateGraph>,
|
||||
debug_data: DebugData,
|
||||
}
|
||||
|
||||
impl fmt::Debug for AnalysisChange {
|
||||
|
|
@ -87,10 +86,6 @@ impl AnalysisChange {
|
|||
pub fn set_crate_graph(&mut self, graph: CrateGraph) {
|
||||
self.crate_graph = Some(graph);
|
||||
}
|
||||
|
||||
pub fn set_debug_root_path(&mut self, source_root_id: SourceRootId, path: String) {
|
||||
self.debug_data.root_paths.insert(source_root_id, path);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -218,8 +213,6 @@ impl RootDatabase {
|
|||
if let Some(crate_graph) = change.crate_graph {
|
||||
self.set_crate_graph_with_durability(Arc::new(crate_graph), Durability::HIGH)
|
||||
}
|
||||
|
||||
Arc::make_mut(&mut self.debug_data).merge(change.debug_data)
|
||||
}
|
||||
|
||||
fn apply_root_change(&mut self, root_id: SourceRootId, root_change: RootChange) {
|
||||
|
|
@ -334,6 +327,7 @@ impl RootDatabase {
|
|||
hir::db::CrateLangItemsQuery
|
||||
hir::db::LangItemQuery
|
||||
hir::db::DocumentationQuery
|
||||
hir::db::ImportMapQuery
|
||||
|
||||
// InternDatabase
|
||||
hir::db::InternFunctionQuery
|
||||
|
|
@ -369,6 +363,7 @@ impl RootDatabase {
|
|||
hir::db::ImplDatumQuery
|
||||
hir::db::AssociatedTyValueQuery
|
||||
hir::db::TraitSolveQuery
|
||||
hir::db::ReturnTypeImplTraitsQuery
|
||||
|
||||
// SymbolsDatabase
|
||||
crate::symbol_index::FileSymbolsQuery
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ use ra_syntax::{
|
|||
use crate::RootDatabase;
|
||||
|
||||
// FIXME: a more precise name would probably be `Symbol`?
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub enum Definition {
|
||||
Macro(MacroDef),
|
||||
Field(Field),
|
||||
|
|
@ -78,10 +78,15 @@ impl Definition {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum NameClass {
|
||||
Definition(Definition),
|
||||
/// `None` in `if let None = Some(82) {}`
|
||||
ConstReference(Definition),
|
||||
FieldShorthand {
|
||||
local: Local,
|
||||
field: Definition,
|
||||
},
|
||||
}
|
||||
|
||||
impl NameClass {
|
||||
|
|
@ -89,12 +94,14 @@ impl NameClass {
|
|||
match self {
|
||||
NameClass::Definition(it) => Some(it),
|
||||
NameClass::ConstReference(_) => None,
|
||||
NameClass::FieldShorthand { local, field: _ } => Some(Definition::Local(local)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn definition(self) -> Definition {
|
||||
match self {
|
||||
NameClass::Definition(it) | NameClass::ConstReference(it) => it,
|
||||
NameClass::FieldShorthand { local: _, field } => field,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -102,18 +109,14 @@ impl NameClass {
|
|||
pub fn classify_name(sema: &Semantics<RootDatabase>, name: &ast::Name) -> Option<NameClass> {
|
||||
let _p = profile("classify_name");
|
||||
|
||||
if let Some(bind_pat) = name.syntax().parent().and_then(ast::BindPat::cast) {
|
||||
let parent = name.syntax().parent()?;
|
||||
|
||||
if let Some(bind_pat) = ast::BindPat::cast(parent.clone()) {
|
||||
if let Some(def) = sema.resolve_bind_pat_to_const(&bind_pat) {
|
||||
return Some(NameClass::ConstReference(Definition::ModuleDef(def)));
|
||||
}
|
||||
}
|
||||
|
||||
classify_name_inner(sema, name).map(NameClass::Definition)
|
||||
}
|
||||
|
||||
fn classify_name_inner(sema: &Semantics<RootDatabase>, name: &ast::Name) -> Option<Definition> {
|
||||
let parent = name.syntax().parent()?;
|
||||
|
||||
match_ast! {
|
||||
match parent {
|
||||
ast::Alias(it) => {
|
||||
|
|
@ -123,63 +126,73 @@ fn classify_name_inner(sema: &Semantics<RootDatabase>, name: &ast::Name) -> Opti
|
|||
let name_ref = path_segment.name_ref()?;
|
||||
let name_ref_class = classify_name_ref(sema, &name_ref)?;
|
||||
|
||||
Some(name_ref_class.definition())
|
||||
Some(NameClass::Definition(name_ref_class.definition()))
|
||||
},
|
||||
ast::BindPat(it) => {
|
||||
let local = sema.to_def(&it)?;
|
||||
Some(Definition::Local(local))
|
||||
|
||||
if let Some(record_field_pat) = it.syntax().parent().and_then(ast::RecordFieldPat::cast) {
|
||||
if record_field_pat.name_ref().is_none() {
|
||||
if let Some(field) = sema.resolve_record_field_pat(&record_field_pat) {
|
||||
let field = Definition::Field(field);
|
||||
return Some(NameClass::FieldShorthand { local, field });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(NameClass::Definition(Definition::Local(local)))
|
||||
},
|
||||
ast::RecordFieldDef(it) => {
|
||||
let field: hir::Field = sema.to_def(&it)?;
|
||||
Some(Definition::Field(field))
|
||||
Some(NameClass::Definition(Definition::Field(field)))
|
||||
},
|
||||
ast::Module(it) => {
|
||||
let def = sema.to_def(&it)?;
|
||||
Some(Definition::ModuleDef(def.into()))
|
||||
Some(NameClass::Definition(Definition::ModuleDef(def.into())))
|
||||
},
|
||||
ast::StructDef(it) => {
|
||||
let def: hir::Struct = sema.to_def(&it)?;
|
||||
Some(Definition::ModuleDef(def.into()))
|
||||
Some(NameClass::Definition(Definition::ModuleDef(def.into())))
|
||||
},
|
||||
ast::UnionDef(it) => {
|
||||
let def: hir::Union = sema.to_def(&it)?;
|
||||
Some(Definition::ModuleDef(def.into()))
|
||||
Some(NameClass::Definition(Definition::ModuleDef(def.into())))
|
||||
},
|
||||
ast::EnumDef(it) => {
|
||||
let def: hir::Enum = sema.to_def(&it)?;
|
||||
Some(Definition::ModuleDef(def.into()))
|
||||
Some(NameClass::Definition(Definition::ModuleDef(def.into())))
|
||||
},
|
||||
ast::TraitDef(it) => {
|
||||
let def: hir::Trait = sema.to_def(&it)?;
|
||||
Some(Definition::ModuleDef(def.into()))
|
||||
Some(NameClass::Definition(Definition::ModuleDef(def.into())))
|
||||
},
|
||||
ast::StaticDef(it) => {
|
||||
let def: hir::Static = sema.to_def(&it)?;
|
||||
Some(Definition::ModuleDef(def.into()))
|
||||
Some(NameClass::Definition(Definition::ModuleDef(def.into())))
|
||||
},
|
||||
ast::EnumVariant(it) => {
|
||||
let def: hir::EnumVariant = sema.to_def(&it)?;
|
||||
Some(Definition::ModuleDef(def.into()))
|
||||
Some(NameClass::Definition(Definition::ModuleDef(def.into())))
|
||||
},
|
||||
ast::FnDef(it) => {
|
||||
let def: hir::Function = sema.to_def(&it)?;
|
||||
Some(Definition::ModuleDef(def.into()))
|
||||
Some(NameClass::Definition(Definition::ModuleDef(def.into())))
|
||||
},
|
||||
ast::ConstDef(it) => {
|
||||
let def: hir::Const = sema.to_def(&it)?;
|
||||
Some(Definition::ModuleDef(def.into()))
|
||||
Some(NameClass::Definition(Definition::ModuleDef(def.into())))
|
||||
},
|
||||
ast::TypeAliasDef(it) => {
|
||||
let def: hir::TypeAlias = sema.to_def(&it)?;
|
||||
Some(Definition::ModuleDef(def.into()))
|
||||
Some(NameClass::Definition(Definition::ModuleDef(def.into())))
|
||||
},
|
||||
ast::MacroCall(it) => {
|
||||
let def = sema.to_def(&it)?;
|
||||
Some(Definition::Macro(def))
|
||||
Some(NameClass::Definition(Definition::Macro(def)))
|
||||
},
|
||||
ast::TypeParam(it) => {
|
||||
let def = sema.to_def(&it)?;
|
||||
Some(Definition::TypeParam(def))
|
||||
Some(NameClass::Definition(Definition::TypeParam(def)))
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//! This module contains an import search funcionality that is provided to the ra_assists module.
|
||||
//! Later, this should be moved away to a separate crate that is accessible from the ra_assists module.
|
||||
|
||||
use hir::{MacroDef, ModuleDef, Semantics};
|
||||
use hir::{Crate, MacroDef, ModuleDef, Semantics};
|
||||
use ra_prof::profile;
|
||||
use ra_syntax::{ast, AstNode, SyntaxKind::NAME};
|
||||
|
||||
|
|
@ -11,44 +11,46 @@ use crate::{
|
|||
RootDatabase,
|
||||
};
|
||||
use either::Either;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
pub struct ImportsLocator<'a> {
|
||||
sema: Semantics<'a, RootDatabase>,
|
||||
krate: Crate,
|
||||
}
|
||||
|
||||
impl<'a> ImportsLocator<'a> {
|
||||
pub fn new(db: &'a RootDatabase) -> Self {
|
||||
Self { sema: Semantics::new(db) }
|
||||
pub fn new(db: &'a RootDatabase, krate: Crate) -> Self {
|
||||
Self { sema: Semantics::new(db), krate }
|
||||
}
|
||||
|
||||
pub fn find_imports(&mut self, name_to_import: &str) -> Vec<Either<ModuleDef, MacroDef>> {
|
||||
let _p = profile("search_for_imports");
|
||||
let db = self.sema.db;
|
||||
|
||||
let project_results = {
|
||||
// Query dependencies first.
|
||||
let mut candidates: FxHashSet<_> =
|
||||
self.krate.query_external_importables(db, name_to_import).collect();
|
||||
|
||||
// Query the local crate using the symbol index.
|
||||
let local_results = {
|
||||
let mut query = Query::new(name_to_import.to_string());
|
||||
query.exact();
|
||||
query.limit(40);
|
||||
symbol_index::world_symbols(db, query)
|
||||
};
|
||||
let lib_results = {
|
||||
let mut query = Query::new(name_to_import.to_string());
|
||||
query.libs();
|
||||
query.exact();
|
||||
query.limit(40);
|
||||
symbol_index::world_symbols(db, query)
|
||||
symbol_index::crate_symbols(db, self.krate.into(), query)
|
||||
};
|
||||
|
||||
project_results
|
||||
.into_iter()
|
||||
.chain(lib_results.into_iter())
|
||||
.filter_map(|import_candidate| self.get_name_definition(&import_candidate))
|
||||
.filter_map(|name_definition_to_import| match name_definition_to_import {
|
||||
Definition::ModuleDef(module_def) => Some(Either::Left(module_def)),
|
||||
Definition::Macro(macro_def) => Some(Either::Right(macro_def)),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
candidates.extend(
|
||||
local_results
|
||||
.into_iter()
|
||||
.filter_map(|import_candidate| self.get_name_definition(&import_candidate))
|
||||
.filter_map(|name_definition_to_import| match name_definition_to_import {
|
||||
Definition::ModuleDef(module_def) => Some(Either::Left(module_def)),
|
||||
Definition::Macro(macro_def) => Some(Either::Right(macro_def)),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
|
||||
candidates.into_iter().collect()
|
||||
}
|
||||
|
||||
fn get_name_definition(&mut self, import_candidate: &FileSymbol) -> Option<Definition> {
|
||||
|
|
|
|||
|
|
@ -16,10 +16,10 @@ use std::sync::Arc;
|
|||
use hir::db::{AstDatabase, DefDatabase};
|
||||
use ra_db::{
|
||||
salsa::{self, Database, Durability},
|
||||
Canceled, CheckCanceled, CrateId, FileId, FileLoader, FileLoaderDelegate, RelativePath,
|
||||
SourceDatabase, SourceRootId, Upcast,
|
||||
Canceled, CheckCanceled, CrateId, FileId, FileLoader, FileLoaderDelegate, SourceDatabase,
|
||||
Upcast,
|
||||
};
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use crate::{line_index::LineIndex, symbol_index::SymbolsDatabase};
|
||||
|
||||
|
|
@ -36,7 +36,6 @@ use crate::{line_index::LineIndex, symbol_index::SymbolsDatabase};
|
|||
#[derive(Debug)]
|
||||
pub struct RootDatabase {
|
||||
runtime: salsa::Runtime<RootDatabase>,
|
||||
pub(crate) debug_data: Arc<DebugData>,
|
||||
pub last_gc: crate::wasm_shims::Instant,
|
||||
pub last_gc_check: crate::wasm_shims::Instant,
|
||||
}
|
||||
|
|
@ -57,23 +56,12 @@ impl FileLoader for RootDatabase {
|
|||
fn file_text(&self, file_id: FileId) -> Arc<String> {
|
||||
FileLoaderDelegate(self).file_text(file_id)
|
||||
}
|
||||
fn resolve_relative_path(
|
||||
&self,
|
||||
anchor: FileId,
|
||||
relative_path: &RelativePath,
|
||||
) -> Option<FileId> {
|
||||
FileLoaderDelegate(self).resolve_relative_path(anchor, relative_path)
|
||||
fn resolve_path(&self, anchor: FileId, path: &str) -> Option<FileId> {
|
||||
FileLoaderDelegate(self).resolve_path(anchor, path)
|
||||
}
|
||||
fn relevant_crates(&self, file_id: FileId) -> Arc<Vec<CrateId>> {
|
||||
fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>> {
|
||||
FileLoaderDelegate(self).relevant_crates(file_id)
|
||||
}
|
||||
fn resolve_extern_path(
|
||||
&self,
|
||||
extern_id: ra_db::ExternSourceId,
|
||||
relative_path: &RelativePath,
|
||||
) -> Option<FileId> {
|
||||
FileLoaderDelegate(self).resolve_extern_path(extern_id, relative_path)
|
||||
}
|
||||
}
|
||||
|
||||
impl salsa::Database for RootDatabase {
|
||||
|
|
@ -109,7 +97,6 @@ impl RootDatabase {
|
|||
runtime: salsa::Runtime::default(),
|
||||
last_gc: crate::wasm_shims::Instant::now(),
|
||||
last_gc_check: crate::wasm_shims::Instant::now(),
|
||||
debug_data: Default::default(),
|
||||
};
|
||||
db.set_crate_graph_with_durability(Default::default(), Durability::HIGH);
|
||||
db.set_local_roots_with_durability(Default::default(), Durability::HIGH);
|
||||
|
|
@ -132,7 +119,6 @@ impl salsa::ParallelDatabase for RootDatabase {
|
|||
runtime: self.runtime.snapshot(self),
|
||||
last_gc: self.last_gc,
|
||||
last_gc_check: self.last_gc_check,
|
||||
debug_data: Arc::clone(&self.debug_data),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -146,14 +132,3 @@ fn line_index(db: &impl LineIndexDatabase, file_id: FileId) -> Arc<LineIndex> {
|
|||
let text = db.file_text(file_id);
|
||||
Arc::new(LineIndex::new(&*text))
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub(crate) struct DebugData {
|
||||
pub(crate) root_paths: FxHashMap<SourceRootId, String>,
|
||||
}
|
||||
|
||||
impl DebugData {
|
||||
pub(crate) fn merge(&mut self, other: DebugData) {
|
||||
self.root_paths.extend(other.root_paths.into_iter());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,17 +22,6 @@ impl SourceChange {
|
|||
) -> Self {
|
||||
SourceChange { source_file_edits, file_system_edits, is_snippet: false }
|
||||
}
|
||||
|
||||
/// Creates a new SourceChange with the given label,
|
||||
/// containing only the given `SourceFileEdits`.
|
||||
pub fn source_file_edits(edits: Vec<SourceFileEdit>) -> Self {
|
||||
SourceChange { source_file_edits: edits, file_system_edits: vec![], is_snippet: false }
|
||||
}
|
||||
/// Creates a new SourceChange with the given label
|
||||
/// from the given `FileId` and `TextEdit`
|
||||
pub fn source_file_edit_from(file_id: FileId, edit: TextEdit) -> Self {
|
||||
SourceFileEdit { file_id, edit }.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -43,11 +32,13 @@ pub struct SourceFileEdit {
|
|||
|
||||
impl From<SourceFileEdit> for SourceChange {
|
||||
fn from(edit: SourceFileEdit) -> SourceChange {
|
||||
SourceChange {
|
||||
source_file_edits: vec![edit],
|
||||
file_system_edits: Vec::new(),
|
||||
is_snippet: false,
|
||||
}
|
||||
vec![edit].into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<SourceFileEdit>> for SourceChange {
|
||||
fn from(source_file_edits: Vec<SourceFileEdit>) -> SourceChange {
|
||||
SourceChange { source_file_edits, file_system_edits: Vec::new(), is_snippet: false }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,9 +29,10 @@ use std::{
|
|||
};
|
||||
|
||||
use fst::{self, Streamer};
|
||||
use hir::db::DefDatabase;
|
||||
use ra_db::{
|
||||
salsa::{self, ParallelDatabase},
|
||||
FileId, SourceDatabaseExt, SourceRootId,
|
||||
CrateId, FileId, SourceDatabaseExt, SourceRootId,
|
||||
};
|
||||
use ra_syntax::{
|
||||
ast::{self, NameOwner},
|
||||
|
|
@ -110,6 +111,14 @@ fn file_symbols(db: &impl SymbolsDatabase, file_id: FileId) -> Arc<SymbolIndex>
|
|||
Arc::new(SymbolIndex::new(symbols))
|
||||
}
|
||||
|
||||
/// Need to wrap Snapshot to provide `Clone` impl for `map_with`
|
||||
struct Snap(salsa::Snapshot<RootDatabase>);
|
||||
impl Clone for Snap {
|
||||
fn clone(&self) -> Snap {
|
||||
Snap(self.0.snapshot())
|
||||
}
|
||||
}
|
||||
|
||||
// Feature: Workspace Symbol
|
||||
//
|
||||
// Uses fuzzy-search to find types, modules and functions by name across your
|
||||
|
|
@ -132,13 +141,7 @@ fn file_symbols(db: &impl SymbolsDatabase, file_id: FileId) -> Arc<SymbolIndex>
|
|||
// | VS Code | kbd:[Ctrl+T]
|
||||
// |===
|
||||
pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec<FileSymbol> {
|
||||
/// Need to wrap Snapshot to provide `Clone` impl for `map_with`
|
||||
struct Snap(salsa::Snapshot<RootDatabase>);
|
||||
impl Clone for Snap {
|
||||
fn clone(&self) -> Snap {
|
||||
Snap(self.0.snapshot())
|
||||
}
|
||||
}
|
||||
let _p = ra_prof::profile("world_symbols").detail(|| query.query.clone());
|
||||
|
||||
let buf: Vec<Arc<SymbolIndex>> = if query.libs {
|
||||
let snap = Snap(db.snapshot());
|
||||
|
|
@ -173,6 +176,33 @@ pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec<FileSymbol> {
|
|||
query.search(&buf)
|
||||
}
|
||||
|
||||
pub fn crate_symbols(db: &RootDatabase, krate: CrateId, query: Query) -> Vec<FileSymbol> {
|
||||
// FIXME(#4842): This now depends on CrateDefMap, why not build the entire symbol index from
|
||||
// that instead?
|
||||
|
||||
let def_map = db.crate_def_map(krate);
|
||||
let mut files = Vec::new();
|
||||
let mut modules = vec![def_map.root];
|
||||
while let Some(module) = modules.pop() {
|
||||
let data = &def_map[module];
|
||||
files.extend(data.origin.file_id());
|
||||
modules.extend(data.children.values());
|
||||
}
|
||||
|
||||
let snap = Snap(db.snapshot());
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
let buf = files
|
||||
.par_iter()
|
||||
.map_with(snap, |db, &file_id| db.0.file_symbols(file_id))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
#[cfg(feature = "wasm")]
|
||||
let buf = files.iter().map(|&file_id| snap.0.file_symbols(file_id)).collect::<Vec<_>>();
|
||||
|
||||
query.search(&buf)
|
||||
}
|
||||
|
||||
pub fn index_resolve(db: &RootDatabase, name_ref: &ast::NameRef) -> Vec<FileSymbol> {
|
||||
let name = name_ref.text();
|
||||
let mut query = Query::new(name.to_string());
|
||||
|
|
@ -298,9 +328,6 @@ impl Query {
|
|||
let mut stream = op.union();
|
||||
let mut res = Vec::new();
|
||||
while let Some((_, indexed_values)) = stream.next() {
|
||||
if res.len() >= self.limit {
|
||||
break;
|
||||
}
|
||||
for indexed_value in indexed_values {
|
||||
let symbol_index = &indices[indexed_value.index];
|
||||
let (start, end) = SymbolIndex::map_value_to_range(indexed_value.value);
|
||||
|
|
@ -312,7 +339,11 @@ impl Query {
|
|||
if self.exact && symbol.name != self.query {
|
||||
continue;
|
||||
}
|
||||
|
||||
res.push(symbol.clone());
|
||||
if res.len() >= self.limit {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,9 +18,10 @@
|
|||
//! // fn foo() {}
|
||||
//! ```
|
||||
//!
|
||||
//! After adding a new inline-test, run `cargo collect-tests` to extract
|
||||
//! it as a standalone text-fixture into `tests/data/parser/inline`, and
|
||||
//! run `cargo test` once to create the "gold" value.
|
||||
//! After adding a new inline-test, run `cargo xtask codegen` to
|
||||
//! extract it as a standalone text-fixture into
|
||||
//! `crates/ra_syntax/test_data/parser/`, and run `cargo test` once to
|
||||
//! create the "gold" value.
|
||||
//!
|
||||
//! Coding convention: rules like `where_clause` always produce either a
|
||||
//! node or an error, rules like `opt_where_clause` may produce nothing.
|
||||
|
|
|
|||
|
|
@ -118,7 +118,22 @@ pub(super) fn maybe_item(p: &mut Parser, m: Marker, flavor: ItemFlavor) -> Resul
|
|||
&& p.at_contextual_kw("default")
|
||||
&& (match p.nth(1) {
|
||||
T![impl] => true,
|
||||
T![fn] | T![type] => {
|
||||
T![unsafe] => {
|
||||
// test default_unsafe_impl
|
||||
// default unsafe impl Foo {}
|
||||
|
||||
// test default_unsafe_fn
|
||||
// impl T for Foo {
|
||||
// default unsafe fn foo() {}
|
||||
// }
|
||||
if p.nth(2) == T![impl] || p.nth(2) == T![fn] {
|
||||
p.bump_remap(T![default]);
|
||||
p.bump(T![unsafe]);
|
||||
has_mods = true;
|
||||
}
|
||||
false
|
||||
}
|
||||
T![fn] | T![type] | T![const] => {
|
||||
if let ItemFlavor::Mod = flavor {
|
||||
true
|
||||
} else {
|
||||
|
|
@ -198,6 +213,9 @@ pub(super) fn maybe_item(p: &mut Parser, m: Marker, flavor: ItemFlavor) -> Resul
|
|||
// default type T = Bar;
|
||||
// default fn foo() {}
|
||||
// }
|
||||
T![const] => {
|
||||
consts::const_def(p, m);
|
||||
}
|
||||
|
||||
// test unsafe_default_impl
|
||||
// unsafe default impl Foo {}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
use super::*;
|
||||
|
||||
pub(super) const PATH_FIRST: TokenSet =
|
||||
token_set![IDENT, SELF_KW, SUPER_KW, CRATE_KW, COLON, L_ANGLE];
|
||||
token_set![IDENT, T![self], T![super], T![crate], T![:], T![<]];
|
||||
|
||||
pub(super) fn is_path_start(p: &Parser) -> bool {
|
||||
is_use_path_start(p) || p.at(T![<])
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use super::*;
|
|||
|
||||
pub(super) const PATTERN_FIRST: TokenSet = expressions::LITERAL_FIRST
|
||||
.union(paths::PATH_FIRST)
|
||||
.union(token_set![BOX_KW, REF_KW, MUT_KW, L_PAREN, L_BRACK, AMP, UNDERSCORE, MINUS, DOT]);
|
||||
.union(token_set![T![box], T![ref], T![mut], T!['('], T!['['], T![&], T![_], T![-], T![.]]);
|
||||
|
||||
pub(crate) fn pattern(p: &mut Parser) {
|
||||
pattern_r(p, PAT_RECOVERY_SET);
|
||||
|
|
@ -88,7 +88,9 @@ fn atom_pat(p: &mut Parser, recovery_set: TokenSet) -> Option<CompletedMarker> {
|
|||
_ => bind_pat(p, true),
|
||||
},
|
||||
|
||||
_ if paths::is_use_path_start(p) => path_or_macro_pat(p),
|
||||
// test type_path_in_pattern
|
||||
// fn main() { let <_>::Foo = (); }
|
||||
_ if paths::is_path_start(p) => path_or_macro_pat(p),
|
||||
_ if is_literal_pat_start(p) => literal_pat(p),
|
||||
|
||||
T![.] if p.at(T![..]) => dot_dot_pat(p),
|
||||
|
|
@ -138,7 +140,7 @@ fn literal_pat(p: &mut Parser) -> CompletedMarker {
|
|||
// let Bar(..) = ();
|
||||
// }
|
||||
fn path_or_macro_pat(p: &mut Parser) -> CompletedMarker {
|
||||
assert!(paths::is_use_path_start(p));
|
||||
assert!(paths::is_path_start(p));
|
||||
let m = p.start();
|
||||
paths::expr_path(p);
|
||||
let kind = match p.current() {
|
||||
|
|
|
|||
|
|
@ -191,10 +191,14 @@ fn where_predicate(p: &mut Parser) {
|
|||
}
|
||||
_ => {
|
||||
// test where_pred_for
|
||||
// fn test<F>()
|
||||
// fn for_trait<F>()
|
||||
// where
|
||||
// for<'a> F: Fn(&'a str)
|
||||
// { }
|
||||
if p.at(T![for]) {
|
||||
types::for_binder(p);
|
||||
}
|
||||
|
||||
types::type_(p);
|
||||
|
||||
if p.at(T![:]) {
|
||||
|
|
|
|||
|
|
@ -216,19 +216,21 @@ pub(super) fn for_binder(p: &mut Parser) {
|
|||
|
||||
// test for_type
|
||||
// type A = for<'a> fn() -> ();
|
||||
// fn foo<T>(_t: &T) where for<'a> &'a T: Iterator {}
|
||||
// fn bar<T>(_t: &T) where for<'a> &'a mut T: Iterator {}
|
||||
// fn baz<T>(_t: &T) where for<'a> <&'a T as Baz>::Foo: Iterator {}
|
||||
// type B = for<'a> unsafe extern "C" fn(&'a ()) -> ();
|
||||
// type Obj = for<'a> PartialEq<&'a i32>;
|
||||
pub(super) fn for_type(p: &mut Parser) {
|
||||
assert!(p.at(T![for]));
|
||||
let m = p.start();
|
||||
for_binder(p);
|
||||
match p.current() {
|
||||
T![fn] | T![unsafe] | T![extern] => fn_pointer_type(p),
|
||||
T![&] => reference_type(p),
|
||||
_ if paths::is_path_start(p) => path_type_(p, false),
|
||||
_ => p.error("expected a path"),
|
||||
T![fn] | T![unsafe] | T![extern] => {}
|
||||
// OK: legacy trait object format
|
||||
_ if paths::is_use_path_start(p) => {}
|
||||
_ => {
|
||||
p.error("expected a function pointer or path");
|
||||
}
|
||||
}
|
||||
type_no_bounds(p);
|
||||
m.complete(p, FOR_TYPE);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,3 +22,4 @@ cargo_metadata = "0.10.0"
|
|||
difference = "2.0.0"
|
||||
# used as proc macro test target
|
||||
serde_derive = "1.0.106"
|
||||
ra_toolchain = { path = "../ra_toolchain" }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
use crate::dylib;
|
||||
use crate::ProcMacroSrv;
|
||||
pub use difference::Changeset as __Changeset;
|
||||
use ra_proc_macro::ListMacrosTask;
|
||||
use std::str::FromStr;
|
||||
use test_utils::assert_eq_text;
|
||||
|
|
@ -13,7 +12,7 @@ mod fixtures {
|
|||
|
||||
// Use current project metadata to get the proc-macro dylib path
|
||||
pub fn dylib_path(crate_name: &str, version: &str) -> std::path::PathBuf {
|
||||
let command = Command::new("cargo")
|
||||
let command = Command::new(ra_toolchain::cargo())
|
||||
.args(&["check", "--message-format", "json"])
|
||||
.output()
|
||||
.unwrap()
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ impl Default for CargoConfig {
|
|||
fn default() -> Self {
|
||||
CargoConfig {
|
||||
no_default_features: false,
|
||||
all_features: true,
|
||||
all_features: false,
|
||||
features: Vec::new(),
|
||||
load_out_dirs_from_check: false,
|
||||
target: None,
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue