Merge branch 'master' into keyword_completion

# Conflicts:
#	docs/user/generated_features.adoc
This commit is contained in:
Mikhail Rakhmanov 2020-06-13 08:42:15 +02:00
commit 16bbf4ab7f
175 changed files with 7416 additions and 3911 deletions

2
.gitignore vendored
View file

@ -8,3 +8,5 @@ crates/*/target
*.iml
.vscode/settings.json
*.html
generated_assists.adoc
generated_features.adoc

83
Cargo.lock generated
View file

@ -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",
]

View file

@ -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;
}

View file

@ -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 };
}"#,
);
}

View file

@ -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),

View file

@ -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;
}
",
);
}
}

View file

@ -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) {

View file

@ -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) }"#,
);
}
}

View file

@ -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)),

View file

@ -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(

View file

@ -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,
}
}
}
"#,
)
}
}

View file

@ -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(

View file

@ -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,

View file

@ -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(

View file

@ -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))
})
}

View file

@ -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)
}
}

View file

@ -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

View file

@ -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);

View file

@ -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]

View file

@ -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)
}

View file

@ -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())

View 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" }

View file

@ -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 }
}

View file

@ -97,7 +97,7 @@ impl Expander {
let macro_call = InFile::new(self.current_file_id, &macro_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);

View file

@ -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 {

View file

@ -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> {

View file

@ -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())
}
}

View file

@ -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#"

View 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)
"###);
}
}

View file

@ -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,
})
}
}

View file

@ -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(),

View file

@ -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,

View file

@ -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()) {

View file

@ -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")
};

View file

@ -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())
}
}

View file

@ -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 {

View file

@ -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" }

View file

@ -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();

View file

@ -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(&macro_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(&macro_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(&macro_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(),
};

View file

@ -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(&macro_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)?
}
};

View file

@ -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,
}

View file

@ -153,6 +153,7 @@ pub mod known {
str,
// Special names
macro_rules,
doc,
// Components of known path (value or mod name)
std,
core,

View file

@ -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)
}
}

View file

@ -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"

View file

@ -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);

View file

@ -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, "_")?,
}

View file

@ -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,
};

View file

@ -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),
}
}

View file

@ -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

View file

@ -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;

View file

@ -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>>,
}

View file

@ -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)))
}
}

View file

@ -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"),

View file

@ -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()),
}
}
}

View file

@ -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 {

View file

@ -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<|>;
}
",

View file

@ -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!(

View file

@ -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
"###
);
}

View file

@ -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,

View file

@ -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)

View file

@ -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?

View file

@ -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")
}
}
}
}

View file

@ -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 {

View file

@ -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" },
);
}
}

View file

@ -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#"

View file

@ -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..)) {

View file

@ -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
}
}

View file

@ -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",
);
}
}

View file

@ -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: [],
},
),
]
"###);
}
}

View file

@ -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>",
},

View file

@ -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))
})
}

View file

@ -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)]

View file

@ -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]

View 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>() -&gt; <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>) -&gt; <span class="builtin_type">bool</span> {
<span class="bool_literal">true</span>
}
}</code></pre>

View file

@ -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; }

View file

@ -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">// =&gt; "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">// =&gt; "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">// =&gt; "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">// =&gt; "{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">&gt;</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>);

View 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>

View file

@ -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>&lt;<span class="type_param declaration">T</span>&gt; {

View file

@ -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; }

View file

@ -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(())
}

View file

@ -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; }

View 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);
}

View file

@ -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",
}
}
}

View file

@ -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);

View file

@ -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(

View file

@ -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

View file

@ -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,
}

View file

@ -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> {

View file

@ -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());
}
}

View file

@ -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 }
}
}

View file

@ -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;
}
}
}
}

View file

@ -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.

View file

@ -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 {}

View file

@ -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![<])

View file

@ -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() {

View file

@ -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![:]) {

View file

@ -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);
}

View file

@ -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" }

View file

@ -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()

View file

@ -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