use syntax::{
ast::{self, AstNode},
- TextRange, TextSize, T,
+ SyntaxElement, TextRange, TextSize, T,
};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// ```
pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?;
+ let new_contents = adjusted_macro_contents(¯o_call)?;
- if !is_valid_macrocall(¯o_call, "dbg")? {
- return None;
- }
-
- let is_leaf = macro_call.syntax().next_sibling().is_none();
-
+ let macro_text_range = macro_call.syntax().text_range();
let macro_end = if macro_call.semicolon_token().is_some() {
- macro_call.syntax().text_range().end() - TextSize::of(';')
+ macro_text_range.end() - TextSize::of(';')
} else {
- macro_call.syntax().text_range().end()
+ macro_text_range.end()
};
- // macro_range determines what will be deleted and replaced with macro_content
- let macro_range = TextRange::new(macro_call.syntax().text_range().start(), macro_end);
- let paste_instead_of_dbg = {
- let text = macro_call.token_tree()?.syntax().text();
-
- // leafiness determines if we should include the parenthesis or not
- let slice_index: TextRange = if is_leaf {
- // leaf means - we can extract the contents of the dbg! in text
- TextRange::new(TextSize::of('('), text.len() - TextSize::of(')'))
- } else {
- // not leaf - means we should keep the parens
- TextRange::up_to(text.len())
- };
- text.slice(slice_index).to_string()
- };
+ acc.add(
+ AssistId("remove_dbg", AssistKind::Refactor),
+ "Remove dbg!()",
+ macro_text_range,
+ |builder| {
+ builder.replace(TextRange::new(macro_text_range.start(), macro_end), new_contents);
+ },
+ )
+}
- let target = macro_call.syntax().text_range();
- acc.add(AssistId("remove_dbg", AssistKind::Refactor), "Remove dbg!()", target, |builder| {
- builder.replace(macro_range, paste_instead_of_dbg);
- })
+fn adjusted_macro_contents(macro_call: &ast::MacroCall) -> Option<String> {
+ let contents = get_valid_macrocall_contents(¯o_call, "dbg")?;
+ let is_leaf = macro_call.syntax().next_sibling().is_none();
+ let macro_text_with_brackets = macro_call.token_tree()?.syntax().text();
+ let slice_index = if is_leaf || !needs_parentheses_around_macro_contents(contents) {
+ TextRange::new(TextSize::of('('), macro_text_with_brackets.len() - TextSize::of(')'))
+ } else {
+ // leave parenthesis around macro contents to preserve the semantics
+ TextRange::up_to(macro_text_with_brackets.len())
+ };
+ Some(macro_text_with_brackets.slice(slice_index).to_string())
}
/// Verifies that the given macro_call actually matches the given name
-/// and contains proper ending tokens
-fn is_valid_macrocall(macro_call: &ast::MacroCall, macro_name: &str) -> Option<bool> {
+/// and contains proper ending tokens, then returns the contents between the ending tokens
+fn get_valid_macrocall_contents(
+ macro_call: &ast::MacroCall,
+ macro_name: &str,
+) -> Option<Vec<SyntaxElement>> {
let path = macro_call.path()?;
let name_ref = path.segment()?.name_ref()?;
// Make sure it is actually a dbg-macro call, dbg followed by !
let excl = path.syntax().next_sibling_or_token()?;
-
if name_ref.text() != macro_name || excl.kind() != T![!] {
return None;
}
- let node = macro_call.token_tree()?.syntax().clone();
- let first_child = node.first_child_or_token()?;
- let last_child = node.last_child_or_token()?;
+ let mut children_with_tokens = macro_call.token_tree()?.syntax().children_with_tokens();
+ let first_child = children_with_tokens.next()?;
+ let mut contents_between_brackets = children_with_tokens.collect::<Vec<_>>();
+ let last_child = contents_between_brackets.pop()?;
+
+ if contents_between_brackets.is_empty() {
+ None
+ } else {
+ match (first_child.kind(), last_child.kind()) {
+ (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => {
+ Some(contents_between_brackets)
+ }
+ _ => None,
+ }
+ }
+}
+
+fn needs_parentheses_around_macro_contents(macro_contents: Vec<SyntaxElement>) -> bool {
+ if macro_contents.len() < 2 {
+ return false;
+ }
+
+ let mut macro_contents_kind_not_in_brackets = Vec::with_capacity(macro_contents.len());
- match (first_child.kind(), last_child.kind()) {
- (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => Some(true),
- _ => Some(false),
+ let mut first_bracket_in_macro = None;
+ let mut unpaired_brackets_in_contents = Vec::new();
+ for element in macro_contents {
+ match element.kind() {
+ T!['('] | T!['['] | T!['{'] => {
+ if let None = first_bracket_in_macro {
+ first_bracket_in_macro = Some(element.clone())
+ }
+ unpaired_brackets_in_contents.push(element);
+ }
+ T![')'] => {
+ if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['('])
+ {
+ return true;
+ }
+ }
+ T![']'] => {
+ if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['['])
+ {
+ return true;
+ }
+ }
+ T!['}'] => {
+ if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['{'])
+ {
+ return true;
+ }
+ }
+ other_kind => {
+ if unpaired_brackets_in_contents.is_empty() {
+ macro_contents_kind_not_in_brackets.push(other_kind);
+ }
+ }
+ }
}
+
+ !unpaired_brackets_in_contents.is_empty()
+ || matches!(first_bracket_in_macro, Some(bracket) if bracket.kind() != T!['('])
+ || macro_contents_kind_not_in_brackets
+ .into_iter()
+ .any(|macro_contents_kind| macro_contents_kind.is_punct())
}
#[cfg(test)]
);
}
+ #[test]
+ fn remove_dbg_from_non_leaf_simple_expression() {
+ check_assist(
+ remove_dbg,
+ "
+fn main() {
+ let mut a = 1;
+ while dbg!<|>(a) < 10000 {
+ a += 1;
+ }
+}
+",
+ "
+fn main() {
+ let mut a = 1;
+ while a < 10000 {
+ a += 1;
+ }
+}
+",
+ );
+ }
+
#[test]
fn test_remove_dbg_keep_expression() {
check_assist(
r#"let res = <|>dbg!(a + b).foo();"#,
r#"let res = (a + b).foo();"#,
);
+
+ check_assist(remove_dbg, r#"let res = <|>dbg!(2 + 2) * 5"#, r#"let res = (2 + 2) * 5"#);
}
#[test]
use rustc_hash::{FxHashMap, FxHashSet};
use syntax::SmolStr;
use tt::TokenExpander;
-use vfs::file_set::FileSet;
+use vfs::{file_set::FileSet, VfsPath};
pub use vfs::FileId;
pub fn new_library(file_set: FileSet) -> SourceRoot {
SourceRoot { is_library: true, file_set }
}
+ pub fn path_for_file(&self, file: &FileId) -> Option<&VfsPath> {
+ self.file_set.path_for_file(file)
+ }
+ pub fn file_for_path(&self, path: &VfsPath) -> Option<&FileId> {
+ self.file_set.file_for_path(path)
+ }
pub fn iter(&self) -> impl Iterator<Item = FileId> + '_ {
self.file_set.iter()
}
mod complete_postfix;
mod complete_macro_in_item_position;
mod complete_trait_impl;
+mod complete_mod;
use ide_db::RootDatabase;
complete_postfix::complete_postfix(&mut acc, &ctx);
complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx);
complete_trait_impl::complete_trait_impl(&mut acc, &ctx);
+ complete_mod::complete_mod(&mut acc, &ctx);
Some(acc)
}
};
pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
+ if ctx.mod_declaration_under_caret.is_some() {
+ return None;
+ }
+
let attribute = ctx.attribute_under_caret.as_ref()?;
match (attribute.path(), attribute.token_tree()) {
(Some(path), Some(token_tree)) if path.to_string() == "derive" => {
--- /dev/null
+//! Completes mod declarations.
+
+use base_db::{SourceDatabaseExt, VfsPath};
+use hir::{Module, ModuleSource};
+use ide_db::RootDatabase;
+use rustc_hash::FxHashSet;
+
+use crate::{CompletionItem, CompletionItemKind};
+
+use super::{
+ completion_context::CompletionContext, completion_item::CompletionKind,
+ completion_item::Completions,
+};
+
+/// Complete mod declaration, i.e. `mod <|> ;`
+pub(super) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
+ let mod_under_caret = match &ctx.mod_declaration_under_caret {
+ Some(mod_under_caret) if mod_under_caret.item_list().is_some() => return None,
+ Some(mod_under_caret) => mod_under_caret,
+ None => return None,
+ };
+
+ let _p = profile::span("completion::complete_mod");
+
+ let current_module = ctx.scope.module()?;
+
+ let module_definition_file =
+ current_module.definition_source(ctx.db).file_id.original_file(ctx.db);
+ let source_root = ctx.db.source_root(ctx.db.file_source_root(module_definition_file));
+ let directory_to_look_for_submodules = directory_to_look_for_submodules(
+ current_module,
+ ctx.db,
+ source_root.path_for_file(&module_definition_file)?,
+ )?;
+
+ let existing_mod_declarations = current_module
+ .children(ctx.db)
+ .filter_map(|module| Some(module.name(ctx.db)?.to_string()))
+ .collect::<FxHashSet<_>>();
+
+ let module_declaration_file =
+ current_module.declaration_source(ctx.db).map(|module_declaration_source_file| {
+ module_declaration_source_file.file_id.original_file(ctx.db)
+ });
+
+ source_root
+ .iter()
+ .filter(|submodule_candidate_file| submodule_candidate_file != &module_definition_file)
+ .filter(|submodule_candidate_file| {
+ Some(submodule_candidate_file) != module_declaration_file.as_ref()
+ })
+ .filter_map(|submodule_file| {
+ let submodule_path = source_root.path_for_file(&submodule_file)?;
+ let directory_with_submodule = submodule_path.parent()?;
+ match submodule_path.name_and_extension()? {
+ ("lib", Some("rs")) | ("main", Some("rs")) => None,
+ ("mod", Some("rs")) => {
+ if directory_with_submodule.parent()? == directory_to_look_for_submodules {
+ match directory_with_submodule.name_and_extension()? {
+ (directory_name, None) => Some(directory_name.to_owned()),
+ _ => None,
+ }
+ } else {
+ None
+ }
+ }
+ (file_name, Some("rs"))
+ if directory_with_submodule == directory_to_look_for_submodules =>
+ {
+ Some(file_name.to_owned())
+ }
+ _ => None,
+ }
+ })
+ .filter(|name| !existing_mod_declarations.contains(name))
+ .for_each(|submodule_name| {
+ let mut label = submodule_name;
+ if mod_under_caret.semicolon_token().is_none() {
+ label.push(';')
+ }
+ acc.add(
+ CompletionItem::new(CompletionKind::Magic, ctx.source_range(), &label)
+ .kind(CompletionItemKind::Module),
+ )
+ });
+
+ Some(())
+}
+
+fn directory_to_look_for_submodules(
+ module: Module,
+ db: &RootDatabase,
+ module_file_path: &VfsPath,
+) -> Option<VfsPath> {
+ let directory_with_module_path = module_file_path.parent()?;
+ let base_directory = match module_file_path.name_and_extension()? {
+ ("mod", Some("rs")) | ("lib", Some("rs")) | ("main", Some("rs")) => {
+ Some(directory_with_module_path)
+ }
+ (regular_rust_file_name, Some("rs")) => {
+ if matches!(
+ (
+ directory_with_module_path
+ .parent()
+ .as_ref()
+ .and_then(|path| path.name_and_extension()),
+ directory_with_module_path.name_and_extension(),
+ ),
+ (Some(("src", None)), Some(("bin", None)))
+ ) {
+ // files in /src/bin/ can import each other directly
+ Some(directory_with_module_path)
+ } else {
+ directory_with_module_path.join(regular_rust_file_name)
+ }
+ }
+ _ => None,
+ }?;
+
+ let mut resulting_path = base_directory;
+ for module in module_chain_to_containing_module_file(module, db) {
+ if let Some(name) = module.name(db) {
+ resulting_path = resulting_path.join(&name.to_string())?;
+ }
+ }
+
+ Some(resulting_path)
+}
+
+fn module_chain_to_containing_module_file(
+ current_module: Module,
+ db: &RootDatabase,
+) -> Vec<Module> {
+ let mut path = Vec::new();
+
+ let mut current_module = Some(current_module);
+ while let Some(ModuleSource::Module(_)) =
+ current_module.map(|module| module.definition_source(db).value)
+ {
+ if let Some(module) = current_module {
+ path.insert(0, module);
+ current_module = module.parent(db);
+ } else {
+ current_module = None;
+ }
+ }
+
+ path
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::completion::{test_utils::completion_list, CompletionKind};
+ use expect_test::{expect, Expect};
+
+ fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture, CompletionKind::Magic);
+ expect.assert_eq(&actual);
+ }
+
+ #[test]
+ fn lib_module_completion() {
+ check(
+ r#"
+ //- /lib.rs
+ mod <|>
+ //- /foo.rs
+ fn foo() {}
+ //- /foo/ignored_foo.rs
+ fn ignored_foo() {}
+ //- /bar/mod.rs
+ fn bar() {}
+ //- /bar/ignored_bar.rs
+ fn ignored_bar() {}
+ "#,
+ expect![[r#"
+ md bar;
+ md foo;
+ "#]],
+ );
+ }
+
+ #[test]
+ fn no_module_completion_with_module_body() {
+ check(
+ r#"
+ //- /lib.rs
+ mod <|> {
+
+ }
+ //- /foo.rs
+ fn foo() {}
+ "#,
+ expect![[r#""#]],
+ );
+ }
+
+ #[test]
+ fn main_module_completion() {
+ check(
+ r#"
+ //- /main.rs
+ mod <|>
+ //- /foo.rs
+ fn foo() {}
+ //- /foo/ignored_foo.rs
+ fn ignored_foo() {}
+ //- /bar/mod.rs
+ fn bar() {}
+ //- /bar/ignored_bar.rs
+ fn ignored_bar() {}
+ "#,
+ expect![[r#"
+ md bar;
+ md foo;
+ "#]],
+ );
+ }
+
+ #[test]
+ fn main_test_module_completion() {
+ check(
+ r#"
+ //- /main.rs
+ mod tests {
+ mod <|>;
+ }
+ //- /tests/foo.rs
+ fn foo() {}
+ "#,
+ expect![[r#"
+ md foo
+ "#]],
+ );
+ }
+
+ #[test]
+ fn directly_nested_module_completion() {
+ check(
+ r#"
+ //- /lib.rs
+ mod foo;
+ //- /foo.rs
+ mod <|>;
+ //- /foo/bar.rs
+ fn bar() {}
+ //- /foo/bar/ignored_bar.rs
+ fn ignored_bar() {}
+ //- /foo/baz/mod.rs
+ fn baz() {}
+ //- /foo/moar/ignored_moar.rs
+ fn ignored_moar() {}
+ "#,
+ expect![[r#"
+ md bar
+ md baz
+ "#]],
+ );
+ }
+
+ #[test]
+ fn nested_in_source_module_completion() {
+ check(
+ r#"
+ //- /lib.rs
+ mod foo;
+ //- /foo.rs
+ mod bar {
+ mod <|>
+ }
+ //- /foo/bar/baz.rs
+ fn baz() {}
+ "#,
+ expect![[r#"
+ md baz;
+ "#]],
+ );
+ }
+
+ // FIXME binary modules are not supported in tests properly
+ // Binary modules are a bit special, they allow importing the modules from `/src/bin`
+ // and that's why are good to test two things:
+ // * no cycles are allowed in mod declarations
+ // * no modules from the parent directory are proposed
+ // Unfortunately, binary modules support is in cargo not rustc,
+ // hence the test does not work now
+ //
+ // #[test]
+ // fn regular_bin_module_completion() {
+ // check(
+ // r#"
+ // //- /src/bin.rs
+ // fn main() {}
+ // //- /src/bin/foo.rs
+ // mod <|>
+ // //- /src/bin/bar.rs
+ // fn bar() {}
+ // //- /src/bin/bar/bar_ignored.rs
+ // fn bar_ignored() {}
+ // "#,
+ // expect![[r#"
+ // md bar;
+ // "#]],
+ // );
+ // }
+
+ #[test]
+ fn already_declared_bin_module_completion_omitted() {
+ check(
+ r#"
+ //- /src/bin.rs
+ fn main() {}
+ //- /src/bin/foo.rs
+ mod <|>
+ //- /src/bin/bar.rs
+ mod foo;
+ fn bar() {}
+ //- /src/bin/bar/bar_ignored.rs
+ fn bar_ignored() {}
+ "#,
+ expect![[r#""#]],
+ );
+ }
+}
None => return,
};
- if ctx.attribute_under_caret.is_some() {
+ if ctx.attribute_under_caret.is_some() || ctx.mod_declaration_under_caret.is_some() {
return;
}
if ctx.record_lit_syntax.is_some()
|| ctx.record_pat_syntax.is_some()
|| ctx.attribute_under_caret.is_some()
+ || ctx.mod_declaration_under_caret.is_some()
{
return;
}
pub(super) is_path_type: bool,
pub(super) has_type_args: bool,
pub(super) attribute_under_caret: Option<ast::Attr>,
+ pub(super) mod_declaration_under_caret: Option<ast::Module>,
pub(super) unsafe_is_prev: bool,
pub(super) if_is_prev: bool,
pub(super) block_expr_parent: bool,
has_type_args: false,
dot_receiver_is_ambiguous_float_literal: false,
attribute_under_caret: None,
+ mod_declaration_under_caret: None,
unsafe_is_prev: false,
in_loop_body: false,
ref_pat_parent: false,
self.trait_as_prev_sibling = has_trait_as_prev_sibling(syntax_element.clone());
self.is_match_arm = is_match_arm(syntax_element.clone());
self.has_item_list_or_source_file_parent =
- has_item_list_or_source_file_parent(syntax_element);
+ has_item_list_or_source_file_parent(syntax_element.clone());
+ self.mod_declaration_under_caret =
+ find_node_at_offset::<ast::Module>(&file_with_fake_ident, offset)
+ .filter(|module| module.item_list().is_none());
}
fn fill(
.filter(|it| it.kind() == IF_KW)
.is_some()
}
+
#[test]
fn test_if_is_prev() {
check_pattern_is_applicable(r"if l<|>", if_is_prev);
#[cfg(test)]
mod tests;
-use hir::{Name, Semantics, VariantDef};
+use hir::{Local, Name, Semantics, VariantDef};
use ide_db::{
defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass},
RootDatabase,
use syntax::{
ast::{self, HasFormatSpecifier},
AstNode, AstToken, Direction, NodeOrToken, SyntaxElement,
- SyntaxKind::*,
- TextRange, WalkEvent, T,
+ SyntaxKind::{self, *},
+ SyntaxNode, SyntaxToken, TextRange, WalkEvent, T,
};
use crate::FileId;
Some(TextRange::new(range_start, range_end))
}
+/// Returns true if the parent nodes of `node` all match the `SyntaxKind`s in `kinds` exactly.
+fn parents_match(mut node: NodeOrToken<SyntaxNode, SyntaxToken>, mut kinds: &[SyntaxKind]) -> bool {
+ while let (Some(parent), [kind, rest @ ..]) = (&node.parent(), kinds) {
+ if parent.kind() != *kind {
+ return false;
+ }
+
+ // FIXME: Would be nice to get parent out of the match, but binding by-move and by-value
+ // in the same pattern is unstable: rust-lang/rust#68354.
+ node = node.parent().unwrap().into();
+ kinds = rest;
+ }
+
+ // Only true if we matched all expected kinds
+ kinds.len() == 0
+}
+
+fn is_consumed_lvalue(
+ node: NodeOrToken<SyntaxNode, SyntaxToken>,
+ local: &Local,
+ db: &RootDatabase,
+) -> bool {
+ // When lvalues are passed as arguments and they're not Copy, then mark them as Consuming.
+ parents_match(node, &[PATH_SEGMENT, PATH, PATH_EXPR, ARG_LIST]) && !local.ty(db).is_copy(db)
+}
+
fn highlight_element(
sema: &Semantics<RootDatabase>,
bindings_shadow_count: &mut FxHashMap<Name, u32>,
let mut h = highlight_def(db, def);
+ if let Definition::Local(local) = &def {
+ if is_consumed_lvalue(name_ref.syntax().clone().into(), local, db) {
+ h |= HighlightModifier::Consuming;
+ }
+ }
+
if let Some(parent) = name_ref.syntax().parent() {
if matches!(parent.kind(), FIELD_EXPR | RECORD_PAT_FIELD) {
if let Definition::Field(field) = def {
.and_then(ast::SelfParam::cast)
.and_then(|p| p.mut_token())
.is_some();
- // closure to enforce lazyness
- let self_path = || {
- sema.resolve_path(&element.parent()?.parent().and_then(ast::Path::cast)?)
- };
+ let self_path = &element
+ .parent()
+ .as_ref()
+ .and_then(SyntaxNode::parent)
+ .and_then(ast::Path::cast)
+ .and_then(|p| sema.resolve_path(&p));
+ let mut h = HighlightTag::SelfKeyword.into();
if self_param_is_mut
- || matches!(self_path(),
+ || matches!(self_path,
Some(hir::PathResolution::Local(local))
if local.is_self(db)
&& (local.is_mut(db) || local.ty(db).is_mutable_reference())
)
{
- HighlightTag::SelfKeyword | HighlightModifier::Mutable
- } else {
- HighlightTag::SelfKeyword.into()
+ h |= HighlightModifier::Mutable
}
+
+ if let Some(hir::PathResolution::Local(local)) = self_path {
+ if is_consumed_lvalue(element, &local, db) {
+ h |= HighlightModifier::Consuming;
+ }
+ }
+
+ h
}
T![ref] => element
.parent()
<span class="punctuation">}</span>
<span class="keyword">impl</span> <span class="struct">Foo</span> <span class="punctuation">{</span>
- <span class="keyword">fn</span> <span class="function declaration">baz</span><span class="punctuation">(</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">)</span> <span class="operator">-></span> <span class="builtin_type">i32</span> <span class="punctuation">{</span>
- <span class="self_keyword">self</span><span class="punctuation">.</span><span class="field">x</span>
+ <span class="keyword">fn</span> <span class="function declaration">baz</span><span class="punctuation">(</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">,</span> <span class="value_param declaration">f</span><span class="punctuation">:</span> <span class="struct">Foo</span><span class="punctuation">)</span> <span class="operator">-></span> <span class="builtin_type">i32</span> <span class="punctuation">{</span>
+ <span class="value_param">f</span><span class="punctuation">.</span><span class="function consuming">baz</span><span class="punctuation">(</span><span class="self_keyword consuming">self</span><span class="punctuation">)</span>
<span class="punctuation">}</span>
<span class="keyword">fn</span> <span class="function declaration">qux</span><span class="punctuation">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">)</span> <span class="punctuation">{</span>
<span class="punctuation">}</span>
<span class="keyword">impl</span> <span class="struct">FooCopy</span> <span class="punctuation">{</span>
- <span class="keyword">fn</span> <span class="function declaration">baz</span><span class="punctuation">(</span><span class="self_keyword">self</span><span class="punctuation">)</span> <span class="operator">-></span> <span class="builtin_type">u32</span> <span class="punctuation">{</span>
- <span class="self_keyword">self</span><span class="punctuation">.</span><span class="field">x</span>
+ <span class="keyword">fn</span> <span class="function declaration">baz</span><span class="punctuation">(</span><span class="self_keyword">self</span><span class="punctuation">,</span> <span class="value_param declaration">f</span><span class="punctuation">:</span> <span class="struct">FooCopy</span><span class="punctuation">)</span> <span class="operator">-></span> <span class="builtin_type">u32</span> <span class="punctuation">{</span>
+ <span class="value_param">f</span><span class="punctuation">.</span><span class="function">baz</span><span class="punctuation">(</span><span class="self_keyword">self</span><span class="punctuation">)</span>
<span class="punctuation">}</span>
<span class="keyword">fn</span> <span class="function declaration">qux</span><span class="punctuation">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">)</span> <span class="punctuation">{</span>
<span class="variable">y</span><span class="punctuation">;</span>
<span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">foo</span> <span class="operator">=</span> <span class="struct">Foo</span> <span class="punctuation">{</span> <span class="field">x</span><span class="punctuation">,</span> <span class="field">y</span><span class="punctuation">:</span> <span class="variable mutable">x</span> <span class="punctuation">}</span><span class="punctuation">;</span>
+ <span class="keyword">let</span> <span class="variable declaration">foo2</span> <span class="operator">=</span> <span class="variable mutable">foo</span><span class="punctuation">.</span><span class="unresolved_reference">clone</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
<span class="variable mutable">foo</span><span class="punctuation">.</span><span class="function">quop</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
<span class="variable mutable">foo</span><span class="punctuation">.</span><span class="function mutable">qux</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
- <span class="variable mutable">foo</span><span class="punctuation">.</span><span class="function consuming">baz</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
+ <span class="variable mutable">foo</span><span class="punctuation">.</span><span class="function consuming">baz</span><span class="punctuation">(</span><span class="variable consuming">foo2</span><span class="punctuation">)</span><span class="punctuation">;</span>
<span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">copy</span> <span class="operator">=</span> <span class="struct">FooCopy</span> <span class="punctuation">{</span> <span class="field">x</span> <span class="punctuation">}</span><span class="punctuation">;</span>
<span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function">quop</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
<span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function mutable">qux</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
- <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function">baz</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
+ <span class="variable mutable">copy</span><span class="punctuation">.</span><span class="function">baz</span><span class="punctuation">(</span><span class="variable mutable">copy</span><span class="punctuation">)</span><span class="punctuation">;</span>
<span class="punctuation">}</span>
<span class="keyword">enum</span> <span class="enum declaration">Option</span><span class="punctuation"><</span><span class="type_param declaration">T</span><span class="punctuation">></span> <span class="punctuation">{</span>
}
impl Foo {
- fn baz(mut self) -> i32 {
- self.x
+ fn baz(mut self, f: Foo) -> i32 {
+ f.baz(self)
}
fn qux(&mut self) {
}
impl FooCopy {
- fn baz(self) -> u32 {
- self.x
+ fn baz(self, f: FooCopy) -> u32 {
+ f.baz(self)
}
fn qux(&mut self) {
y;
let mut foo = Foo { x, y: x };
+ let foo2 = foo.clone();
foo.quop();
foo.qux();
- foo.baz();
+ foo.baz(foo2);
let mut copy = FooCopy { x };
copy.quop();
copy.qux();
- copy.baz();
+ copy.baz(copy);
}
enum Option<T> {
/// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`.
Cargo { cargo: CargoWorkspace, sysroot: Sysroot },
/// Project workspace was manually specified using a `rust-project.json` file.
- Json { project: ProjectJson },
+ Json { project: ProjectJson, sysroot: Option<Sysroot> },
}
impl fmt::Debug for ProjectWorkspace {
.field("n_packages", &cargo.packages().len())
.field("n_sysroot_crates", &sysroot.crates().len())
.finish(),
- ProjectWorkspace::Json { project } => {
+ ProjectWorkspace::Json { project, sysroot } => {
let mut debug_struct = f.debug_struct("Json");
debug_struct.field("n_crates", &project.n_crates());
- if let Some(sysroot) = &project.sysroot {
+ if let Some(sysroot) = sysroot {
debug_struct.field("n_sysroot_crates", &sysroot.crates().len());
}
debug_struct.finish()
})?;
let project_location = project_json.parent().unwrap().to_path_buf();
let project = ProjectJson::new(&project_location, data);
- ProjectWorkspace::Json { project }
+ let sysroot = match &project.sysroot_src {
+ Some(path) => Some(Sysroot::load(path)?),
+ None => None,
+ };
+ ProjectWorkspace::Json { project, sysroot }
}
ProjectManifest::CargoToml(cargo_toml) => {
let cargo_version = utf8_stdout({
Ok(res)
}
+ pub fn load_inline(project_json: ProjectJson) -> Result<ProjectWorkspace> {
+ let sysroot = match &project_json.sysroot_src {
+ Some(path) => Some(Sysroot::load(path)?),
+ None => None,
+ };
+
+ Ok(ProjectWorkspace::Json { project: project_json, sysroot })
+ }
+
/// Returns the roots for the current `ProjectWorkspace`
/// The return type contains the path and whether or not
/// the root is a member of the current workspace
pub fn to_roots(&self) -> Vec<PackageRoot> {
match self {
- ProjectWorkspace::Json { project } => project
+ ProjectWorkspace::Json { project, sysroot } => project
.crates()
.map(|(_, krate)| PackageRoot {
is_member: krate.is_workspace_member,
})
.collect::<FxHashSet<_>>()
.into_iter()
- .chain(project.sysroot.as_ref().into_iter().flat_map(|sysroot| {
+ .chain(sysroot.as_ref().into_iter().flat_map(|sysroot| {
sysroot.crates().map(move |krate| PackageRoot {
is_member: false,
include: vec![sysroot[krate].root_dir().to_path_buf()],
pub fn proc_macro_dylib_paths(&self) -> Vec<AbsPathBuf> {
match self {
- ProjectWorkspace::Json { project } => project
+ ProjectWorkspace::Json { project, sysroot: _ } => project
.crates()
.filter_map(|(_, krate)| krate.proc_macro_dylib_path.as_ref())
.cloned()
) -> CrateGraph {
let mut crate_graph = CrateGraph::default();
match self {
- ProjectWorkspace::Json { project } => {
- let sysroot_dps = project
- .sysroot
+ ProjectWorkspace::Json { project, sysroot } => {
+ let sysroot_dps = sysroot
.as_ref()
.map(|sysroot| sysroot_to_crate_graph(&mut crate_graph, sysroot, target, load));
use rustc_hash::FxHashMap;
use serde::{de, Deserialize};
-use crate::{cfg_flag::CfgFlag, Sysroot};
+use crate::cfg_flag::CfgFlag;
/// Roots and crates that compose this Rust project.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ProjectJson {
- pub(crate) sysroot: Option<Sysroot>,
+ pub(crate) sysroot_src: Option<AbsPathBuf>,
crates: Vec<Crate>,
}
impl ProjectJson {
pub fn new(base: &AbsPath, data: ProjectJsonData) -> ProjectJson {
ProjectJson {
- sysroot: data.sysroot_src.map(|it| base.join(it)).map(|it| Sysroot::load(&it)),
+ sysroot_src: data.sysroot_src.map(|it| base.join(it)),
crates: data
.crates
.into_iter()
pub fn discover(cargo_toml: &AbsPath) -> Result<Sysroot> {
let current_dir = cargo_toml.parent().unwrap();
let sysroot_src_dir = discover_sysroot_src_dir(current_dir)?;
- let res = Sysroot::load(&sysroot_src_dir);
+ let res = Sysroot::load(&sysroot_src_dir)?;
Ok(res)
}
- pub fn load(sysroot_src_dir: &AbsPath) -> Sysroot {
+ pub fn load(sysroot_src_dir: &AbsPath) -> Result<Sysroot> {
let mut sysroot = Sysroot { crates: Arena::default() };
for name in SYSROOT_CRATES.trim().lines() {
}
}
- sysroot
+ if sysroot.by_name("core").is_none() {
+ anyhow::bail!(
+ "could not find libcore in sysroot path `{}`",
+ sysroot_src_dir.as_ref().display()
+ );
+ }
+
+ Ok(sysroot)
}
fn by_name(&self, name: &str) -> Option<SysrootCrate> {
},
fixes: [],
},
+ MappedRustDiagnostic {
+ url: "file:///test/crates/hir_def/src/path.rs",
+ diagnostic: Diagnostic {
+ range: Range {
+ start: Position {
+ line: 264,
+ character: 8,
+ },
+ end: Position {
+ line: 264,
+ character: 76,
+ },
+ },
+ severity: Some(
+ Error,
+ ),
+ code: None,
+ source: Some(
+ "rustc",
+ ),
+ message: "Please register your known path in the path module",
+ related_information: Some(
+ [
+ DiagnosticRelatedInformation {
+ location: Location {
+ uri: "file:///test/crates/hir_def/src/data.rs",
+ range: Range {
+ start: Position {
+ line: 79,
+ character: 15,
+ },
+ end: Position {
+ line: 79,
+ character: 41,
+ },
+ },
+ },
+ message: "Exact error occured here",
+ },
+ ],
+ ),
+ tags: None,
+ },
+ fixes: [],
+ },
]
// If error occurs from macro expansion, add related info pointing to
// where the error originated
- if !is_from_macro(&primary_span.file_name) && primary_span.expansion.is_some() {
- related_information.push(lsp_types::DiagnosticRelatedInformation {
- location: location_naive(workspace_root, &primary_span),
- message: "Error originated from macro here".to_string(),
- });
- }
+ // Also, we would generate an additional diagnostic, so that exact place of macro
+ // will be highlighted in the error origin place.
+ let additional_diagnostic =
+ if !is_from_macro(&primary_span.file_name) && primary_span.expansion.is_some() {
+ let in_macro_location = location_naive(workspace_root, &primary_span);
+
+ // Add related information for the main disagnostic.
+ related_information.push(lsp_types::DiagnosticRelatedInformation {
+ location: in_macro_location.clone(),
+ message: "Error originated from macro here".to_string(),
+ });
+
+ // For the additional in-macro diagnostic we add the inverse message pointing to the error location in code.
+ let information_for_additional_diagnostic =
+ vec![lsp_types::DiagnosticRelatedInformation {
+ location: location.clone(),
+ message: "Exact error occured here".to_string(),
+ }];
+
+ let diagnostic = lsp_types::Diagnostic {
+ range: in_macro_location.range,
+ severity,
+ code: code.clone().map(lsp_types::NumberOrString::String),
+ source: Some(source.clone()),
+ message: message.clone(),
+ related_information: Some(information_for_additional_diagnostic),
+ tags: if tags.is_empty() { None } else { Some(tags.clone()) },
+ };
+
+ Some(MappedRustDiagnostic {
+ url: in_macro_location.uri,
+ diagnostic,
+ fixes: fixes.clone(),
+ })
+ } else {
+ None
+ };
let diagnostic = lsp_types::Diagnostic {
range: location.range,
tags: if tags.is_empty() { None } else { Some(tags.clone()) },
};
- MappedRustDiagnostic { url: location.uri, diagnostic, fixes: fixes.clone() }
+ let main_diagnostic =
+ MappedRustDiagnostic { url: location.uri, diagnostic, fixes: fixes.clone() };
+ match additional_diagnostic {
+ None => vec![main_diagnostic],
+ Some(additional_diagnostic) => vec![main_diagnostic, additional_diagnostic],
+ }
})
+ .flatten()
.collect()
}
)
}
LinkedProject::InlineJsonProject(it) => {
- Ok(project_model::ProjectWorkspace::Json { project: it.clone() })
+ project_model::ProjectWorkspace::load_inline(it.clone())
}
})
.collect::<Vec<_>>();
let mut base = self.paths[&anchor].clone();
base.pop();
let path = base.join(path)?;
- let res = self.files.get(&path).copied();
- res
+ self.files.get(&path).copied()
+ }
+
+ pub fn file_for_path(&self, path: &VfsPath) -> Option<&FileId> {
+ self.files.get(path)
}
+
+ pub fn path_for_file(&self, file: &FileId) -> Option<&VfsPath> {
+ self.paths.get(file)
+ }
+
pub fn insert(&mut self, file_id: FileId, path: VfsPath) {
self.files.insert(path.clone(), file_id);
self.paths.insert(file_id, path);
}
+
pub fn iter(&self) -> impl Iterator<Item = FileId> + '_ {
self.paths.keys().copied()
}
(VfsPathRepr::VirtualPath(_), _) => false,
}
}
+ pub fn parent(&self) -> Option<VfsPath> {
+ let mut parent = self.clone();
+ if parent.pop() {
+ Some(parent)
+ } else {
+ None
+ }
+ }
+
+ pub fn name_and_extension(&self) -> Option<(&str, Option<&str>)> {
+ match &self.0 {
+ VfsPathRepr::PathBuf(p) => Some((
+ p.file_stem()?.to_str()?,
+ p.extension().and_then(|extension| extension.to_str()),
+ )),
+ VfsPathRepr::VirtualPath(p) => p.name_and_extension(),
+ }
+ }
// Don't make this `pub`
pub(crate) fn encode(&self, buf: &mut Vec<u8>) {
res.0 = format!("{}/{}", res.0, path);
Some(res)
}
+
+ pub fn name_and_extension(&self) -> Option<(&str, Option<&str>)> {
+ let file_path = if self.0.ends_with('/') { &self.0[..&self.0.len() - 1] } else { &self.0 };
+ let file_name = match file_path.rfind('/') {
+ Some(position) => &file_path[position + 1..],
+ None => file_path,
+ };
+
+ if file_name.is_empty() {
+ None
+ } else {
+ let mut file_stem_and_extension = file_name.rsplitn(2, '.');
+ let extension = file_stem_and_extension.next();
+ let file_stem = file_stem_and_extension.next();
+
+ match (file_stem, extension) {
+ (None, None) => None,
+ (None, Some(_)) | (Some(""), Some(_)) => Some((file_name, None)),
+ (Some(file_stem), extension) => Some((file_stem, extension)),
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn virtual_path_extensions() {
+ assert_eq!(VirtualPath("/".to_string()).name_and_extension(), None);
+ assert_eq!(
+ VirtualPath("/directory".to_string()).name_and_extension(),
+ Some(("directory", None))
+ );
+ assert_eq!(
+ VirtualPath("/directory/".to_string()).name_and_extension(),
+ Some(("directory", None))
+ );
+ assert_eq!(
+ VirtualPath("/directory/file".to_string()).name_and_extension(),
+ Some(("file", None))
+ );
+ assert_eq!(
+ VirtualPath("/directory/.file".to_string()).name_and_extension(),
+ Some((".file", None))
+ );
+ assert_eq!(
+ VirtualPath("/directory/.file.rs".to_string()).name_and_extension(),
+ Some((".file", Some("rs")))
+ );
+ assert_eq!(
+ VirtualPath("/directory/file.rs".to_string()).name_and_extension(),
+ Some(("file", Some("rs")))
+ );
+ }
}