10105: RfC: Use `todo!()` instead of `()` for missing fields r=jonas-schievink a=jo-so
Most commonly a field of a struct can be initialized with its default value than an empty tuple.
Co-authored-by: Jörg Sommer <joerg@jo-so.de>
* text=auto eol=lf
+# git grep shouldn't match entries in this benchmark data
+bench_data/** binary
crates/syntax/test_data/** -text eof=LF
# Older git versions try to fix line endings on images, this prevents it.
*.png binary
"profile",
"rustc-hash",
"salsa",
- "stdx",
"syntax",
"test_utils",
"tt",
[[package]]
name = "chalk-derive"
-version = "0.70.0"
+version = "0.71.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b29a4ef88867aee29bc709976d9b0a20ddb2c52aeca0bd635893a74fa77d7f6"
+checksum = "059cce4ba41e57dd82f55b348d3e83cb30fd142479d00287f08c4ae66f9e7197"
dependencies = [
"proc-macro2",
"quote",
[[package]]
name = "chalk-ir"
-version = "0.70.0"
+version = "0.71.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3efd5b260d23af3daebae111ea4066604dd3cdb18ca610358ab2e2a7aab71461"
+checksum = "c0f9b041f3fcc136dbf8a92cef5f6ac743f9800467763502f5924349b781cbe0"
dependencies = [
"bitflags",
"chalk-derive",
[[package]]
name = "chalk-recursive"
-version = "0.70.0"
+version = "0.71.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c8089c69051fa6bfdadb67f6dc951881f99fef6814e0e0c27c47218e34d8adb"
+checksum = "b1dd77179b3310dea3838b73e0f5990fcf4d1c00bfd2bc43d984faa8d2783ff1"
dependencies = [
"chalk-derive",
"chalk-ir",
[[package]]
name = "chalk-solve"
-version = "0.70.0"
+version = "0.71.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ad0c276126d7787577d22f82785d8f2795318ad30349e66b487a6d6a591e351"
+checksum = "0c8ff6810c6bcac76950d1d292f71862e5757f483b8745a9186e649076b913be"
dependencies = [
"chalk-derive",
"chalk-ir",
"hir_def",
"hir_expand",
"hir_ty",
- "indexmap",
"itertools",
"once_cell",
"profile",
"profile",
"rustc-hash",
"syntax",
- "test_utils",
"tracing",
"tt",
]
"syntax",
"test_utils",
"text_edit",
- "tracing",
"xshell",
]
name = "ide_db"
version = "0.0.0"
dependencies = [
+ "arrayvec",
"base_db",
"cov-mark",
"either",
"cov-mark",
"expect-test",
"parser",
- "profile",
"rustc-hash",
"smallvec",
"stdx",
name = "proc_macro_api"
version = "0.0.0"
dependencies = [
- "crossbeam-channel",
- "jod-thread",
"memmap2",
"object",
"paths",
name = "proc_macro_srv"
version = "0.0.0"
dependencies = [
- "cargo_metadata",
"expect-test",
"libloading",
"mbe",
"paths",
"proc_macro_api",
"proc_macro_test",
- "test_utils",
- "toolchain",
"tt",
]
[[package]]
name = "pulldown-cmark-to-cmark"
-version = "6.0.2"
+version = "6.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95048382115a9da7be92ad51c84064d585b7da17472dcaa7f5eed8853c4c3707"
+checksum = "a72d775989b8b4cc8e5e924a99d6b3ed960da727f78394b7abd539301972e08e"
dependencies = [
"pulldown-cmark",
]
[[package]]
name = "rowan"
-version = "0.13.2"
+version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a938f42b9c73aeece236481f37adb3debb7dfe3ae347cd6a45b5797d9ce4250"
+checksum = "86f050538a65de83ae021294fb50d57f71fb4530fe79af755fc4d4cd61082c01"
dependencies = [
"countme",
"hashbrown",
"stdx",
"syntax",
"test_utils",
- "text_edit",
"threadpool",
"tikv-jemallocator",
"toolchain",
name = "syntax"
version = "0.0.0"
dependencies = [
- "arrayvec",
"cov-mark",
"expect-test",
"indexmap",
"jod-thread",
"notify",
"paths",
- "rustc-hash",
"tracing",
"vfs",
"walkdir",
[[package]]
name = "xshell"
-version = "0.1.15"
+version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07a06b78bf7920975954d1bef9dadedbb522dc886e14532b7cd4c83a10601867"
+checksum = "eaad2035244c56da05573d4d7fda5f903c60a5f35b9110e157a14a1df45a9f14"
dependencies = [
"xshell-macros",
]
[[package]]
name = "xshell-macros"
-version = "0.1.15"
+version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2b955be4ccb0caffb7312052b5b9d31f24cac4c4898869290f1580cb6d73dc2"
+checksum = "4916a4a3cad759e499a3620523bf9545cc162d7a06163727dde97ce9aaa4cf39"
[[package]]
name = "xtask"
tt = { path = "../tt", version = "0.0.0" }
test_utils = { path = "../test_utils", version = "0.0.0" }
vfs = { path = "../vfs", version = "0.0.0" }
-stdx = { path = "../stdx", version = "0.0.0" }
use test_utils::{
extract_range_or_offset, Fixture, RangeOrOffset, CURSOR_MARKER, ESCAPED_CURSOR_MARKER,
};
+use tt::Subtree;
use vfs::{file_set::FileSet, VfsPath};
use crate::{
input::CrateName, Change, CrateDisplayName, CrateGraph, CrateId, Edition, Env, FileId,
- FilePosition, FileRange, SourceDatabaseExt, SourceRoot, SourceRootId,
+ FilePosition, FileRange, ProcMacro, ProcMacroExpander, ProcMacroExpansionError,
+ SourceDatabaseExt, SourceRoot, SourceRootId,
};
pub const WORKSPACE: SourceRootId = SourceRootId(0);
impl ChangeFixture {
pub fn parse(ra_fixture: &str) -> ChangeFixture {
- let (mini_core, fixture) = Fixture::parse(ra_fixture);
+ let (mini_core, proc_macros, fixture) = Fixture::parse(ra_fixture);
let mut change = Change::new();
let mut files = Vec::new();
crate_graph.add_dep(krate, CrateName::new("core").unwrap(), core_crate).unwrap();
}
}
+
+ if !proc_macros.is_empty() {
+ let proc_lib_file = file_id;
+ file_id.0 += 1;
+
+ let (proc_macro, source) = test_proc_macros(&proc_macros);
+ let mut fs = FileSet::default();
+ fs.insert(
+ proc_lib_file,
+ VfsPath::new_virtual_path("/sysroot/proc_macros/lib.rs".to_string()),
+ );
+ roots.push(SourceRoot::new_library(fs));
+
+ change.change_file(proc_lib_file, Some(Arc::new(source)));
+
+ let all_crates = crate_graph.crates_in_topological_order();
+
+ let proc_macros_crate = crate_graph.add_crate_root(
+ proc_lib_file,
+ Edition::Edition2021,
+ Some(CrateDisplayName::from_canonical_name("proc_macros".to_string())),
+ CfgOptions::default(),
+ CfgOptions::default(),
+ Env::default(),
+ proc_macro,
+ );
+
+ for krate in all_crates {
+ crate_graph
+ .add_dep(krate, CrateName::new("proc_macros").unwrap(), proc_macros_crate)
+ .unwrap();
+ }
+ }
+
let root = match current_source_root_kind {
SourceRootKind::Local => SourceRoot::new_local(mem::take(&mut file_set)),
SourceRootKind::Library => SourceRoot::new_library(mem::take(&mut file_set)),
}
}
+fn test_proc_macros(proc_macros: &[String]) -> (Vec<ProcMacro>, String) {
+ // The source here is only required so that paths to the macros exist and are resolvable.
+ let source = r#"
+#[proc_macro_attribute]
+pub fn identity(_attr: TokenStream, item: TokenStream) -> TokenStream {
+ item
+}
+#[proc_macro_attribute]
+pub fn input_replace(attr: TokenStream, _item: TokenStream) -> TokenStream {
+ attr
+}
+#[proc_macro]
+pub fn mirror(input: TokenStream) -> TokenStream {
+ input
+}
+"#;
+ let proc_macros = std::array::IntoIter::new([
+ ProcMacro {
+ name: "identity".into(),
+ kind: crate::ProcMacroKind::Attr,
+ expander: Arc::new(IdentityProcMacroExpander),
+ },
+ ProcMacro {
+ name: "input_replace".into(),
+ kind: crate::ProcMacroKind::Attr,
+ expander: Arc::new(AttributeInputReplaceProcMacroExpander),
+ },
+ ProcMacro {
+ name: "mirror".into(),
+ kind: crate::ProcMacroKind::FuncLike,
+ expander: Arc::new(MirrorProcMacroExpander),
+ },
+ ])
+ .filter(|pm| proc_macros.iter().any(|name| name == pm.name))
+ .collect();
+ (proc_macros, source.into())
+}
+
#[derive(Debug, Clone, Copy)]
enum SourceRootKind {
Local,
}
}
}
+
+// Identity mapping
+#[derive(Debug)]
+struct IdentityProcMacroExpander;
+impl ProcMacroExpander for IdentityProcMacroExpander {
+ fn expand(
+ &self,
+ subtree: &Subtree,
+ _: Option<&Subtree>,
+ _: &Env,
+ ) -> Result<Subtree, ProcMacroExpansionError> {
+ Ok(subtree.clone())
+ }
+}
+
+// Pastes the attribute input as its output
+#[derive(Debug)]
+struct AttributeInputReplaceProcMacroExpander;
+impl ProcMacroExpander for AttributeInputReplaceProcMacroExpander {
+ fn expand(
+ &self,
+ _: &Subtree,
+ attrs: Option<&Subtree>,
+ _: &Env,
+ ) -> Result<Subtree, ProcMacroExpansionError> {
+ attrs
+ .cloned()
+ .ok_or_else(|| ProcMacroExpansionError::Panic("Expected attribute input".into()))
+ }
+}
+
+#[derive(Debug)]
+struct MirrorProcMacroExpander;
+impl ProcMacroExpander for MirrorProcMacroExpander {
+ fn expand(
+ &self,
+ input: &Subtree,
+ _: Option<&Subtree>,
+ _: &Env,
+ ) -> Result<Subtree, ProcMacroExpansionError> {
+ fn traverse(input: &Subtree) -> Subtree {
+ let mut res = Subtree::default();
+ res.delimiter = input.delimiter;
+ for tt in input.token_trees.iter().rev() {
+ let tt = match tt {
+ tt::TokenTree::Leaf(leaf) => tt::TokenTree::Leaf(leaf.clone()),
+ tt::TokenTree::Subtree(sub) => tt::TokenTree::Subtree(traverse(sub)),
+ };
+ res.token_trees.push(tt);
+ }
+ res
+ }
+ Ok(traverse(input))
+ }
+}
pub struct FlycheckHandle {
// XXX: drop order is significant
sender: Sender<Restart>,
- thread: jod_thread::JoinHandle,
+ _thread: jod_thread::JoinHandle,
}
impl FlycheckHandle {
.name("Flycheck".to_owned())
.spawn(move || actor.run(receiver))
.expect("failed to spawn thread");
- FlycheckHandle { sender, thread }
+ FlycheckHandle { sender, _thread: thread }
}
/// Schedule a re-start of the cargo check worker.
itertools = "0.10.0"
smallvec = "1.4.0"
once_cell = "1"
-indexmap = "1.7"
stdx = { path = "../stdx", version = "0.0.0" }
syntax = { path = "../syntax", version = "0.0.0" }
type_ref::{TypeBound, TypeRef},
AdtId, GenericDefId,
};
-use hir_ty::display::{
- write_bounds_like_dyn_trait_with_prefix, write_visibility, HirDisplay, HirDisplayError,
- HirFormatter, SizedByDefault,
+use hir_ty::{
+ display::{
+ write_bounds_like_dyn_trait_with_prefix, write_visibility, HirDisplay, HirDisplayError,
+ HirFormatter, SizedByDefault,
+ },
+ Interner, TraitRefExt, WhereClause,
};
-use hir_ty::Interner;
use syntax::ast::{self, NameOwner};
use crate::{
- Adt, Const, ConstParam, Enum, Field, Function, GenericParam, HasVisibility, LifetimeParam,
- Module, Static, Struct, Trait, TyBuilder, Type, TypeAlias, TypeParam, Union, Variant,
+ Adt, Const, ConstParam, Enum, Field, Function, GenericParam, HasCrate, HasVisibility,
+ LifetimeParam, Module, Static, Struct, Trait, TyBuilder, Type, TypeAlias, TypeParam, Union,
+ Variant,
};
impl HirDisplay for Function {
impl HirDisplay for TypeParam {
fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> {
write!(f, "{}", self.name(f.db))?;
+ if f.omit_verbose_types() {
+ return Ok(());
+ }
+
let bounds = f.db.generic_predicates_for_param(self.id);
let substs = TyBuilder::type_params_subst(f.db, self.id.parent);
- let predicates =
- bounds.iter().cloned().map(|b| b.substitute(&Interner, &substs)).collect::<Vec<_>>();
- if !(predicates.is_empty() || f.omit_verbose_types()) {
- let default_sized = SizedByDefault::Sized { anchor: self.module(f.db).krate().id };
+ let predicates: Vec<_> =
+ bounds.iter().cloned().map(|b| b.substitute(&Interner, &substs)).collect();
+ let krate = self.id.parent.krate(f.db).id;
+ let sized_trait =
+ f.db.lang_item(krate, "sized".into()).and_then(|lang_item| lang_item.as_trait());
+ let has_only_sized_bound = predicates.iter().all(move |pred| match pred.skip_binders() {
+ WhereClause::Implemented(it) => Some(it.hir_trait_id()) == sized_trait,
+ _ => false,
+ });
+ let has_only_not_sized_bound = predicates.is_empty();
+ if !has_only_sized_bound || has_only_not_sized_bound {
+ let default_sized = SizedByDefault::Sized { anchor: krate };
write_bounds_like_dyn_trait_with_prefix(":", &predicates, default_sized, f)?;
}
Ok(())
mod display;
-use std::{iter, sync::Arc};
+use std::{iter, ops::ControlFlow, sync::Arc};
use arrayvec::ArrayVec;
use base_db::{CrateDisplayName, CrateId, Edition, FileId};
krate,
traits_in_scope,
name,
- &mut |ty, assoc_item_id| match assoc_item_id {
- AssocItemId::FunctionId(it) => {
- slot = callback(self.derived(ty.clone()), it.into());
- slot.is_some()
+ &mut |ty, assoc_item_id| {
+ if let AssocItemId::FunctionId(func) = assoc_item_id {
+ if let Some(res) = callback(self.derived(ty.clone()), func.into()) {
+ slot = Some(res);
+ return ControlFlow::Break(());
+ }
}
- AssocItemId::ConstId(_) | AssocItemId::TypeAliasId(_) => false,
+ ControlFlow::Continue(())
},
);
slot
krate: Crate,
traits_in_scope: &FxHashSet<TraitId>,
name: Option<&Name>,
- callback: &mut dyn FnMut(&Ty, AssocItemId) -> bool,
+ callback: &mut dyn FnMut(&Ty, AssocItemId) -> ControlFlow<()>,
) {
// There should be no inference vars in types passed here
// FIXME check that?
None,
name,
method_resolution::LookupMode::MethodCall,
- callback,
+ &mut |ty, id| callback(&ty.value, id),
);
}
traits_in_scope,
name,
&mut |ty, assoc_item_id| {
- slot = callback(self.derived(ty.clone()), assoc_item_id.into());
- slot.is_some()
+ if let Some(res) = callback(self.derived(ty.clone()), assoc_item_id.into()) {
+ slot = Some(res);
+ return ControlFlow::Break(());
+ }
+ ControlFlow::Continue(())
},
);
slot
krate: Crate,
traits_in_scope: &FxHashSet<TraitId>,
name: Option<&Name>,
- callback: &mut dyn FnMut(&Ty, AssocItemId) -> bool,
+ callback: &mut dyn FnMut(&Ty, AssocItemId) -> ControlFlow<()>,
) {
let canonical = hir_ty::replace_errors_with_variables(&self.ty);
None,
name,
method_resolution::LookupMode::Path,
- callback,
+ &mut |ty, id| callback(&ty.value, id),
);
}
use rustc_hash::{FxHashMap, FxHashSet};
use smallvec::{smallvec, SmallVec};
use syntax::{
- algo::find_node_at_offset,
+ algo::skip_trivia_token,
ast::{self, GenericParamsOwner, LoopBodyOwner},
- match_ast, AstNode, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange, TextSize,
+ match_ast, AstNode, Direction, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange, TextSize,
};
use crate::{
self.imp.speculative_expand(actual_macro_call, speculative_args, token_to_map)
}
+ pub fn speculative_expand_attr_macro(
+ &self,
+ actual_macro_call: &ast::Item,
+ speculative_args: &ast::Item,
+ token_to_map: SyntaxToken,
+ ) -> Option<(SyntaxNode, SyntaxToken)> {
+ self.imp.speculative_expand_attr(actual_macro_call, speculative_args, token_to_map)
+ }
+
// FIXME: Rename to descend_into_macros_single
pub fn descend_into_macros(&self, token: SyntaxToken) -> SyntaxToken {
self.imp.descend_into_macros(token).pop().unwrap()
self.imp.descend_into_macros(token)
}
- pub fn descend_node_at_offset<N: ast::AstNode>(
- &self,
- node: &SyntaxNode,
- offset: TextSize,
- ) -> Option<N> {
- self.imp.descend_node_at_offset(node, offset).flatten().find_map(N::cast)
+ /// Maps a node down by mapping its first and last token down.
+ pub fn descend_node_into_attributes<N: AstNode>(&self, node: N) -> SmallVec<[N; 1]> {
+ self.imp.descend_node_into_attributes(node)
}
pub fn hir_file_for(&self, syntax_node: &SyntaxNode) -> HirFileId {
self.imp.original_range(node)
}
+ pub fn original_range_opt(&self, node: &SyntaxNode) -> Option<FileRange> {
+ self.imp.original_range_opt(node)
+ }
+
pub fn diagnostics_display_range(&self, diagnostics: InFile<SyntaxNodePtr>) -> FileRange {
self.imp.diagnostics_display_range(diagnostics)
}
) -> impl Iterator<Item = SyntaxNode> + '_ {
token.parent().into_iter().flat_map(move |it| self.ancestors_with_macros(it))
}
+
pub fn ancestors_with_macros(&self, node: SyntaxNode) -> impl Iterator<Item = SyntaxNode> + '_ {
self.imp.ancestors_with_macros(node)
}
node: &SyntaxNode,
offset: TextSize,
) -> Option<N> {
- if let Some(it) = find_node_at_offset(node, offset) {
- return Some(it);
- }
-
self.imp.descend_node_at_offset(node, offset).flatten().find_map(N::cast)
}
hir_expand::db::expand_speculative(
self.db.upcast(),
macro_call_id,
- speculative_args,
+ speculative_args.syntax(),
token_to_map,
)
}
+ fn speculative_expand_attr(
+ &self,
+ actual_macro_call: &ast::Item,
+ speculative_args: &ast::Item,
+ token_to_map: SyntaxToken,
+ ) -> Option<(SyntaxNode, SyntaxToken)> {
+ let sa = self.analyze(actual_macro_call.syntax());
+ let macro_call = InFile::new(sa.file_id, actual_macro_call.clone());
+ let macro_call_id = self.with_ctx(|ctx| ctx.item_to_macro_call(macro_call))?;
+ hir_expand::db::expand_speculative(
+ self.db.upcast(),
+ macro_call_id,
+ speculative_args.syntax(),
+ token_to_map,
+ )
+ }
+
+ // This might not be the correct way to do this, but it works for now
+ fn descend_node_into_attributes<N: AstNode>(&self, node: N) -> SmallVec<[N; 1]> {
+ let mut res = smallvec![];
+ let tokens = (|| {
+ let first = skip_trivia_token(node.syntax().first_token()?, Direction::Next)?;
+ let last = skip_trivia_token(node.syntax().last_token()?, Direction::Prev)?;
+ Some((first, last))
+ })();
+ let (first, last) = match tokens {
+ Some(it) => it,
+ None => return res,
+ };
+
+ if first == last {
+ self.descend_into_macros_impl(first, |InFile { value, .. }| {
+ if let Some(node) = value.ancestors().find_map(N::cast) {
+ res.push(node)
+ }
+ });
+ } else {
+ // Descend first and last token, then zip them to look for the node they belong to
+ let mut scratch: SmallVec<[_; 1]> = smallvec![];
+ self.descend_into_macros_impl(first, |token| {
+ scratch.push(token);
+ });
+
+ let mut scratch = scratch.into_iter();
+ self.descend_into_macros_impl(last, |InFile { value: last, file_id: last_fid }| {
+ if let Some(InFile { value: first, file_id: first_fid }) = scratch.next() {
+ if first_fid == last_fid {
+ if let Some(p) = first.parent() {
+ let range = first.text_range().cover(last.text_range());
+ let node = find_root(&p)
+ .covering_element(range)
+ .ancestors()
+ .take_while(|it| it.text_range() == range)
+ .find_map(N::cast);
+ if let Some(node) = node {
+ res.push(node);
+ }
+ }
+ }
+ }
+ });
+ }
+ res
+ }
+
fn descend_into_macros(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> {
+ let mut res = smallvec![];
+ self.descend_into_macros_impl(token, |InFile { value, .. }| res.push(value));
+ res
+ }
+
+ fn descend_into_macros_impl(&self, token: SyntaxToken, mut f: impl FnMut(InFile<SyntaxToken>)) {
let _p = profile::span("descend_into_macros");
let parent = match token.parent() {
Some(it) => it,
- None => return smallvec![token],
+ None => return,
};
let sa = self.analyze(&parent);
let mut queue = vec![InFile::new(sa.file_id, token)];
let mut cache = self.expansion_info_cache.borrow_mut();
- let mut res = smallvec![];
// Remap the next token in the queue into a macro call its in, if it is not being remapped
// either due to not being in a macro-call or because its unused push it into the result vec,
// otherwise push the remapped tokens back into the queue as they can potentially be remapped again.
while let Some(token) = queue.pop() {
self.db.unwind_if_cancelled();
-
let was_not_remapped = (|| {
- for node in token.value.ancestors() {
- if let Some(macro_call) = ast::MacroCall::cast(node.clone()) {
- let tt = match macro_call.token_tree() {
- Some(tt) => tt,
- None => continue,
- };
- let l_delim = match tt.left_delimiter_token() {
- Some(it) => it.text_range().end(),
- None => tt.syntax().text_range().start(),
- };
- let r_delim = match tt.right_delimiter_token() {
- Some(it) => it.text_range().start(),
- None => tt.syntax().text_range().end(),
- };
- if !TextRange::new(l_delim, r_delim)
- .contains_range(token.value.text_range())
- {
- continue;
- }
- let file_id = match sa.expand(self.db, token.with_value(¯o_call)) {
- Some(file_id) => file_id,
- None => continue,
- };
- let tokens = cache
- .entry(file_id)
- .or_insert_with(|| file_id.expansion_info(self.db.upcast()))
- .as_ref()?
- .map_token_down(self.db.upcast(), None, token.as_ref())?;
-
- let len = queue.len();
- queue.extend(tokens.inspect(|token| {
- if let Some(parent) = token.value.parent() {
- self.cache(find_root(&parent), token.file_id);
- }
- }));
- return (queue.len() != len).then(|| ());
- } else if let Some(item) = ast::Item::cast(node.clone()) {
- if let Some(call_id) = self
- .with_ctx(|ctx| ctx.item_to_macro_call(token.with_value(item.clone())))
- {
- let file_id = call_id.as_file();
- let tokens = cache
- .entry(file_id)
- .or_insert_with(|| file_id.expansion_info(self.db.upcast()))
- .as_ref()?
- .map_token_down(self.db.upcast(), Some(item), token.as_ref())?;
-
- let len = queue.len();
- queue.extend(tokens.inspect(|token| {
- if let Some(parent) = token.value.parent() {
- self.cache(find_root(&parent), token.file_id);
- }
- }));
- return (queue.len() != len).then(|| ());
+ if let Some((call_id, item)) = token
+ .value
+ .ancestors()
+ .filter_map(ast::Item::cast)
+ .filter_map(|item| {
+ self.with_ctx(|ctx| ctx.item_to_macro_call(token.with_value(item.clone())))
+ .zip(Some(item))
+ })
+ .last()
+ {
+ let file_id = call_id.as_file();
+ let tokens = cache
+ .entry(file_id)
+ .or_insert_with(|| file_id.expansion_info(self.db.upcast()))
+ .as_ref()?
+ .map_token_down(self.db.upcast(), Some(item), token.as_ref())?;
+
+ let len = queue.len();
+ queue.extend(tokens.inspect(|token| {
+ if let Some(parent) = token.value.parent() {
+ self.cache(find_root(&parent), token.file_id);
}
+ }));
+ return (queue.len() != len).then(|| ());
+ }
+
+ if let Some(macro_call) = token.value.ancestors().find_map(ast::MacroCall::cast) {
+ let tt = macro_call.token_tree()?;
+ let l_delim = match tt.left_delimiter_token() {
+ Some(it) => it.text_range().end(),
+ None => tt.syntax().text_range().start(),
+ };
+ let r_delim = match tt.right_delimiter_token() {
+ Some(it) => it.text_range().start(),
+ None => tt.syntax().text_range().end(),
+ };
+ if !TextRange::new(l_delim, r_delim).contains_range(token.value.text_range()) {
+ return None;
}
+ let file_id = sa.expand(self.db, token.with_value(¯o_call))?;
+ let tokens = cache
+ .entry(file_id)
+ .or_insert_with(|| file_id.expansion_info(self.db.upcast()))
+ .as_ref()?
+ .map_token_down(self.db.upcast(), None, token.as_ref())?;
+
+ let len = queue.len();
+ queue.extend(tokens.inspect(|token| {
+ if let Some(parent) = token.value.parent() {
+ self.cache(find_root(&parent), token.file_id);
+ }
+ }));
+ return (queue.len() != len).then(|| ());
}
None
})()
.is_none();
+
if was_not_remapped {
- res.push(token.value)
+ f(token)
}
}
- res
}
// Note this return type is deliberate as [`find_nodes_at_offset_with_descend`] wants to stop
// traversing the inner iterator when it finds a node.
+ // The outer iterator is over the tokens descendants
+ // The inner iterator is the ancestors of a descendant
fn descend_node_at_offset(
&self,
node: &SyntaxNode,
offset: TextSize,
) -> impl Iterator<Item = impl Iterator<Item = SyntaxNode> + '_> + '_ {
- // Handle macro token cases
node.token_at_offset(offset)
.map(move |token| self.descend_into_macros(token))
- .map(|it| it.into_iter().map(move |it| self.token_ancestors_with_macros(it)))
- .flatten()
+ .map(|descendants| {
+ descendants.into_iter().map(move |it| self.token_ancestors_with_macros(it))
+ })
+ // re-order the tokens from token_at_offset by returning the ancestors with the smaller first nodes first
+ // See algo::ancestors_at_offset, which uses the same approach
+ .kmerge_by(|left, right| {
+ left.clone()
+ .map(|node| node.text_range().len())
+ .lt(right.clone().map(|node| node.text_range().len()))
+ })
}
fn original_range(&self, node: &SyntaxNode) -> FileRange {
node.as_ref().original_file_range(self.db.upcast())
}
+ fn original_range_opt(&self, node: &SyntaxNode) -> Option<FileRange> {
+ let node = self.find_file(node.clone());
+ node.as_ref().original_file_range_opt(self.db.upcast())
+ }
+
fn diagnostics_display_range(&self, src: InFile<SyntaxNodePtr>) -> FileRange {
let root = self.db.parse_or_expand(src.file_id).unwrap();
let node = src.value.to_node(&root);
fn token_ancestors_with_macros(
&self,
token: SyntaxToken,
- ) -> impl Iterator<Item = SyntaxNode> + '_ {
+ ) -> impl Iterator<Item = SyntaxNode> + Clone + '_ {
token.parent().into_iter().flat_map(move |parent| self.ancestors_with_macros(parent))
}
- fn ancestors_with_macros(&self, node: SyntaxNode) -> impl Iterator<Item = SyntaxNode> + '_ {
+ fn ancestors_with_macros(
+ &self,
+ node: SyntaxNode,
+ ) -> impl Iterator<Item = SyntaxNode> + Clone + '_ {
let node = self.find_file(node);
node.ancestors_with_macros(self.db.upcast()).map(|it| it.value)
}
fn resolve_lifetime_param(&self, lifetime: &ast::Lifetime) -> Option<LifetimeParam> {
let text = lifetime.text();
let lifetime_param = lifetime.syntax().ancestors().find_map(|syn| {
- let gpl = match_ast! {
- match syn {
- ast::Fn(it) => it.generic_param_list()?,
- ast::TypeAlias(it) => it.generic_param_list()?,
- ast::Struct(it) => it.generic_param_list()?,
- ast::Enum(it) => it.generic_param_list()?,
- ast::Union(it) => it.generic_param_list()?,
- ast::Trait(it) => it.generic_param_list()?,
- ast::Impl(it) => it.generic_param_list()?,
- ast::WherePred(it) => it.generic_param_list()?,
- ast::ForType(it) => it.generic_param_list()?,
- _ => return None,
- }
- };
+ let gpl = ast::DynGenericParamsOwner::cast(syn)?.generic_param_list()?;
gpl.lifetime_params()
.find(|tp| tp.lifetime().as_ref().map(|lt| lt.text()).as_ref() == Some(&text))
})?;
pub(super) fn module_to_def(&mut self, src: InFile<ast::Module>) -> Option<ModuleId> {
let _p = profile::span("module_to_def");
- let parent_declaration =
- src.syntax().cloned().ancestors_with_macros(self.db.upcast()).skip(1).find_map(|it| {
+ let parent_declaration = src
+ .syntax()
+ .cloned()
+ .ancestors_with_macros_skip_attr_item(self.db.upcast())
+ .skip(1)
+ .find_map(|it| {
let m = ast::Module::cast(it.value.clone())?;
Some(it.with_value(m))
});
}
pub(super) fn find_container(&mut self, src: InFile<&SyntaxNode>) -> Option<ChildContainer> {
- for container in src.cloned().ancestors_with_macros(self.db.upcast()).skip(1) {
+ for container in src.cloned().ancestors_with_macros_skip_attr_item(self.db.upcast()).skip(1)
+ {
if let Some(res) = self.container_to_def(container) {
return Some(res);
}
}
fn find_generic_param_container(&mut self, src: InFile<&SyntaxNode>) -> Option<GenericDefId> {
- for container in src.cloned().ancestors_with_macros(self.db.upcast()).skip(1) {
+ for container in src.cloned().ancestors_with_macros_skip_attr_item(self.db.upcast()).skip(1)
+ {
let res: GenericDefId = match_ast! {
match (container.value) {
ast::Fn(it) => self.fn_to_def(container.with_value(it))?.into(),
}
fn find_pat_or_label_container(&mut self, src: InFile<&SyntaxNode>) -> Option<DefWithBodyId> {
- for container in src.cloned().ancestors_with_macros(self.db.upcast()).skip(1) {
+ for container in src.cloned().ancestors_with_macros_skip_attr_item(self.db.upcast()).skip(1)
+ {
let res: DefWithBodyId = match_ast! {
match (container.value) {
ast::Const(it) => self.const_to_def(container.with_value(it))?.into(),
body: Option<Arc<Body>>,
body_source_map: Option<Arc<BodySourceMap>>,
infer: Option<Arc<InferenceResult>>,
- scopes: Option<Arc<ExprScopes>>,
}
impl SourceAnalyzer {
body: Some(body),
body_source_map: Some(source_map),
infer: Some(db.infer(def)),
- scopes: Some(scopes),
file_id: node.file_id,
}
}
body: None,
body_source_map: None,
infer: None,
- scopes: None,
file_id: node.file_id,
}
}
use mbe::{syntax_node_to_token_tree, DelimiterKind};
use smallvec::{smallvec, SmallVec};
use syntax::{
- ast::{self, AstNode, AttrsOwner},
+ ast::{self, AstNode, AttrsOwner, IsString},
match_ast, AstPtr, AstToken, SmolStr, SyntaxNode, TextRange, TextSize,
};
use tt::Subtree;
let file_id = id.parent.file_id(db);
let root = db.parse_or_expand(file_id).unwrap();
let owner = match &map[id.local_id] {
- Either::Left(it) => ast::AttrsOwnerNode::new(it.to_node(&root)),
- Either::Right(it) => ast::AttrsOwnerNode::new(it.to_node(&root)),
+ Either::Left(it) => ast::DynAttrsOwner::new(it.to_node(&root)),
+ Either::Right(it) => ast::DynAttrsOwner::new(it.to_node(&root)),
};
InFile::new(file_id, owner)
}
AttrDefId::AdtId(adt) => match adt {
- AdtId::StructId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
- AdtId::UnionId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
- AdtId::EnumId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
+ AdtId::StructId(id) => id.lookup(db).source(db).map(ast::DynAttrsOwner::new),
+ AdtId::UnionId(id) => id.lookup(db).source(db).map(ast::DynAttrsOwner::new),
+ AdtId::EnumId(id) => id.lookup(db).source(db).map(ast::DynAttrsOwner::new),
},
- AttrDefId::FunctionId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
+ AttrDefId::FunctionId(id) => id.lookup(db).source(db).map(ast::DynAttrsOwner::new),
AttrDefId::EnumVariantId(id) => {
let map = db.variants_attrs_source_map(id.parent);
let file_id = id.parent.lookup(db).id.file_id();
let root = db.parse_or_expand(file_id).unwrap();
- InFile::new(file_id, ast::AttrsOwnerNode::new(map[id.local_id].to_node(&root)))
+ InFile::new(file_id, ast::DynAttrsOwner::new(map[id.local_id].to_node(&root)))
}
- AttrDefId::StaticId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
- AttrDefId::ConstId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
- AttrDefId::TraitId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
- AttrDefId::TypeAliasId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
+ AttrDefId::StaticId(id) => id.lookup(db).source(db).map(ast::DynAttrsOwner::new),
+ AttrDefId::ConstId(id) => id.lookup(db).source(db).map(ast::DynAttrsOwner::new),
+ AttrDefId::TraitId(id) => id.lookup(db).source(db).map(ast::DynAttrsOwner::new),
+ AttrDefId::TypeAliasId(id) => id.lookup(db).source(db).map(ast::DynAttrsOwner::new),
AttrDefId::MacroDefId(id) => id.ast_id().either(
- |it| it.with_value(ast::AttrsOwnerNode::new(it.to_node(db.upcast()))),
- |it| it.with_value(ast::AttrsOwnerNode::new(it.to_node(db.upcast()))),
+ |it| it.with_value(ast::DynAttrsOwner::new(it.to_node(db.upcast()))),
+ |it| it.with_value(ast::DynAttrsOwner::new(it.to_node(db.upcast()))),
),
- AttrDefId::ImplId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
+ AttrDefId::ImplId(id) => id.lookup(db).source(db).map(ast::DynAttrsOwner::new),
AttrDefId::GenericParamId(id) => match id {
GenericParamId::TypeParamId(id) => {
id.parent.child_source(db).map(|source| match &source[id.local_id] {
- Either::Left(id) => ast::AttrsOwnerNode::new(id.clone()),
- Either::Right(id) => ast::AttrsOwnerNode::new(id.clone()),
+ Either::Left(id) => ast::DynAttrsOwner::new(id.clone()),
+ Either::Right(id) => ast::DynAttrsOwner::new(id.clone()),
})
}
GenericParamId::LifetimeParamId(id) => id
.parent
.child_source(db)
- .map(|source| ast::AttrsOwnerNode::new(source[id.local_id].clone())),
+ .map(|source| ast::DynAttrsOwner::new(source[id.local_id].clone())),
GenericParamId::ConstParamId(id) => id
.parent
.child_source(db)
- .map(|source| ast::AttrsOwnerNode::new(source[id.local_id].clone())),
+ .map(|source| ast::DynAttrsOwner::new(source[id.local_id].clone())),
},
};
Some((attrs, docs))
}
+#[derive(Debug)]
pub struct AttrSourceMap {
attrs: Vec<InFile<ast::Attr>>,
doc_comments: Vec<InFile<ast::Comment>>,
}
/// A struct to map text ranges from [`Documentation`] back to TextRanges in the syntax tree.
+#[derive(Debug)]
pub struct DocsRangeMap {
source_map: AttrSourceMap,
// (docstring-line-range, attr_index, attr-string-range)
}
impl DocsRangeMap {
+ /// Maps a [`TextRange`] relative to the documentation string back to its AST range
pub fn map(&self, range: TextRange) -> Option<InFile<TextRange>> {
let found = self.mapping.binary_search_by(|(probe, ..)| probe.ordering(range)).ok()?;
let (line_docs_range, idx, original_line_src_range) = self.mapping[found];
let InFile { file_id, value: source } = self.source_map.source_of_id(idx);
match source {
- Either::Left(_) => None, // FIXME, figure out a nice way to handle doc attributes here
- // as well as for whats done in syntax highlight doc injection
+ Either::Left(attr) => {
+ let string = get_doc_string_in_attr(&attr)?;
+ let text_range = string.open_quote_text_range()?;
+ let range = TextRange::at(
+ text_range.end() + original_line_src_range.start() + relative_range.start(),
+ string.syntax().text_range().len().min(range.len()),
+ );
+ Some(InFile { file_id, value: range })
+ }
Either::Right(comment) => {
let text_range = comment.syntax().text_range();
let range = TextRange::at(
}
}
+fn get_doc_string_in_attr(it: &ast::Attr) -> Option<ast::String> {
+ match it.expr() {
+ // #[doc = lit]
+ Some(ast::Expr::Literal(lit)) => match lit.kind() {
+ ast::LiteralKind::String(it) => Some(it),
+ _ => None,
+ },
+ // #[cfg_attr(..., doc = "", ...)]
+ None => {
+ // FIXME: See highlight injection for what to do here
+ None
+ }
+ _ => None,
+ }
+}
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct AttrId {
is_doc_comment: bool,
name = tt::Ident { text: it.clone(), id: tt::TokenId::unspecified() }.as_name();
&name
}
- None => &mac.name,
+ None => {
+ match attrs.by_key("rustc_builtin_macro").tt_values().next().and_then(|tt| {
+ match tt.token_trees.first() {
+ Some(tt::TokenTree::Leaf(tt::Leaf::Ident(name))) => Some(name),
+ _ => None,
+ }
+ }) {
+ Some(ident) => {
+ name = ident.as_name();
+ &name
+ }
+ None => &mac.name,
+ }
+ }
};
let krate = self.def_collector.def_map.krate;
match find_builtin_macro(name, krate, ast_id) {
limit = { path = "../limit", version = "0.0.0" }
[dev-dependencies]
-test_utils = { path = "../test_utils" }
expect-test = "1.1"
);
}
+ #[test]
+ fn test_format_args_expand_with_broken_member_access() {
+ check_expansion(
+ r#"
+ #[rustc_builtin_macro]
+ macro_rules! format_args {
+ ($fmt:expr) => ({ /* compiler built-in */ });
+ ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
+ }
+ format_args!("{} {:?}", a.);
+ "#,
+ expect![[
+ r#"unsafe{std::fmt::Arguments::new_v1(&[], &[std::fmt::ArgumentV1::new(&(a.),std::fmt::Display::fmt),])}"#
+ ]],
+ );
+ }
+
#[test]
fn test_include_bytes_expand() {
check_expansion(
use std::sync::Arc;
use base_db::{salsa, SourceDatabase};
-use itertools::Itertools;
use limit::Limit;
-use mbe::{ExpandError, ExpandResult};
+use mbe::{syntax_node_to_token_tree, ExpandError, ExpandResult};
+use rustc_hash::FxHashSet;
use syntax::{
algo::diff,
ast::{self, AttrsOwner, NameOwner},
- AstNode, GreenNode, Parse, SyntaxNode, SyntaxToken, TextRange, T,
+ AstNode, GreenNode, Parse, SyntaxNode, SyntaxToken, T,
};
use crate::{
pub fn expand_speculative(
db: &dyn AstDatabase,
actual_macro_call: MacroCallId,
- speculative_args: &ast::TokenTree,
+ speculative_args: &SyntaxNode,
token_to_map: SyntaxToken,
) -> Option<(SyntaxNode, SyntaxToken)> {
- let (tt, tmap_1) = mbe::syntax_node_to_token_tree(speculative_args.syntax());
- let range =
- token_to_map.text_range().checked_sub(speculative_args.syntax().text_range().start())?;
- let token_id = tmap_1.token_by_range(range)?;
-
- let macro_def = {
- let loc: MacroCallLoc = db.lookup_intern_macro(actual_macro_call);
- db.macro_def(loc.def)?
+ let loc = db.lookup_intern_macro(actual_macro_call);
+ let macro_def = db.macro_def(loc.def)?;
+ let token_range = token_to_map.text_range();
+
+ // Build the subtree and token mapping for the speculative args
+ let censor = censor_for_macro_input(&loc, &speculative_args);
+ let (mut tt, spec_args_tmap) =
+ mbe::syntax_node_to_token_tree_censored(&speculative_args, &censor);
+
+ let (attr_arg, token_id) = match loc.kind {
+ MacroCallKind::Attr { invoc_attr_index, .. } => {
+ // Attributes may have an input token tree, build the subtree and map for this as well
+ // then try finding a token id for our token if it is inside this input subtree.
+ let item = ast::Item::cast(speculative_args.clone())?;
+ let attr = item.attrs().nth(invoc_attr_index as usize)?;
+ match attr.token_tree() {
+ Some(token_tree) => {
+ let (mut tree, map) = syntax_node_to_token_tree(attr.token_tree()?.syntax());
+ tree.delimiter = None;
+
+ let shift = mbe::Shift::new(&tt);
+ shift.shift_all(&mut tree);
+
+ let token_id = if token_tree.syntax().text_range().contains_range(token_range) {
+ let attr_input_start =
+ token_tree.left_delimiter_token()?.text_range().start();
+ let range = token_range.checked_sub(attr_input_start)?;
+ let token_id = shift.shift(map.token_by_range(range)?);
+ Some(token_id)
+ } else {
+ None
+ };
+ (Some(tree), token_id)
+ }
+ _ => (None, None),
+ }
+ }
+ _ => (None, None),
+ };
+ let token_id = match token_id {
+ Some(token_id) => token_id,
+ // token wasn't inside an attribute input so it has to be in the general macro input
+ None => {
+ let range = token_range.checked_sub(speculative_args.text_range().start())?;
+ let token_id = spec_args_tmap.token_by_range(range)?;
+ macro_def.map_id_down(token_id)
+ }
};
- let speculative_expansion = macro_def.expand(db, actual_macro_call, &tt);
+ // Do the actual expansion, we need to directly expand the proc macro due to the attribute args
+ // Otherwise the expand query will fetch the non speculative attribute args and pass those instead.
+ let speculative_expansion = if let MacroDefKind::ProcMacro(expander, ..) = loc.def.kind {
+ tt.delimiter = None;
+ expander.expand(db, loc.krate, &tt, attr_arg.as_ref())
+ } else {
+ macro_def.expand(db, actual_macro_call, &tt)
+ };
let expand_to = macro_expand_to(db, actual_macro_call);
+ let (node, rev_tmap) =
+ token_tree_to_syntax_node(&speculative_expansion.value, expand_to).ok()?;
- let (node, tmap_2) = token_tree_to_syntax_node(&speculative_expansion.value, expand_to).ok()?;
-
- let token_id = macro_def.map_id_down(token_id);
- let range = tmap_2.first_range_by_token(token_id, token_to_map.kind())?;
+ let range = rev_tmap.first_range_by_token(token_id, token_to_map.kind())?;
let token = node.syntax_node().covering_element(range).into_token()?;
Some((node.syntax_node(), token))
}
let loc = db.lookup_intern_macro(id);
let node = SyntaxNode::new_root(arg);
- let censor = match loc.kind {
- MacroCallKind::FnLike { .. } => None,
- MacroCallKind::Derive { derive_attr_index, .. } => match ast::Item::cast(node.clone()) {
- Some(item) => item
- .attrs()
- .map(|attr| attr.syntax().text_range())
- .take(derive_attr_index as usize + 1)
- .fold1(TextRange::cover),
- None => None,
- },
- MacroCallKind::Attr { invoc_attr_index, .. } => match ast::Item::cast(node.clone()) {
- Some(item) => {
- item.attrs().nth(invoc_attr_index as usize).map(|attr| attr.syntax().text_range())
- }
- None => None,
- },
- };
- let (mut tt, tmap) = mbe::syntax_node_to_token_tree_censored(&node, censor);
+ let censor = censor_for_macro_input(&loc, &node);
+ let (mut tt, tmap) = mbe::syntax_node_to_token_tree_censored(&node, &censor);
if loc.def.is_proc_macro() {
// proc macros expect their inputs without parentheses, MBEs expect it with them included
Some(Arc::new((tt, tmap)))
}
+fn censor_for_macro_input(loc: &MacroCallLoc, node: &SyntaxNode) -> FxHashSet<SyntaxNode> {
+ (|| {
+ let censor = match loc.kind {
+ MacroCallKind::FnLike { .. } => return None,
+ MacroCallKind::Derive { derive_attr_index, .. } => ast::Item::cast(node.clone())?
+ .attrs()
+ .take(derive_attr_index as usize + 1)
+ .filter(|attr| attr.simple_name().as_deref() == Some("derive"))
+ .map(|it| it.syntax().clone())
+ .collect(),
+ MacroCallKind::Attr { invoc_attr_index, .. } => ast::Item::cast(node.clone())?
+ .attrs()
+ .nth(invoc_attr_index as usize)
+ .map(|attr| attr.syntax().clone())
+ .into_iter()
+ .collect(),
+ };
+ Some(censor)
+ })()
+ .unwrap_or_default()
+}
+
fn macro_arg_text(db: &dyn AstDatabase, id: MacroCallId) -> Option<GreenNode> {
let loc = db.lookup_intern_macro(id);
let arg = loc.kind.arg(db)?;
None => return ExpandResult::str_err("Failed to lower macro args to token tree".into()),
};
- let macro_rules = match db.macro_def(loc.def) {
+ let expander = match db.macro_def(loc.def) {
Some(it) => it,
None => return ExpandResult::str_err("Failed to find macro definition".into()),
};
- let ExpandResult { value: tt, err } = macro_rules.expand(db, id, ¯o_arg.0);
+ let ExpandResult { value: tt, err } = expander.expand(db, id, ¯o_arg.0);
// Set a hard limit for the expanded tt
let count = tt.count();
// XXX: Make ExpandResult a real error and use .map_err instead?
// Collect replacement
for child in children {
- let def = diagnostic_sink
- .option_with(|| macro_resolver(child.path()?), || err("failed to resolve macro"))?;
+ let def = diagnostic_sink.option_with(
+ || macro_resolver(child.path()?),
+ || {
+ let path = child.path().map(|path| format!(" `{}!`", path)).unwrap_or_default();
+ err(format!("failed to resolve macro{}", path))
+ },
+ )?;
let insert = match def.kind {
MacroDefKind::BuiltInEager(..) => {
let id = expand_eager_macro(
}
}
+ /// Return whether this file is an include macro
+ pub fn is_attr_macro(&self, db: &dyn db::AstDatabase) -> bool {
+ match self.0 {
+ HirFileIdRepr::MacroFile(macro_file) => {
+ let loc: MacroCallLoc = db.lookup_intern_macro(macro_file.macro_call_id);
+ matches!(loc.kind, MacroCallKind::Attr { .. })
+ }
+ _ => false,
+ }
+ }
+
pub fn is_macro(self) -> bool {
matches!(self.0, HirFileIdRepr::MacroFile(_))
}
) -> Option<impl Iterator<Item = InFile<SyntaxToken>> + '_> {
assert_eq!(token.file_id, self.arg.file_id);
let token_id = if let Some(item) = item {
+ // check if we are mapping down in an attribute input
let call_id = match self.expanded.file_id.0 {
HirFileIdRepr::FileId(_) => return None,
HirFileIdRepr::MacroFile(macro_file) => macro_file.macro_call_id,
pub fn ancestors_with_macros(
self,
db: &dyn db::AstDatabase,
- ) -> impl Iterator<Item = InFile<SyntaxNode>> + '_ {
+ ) -> impl Iterator<Item = InFile<SyntaxNode>> + Clone + '_ {
iter::successors(Some(self), move |node| match node.value.parent() {
Some(parent) => Some(node.with_value(parent)),
None => {
}
})
}
+
+ /// Skips the attributed item that caused the macro invocation we are climbing up
+ pub fn ancestors_with_macros_skip_attr_item(
+ self,
+ db: &dyn db::AstDatabase,
+ ) -> impl Iterator<Item = InFile<SyntaxNode>> + '_ {
+ iter::successors(Some(self), move |node| match node.value.parent() {
+ Some(parent) => Some(node.with_value(parent)),
+ None => {
+ let parent_node = node.file_id.call_node(db)?;
+ if node.file_id.is_attr_macro(db) {
+ // macro call was an attributed item, skip it
+ // FIXME: does this fail if this is a direct expansion of another macro?
+ parent_node.map(|node| node.parent()).transpose()
+ } else {
+ Some(parent_node)
+ }
+ }
+ })
+ }
}
impl<'a> InFile<&'a SyntaxNode> {
tracing = "0.1"
rustc-hash = "1.1.0"
scoped-tls = "1"
-chalk-solve = { version = "0.70", default-features = false }
-chalk-ir = "0.70"
-chalk-recursive = { version = "0.70", default-features = false }
+chalk-solve = { version = "0.71", default-features = false }
+chalk-ir = "0.71"
+chalk-recursive = { version = "0.71", default-features = false }
la-arena = { version = "0.2.0", path = "../../lib/arena" }
once_cell = { version = "1.5.0" }
"registry",
] }
tracing-tree = { version = "0.1.10" }
-once_cell = { version = "1.5.0", features = ["unstable"] }
// would have to pass the solution up to the inference context, but
// that requires a larger refactoring (especially if the deref
// happens during method resolution). So for the moment, we just
- // check that we're not in the situation we're we would actually
+ // check that we're not in the situation where we would actually
// need to handle the values of the additional variables, i.e.
// they're just being 'passed through'. In the 'standard' case where
// we have `impl<T> Deref for Foo<T> { Target = T }`, that should be
WellKnownTrait::Unpin => "unpin",
WellKnownTrait::CoerceUnsized => "coerce_unsized",
WellKnownTrait::DiscriminantKind => "discriminant_kind",
+ WellKnownTrait::Generator => "generator",
}
}
};
pub(crate) use hir_def::{
- body::{Body, BodySourceMap},
+ body::Body,
expr::{Expr, ExprId, MatchArm, Pat, PatId},
LocalFieldId, VariantId,
};
db: &dyn HirDatabase,
infer: Arc<InferenceResult>,
) {
- let (body, source_map): (Arc<Body>, Arc<BodySourceMap>) =
- db.body_with_source_map(self.owner);
+ let body = db.body(self.owner);
let match_expr_ty = if infer.type_of_expr[match_expr].is_unknown() {
return;
infer: &infer,
db,
pattern_arena: &pattern_arena,
- panic_context: &|| {
- use syntax::AstNode;
- let match_expr_text = source_map
- .expr_syntax(match_expr)
- .ok()
- .and_then(|scrutinee_sptr| {
- let root = scrutinee_sptr.file_syntax(db.upcast());
- scrutinee_sptr.value.to_node(&root).syntax().parent()
- })
- .map(|node| node.to_string());
- format!(
- "expression:\n{}",
- match_expr_text.as_deref().unwrap_or("<synthesized expr>")
- )
- },
};
let report = compute_match_usefulness(&cx, &m_arms);
use hir_def::{EnumVariantId, HasModule, LocalFieldId, VariantId};
use smallvec::{smallvec, SmallVec};
+use stdx::never;
use crate::{AdtId, Interner, Scalar, Ty, TyExt, TyKind};
PatKind::Leaf { .. } | PatKind::Deref { .. } => Single,
&PatKind::Variant { enum_variant, .. } => Variant(enum_variant),
&PatKind::LiteralBool { value } => IntRange(IntRange::from_bool(value)),
- PatKind::Or { .. } => cx.bug("Or-pattern should have been expanded earlier on."),
+ PatKind::Or { .. } => {
+ never!("Or-pattern should have been expanded earlier on.");
+ Wildcard
+ }
}
}
/// this checks for inclusion.
// We inline because this has a single call site in `Matrix::specialize_constructor`.
#[inline]
- pub(super) fn is_covered_by(&self, pcx: PatCtxt<'_>, other: &Self) -> bool {
+ pub(super) fn is_covered_by(&self, _pcx: PatCtxt<'_>, other: &Self) -> bool {
// This must be kept in sync with `is_covered_by_any`.
match (self, other) {
// Wildcards cover anything
// Only a wildcard pattern can match the special extra constructor.
(NonExhaustive, _) => false,
- _ => pcx.cx.bug(&format!(
- "trying to compare incompatible constructors {:?} and {:?}",
- self, other
- )),
+ _ => {
+ never!("trying to compare incompatible constructors {:?} and {:?}", self, other);
+ // Continue with 'whatever is covered' supposed to result in false no-error diagnostic.
+ true
+ }
}
}
/// Faster version of `is_covered_by` when applied to many constructors. `used_ctors` is
/// assumed to be built from `matrix.head_ctors()` with wildcards filtered out, and `self` is
/// assumed to have been split from a wildcard.
- fn is_covered_by_any(&self, pcx: PatCtxt<'_>, used_ctors: &[Constructor]) -> bool {
+ fn is_covered_by_any(&self, _pcx: PatCtxt<'_>, used_ctors: &[Constructor]) -> bool {
if used_ctors.is_empty() {
return false;
}
// This constructor is never covered by anything else
NonExhaustive => false,
Str(..) | FloatRange(..) | Opaque | Missing | Wildcard => {
- pcx.cx.bug(&format!("found unexpected ctor in all_ctors: {:?}", self))
+ never!("found unexpected ctor in all_ctors: {:?}", self);
+ true
}
}
}
}
}
ty_kind => {
- cx.bug(&format!("Unexpected type for `Single` constructor: {:?}", ty_kind))
+ never!("Unexpected type for `Single` constructor: {:?}", ty_kind);
+ Fields::from_single_pattern(wildcard_from_ty(ty))
}
},
Slice(..) => {
// can ignore this issue.
TyKind::Ref(..) => PatKind::Deref { subpattern: subpatterns.next().unwrap() },
TyKind::Slice(..) | TyKind::Array(..) => {
- pcx.cx.bug(&format!("bad slice pattern {:?} {:?}", ctor, pcx.ty))
+ never!("bad slice pattern {:?} {:?}", ctor, pcx.ty);
+ PatKind::Wild
}
_ => PatKind::Wild,
},
Constructor::IntRange(_) => UNHANDLED,
NonExhaustive => PatKind::Wild,
Wildcard => return Pat::wildcard_from_ty(pcx.ty.clone()),
- Opaque => pcx.cx.bug("we should not try to apply an opaque constructor"),
- Missing => pcx.cx.bug(
- "trying to apply the `Missing` constructor;\
- this should have been done in `apply_constructors`",
- ),
+ Opaque => {
+ never!("we should not try to apply an opaque constructor");
+ PatKind::Wild
+ }
+ Missing => {
+ never!(
+ "trying to apply the `Missing` constructor; \
+ this should have been done in `apply_constructors`",
+ );
+ PatKind::Wild
+ }
};
Pat { ty: pcx.ty.clone(), kind: Box::new(pat) }
pub(crate) db: &'a dyn HirDatabase,
/// Lowered patterns from arms plus generated by the check.
pub(crate) pattern_arena: &'a RefCell<PatternArena>,
- pub(crate) panic_context: &'a dyn Fn() -> String,
}
impl<'a> MatchCheckCtx<'a> {
pub(super) fn type_of(&self, pat: PatId) -> Ty {
self.pattern_arena.borrow()[pat].ty.clone()
}
-
- #[track_caller]
- pub(super) fn bug(&self, info: &str) -> ! {
- panic!("bug: {}\n{}", info, (self.panic_context)());
- }
}
#[derive(Copy, Clone)]
arms: &[MatchArm],
) -> UsefulnessReport {
let mut matrix = Matrix::empty();
- let arm_usefulness: Vec<_> = arms
+ let arm_usefulness = arms
.iter()
.copied()
.map(|arm| {
continue;
}
- let referent_ty = canonicalized.decanonicalize_ty(referent_ty.value);
+ let referent_ty = canonicalized.decanonicalize_ty(&mut self.table, referent_ty);
// At this point, we have deref'd `a` to `referent_ty`. So
// imagine we are coercing from `&'a mut Vec<T>` to `&'b mut [T]`.
},
);
let res = derefs.by_ref().find_map(|(callee_deref_ty, _)| {
- self.callable_sig(
- &canonicalized.decanonicalize_ty(callee_deref_ty.value),
- args.len(),
- )
+ let ty = &canonicalized.decanonicalize_ty(&mut self.table, callee_deref_ty);
+ self.callable_sig(ty, args.len())
});
let (param_tys, ret_ty): (Vec<Ty>, Ty) = match res {
Some(res) => {
},
);
let ty = autoderef.by_ref().find_map(|(derefed_ty, _)| {
- let def_db = self.db.upcast();
let module = self.resolver.module();
+ let db = self.db;
let is_visible = |field_id: &FieldId| {
module
.map(|mod_id| {
- self.db.field_visibilities(field_id.parent)[field_id.local_id]
- .is_visible_from(def_db, mod_id)
+ db.field_visibilities(field_id.parent)[field_id.local_id]
+ .is_visible_from(db.upcast(), mod_id)
})
.unwrap_or(true)
};
- match canonicalized.decanonicalize_ty(derefed_ty.value).kind(&Interner) {
+ match canonicalized
+ .decanonicalize_ty(&mut self.table, derefed_ty)
+ .kind(&Interner)
+ {
TyKind::Tuple(_, substs) => name.as_tuple_index().and_then(|idx| {
substs
.as_slice(&Interner)
},
) {
Some(derefed_ty) => {
- canonicalized.decanonicalize_ty(derefed_ty.value)
+ canonicalized.decanonicalize_ty(&mut self.table, derefed_ty)
}
None => self.err_ty(),
}
krate,
index_trait,
);
- let self_ty =
- self_ty.map_or(self.err_ty(), |t| canonicalized.decanonicalize_ty(t.value));
+ let self_ty = self_ty.map_or(self.err_ty(), |t| {
+ canonicalized.decanonicalize_ty(&mut self.table, t)
+ });
self.resolve_associated_type_with_params(
self_ty,
self.resolve_ops_index_output(),
});
let (receiver_ty, method_ty, substs) = match resolved {
Some((ty, func)) => {
- let ty = canonicalized_receiver.decanonicalize_ty(ty);
+ let ty = canonicalized_receiver.decanonicalize_ty(&mut self.table, ty);
let generics = generics(self.db.upcast(), func.into());
let substs = self.substs_for_method_call(generics, generic_args, &ty);
self.write_method_resolution(tgt_expr, func, substs.clone());
}
impl<T: HasInterner<Interner = Interner>> Canonicalized<T> {
- pub(super) fn decanonicalize_ty(&self, ty: Ty) -> Ty {
- chalk_ir::Substitute::apply(&self.free_vars, ty, &Interner)
+ /// this method is wrong and shouldn't exist
+ pub(super) fn decanonicalize_ty(&self, table: &mut InferenceTable, ty: Canonical<Ty>) -> Ty {
+ let mut vars = self.free_vars.clone();
+ while ty.binders.len(&Interner) > vars.len() {
+ vars.push(table.new_type_var().cast(&Interner));
+ }
+ chalk_ir::Substitute::apply(&vars, ty.value, &Interner)
}
pub(super) fn apply_solution(
let mut expander = self.expander.borrow_mut();
if expander.is_some() {
(Some(expander), false)
+ } else if let Some(module_id) = self.resolver.module() {
+ *expander =
+ Some(Expander::new(self.db.upcast(), macro_call.file_id, module_id));
+ (Some(expander), true)
} else {
- if let Some(module_id) = self.resolver.module() {
- *expander = Some(Expander::new(
- self.db.upcast(),
- macro_call.file_id,
- module_id,
- ));
- (Some(expander), true)
- } else {
- (None, false)
- }
+ (None, false)
}
};
let ty = if let Some(mut expander) = expander {
) -> (Ty, Option<TypeNs>) {
let ty = match resolution {
TypeNs::TraitId(trait_) => {
- let ty = if remaining_segments.len() == 1 {
- let trait_ref =
- self.lower_trait_ref_from_resolved_path(trait_, resolved_segment, None);
- let segment = remaining_segments.first().unwrap();
- let found = self
- .db
- .trait_data(trait_ref.hir_trait_id())
- .associated_type_by_name(segment.name);
- match found {
- Some(associated_ty) => {
- // FIXME handle type parameters on the segment
- TyKind::Alias(AliasTy::Projection(ProjectionTy {
- associated_ty_id: to_assoc_type_id(associated_ty),
- substitution: trait_ref.substitution,
- }))
- .intern(&Interner)
- }
- None => {
- // FIXME: report error (associated type not found)
- TyKind::Error.intern(&Interner)
+ let ty = match remaining_segments.len() {
+ 1 => {
+ let trait_ref =
+ self.lower_trait_ref_from_resolved_path(trait_, resolved_segment, None);
+ let segment = remaining_segments.first().unwrap();
+ let found = self
+ .db
+ .trait_data(trait_ref.hir_trait_id())
+ .associated_type_by_name(segment.name);
+ match found {
+ Some(associated_ty) => {
+ // FIXME handle type parameters on the segment
+ TyKind::Alias(AliasTy::Projection(ProjectionTy {
+ associated_ty_id: to_assoc_type_id(associated_ty),
+ substitution: trait_ref.substitution,
+ }))
+ .intern(&Interner)
+ }
+ None => {
+ // FIXME: report error (associated type not found)
+ TyKind::Error.intern(&Interner)
+ }
}
}
- } else if remaining_segments.len() > 1 {
- // FIXME report error (ambiguous associated type)
- TyKind::Error.intern(&Interner)
- } else {
- let self_ty = Some(
- TyKind::BoundVar(BoundVar::new(DebruijnIndex::INNERMOST, 0))
- .intern(&Interner),
- );
- let trait_ref = self.with_shifted_in(DebruijnIndex::ONE, |ctx| {
- ctx.lower_trait_ref_from_resolved_path(trait_, resolved_segment, self_ty)
- });
- let dyn_ty = DynTy {
- bounds: crate::make_only_type_binders(
- 1,
- QuantifiedWhereClauses::from_iter(
- &Interner,
- Some(crate::wrap_empty_binders(WhereClause::Implemented(
- trait_ref,
- ))),
+ 0 => {
+ let self_ty = Some(
+ TyKind::BoundVar(BoundVar::new(DebruijnIndex::INNERMOST, 0))
+ .intern(&Interner),
+ );
+ let trait_ref = self.with_shifted_in(DebruijnIndex::ONE, |ctx| {
+ ctx.lower_trait_ref_from_resolved_path(
+ trait_,
+ resolved_segment,
+ self_ty,
+ )
+ });
+ let dyn_ty = DynTy {
+ bounds: crate::make_only_type_binders(
+ 1,
+ QuantifiedWhereClauses::from_iter(
+ &Interner,
+ Some(crate::wrap_empty_binders(WhereClause::Implemented(
+ trait_ref,
+ ))),
+ ),
),
- ),
- lifetime: static_lifetime(),
- };
- TyKind::Dyn(dyn_ty).intern(&Interner)
+ lifetime: static_lifetime(),
+ };
+ TyKind::Dyn(dyn_ty).intern(&Interner)
+ }
+ _ => {
+ // FIXME report error (ambiguous associated type)
+ TyKind::Error.intern(&Interner)
+ }
};
return (ty, None);
}
let ctx =
TyLoweringContext::new(db, &resolver).with_type_param_mode(TypeParamLoweringMode::Variable);
let generics = generics(db.upcast(), param_id.parent);
- resolver
+ let mut predicates: Vec<_> = resolver
.where_predicates_in_scope()
// we have to filter out all other predicates *first*, before attempting to lower them
.filter(|pred| match pred {
WherePredicate::Lifetime { .. } => false,
})
.flat_map(|pred| ctx.lower_where_predicate(pred, true).map(|p| make_binders(&generics, p)))
- .collect()
+ .collect();
+
+ let subst = generics.bound_vars_subst(DebruijnIndex::INNERMOST);
+ let explicitly_unsized_tys = ctx.unsized_types.into_inner();
+ let implicitly_sized_predicates =
+ implicitly_sized_clauses(db, param_id.parent, &explicitly_unsized_tys, &subst, &resolver)
+ .map(|p| make_binders(&generics, crate::wrap_empty_binders(p)));
+ predicates.extend(implicitly_sized_predicates);
+ predicates.into()
}
pub(crate) fn generic_predicates_for_param_recover(
//! For details about how this works in rustc, see the method lookup page in the
//! [rustc guide](https://rust-lang.github.io/rustc-guide/method-lookup.html)
//! and the corresponding code mostly in librustc_typeck/check/method/probe.rs.
-use std::{iter, sync::Arc};
+use std::{iter, ops::ControlFlow, sync::Arc};
use arrayvec::ArrayVec;
use base_db::{CrateId, Edition};
traits_in_scope: &FxHashSet<TraitId>,
visible_from_module: Option<ModuleId>,
name: &Name,
-) -> Option<(Ty, FunctionId)> {
+) -> Option<(Canonical<Ty>, FunctionId)> {
iterate_method_candidates(
ty,
db,
visible_from_module: Option<ModuleId>,
name: Option<&Name>,
mode: LookupMode,
- mut callback: impl FnMut(&Ty, AssocItemId) -> Option<T>,
+ mut callback: impl FnMut(&Canonical<Ty>, AssocItemId) -> Option<T>,
) -> Option<T> {
let mut slot = None;
iterate_method_candidates_dyn(
mode,
&mut |ty, item| {
assert!(slot.is_none());
- slot = callback(ty, item);
- slot.is_some()
+ if let Some(it) = callback(ty, item) {
+ slot = Some(it);
+ return ControlFlow::Break(());
+ }
+ ControlFlow::Continue(())
},
);
slot
visible_from_module: Option<ModuleId>,
name: Option<&Name>,
mode: LookupMode,
- callback: &mut dyn FnMut(&Ty, AssocItemId) -> bool,
-) -> bool {
+ callback: &mut dyn FnMut(&Canonical<Ty>, AssocItemId) -> ControlFlow<()>,
+) -> ControlFlow<()> {
match mode {
LookupMode::MethodCall => {
// For method calls, rust first does any number of autoderef, and then one
let deref_chain = autoderef_method_receiver(db, krate, ty);
for i in 0..deref_chain.len() {
- if iterate_method_candidates_with_autoref(
+ iterate_method_candidates_with_autoref(
&deref_chain[i..],
db,
env.clone(),
visible_from_module,
name,
callback,
- ) {
- return true;
- }
+ )?;
}
- false
+ ControlFlow::Continue(())
}
LookupMode::Path => {
// No autoderef for path lookups
traits_in_scope: &FxHashSet<TraitId>,
visible_from_module: Option<ModuleId>,
name: Option<&Name>,
- mut callback: &mut dyn FnMut(&Ty, AssocItemId) -> bool,
-) -> bool {
- if iterate_method_candidates_by_receiver(
+ mut callback: &mut dyn FnMut(&Canonical<Ty>, AssocItemId) -> ControlFlow<()>,
+) -> ControlFlow<()> {
+ iterate_method_candidates_by_receiver(
&deref_chain[0],
&deref_chain[1..],
db,
visible_from_module,
name,
&mut callback,
- ) {
- return true;
- }
+ )?;
+
let refed = Canonical {
binders: deref_chain[0].binders.clone(),
value: TyKind::Ref(Mutability::Not, static_lifetime(), deref_chain[0].value.clone())
.intern(&Interner),
};
- if iterate_method_candidates_by_receiver(
+
+ iterate_method_candidates_by_receiver(
&refed,
deref_chain,
db,
visible_from_module,
name,
&mut callback,
- ) {
- return true;
- }
+ )?;
+
let ref_muted = Canonical {
binders: deref_chain[0].binders.clone(),
value: TyKind::Ref(Mutability::Mut, static_lifetime(), deref_chain[0].value.clone())
.intern(&Interner),
};
- if iterate_method_candidates_by_receiver(
+
+ iterate_method_candidates_by_receiver(
&ref_muted,
deref_chain,
db,
visible_from_module,
name,
&mut callback,
- ) {
- return true;
- }
- false
+ )
}
fn iterate_method_candidates_by_receiver(
traits_in_scope: &FxHashSet<TraitId>,
visible_from_module: Option<ModuleId>,
name: Option<&Name>,
- mut callback: &mut dyn FnMut(&Ty, AssocItemId) -> bool,
-) -> bool {
+ mut callback: &mut dyn FnMut(&Canonical<Ty>, AssocItemId) -> ControlFlow<()>,
+) -> ControlFlow<()> {
// We're looking for methods with *receiver* type receiver_ty. These could
// be found in any of the derefs of receiver_ty, so we have to go through
// that.
for self_ty in std::iter::once(receiver_ty).chain(rest_of_deref_chain) {
- if iterate_inherent_methods(
+ iterate_inherent_methods(
self_ty,
db,
env.clone(),
krate,
visible_from_module,
&mut callback,
- ) {
- return true;
- }
+ )?
}
+
for self_ty in std::iter::once(receiver_ty).chain(rest_of_deref_chain) {
- if iterate_trait_method_candidates(
+ iterate_trait_method_candidates(
self_ty,
db,
env.clone(),
name,
Some(receiver_ty),
&mut callback,
- ) {
- return true;
- }
+ )?
}
- false
+
+ ControlFlow::Continue(())
}
fn iterate_method_candidates_for_self_ty(
traits_in_scope: &FxHashSet<TraitId>,
visible_from_module: Option<ModuleId>,
name: Option<&Name>,
- mut callback: &mut dyn FnMut(&Ty, AssocItemId) -> bool,
-) -> bool {
- if iterate_inherent_methods(
+ mut callback: &mut dyn FnMut(&Canonical<Ty>, AssocItemId) -> ControlFlow<()>,
+) -> ControlFlow<()> {
+ iterate_inherent_methods(
self_ty,
db,
env.clone(),
krate,
visible_from_module,
&mut callback,
- ) {
- return true;
- }
+ )?;
iterate_trait_method_candidates(self_ty, db, env, krate, traits_in_scope, name, None, callback)
}
traits_in_scope: &FxHashSet<TraitId>,
name: Option<&Name>,
receiver_ty: Option<&Canonical<Ty>>,
- callback: &mut dyn FnMut(&Ty, AssocItemId) -> bool,
-) -> bool {
+ callback: &mut dyn FnMut(&Canonical<Ty>, AssocItemId) -> ControlFlow<()>,
+) -> ControlFlow<()> {
let receiver_is_array = matches!(self_ty.value.kind(&Interner), chalk_ir::TyKind::Array(..));
// if ty is `dyn Trait`, the trait doesn't need to be in scope
let inherent_trait =
self_ty.value.dyn_trait().into_iter().flat_map(|t| all_super_traits(db.upcast(), t));
- let env_traits = if let TyKind::Placeholder(_) = self_ty.value.kind(&Interner) {
- // if we have `T: Trait` in the param env, the trait doesn't need to be in scope
- env.traits_in_scope_from_clauses(&self_ty.value)
- .flat_map(|t| all_super_traits(db.upcast(), t))
- .collect()
- } else {
- Vec::new()
+ let env_traits = match self_ty.value.kind(&Interner) {
+ TyKind::Placeholder(_) => {
+ // if we have `T: Trait` in the param env, the trait doesn't need to be in scope
+ env.traits_in_scope_from_clauses(&self_ty.value)
+ .flat_map(|t| all_super_traits(db.upcast(), t))
+ .collect()
+ }
+ _ => Vec::new(),
};
let traits =
inherent_trait.chain(env_traits.into_iter()).chain(traits_in_scope.iter().copied());
+
'traits: for t in traits {
let data = db.trait_data(t);
}
known_implemented = true;
// FIXME: we shouldn't be ignoring the binders here
- if callback(&self_ty.value, *item) {
- return true;
- }
+ callback(self_ty, *item)?
}
}
- false
+ ControlFlow::Continue(())
}
fn filter_inherent_impls_for_self_ty<'i>(
receiver_ty: Option<&Canonical<Ty>>,
krate: CrateId,
visible_from_module: Option<ModuleId>,
- callback: &mut dyn FnMut(&Ty, AssocItemId) -> bool,
-) -> bool {
+ callback: &mut dyn FnMut(&Canonical<Ty>, AssocItemId) -> ControlFlow<()>,
+) -> ControlFlow<()> {
let def_crates = match def_crates(db, &self_ty.value, krate) {
Some(k) => k,
- None => return false,
+ None => return ControlFlow::Continue(()),
};
+
for krate in def_crates {
let impls = db.inherent_impls_in_crate(krate);
cov_mark::hit!(impl_self_type_match_without_receiver);
continue;
}
- let receiver_ty = receiver_ty.map(|x| &x.value).unwrap_or(&self_ty.value);
- if callback(receiver_ty, item) {
- return true;
- }
+ let receiver_ty = receiver_ty.unwrap_or(self_ty);
+ callback(receiver_ty, item)?;
}
}
}
- false
+ ControlFlow::Continue(())
}
/// Returns the self type for the index trait call.
"#,
);
}
+
+#[test]
+fn bitslice_panic() {
+ check_no_mismatches(
+ r#"
+//- minicore: option, deref
+
+pub trait BitView {
+ type Store;
+}
+
+pub struct Lsb0;
+
+pub struct BitArray<V: BitView> { }
+
+pub struct BitSlice<T> { }
+
+impl<V: BitView> core::ops::Deref for BitArray<V> {
+ type Target = BitSlice<V::Store>;
+}
+
+impl<T> BitSlice<T> {
+ pub fn split_first(&self) -> Option<(T, &Self)> { loop {} }
+}
+
+fn multiexp_inner() {
+ let exp: &BitArray<Foo>;
+ exp.split_first();
+}
+ "#,
+ );
+}
use indexmap::IndexMap;
use hir::Semantics;
-use ide_db::{call_info::FnCallNode, RootDatabase};
-use syntax::{ast, AstNode, TextRange};
-
-use crate::{
- display::TryToNav, goto_definition, references, FilePosition, NavigationTarget, RangeInfo,
+use ide_db::{
+ call_info::FnCallNode,
+ defs::{Definition, NameClass, NameRefClass},
+ helpers::pick_best_token,
+ search::FileReference,
+ RootDatabase,
};
+use syntax::{ast, AstNode, SyntaxKind::NAME, TextRange};
+
+use crate::{display::TryToNav, goto_definition, FilePosition, NavigationTarget, RangeInfo};
#[derive(Debug, Clone)]
pub struct CallItem {
}
impl CallItem {
- #[cfg(test)]
- pub(crate) fn assert_match(&self, expected: &str) {
- let actual = self.debug_render();
- test_utils::assert_eq_text!(expected.trim(), actual.trim(),);
- }
-
#[cfg(test)]
pub(crate) fn debug_render(&self) -> String {
format!("{} : {:?}", self.target.debug_render(), self.ranges)
goto_definition::goto_definition(db, position)
}
-pub(crate) fn incoming_calls(db: &RootDatabase, position: FilePosition) -> Option<Vec<CallItem>> {
- let sema = Semantics::new(db);
-
- // 1. Find all refs
- // 2. Loop through refs and determine unique fndef. This will become our `from: CallHierarchyItem,` in the reply.
- // 3. Add ranges relative to the start of the fndef.
- let refs = references::find_all_refs(&sema, position, None)?;
+pub(crate) fn incoming_calls(
+ db: &RootDatabase,
+ FilePosition { file_id, offset }: FilePosition,
+) -> Option<Vec<CallItem>> {
+ let sema = &Semantics::new(db);
+ let file = sema.parse(file_id);
+ let file = file.syntax();
let mut calls = CallLocations::default();
- for (file_id, references) in refs.into_iter().flat_map(|refs| refs.references) {
- let file = sema.parse(file_id);
- let file = file.syntax();
- for (relative_range, token) in references
- .into_iter()
- .filter_map(|(range, _)| Some(range).zip(file.token_at_offset(range.start()).next()))
- {
- let token = sema.descend_into_macros(token);
+ let references = sema
+ .find_nodes_at_offset_with_descend(file, offset)
+ .filter_map(move |node| match node {
+ ast::NameLike::NameRef(name_ref) => match NameRefClass::classify(sema, &name_ref)? {
+ NameRefClass::Definition(
+ def @ Definition::ModuleDef(hir::ModuleDef::Function(_)),
+ ) => Some(def),
+ _ => None,
+ },
+ ast::NameLike::Name(name) => match NameClass::classify(sema, &name)? {
+ NameClass::Definition(def @ Definition::ModuleDef(hir::ModuleDef::Function(_))) => {
+ Some(def)
+ }
+ _ => None,
+ },
+ ast::NameLike::Lifetime(_) => None,
+ })
+ .flat_map(|func| func.usages(sema).all());
+
+ for (_, references) in references {
+ let references = references.into_iter().map(|FileReference { name, .. }| name);
+ for name in references {
// This target is the containing function
- if let Some(nav) = token.ancestors().find_map(|node| {
+ let nav = sema.ancestors_with_macros(name.syntax().clone()).find_map(|node| {
let def = ast::Fn::cast(node).and_then(|fn_| sema.to_def(&fn_))?;
def.try_to_nav(sema.db)
- }) {
- calls.add(&nav, relative_range);
+ });
+ if let Some(nav) = nav {
+ calls.add(nav, sema.original_range(name.syntax()).range);
}
}
}
let file_id = position.file_id;
let file = sema.parse(file_id);
let file = file.syntax();
- let token = file.token_at_offset(position.offset).next()?;
- let token = sema.descend_into_macros(token);
-
+ let token = pick_best_token(file.token_at_offset(position.offset), |kind| match kind {
+ NAME => 1,
+ _ => 0,
+ })?;
let mut calls = CallLocations::default();
- token
- .parent()
+ sema.descend_into_macros_many(token)
.into_iter()
- .flat_map(|it| it.descendants())
+ .filter_map(|it| it.ancestors().nth(1).and_then(ast::Item::cast))
+ .filter_map(|item| match item {
+ ast::Item::Const(c) => c.body().map(|it| it.syntax().descendants()),
+ ast::Item::Fn(f) => f.body().map(|it| it.syntax().descendants()),
+ ast::Item::Static(s) => s.body().map(|it| it.syntax().descendants()),
+ _ => None,
+ })
+ .flatten()
.filter_map(|node| FnCallNode::with_node_exact(&node))
.filter_map(|call_node| {
let name_ref = call_node.name_ref()?;
}?;
Some((func_target, name_ref.syntax().text_range()))
})
- .for_each(|(nav, range)| calls.add(&nav, range));
+ .for_each(|(nav, range)| calls.add(nav, range));
Some(calls.into_items())
}
}
impl CallLocations {
- fn add(&mut self, target: &NavigationTarget, range: TextRange) {
- self.funcs.entry(target.clone()).or_default().push(range);
+ fn add(&mut self, target: NavigationTarget, range: TextRange) {
+ self.funcs.entry(target).or_default().push(range);
}
fn into_items(self) -> Vec<CallItem> {
#[cfg(test)]
mod tests {
+ use expect_test::{expect, Expect};
use ide_db::base_db::FilePosition;
+ use itertools::Itertools;
use crate::fixture;
fn check_hierarchy(
ra_fixture: &str,
- expected: &str,
- expected_incoming: &[&str],
- expected_outgoing: &[&str],
+ expected: Expect,
+ expected_incoming: Expect,
+ expected_outgoing: Expect,
) {
let (analysis, pos) = fixture::position(ra_fixture);
let mut navs = analysis.call_hierarchy(pos).unwrap().unwrap().info;
assert_eq!(navs.len(), 1);
let nav = navs.pop().unwrap();
- nav.assert_match(expected);
+ expected.assert_eq(&nav.debug_render());
let item_pos =
FilePosition { file_id: nav.file_id, offset: nav.focus_or_full_range().start() };
let incoming_calls = analysis.incoming_calls(item_pos).unwrap().unwrap();
- assert_eq!(incoming_calls.len(), expected_incoming.len());
-
- for call in 0..incoming_calls.len() {
- incoming_calls[call].assert_match(expected_incoming[call]);
- }
+ expected_incoming
+ .assert_eq(&incoming_calls.into_iter().map(|call| call.debug_render()).join("\n"));
let outgoing_calls = analysis.outgoing_calls(item_pos).unwrap().unwrap();
- assert_eq!(outgoing_calls.len(), expected_outgoing.len());
-
- for call in 0..outgoing_calls.len() {
- outgoing_calls[call].assert_match(expected_outgoing[call]);
- }
+ expected_outgoing
+ .assert_eq(&outgoing_calls.into_iter().map(|call| call.debug_render()).join("\n"));
}
#[test]
call$0ee();
}
"#,
- "callee Function FileId(0) 0..14 3..9",
- &["caller Function FileId(0) 15..44 18..24 : [33..39]"],
- &[],
+ expect![["callee Function FileId(0) 0..14 3..9"]],
+ expect![["caller Function FileId(0) 15..44 18..24 : [33..39]"]],
+ expect![[]],
);
}
callee();
}
"#,
- "callee Function FileId(0) 0..14 3..9",
- &["caller Function FileId(0) 15..44 18..24 : [33..39]"],
- &[],
+ expect![["callee Function FileId(0) 0..14 3..9"]],
+ expect![["caller Function FileId(0) 15..44 18..24 : [33..39]"]],
+ expect![[]],
);
}
callee();
}
"#,
- "callee Function FileId(0) 0..14 3..9",
- &["caller Function FileId(0) 15..58 18..24 : [33..39, 47..53]"],
- &[],
+ expect![["callee Function FileId(0) 0..14 3..9"]],
+ expect![["caller Function FileId(0) 15..58 18..24 : [33..39, 47..53]"]],
+ expect![[]],
);
}
callee();
}
"#,
- "callee Function FileId(0) 0..14 3..9",
- &[
- "caller1 Function FileId(0) 15..45 18..25 : [34..40]",
- "caller2 Function FileId(0) 47..77 50..57 : [66..72]",
- ],
- &[],
+ expect![["callee Function FileId(0) 0..14 3..9"]],
+ expect![["
+ caller1 Function FileId(0) 15..45 18..25 : [34..40]
+ caller2 Function FileId(0) 47..77 50..57 : [66..72]"]],
+ expect![[]],
);
}
}
}
"#,
- "callee Function FileId(0) 0..14 3..9",
- &[
- "caller1 Function FileId(0) 15..45 18..25 : [34..40]",
- "test_caller Function FileId(0) 95..149 110..121 : [134..140]",
- ],
- &[],
+ expect![["callee Function FileId(0) 0..14 3..9"]],
+ expect![[r#"
+ caller1 Function FileId(0) 15..45 18..25 : [34..40]
+ test_caller Function FileId(0) 95..149 110..121 : [134..140]"#]],
+ expect![[]],
);
}
//- /foo/mod.rs
pub fn callee() {}
"#,
- "callee Function FileId(1) 0..18 7..13",
- &["caller Function FileId(0) 27..56 30..36 : [45..51]"],
- &[],
+ expect![["callee Function FileId(1) 0..18 7..13"]],
+ expect![["caller Function FileId(0) 27..56 30..36 : [45..51]"]],
+ expect![[]],
);
}
callee();
}
"#,
- "caller Function FileId(0) 15..58 18..24",
- &[],
- &["callee Function FileId(0) 0..14 3..9 : [33..39, 47..53]"],
+ expect![["caller Function FileId(0) 15..58 18..24"]],
+ expect![[]],
+ expect![["callee Function FileId(0) 0..14 3..9 : [33..39, 47..53]"]],
);
}
//- /foo/mod.rs
pub fn callee() {}
"#,
- "caller Function FileId(0) 27..56 30..36",
- &[],
- &["callee Function FileId(1) 0..18 7..13 : [45..51]"],
+ expect![["caller Function FileId(0) 27..56 30..36"]],
+ expect![[]],
+ expect![["callee Function FileId(1) 0..18 7..13 : [45..51]"]],
);
}
}
"#,
- "caller2 Function FileId(0) 33..64 36..43",
- &["caller1 Function FileId(0) 0..31 3..10 : [19..26]"],
- &["caller3 Function FileId(0) 66..83 69..76 : [52..59]"],
+ expect![["caller2 Function FileId(0) 33..64 36..43"]],
+ expect![["caller1 Function FileId(0) 0..31 3..10 : [19..26]"]],
+ expect![["caller3 Function FileId(0) 66..83 69..76 : [52..59]"]],
);
}
a$0()
}
"#,
- "a Function FileId(0) 0..18 3..4",
- &["main Function FileId(0) 31..52 34..38 : [47..48]"],
- &["b Function FileId(0) 20..29 23..24 : [13..14]"],
+ expect![["a Function FileId(0) 0..18 3..4"]],
+ expect![["main Function FileId(0) 31..52 34..38 : [47..48]"]],
+ expect![["b Function FileId(0) 20..29 23..24 : [13..14]"]],
);
check_hierarchy(
a()
}
"#,
- "b Function FileId(0) 20..29 23..24",
- &["a Function FileId(0) 0..18 3..4 : [13..14]"],
- &[],
+ expect![["b Function FileId(0) 20..29 23..24"]],
+ expect![["a Function FileId(0) 0..18 3..4 : [13..14]"]],
+ expect![[]],
+ );
+ }
+
+ #[test]
+ fn test_call_hierarchy_in_macros_incoming() {
+ check_hierarchy(
+ r#"
+macro_rules! define {
+ ($ident:ident) => {
+ fn $ident {}
+ }
+}
+macro_rules! call {
+ ($ident:ident) => {
+ $ident()
+ }
+}
+define!(callee)
+fn caller() {
+ call!(call$0ee);
+}
+"#,
+ expect![[r#"callee Function FileId(0) 144..159 152..158"#]],
+ expect![[r#"caller Function FileId(0) 160..194 163..169 : [184..190]"#]],
+ expect![[]],
+ );
+ check_hierarchy(
+ r#"
+macro_rules! define {
+ ($ident:ident) => {
+ fn $ident {}
+ }
+}
+macro_rules! call {
+ ($ident:ident) => {
+ $ident()
+ }
+}
+define!(cal$0lee)
+fn caller() {
+ call!(callee);
+}
+"#,
+ expect![[r#"callee Function FileId(0) 144..159 152..158"#]],
+ expect![[r#"caller Function FileId(0) 160..194 163..169 : [184..190]"#]],
+ expect![[]],
+ );
+ }
+
+ #[test]
+ fn test_call_hierarchy_in_macros_outgoing() {
+ check_hierarchy(
+ r#"
+macro_rules! define {
+ ($ident:ident) => {
+ fn $ident {}
+ }
+}
+macro_rules! call {
+ ($ident:ident) => {
+ $ident()
+ }
+}
+define!(callee)
+fn caller$0() {
+ call!(callee);
+}
+"#,
+ expect![[r#"caller Function FileId(0) 160..194 163..169"#]],
+ expect![[]],
+ // FIXME
+ expect![[]],
);
}
}
//! into types that may be used to render in a UI.
pub(crate) mod navigation_target;
-
pub(crate) use navigation_target::{ToNav, TryToNav};
-
-pub(crate) use syntax::display::macro_label;
module.to_nav(db)
}
- #[cfg(test)]
- pub(crate) fn assert_match(&self, expected: &str) {
- let actual = self.debug_render();
- test_utils::assert_eq_text!(expected.trim(), actual.trim(),);
- }
-
#[cfg(test)]
pub(crate) fn debug_render(&self) -> String {
let mut buf = format!(
helpers::pick_best_token,
RootDatabase,
};
-use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxNode, TextRange, T};
+use syntax::{
+ ast::{self, IsString},
+ match_ast, AstNode, AstToken,
+ SyntaxKind::*,
+ SyntaxNode, SyntaxToken, TextRange, TextSize, T,
+};
use crate::{
doc_links::intra_doc_links::{parse_intra_doc_link, strip_prefixes_suffixes},
}
}
+pub(crate) struct DocCommentToken {
+ doc_token: SyntaxToken,
+ prefix_len: TextSize,
+}
+
+pub(crate) fn token_as_doc_comment(doc_token: &SyntaxToken) -> Option<DocCommentToken> {
+ (match_ast! {
+ match doc_token {
+ ast::Comment(comment) => TextSize::try_from(comment.prefix().len()).ok(),
+ ast::String(string) => doc_token.ancestors().find_map(ast::Attr::cast)
+ .filter(|attr| attr.simple_name().as_deref() == Some("doc")).and_then(|_| string.open_quote_text_range().map(|it| it.len())),
+ _ => None,
+ }
+ }).map(|prefix_len| DocCommentToken { prefix_len, doc_token: doc_token.clone() })
+}
+
+impl DocCommentToken {
+ pub(crate) fn get_definition_with_descend_at<T>(
+ self,
+ sema: &Semantics<RootDatabase>,
+ offset: TextSize,
+ // Definition, CommentOwner, range of intra doc link in original file
+ mut cb: impl FnMut(Definition, SyntaxNode, TextRange) -> Option<T>,
+ ) -> Option<T> {
+ let DocCommentToken { prefix_len, doc_token } = self;
+ // offset relative to the comments contents
+ let original_start = doc_token.text_range().start();
+ let relative_comment_offset = offset - original_start - prefix_len;
+
+ sema.descend_into_macros_many(doc_token.clone()).into_iter().find_map(|t| {
+ let (node, descended_prefix_len) = match_ast! {
+ match t {
+ ast::Comment(comment) => (t.parent()?, TextSize::try_from(comment.prefix().len()).ok()?),
+ ast::String(string) => (t.ancestors().skip_while(|n| n.kind() != ATTR).nth(1)?, string.open_quote_text_range()?.len()),
+ _ => return None,
+ }
+ };
+ let token_start = t.text_range().start();
+ let abs_in_expansion_offset = token_start + relative_comment_offset + descended_prefix_len;
+
+ let (attributes, def) = doc_attributes(sema, &node)?;
+ let (docs, doc_mapping) = attributes.docs_with_rangemap(sema.db)?;
+ let (in_expansion_range, link, ns) =
+ extract_definitions_from_docs(&docs).into_iter().find_map(|(range, link, ns)| {
+ let mapped = doc_mapping.map(range)?;
+ (mapped.value.contains(abs_in_expansion_offset)).then(|| (mapped.value, link, ns))
+ })?;
+ // get the relative range to the doc/attribute in the expansion
+ let in_expansion_relative_range = in_expansion_range - descended_prefix_len - token_start;
+ // Apply relative range to the original input comment
+ let absolute_range = in_expansion_relative_range + original_start + prefix_len;
+ let def = match resolve_doc_path_for_def(sema.db, def, &link, ns)? {
+ Either::Left(it) => Definition::ModuleDef(it),
+ Either::Right(it) => Definition::Macro(it),
+ };
+ cb(def, node, absolute_range)
+ })
+ }
+}
+
fn broken_link_clone_cb<'a, 'b>(link: BrokenLink<'a>) -> Option<(CowStr<'b>, CowStr<'b>)> {
// These allocations are actually unnecessary but the lifetimes on BrokenLinkCallback are wrong
// this is fixed in the repo but not on the crates.io release yet
SyntaxKind::IDENT => 1,
_ => 0,
})?;
+
let descended = sema.descend_into_macros(tok.clone());
if let Some(attr) = descended.ancestors().find_map(ast::Attr::cast) {
if let Some((path, tt)) = attr.as_simple_call() {
}
}
}
+
+ // FIXME: Intermix attribute and bang! expansions
+ // currently we only recursively expand one of the two types
let mut expanded = None;
let mut name = None;
for node in tok.ancestors() {
//! Utilities for creating `Analysis` instances for tests.
+use hir::db::DefDatabase;
use ide_db::base_db::fixture::ChangeFixture;
use test_utils::{extract_annotations, RangeOrOffset};
pub(crate) fn file(ra_fixture: &str) -> (Analysis, FileId) {
let mut host = AnalysisHost::default();
let change_fixture = ChangeFixture::parse(ra_fixture);
+ host.db.set_enable_proc_attr_macros(true);
host.db.apply_change(change_fixture.change);
(host.analysis(), change_fixture.files[0])
}
pub(crate) fn position(ra_fixture: &str) -> (Analysis, FilePosition) {
let mut host = AnalysisHost::default();
let change_fixture = ChangeFixture::parse(ra_fixture);
+ host.db.set_enable_proc_attr_macros(true);
host.db.apply_change(change_fixture.change);
let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
let offset = range_or_offset.expect_offset();
pub(crate) fn range(ra_fixture: &str) -> (Analysis, FileRange) {
let mut host = AnalysisHost::default();
let change_fixture = ChangeFixture::parse(ra_fixture);
+ host.db.set_enable_proc_attr_macros(true);
host.db.apply_change(change_fixture.change);
let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
let range = range_or_offset.expect_range();
pub(crate) fn range_or_position(ra_fixture: &str) -> (Analysis, FileId, RangeOrOffset) {
let mut host = AnalysisHost::default();
let change_fixture = ChangeFixture::parse(ra_fixture);
+ host.db.set_enable_proc_attr_macros(true);
host.db.apply_change(change_fixture.change);
let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
(host.analysis(), file_id, range_or_offset)
pub(crate) fn annotations(ra_fixture: &str) -> (Analysis, FilePosition, Vec<(FileRange, String)>) {
let mut host = AnalysisHost::default();
let change_fixture = ChangeFixture::parse(ra_fixture);
+ host.db.set_enable_proc_attr_macros(true);
host.db.apply_change(change_fixture.change);
let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
let offset = range_or_offset.expect_offset();
-use std::{convert::TryInto, iter};
+use std::convert::TryInto;
-use either::Either;
-use hir::{AsAssocItem, InFile, ModuleDef, Semantics};
+use crate::{
+ display::TryToNav, doc_links::token_as_doc_comment, FilePosition, NavigationTarget, RangeInfo,
+};
+use hir::{AsAssocItem, ModuleDef, Semantics};
use ide_db::{
base_db::{AnchoredPath, FileId, FileLoader},
- defs::{Definition, NameClass, NameRefClass},
- helpers::{pick_best_token, try_resolve_derive_input_at},
+ defs::Definition,
+ helpers::pick_best_token,
RootDatabase,
};
-use syntax::{ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TextRange, T};
-
-use crate::{
- display::{ToNav, TryToNav},
- doc_links::{doc_attributes, extract_definitions_from_docs, resolve_doc_path_for_def},
- FilePosition, NavigationTarget, RangeInfo,
-};
+use itertools::Itertools;
+use syntax::{ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TextRange, T};
// Feature: Go to Definition
//
db: &RootDatabase,
position: FilePosition,
) -> Option<RangeInfo<Vec<NavigationTarget>>> {
- let sema = Semantics::new(db);
+ let sema = &Semantics::new(db);
let file = sema.parse(position.file_id).syntax().clone();
let original_token =
pick_best_token(file.token_at_offset(position.offset), |kind| match kind {
kind if kind.is_trivia() => 0,
_ => 1,
})?;
- let token = sema.descend_into_macros(original_token.clone());
- let parent = token.parent()?;
- if let Some(_) = ast::Comment::cast(token.clone()) {
- let (attributes, def) = doc_attributes(&sema, &parent)?;
- let (docs, doc_mapping) = attributes.docs_with_rangemap(db)?;
- let (_, link, ns) =
- extract_definitions_from_docs(&docs).into_iter().find(|&(range, ..)| {
- doc_mapping.map(range).map_or(false, |InFile { file_id, value: range }| {
- file_id == position.file_id.into() && range.contains(position.offset)
- })
- })?;
- let nav = resolve_doc_path_for_def(db, def, &link, ns)?.try_to_nav(db)?;
- return Some(RangeInfo::new(original_token.text_range(), vec![nav]));
- }
-
- let navs = match_ast! {
- match parent {
- ast::NameRef(name_ref) => {
- reference_definition(&sema, Either::Right(&name_ref))
- },
- ast::Name(name) => {
- match NameClass::classify(&sema, &name)? {
- NameClass::Definition(def) | NameClass::ConstReference(def) => {
- try_find_trait_item_definition(sema.db, &def).unwrap_or_else(|| def_to_nav(sema.db, def))
- }
- NameClass::PatFieldShorthand { local_def, field_ref } => {
- local_and_field_to_nav(sema.db, local_def, field_ref)
- },
- }
- },
- ast::Lifetime(lt) => if let Some(name_class) = NameClass::classify_lifetime(&sema, <) {
- match name_class {
- NameClass::Definition(def) => def_to_nav(sema.db, def),
- _ => return None,
+ if let Some(doc_comment) = token_as_doc_comment(&original_token) {
+ return doc_comment.get_definition_with_descend_at(sema, position.offset, |def, _, _| {
+ let nav = def.try_to_nav(db)?;
+ Some(RangeInfo::new(original_token.text_range(), vec![nav]))
+ });
+ }
+ let navs = sema
+ .descend_into_macros_many(original_token.clone())
+ .into_iter()
+ .filter_map(|token| {
+ let parent = token.parent()?;
+ if let Some(tt) = ast::TokenTree::cast(parent.clone()) {
+ if let x @ Some(_) =
+ try_lookup_include_path(&sema, tt, token.clone(), position.file_id)
+ {
+ return x;
}
- } else {
- reference_definition(&sema, Either::Left(<))
- },
- ast::TokenTree(tt) => try_lookup_include_path_or_derive(&sema, tt, token, position.file_id)?,
- _ => return None,
- }
- };
+ }
+ Some(
+ Definition::from_token(&sema, &token)
+ .into_iter()
+ .flat_map(|def| {
+ try_find_trait_item_definition(sema.db, &def)
+ .unwrap_or_else(|| def_to_nav(sema.db, def))
+ })
+ .collect::<Vec<_>>(),
+ )
+ })
+ .flatten()
+ .unique()
+ .collect::<Vec<NavigationTarget>>();
Some(RangeInfo::new(original_token.text_range(), navs))
}
-fn try_lookup_include_path_or_derive(
+fn try_lookup_include_path(
sema: &Semantics<RootDatabase>,
tt: ast::TokenTree,
token: SyntaxToken,
file_id: FileId,
) -> Option<Vec<NavigationTarget>> {
- match ast::String::cast(token.clone()) {
- Some(token) => {
- let path = token.value()?.into_owned();
- let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?;
- let name = macro_call.path()?.segment()?.name_ref()?;
- if !matches!(&*name.text(), "include" | "include_str" | "include_bytes") {
- return None;
- }
- let file_id = sema.db.resolve_path(AnchoredPath { anchor: file_id, path: &path })?;
- let size = sema.db.file_text(file_id).len().try_into().ok()?;
- Some(vec![NavigationTarget {
- file_id,
- full_range: TextRange::new(0.into(), size),
- name: path.into(),
- focus_range: None,
- kind: None,
- container_name: None,
- description: None,
- docs: None,
- }])
- }
- None => try_resolve_derive_input_at(
- sema,
- &tt.syntax().ancestors().nth(2).and_then(ast::Attr::cast)?,
- &token,
- )
- .and_then(|it| it.try_to_nav(sema.db))
- .map(|it| vec![it]),
- }
+ let token = ast::String::cast(token.clone())?;
+ let path = token.value()?.into_owned();
+ let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?;
+ let name = macro_call.path()?.segment()?.name_ref()?;
+ if !matches!(&*name.text(), "include" | "include_str" | "include_bytes") {
+ return None;
+ }
+ let file_id = sema.db.resolve_path(AnchoredPath { anchor: file_id, path: &path })?;
+ let size = sema.db.file_text(file_id).len().try_into().ok()?;
+ Some(vec![NavigationTarget {
+ file_id,
+ full_range: TextRange::new(0.into(), size),
+ name: path.into(),
+ focus_range: None,
+ kind: None,
+ container_name: None,
+ description: None,
+ docs: None,
+ }])
}
/// finds the trait definition of an impl'd item
.map(|it| vec![it])
}
-pub(crate) fn reference_definition(
- sema: &Semantics<RootDatabase>,
- name_ref: Either<&ast::Lifetime, &ast::NameRef>,
-) -> Vec<NavigationTarget> {
- let name_kind = match name_ref.either(
- |lifetime| NameRefClass::classify_lifetime(sema, lifetime),
- |name_ref| NameRefClass::classify(sema, name_ref),
- ) {
- Some(class) => class,
- None => return Vec::new(),
- };
- match name_kind {
- NameRefClass::Definition(def) => def_to_nav(sema.db, def),
- NameRefClass::FieldShorthand { local_ref, field_ref } => {
- local_and_field_to_nav(sema.db, local_ref, field_ref)
- }
- }
-}
-
fn def_to_nav(db: &RootDatabase, def: Definition) -> Vec<NavigationTarget> {
def.try_to_nav(db).map(|it| vec![it]).unwrap_or_default()
}
-fn local_and_field_to_nav(
- db: &RootDatabase,
- local: hir::Local,
- field: hir::Field,
-) -> Vec<NavigationTarget> {
- iter::once(local.to_nav(db)).chain(field.try_to_nav(db)).collect()
-}
-
#[cfg(test)]
mod tests {
use ide_db::base_db::FileRange;
use crate::fixture;
+ #[track_caller]
fn check(ra_fixture: &str) {
let (analysis, position, expected) = fixture::annotations(ra_fixture);
let navs = analysis.goto_definition(position).unwrap().expect("no definition found").info;
assert!(navs.is_empty(), "didn't expect this to resolve anywhere: {:?}", navs)
}
+ #[test]
+ fn goto_def_in_mac_call_in_attr_invoc() {
+ check(
+ r#"
+//- proc_macros: identity
+pub struct Struct {
+ // ^^^^^^
+ field: i32,
+}
+
+macro_rules! identity {
+ ($($tt:tt)*) => {$($tt)*};
+}
+
+#[proc_macros::identity]
+fn function() {
+ identity!(Struct$0 { field: 0 });
+}
+
+"#,
+ )
+ }
+
#[test]
fn goto_def_for_extern_crate() {
check(
"#,
);
}
+
+ #[test]
+ fn goto_def_in_macro_multi() {
+ check(
+ r#"
+struct Foo {
+ foo: ()
+ //^^^
+}
+macro_rules! foo {
+ ($ident:ident) => {
+ fn $ident(Foo { $ident }: Foo) {}
+ }
+}
+foo!(foo$0);
+ //^^^
+ //^^^
+"#,
+ );
+ check(
+ r#"
+fn bar() {}
+ //^^^
+struct bar;
+ //^^^
+macro_rules! foo {
+ ($ident:ident) => {
+ fn foo() {
+ let _: $ident = $ident;
+ }
+ }
+}
+
+foo!(bar$0);
+"#,
+ );
+ }
}
);
}
+ #[test]
+ fn test_hl_local_in_attr() {
+ check(
+ r#"
+//- proc_macros: identity
+#[proc_macros::identity]
+fn foo() {
+ let mut bar = 3;
+ // ^^^ write
+ bar$0;
+ // ^^^ read
+}
+"#,
+ );
+ }
+
#[test]
fn test_multi_macro_usage() {
check(
+mod render;
+
+#[cfg(test)]
+mod tests;
+
+use std::iter;
+
use either::Either;
-use hir::{AsAssocItem, HasAttrs, HasSource, HirDisplay, Semantics, TypeInfo};
+use hir::{HasSource, Semantics};
use ide_db::{
- base_db::{FileRange, SourceDatabase},
- defs::{Definition, NameClass, NameRefClass},
- helpers::{
- generated_lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES},
- pick_best_token, try_resolve_derive_input_at, FamousDefs,
- },
+ base_db::FileRange,
+ defs::Definition,
+ helpers::{pick_best_token, FamousDefs},
RootDatabase,
};
use itertools::Itertools;
-use stdx::format_to;
-use syntax::{
- algo, ast, display::fn_as_proc_macro_label, match_ast, AstNode, Direction, SyntaxKind::*,
- SyntaxNode, SyntaxToken, T,
-};
+use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxNode, SyntaxToken, T};
use crate::{
- display::{macro_label, TryToNav},
- doc_links::{
- doc_attributes, extract_definitions_from_docs, remove_links, resolve_doc_path_for_def,
- rewrite_links,
- },
- markdown_remove::remove_markdown,
+ display::TryToNav,
+ doc_links::token_as_doc_comment,
markup::Markup,
runnables::{runnable_fn, runnable_mod},
FileId, FilePosition, NavigationTarget, RangeInfo, Runnable,
.into_iter()
.filter_map(|it| {
Some(HoverGotoTypeData {
- mod_path: render_path(
+ mod_path: render::path(
db,
it.module(db)?,
it.name(db).map(|name| name.to_string()),
FileRange { file_id, range }: FileRange,
config: &HoverConfig,
) -> Option<RangeInfo<HoverResult>> {
- let sema = hir::Semantics::new(db);
+ let sema = &hir::Semantics::new(db);
let file = sema.parse(file_id).syntax().clone();
if !range.is_empty() {
}
let offset = range.start();
- let token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
+ let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] => 3,
T!['('] | T![')'] => 2,
kind if kind.is_trivia() => 0,
_ => 1,
})?;
- let token = sema.descend_into_macros(token);
- let node = token.parent()?;
- let mut range_override = None;
- let definition = match_ast! {
- match node {
- ast::Name(name) => NameClass::classify(&sema, &name).map(|class| match class {
- NameClass::Definition(it) | NameClass::ConstReference(it) => it,
- NameClass::PatFieldShorthand { local_def, field_ref: _ } => Definition::Local(local_def),
- }),
- ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|class| match class {
- NameRefClass::Definition(def) => def,
- NameRefClass::FieldShorthand { local_ref: _, field_ref } => {
- Definition::Field(field_ref)
- }
- }),
- ast::Lifetime(lifetime) => NameClass::classify_lifetime(&sema, &lifetime).map_or_else(
- || {
- NameRefClass::classify_lifetime(&sema, &lifetime).and_then(|class| match class {
- NameRefClass::Definition(it) => Some(it),
- _ => None,
- })
- },
- NameClass::defined,
- ),
- _ => {
- // intra-doc links
- if token.kind() == COMMENT {
- cov_mark::hit!(no_highlight_on_comment_hover);
- let (attributes, def) = doc_attributes(&sema, &node)?;
- let (docs, doc_mapping) = attributes.docs_with_rangemap(db)?;
- let (idl_range, link, ns) =
- extract_definitions_from_docs(&docs).into_iter().find_map(|(range, link, ns)| {
- let mapped = doc_mapping.map(range)?;
- (mapped.file_id == file_id.into() && mapped.value.contains(offset)).then(||(mapped.value, link, ns))
- })?;
- range_override = Some(idl_range);
- Some(match resolve_doc_path_for_def(db,def, &link,ns)? {
- Either::Left(it) => Definition::ModuleDef(it),
- Either::Right(it) => Definition::Macro(it),
- })
- // attributes, require special machinery as they are mere ident tokens
- } else if let Some(attr) = token.ancestors().find_map(ast::Attr::cast) {
- // lints
- if let res@Some(_) = try_hover_for_lint(&attr, &token) {
- return res;
- // derives
- } else {
- range_override = Some(token.text_range());
- try_resolve_derive_input_at(&sema, &attr, &token).map(Definition::Macro)
- }
- } else {
- None
- }
- },
- }
- };
-
- if let Some(definition) = definition {
- let famous_defs = match &definition {
- Definition::ModuleDef(hir::ModuleDef::BuiltinType(_)) => {
- Some(FamousDefs(&sema, sema.scope(&node).krate()))
- }
- _ => None,
- };
- if let Some(markup) = hover_for_definition(db, definition, famous_defs.as_ref(), config) {
- let mut res = HoverResult::default();
- res.markup = process_markup(sema.db, definition, &markup, config);
- if let Some(action) = show_implementations_action(db, definition) {
- res.actions.push(action);
- }
-
- if let Some(action) = show_fn_references_action(db, definition) {
- res.actions.push(action);
- }
- if let Some(action) = runnable_action(&sema, definition, file_id) {
- res.actions.push(action);
- }
+ if let Some(doc_comment) = token_as_doc_comment(&original_token) {
+ cov_mark::hit!(no_highlight_on_comment_hover);
+ return doc_comment.get_definition_with_descend_at(sema, offset, |def, node, range| {
+ let res = hover_for_definition(sema, file_id, def, &node, config)?;
+ Some(RangeInfo::new(range, res))
+ });
+ }
- if let Some(action) = goto_type_action_for_def(db, definition) {
- res.actions.push(action);
- }
+ let descended = sema.descend_into_macros_many(original_token.clone());
- let range = range_override.unwrap_or_else(|| sema.original_range(&node).range);
- return Some(RangeInfo::new(range, res));
- }
+ // FIXME: Definition should include known lints and the like instead of having this special case here
+ if let Some(res) = descended.iter().find_map(|token| {
+ let attr = token.ancestors().find_map(ast::Attr::cast)?;
+ render::try_for_lint(&attr, &token)
+ }) {
+ return Some(RangeInfo::new(original_token.text_range(), res));
}
- if let res @ Some(_) = hover_for_keyword(&sema, config, &token) {
- return res;
+ let result = descended
+ .iter()
+ .filter_map(|token| {
+ let node = token.parent()?;
+ let defs = Definition::from_token(sema, token);
+ Some(defs.into_iter().zip(iter::once(node).cycle()))
+ })
+ .flatten()
+ .unique_by(|&(def, _)| def)
+ .filter_map(|(def, node)| hover_for_definition(sema, file_id, def, &node, config))
+ .reduce(|mut acc, HoverResult { markup, actions }| {
+ acc.actions.extend(actions);
+ acc.markup = Markup::from(format!("{}\n---\n{}", acc.markup, markup));
+ acc
+ });
+ if result.is_none() {
+ // fallbacks, show keywords or types
+ if let Some(res) = render::keyword(sema, config, &original_token) {
+ return Some(RangeInfo::new(original_token.text_range(), res));
+ }
+ if let res @ Some(_) =
+ descended.iter().find_map(|token| hover_type_fallback(sema, config, token))
+ {
+ return res;
+ }
}
+ result.map(|res| RangeInfo::new(original_token.text_range(), res))
+}
- // No definition below cursor, fall back to showing type hovers.
+pub(crate) fn hover_for_definition(
+ sema: &Semantics<RootDatabase>,
+ file_id: FileId,
+ definition: Definition,
+ node: &SyntaxNode,
+ config: &HoverConfig,
+) -> Option<HoverResult> {
+ let famous_defs = match &definition {
+ Definition::ModuleDef(hir::ModuleDef::BuiltinType(_)) => {
+ Some(FamousDefs(&sema, sema.scope(&node).krate()))
+ }
+ _ => None,
+ };
+ if let Some(markup) = render::definition(sema.db, definition, famous_defs.as_ref(), config) {
+ let mut res = HoverResult::default();
+ res.markup = render::process_markup(sema.db, definition, &markup, config);
+ if let Some(action) = show_implementations_action(sema.db, definition) {
+ res.actions.push(action);
+ }
- let node = token
- .ancestors()
- .take_while(|it| !ast::Item::can_cast(it.kind()))
- .find(|n| ast::Expr::can_cast(n.kind()) || ast::Pat::can_cast(n.kind()))?;
+ if let Some(action) = show_fn_references_action(sema.db, definition) {
+ res.actions.push(action);
+ }
- let expr_or_pat = match_ast! {
- match node {
- ast::Expr(it) => Either::Left(it),
- ast::Pat(it) => Either::Right(it),
- // If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve.
- // (e.g expanding a builtin macro). So we give up here.
- ast::MacroCall(_it) => return None,
- _ => return None,
+ if let Some(action) = runnable_action(&sema, definition, file_id) {
+ res.actions.push(action);
}
- };
- let res = hover_type_info(&sema, config, &expr_or_pat)?;
- let range = sema.original_range(&node).range;
- Some(RangeInfo::new(range, res))
+ if let Some(action) = goto_type_action_for_def(sema.db, definition) {
+ res.actions.push(action);
+ }
+ return Some(res);
+ }
+ None
}
fn hover_ranged(
}
})?;
let res = match &expr_or_pat {
- Either::Left(ast::Expr::TryExpr(try_expr)) => hover_try_expr(sema, config, try_expr),
+ Either::Left(ast::Expr::TryExpr(try_expr)) => render::try_expr(sema, config, try_expr),
+ Either::Left(ast::Expr::PrefixExpr(prefix_expr))
+ if prefix_expr.op_kind() == Some(ast::UnaryOp::Deref) =>
+ {
+ render::deref_expr(sema, config, prefix_expr)
+ }
_ => None,
};
- let res = res.or_else(|| hover_type_info(sema, config, &expr_or_pat));
+ let res = res.or_else(|| render::type_info(sema, config, &expr_or_pat));
res.map(|it| {
let range = match expr_or_pat {
Either::Left(it) => it.syntax().text_range(),
})
}
-fn hover_try_expr(
- sema: &Semantics<RootDatabase>,
- config: &HoverConfig,
- try_expr: &ast::TryExpr,
-) -> Option<HoverResult> {
- let inner_ty = sema.type_of_expr(&try_expr.expr()?)?.original;
- let mut ancestors = try_expr.syntax().ancestors();
- let mut body_ty = loop {
- let next = ancestors.next()?;
- break match_ast! {
- match next {
- ast::Fn(fn_) => sema.to_def(&fn_)?.ret_type(sema.db),
- ast::Item(__) => return None,
- ast::ClosureExpr(closure) => sema.type_of_expr(&closure.body()?)?.original,
- ast::EffectExpr(effect) => if matches!(effect.effect(), ast::Effect::Async(_) | ast::Effect::Try(_)| ast::Effect::Const(_)) {
- sema.type_of_expr(&effect.block_expr()?.into())?.original
- } else {
- continue;
- },
- _ => continue,
- }
- };
- };
-
- if inner_ty == body_ty {
- return None;
- }
-
- let mut inner_ty = inner_ty;
- let mut s = "Try Target".to_owned();
-
- let adts = inner_ty.as_adt().zip(body_ty.as_adt());
- if let Some((hir::Adt::Enum(inner), hir::Adt::Enum(body))) = adts {
- let famous_defs = FamousDefs(sema, sema.scope(&try_expr.syntax()).krate());
- // special case for two options, there is no value in showing them
- if let Some(option_enum) = famous_defs.core_option_Option() {
- if inner == option_enum && body == option_enum {
- cov_mark::hit!(hover_try_expr_opt_opt);
- return None;
- }
- }
-
- // special case two results to show the error variants only
- if let Some(result_enum) = famous_defs.core_result_Result() {
- if inner == result_enum && body == result_enum {
- let error_type_args =
- inner_ty.type_arguments().nth(1).zip(body_ty.type_arguments().nth(1));
- if let Some((inner, body)) = error_type_args {
- inner_ty = inner;
- body_ty = body;
- s = "Try Error".to_owned();
- }
- }
- }
- }
-
- let mut res = HoverResult::default();
-
- let mut targets: Vec<hir::ModuleDef> = Vec::new();
- let mut push_new_def = |item: hir::ModuleDef| {
- if !targets.contains(&item) {
- targets.push(item);
- }
- };
- walk_and_push_ty(sema.db, &inner_ty, &mut push_new_def);
- walk_and_push_ty(sema.db, &body_ty, &mut push_new_def);
- res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
-
- let inner_ty = inner_ty.display(sema.db).to_string();
- let body_ty = body_ty.display(sema.db).to_string();
- let ty_len_max = inner_ty.len().max(body_ty.len());
-
- let l = "Propagated as: ".len() - " Type: ".len();
- let static_text_len_diff = l as isize - s.len() as isize;
- let tpad = static_text_len_diff.max(0) as usize;
- let ppad = static_text_len_diff.min(0).abs() as usize;
-
- res.markup = format!(
- "{bt_start}{} Type: {:>pad0$}\nPropagated as: {:>pad1$}\n{bt_end}",
- s,
- inner_ty,
- body_ty,
- pad0 = ty_len_max + tpad,
- pad1 = ty_len_max + ppad,
- bt_start = if config.markdown() { "```text\n" } else { "" },
- bt_end = if config.markdown() { "```\n" } else { "" }
- )
- .into();
- Some(res)
-}
-
-fn hover_type_info(
+fn hover_type_fallback(
sema: &Semantics<RootDatabase>,
config: &HoverConfig,
- expr_or_pat: &Either<ast::Expr, ast::Pat>,
-) -> Option<HoverResult> {
- let TypeInfo { original, adjusted } = match expr_or_pat {
- Either::Left(expr) => sema.type_of_expr(expr)?,
- Either::Right(pat) => sema.type_of_pat(pat)?,
- };
-
- let mut res = HoverResult::default();
- let mut targets: Vec<hir::ModuleDef> = Vec::new();
- let mut push_new_def = |item: hir::ModuleDef| {
- if !targets.contains(&item) {
- targets.push(item);
- }
- };
- walk_and_push_ty(sema.db, &original, &mut push_new_def);
-
- res.markup = if let Some(adjusted_ty) = adjusted {
- walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def);
- let original = original.display(sema.db).to_string();
- let adjusted = adjusted_ty.display(sema.db).to_string();
- let static_text_diff_len = "Coerced to: ".len() - "Type: ".len();
- format!(
- "{bt_start}Type: {:>apad$}\nCoerced to: {:>opad$}\n{bt_end}",
- original,
- adjusted,
- apad = static_text_diff_len + adjusted.len().max(original.len()),
- opad = original.len(),
- bt_start = if config.markdown() { "```text\n" } else { "" },
- bt_end = if config.markdown() { "```\n" } else { "" }
- )
- .into()
- } else {
- if config.markdown() {
- Markup::fenced_block(&original.display(sema.db))
- } else {
- original.display(sema.db).to_string().into()
- }
- };
- res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
- Some(res)
-}
+ token: &SyntaxToken,
+) -> Option<RangeInfo<HoverResult>> {
+ let node = token
+ .ancestors()
+ .take_while(|it| !ast::Item::can_cast(it.kind()))
+ .find(|n| ast::Expr::can_cast(n.kind()) || ast::Pat::can_cast(n.kind()))?;
-fn try_hover_for_lint(attr: &ast::Attr, token: &SyntaxToken) -> Option<RangeInfo<HoverResult>> {
- let (path, tt) = attr.as_simple_call()?;
- if !tt.syntax().text_range().contains(token.text_range().start()) {
- return None;
- }
- let (is_clippy, lints) = match &*path {
- "feature" => (false, FEATURES),
- "allow" | "deny" | "forbid" | "warn" => {
- let is_clippy = algo::non_trivia_sibling(token.clone().into(), Direction::Prev)
- .filter(|t| t.kind() == T![:])
- .and_then(|t| algo::non_trivia_sibling(t, Direction::Prev))
- .filter(|t| t.kind() == T![:])
- .and_then(|t| algo::non_trivia_sibling(t, Direction::Prev))
- .map_or(false, |t| {
- t.kind() == T![ident] && t.into_token().map_or(false, |t| t.text() == "clippy")
- });
- if is_clippy {
- (true, CLIPPY_LINTS)
- } else {
- (false, DEFAULT_LINTS)
- }
+ let expr_or_pat = match_ast! {
+ match node {
+ ast::Expr(it) => Either::Left(it),
+ ast::Pat(it) => Either::Right(it),
+ // If this node is a MACRO_CALL, it means that `descend_into_macros_many` failed to resolve.
+ // (e.g expanding a builtin macro). So we give up here.
+ ast::MacroCall(_it) => return None,
+ _ => return None,
}
- _ => return None,
- };
-
- let tmp;
- let needle = if is_clippy {
- tmp = format!("clippy::{}", token.text());
- &tmp
- } else {
- &*token.text()
};
- let lint =
- lints.binary_search_by_key(&needle, |lint| lint.label).ok().map(|idx| &lints[idx])?;
- Some(RangeInfo::new(
- token.text_range(),
- HoverResult {
- markup: Markup::from(format!("```\n{}\n```\n___\n\n{}", lint.label, lint.description)),
- ..Default::default()
- },
- ))
+ let res = render::type_info(&sema, config, &expr_or_pat)?;
+ let range = sema.original_range(&node).range;
+ Some(RangeInfo::new(range, res))
}
fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
}
});
}
-
-fn hover_markup(docs: Option<String>, desc: String, mod_path: Option<String>) -> Option<Markup> {
- let mut buf = String::new();
-
- if let Some(mod_path) = mod_path {
- if !mod_path.is_empty() {
- format_to!(buf, "```rust\n{}\n```\n\n", mod_path);
- }
- }
- format_to!(buf, "```rust\n{}\n```", desc);
-
- if let Some(doc) = docs {
- format_to!(buf, "\n___\n\n{}", doc);
- }
- Some(buf.into())
-}
-
-fn process_markup(
- db: &RootDatabase,
- def: Definition,
- markup: &Markup,
- config: &HoverConfig,
-) -> Markup {
- let markup = markup.as_str();
- let markup = if !config.markdown() {
- remove_markdown(markup)
- } else if config.links_in_hover {
- rewrite_links(db, markup, def)
- } else {
- remove_links(markup)
- };
- Markup::from(markup)
-}
-
-fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String> {
- match def {
- Definition::Field(f) => Some(f.parent_def(db).name(db)),
- Definition::Local(l) => l.parent(db).name(db),
- Definition::ModuleDef(md) => match md {
- hir::ModuleDef::Function(f) => match f.as_assoc_item(db)?.container(db) {
- hir::AssocItemContainer::Trait(t) => Some(t.name(db)),
- hir::AssocItemContainer::Impl(i) => i.self_ty(db).as_adt().map(|adt| adt.name(db)),
- },
- hir::ModuleDef::Variant(e) => Some(e.parent_enum(db).name(db)),
- _ => None,
- },
- _ => None,
- }
- .map(|name| name.to_string())
-}
-
-fn render_path(db: &RootDatabase, module: hir::Module, item_name: Option<String>) -> String {
- let crate_name =
- db.crate_graph()[module.krate().into()].display_name.as_ref().map(|it| it.to_string());
- let module_path = module
- .path_to_root(db)
- .into_iter()
- .rev()
- .flat_map(|it| it.name(db).map(|name| name.to_string()));
- crate_name.into_iter().chain(module_path).chain(item_name).join("::")
-}
-
-fn definition_mod_path(db: &RootDatabase, def: &Definition) -> Option<String> {
- if let Definition::GenericParam(_) = def {
- return None;
- }
- def.module(db).map(|module| render_path(db, module, definition_owner_name(db, def)))
-}
-
-fn hover_for_definition(
- db: &RootDatabase,
- def: Definition,
- famous_defs: Option<&FamousDefs>,
- config: &HoverConfig,
-) -> Option<Markup> {
- let mod_path = definition_mod_path(db, &def);
- let (label, docs) = match def {
- Definition::Macro(it) => (
- match &it.source(db)?.value {
- Either::Left(mac) => macro_label(mac),
- Either::Right(mac_fn) => fn_as_proc_macro_label(mac_fn),
- },
- it.attrs(db).docs(),
- ),
- Definition::Field(def) => label_and_docs(db, def),
- Definition::ModuleDef(it) => match it {
- hir::ModuleDef::Module(it) => label_and_docs(db, it),
- hir::ModuleDef::Function(it) => label_and_docs(db, it),
- hir::ModuleDef::Adt(it) => label_and_docs(db, it),
- hir::ModuleDef::Variant(it) => label_and_docs(db, it),
- hir::ModuleDef::Const(it) => label_and_docs(db, it),
- hir::ModuleDef::Static(it) => label_and_docs(db, it),
- hir::ModuleDef::Trait(it) => label_and_docs(db, it),
- hir::ModuleDef::TypeAlias(it) => label_and_docs(db, it),
- hir::ModuleDef::BuiltinType(it) => {
- return famous_defs
- .and_then(|fd| hover_for_builtin(fd, it))
- .or_else(|| Some(Markup::fenced_block(&it.name())))
- }
- },
- Definition::Local(it) => return hover_for_local(it, db),
- Definition::SelfType(impl_def) => {
- impl_def.self_ty(db).as_adt().map(|adt| label_and_docs(db, adt))?
- }
- Definition::GenericParam(it) => label_and_docs(db, it),
- Definition::Label(it) => return Some(Markup::fenced_block(&it.name(db))),
- };
-
- return hover_markup(
- docs.filter(|_| config.documentation.is_some()).map(Into::into),
- label,
- mod_path,
- );
-
- fn label_and_docs<D>(db: &RootDatabase, def: D) -> (String, Option<hir::Documentation>)
- where
- D: HasAttrs + HirDisplay,
- {
- let label = def.display(db).to_string();
- let docs = def.attrs(db).docs();
- (label, docs)
- }
-}
-
-fn hover_for_local(it: hir::Local, db: &RootDatabase) -> Option<Markup> {
- let ty = it.ty(db);
- let ty = ty.display(db);
- let is_mut = if it.is_mut(db) { "mut " } else { "" };
- let desc = match it.source(db).value {
- Either::Left(ident) => {
- let name = it.name(db).unwrap();
- let let_kw = if ident
- .syntax()
- .parent()
- .map_or(false, |p| p.kind() == LET_STMT || p.kind() == CONDITION)
- {
- "let "
- } else {
- ""
- };
- format!("{}{}{}: {}", let_kw, is_mut, name, ty)
- }
- Either::Right(_) => format!("{}self: {}", is_mut, ty),
- };
- hover_markup(None, desc, None)
-}
-
-fn hover_for_keyword(
- sema: &Semantics<RootDatabase>,
- config: &HoverConfig,
- token: &SyntaxToken,
-) -> Option<RangeInfo<HoverResult>> {
- if !token.kind().is_keyword() || !config.documentation.is_some() {
- return None;
- }
- let famous_defs = FamousDefs(sema, sema.scope(&token.parent()?).krate());
- // std exposes {}_keyword modules with docstrings on the root to document keywords
- let keyword_mod = format!("{}_keyword", token.text());
- let doc_owner = find_std_module(&famous_defs, &keyword_mod)?;
- let docs = doc_owner.attrs(sema.db).docs()?;
- let markup = process_markup(
- sema.db,
- Definition::ModuleDef(doc_owner.into()),
- &hover_markup(Some(docs.into()), token.text().into(), None)?,
- config,
- );
- Some(RangeInfo::new(token.text_range(), HoverResult { markup, actions: Default::default() }))
-}
-
-fn hover_for_builtin(famous_defs: &FamousDefs, builtin: hir::BuiltinType) -> Option<Markup> {
- // std exposes prim_{} modules with docstrings on the root to document the builtins
- let primitive_mod = format!("prim_{}", builtin.name());
- let doc_owner = find_std_module(famous_defs, &primitive_mod)?;
- let docs = doc_owner.attrs(famous_defs.0.db).docs()?;
- hover_markup(Some(docs.into()), builtin.name().to_string(), None)
-}
-
-fn find_std_module(famous_defs: &FamousDefs, name: &str) -> Option<hir::Module> {
- let db = famous_defs.0.db;
- let std_crate = famous_defs.std()?;
- let std_root_module = std_crate.root_module(db);
- std_root_module
- .children(db)
- .find(|module| module.name(db).map_or(false, |module| module.to_string() == name))
-}
-
-#[cfg(test)]
-mod tests {
- use expect_test::{expect, Expect};
- use ide_db::base_db::{FileLoader, FileRange};
- use syntax::TextRange;
-
- use crate::{fixture, hover::HoverDocFormat, HoverConfig};
-
- fn check_hover_no_result(ra_fixture: &str) {
- let (analysis, position) = fixture::position(ra_fixture);
- let hover = analysis
- .hover(
- &HoverConfig {
- links_in_hover: true,
- documentation: Some(HoverDocFormat::Markdown),
- },
- FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
- )
- .unwrap();
- assert!(hover.is_none());
- }
-
- fn check(ra_fixture: &str, expect: Expect) {
- let (analysis, position) = fixture::position(ra_fixture);
- let hover = analysis
- .hover(
- &HoverConfig {
- links_in_hover: true,
- documentation: Some(HoverDocFormat::Markdown),
- },
- FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
- )
- .unwrap()
- .unwrap();
-
- let content = analysis.db.file_text(position.file_id);
- let hovered_element = &content[hover.range];
-
- let actual = format!("*{}*\n{}\n", hovered_element, hover.info.markup);
- expect.assert_eq(&actual)
- }
-
- fn check_hover_no_links(ra_fixture: &str, expect: Expect) {
- let (analysis, position) = fixture::position(ra_fixture);
- let hover = analysis
- .hover(
- &HoverConfig {
- links_in_hover: false,
- documentation: Some(HoverDocFormat::Markdown),
- },
- FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
- )
- .unwrap()
- .unwrap();
-
- let content = analysis.db.file_text(position.file_id);
- let hovered_element = &content[hover.range];
-
- let actual = format!("*{}*\n{}\n", hovered_element, hover.info.markup);
- expect.assert_eq(&actual)
- }
-
- fn check_hover_no_markdown(ra_fixture: &str, expect: Expect) {
- let (analysis, position) = fixture::position(ra_fixture);
- let hover = analysis
- .hover(
- &HoverConfig {
- links_in_hover: true,
- documentation: Some(HoverDocFormat::PlainText),
- },
- FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
- )
- .unwrap()
- .unwrap();
-
- let content = analysis.db.file_text(position.file_id);
- let hovered_element = &content[hover.range];
-
- let actual = format!("*{}*\n{}\n", hovered_element, hover.info.markup);
- expect.assert_eq(&actual)
- }
-
- fn check_actions(ra_fixture: &str, expect: Expect) {
- let (analysis, file_id, position) = fixture::range_or_position(ra_fixture);
- let hover = analysis
- .hover(
- &HoverConfig {
- links_in_hover: true,
- documentation: Some(HoverDocFormat::Markdown),
- },
- FileRange { file_id, range: position.range_or_empty() },
- )
- .unwrap()
- .unwrap();
- expect.assert_debug_eq(&hover.info.actions)
- }
-
- fn check_hover_range(ra_fixture: &str, expect: Expect) {
- let (analysis, range) = fixture::range(ra_fixture);
- let hover = analysis
- .hover(
- &HoverConfig {
- links_in_hover: false,
- documentation: Some(HoverDocFormat::Markdown),
- },
- range,
- )
- .unwrap()
- .unwrap();
- expect.assert_eq(hover.info.markup.as_str())
- }
-
- fn check_hover_range_no_results(ra_fixture: &str) {
- let (analysis, range) = fixture::range(ra_fixture);
- let hover = analysis
- .hover(
- &HoverConfig {
- links_in_hover: false,
- documentation: Some(HoverDocFormat::Markdown),
- },
- range,
- )
- .unwrap();
- assert!(hover.is_none());
- }
-
- #[test]
- fn hover_shows_type_of_an_expression() {
- check(
- r#"
-pub fn foo() -> u32 { 1 }
-
-fn main() {
- let foo_test = foo()$0;
-}
-"#,
- expect![[r#"
- *foo()*
- ```rust
- u32
- ```
- "#]],
- );
- }
-
- #[test]
- fn hover_remove_markdown_if_configured() {
- check_hover_no_markdown(
- r#"
-pub fn foo() -> u32 { 1 }
-
-fn main() {
- let foo_test = foo()$0;
-}
-"#,
- expect![[r#"
- *foo()*
- u32
- "#]],
- );
- }
-
- #[test]
- fn hover_shows_long_type_of_an_expression() {
- check(
- r#"
-struct Scan<A, B, C> { a: A, b: B, c: C }
-struct Iter<I> { inner: I }
-enum Option<T> { Some(T), None }
-
-struct OtherStruct<T> { i: T }
-
-fn scan<A, B, C>(a: A, b: B, c: C) -> Iter<Scan<OtherStruct<A>, B, C>> {
- Iter { inner: Scan { a, b, c } }
-}
-
-fn main() {
- let num: i32 = 55;
- let closure = |memo: &mut u32, value: &u32, _another: &mut u32| -> Option<u32> {
- Option::Some(*memo + value)
- };
- let number = 5u32;
- let mut iter$0 = scan(OtherStruct { i: num }, closure, number);
-}
-"#,
- expect![[r#"
- *iter*
-
- ```rust
- let mut iter: Iter<Scan<OtherStruct<OtherStruct<i32>>, |&mut u32, &u32, &mut u32| -> Option<u32>, u32>>
- ```
- "#]],
- );
- }
-
- #[test]
- fn hover_shows_fn_signature() {
- // Single file with result
- check(
- r#"
-pub fn foo() -> u32 { 1 }
-
-fn main() { let foo_test = fo$0o(); }
-"#,
- expect![[r#"
- *foo*
-
- ```rust
- test
- ```
-
- ```rust
- pub fn foo() -> u32
- ```
- "#]],
- );
-
- // Multiple candidates but results are ambiguous.
- check(
- r#"
-//- /a.rs
-pub fn foo() -> u32 { 1 }
-
-//- /b.rs
-pub fn foo() -> &str { "" }
-
-//- /c.rs
-pub fn foo(a: u32, b: u32) {}
-
-//- /main.rs
-mod a;
-mod b;
-mod c;
-
-fn main() { let foo_test = fo$0o(); }
- "#,
- expect![[r#"
- *foo*
- ```rust
- {unknown}
- ```
- "#]],
- );
-
- // Use literal `crate` in path
- check(
- r#"
-pub struct X;
-
-fn foo() -> crate::X { X }
-
-fn main() { f$0oo(); }
- "#,
- expect![[r#"
- *foo*
-
- ```rust
- test
- ```
-
- ```rust
- fn foo() -> crate::X
- ```
- "#]],
- );
-
- // Check `super` in path
- check(
- r#"
-pub struct X;
-
-mod m { pub fn foo() -> super::X { super::X } }
-
-fn main() { m::f$0oo(); }
- "#,
- expect![[r#"
- *foo*
-
- ```rust
- test::m
- ```
-
- ```rust
- pub fn foo() -> super::X
- ```
- "#]],
- );
- }
-
- #[test]
- fn hover_shows_fn_signature_with_type_params() {
- check(
- r#"
-pub fn foo<'a, T: AsRef<str>>(b: &'a T) -> &'a str { }
-
-fn main() { let foo_test = fo$0o(); }
- "#,
- expect![[r#"
- *foo*
-
- ```rust
- test
- ```
-
- ```rust
- pub fn foo<'a, T>(b: &'a T) -> &'a str
- where
- T: AsRef<str>,
- ```
- "#]],
- );
- }
-
- #[test]
- fn hover_shows_fn_signature_on_fn_name() {
- check(
- r#"
-pub fn foo$0(a: u32, b: u32) -> u32 {}
-
-fn main() { }
-"#,
- expect![[r#"
- *foo*
-
- ```rust
- test
- ```
-
- ```rust
- pub fn foo(a: u32, b: u32) -> u32
- ```
- "#]],
- );
- }
-
- #[test]
- fn hover_shows_fn_doc() {
- check(
- r#"
-/// # Example
-/// ```
-/// # use std::path::Path;
-/// #
-/// foo(Path::new("hello, world!"))
-/// ```
-pub fn foo$0(_: &Path) {}
-
-fn main() { }
-"#,
- expect![[r##"
- *foo*
-
- ```rust
- test
- ```
-
- ```rust
- pub fn foo(_: &Path)
- ```
-
- ---
-
- # Example
-
- ```
- # use std::path::Path;
- #
- foo(Path::new("hello, world!"))
- ```
- "##]],
- );
- }
-
- #[test]
- fn hover_shows_fn_doc_attr_raw_string() {
- check(
- r##"
-#[doc = r#"Raw string doc attr"#]
-pub fn foo$0(_: &Path) {}
-
-fn main() { }
-"##,
- expect![[r##"
- *foo*
-
- ```rust
- test
- ```
-
- ```rust
- pub fn foo(_: &Path)
- ```
-
- ---
-
- Raw string doc attr
- "##]],
- );
- }
-
- #[test]
- fn hover_shows_struct_field_info() {
- // Hovering over the field when instantiating
- check(
- r#"
-struct Foo { field_a: u32 }
-
-fn main() {
- let foo = Foo { field_a$0: 0, };
-}
-"#,
- expect![[r#"
- *field_a*
-
- ```rust
- test::Foo
- ```
-
- ```rust
- field_a: u32
- ```
- "#]],
- );
-
- // Hovering over the field in the definition
- check(
- r#"
-struct Foo { field_a$0: u32 }
-
-fn main() {
- let foo = Foo { field_a: 0 };
-}
-"#,
- expect![[r#"
- *field_a*
-
- ```rust
- test::Foo
- ```
-
- ```rust
- field_a: u32
- ```
- "#]],
- );
- }
-
- #[test]
- fn hover_const_static() {
- check(
- r#"const foo$0: u32 = 123;"#,
- expect![[r#"
- *foo*
-
- ```rust
- test
- ```
-
- ```rust
- const foo: u32
- ```
- "#]],
- );
- check(
- r#"static foo$0: u32 = 456;"#,
- expect![[r#"
- *foo*
-
- ```rust
- test
- ```
-
- ```rust
- static foo: u32
- ```
- "#]],
- );
- }
-
- #[test]
- fn hover_default_generic_types() {
- check(
- r#"
-struct Test<K, T = u8> { k: K, t: T }
-
-fn main() {
- let zz$0 = Test { t: 23u8, k: 33 };
-}"#,
- expect![[r#"
- *zz*
-
- ```rust
- let zz: Test<i32, u8>
- ```
- "#]],
- );
- }
-
- #[test]
- fn hover_some() {
- check(
- r#"
-enum Option<T> { Some(T) }
-use Option::Some;
-
-fn main() { So$0me(12); }
-"#,
- expect![[r#"
- *Some*
-
- ```rust
- test::Option
- ```
-
- ```rust
- Some(T)
- ```
- "#]],
- );
-
- check(
- r#"
-enum Option<T> { Some(T) }
-use Option::Some;
-
-fn main() { let b$0ar = Some(12); }
-"#,
- expect![[r#"
- *bar*
-
- ```rust
- let bar: Option<i32>
- ```
- "#]],
- );
- }
-
- #[test]
- fn hover_enum_variant() {
- check(
- r#"
-enum Option<T> {
- /// The None variant
- Non$0e
-}
-"#,
- expect![[r#"
- *None*
-
- ```rust
- test::Option
- ```
-
- ```rust
- None
- ```
-
- ---
-
- The None variant
- "#]],
- );
-
- check(
- r#"
-enum Option<T> {
- /// The Some variant
- Some(T)
-}
-fn main() {
- let s = Option::Som$0e(12);
-}
-"#,
- expect![[r#"
- *Some*
-
- ```rust
- test::Option
- ```
-
- ```rust
- Some(T)
- ```
-
- ---
-
- The Some variant
- "#]],
- );
- }
-
- #[test]
- fn hover_for_local_variable() {
- check(
- r#"fn func(foo: i32) { fo$0o; }"#,
- expect![[r#"
- *foo*
-
- ```rust
- foo: i32
- ```
- "#]],
- )
- }
-
- #[test]
- fn hover_for_local_variable_pat() {
- check(
- r#"fn func(fo$0o: i32) {}"#,
- expect![[r#"
- *foo*
-
- ```rust
- foo: i32
- ```
- "#]],
- )
- }
-
- #[test]
- fn hover_local_var_edge() {
- check(
- r#"fn func(foo: i32) { if true { $0foo; }; }"#,
- expect![[r#"
- *foo*
-
- ```rust
- foo: i32
- ```
- "#]],
- )
- }
-
- #[test]
- fn hover_for_param_edge() {
- check(
- r#"fn func($0foo: i32) {}"#,
- expect![[r#"
- *foo*
-
- ```rust
- foo: i32
- ```
- "#]],
- )
- }
-
- #[test]
- fn hover_for_param_with_multiple_traits() {
- check(
- r#"
- //- minicore: sized
- trait Deref {
- type Target: ?Sized;
- }
- trait DerefMut {
- type Target: ?Sized;
- }
- fn f(_x$0: impl Deref<Target=u8> + DerefMut<Target=u8>) {}"#,
- expect![[r#"
- *_x*
-
- ```rust
- _x: impl Deref<Target = u8> + DerefMut<Target = u8>
- ```
- "#]],
- )
- }
-
- #[test]
- fn test_hover_infer_associated_method_result() {
- check(
- r#"
-struct Thing { x: u32 }
-
-impl Thing {
- fn new() -> Thing { Thing { x: 0 } }
-}
-
-fn main() { let foo_$0test = Thing::new(); }
-"#,
- expect![[r#"
- *foo_test*
-
- ```rust
- let foo_test: Thing
- ```
- "#]],
- )
- }
-
- #[test]
- fn test_hover_infer_associated_method_exact() {
- check(
- r#"
-mod wrapper {
- struct Thing { x: u32 }
-
- impl Thing {
- fn new() -> Thing { Thing { x: 0 } }
- }
-}
-
-fn main() { let foo_test = wrapper::Thing::new$0(); }
-"#,
- expect![[r#"
- *new*
-
- ```rust
- test::wrapper::Thing
- ```
-
- ```rust
- fn new() -> Thing
- ```
- "#]],
- )
- }
-
- #[test]
- fn test_hover_infer_associated_const_in_pattern() {
- check(
- r#"
-struct X;
-impl X {
- const C: u32 = 1;
-}
-
-fn main() {
- match 1 {
- X::C$0 => {},
- 2 => {},
- _ => {}
- };
-}
-"#,
- expect![[r#"
- *C*
-
- ```rust
- test
- ```
-
- ```rust
- const C: u32
- ```
- "#]],
- )
- }
-
- #[test]
- fn test_hover_self() {
- check(
- r#"
-struct Thing { x: u32 }
-impl Thing {
- fn new() -> Self { Self$0 { x: 0 } }
-}
-"#,
- expect![[r#"
- *Self*
-
- ```rust
- test
- ```
-
- ```rust
- struct Thing
- ```
- "#]],
- );
- check(
- r#"
-struct Thing { x: u32 }
-impl Thing {
- fn new() -> Self$0 { Self { x: 0 } }
-}
-"#,
- expect![[r#"
- *Self*
-
- ```rust
- test
- ```
-
- ```rust
- struct Thing
- ```
- "#]],
- );
- check(
- r#"
-enum Thing { A }
-impl Thing {
- pub fn new() -> Self$0 { Thing::A }
-}
-"#,
- expect![[r#"
- *Self*
-
- ```rust
- test
- ```
-
- ```rust
- enum Thing
- ```
- "#]],
- );
- check(
- r#"
- enum Thing { A }
- impl Thing {
- pub fn thing(a: Self$0) {}
- }
- "#,
- expect![[r#"
- *Self*
-
- ```rust
- test
- ```
-
- ```rust
- enum Thing
- ```
- "#]],
- );
- }
-
- #[test]
- fn test_hover_shadowing_pat() {
- check(
- r#"
-fn x() {}
-
-fn y() {
- let x = 0i32;
- x$0;
-}
-"#,
- expect![[r#"
- *x*
-
- ```rust
- let x: i32
- ```
- "#]],
- )
- }
-
- #[test]
- fn test_hover_macro_invocation() {
- check(
- r#"
-macro_rules! foo { () => {} }
-
-fn f() { fo$0o!(); }
-"#,
- expect![[r#"
- *foo*
-
- ```rust
- test
- ```
-
- ```rust
- macro_rules! foo
- ```
- "#]],
- )
- }
-
- #[test]
- fn test_hover_macro2_invocation() {
- check(
- r#"
-/// foo bar
-///
-/// foo bar baz
-macro foo() {}
-
-fn f() { fo$0o!(); }
-"#,
- expect![[r#"
- *foo*
-
- ```rust
- test
- ```
-
- ```rust
- macro foo
- ```
-
- ---
-
- foo bar
-
- foo bar baz
- "#]],
- )
- }
-
- #[test]
- fn test_hover_tuple_field() {
- check(
- r#"struct TS(String, i32$0);"#,
- expect![[r#"
- *i32*
-
- ```rust
- i32
- ```
- "#]],
- )
- }
-
- #[test]
- fn test_hover_through_macro() {
- check(
- r#"
-macro_rules! id { ($($tt:tt)*) => { $($tt)* } }
-fn foo() {}
-id! {
- fn bar() { fo$0o(); }
-}
-"#,
- expect![[r#"
- *foo*
-
- ```rust
- test
- ```
-
- ```rust
- fn foo()
- ```
- "#]],
- );
- }
-
- #[test]
- fn test_hover_through_expr_in_macro() {
- check(
- r#"
-macro_rules! id { ($($tt:tt)*) => { $($tt)* } }
-fn foo(bar:u32) { let a = id!(ba$0r); }
-"#,
- expect![[r#"
- *bar*
-
- ```rust
- bar: u32
- ```
- "#]],
- );
- }
-
- #[test]
- fn test_hover_through_expr_in_macro_recursive() {
- check(
- r#"
-macro_rules! id_deep { ($($tt:tt)*) => { $($tt)* } }
-macro_rules! id { ($($tt:tt)*) => { id_deep!($($tt)*) } }
-fn foo(bar:u32) { let a = id!(ba$0r); }
-"#,
- expect![[r#"
- *bar*
-
- ```rust
- bar: u32
- ```
- "#]],
- );
- }
-
- #[test]
- fn test_hover_through_func_in_macro_recursive() {
- check(
- r#"
-macro_rules! id_deep { ($($tt:tt)*) => { $($tt)* } }
-macro_rules! id { ($($tt:tt)*) => { id_deep!($($tt)*) } }
-fn bar() -> u32 { 0 }
-fn foo() { let a = id!([0u32, bar($0)] ); }
-"#,
- expect![[r#"
- *bar()*
- ```rust
- u32
- ```
- "#]],
- );
- }
-
- #[test]
- fn test_hover_through_literal_string_in_macro() {
- check(
- r#"
-macro_rules! arr { ($($tt:tt)*) => { [$($tt)*)] } }
-fn foo() {
- let mastered_for_itunes = "";
- let _ = arr!("Tr$0acks", &mastered_for_itunes);
-}
-"#,
- expect![[r#"
- *"Tracks"*
- ```rust
- &str
- ```
- "#]],
- );
- }
-
- #[test]
- fn test_hover_through_assert_macro() {
- check(
- r#"
-#[rustc_builtin_macro]
-macro_rules! assert {}
-
-fn bar() -> bool { true }
-fn foo() {
- assert!(ba$0r());
-}
-"#,
- expect![[r#"
- *bar*
-
- ```rust
- test
- ```
-
- ```rust
- fn bar() -> bool
- ```
- "#]],
- );
- }
-
- #[test]
- fn test_hover_through_literal_string_in_builtin_macro() {
- check_hover_no_result(
- r#"
- #[rustc_builtin_macro]
- macro_rules! format {}
-
- fn foo() {
- format!("hel$0lo {}", 0);
- }
-"#,
- );
- }
-
- #[test]
- fn test_hover_non_ascii_space_doc() {
- check(
- "
-/// <- `\u{3000}` here
-fn foo() { }
-
-fn bar() { fo$0o(); }
-",
- expect![[r#"
- *foo*
-
- ```rust
- test
- ```
-
- ```rust
- fn foo()
- ```
-
- ---
-
- \<- ` ` here
- "#]],
- );
- }
-
- #[test]
- fn test_hover_function_show_qualifiers() {
- check(
- r#"async fn foo$0() {}"#,
- expect![[r#"
- *foo*
-
- ```rust
- test
- ```
-
- ```rust
- async fn foo()
- ```
- "#]],
- );
- check(
- r#"pub const unsafe fn foo$0() {}"#,
- expect![[r#"
- *foo*
-
- ```rust
- test
- ```
-
- ```rust
- pub const unsafe fn foo()
- ```
- "#]],
- );
- // Top level `pub(crate)` will be displayed as no visibility.
- check(
- r#"mod m { pub(crate) async unsafe extern "C" fn foo$0() {} }"#,
- expect![[r#"
- *foo*
-
- ```rust
- test::m
- ```
-
- ```rust
- pub(crate) async unsafe extern "C" fn foo()
- ```
- "#]],
- );
- }
-
- #[test]
- fn test_hover_trait_show_qualifiers() {
- check_actions(
- r"unsafe trait foo$0() {}",
- expect![[r#"
- [
- Implementation(
- FilePosition {
- file_id: FileId(
- 0,
- ),
- offset: 13,
- },
- ),
- ]
- "#]],
- );
- }
-
- #[test]
- fn test_hover_extern_crate() {
- check(
- r#"
-//- /main.rs crate:main deps:std
-extern crate st$0d;
-//- /std/lib.rs crate:std
-//! Standard library for this test
-//!
-//! Printed?
-//! abc123
-"#,
- expect![[r#"
- *std*
-
- ```rust
- extern crate std
- ```
-
- ---
-
- Standard library for this test
-
- Printed?
- abc123
- "#]],
- );
- check(
- r#"
-//- /main.rs crate:main deps:std
-extern crate std as ab$0c;
-//- /std/lib.rs crate:std
-//! Standard library for this test
-//!
-//! Printed?
-//! abc123
-"#,
- expect![[r#"
- *abc*
-
- ```rust
- extern crate std
- ```
-
- ---
-
- Standard library for this test
-
- Printed?
- abc123
- "#]],
- );
- }
-
- #[test]
- fn test_hover_mod_with_same_name_as_function() {
- check(
- r#"
-use self::m$0y::Bar;
-mod my { pub struct Bar; }
-
-fn my() {}
-"#,
- expect![[r#"
- *my*
-
- ```rust
- test
- ```
-
- ```rust
- mod my
- ```
- "#]],
- );
- }
-
- #[test]
- fn test_hover_struct_doc_comment() {
- check(
- r#"
-/// This is an example
-/// multiline doc
-///
-/// # Example
-///
-/// ```
-/// let five = 5;
-///
-/// assert_eq!(6, my_crate::add_one(5));
-/// ```
-struct Bar;
-
-fn foo() { let bar = Ba$0r; }
-"#,
- expect![[r##"
- *Bar*
-
- ```rust
- test
- ```
-
- ```rust
- struct Bar
- ```
-
- ---
-
- This is an example
- multiline doc
-
- # Example
-
- ```
- let five = 5;
-
- assert_eq!(6, my_crate::add_one(5));
- ```
- "##]],
- );
- }
-
- #[test]
- fn test_hover_struct_doc_attr() {
- check(
- r#"
-#[doc = "bar docs"]
-struct Bar;
-
-fn foo() { let bar = Ba$0r; }
-"#,
- expect![[r#"
- *Bar*
-
- ```rust
- test
- ```
-
- ```rust
- struct Bar
- ```
-
- ---
-
- bar docs
- "#]],
- );
- }
-
- #[test]
- fn test_hover_struct_doc_attr_multiple_and_mixed() {
- check(
- r#"
-/// bar docs 0
-#[doc = "bar docs 1"]
-#[doc = "bar docs 2"]
-struct Bar;
-
-fn foo() { let bar = Ba$0r; }
-"#,
- expect![[r#"
- *Bar*
-
- ```rust
- test
- ```
-
- ```rust
- struct Bar
- ```
-
- ---
-
- bar docs 0
- bar docs 1
- bar docs 2
- "#]],
- );
- }
-
- #[test]
- fn test_hover_external_url() {
- check(
- r#"
-pub struct Foo;
-/// [external](https://www.google.com)
-pub struct B$0ar
-"#,
- expect![[r#"
- *Bar*
-
- ```rust
- test
- ```
-
- ```rust
- pub struct Bar
- ```
-
- ---
-
- [external](https://www.google.com)
- "#]],
- );
- }
-
- // Check that we don't rewrite links which we can't identify
- #[test]
- fn test_hover_unknown_target() {
- check(
- r#"
-pub struct Foo;
-/// [baz](Baz)
-pub struct B$0ar
-"#,
- expect![[r#"
- *Bar*
-
- ```rust
- test
- ```
-
- ```rust
- pub struct Bar
- ```
-
- ---
-
- [baz](Baz)
- "#]],
- );
- }
-
- #[test]
- fn test_hover_no_links() {
- check_hover_no_links(
- r#"
-/// Test cases:
-/// case 1. bare URL: https://www.example.com/
-/// case 2. inline URL with title: [example](https://www.example.com/)
-/// case 3. code reference: [`Result`]
-/// case 4. code reference but miss footnote: [`String`]
-/// case 5. autolink: <http://www.example.com/>
-/// case 6. email address: <test@example.com>
-/// case 7. reference: [example][example]
-/// case 8. collapsed link: [example][]
-/// case 9. shortcut link: [example]
-/// case 10. inline without URL: [example]()
-/// case 11. reference: [foo][foo]
-/// case 12. reference: [foo][bar]
-/// case 13. collapsed link: [foo][]
-/// case 14. shortcut link: [foo]
-/// case 15. inline without URL: [foo]()
-/// case 16. just escaped text: \[foo]
-/// case 17. inline link: [Foo](foo::Foo)
-///
-/// [`Result`]: ../../std/result/enum.Result.html
-/// [^example]: https://www.example.com/
-pub fn fo$0o() {}
-"#,
- expect![[r#"
- *foo*
-
- ```rust
- test
- ```
-
- ```rust
- pub fn foo()
- ```
-
- ---
-
- Test cases:
- case 1. bare URL: https://www.example.com/
- case 2. inline URL with title: [example](https://www.example.com/)
- case 3. code reference: `Result`
- case 4. code reference but miss footnote: `String`
- case 5. autolink: http://www.example.com/
- case 6. email address: test@example.com
- case 7. reference: example
- case 8. collapsed link: example
- case 9. shortcut link: example
- case 10. inline without URL: example
- case 11. reference: foo
- case 12. reference: foo
- case 13. collapsed link: foo
- case 14. shortcut link: foo
- case 15. inline without URL: foo
- case 16. just escaped text: \[foo\]
- case 17. inline link: Foo
-
- [^example]: https://www.example.com/
- "#]],
- );
- }
-
- #[test]
- fn test_hover_macro_generated_struct_fn_doc_comment() {
- cov_mark::check!(hover_macro_generated_struct_fn_doc_comment);
-
- check(
- r#"
-macro_rules! bar {
- () => {
- struct Bar;
- impl Bar {
- /// Do the foo
- fn foo(&self) {}
- }
- }
-}
-
-bar!();
-
-fn foo() { let bar = Bar; bar.fo$0o(); }
-"#,
- expect![[r#"
- *foo*
-
- ```rust
- test::Bar
- ```
-
- ```rust
- fn foo(&self)
- ```
-
- ---
-
- Do the foo
- "#]],
- );
- }
-
- #[test]
- fn test_hover_macro_generated_struct_fn_doc_attr() {
- cov_mark::check!(hover_macro_generated_struct_fn_doc_attr);
-
- check(
- r#"
-macro_rules! bar {
- () => {
- struct Bar;
- impl Bar {
- #[doc = "Do the foo"]
- fn foo(&self) {}
- }
- }
-}
-
-bar!();
-
-fn foo() { let bar = Bar; bar.fo$0o(); }
-"#,
- expect![[r#"
- *foo*
-
- ```rust
- test::Bar
- ```
-
- ```rust
- fn foo(&self)
- ```
-
- ---
-
- Do the foo
- "#]],
- );
- }
-
- #[test]
- fn test_hover_trait_has_impl_action() {
- check_actions(
- r#"trait foo$0() {}"#,
- expect![[r#"
- [
- Implementation(
- FilePosition {
- file_id: FileId(
- 0,
- ),
- offset: 6,
- },
- ),
- ]
- "#]],
- );
- }
-
- #[test]
- fn test_hover_struct_has_impl_action() {
- check_actions(
- r"struct foo$0() {}",
- expect![[r#"
- [
- Implementation(
- FilePosition {
- file_id: FileId(
- 0,
- ),
- offset: 7,
- },
- ),
- ]
- "#]],
- );
- }
-
- #[test]
- fn test_hover_union_has_impl_action() {
- check_actions(
- r#"union foo$0() {}"#,
- expect![[r#"
- [
- Implementation(
- FilePosition {
- file_id: FileId(
- 0,
- ),
- offset: 6,
- },
- ),
- ]
- "#]],
- );
- }
-
- #[test]
- fn test_hover_enum_has_impl_action() {
- check_actions(
- r"enum foo$0() { A, B }",
- expect![[r#"
- [
- Implementation(
- FilePosition {
- file_id: FileId(
- 0,
- ),
- offset: 5,
- },
- ),
- ]
- "#]],
- );
- }
-
- #[test]
- fn test_hover_self_has_impl_action() {
- check_actions(
- r#"struct foo where Self$0:;"#,
- expect![[r#"
- [
- Implementation(
- FilePosition {
- file_id: FileId(
- 0,
- ),
- offset: 7,
- },
- ),
- ]
- "#]],
- );
- }
-
- #[test]
- fn test_hover_test_has_action() {
- check_actions(
- r#"
-#[test]
-fn foo_$0test() {}
-"#,
- expect![[r#"
- [
- Reference(
- FilePosition {
- file_id: FileId(
- 0,
- ),
- offset: 11,
- },
- ),
- Runnable(
- Runnable {
- use_name_in_title: false,
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 0..24,
- focus_range: 11..19,
- name: "foo_test",
- kind: Function,
- },
- kind: Test {
- test_id: Path(
- "foo_test",
- ),
- attr: TestAttr {
- ignore: false,
- },
- },
- cfg: None,
- },
- ),
- ]
- "#]],
- );
- }
-
- #[test]
- fn test_hover_test_mod_has_action() {
- check_actions(
- r#"
-mod tests$0 {
- #[test]
- fn foo_test() {}
-}
-"#,
- expect![[r#"
- [
- Runnable(
- Runnable {
- use_name_in_title: false,
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 0..46,
- focus_range: 4..9,
- name: "tests",
- kind: Module,
- description: "mod tests",
- },
- kind: TestMod {
- path: "tests",
- },
- cfg: None,
- },
- ),
- ]
- "#]],
- );
- }
-
- #[test]
- fn test_hover_struct_has_goto_type_action() {
- check_actions(
- r#"
-struct S{ f1: u32 }
-
-fn main() { let s$0t = S{ f1:0 }; }
-"#,
- expect![[r#"
- [
- GoToType(
- [
- HoverGotoTypeData {
- mod_path: "test::S",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 0..19,
- focus_range: 7..8,
- name: "S",
- kind: Struct,
- description: "struct S",
- },
- },
- ],
- ),
- ]
- "#]],
- );
- }
-
- #[test]
- fn test_hover_generic_struct_has_goto_type_actions() {
- check_actions(
- r#"
-struct Arg(u32);
-struct S<T>{ f1: T }
-
-fn main() { let s$0t = S{ f1:Arg(0) }; }
-"#,
- expect![[r#"
- [
- GoToType(
- [
- HoverGotoTypeData {
- mod_path: "test::S",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 17..37,
- focus_range: 24..25,
- name: "S",
- kind: Struct,
- description: "struct S<T>",
- },
- },
- HoverGotoTypeData {
- mod_path: "test::Arg",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 0..16,
- focus_range: 7..10,
- name: "Arg",
- kind: Struct,
- description: "struct Arg",
- },
- },
- ],
- ),
- ]
- "#]],
- );
- }
-
- #[test]
- fn test_hover_generic_struct_has_flattened_goto_type_actions() {
- check_actions(
- r#"
-struct Arg(u32);
-struct S<T>{ f1: T }
-
-fn main() { let s$0t = S{ f1: S{ f1: Arg(0) } }; }
-"#,
- expect![[r#"
- [
- GoToType(
- [
- HoverGotoTypeData {
- mod_path: "test::S",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 17..37,
- focus_range: 24..25,
- name: "S",
- kind: Struct,
- description: "struct S<T>",
- },
- },
- HoverGotoTypeData {
- mod_path: "test::Arg",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 0..16,
- focus_range: 7..10,
- name: "Arg",
- kind: Struct,
- description: "struct Arg",
- },
- },
- ],
- ),
- ]
- "#]],
- );
- }
-
- #[test]
- fn test_hover_tuple_has_goto_type_actions() {
- check_actions(
- r#"
-struct A(u32);
-struct B(u32);
-mod M {
- pub struct C(u32);
-}
-
-fn main() { let s$0t = (A(1), B(2), M::C(3) ); }
-"#,
- expect![[r#"
- [
- GoToType(
- [
- HoverGotoTypeData {
- mod_path: "test::A",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 0..14,
- focus_range: 7..8,
- name: "A",
- kind: Struct,
- description: "struct A",
- },
- },
- HoverGotoTypeData {
- mod_path: "test::B",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 15..29,
- focus_range: 22..23,
- name: "B",
- kind: Struct,
- description: "struct B",
- },
- },
- HoverGotoTypeData {
- mod_path: "test::M::C",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 42..60,
- focus_range: 53..54,
- name: "C",
- kind: Struct,
- description: "pub struct C",
- },
- },
- ],
- ),
- ]
- "#]],
- );
- }
-
- #[test]
- fn test_hover_return_impl_trait_has_goto_type_action() {
- check_actions(
- r#"
-trait Foo {}
-fn foo() -> impl Foo {}
-
-fn main() { let s$0t = foo(); }
-"#,
- expect![[r#"
- [
- GoToType(
- [
- HoverGotoTypeData {
- mod_path: "test::Foo",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 0..12,
- focus_range: 6..9,
- name: "Foo",
- kind: Trait,
- description: "trait Foo",
- },
- },
- ],
- ),
- ]
- "#]],
- );
- }
-
- #[test]
- fn test_hover_generic_return_impl_trait_has_goto_type_action() {
- check_actions(
- r#"
-trait Foo<T> {}
-struct S;
-fn foo() -> impl Foo<S> {}
-
-fn main() { let s$0t = foo(); }
-"#,
- expect![[r#"
- [
- GoToType(
- [
- HoverGotoTypeData {
- mod_path: "test::Foo",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 0..15,
- focus_range: 6..9,
- name: "Foo",
- kind: Trait,
- description: "trait Foo<T>",
- },
- },
- HoverGotoTypeData {
- mod_path: "test::S",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 16..25,
- focus_range: 23..24,
- name: "S",
- kind: Struct,
- description: "struct S",
- },
- },
- ],
- ),
- ]
- "#]],
- );
- }
-
- #[test]
- fn test_hover_return_impl_traits_has_goto_type_action() {
- check_actions(
- r#"
-trait Foo {}
-trait Bar {}
-fn foo() -> impl Foo + Bar {}
-
-fn main() { let s$0t = foo(); }
-"#,
- expect![[r#"
- [
- GoToType(
- [
- HoverGotoTypeData {
- mod_path: "test::Foo",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 0..12,
- focus_range: 6..9,
- name: "Foo",
- kind: Trait,
- description: "trait Foo",
- },
- },
- HoverGotoTypeData {
- mod_path: "test::Bar",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 13..25,
- focus_range: 19..22,
- name: "Bar",
- kind: Trait,
- description: "trait Bar",
- },
- },
- ],
- ),
- ]
- "#]],
- );
- }
-
- #[test]
- fn test_hover_generic_return_impl_traits_has_goto_type_action() {
- check_actions(
- r#"
-trait Foo<T> {}
-trait Bar<T> {}
-struct S1 {}
-struct S2 {}
-
-fn foo() -> impl Foo<S1> + Bar<S2> {}
-
-fn main() { let s$0t = foo(); }
-"#,
- expect![[r#"
- [
- GoToType(
- [
- HoverGotoTypeData {
- mod_path: "test::Foo",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 0..15,
- focus_range: 6..9,
- name: "Foo",
- kind: Trait,
- description: "trait Foo<T>",
- },
- },
- HoverGotoTypeData {
- mod_path: "test::Bar",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 16..31,
- focus_range: 22..25,
- name: "Bar",
- kind: Trait,
- description: "trait Bar<T>",
- },
- },
- HoverGotoTypeData {
- mod_path: "test::S1",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 32..44,
- focus_range: 39..41,
- name: "S1",
- kind: Struct,
- description: "struct S1",
- },
- },
- HoverGotoTypeData {
- mod_path: "test::S2",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 45..57,
- focus_range: 52..54,
- name: "S2",
- kind: Struct,
- description: "struct S2",
- },
- },
- ],
- ),
- ]
- "#]],
- );
- }
-
- #[test]
- fn test_hover_arg_impl_trait_has_goto_type_action() {
- check_actions(
- r#"
-trait Foo {}
-fn foo(ar$0g: &impl Foo) {}
-"#,
- expect![[r#"
- [
- GoToType(
- [
- HoverGotoTypeData {
- mod_path: "test::Foo",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 0..12,
- focus_range: 6..9,
- name: "Foo",
- kind: Trait,
- description: "trait Foo",
- },
- },
- ],
- ),
- ]
- "#]],
- );
- }
-
- #[test]
- fn test_hover_arg_impl_traits_has_goto_type_action() {
- check_actions(
- r#"
-trait Foo {}
-trait Bar<T> {}
-struct S{}
-
-fn foo(ar$0g: &impl Foo + Bar<S>) {}
-"#,
- expect![[r#"
- [
- GoToType(
- [
- HoverGotoTypeData {
- mod_path: "test::Foo",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 0..12,
- focus_range: 6..9,
- name: "Foo",
- kind: Trait,
- description: "trait Foo",
- },
- },
- HoverGotoTypeData {
- mod_path: "test::Bar",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 13..28,
- focus_range: 19..22,
- name: "Bar",
- kind: Trait,
- description: "trait Bar<T>",
- },
- },
- HoverGotoTypeData {
- mod_path: "test::S",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 29..39,
- focus_range: 36..37,
- name: "S",
- kind: Struct,
- description: "struct S",
- },
- },
- ],
- ),
- ]
- "#]],
- );
- }
-
- #[test]
- fn test_hover_async_block_impl_trait_has_goto_type_action() {
- check_actions(
- r#"
-//- minicore: future
-struct S;
-fn foo() {
- let fo$0o = async { S };
-}
-"#,
- expect![[r#"
- [
- GoToType(
- [
- HoverGotoTypeData {
- mod_path: "core::future::Future",
- nav: NavigationTarget {
- file_id: FileId(
- 1,
- ),
- full_range: 254..436,
- focus_range: 293..299,
- name: "Future",
- kind: Trait,
- description: "pub trait Future",
- },
- },
- HoverGotoTypeData {
- mod_path: "test::S",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 0..9,
- focus_range: 7..8,
- name: "S",
- kind: Struct,
- description: "struct S",
- },
- },
- ],
- ),
- ]
- "#]],
- );
- }
-
- #[test]
- fn test_hover_arg_generic_impl_trait_has_goto_type_action() {
- check_actions(
- r#"
-trait Foo<T> {}
-struct S {}
-fn foo(ar$0g: &impl Foo<S>) {}
-"#,
- expect![[r#"
- [
- GoToType(
- [
- HoverGotoTypeData {
- mod_path: "test::Foo",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 0..15,
- focus_range: 6..9,
- name: "Foo",
- kind: Trait,
- description: "trait Foo<T>",
- },
- },
- HoverGotoTypeData {
- mod_path: "test::S",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 16..27,
- focus_range: 23..24,
- name: "S",
- kind: Struct,
- description: "struct S",
- },
- },
- ],
- ),
- ]
- "#]],
- );
- }
-
- #[test]
- fn test_hover_dyn_return_has_goto_type_action() {
- check_actions(
- r#"
-trait Foo {}
-struct S;
-impl Foo for S {}
-
-struct B<T>{}
-fn foo() -> B<dyn Foo> {}
-
-fn main() { let s$0t = foo(); }
-"#,
- expect![[r#"
- [
- GoToType(
- [
- HoverGotoTypeData {
- mod_path: "test::B",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 42..55,
- focus_range: 49..50,
- name: "B",
- kind: Struct,
- description: "struct B<T>",
- },
- },
- HoverGotoTypeData {
- mod_path: "test::Foo",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 0..12,
- focus_range: 6..9,
- name: "Foo",
- kind: Trait,
- description: "trait Foo",
- },
- },
- ],
- ),
- ]
- "#]],
- );
- }
-
- #[test]
- fn test_hover_dyn_arg_has_goto_type_action() {
- check_actions(
- r#"
-trait Foo {}
-fn foo(ar$0g: &dyn Foo) {}
-"#,
- expect![[r#"
- [
- GoToType(
- [
- HoverGotoTypeData {
- mod_path: "test::Foo",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 0..12,
- focus_range: 6..9,
- name: "Foo",
- kind: Trait,
- description: "trait Foo",
- },
- },
- ],
- ),
- ]
- "#]],
- );
- }
-
- #[test]
- fn test_hover_generic_dyn_arg_has_goto_type_action() {
- check_actions(
- r#"
-trait Foo<T> {}
-struct S {}
-fn foo(ar$0g: &dyn Foo<S>) {}
-"#,
- expect![[r#"
- [
- GoToType(
- [
- HoverGotoTypeData {
- mod_path: "test::Foo",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 0..15,
- focus_range: 6..9,
- name: "Foo",
- kind: Trait,
- description: "trait Foo<T>",
- },
- },
- HoverGotoTypeData {
- mod_path: "test::S",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 16..27,
- focus_range: 23..24,
- name: "S",
- kind: Struct,
- description: "struct S",
- },
- },
- ],
- ),
- ]
- "#]],
- );
- }
-
- #[test]
- fn test_hover_goto_type_action_links_order() {
- check_actions(
- r#"
-trait ImplTrait<T> {}
-trait DynTrait<T> {}
-struct B<T> {}
-struct S {}
-
-fn foo(a$0rg: &impl ImplTrait<B<dyn DynTrait<B<S>>>>) {}
-"#,
- expect![[r#"
- [
- GoToType(
- [
- HoverGotoTypeData {
- mod_path: "test::ImplTrait",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 0..21,
- focus_range: 6..15,
- name: "ImplTrait",
- kind: Trait,
- description: "trait ImplTrait<T>",
- },
- },
- HoverGotoTypeData {
- mod_path: "test::B",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 43..57,
- focus_range: 50..51,
- name: "B",
- kind: Struct,
- description: "struct B<T>",
- },
- },
- HoverGotoTypeData {
- mod_path: "test::DynTrait",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 22..42,
- focus_range: 28..36,
- name: "DynTrait",
- kind: Trait,
- description: "trait DynTrait<T>",
- },
- },
- HoverGotoTypeData {
- mod_path: "test::S",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 58..69,
- focus_range: 65..66,
- name: "S",
- kind: Struct,
- description: "struct S",
- },
- },
- ],
- ),
- ]
- "#]],
- );
- }
-
- #[test]
- fn test_hover_associated_type_has_goto_type_action() {
- check_actions(
- r#"
-trait Foo {
- type Item;
- fn get(self) -> Self::Item {}
-}
-
-struct Bar{}
-struct S{}
-
-impl Foo for S { type Item = Bar; }
-
-fn test() -> impl Foo { S {} }
-
-fn main() { let s$0t = test().get(); }
-"#,
- expect![[r#"
- [
- GoToType(
- [
- HoverGotoTypeData {
- mod_path: "test::Foo",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 0..62,
- focus_range: 6..9,
- name: "Foo",
- kind: Trait,
- description: "trait Foo",
- },
- },
- ],
- ),
- ]
- "#]],
- );
- }
-
- #[test]
- fn test_hover_const_param_has_goto_type_action() {
- check_actions(
- r#"
-struct Bar;
-struct Foo<const BAR: Bar>;
-
-impl<const BAR: Bar> Foo<BAR$0> {}
-"#,
- expect![[r#"
- [
- GoToType(
- [
- HoverGotoTypeData {
- mod_path: "test::Bar",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 0..11,
- focus_range: 7..10,
- name: "Bar",
- kind: Struct,
- description: "struct Bar",
- },
- },
- ],
- ),
- ]
- "#]],
- );
- }
-
- #[test]
- fn test_hover_type_param_has_goto_type_action() {
- check_actions(
- r#"
-trait Foo {}
-
-fn foo<T: Foo>(t: T$0){}
-"#,
- expect![[r#"
- [
- GoToType(
- [
- HoverGotoTypeData {
- mod_path: "test::Foo",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 0..12,
- focus_range: 6..9,
- name: "Foo",
- kind: Trait,
- description: "trait Foo",
- },
- },
- ],
- ),
- ]
- "#]],
- );
- }
-
- #[test]
- fn test_hover_self_has_go_to_type() {
- check_actions(
- r#"
-struct Foo;
-impl Foo {
- fn foo(&self$0) {}
-}
-"#,
- expect![[r#"
- [
- GoToType(
- [
- HoverGotoTypeData {
- mod_path: "test::Foo",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 0..11,
- focus_range: 7..10,
- name: "Foo",
- kind: Struct,
- description: "struct Foo",
- },
- },
- ],
- ),
- ]
- "#]],
- );
- }
-
- #[test]
- fn hover_displays_normalized_crate_names() {
- check(
- r#"
-//- /lib.rs crate:name-with-dashes
-pub mod wrapper {
- pub struct Thing { x: u32 }
-
- impl Thing {
- pub fn new() -> Thing { Thing { x: 0 } }
- }
-}
-
-//- /main.rs crate:main deps:name-with-dashes
-fn main() { let foo_test = name_with_dashes::wrapper::Thing::new$0(); }
-"#,
- expect![[r#"
- *new*
-
- ```rust
- name_with_dashes::wrapper::Thing
- ```
-
- ```rust
- pub fn new() -> Thing
- ```
- "#]],
- )
- }
-
- #[test]
- fn hover_field_pat_shorthand_ref_match_ergonomics() {
- check(
- r#"
-struct S {
- f: i32,
-}
-
-fn main() {
- let s = S { f: 0 };
- let S { f$0 } = &s;
-}
-"#,
- expect![[r#"
- *f*
-
- ```rust
- f: &i32
- ```
- "#]],
- );
- }
-
- #[test]
- fn hover_self_param_shows_type() {
- check(
- r#"
-struct Foo {}
-impl Foo {
- fn bar(&sel$0f) {}
-}
-"#,
- expect![[r#"
- *self*
-
- ```rust
- self: &Foo
- ```
- "#]],
- );
- }
-
- #[test]
- fn hover_self_param_shows_type_for_arbitrary_self_type() {
- check(
- r#"
-struct Arc<T>(T);
-struct Foo {}
-impl Foo {
- fn bar(sel$0f: Arc<Foo>) {}
-}
-"#,
- expect![[r#"
- *self*
-
- ```rust
- self: Arc<Foo>
- ```
- "#]],
- );
- }
-
- #[test]
- fn hover_doc_outer_inner() {
- check(
- r#"
-/// Be quick;
-mod Foo$0 {
- //! time is mana
-
- /// This comment belongs to the function
- fn foo() {}
-}
-"#,
- expect![[r#"
- *Foo*
-
- ```rust
- test
- ```
-
- ```rust
- mod Foo
- ```
-
- ---
-
- Be quick;
- time is mana
- "#]],
- );
- }
-
- #[test]
- fn hover_doc_outer_inner_attribue() {
- check(
- r#"
-#[doc = "Be quick;"]
-mod Foo$0 {
- #![doc = "time is mana"]
-
- #[doc = "This comment belongs to the function"]
- fn foo() {}
-}
-"#,
- expect![[r#"
- *Foo*
-
- ```rust
- test
- ```
-
- ```rust
- mod Foo
- ```
-
- ---
-
- Be quick;
- time is mana
- "#]],
- );
- }
-
- #[test]
- fn hover_doc_block_style_indentend() {
- check(
- r#"
-/**
- foo
- ```rust
- let x = 3;
- ```
-*/
-fn foo$0() {}
-"#,
- expect![[r#"
- *foo*
-
- ```rust
- test
- ```
-
- ```rust
- fn foo()
- ```
-
- ---
-
- foo
-
- ```rust
- let x = 3;
- ```
- "#]],
- );
- }
-
- #[test]
- fn hover_comments_dont_highlight_parent() {
- cov_mark::check!(no_highlight_on_comment_hover);
- check_hover_no_result(
- r#"
-fn no_hover() {
- // no$0hover
-}
-"#,
- );
- }
-
- #[test]
- fn hover_label() {
- check(
- r#"
-fn foo() {
- 'label$0: loop {}
-}
-"#,
- expect![[r#"
- *'label*
-
- ```rust
- 'label
- ```
- "#]],
- );
- }
-
- #[test]
- fn hover_lifetime() {
- check(
- r#"fn foo<'lifetime>(_: &'lifetime$0 ()) {}"#,
- expect![[r#"
- *'lifetime*
-
- ```rust
- 'lifetime
- ```
- "#]],
- );
- }
-
- #[test]
- fn hover_type_param() {
- check(
- r#"
-//- minicore: sized
-struct Foo<T>(T);
-trait Copy {}
-trait Clone {}
-impl<T: Copy + Clone> Foo<T$0> where T: Sized {}
-"#,
- expect![[r#"
- *T*
-
- ```rust
- T: Copy + Clone
- ```
- "#]],
- );
- check(
- r#"
-struct Foo<T>(T);
-impl<T> Foo<T$0> {}
-"#,
- expect![[r#"
- *T*
-
- ```rust
- T
- ```
- "#]],
- );
- // lifetimes bounds arent being tracked yet
- check(
- r#"
-struct Foo<T>(T);
-impl<T: 'static> Foo<T$0> {}
-"#,
- expect![[r#"
- *T*
-
- ```rust
- T
- ```
- "#]],
- );
- }
-
- #[test]
- fn hover_type_param_not_sized() {
- check(
- r#"
-//- minicore: sized
-struct Foo<T>(T);
-trait Copy {}
-trait Clone {}
-impl<T: Copy + Clone> Foo<T$0> where T: ?Sized {}
-"#,
- expect![[r#"
- *T*
-
- ```rust
- T: Copy + Clone + ?Sized
- ```
- "#]],
- );
- }
-
- #[test]
- fn hover_const_param() {
- check(
- r#"
-struct Foo<const LEN: usize>;
-impl<const LEN: usize> Foo<LEN$0> {}
-"#,
- expect![[r#"
- *LEN*
-
- ```rust
- const LEN: usize
- ```
- "#]],
- );
- }
-
- #[test]
- fn hover_const_pat() {
- check(
- r#"
-/// This is a doc
-const FOO: usize = 3;
-fn foo() {
- match 5 {
- FOO$0 => (),
- _ => ()
- }
-}
-"#,
- expect![[r#"
- *FOO*
-
- ```rust
- test
- ```
-
- ```rust
- const FOO: usize
- ```
-
- ---
-
- This is a doc
- "#]],
- );
- }
-
- #[test]
- fn hover_mod_def() {
- check(
- r#"
-//- /main.rs
-mod foo$0;
-//- /foo.rs
-//! For the horde!
-"#,
- expect![[r#"
- *foo*
-
- ```rust
- test
- ```
-
- ```rust
- mod foo
- ```
-
- ---
-
- For the horde!
- "#]],
- );
- }
-
- #[test]
- fn hover_self_in_use() {
- check(
- r#"
-//! This should not appear
-mod foo {
- /// But this should appear
- pub mod bar {}
-}
-use foo::bar::{self$0};
-"#,
- expect![[r#"
- *self*
-
- ```rust
- test::foo
- ```
-
- ```rust
- mod bar
- ```
-
- ---
-
- But this should appear
- "#]],
- )
- }
-
- #[test]
- fn hover_keyword() {
- check(
- r#"
-//- /main.rs crate:main deps:std
-fn f() { retur$0n; }
-//- /libstd.rs crate:std
-/// Docs for return_keyword
-mod return_keyword {}
-"#,
- expect![[r#"
- *return*
-
- ```rust
- return
- ```
-
- ---
-
- Docs for return_keyword
- "#]],
- );
- }
-
- #[test]
- fn hover_builtin() {
- check(
- r#"
-//- /main.rs crate:main deps:std
-cosnt _: &str$0 = ""; }
-
-//- /libstd.rs crate:std
-/// Docs for prim_str
-mod prim_str {}
-"#,
- expect![[r#"
- *str*
-
- ```rust
- str
- ```
-
- ---
-
- Docs for prim_str
- "#]],
- );
- }
-
- #[test]
- fn hover_macro_expanded_function() {
- check(
- r#"
-struct S<'a, T>(&'a T);
-trait Clone {}
-macro_rules! foo {
- () => {
- fn bar<'t, T: Clone + 't>(s: &mut S<'t, T>, t: u32) -> *mut u32 where
- 't: 't + 't,
- for<'a> T: Clone + 'a
- { 0 as _ }
- };
-}
-
-foo!();
-
-fn main() {
- bar$0;
-}
-"#,
- expect![[r#"
- *bar*
-
- ```rust
- test
- ```
-
- ```rust
- fn bar<'t, T>(s: &mut S<'t, T>, t: u32) -> *mut u32
- where
- T: Clone + 't,
- 't: 't + 't,
- for<'a> T: Clone + 'a,
- ```
- "#]],
- )
- }
-
- #[test]
- fn hover_intra_doc_links() {
- check(
- r#"
-
-pub mod theitem {
- /// This is the item. Cool!
- pub struct TheItem;
-}
-
-/// Gives you a [`TheItem$0`].
-///
-/// [`TheItem`]: theitem::TheItem
-pub fn gimme() -> theitem::TheItem {
- theitem::TheItem
-}
-"#,
- expect![[r#"
- *[`TheItem`]*
-
- ```rust
- test::theitem
- ```
-
- ```rust
- pub struct TheItem
- ```
-
- ---
-
- This is the item. Cool!
- "#]],
- );
- }
-
- #[test]
- fn hover_generic_assoc() {
- check(
- r#"
-fn foo<T: A>() where T::Assoc$0: {}
-
-trait A {
- type Assoc;
-}"#,
- expect![[r#"
- *Assoc*
-
- ```rust
- test
- ```
-
- ```rust
- type Assoc
- ```
- "#]],
- );
- check(
- r#"
-fn foo<T: A>() {
- let _: <T>::Assoc$0;
-}
-
-trait A {
- type Assoc;
-}"#,
- expect![[r#"
- *Assoc*
-
- ```rust
- test
- ```
-
- ```rust
- type Assoc
- ```
- "#]],
- );
- check(
- r#"
-trait A where
- Self::Assoc$0: ,
-{
- type Assoc;
-}"#,
- expect![[r#"
- *Assoc*
-
- ```rust
- test
- ```
-
- ```rust
- type Assoc
- ```
- "#]],
- );
- }
-
- #[test]
- fn string_shadowed_with_inner_items() {
- check(
- r#"
-//- /main.rs crate:main deps:alloc
-
-/// Custom `String` type.
-struct String;
-
-fn f() {
- let _: String$0;
-
- fn inner() {}
-}
-
-//- /alloc.rs crate:alloc
-#[prelude_import]
-pub use string::*;
-
-mod string {
- /// This is `alloc::String`.
- pub struct String;
-}
-"#,
- expect![[r#"
- *String*
-
- ```rust
- main
- ```
-
- ```rust
- struct String
- ```
-
- ---
-
- Custom `String` type.
- "#]],
- )
- }
-
- #[test]
- fn function_doesnt_shadow_crate_in_use_tree() {
- check(
- r#"
-//- /main.rs crate:main deps:foo
-use foo$0::{foo};
-
-//- /foo.rs crate:foo
-pub fn foo() {}
-"#,
- expect![[r#"
- *foo*
-
- ```rust
- extern crate foo
- ```
- "#]],
- )
- }
-
- #[test]
- fn hover_feature() {
- check(
- r#"#![feature(box_syntax$0)]"#,
- expect![[r##"
- *box_syntax*
- ```
- box_syntax
- ```
- ___
-
- # `box_syntax`
-
- The tracking issue for this feature is: [#49733]
-
- [#49733]: https://github.com/rust-lang/rust/issues/49733
-
- See also [`box_patterns`](box-patterns.md)
-
- ------------------------
-
- Currently the only stable way to create a `Box` is via the `Box::new` method.
- Also it is not possible in stable Rust to destructure a `Box` in a match
- pattern. The unstable `box` keyword can be used to create a `Box`. An example
- usage would be:
-
- ```rust
- #![feature(box_syntax)]
-
- fn main() {
- let b = box 5;
- }
- ```
-
- "##]],
- )
- }
-
- #[test]
- fn hover_lint() {
- check(
- r#"#![allow(arithmetic_overflow$0)]"#,
- expect![[r#"
- *arithmetic_overflow*
- ```
- arithmetic_overflow
- ```
- ___
-
- arithmetic operation overflows
- "#]],
- )
- }
-
- #[test]
- fn hover_clippy_lint() {
- check(
- r#"#![allow(clippy::almost_swapped$0)]"#,
- expect![[r#"
- *almost_swapped*
- ```
- clippy::almost_swapped
- ```
- ___
-
- Checks for `foo = bar; bar = foo` sequences.
- "#]],
- )
- }
-
- #[test]
- fn hover_attr_path_qualifier() {
- cov_mark::check!(name_ref_classify_attr_path_qualifier);
- check(
- r#"
-//- /foo.rs crate:foo
-
-//- /lib.rs crate:main.rs deps:foo
-#[fo$0o::bar()]
-struct Foo;
-"#,
- expect![[r#"
- *foo*
-
- ```rust
- extern crate foo
- ```
- "#]],
- )
- }
-
- #[test]
- fn hover_rename() {
- check(
- r#"
-use self as foo$0;
-"#,
- expect![[r#"
- *foo*
-
- ```rust
- extern crate test
- ```
- "#]],
- );
- check(
- r#"
-mod bar {}
-use bar::{self as foo$0};
-"#,
- expect![[r#"
- *foo*
-
- ```rust
- test
- ```
-
- ```rust
- mod bar
- ```
- "#]],
- );
- check(
- r#"
-mod bar {
- use super as foo$0;
-}
-"#,
- expect![[r#"
- *foo*
-
- ```rust
- extern crate test
- ```
- "#]],
- );
- check(
- r#"
-use crate as foo$0;
-"#,
- expect![[r#"
- *foo*
-
- ```rust
- extern crate test
- ```
- "#]],
- );
- }
-
- #[test]
- fn hover_derive_input() {
- check(
- r#"
-#[rustc_builtin_macro]
-pub macro Copy {}
-#[derive(Copy$0)]
-struct Foo;
-"#,
- expect![[r#"
- *Copy*
-
- ```rust
- test
- ```
-
- ```rust
- pub macro Copy
- ```
- "#]],
- );
- check(
- r#"
-mod foo {
- #[rustc_builtin_macro]
- pub macro Copy {}
-}
-#[derive(foo::Copy$0)]
-struct Foo;
-"#,
- expect![[r#"
- *Copy*
-
- ```rust
- test
- ```
-
- ```rust
- pub macro Copy
- ```
- "#]],
- );
- }
-
- #[test]
- fn hover_range_math() {
- check_hover_range(
- r#"
-fn f() { let expr = $01 + 2 * 3$0 }
-"#,
- expect![[r#"
- ```rust
- i32
- ```"#]],
- );
-
- check_hover_range(
- r#"
-fn f() { let expr = 1 $0+ 2 * $03 }
-"#,
- expect![[r#"
- ```rust
- i32
- ```"#]],
- );
-
- check_hover_range(
- r#"
-fn f() { let expr = 1 + $02 * 3$0 }
-"#,
- expect![[r#"
- ```rust
- i32
- ```"#]],
- );
- }
-
- #[test]
- fn hover_range_arrays() {
- check_hover_range(
- r#"
-fn f() { let expr = $0[1, 2, 3, 4]$0 }
-"#,
- expect![[r#"
- ```rust
- [i32; 4]
- ```"#]],
- );
-
- check_hover_range(
- r#"
-fn f() { let expr = [1, 2, $03, 4]$0 }
-"#,
- expect![[r#"
- ```rust
- [i32; 4]
- ```"#]],
- );
-
- check_hover_range(
- r#"
-fn f() { let expr = [1, 2, $03$0, 4] }
-"#,
- expect![[r#"
- ```rust
- i32
- ```"#]],
- );
- }
-
- #[test]
- fn hover_range_functions() {
- check_hover_range(
- r#"
-fn f<T>(a: &[T]) { }
-fn b() { $0f$0(&[1, 2, 3, 4, 5]); }
-"#,
- expect![[r#"
- ```rust
- fn f<i32>(&[i32])
- ```"#]],
- );
-
- check_hover_range(
- r#"
-fn f<T>(a: &[T]) { }
-fn b() { f($0&[1, 2, 3, 4, 5]$0); }
-"#,
- expect![[r#"
- ```rust
- &[i32; 5]
- ```"#]],
- );
- }
-
- #[test]
- fn hover_range_shows_nothing_when_invalid() {
- check_hover_range_no_results(
- r#"
-fn f<T>(a: &[T]) { }
-fn b()$0 { f(&[1, 2, 3, 4, 5]); }$0
-"#,
- );
-
- check_hover_range_no_results(
- r#"
-fn f<T>$0(a: &[T]) { }
-fn b() { f(&[1, 2, 3,$0 4, 5]); }
-"#,
- );
-
- check_hover_range_no_results(
- r#"
-fn $0f() { let expr = [1, 2, 3, 4]$0 }
-"#,
- );
- }
-
- #[test]
- fn hover_range_shows_unit_for_statements() {
- check_hover_range(
- r#"
-fn f<T>(a: &[T]) { }
-fn b() { $0f(&[1, 2, 3, 4, 5]); }$0
-"#,
- expect![[r#"
- ```rust
- ()
- ```"#]],
- );
-
- check_hover_range(
- r#"
-fn f() { let expr$0 = $0[1, 2, 3, 4] }
-"#,
- expect![[r#"
- ```rust
- ()
- ```"#]],
- );
- }
-
- #[test]
- fn hover_range_for_pat() {
- check_hover_range(
- r#"
-fn foo() {
- let $0x$0 = 0;
-}
-"#,
- expect![[r#"
- ```rust
- i32
- ```"#]],
- );
-
- check_hover_range(
- r#"
-fn foo() {
- let $0x$0 = "";
-}
-"#,
- expect![[r#"
- ```rust
- &str
- ```"#]],
- );
- }
-
- #[test]
- fn hover_range_shows_coercions_if_applicable_expr() {
- check_hover_range(
- r#"
-fn foo() {
- let x: &u32 = $0&&&&&0$0;
-}
-"#,
- expect![[r#"
- ```text
- Type: &&&&&u32
- Coerced to: &u32
- ```
- "#]],
- );
- check_hover_range(
- r#"
-fn foo() {
- let x: *const u32 = $0&0$0;
-}
-"#,
- expect![[r#"
- ```text
- Type: &u32
- Coerced to: *const u32
- ```
- "#]],
- );
- }
-
- #[test]
- fn hover_range_shows_type_actions() {
- check_actions(
- r#"
-struct Foo;
-fn foo() {
- let x: &Foo = $0&&&&&Foo$0;
-}
-"#,
- expect![[r#"
- [
- GoToType(
- [
- HoverGotoTypeData {
- mod_path: "test::Foo",
- nav: NavigationTarget {
- file_id: FileId(
- 0,
- ),
- full_range: 0..11,
- focus_range: 7..10,
- name: "Foo",
- kind: Struct,
- description: "struct Foo",
- },
- },
- ],
- ),
- ]
- "#]],
- );
- }
-
- #[test]
- fn hover_try_expr_res() {
- check_hover_range(
- r#"
-//- minicore:result
-struct FooError;
-
-fn foo() -> Result<(), FooError> {
- Ok($0Result::<(), FooError>::Ok(())?$0)
-}
-"#,
- expect![[r#"
- ```rust
- ()
- ```"#]],
- );
- check_hover_range(
- r#"
-//- minicore:result
-struct FooError;
-struct BarError;
-
-fn foo() -> Result<(), FooError> {
- Ok($0Result::<(), BarError>::Ok(())?$0)
-}
-"#,
- expect![[r#"
- ```text
- Try Error Type: BarError
- Propagated as: FooError
- ```
- "#]],
- );
- }
-
- #[test]
- fn hover_try_expr() {
- check_hover_range(
- r#"
-struct NotResult<T, U>(T, U);
-struct Short;
-struct Looooong;
-
-fn foo() -> NotResult<(), Looooong> {
- $0NotResult((), Short)?$0;
-}
-"#,
- expect![[r#"
- ```text
- Try Target Type: NotResult<(), Short>
- Propagated as: NotResult<(), Looooong>
- ```
- "#]],
- );
- check_hover_range(
- r#"
-struct NotResult<T, U>(T, U);
-struct Short;
-struct Looooong;
-
-fn foo() -> NotResult<(), Short> {
- $0NotResult((), Looooong)?$0;
-}
-"#,
- expect![[r#"
- ```text
- Try Target Type: NotResult<(), Looooong>
- Propagated as: NotResult<(), Short>
- ```
- "#]],
- );
- }
-
- #[test]
- fn hover_try_expr_option() {
- cov_mark::check!(hover_try_expr_opt_opt);
- check_hover_range(
- r#"
-//- minicore: option, try
-
-fn foo() -> Option<()> {
- $0Some(0)?$0;
- None
-}
-"#,
- expect![[r#"
- ```rust
- <Option<i32> as Try>::Output
- ```"#]],
- );
- }
-}
--- /dev/null
+//! Logic for rendering the different hover messages
+use either::Either;
+use hir::{AsAssocItem, HasAttrs, HasSource, HirDisplay, Semantics, TypeInfo};
+use ide_db::{
+ base_db::SourceDatabase,
+ defs::Definition,
+ helpers::{
+ generated_lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES},
+ FamousDefs,
+ },
+ RootDatabase,
+};
+use itertools::Itertools;
+use stdx::format_to;
+use syntax::{
+ algo, ast,
+ display::{fn_as_proc_macro_label, macro_label},
+ match_ast, AstNode, Direction,
+ SyntaxKind::{CONDITION, LET_STMT},
+ SyntaxToken, T,
+};
+
+use crate::{
+ doc_links::{remove_links, rewrite_links},
+ hover::walk_and_push_ty,
+ markdown_remove::remove_markdown,
+ HoverAction, HoverConfig, HoverResult, Markup,
+};
+
+pub(super) fn type_info(
+ sema: &Semantics<RootDatabase>,
+ config: &HoverConfig,
+ expr_or_pat: &Either<ast::Expr, ast::Pat>,
+) -> Option<HoverResult> {
+ let TypeInfo { original, adjusted } = match expr_or_pat {
+ Either::Left(expr) => sema.type_of_expr(expr)?,
+ Either::Right(pat) => sema.type_of_pat(pat)?,
+ };
+
+ let mut res = HoverResult::default();
+ let mut targets: Vec<hir::ModuleDef> = Vec::new();
+ let mut push_new_def = |item: hir::ModuleDef| {
+ if !targets.contains(&item) {
+ targets.push(item);
+ }
+ };
+ walk_and_push_ty(sema.db, &original, &mut push_new_def);
+
+ res.markup = if let Some(adjusted_ty) = adjusted {
+ walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def);
+ let original = original.display(sema.db).to_string();
+ let adjusted = adjusted_ty.display(sema.db).to_string();
+ let static_text_diff_len = "Coerced to: ".len() - "Type: ".len();
+ format!(
+ "{bt_start}Type: {:>apad$}\nCoerced to: {:>opad$}\n{bt_end}",
+ original,
+ adjusted,
+ apad = static_text_diff_len + adjusted.len().max(original.len()),
+ opad = original.len(),
+ bt_start = if config.markdown() { "```text\n" } else { "" },
+ bt_end = if config.markdown() { "```\n" } else { "" }
+ )
+ .into()
+ } else {
+ if config.markdown() {
+ Markup::fenced_block(&original.display(sema.db))
+ } else {
+ original.display(sema.db).to_string().into()
+ }
+ };
+ res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
+ Some(res)
+}
+
+pub(super) fn try_expr(
+ sema: &Semantics<RootDatabase>,
+ config: &HoverConfig,
+ try_expr: &ast::TryExpr,
+) -> Option<HoverResult> {
+ let inner_ty = sema.type_of_expr(&try_expr.expr()?)?.original;
+ let mut ancestors = try_expr.syntax().ancestors();
+ let mut body_ty = loop {
+ let next = ancestors.next()?;
+ break match_ast! {
+ match next {
+ ast::Fn(fn_) => sema.to_def(&fn_)?.ret_type(sema.db),
+ ast::Item(__) => return None,
+ ast::ClosureExpr(closure) => sema.type_of_expr(&closure.body()?)?.original,
+ ast::EffectExpr(effect) => if matches!(effect.effect(), ast::Effect::Async(_) | ast::Effect::Try(_)| ast::Effect::Const(_)) {
+ sema.type_of_expr(&effect.block_expr()?.into())?.original
+ } else {
+ continue;
+ },
+ _ => continue,
+ }
+ };
+ };
+
+ if inner_ty == body_ty {
+ return None;
+ }
+
+ let mut inner_ty = inner_ty;
+ let mut s = "Try Target".to_owned();
+
+ let adts = inner_ty.as_adt().zip(body_ty.as_adt());
+ if let Some((hir::Adt::Enum(inner), hir::Adt::Enum(body))) = adts {
+ let famous_defs = FamousDefs(sema, sema.scope(&try_expr.syntax()).krate());
+ // special case for two options, there is no value in showing them
+ if let Some(option_enum) = famous_defs.core_option_Option() {
+ if inner == option_enum && body == option_enum {
+ cov_mark::hit!(hover_try_expr_opt_opt);
+ return None;
+ }
+ }
+
+ // special case two results to show the error variants only
+ if let Some(result_enum) = famous_defs.core_result_Result() {
+ if inner == result_enum && body == result_enum {
+ let error_type_args =
+ inner_ty.type_arguments().nth(1).zip(body_ty.type_arguments().nth(1));
+ if let Some((inner, body)) = error_type_args {
+ inner_ty = inner;
+ body_ty = body;
+ s = "Try Error".to_owned();
+ }
+ }
+ }
+ }
+
+ let mut res = HoverResult::default();
+
+ let mut targets: Vec<hir::ModuleDef> = Vec::new();
+ let mut push_new_def = |item: hir::ModuleDef| {
+ if !targets.contains(&item) {
+ targets.push(item);
+ }
+ };
+ walk_and_push_ty(sema.db, &inner_ty, &mut push_new_def);
+ walk_and_push_ty(sema.db, &body_ty, &mut push_new_def);
+ res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
+
+ let inner_ty = inner_ty.display(sema.db).to_string();
+ let body_ty = body_ty.display(sema.db).to_string();
+ let ty_len_max = inner_ty.len().max(body_ty.len());
+
+ let l = "Propagated as: ".len() - " Type: ".len();
+ let static_text_len_diff = l as isize - s.len() as isize;
+ let tpad = static_text_len_diff.max(0) as usize;
+ let ppad = static_text_len_diff.min(0).abs() as usize;
+
+ res.markup = format!(
+ "{bt_start}{} Type: {:>pad0$}\nPropagated as: {:>pad1$}\n{bt_end}",
+ s,
+ inner_ty,
+ body_ty,
+ pad0 = ty_len_max + tpad,
+ pad1 = ty_len_max + ppad,
+ bt_start = if config.markdown() { "```text\n" } else { "" },
+ bt_end = if config.markdown() { "```\n" } else { "" }
+ )
+ .into();
+ Some(res)
+}
+
+pub(super) fn deref_expr(
+ sema: &Semantics<RootDatabase>,
+ config: &HoverConfig,
+ deref_expr: &ast::PrefixExpr,
+) -> Option<HoverResult> {
+ let inner_ty = sema.type_of_expr(&deref_expr.expr()?)?.original;
+ let TypeInfo { original, adjusted } =
+ sema.type_of_expr(&ast::Expr::from(deref_expr.clone()))?;
+
+ let mut res = HoverResult::default();
+ let mut targets: Vec<hir::ModuleDef> = Vec::new();
+ let mut push_new_def = |item: hir::ModuleDef| {
+ if !targets.contains(&item) {
+ targets.push(item);
+ }
+ };
+ walk_and_push_ty(sema.db, &inner_ty, &mut push_new_def);
+ walk_and_push_ty(sema.db, &original, &mut push_new_def);
+
+ res.markup = if let Some(adjusted_ty) = adjusted {
+ walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def);
+ let original = original.display(sema.db).to_string();
+ let adjusted = adjusted_ty.display(sema.db).to_string();
+ let inner = inner_ty.display(sema.db).to_string();
+ let type_len = "To type: ".len();
+ let coerced_len = "Coerced to: ".len();
+ let deref_len = "Dereferenced from: ".len();
+ let max_len = (original.len() + type_len)
+ .max(adjusted.len() + coerced_len)
+ .max(inner.len() + deref_len);
+ format!(
+ "{bt_start}Dereferenced from: {:>ipad$}\nTo type: {:>apad$}\nCoerced to: {:>opad$}\n{bt_end}",
+ inner,
+ original,
+ adjusted,
+ ipad = max_len - deref_len,
+ apad = max_len - type_len,
+ opad = max_len - coerced_len,
+ bt_start = if config.markdown() { "```text\n" } else { "" },
+ bt_end = if config.markdown() { "```\n" } else { "" }
+ )
+ .into()
+ } else {
+ let original = original.display(sema.db).to_string();
+ let inner = inner_ty.display(sema.db).to_string();
+ let type_len = "To type: ".len();
+ let deref_len = "Dereferenced from: ".len();
+ let max_len = (original.len() + type_len).max(inner.len() + deref_len);
+ format!(
+ "{bt_start}Dereferenced from: {:>ipad$}\nTo type: {:>apad$}\n{bt_end}",
+ inner,
+ original,
+ ipad = max_len - deref_len,
+ apad = max_len - type_len,
+ bt_start = if config.markdown() { "```text\n" } else { "" },
+ bt_end = if config.markdown() { "```\n" } else { "" }
+ )
+ .into()
+ };
+ res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
+
+ Some(res)
+}
+
+pub(super) fn keyword(
+ sema: &Semantics<RootDatabase>,
+ config: &HoverConfig,
+ token: &SyntaxToken,
+) -> Option<HoverResult> {
+ if !token.kind().is_keyword() || !config.documentation.is_some() {
+ return None;
+ }
+ let famous_defs = FamousDefs(sema, sema.scope(&token.parent()?).krate());
+ // std exposes {}_keyword modules with docstrings on the root to document keywords
+ let keyword_mod = format!("{}_keyword", token.text());
+ let doc_owner = find_std_module(&famous_defs, &keyword_mod)?;
+ let docs = doc_owner.attrs(sema.db).docs()?;
+ let markup = process_markup(
+ sema.db,
+ Definition::ModuleDef(doc_owner.into()),
+ &markup(Some(docs.into()), token.text().into(), None)?,
+ config,
+ );
+ Some(HoverResult { markup, actions: Default::default() })
+}
+
+pub(super) fn try_for_lint(attr: &ast::Attr, token: &SyntaxToken) -> Option<HoverResult> {
+ let (path, tt) = attr.as_simple_call()?;
+ if !tt.syntax().text_range().contains(token.text_range().start()) {
+ return None;
+ }
+ let (is_clippy, lints) = match &*path {
+ "feature" => (false, FEATURES),
+ "allow" | "deny" | "forbid" | "warn" => {
+ let is_clippy = algo::non_trivia_sibling(token.clone().into(), Direction::Prev)
+ .filter(|t| t.kind() == T![:])
+ .and_then(|t| algo::non_trivia_sibling(t, Direction::Prev))
+ .filter(|t| t.kind() == T![:])
+ .and_then(|t| algo::non_trivia_sibling(t, Direction::Prev))
+ .map_or(false, |t| {
+ t.kind() == T![ident] && t.into_token().map_or(false, |t| t.text() == "clippy")
+ });
+ if is_clippy {
+ (true, CLIPPY_LINTS)
+ } else {
+ (false, DEFAULT_LINTS)
+ }
+ }
+ _ => return None,
+ };
+
+ let tmp;
+ let needle = if is_clippy {
+ tmp = format!("clippy::{}", token.text());
+ &tmp
+ } else {
+ &*token.text()
+ };
+
+ let lint =
+ lints.binary_search_by_key(&needle, |lint| lint.label).ok().map(|idx| &lints[idx])?;
+ Some(HoverResult {
+ markup: Markup::from(format!("```\n{}\n```\n___\n\n{}", lint.label, lint.description)),
+ ..Default::default()
+ })
+}
+
+pub(super) fn process_markup(
+ db: &RootDatabase,
+ def: Definition,
+ markup: &Markup,
+ config: &HoverConfig,
+) -> Markup {
+ let markup = markup.as_str();
+ let markup = if !config.markdown() {
+ remove_markdown(markup)
+ } else if config.links_in_hover {
+ rewrite_links(db, markup, def)
+ } else {
+ remove_links(markup)
+ };
+ Markup::from(markup)
+}
+
+fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String> {
+ match def {
+ Definition::Field(f) => Some(f.parent_def(db).name(db)),
+ Definition::Local(l) => l.parent(db).name(db),
+ Definition::ModuleDef(md) => match md {
+ hir::ModuleDef::Function(f) => match f.as_assoc_item(db)?.container(db) {
+ hir::AssocItemContainer::Trait(t) => Some(t.name(db)),
+ hir::AssocItemContainer::Impl(i) => i.self_ty(db).as_adt().map(|adt| adt.name(db)),
+ },
+ hir::ModuleDef::Variant(e) => Some(e.parent_enum(db).name(db)),
+ _ => None,
+ },
+ _ => None,
+ }
+ .map(|name| name.to_string())
+}
+
+pub(super) fn path(db: &RootDatabase, module: hir::Module, item_name: Option<String>) -> String {
+ let crate_name =
+ db.crate_graph()[module.krate().into()].display_name.as_ref().map(|it| it.to_string());
+ let module_path = module
+ .path_to_root(db)
+ .into_iter()
+ .rev()
+ .flat_map(|it| it.name(db).map(|name| name.to_string()));
+ crate_name.into_iter().chain(module_path).chain(item_name).join("::")
+}
+
+pub(super) fn definition(
+ db: &RootDatabase,
+ def: Definition,
+ famous_defs: Option<&FamousDefs>,
+ config: &HoverConfig,
+) -> Option<Markup> {
+ let mod_path = definition_mod_path(db, &def);
+ let (label, docs) = match def {
+ Definition::Macro(it) => (
+ match &it.source(db)?.value {
+ Either::Left(mac) => macro_label(mac),
+ Either::Right(mac_fn) => fn_as_proc_macro_label(mac_fn),
+ },
+ it.attrs(db).docs(),
+ ),
+ Definition::Field(def) => label_and_docs(db, def),
+ Definition::ModuleDef(it) => match it {
+ hir::ModuleDef::Module(it) => label_and_docs(db, it),
+ hir::ModuleDef::Function(it) => label_and_docs(db, it),
+ hir::ModuleDef::Adt(it) => label_and_docs(db, it),
+ hir::ModuleDef::Variant(it) => label_and_docs(db, it),
+ hir::ModuleDef::Const(it) => label_and_docs(db, it),
+ hir::ModuleDef::Static(it) => label_and_docs(db, it),
+ hir::ModuleDef::Trait(it) => label_and_docs(db, it),
+ hir::ModuleDef::TypeAlias(it) => label_and_docs(db, it),
+ hir::ModuleDef::BuiltinType(it) => {
+ return famous_defs
+ .and_then(|fd| builtin(fd, it))
+ .or_else(|| Some(Markup::fenced_block(&it.name())))
+ }
+ },
+ Definition::Local(it) => return local(db, it),
+ Definition::SelfType(impl_def) => {
+ impl_def.self_ty(db).as_adt().map(|adt| label_and_docs(db, adt))?
+ }
+ Definition::GenericParam(it) => label_and_docs(db, it),
+ Definition::Label(it) => return Some(Markup::fenced_block(&it.name(db))),
+ };
+
+ markup(docs.filter(|_| config.documentation.is_some()).map(Into::into), label, mod_path)
+}
+
+fn label_and_docs<D>(db: &RootDatabase, def: D) -> (String, Option<hir::Documentation>)
+where
+ D: HasAttrs + HirDisplay,
+{
+ let label = def.display(db).to_string();
+ let docs = def.attrs(db).docs();
+ (label, docs)
+}
+
+fn definition_mod_path(db: &RootDatabase, def: &Definition) -> Option<String> {
+ if let Definition::GenericParam(_) = def {
+ return None;
+ }
+ def.module(db).map(|module| path(db, module, definition_owner_name(db, def)))
+}
+
+fn markup(docs: Option<String>, desc: String, mod_path: Option<String>) -> Option<Markup> {
+ let mut buf = String::new();
+
+ if let Some(mod_path) = mod_path {
+ if !mod_path.is_empty() {
+ format_to!(buf, "```rust\n{}\n```\n\n", mod_path);
+ }
+ }
+ format_to!(buf, "```rust\n{}\n```", desc);
+
+ if let Some(doc) = docs {
+ format_to!(buf, "\n___\n\n{}", doc);
+ }
+ Some(buf.into())
+}
+
+fn builtin(famous_defs: &FamousDefs, builtin: hir::BuiltinType) -> Option<Markup> {
+ // std exposes prim_{} modules with docstrings on the root to document the builtins
+ let primitive_mod = format!("prim_{}", builtin.name());
+ let doc_owner = find_std_module(famous_defs, &primitive_mod)?;
+ let docs = doc_owner.attrs(famous_defs.0.db).docs()?;
+ markup(Some(docs.into()), builtin.name().to_string(), None)
+}
+
+fn find_std_module(famous_defs: &FamousDefs, name: &str) -> Option<hir::Module> {
+ let db = famous_defs.0.db;
+ let std_crate = famous_defs.std()?;
+ let std_root_module = std_crate.root_module(db);
+ std_root_module
+ .children(db)
+ .find(|module| module.name(db).map_or(false, |module| module.to_string() == name))
+}
+
+fn local(db: &RootDatabase, it: hir::Local) -> Option<Markup> {
+ let ty = it.ty(db);
+ let ty = ty.display(db);
+ let is_mut = if it.is_mut(db) { "mut " } else { "" };
+ let desc = match it.source(db).value {
+ Either::Left(ident) => {
+ let name = it.name(db).unwrap();
+ let let_kw = if ident
+ .syntax()
+ .parent()
+ .map_or(false, |p| p.kind() == LET_STMT || p.kind() == CONDITION)
+ {
+ "let "
+ } else {
+ ""
+ };
+ format!("{}{}{}: {}", let_kw, is_mut, name, ty)
+ }
+ Either::Right(_) => format!("{}self: {}", is_mut, ty),
+ };
+ markup(None, desc, None)
+}
--- /dev/null
+use expect_test::{expect, Expect};
+use ide_db::base_db::{FileLoader, FileRange};
+use syntax::TextRange;
+
+use crate::{fixture, hover::HoverDocFormat, HoverConfig};
+
+fn check_hover_no_result(ra_fixture: &str) {
+ let (analysis, position) = fixture::position(ra_fixture);
+ let hover = analysis
+ .hover(
+ &HoverConfig { links_in_hover: true, documentation: Some(HoverDocFormat::Markdown) },
+ FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
+ )
+ .unwrap();
+ assert!(hover.is_none(), "hover not expected but found: {:?}", hover.unwrap());
+}
+
+fn check(ra_fixture: &str, expect: Expect) {
+ let (analysis, position) = fixture::position(ra_fixture);
+ let hover = analysis
+ .hover(
+ &HoverConfig { links_in_hover: true, documentation: Some(HoverDocFormat::Markdown) },
+ FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
+ )
+ .unwrap()
+ .unwrap();
+
+ let content = analysis.db.file_text(position.file_id);
+ let hovered_element = &content[hover.range];
+
+ let actual = format!("*{}*\n{}\n", hovered_element, hover.info.markup);
+ expect.assert_eq(&actual)
+}
+
+fn check_hover_no_links(ra_fixture: &str, expect: Expect) {
+ let (analysis, position) = fixture::position(ra_fixture);
+ let hover = analysis
+ .hover(
+ &HoverConfig { links_in_hover: false, documentation: Some(HoverDocFormat::Markdown) },
+ FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
+ )
+ .unwrap()
+ .unwrap();
+
+ let content = analysis.db.file_text(position.file_id);
+ let hovered_element = &content[hover.range];
+
+ let actual = format!("*{}*\n{}\n", hovered_element, hover.info.markup);
+ expect.assert_eq(&actual)
+}
+
+fn check_hover_no_markdown(ra_fixture: &str, expect: Expect) {
+ let (analysis, position) = fixture::position(ra_fixture);
+ let hover = analysis
+ .hover(
+ &HoverConfig { links_in_hover: true, documentation: Some(HoverDocFormat::PlainText) },
+ FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
+ )
+ .unwrap()
+ .unwrap();
+
+ let content = analysis.db.file_text(position.file_id);
+ let hovered_element = &content[hover.range];
+
+ let actual = format!("*{}*\n{}\n", hovered_element, hover.info.markup);
+ expect.assert_eq(&actual)
+}
+
+fn check_actions(ra_fixture: &str, expect: Expect) {
+ let (analysis, file_id, position) = fixture::range_or_position(ra_fixture);
+ let hover = analysis
+ .hover(
+ &HoverConfig { links_in_hover: true, documentation: Some(HoverDocFormat::Markdown) },
+ FileRange { file_id, range: position.range_or_empty() },
+ )
+ .unwrap()
+ .unwrap();
+ expect.assert_debug_eq(&hover.info.actions)
+}
+
+fn check_hover_range(ra_fixture: &str, expect: Expect) {
+ let (analysis, range) = fixture::range(ra_fixture);
+ let hover = analysis
+ .hover(
+ &HoverConfig { links_in_hover: false, documentation: Some(HoverDocFormat::Markdown) },
+ range,
+ )
+ .unwrap()
+ .unwrap();
+ expect.assert_eq(hover.info.markup.as_str())
+}
+
+fn check_hover_range_no_results(ra_fixture: &str) {
+ let (analysis, range) = fixture::range(ra_fixture);
+ let hover = analysis
+ .hover(
+ &HoverConfig { links_in_hover: false, documentation: Some(HoverDocFormat::Markdown) },
+ range,
+ )
+ .unwrap();
+ assert!(hover.is_none());
+}
+
+#[test]
+fn hover_descend_macros_avoids_duplicates() {
+ check(
+ r#"
+macro_rules! dupe_use {
+ ($local:ident) => {
+ {
+ $local;
+ $local;
+ }
+ }
+}
+fn foo() {
+ let local = 0;
+ dupe_use!(local$0);
+}
+"#,
+ expect![[r#"
+ *local*
+
+ ```rust
+ let local: i32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_shows_all_macro_descends() {
+ check(
+ r#"
+macro_rules! m {
+ ($name:ident) => {
+ /// Outer
+ fn $name() {}
+
+ mod module {
+ /// Inner
+ fn $name() {}
+ }
+ };
+}
+
+m!(ab$0c);
+ "#,
+ expect![[r#"
+ *abc*
+
+ ```rust
+ test::module
+ ```
+
+ ```rust
+ fn abc()
+ ```
+
+ ---
+
+ Inner
+ ---
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn abc()
+ ```
+
+ ---
+
+ Outer
+ "#]],
+ );
+}
+
+#[test]
+fn hover_shows_type_of_an_expression() {
+ check(
+ r#"
+pub fn foo() -> u32 { 1 }
+
+fn main() {
+ let foo_test = foo()$0;
+}
+"#,
+ expect![[r#"
+ *foo()*
+ ```rust
+ u32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_remove_markdown_if_configured() {
+ check_hover_no_markdown(
+ r#"
+pub fn foo() -> u32 { 1 }
+
+fn main() {
+ let foo_test = foo()$0;
+}
+"#,
+ expect![[r#"
+ *foo()*
+ u32
+ "#]],
+ );
+}
+
+#[test]
+fn hover_shows_long_type_of_an_expression() {
+ check(
+ r#"
+struct Scan<A, B, C> { a: A, b: B, c: C }
+struct Iter<I> { inner: I }
+enum Option<T> { Some(T), None }
+
+struct OtherStruct<T> { i: T }
+
+fn scan<A, B, C>(a: A, b: B, c: C) -> Iter<Scan<OtherStruct<A>, B, C>> {
+ Iter { inner: Scan { a, b, c } }
+}
+
+fn main() {
+ let num: i32 = 55;
+ let closure = |memo: &mut u32, value: &u32, _another: &mut u32| -> Option<u32> {
+ Option::Some(*memo + value)
+ };
+ let number = 5u32;
+ let mut iter$0 = scan(OtherStruct { i: num }, closure, number);
+}
+"#,
+ expect![[r#"
+ *iter*
+
+ ```rust
+ let mut iter: Iter<Scan<OtherStruct<OtherStruct<i32>>, |&mut u32, &u32, &mut u32| -> Option<u32>, u32>>
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_shows_fn_signature() {
+ // Single file with result
+ check(
+ r#"
+pub fn foo() -> u32 { 1 }
+
+fn main() { let foo_test = fo$0o(); }
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub fn foo() -> u32
+ ```
+ "#]],
+ );
+
+ // Multiple candidates but results are ambiguous.
+ check(
+ r#"
+//- /a.rs
+pub fn foo() -> u32 { 1 }
+
+//- /b.rs
+pub fn foo() -> &str { "" }
+
+//- /c.rs
+pub fn foo(a: u32, b: u32) {}
+
+//- /main.rs
+mod a;
+mod b;
+mod c;
+
+fn main() { let foo_test = fo$0o(); }
+ "#,
+ expect![[r#"
+ *foo*
+ ```rust
+ {unknown}
+ ```
+ "#]],
+ );
+
+ // Use literal `crate` in path
+ check(
+ r#"
+pub struct X;
+
+fn foo() -> crate::X { X }
+
+fn main() { f$0oo(); }
+ "#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn foo() -> crate::X
+ ```
+ "#]],
+ );
+
+ // Check `super` in path
+ check(
+ r#"
+pub struct X;
+
+mod m { pub fn foo() -> super::X { super::X } }
+
+fn main() { m::f$0oo(); }
+ "#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test::m
+ ```
+
+ ```rust
+ pub fn foo() -> super::X
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_shows_fn_signature_with_type_params() {
+ check(
+ r#"
+pub fn foo<'a, T: AsRef<str>>(b: &'a T) -> &'a str { }
+
+fn main() { let foo_test = fo$0o(); }
+ "#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub fn foo<'a, T>(b: &'a T) -> &'a str
+ where
+ T: AsRef<str>,
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_shows_fn_signature_on_fn_name() {
+ check(
+ r#"
+pub fn foo$0(a: u32, b: u32) -> u32 {}
+
+fn main() { }
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub fn foo(a: u32, b: u32) -> u32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_shows_fn_doc() {
+ check(
+ r#"
+/// # Example
+/// ```
+/// # use std::path::Path;
+/// #
+/// foo(Path::new("hello, world!"))
+/// ```
+pub fn foo$0(_: &Path) {}
+
+fn main() { }
+"#,
+ expect![[r##"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub fn foo(_: &Path)
+ ```
+
+ ---
+
+ # Example
+
+ ```
+ # use std::path::Path;
+ #
+ foo(Path::new("hello, world!"))
+ ```
+ "##]],
+ );
+}
+
+#[test]
+fn hover_shows_fn_doc_attr_raw_string() {
+ check(
+ r##"
+#[doc = r#"Raw string doc attr"#]
+pub fn foo$0(_: &Path) {}
+
+fn main() { }
+"##,
+ expect![[r##"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub fn foo(_: &Path)
+ ```
+
+ ---
+
+ Raw string doc attr
+ "##]],
+ );
+}
+
+#[test]
+fn hover_shows_struct_field_info() {
+ // Hovering over the field when instantiating
+ check(
+ r#"
+struct Foo { field_a: u32 }
+
+fn main() {
+ let foo = Foo { field_a$0: 0, };
+}
+"#,
+ expect![[r#"
+ *field_a*
+
+ ```rust
+ test::Foo
+ ```
+
+ ```rust
+ field_a: u32
+ ```
+ "#]],
+ );
+
+ // Hovering over the field in the definition
+ check(
+ r#"
+struct Foo { field_a$0: u32 }
+
+fn main() {
+ let foo = Foo { field_a: 0 };
+}
+"#,
+ expect![[r#"
+ *field_a*
+
+ ```rust
+ test::Foo
+ ```
+
+ ```rust
+ field_a: u32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_const_static() {
+ check(
+ r#"const foo$0: u32 = 123;"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const foo: u32
+ ```
+ "#]],
+ );
+ check(
+ r#"static foo$0: u32 = 456;"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ static foo: u32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_default_generic_types() {
+ check(
+ r#"
+struct Test<K, T = u8> { k: K, t: T }
+
+fn main() {
+ let zz$0 = Test { t: 23u8, k: 33 };
+}"#,
+ expect![[r#"
+ *zz*
+
+ ```rust
+ let zz: Test<i32, u8>
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_some() {
+ check(
+ r#"
+enum Option<T> { Some(T) }
+use Option::Some;
+
+fn main() { So$0me(12); }
+"#,
+ expect![[r#"
+ *Some*
+
+ ```rust
+ test::Option
+ ```
+
+ ```rust
+ Some(T)
+ ```
+ "#]],
+ );
+
+ check(
+ r#"
+enum Option<T> { Some(T) }
+use Option::Some;
+
+fn main() { let b$0ar = Some(12); }
+"#,
+ expect![[r#"
+ *bar*
+
+ ```rust
+ let bar: Option<i32>
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_enum_variant() {
+ check(
+ r#"
+enum Option<T> {
+ /// The None variant
+ Non$0e
+}
+"#,
+ expect![[r#"
+ *None*
+
+ ```rust
+ test::Option
+ ```
+
+ ```rust
+ None
+ ```
+
+ ---
+
+ The None variant
+ "#]],
+ );
+
+ check(
+ r#"
+enum Option<T> {
+ /// The Some variant
+ Some(T)
+}
+fn main() {
+ let s = Option::Som$0e(12);
+}
+"#,
+ expect![[r#"
+ *Some*
+
+ ```rust
+ test::Option
+ ```
+
+ ```rust
+ Some(T)
+ ```
+
+ ---
+
+ The Some variant
+ "#]],
+ );
+}
+
+#[test]
+fn hover_for_local_variable() {
+ check(
+ r#"fn func(foo: i32) { fo$0o; }"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ foo: i32
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn hover_for_local_variable_pat() {
+ check(
+ r#"fn func(fo$0o: i32) {}"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ foo: i32
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn hover_local_var_edge() {
+ check(
+ r#"fn func(foo: i32) { if true { $0foo; }; }"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ foo: i32
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn hover_for_param_edge() {
+ check(
+ r#"fn func($0foo: i32) {}"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ foo: i32
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn hover_for_param_with_multiple_traits() {
+ check(
+ r#"
+ //- minicore: sized
+ trait Deref {
+ type Target: ?Sized;
+ }
+ trait DerefMut {
+ type Target: ?Sized;
+ }
+ fn f(_x$0: impl Deref<Target=u8> + DerefMut<Target=u8>) {}"#,
+ expect![[r#"
+ *_x*
+
+ ```rust
+ _x: impl Deref<Target = u8> + DerefMut<Target = u8>
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn test_hover_infer_associated_method_result() {
+ check(
+ r#"
+struct Thing { x: u32 }
+
+impl Thing {
+ fn new() -> Thing { Thing { x: 0 } }
+}
+
+fn main() { let foo_$0test = Thing::new(); }
+"#,
+ expect![[r#"
+ *foo_test*
+
+ ```rust
+ let foo_test: Thing
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn test_hover_infer_associated_method_exact() {
+ check(
+ r#"
+mod wrapper {
+ struct Thing { x: u32 }
+
+ impl Thing {
+ fn new() -> Thing { Thing { x: 0 } }
+ }
+}
+
+fn main() { let foo_test = wrapper::Thing::new$0(); }
+"#,
+ expect![[r#"
+ *new*
+
+ ```rust
+ test::wrapper::Thing
+ ```
+
+ ```rust
+ fn new() -> Thing
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn test_hover_infer_associated_const_in_pattern() {
+ check(
+ r#"
+struct X;
+impl X {
+ const C: u32 = 1;
+}
+
+fn main() {
+ match 1 {
+ X::C$0 => {},
+ 2 => {},
+ _ => {}
+ };
+}
+"#,
+ expect![[r#"
+ *C*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const C: u32
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn test_hover_self() {
+ check(
+ r#"
+struct Thing { x: u32 }
+impl Thing {
+ fn new() -> Self { Self$0 { x: 0 } }
+}
+"#,
+ expect![[r#"
+ *Self*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ struct Thing
+ ```
+ "#]],
+ );
+ check(
+ r#"
+struct Thing { x: u32 }
+impl Thing {
+ fn new() -> Self$0 { Self { x: 0 } }
+}
+"#,
+ expect![[r#"
+ *Self*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ struct Thing
+ ```
+ "#]],
+ );
+ check(
+ r#"
+enum Thing { A }
+impl Thing {
+ pub fn new() -> Self$0 { Thing::A }
+}
+"#,
+ expect![[r#"
+ *Self*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ enum Thing
+ ```
+ "#]],
+ );
+ check(
+ r#"
+ enum Thing { A }
+ impl Thing {
+ pub fn thing(a: Self$0) {}
+ }
+ "#,
+ expect![[r#"
+ *Self*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ enum Thing
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_shadowing_pat() {
+ check(
+ r#"
+fn x() {}
+
+fn y() {
+ let x = 0i32;
+ x$0;
+}
+"#,
+ expect![[r#"
+ *x*
+
+ ```rust
+ let x: i32
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn test_hover_macro_invocation() {
+ check(
+ r#"
+macro_rules! foo { () => {} }
+
+fn f() { fo$0o!(); }
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ macro_rules! foo
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn test_hover_macro2_invocation() {
+ check(
+ r#"
+/// foo bar
+///
+/// foo bar baz
+macro foo() {}
+
+fn f() { fo$0o!(); }
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ macro foo
+ ```
+
+ ---
+
+ foo bar
+
+ foo bar baz
+ "#]],
+ )
+}
+
+#[test]
+fn test_hover_tuple_field() {
+ check(
+ r#"struct TS(String, i32$0);"#,
+ expect![[r#"
+ *i32*
+
+ ```rust
+ i32
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn test_hover_through_macro() {
+ check(
+ r#"
+macro_rules! id { ($($tt:tt)*) => { $($tt)* } }
+fn foo() {}
+id! {
+ fn bar() { fo$0o(); }
+}
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn foo()
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_through_attr() {
+ check(
+ r#"
+//- proc_macros: identity
+#[proc_macros::identity]
+fn foo$0() {}
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn foo()
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_through_expr_in_macro() {
+ check(
+ r#"
+macro_rules! id { ($($tt:tt)*) => { $($tt)* } }
+fn foo(bar:u32) { let a = id!(ba$0r); }
+"#,
+ expect![[r#"
+ *bar*
+
+ ```rust
+ bar: u32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_through_expr_in_macro_recursive() {
+ check(
+ r#"
+macro_rules! id_deep { ($($tt:tt)*) => { $($tt)* } }
+macro_rules! id { ($($tt:tt)*) => { id_deep!($($tt)*) } }
+fn foo(bar:u32) { let a = id!(ba$0r); }
+"#,
+ expect![[r#"
+ *bar*
+
+ ```rust
+ bar: u32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_through_func_in_macro_recursive() {
+ check(
+ r#"
+macro_rules! id_deep { ($($tt:tt)*) => { $($tt)* } }
+macro_rules! id { ($($tt:tt)*) => { id_deep!($($tt)*) } }
+fn bar() -> u32 { 0 }
+fn foo() { let a = id!([0u32, bar($0)] ); }
+"#,
+ expect![[r#"
+ *bar()*
+ ```rust
+ u32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_through_literal_string_in_macro() {
+ check(
+ r#"
+macro_rules! arr { ($($tt:tt)*) => { [$($tt)*)] } }
+fn foo() {
+ let mastered_for_itunes = "";
+ let _ = arr!("Tr$0acks", &mastered_for_itunes);
+}
+"#,
+ expect![[r#"
+ *"Tracks"*
+ ```rust
+ &str
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_through_assert_macro() {
+ check(
+ r#"
+#[rustc_builtin_macro]
+macro_rules! assert {}
+
+fn bar() -> bool { true }
+fn foo() {
+ assert!(ba$0r());
+}
+"#,
+ expect![[r#"
+ *bar*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn bar() -> bool
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_through_literal_string_in_builtin_macro() {
+ check_hover_no_result(
+ r#"
+ #[rustc_builtin_macro]
+ macro_rules! format {}
+
+ fn foo() {
+ format!("hel$0lo {}", 0);
+ }
+"#,
+ );
+}
+
+#[test]
+fn test_hover_non_ascii_space_doc() {
+ check(
+ "
+/// <- `\u{3000}` here
+fn foo() { }
+
+fn bar() { fo$0o(); }
+",
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn foo()
+ ```
+
+ ---
+
+ \<- ` ` here
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_function_show_qualifiers() {
+ check(
+ r#"async fn foo$0() {}"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ async fn foo()
+ ```
+ "#]],
+ );
+ check(
+ r#"pub const unsafe fn foo$0() {}"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub const unsafe fn foo()
+ ```
+ "#]],
+ );
+ // Top level `pub(crate)` will be displayed as no visibility.
+ check(
+ r#"mod m { pub(crate) async unsafe extern "C" fn foo$0() {} }"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test::m
+ ```
+
+ ```rust
+ pub(crate) async unsafe extern "C" fn foo()
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_trait_show_qualifiers() {
+ check_actions(
+ r"unsafe trait foo$0() {}",
+ expect![[r#"
+ [
+ Implementation(
+ FilePosition {
+ file_id: FileId(
+ 0,
+ ),
+ offset: 13,
+ },
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_extern_crate() {
+ check(
+ r#"
+//- /main.rs crate:main deps:std
+extern crate st$0d;
+//- /std/lib.rs crate:std
+//! Standard library for this test
+//!
+//! Printed?
+//! abc123
+"#,
+ expect![[r#"
+ *std*
+
+ ```rust
+ extern crate std
+ ```
+
+ ---
+
+ Standard library for this test
+
+ Printed?
+ abc123
+ "#]],
+ );
+ check(
+ r#"
+//- /main.rs crate:main deps:std
+extern crate std as ab$0c;
+//- /std/lib.rs crate:std
+//! Standard library for this test
+//!
+//! Printed?
+//! abc123
+"#,
+ expect![[r#"
+ *abc*
+
+ ```rust
+ extern crate std
+ ```
+
+ ---
+
+ Standard library for this test
+
+ Printed?
+ abc123
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_mod_with_same_name_as_function() {
+ check(
+ r#"
+use self::m$0y::Bar;
+mod my { pub struct Bar; }
+
+fn my() {}
+"#,
+ expect![[r#"
+ *my*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ mod my
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_struct_doc_comment() {
+ check(
+ r#"
+/// This is an example
+/// multiline doc
+///
+/// # Example
+///
+/// ```
+/// let five = 5;
+///
+/// assert_eq!(6, my_crate::add_one(5));
+/// ```
+struct Bar;
+
+fn foo() { let bar = Ba$0r; }
+"#,
+ expect![[r##"
+ *Bar*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ struct Bar
+ ```
+
+ ---
+
+ This is an example
+ multiline doc
+
+ # Example
+
+ ```
+ let five = 5;
+
+ assert_eq!(6, my_crate::add_one(5));
+ ```
+ "##]],
+ );
+}
+
+#[test]
+fn test_hover_struct_doc_attr() {
+ check(
+ r#"
+#[doc = "bar docs"]
+struct Bar;
+
+fn foo() { let bar = Ba$0r; }
+"#,
+ expect![[r#"
+ *Bar*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ struct Bar
+ ```
+
+ ---
+
+ bar docs
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_struct_doc_attr_multiple_and_mixed() {
+ check(
+ r#"
+/// bar docs 0
+#[doc = "bar docs 1"]
+#[doc = "bar docs 2"]
+struct Bar;
+
+fn foo() { let bar = Ba$0r; }
+"#,
+ expect![[r#"
+ *Bar*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ struct Bar
+ ```
+
+ ---
+
+ bar docs 0
+ bar docs 1
+ bar docs 2
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_external_url() {
+ check(
+ r#"
+pub struct Foo;
+/// [external](https://www.google.com)
+pub struct B$0ar
+"#,
+ expect![[r#"
+ *Bar*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub struct Bar
+ ```
+
+ ---
+
+ [external](https://www.google.com)
+ "#]],
+ );
+}
+
+// Check that we don't rewrite links which we can't identify
+#[test]
+fn test_hover_unknown_target() {
+ check(
+ r#"
+pub struct Foo;
+/// [baz](Baz)
+pub struct B$0ar
+"#,
+ expect![[r#"
+ *Bar*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub struct Bar
+ ```
+
+ ---
+
+ [baz](Baz)
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_no_links() {
+ check_hover_no_links(
+ r#"
+/// Test cases:
+/// case 1. bare URL: https://www.example.com/
+/// case 2. inline URL with title: [example](https://www.example.com/)
+/// case 3. code reference: [`Result`]
+/// case 4. code reference but miss footnote: [`String`]
+/// case 5. autolink: <http://www.example.com/>
+/// case 6. email address: <test@example.com>
+/// case 7. reference: [example][example]
+/// case 8. collapsed link: [example][]
+/// case 9. shortcut link: [example]
+/// case 10. inline without URL: [example]()
+/// case 11. reference: [foo][foo]
+/// case 12. reference: [foo][bar]
+/// case 13. collapsed link: [foo][]
+/// case 14. shortcut link: [foo]
+/// case 15. inline without URL: [foo]()
+/// case 16. just escaped text: \[foo]
+/// case 17. inline link: [Foo](foo::Foo)
+///
+/// [`Result`]: ../../std/result/enum.Result.html
+/// [^example]: https://www.example.com/
+pub fn fo$0o() {}
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub fn foo()
+ ```
+
+ ---
+
+ Test cases:
+ case 1. bare URL: https://www.example.com/
+ case 2. inline URL with title: [example](https://www.example.com/)
+ case 3. code reference: `Result`
+ case 4. code reference but miss footnote: `String`
+ case 5. autolink: http://www.example.com/
+ case 6. email address: test@example.com
+ case 7. reference: example
+ case 8. collapsed link: example
+ case 9. shortcut link: example
+ case 10. inline without URL: example
+ case 11. reference: foo
+ case 12. reference: foo
+ case 13. collapsed link: foo
+ case 14. shortcut link: foo
+ case 15. inline without URL: foo
+ case 16. just escaped text: \[foo\]
+ case 17. inline link: Foo
+
+ [^example]: https://www.example.com/
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_macro_generated_struct_fn_doc_comment() {
+ cov_mark::check!(hover_macro_generated_struct_fn_doc_comment);
+
+ check(
+ r#"
+macro_rules! bar {
+ () => {
+ struct Bar;
+ impl Bar {
+ /// Do the foo
+ fn foo(&self) {}
+ }
+ }
+}
+
+bar!();
+
+fn foo() { let bar = Bar; bar.fo$0o(); }
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test::Bar
+ ```
+
+ ```rust
+ fn foo(&self)
+ ```
+
+ ---
+
+ Do the foo
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_macro_generated_struct_fn_doc_attr() {
+ cov_mark::check!(hover_macro_generated_struct_fn_doc_attr);
+
+ check(
+ r#"
+macro_rules! bar {
+ () => {
+ struct Bar;
+ impl Bar {
+ #[doc = "Do the foo"]
+ fn foo(&self) {}
+ }
+ }
+}
+
+bar!();
+
+fn foo() { let bar = Bar; bar.fo$0o(); }
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test::Bar
+ ```
+
+ ```rust
+ fn foo(&self)
+ ```
+
+ ---
+
+ Do the foo
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_trait_has_impl_action() {
+ check_actions(
+ r#"trait foo$0() {}"#,
+ expect![[r#"
+ [
+ Implementation(
+ FilePosition {
+ file_id: FileId(
+ 0,
+ ),
+ offset: 6,
+ },
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_struct_has_impl_action() {
+ check_actions(
+ r"struct foo$0() {}",
+ expect![[r#"
+ [
+ Implementation(
+ FilePosition {
+ file_id: FileId(
+ 0,
+ ),
+ offset: 7,
+ },
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_union_has_impl_action() {
+ check_actions(
+ r#"union foo$0() {}"#,
+ expect![[r#"
+ [
+ Implementation(
+ FilePosition {
+ file_id: FileId(
+ 0,
+ ),
+ offset: 6,
+ },
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_enum_has_impl_action() {
+ check_actions(
+ r"enum foo$0() { A, B }",
+ expect![[r#"
+ [
+ Implementation(
+ FilePosition {
+ file_id: FileId(
+ 0,
+ ),
+ offset: 5,
+ },
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_self_has_impl_action() {
+ check_actions(
+ r#"struct foo where Self$0:;"#,
+ expect![[r#"
+ [
+ Implementation(
+ FilePosition {
+ file_id: FileId(
+ 0,
+ ),
+ offset: 7,
+ },
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_test_has_action() {
+ check_actions(
+ r#"
+#[test]
+fn foo_$0test() {}
+"#,
+ expect![[r#"
+ [
+ Reference(
+ FilePosition {
+ file_id: FileId(
+ 0,
+ ),
+ offset: 11,
+ },
+ ),
+ Runnable(
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..24,
+ focus_range: 11..19,
+ name: "foo_test",
+ kind: Function,
+ },
+ kind: Test {
+ test_id: Path(
+ "foo_test",
+ ),
+ attr: TestAttr {
+ ignore: false,
+ },
+ },
+ cfg: None,
+ },
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_test_mod_has_action() {
+ check_actions(
+ r#"
+mod tests$0 {
+ #[test]
+ fn foo_test() {}
+}
+"#,
+ expect![[r#"
+ [
+ Runnable(
+ Runnable {
+ use_name_in_title: false,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..46,
+ focus_range: 4..9,
+ name: "tests",
+ kind: Module,
+ description: "mod tests",
+ },
+ kind: TestMod {
+ path: "tests",
+ },
+ cfg: None,
+ },
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_struct_has_goto_type_action() {
+ check_actions(
+ r#"
+struct S{ f1: u32 }
+
+fn main() { let s$0t = S{ f1:0 }; }
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::S",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..19,
+ focus_range: 7..8,
+ name: "S",
+ kind: Struct,
+ description: "struct S",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_generic_struct_has_goto_type_actions() {
+ check_actions(
+ r#"
+struct Arg(u32);
+struct S<T>{ f1: T }
+
+fn main() { let s$0t = S{ f1:Arg(0) }; }
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::S",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 17..37,
+ focus_range: 24..25,
+ name: "S",
+ kind: Struct,
+ description: "struct S<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::Arg",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..16,
+ focus_range: 7..10,
+ name: "Arg",
+ kind: Struct,
+ description: "struct Arg",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_generic_struct_has_flattened_goto_type_actions() {
+ check_actions(
+ r#"
+struct Arg(u32);
+struct S<T>{ f1: T }
+
+fn main() { let s$0t = S{ f1: S{ f1: Arg(0) } }; }
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::S",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 17..37,
+ focus_range: 24..25,
+ name: "S",
+ kind: Struct,
+ description: "struct S<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::Arg",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..16,
+ focus_range: 7..10,
+ name: "Arg",
+ kind: Struct,
+ description: "struct Arg",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_tuple_has_goto_type_actions() {
+ check_actions(
+ r#"
+struct A(u32);
+struct B(u32);
+mod M {
+ pub struct C(u32);
+}
+
+fn main() { let s$0t = (A(1), B(2), M::C(3) ); }
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::A",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..14,
+ focus_range: 7..8,
+ name: "A",
+ kind: Struct,
+ description: "struct A",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::B",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 15..29,
+ focus_range: 22..23,
+ name: "B",
+ kind: Struct,
+ description: "struct B",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::M::C",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 42..60,
+ focus_range: 53..54,
+ name: "C",
+ kind: Struct,
+ description: "pub struct C",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_return_impl_trait_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo {}
+fn foo() -> impl Foo {}
+
+fn main() { let s$0t = foo(); }
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..12,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_generic_return_impl_trait_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo<T> {}
+struct S;
+fn foo() -> impl Foo<S> {}
+
+fn main() { let s$0t = foo(); }
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..15,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::S",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 16..25,
+ focus_range: 23..24,
+ name: "S",
+ kind: Struct,
+ description: "struct S",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_return_impl_traits_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo {}
+trait Bar {}
+fn foo() -> impl Foo + Bar {}
+
+fn main() { let s$0t = foo(); }
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..12,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::Bar",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 13..25,
+ focus_range: 19..22,
+ name: "Bar",
+ kind: Trait,
+ description: "trait Bar",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_generic_return_impl_traits_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo<T> {}
+trait Bar<T> {}
+struct S1 {}
+struct S2 {}
+
+fn foo() -> impl Foo<S1> + Bar<S2> {}
+
+fn main() { let s$0t = foo(); }
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..15,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::Bar",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 16..31,
+ focus_range: 22..25,
+ name: "Bar",
+ kind: Trait,
+ description: "trait Bar<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::S1",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 32..44,
+ focus_range: 39..41,
+ name: "S1",
+ kind: Struct,
+ description: "struct S1",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::S2",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 45..57,
+ focus_range: 52..54,
+ name: "S2",
+ kind: Struct,
+ description: "struct S2",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_arg_impl_trait_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo {}
+fn foo(ar$0g: &impl Foo) {}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..12,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_arg_impl_traits_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo {}
+trait Bar<T> {}
+struct S{}
+
+fn foo(ar$0g: &impl Foo + Bar<S>) {}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..12,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::Bar",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 13..28,
+ focus_range: 19..22,
+ name: "Bar",
+ kind: Trait,
+ description: "trait Bar<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::S",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 29..39,
+ focus_range: 36..37,
+ name: "S",
+ kind: Struct,
+ description: "struct S",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_async_block_impl_trait_has_goto_type_action() {
+ check_actions(
+ r#"
+//- minicore: future
+struct S;
+fn foo() {
+ let fo$0o = async { S };
+}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "core::future::Future",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 1,
+ ),
+ full_range: 254..436,
+ focus_range: 293..299,
+ name: "Future",
+ kind: Trait,
+ description: "pub trait Future",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::S",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..9,
+ focus_range: 7..8,
+ name: "S",
+ kind: Struct,
+ description: "struct S",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_arg_generic_impl_trait_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo<T> {}
+struct S {}
+fn foo(ar$0g: &impl Foo<S>) {}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..15,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::S",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 16..27,
+ focus_range: 23..24,
+ name: "S",
+ kind: Struct,
+ description: "struct S",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_dyn_return_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo {}
+struct S;
+impl Foo for S {}
+
+struct B<T>{}
+fn foo() -> B<dyn Foo> {}
+
+fn main() { let s$0t = foo(); }
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::B",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 42..55,
+ focus_range: 49..50,
+ name: "B",
+ kind: Struct,
+ description: "struct B<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..12,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_dyn_arg_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo {}
+fn foo(ar$0g: &dyn Foo) {}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..12,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_generic_dyn_arg_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo<T> {}
+struct S {}
+fn foo(ar$0g: &dyn Foo<S>) {}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..15,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::S",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 16..27,
+ focus_range: 23..24,
+ name: "S",
+ kind: Struct,
+ description: "struct S",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_goto_type_action_links_order() {
+ check_actions(
+ r#"
+trait ImplTrait<T> {}
+trait DynTrait<T> {}
+struct B<T> {}
+struct S {}
+
+fn foo(a$0rg: &impl ImplTrait<B<dyn DynTrait<B<S>>>>) {}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::ImplTrait",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..21,
+ focus_range: 6..15,
+ name: "ImplTrait",
+ kind: Trait,
+ description: "trait ImplTrait<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::B",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 43..57,
+ focus_range: 50..51,
+ name: "B",
+ kind: Struct,
+ description: "struct B<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::DynTrait",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 22..42,
+ focus_range: 28..36,
+ name: "DynTrait",
+ kind: Trait,
+ description: "trait DynTrait<T>",
+ },
+ },
+ HoverGotoTypeData {
+ mod_path: "test::S",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 58..69,
+ focus_range: 65..66,
+ name: "S",
+ kind: Struct,
+ description: "struct S",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_associated_type_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo {
+ type Item;
+ fn get(self) -> Self::Item {}
+}
+
+struct Bar{}
+struct S{}
+
+impl Foo for S { type Item = Bar; }
+
+fn test() -> impl Foo { S {} }
+
+fn main() { let s$0t = test().get(); }
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..62,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_const_param_has_goto_type_action() {
+ check_actions(
+ r#"
+struct Bar;
+struct Foo<const BAR: Bar>;
+
+impl<const BAR: Bar> Foo<BAR$0> {}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Bar",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..11,
+ focus_range: 7..10,
+ name: "Bar",
+ kind: Struct,
+ description: "struct Bar",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_type_param_has_goto_type_action() {
+ check_actions(
+ r#"
+trait Foo {}
+
+fn foo<T: Foo>(t: T$0){}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..12,
+ focus_range: 6..9,
+ name: "Foo",
+ kind: Trait,
+ description: "trait Foo",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn test_hover_self_has_go_to_type() {
+ check_actions(
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(&self$0) {}
+}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..11,
+ focus_range: 7..10,
+ name: "Foo",
+ kind: Struct,
+ description: "struct Foo",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn hover_displays_normalized_crate_names() {
+ check(
+ r#"
+//- /lib.rs crate:name-with-dashes
+pub mod wrapper {
+ pub struct Thing { x: u32 }
+
+ impl Thing {
+ pub fn new() -> Thing { Thing { x: 0 } }
+ }
+}
+
+//- /main.rs crate:main deps:name-with-dashes
+fn main() { let foo_test = name_with_dashes::wrapper::Thing::new$0(); }
+"#,
+ expect![[r#"
+ *new*
+
+ ```rust
+ name_with_dashes::wrapper::Thing
+ ```
+
+ ```rust
+ pub fn new() -> Thing
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn hover_field_pat_shorthand_ref_match_ergonomics() {
+ check(
+ r#"
+struct S {
+ f: i32,
+}
+
+fn main() {
+ let s = S { f: 0 };
+ let S { f$0 } = &s;
+}
+"#,
+ expect![[r#"
+ *f*
+
+ ```rust
+ f: &i32
+ ```
+ ---
+
+ ```rust
+ test::S
+ ```
+
+ ```rust
+ f: i32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_self_param_shows_type() {
+ check(
+ r#"
+struct Foo {}
+impl Foo {
+ fn bar(&sel$0f) {}
+}
+"#,
+ expect![[r#"
+ *self*
+
+ ```rust
+ self: &Foo
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_self_param_shows_type_for_arbitrary_self_type() {
+ check(
+ r#"
+struct Arc<T>(T);
+struct Foo {}
+impl Foo {
+ fn bar(sel$0f: Arc<Foo>) {}
+}
+"#,
+ expect![[r#"
+ *self*
+
+ ```rust
+ self: Arc<Foo>
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_doc_outer_inner() {
+ check(
+ r#"
+/// Be quick;
+mod Foo$0 {
+ //! time is mana
+
+ /// This comment belongs to the function
+ fn foo() {}
+}
+"#,
+ expect![[r#"
+ *Foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ mod Foo
+ ```
+
+ ---
+
+ Be quick;
+ time is mana
+ "#]],
+ );
+}
+
+#[test]
+fn hover_doc_outer_inner_attribue() {
+ check(
+ r#"
+#[doc = "Be quick;"]
+mod Foo$0 {
+ #![doc = "time is mana"]
+
+ #[doc = "This comment belongs to the function"]
+ fn foo() {}
+}
+"#,
+ expect![[r#"
+ *Foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ mod Foo
+ ```
+
+ ---
+
+ Be quick;
+ time is mana
+ "#]],
+ );
+}
+
+#[test]
+fn hover_doc_block_style_indentend() {
+ check(
+ r#"
+/**
+ foo
+ ```rust
+ let x = 3;
+ ```
+*/
+fn foo$0() {}
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn foo()
+ ```
+
+ ---
+
+ foo
+
+ ```rust
+ let x = 3;
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_comments_dont_highlight_parent() {
+ cov_mark::check!(no_highlight_on_comment_hover);
+ check_hover_no_result(
+ r#"
+fn no_hover() {
+ // no$0hover
+}
+"#,
+ );
+}
+
+#[test]
+fn hover_label() {
+ check(
+ r#"
+fn foo() {
+ 'label$0: loop {}
+}
+"#,
+ expect![[r#"
+ *'label*
+
+ ```rust
+ 'label
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_lifetime() {
+ check(
+ r#"fn foo<'lifetime>(_: &'lifetime$0 ()) {}"#,
+ expect![[r#"
+ *'lifetime*
+
+ ```rust
+ 'lifetime
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_type_param() {
+ check(
+ r#"
+//- minicore: sized
+struct Foo<T>(T);
+trait TraitA {}
+trait TraitB {}
+impl<T: TraitA + TraitB> Foo<T$0> where T: Sized {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T: TraitA + TraitB
+ ```
+ "#]],
+ );
+ check(
+ r#"
+//- minicore: sized
+struct Foo<T>(T);
+impl<T> Foo<T$0> {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T
+ ```
+ "#]],
+ );
+ // lifetimes bounds arent being tracked yet
+ check(
+ r#"
+//- minicore: sized
+struct Foo<T>(T);
+impl<T: 'static> Foo<T$0> {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_type_param_sized_bounds() {
+ // implicit `: Sized` bound
+ check(
+ r#"
+//- minicore: sized
+trait Trait {}
+struct Foo<T>(T);
+impl<T: Trait> Foo<T$0> {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T: Trait
+ ```
+ "#]],
+ );
+ check(
+ r#"
+//- minicore: sized
+trait Trait {}
+struct Foo<T>(T);
+impl<T: Trait + ?Sized> Foo<T$0> {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T: Trait + ?Sized
+ ```
+ "#]],
+ );
+}
+
+mod type_param_sized_bounds {
+ use super::*;
+
+ #[test]
+ fn single_implicit() {
+ check(
+ r#"
+//- minicore: sized
+fn foo<T$0>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T
+ ```
+ "#]],
+ );
+ }
+
+ #[test]
+ fn single_explicit() {
+ check(
+ r#"
+//- minicore: sized
+fn foo<T$0: Sized>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T
+ ```
+ "#]],
+ );
+ }
+
+ #[test]
+ fn single_relaxed() {
+ check(
+ r#"
+//- minicore: sized
+fn foo<T$0: ?Sized>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T: ?Sized
+ ```
+ "#]],
+ );
+ }
+
+ #[test]
+ fn multiple_implicit() {
+ check(
+ r#"
+//- minicore: sized
+trait Trait {}
+fn foo<T$0: Trait>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T: Trait
+ ```
+ "#]],
+ );
+ }
+
+ #[test]
+ fn multiple_explicit() {
+ check(
+ r#"
+//- minicore: sized
+trait Trait {}
+fn foo<T$0: Trait + Sized>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T: Trait
+ ```
+ "#]],
+ );
+ }
+
+ #[test]
+ fn multiple_relaxed() {
+ check(
+ r#"
+//- minicore: sized
+trait Trait {}
+fn foo<T$0: Trait + ?Sized>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T: Trait + ?Sized
+ ```
+ "#]],
+ );
+ }
+
+ #[test]
+ fn mixed() {
+ check(
+ r#"
+//- minicore: sized
+fn foo<T$0: ?Sized + Sized + Sized>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T
+ ```
+ "#]],
+ );
+ check(
+ r#"
+//- minicore: sized
+trait Trait {}
+fn foo<T$0: Sized + ?Sized + Sized + Trait>() {}
+"#,
+ expect![[r#"
+ *T*
+
+ ```rust
+ T: Trait
+ ```
+ "#]],
+ );
+ }
+}
+
+#[test]
+fn hover_const_param() {
+ check(
+ r#"
+struct Foo<const LEN: usize>;
+impl<const LEN: usize> Foo<LEN$0> {}
+"#,
+ expect![[r#"
+ *LEN*
+
+ ```rust
+ const LEN: usize
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_const_pat() {
+ check(
+ r#"
+/// This is a doc
+const FOO: usize = 3;
+fn foo() {
+ match 5 {
+ FOO$0 => (),
+ _ => ()
+ }
+}
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: usize
+ ```
+
+ ---
+
+ This is a doc
+ "#]],
+ );
+}
+
+#[test]
+fn hover_mod_def() {
+ check(
+ r#"
+//- /main.rs
+mod foo$0;
+//- /foo.rs
+//! For the horde!
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ mod foo
+ ```
+
+ ---
+
+ For the horde!
+ "#]],
+ );
+}
+
+#[test]
+fn hover_self_in_use() {
+ check(
+ r#"
+//! This should not appear
+mod foo {
+ /// But this should appear
+ pub mod bar {}
+}
+use foo::bar::{self$0};
+"#,
+ expect![[r#"
+ *self*
+
+ ```rust
+ test::foo
+ ```
+
+ ```rust
+ mod bar
+ ```
+
+ ---
+
+ But this should appear
+ "#]],
+ )
+}
+
+#[test]
+fn hover_keyword() {
+ check(
+ r#"
+//- /main.rs crate:main deps:std
+fn f() { retur$0n; }
+//- /libstd.rs crate:std
+/// Docs for return_keyword
+mod return_keyword {}
+"#,
+ expect![[r#"
+ *return*
+
+ ```rust
+ return
+ ```
+
+ ---
+
+ Docs for return_keyword
+ "#]],
+ );
+}
+
+#[test]
+fn hover_builtin() {
+ check(
+ r#"
+//- /main.rs crate:main deps:std
+cosnt _: &str$0 = ""; }
+
+//- /libstd.rs crate:std
+/// Docs for prim_str
+mod prim_str {}
+"#,
+ expect![[r#"
+ *str*
+
+ ```rust
+ str
+ ```
+
+ ---
+
+ Docs for prim_str
+ "#]],
+ );
+}
+
+#[test]
+fn hover_macro_expanded_function() {
+ check(
+ r#"
+struct S<'a, T>(&'a T);
+trait Clone {}
+macro_rules! foo {
+ () => {
+ fn bar<'t, T: Clone + 't>(s: &mut S<'t, T>, t: u32) -> *mut u32 where
+ 't: 't + 't,
+ for<'a> T: Clone + 'a
+ { 0 as _ }
+ };
+}
+
+foo!();
+
+fn main() {
+ bar$0;
+}
+"#,
+ expect![[r#"
+ *bar*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn bar<'t, T>(s: &mut S<'t, T>, t: u32) -> *mut u32
+ where
+ T: Clone + 't,
+ 't: 't + 't,
+ for<'a> T: Clone + 'a,
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn hover_intra_doc_links() {
+ check(
+ r#"
+
+pub mod theitem {
+ /// This is the item. Cool!
+ pub struct TheItem;
+}
+
+/// Gives you a [`TheItem$0`].
+///
+/// [`TheItem`]: theitem::TheItem
+pub fn gimme() -> theitem::TheItem {
+ theitem::TheItem
+}
+"#,
+ expect![[r#"
+ *[`TheItem`]*
+
+ ```rust
+ test::theitem
+ ```
+
+ ```rust
+ pub struct TheItem
+ ```
+
+ ---
+
+ This is the item. Cool!
+ "#]],
+ );
+}
+
+#[test]
+fn hover_generic_assoc() {
+ check(
+ r#"
+fn foo<T: A>() where T::Assoc$0: {}
+
+trait A {
+ type Assoc;
+}"#,
+ expect![[r#"
+ *Assoc*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ type Assoc
+ ```
+ "#]],
+ );
+ check(
+ r#"
+fn foo<T: A>() {
+ let _: <T>::Assoc$0;
+}
+
+trait A {
+ type Assoc;
+}"#,
+ expect![[r#"
+ *Assoc*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ type Assoc
+ ```
+ "#]],
+ );
+ check(
+ r#"
+trait A where
+ Self::Assoc$0: ,
+{
+ type Assoc;
+}"#,
+ expect![[r#"
+ *Assoc*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ type Assoc
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn string_shadowed_with_inner_items() {
+ check(
+ r#"
+//- /main.rs crate:main deps:alloc
+
+/// Custom `String` type.
+struct String;
+
+fn f() {
+ let _: String$0;
+
+ fn inner() {}
+}
+
+//- /alloc.rs crate:alloc
+#[prelude_import]
+pub use string::*;
+
+mod string {
+ /// This is `alloc::String`.
+ pub struct String;
+}
+"#,
+ expect![[r#"
+ *String*
+
+ ```rust
+ main
+ ```
+
+ ```rust
+ struct String
+ ```
+
+ ---
+
+ Custom `String` type.
+ "#]],
+ )
+}
+
+#[test]
+fn function_doesnt_shadow_crate_in_use_tree() {
+ check(
+ r#"
+//- /main.rs crate:main deps:foo
+use foo$0::{foo};
+
+//- /foo.rs crate:foo
+pub fn foo() {}
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ extern crate foo
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn hover_feature() {
+ check(
+ r#"#![feature(box_syntax$0)]"#,
+ expect![[r##"
+ *box_syntax*
+ ```
+ box_syntax
+ ```
+ ___
+
+ # `box_syntax`
+
+ The tracking issue for this feature is: [#49733]
+
+ [#49733]: https://github.com/rust-lang/rust/issues/49733
+
+ See also [`box_patterns`](box-patterns.md)
+
+ ------------------------
+
+ Currently the only stable way to create a `Box` is via the `Box::new` method.
+ Also it is not possible in stable Rust to destructure a `Box` in a match
+ pattern. The unstable `box` keyword can be used to create a `Box`. An example
+ usage would be:
+
+ ```rust
+ #![feature(box_syntax)]
+
+ fn main() {
+ let b = box 5;
+ }
+ ```
+
+ "##]],
+ )
+}
+
+#[test]
+fn hover_lint() {
+ check(
+ r#"#![allow(arithmetic_overflow$0)]"#,
+ expect![[r#"
+ *arithmetic_overflow*
+ ```
+ arithmetic_overflow
+ ```
+ ___
+
+ arithmetic operation overflows
+ "#]],
+ )
+}
+
+#[test]
+fn hover_clippy_lint() {
+ check(
+ r#"#![allow(clippy::almost_swapped$0)]"#,
+ expect![[r#"
+ *almost_swapped*
+ ```
+ clippy::almost_swapped
+ ```
+ ___
+
+ Checks for `foo = bar; bar = foo` sequences.
+ "#]],
+ )
+}
+
+#[test]
+fn hover_attr_path_qualifier() {
+ cov_mark::check!(name_ref_classify_attr_path_qualifier);
+ check(
+ r#"
+//- /foo.rs crate:foo
+
+//- /lib.rs crate:main.rs deps:foo
+#[fo$0o::bar()]
+struct Foo;
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ extern crate foo
+ ```
+ "#]],
+ )
+}
+
+#[test]
+fn hover_rename() {
+ check(
+ r#"
+use self as foo$0;
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ extern crate test
+ ```
+ "#]],
+ );
+ check(
+ r#"
+mod bar {}
+use bar::{self as foo$0};
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ mod bar
+ ```
+ "#]],
+ );
+ check(
+ r#"
+mod bar {
+ use super as foo$0;
+}
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ extern crate test
+ ```
+ "#]],
+ );
+ check(
+ r#"
+use crate as foo$0;
+"#,
+ expect![[r#"
+ *foo*
+
+ ```rust
+ extern crate test
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_attribute_in_macro() {
+ check(
+ r#"
+macro_rules! identity {
+ ($struct:item) => {
+ $struct
+ };
+}
+#[rustc_builtin_macro]
+pub macro Copy {}
+identity!{
+ #[derive(Copy$0)]
+ struct Foo;
+}
+"#,
+ expect![[r#"
+ *Copy*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub macro Copy
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_derive_input() {
+ check(
+ r#"
+#[rustc_builtin_macro]
+pub macro Copy {}
+#[derive(Copy$0)]
+struct Foo;
+"#,
+ expect![[r#"
+ *Copy*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub macro Copy
+ ```
+ "#]],
+ );
+ check(
+ r#"
+mod foo {
+ #[rustc_builtin_macro]
+ pub macro Copy {}
+}
+#[derive(foo::Copy$0)]
+struct Foo;
+"#,
+ expect![[r#"
+ *Copy*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub macro Copy
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_range_math() {
+ check_hover_range(
+ r#"
+fn f() { let expr = $01 + 2 * 3$0 }
+"#,
+ expect![[r#"
+ ```rust
+ i32
+ ```"#]],
+ );
+
+ check_hover_range(
+ r#"
+fn f() { let expr = 1 $0+ 2 * $03 }
+"#,
+ expect![[r#"
+ ```rust
+ i32
+ ```"#]],
+ );
+
+ check_hover_range(
+ r#"
+fn f() { let expr = 1 + $02 * 3$0 }
+"#,
+ expect![[r#"
+ ```rust
+ i32
+ ```"#]],
+ );
+}
+
+#[test]
+fn hover_range_arrays() {
+ check_hover_range(
+ r#"
+fn f() { let expr = $0[1, 2, 3, 4]$0 }
+"#,
+ expect![[r#"
+ ```rust
+ [i32; 4]
+ ```"#]],
+ );
+
+ check_hover_range(
+ r#"
+fn f() { let expr = [1, 2, $03, 4]$0 }
+"#,
+ expect![[r#"
+ ```rust
+ [i32; 4]
+ ```"#]],
+ );
+
+ check_hover_range(
+ r#"
+fn f() { let expr = [1, 2, $03$0, 4] }
+"#,
+ expect![[r#"
+ ```rust
+ i32
+ ```"#]],
+ );
+}
+
+#[test]
+fn hover_range_functions() {
+ check_hover_range(
+ r#"
+fn f<T>(a: &[T]) { }
+fn b() { $0f$0(&[1, 2, 3, 4, 5]); }
+"#,
+ expect![[r#"
+ ```rust
+ fn f<i32>(&[i32])
+ ```"#]],
+ );
+
+ check_hover_range(
+ r#"
+fn f<T>(a: &[T]) { }
+fn b() { f($0&[1, 2, 3, 4, 5]$0); }
+"#,
+ expect![[r#"
+ ```rust
+ &[i32; 5]
+ ```"#]],
+ );
+}
+
+#[test]
+fn hover_range_shows_nothing_when_invalid() {
+ check_hover_range_no_results(
+ r#"
+fn f<T>(a: &[T]) { }
+fn b()$0 { f(&[1, 2, 3, 4, 5]); }$0
+"#,
+ );
+
+ check_hover_range_no_results(
+ r#"
+fn f<T>$0(a: &[T]) { }
+fn b() { f(&[1, 2, 3,$0 4, 5]); }
+"#,
+ );
+
+ check_hover_range_no_results(
+ r#"
+fn $0f() { let expr = [1, 2, 3, 4]$0 }
+"#,
+ );
+}
+
+#[test]
+fn hover_range_shows_unit_for_statements() {
+ check_hover_range(
+ r#"
+fn f<T>(a: &[T]) { }
+fn b() { $0f(&[1, 2, 3, 4, 5]); }$0
+"#,
+ expect![[r#"
+ ```rust
+ ()
+ ```"#]],
+ );
+
+ check_hover_range(
+ r#"
+fn f() { let expr$0 = $0[1, 2, 3, 4] }
+"#,
+ expect![[r#"
+ ```rust
+ ()
+ ```"#]],
+ );
+}
+
+#[test]
+fn hover_range_for_pat() {
+ check_hover_range(
+ r#"
+fn foo() {
+ let $0x$0 = 0;
+}
+"#,
+ expect![[r#"
+ ```rust
+ i32
+ ```"#]],
+ );
+
+ check_hover_range(
+ r#"
+fn foo() {
+ let $0x$0 = "";
+}
+"#,
+ expect![[r#"
+ ```rust
+ &str
+ ```"#]],
+ );
+}
+
+#[test]
+fn hover_range_shows_coercions_if_applicable_expr() {
+ check_hover_range(
+ r#"
+fn foo() {
+ let x: &u32 = $0&&&&&0$0;
+}
+"#,
+ expect![[r#"
+ ```text
+ Type: &&&&&u32
+ Coerced to: &u32
+ ```
+ "#]],
+ );
+ check_hover_range(
+ r#"
+fn foo() {
+ let x: *const u32 = $0&0$0;
+}
+"#,
+ expect![[r#"
+ ```text
+ Type: &u32
+ Coerced to: *const u32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_range_shows_type_actions() {
+ check_actions(
+ r#"
+struct Foo;
+fn foo() {
+ let x: &Foo = $0&&&&&Foo$0;
+}
+"#,
+ expect![[r#"
+ [
+ GoToType(
+ [
+ HoverGotoTypeData {
+ mod_path: "test::Foo",
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 0..11,
+ focus_range: 7..10,
+ name: "Foo",
+ kind: Struct,
+ description: "struct Foo",
+ },
+ },
+ ],
+ ),
+ ]
+ "#]],
+ );
+}
+
+#[test]
+fn hover_try_expr_res() {
+ check_hover_range(
+ r#"
+//- minicore:result
+struct FooError;
+
+fn foo() -> Result<(), FooError> {
+ Ok($0Result::<(), FooError>::Ok(())?$0)
+}
+"#,
+ expect![[r#"
+ ```rust
+ ()
+ ```"#]],
+ );
+ check_hover_range(
+ r#"
+//- minicore:result
+struct FooError;
+struct BarError;
+
+fn foo() -> Result<(), FooError> {
+ Ok($0Result::<(), BarError>::Ok(())?$0)
+}
+"#,
+ expect![[r#"
+ ```text
+ Try Error Type: BarError
+ Propagated as: FooError
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_try_expr() {
+ check_hover_range(
+ r#"
+struct NotResult<T, U>(T, U);
+struct Short;
+struct Looooong;
+
+fn foo() -> NotResult<(), Looooong> {
+ $0NotResult((), Short)?$0;
+}
+"#,
+ expect![[r#"
+ ```text
+ Try Target Type: NotResult<(), Short>
+ Propagated as: NotResult<(), Looooong>
+ ```
+ "#]],
+ );
+ check_hover_range(
+ r#"
+struct NotResult<T, U>(T, U);
+struct Short;
+struct Looooong;
+
+fn foo() -> NotResult<(), Short> {
+ $0NotResult((), Looooong)?$0;
+}
+"#,
+ expect![[r#"
+ ```text
+ Try Target Type: NotResult<(), Looooong>
+ Propagated as: NotResult<(), Short>
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_try_expr_option() {
+ cov_mark::check!(hover_try_expr_opt_opt);
+ check_hover_range(
+ r#"
+//- minicore: option, try
+
+fn foo() -> Option<()> {
+ $0Some(0)?$0;
+ None
+}
+"#,
+ expect![[r#"
+ ```rust
+ <Option<i32> as Try>::Output
+ ```"#]],
+ );
+}
+
+#[test]
+fn hover_deref_expr() {
+ check_hover_range(
+ r#"
+//- minicore: deref
+use core::ops::Deref;
+
+struct DerefExample<T> {
+ value: T
+}
+
+impl<T> Deref for DerefExample<T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.value
+ }
+}
+
+fn foo() {
+ let x = DerefExample { value: 0 };
+ let y: i32 = $0*x$0;
+}
+"#,
+ expect![[r#"
+ ```text
+ Dereferenced from: DerefExample<i32>
+ To type: i32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_deref_expr_with_coercion() {
+ check_hover_range(
+ r#"
+//- minicore: deref
+use core::ops::Deref;
+
+struct DerefExample<T> {
+ value: T
+}
+
+impl<T> Deref for DerefExample<T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.value
+ }
+}
+
+fn foo() {
+ let x = DerefExample { value: &&&&&0 };
+ let y: &i32 = $0*x$0;
+}
+"#,
+ expect![[r#"
+ ```text
+ Dereferenced from: DerefExample<&&&&&i32>
+ To type: &&&&&i32
+ Coerced to: &i32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_intra_in_macro() {
+ check(
+ r#"
+macro_rules! foo_macro {
+ ($(#[$attr:meta])* $name:ident) => {
+ $(#[$attr])*
+ pub struct $name;
+ }
+}
+
+foo_macro!(
+ /// Doc comment for [`Foo$0`]
+ Foo
+);
+"#,
+ expect![[r#"
+ *[`Foo`]*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub struct Foo
+ ```
+
+ ---
+
+ Doc comment for [`Foo`](https://docs.rs/test/*/test/struct.Foo.html)
+ "#]],
+ );
+}
+
+#[test]
+fn hover_intra_in_attr() {
+ check(
+ r#"
+#[doc = "Doc comment for [`Foo$0`]"]
+pub struct Foo;
+"#,
+ expect![[r#"
+ *[`Foo`]*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ pub struct Foo
+ ```
+
+ ---
+
+ Doc comment for [`Foo`](https://docs.rs/test/*/test/struct.Foo.html)
+ "#]],
+ );
+}
use either::Either;
use hir::{known, Callable, HasVisibility, HirDisplay, Semantics, TypeInfo};
-use ide_db::helpers::FamousDefs;
use ide_db::RootDatabase;
+use ide_db::{base_db::FileRange, helpers::FamousDefs};
use stdx::to_lower_snake_case;
use syntax::{
ast::{self, ArgListOwner, AstNode, NameOwner},
let _p = profile::span("inlay_hints");
let sema = Semantics::new(db);
let file = sema.parse(file_id);
+ let file = file.syntax();
let mut res = Vec::new();
- for node in file.syntax().descendants() {
- if let Some(expr) = ast::Expr::cast(node.clone()) {
- get_chaining_hints(&mut res, &sema, config, expr);
- }
- match_ast! {
- match node {
- ast::CallExpr(it) => { get_param_name_hints(&mut res, &sema, config, ast::Expr::from(it)); },
- ast::MethodCallExpr(it) => { get_param_name_hints(&mut res, &sema, config, ast::Expr::from(it)); },
- ast::IdentPat(it) => { get_bind_pat_hints(&mut res, &sema, config, it); },
+ for node in file.descendants() {
+ if let Some(expr) = ast::Expr::cast(node.clone()) {
+ get_chaining_hints(&mut res, &sema, config, &expr);
+ match expr {
+ ast::Expr::CallExpr(it) => {
+ get_param_name_hints(&mut res, &sema, config, ast::Expr::from(it));
+ }
+ ast::Expr::MethodCallExpr(it) => {
+ get_param_name_hints(&mut res, &sema, config, ast::Expr::from(it));
+ }
_ => (),
}
+ } else if let Some(it) = ast::IdentPat::cast(node.clone()) {
+ get_bind_pat_hints(&mut res, &sema, config, &it);
}
}
res
acc: &mut Vec<InlayHint>,
sema: &Semantics<RootDatabase>,
config: &InlayHintsConfig,
- expr: ast::Expr,
+ expr: &ast::Expr,
) -> Option<()> {
if !config.chaining_hints {
return None;
return None;
}
- let krate = sema.scope(expr.syntax()).module().map(|it| it.krate());
+ let descended = sema.descend_node_into_attributes(expr.clone()).pop();
+ let desc_expr = descended.as_ref().unwrap_or(expr);
+ let krate = sema.scope(desc_expr.syntax()).module().map(|it| it.krate());
let famous_defs = FamousDefs(sema, krate);
let mut tokens = expr
next_next = tokens.next()?.kind();
}
if next_next == T![.] {
- let ty = sema.type_of_expr(&expr)?.original;
+ let ty = sema.type_of_expr(desc_expr)?.original;
if ty.is_unknown() {
return None;
}
.into_iter()
.zip(arg_list.args())
.filter_map(|((param, _ty), arg)| {
+ // Only annotate hints for expressions that exist in the original file
+ let range = sema.original_range_opt(arg.syntax())?;
let param_name = match param? {
Either::Left(_) => "self".to_string(),
Either::Right(pat) => match pat {
_ => return None,
},
};
- Some((param_name, arg))
+ Some((param_name, arg, range))
})
- .filter(|(param_name, arg)| !should_hide_param_name_hint(sema, &callable, param_name, arg))
- .map(|(param_name, arg)| InlayHint {
- range: arg.syntax().text_range(),
+ .filter(|(param_name, arg, _)| {
+ !should_hide_param_name_hint(sema, &callable, param_name, arg)
+ })
+ .map(|(param_name, _, FileRange { range, .. })| InlayHint {
+ range,
kind: InlayKind::ParameterHint,
label: param_name.into(),
});
acc: &mut Vec<InlayHint>,
sema: &Semantics<RootDatabase>,
config: &InlayHintsConfig,
- pat: ast::IdentPat,
+ pat: &ast::IdentPat,
) -> Option<()> {
if !config.type_hints {
return None;
}
- let krate = sema.scope(pat.syntax()).module().map(|it| it.krate());
+ let descended = sema.descend_node_into_attributes(pat.clone()).pop();
+ let desc_pat = descended.as_ref().unwrap_or(pat);
+ let krate = sema.scope(desc_pat.syntax()).module().map(|it| it.krate());
let famous_defs = FamousDefs(sema, krate);
- let ty = sema.type_of_pat(&pat.clone().into())?.original;
+ let ty = sema.type_of_pat(&desc_pat.clone().into())?.original;
if should_not_display_type_hint(sema, &pat, &ty) {
return None;
) -> Option<(hir::Callable, ast::ArgList)> {
match expr {
ast::Expr::CallExpr(expr) => {
+ let descended = sema.descend_node_into_attributes(expr.clone()).pop();
+ let expr = descended.as_ref().unwrap_or(expr);
sema.type_of_expr(&expr.expr()?)?.original.as_callable(sema.db).zip(expr.arg_list())
}
ast::Expr::MethodCallExpr(expr) => {
+ let descended = sema.descend_node_into_attributes(expr.clone()).pop();
+ let expr = descended.as_ref().unwrap_or(expr);
sema.resolve_method_call_as_callable(expr).zip(expr.arg_list())
}
_ => None,
"#]],
);
}
+
+ #[test]
+ fn hints_in_attr_call() {
+ check_expect(
+ TEST_CONFIG,
+ r#"
+//- proc_macros: identity, input_replace
+struct Struct;
+impl Struct {
+ fn chain(self) -> Self {
+ self
+ }
+}
+#[proc_macros::identity]
+fn main() {
+ let strukt = Struct;
+ strukt
+ .chain()
+ .chain()
+ .chain();
+ Struct::chain(strukt);
+}
+"#,
+ expect![[r#"
+ [
+ InlayHint {
+ range: 124..130,
+ kind: TypeHint,
+ label: "Struct",
+ },
+ InlayHint {
+ range: 145..185,
+ kind: ChainingHint,
+ label: "Struct",
+ },
+ InlayHint {
+ range: 145..168,
+ kind: ChainingHint,
+ label: "Struct",
+ },
+ InlayHint {
+ range: 222..228,
+ kind: ParameterHint,
+ label: "self",
+ },
+ ]
+ "#]],
+ );
+ }
}
}
.map(|nav| {
let decl_range = nav.focus_or_full_range();
- Declaration { nav, access: decl_access(&def, &syntax, decl_range) }
+ Declaration {
+ access: decl_access(&def, sema.parse(nav.file_id).syntax(), decl_range),
+ nav,
+ }
});
if is_literal_search {
retain_adt_literal_usages(&mut usages, def, sema);
"#]],
)
}
+
+ #[test]
+ fn attr_expanded() {
+ check(
+ r#"
+//- proc_macros: identity
+
+#[proc_macros::identity]
+fn func$0() {
+ func();
+}
+"#,
+ expect![[r#"
+ func Function FileId(0) 26..51 29..33
+
+ FileId(0) 42..46
+ "#]],
+ )
+ }
}
"error: No identifier available to rename",
)
}
+
+ #[test]
+ fn attributed_item() {
+ check(
+ "function",
+ r#"
+//- proc_macros: identity
+
+#[proc_macros::identity]
+fn func$0() {
+ func();
+}
+"#,
+ r#"
+
+#[proc_macros::identity]
+fn function() {
+ function();
+}
+"#,
+ )
+ }
}
);
}
+ #[test]
+ fn attributed_module() {
+ check(
+ r#"
+//- proc_macros: identity
+//- /lib.rs
+$0
+#[proc_macros::identity]
+mod module {
+ #[test]
+ fn t0() {}
+ #[test]
+ fn t1() {}
+}
+"#,
+ &[TestMod, Test, Test],
+ expect![[r#"
+ [
+ Runnable {
+ use_name_in_title: true,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 26..94,
+ focus_range: 30..36,
+ name: "module",
+ kind: Module,
+ description: "mod module",
+ },
+ kind: TestMod {
+ path: "module",
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: true,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 43..65,
+ focus_range: 58..60,
+ name: "t0",
+ kind: Function,
+ },
+ kind: Test {
+ test_id: Path(
+ "module::t0",
+ ),
+ attr: TestAttr {
+ ignore: false,
+ },
+ },
+ cfg: None,
+ },
+ Runnable {
+ use_name_in_title: true,
+ nav: NavigationTarget {
+ file_id: FileId(
+ 0,
+ ),
+ full_range: 70..92,
+ focus_range: 85..87,
+ name: "t1",
+ kind: Function,
+ },
+ kind: Test {
+ test_id: Path(
+ "module::t1",
+ ),
+ attr: TestAttr {
+ ignore: false,
+ },
+ },
+ cfg: None,
+ },
+ ]
+ "#]],
+ );
+ }
+
#[test]
fn find_no_tests() {
check_tests(
Definition::ModuleDef(hir::ModuleDef::Trait(trait_))
if trait_.is_unsafe(db) =>
{
- if ast::Impl::for_trait_name_ref(&name_ref).is_some() {
+ if ast::Impl::for_trait_name_ref(&name_ref)
+ .map_or(false, |impl_| impl_.unsafe_token().is_some())
+ {
h |= HlMod::Unsafe;
}
}
.function.unsafe { color: #BC8383; }
.trait.unsafe { color: #BC8383; }
.operator.unsafe { color: #BC8383; }
+.mutable.unsafe { color: #BC8383; text-decoration: underline; }
+.keyword.unsafe { color: #BC8383; font-weight: bold; }
.parameter { color: #94BFF3; }
.text { color: #DCDCCC; }
.type { color: #7CB8BB; }
.mutable { text-decoration: underline; }
.escape_sequence { color: #94BFF3; }
.keyword { color: #F0DFAF; font-weight: bold; }
-.keyword.unsafe { color: #BC8383; font-weight: bold; }
.control { font-style: italic; }
.reference { font-style: italic; font-weight: bold; }
.function.unsafe { color: #BC8383; }
.trait.unsafe { color: #BC8383; }
.operator.unsafe { color: #BC8383; }
+.mutable.unsafe { color: #BC8383; text-decoration: underline; }
+.keyword.unsafe { color: #BC8383; font-weight: bold; }
.parameter { color: #94BFF3; }
.text { color: #DCDCCC; }
.type { color: #7CB8BB; }
.mutable { text-decoration: underline; }
.escape_sequence { color: #94BFF3; }
.keyword { color: #F0DFAF; font-weight: bold; }
-.keyword.unsafe { color: #BC8383; font-weight: bold; }
.control { font-style: italic; }
.reference { font-style: italic; font-weight: bold; }
.function.unsafe { color: #BC8383; }
.trait.unsafe { color: #BC8383; }
.operator.unsafe { color: #BC8383; }
+.mutable.unsafe { color: #BC8383; text-decoration: underline; }
+.keyword.unsafe { color: #BC8383; font-weight: bold; }
.parameter { color: #94BFF3; }
.text { color: #DCDCCC; }
.type { color: #7CB8BB; }
.mutable { text-decoration: underline; }
.escape_sequence { color: #94BFF3; }
.keyword { color: #F0DFAF; font-weight: bold; }
-.keyword.unsafe { color: #BC8383; font-weight: bold; }
.control { font-style: italic; }
.reference { font-style: italic; font-weight: bold; }
.function.unsafe { color: #BC8383; }
.trait.unsafe { color: #BC8383; }
.operator.unsafe { color: #BC8383; }
+.mutable.unsafe { color: #BC8383; text-decoration: underline; }
+.keyword.unsafe { color: #BC8383; font-weight: bold; }
.parameter { color: #94BFF3; }
.text { color: #DCDCCC; }
.type { color: #7CB8BB; }
.mutable { text-decoration: underline; }
.escape_sequence { color: #94BFF3; }
.keyword { color: #F0DFAF; font-weight: bold; }
-.keyword.unsafe { color: #BC8383; font-weight: bold; }
.control { font-style: italic; }
.reference { font-style: italic; font-weight: bold; }
.function.unsafe { color: #BC8383; }
.trait.unsafe { color: #BC8383; }
.operator.unsafe { color: #BC8383; }
+.mutable.unsafe { color: #BC8383; text-decoration: underline; }
+.keyword.unsafe { color: #BC8383; font-weight: bold; }
.parameter { color: #94BFF3; }
.text { color: #DCDCCC; }
.type { color: #7CB8BB; }
.mutable { text-decoration: underline; }
.escape_sequence { color: #94BFF3; }
.keyword { color: #F0DFAF; font-weight: bold; }
-.keyword.unsafe { color: #BC8383; font-weight: bold; }
.control { font-style: italic; }
.reference { font-style: italic; font-weight: bold; }
.function.unsafe { color: #BC8383; }
.trait.unsafe { color: #BC8383; }
.operator.unsafe { color: #BC8383; }
+.mutable.unsafe { color: #BC8383; text-decoration: underline; }
+.keyword.unsafe { color: #BC8383; font-weight: bold; }
.parameter { color: #94BFF3; }
.text { color: #DCDCCC; }
.type { color: #7CB8BB; }
.mutable { text-decoration: underline; }
.escape_sequence { color: #94BFF3; }
.keyword { color: #F0DFAF; font-weight: bold; }
-.keyword.unsafe { color: #BC8383; font-weight: bold; }
.control { font-style: italic; }
.reference { font-style: italic; font-weight: bold; }
.function.unsafe { color: #BC8383; }
.trait.unsafe { color: #BC8383; }
.operator.unsafe { color: #BC8383; }
+.mutable.unsafe { color: #BC8383; text-decoration: underline; }
+.keyword.unsafe { color: #BC8383; font-weight: bold; }
.parameter { color: #94BFF3; }
.text { color: #DCDCCC; }
.type { color: #7CB8BB; }
.mutable { text-decoration: underline; }
.escape_sequence { color: #94BFF3; }
.keyword { color: #F0DFAF; font-weight: bold; }
-.keyword.unsafe { color: #BC8383; font-weight: bold; }
.control { font-style: italic; }
.reference { font-style: italic; font-weight: bold; }
.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
</style>
-<pre><code><span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function declaration unsafe">unsafe_fn</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+<pre><code><span class="keyword">static</span> <span class="keyword">mut</span> <span class="static declaration mutable unsafe">MUT_GLOBAL</span><span class="colon">:</span> <span class="struct">Struct</span> <span class="operator">=</span> <span class="struct">Struct</span> <span class="brace">{</span> <span class="field">field</span><span class="colon">:</span> <span class="numeric_literal">0</span> <span class="brace">}</span><span class="semicolon">;</span>
+<span class="keyword">static</span> <span class="static declaration">GLOBAL</span><span class="colon">:</span> <span class="struct">Struct</span> <span class="operator">=</span> <span class="struct">Struct</span> <span class="brace">{</span> <span class="field">field</span><span class="colon">:</span> <span class="numeric_literal">0</span> <span class="brace">}</span><span class="semicolon">;</span>
+<span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function declaration unsafe">unsafe_fn</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
<span class="keyword">union</span> <span class="union declaration">Union</span> <span class="brace">{</span>
<span class="field declaration">a</span><span class="colon">:</span> <span class="builtin_type">u32</span><span class="comma">,</span>
<span class="field declaration">b</span><span class="colon">:</span> <span class="builtin_type">f32</span><span class="comma">,</span>
<span class="brace">}</span>
-<span class="keyword">struct</span> <span class="struct declaration">HasUnsafeFn</span><span class="semicolon">;</span>
-
-<span class="keyword">impl</span> <span class="struct">HasUnsafeFn</span> <span class="brace">{</span>
+<span class="keyword">struct</span> <span class="struct declaration">Struct</span> <span class="brace">{</span> <span class="field declaration">field</span><span class="colon">:</span> <span class="builtin_type">i32</span> <span class="brace">}</span>
+<span class="keyword">impl</span> <span class="struct">Struct</span> <span class="brace">{</span>
<span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function associated declaration reference unsafe">unsafe_method</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
<span class="brace">}</span>
-<span class="keyword">struct</span> <span class="struct declaration">TypeForStaticMut</span> <span class="brace">{</span>
- <span class="field declaration">a</span><span class="colon">:</span> <span class="builtin_type">u8</span>
-<span class="brace">}</span>
-
-<span class="keyword">static</span> <span class="keyword">mut</span> <span class="static declaration mutable unsafe">global_mut</span><span class="colon">:</span> <span class="struct">TypeForStaticMut</span> <span class="operator">=</span> <span class="struct">TypeForStaticMut</span> <span class="brace">{</span> <span class="field">a</span><span class="colon">:</span> <span class="numeric_literal">0</span> <span class="brace">}</span><span class="semicolon">;</span>
-
<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute">repr</span><span class="parenthesis attribute">(</span><span class="none attribute">packed</span><span class="parenthesis attribute">)</span><span class="attribute attribute">]</span>
<span class="keyword">struct</span> <span class="struct declaration">Packed</span> <span class="brace">{</span>
<span class="field declaration">a</span><span class="colon">:</span> <span class="builtin_type">u16</span><span class="comma">,</span>
<span class="keyword unsafe">unsafe</span> <span class="keyword">trait</span> <span class="trait declaration unsafe">UnsafeTrait</span> <span class="brace">{</span><span class="brace">}</span>
<span class="keyword unsafe">unsafe</span> <span class="keyword">impl</span> <span class="trait unsafe">UnsafeTrait</span> <span class="keyword">for</span> <span class="struct">Packed</span> <span class="brace">{</span><span class="brace">}</span>
+<span class="keyword">impl</span> <span class="punctuation">!</span><span class="trait">UnsafeTrait</span> <span class="keyword">for</span> <span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
-<span class="keyword">fn</span> <span class="function declaration">require_unsafe_trait</span><span class="angle"><</span><span class="type_param declaration">T</span><span class="colon">:</span> <span class="trait">UnsafeTrait</span><span class="angle">></span><span class="parenthesis">(</span><span class="punctuation">_</span><span class="colon">:</span> <span class="type_param">T</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+<span class="keyword">fn</span> <span class="function declaration">unsafe_trait_bound</span><span class="angle"><</span><span class="type_param declaration">T</span><span class="colon">:</span> <span class="trait">UnsafeTrait</span><span class="angle">></span><span class="parenthesis">(</span><span class="punctuation">_</span><span class="colon">:</span> <span class="type_param">T</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
<span class="keyword">trait</span> <span class="trait declaration">DoTheAutoref</span> <span class="brace">{</span>
<span class="keyword">fn</span> <span class="function associated declaration reference trait">calls_autoref</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration reference">self</span><span class="parenthesis">)</span><span class="semicolon">;</span>
<span class="union">Union</span> <span class="brace">{</span> <span class="field unsafe">b</span><span class="colon">:</span> <span class="numeric_literal">0</span> <span class="brace">}</span> <span class="operator">=></span> <span class="parenthesis">(</span><span class="parenthesis">)</span><span class="comma">,</span>
<span class="union">Union</span> <span class="brace">{</span> <span class="field unsafe">a</span> <span class="brace">}</span> <span class="operator">=></span> <span class="parenthesis">(</span><span class="parenthesis">)</span><span class="comma">,</span>
<span class="brace">}</span>
- <span class="struct">HasUnsafeFn</span><span class="operator">.</span><span class="function associated reference unsafe">unsafe_method</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+ <span class="struct">Struct</span> <span class="brace">{</span> <span class="field">field</span><span class="colon">:</span> <span class="numeric_literal">0</span> <span class="brace">}</span><span class="operator">.</span><span class="function associated reference unsafe">unsafe_method</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
<span class="comment">// unsafe deref</span>
- <span class="keyword">let</span> <span class="variable declaration">y</span> <span class="operator">=</span> <span class="operator unsafe">*</span><span class="variable">x</span><span class="semicolon">;</span>
+ <span class="operator unsafe">*</span><span class="variable">x</span><span class="semicolon">;</span>
<span class="comment">// unsafe access to a static mut</span>
- <span class="keyword">let</span> <span class="variable declaration">a</span> <span class="operator">=</span> <span class="static mutable unsafe">global_mut</span><span class="operator">.</span><span class="field">a</span><span class="semicolon">;</span>
+ <span class="static mutable unsafe">MUT_GLOBAL</span><span class="operator">.</span><span class="field">field</span><span class="semicolon">;</span>
+ <span class="static">GLOBAL</span><span class="operator">.</span><span class="field">field</span><span class="semicolon">;</span>
<span class="comment">// unsafe ref of packed fields</span>
<span class="keyword">let</span> <span class="variable declaration">packed</span> <span class="operator">=</span> <span class="struct">Packed</span> <span class="brace">{</span> <span class="field">a</span><span class="colon">:</span> <span class="numeric_literal">0</span> <span class="brace">}</span><span class="semicolon">;</span>
.function.unsafe { color: #BC8383; }
.trait.unsafe { color: #BC8383; }
.operator.unsafe { color: #BC8383; }
+.mutable.unsafe { color: #BC8383; text-decoration: underline; }
+.keyword.unsafe { color: #BC8383; font-weight: bold; }
.parameter { color: #94BFF3; }
.text { color: #DCDCCC; }
.type { color: #7CB8BB; }
.mutable { text-decoration: underline; }
.escape_sequence { color: #94BFF3; }
.keyword { color: #F0DFAF; font-weight: bold; }
-.keyword.unsafe { color: #BC8383; font-weight: bold; }
.control { font-style: italic; }
.reference { font-style: italic; font-weight: bold; }
<span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration public">Copy</span> <span class="brace">{</span><span class="brace">}</span>
<span class="brace">}</span>
+<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="module attribute">proc_macros</span><span class="operator attribute">::</span><span class="builtin_attr attribute">identity</span><span class="attribute attribute">]</span>
<span class="keyword">pub</span> <span class="keyword">mod</span> <span class="module declaration public">ops</span> <span class="brace">{</span>
- <span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute">lang</span><span class="attribute attribute"> </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"fn_once"</span><span class="attribute attribute">]</span>
+ <span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute">lang</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"fn_once"</span><span class="attribute attribute">]</span>
<span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration public">FnOnce</span><span class="angle"><</span><span class="type_param declaration">Args</span><span class="angle">></span> <span class="brace">{</span><span class="brace">}</span>
- <span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute">lang</span><span class="attribute attribute"> </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"fn_mut"</span><span class="attribute attribute">]</span>
+ <span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute">lang</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"fn_mut"</span><span class="attribute attribute">]</span>
<span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration public">FnMut</span><span class="angle"><</span><span class="type_param declaration">Args</span><span class="angle">></span><span class="colon">:</span> <span class="trait public">FnOnce</span><span class="angle"><</span><span class="type_param">Args</span><span class="angle">></span> <span class="brace">{</span><span class="brace">}</span>
- <span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute">lang</span><span class="attribute attribute"> </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"fn"</span><span class="attribute attribute">]</span>
+ <span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute">lang</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"fn"</span><span class="attribute attribute">]</span>
<span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration public">Fn</span><span class="angle"><</span><span class="type_param declaration">Args</span><span class="angle">></span><span class="colon">:</span> <span class="trait public">FnMut</span><span class="angle"><</span><span class="type_param">Args</span><span class="angle">></span> <span class="brace">{</span><span class="brace">}</span>
<span class="brace">}</span>
-
-<span class="keyword">struct</span> <span class="struct declaration">Foo</span> <span class="brace">{</span>
- <span class="keyword">pub</span> <span class="field declaration public">x</span><span class="colon">:</span> <span class="builtin_type">i32</span><span class="comma">,</span>
- <span class="keyword">pub</span> <span class="field declaration public">y</span><span class="colon">:</span> <span class="builtin_type">i32</span><span class="comma">,</span>
+proc_macros::<span class="macro">mirror!</span> <span class="brace">{</span>
+ <span class="brace">{</span>
+ <span class="comma">,</span><span class="builtin_type">i32</span> <span class="colon">:</span><span class="field declaration public">x</span> <span class="keyword">pub</span>
+ <span class="comma">,</span><span class="builtin_type">i32</span> <span class="colon">:</span><span class="field declaration public">y</span> <span class="keyword">pub</span>
+ <span class="brace">}</span> <span class="struct declaration">Foo</span> <span class="keyword">struct</span>
<span class="brace">}</span>
<span class="keyword">trait</span> <span class="trait declaration">Bar</span> <span class="keyword">where</span> <span class="type_param">Self</span><span class="colon">:</span> <span class="brace">{</span>
<span class="function">str</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
<span class="brace">}</span>
-<span class="keyword">static</span> <span class="keyword">mut</span> <span class="static declaration mutable unsafe">STATIC_MUT</span><span class="colon">:</span> <span class="builtin_type">i32</span> <span class="operator">=</span> <span class="numeric_literal">0</span><span class="semicolon">;</span>
-
<span class="keyword">fn</span> <span class="function declaration">foo</span><span class="angle"><</span><span class="lifetime declaration">'a</span><span class="comma">,</span> <span class="type_param declaration">T</span><span class="angle">></span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">-></span> <span class="type_param">T</span> <span class="brace">{</span>
<span class="function">foo</span><span class="operator">::</span><span class="angle"><</span><span class="lifetime">'a</span><span class="comma">,</span> <span class="builtin_type">i32</span><span class="angle">></span><span class="parenthesis">(</span><span class="parenthesis">)</span>
<span class="brace">}</span>
<span class="keyword">let</span> <span class="variable declaration">x</span> <span class="operator">=</span> <span class="numeric_literal">92</span><span class="semicolon">;</span>
<span class="variable mutable">vec</span><span class="operator">.</span><span class="unresolved_reference">push</span><span class="parenthesis">(</span><span class="struct">Foo</span> <span class="brace">{</span> <span class="field">x</span><span class="comma">,</span> <span class="field public">y</span><span class="colon">:</span> <span class="numeric_literal">1</span> <span class="brace">}</span><span class="parenthesis">)</span><span class="semicolon">;</span>
<span class="brace">}</span>
- <span class="keyword unsafe">unsafe</span> <span class="brace">{</span>
- <span class="variable mutable">vec</span><span class="operator">.</span><span class="unresolved_reference">set_len</span><span class="parenthesis">(</span><span class="numeric_literal">0</span><span class="parenthesis">)</span><span class="semicolon">;</span>
- <span class="static mutable unsafe">STATIC_MUT</span> <span class="operator">=</span> <span class="numeric_literal">1</span><span class="semicolon">;</span>
- <span class="brace">}</span>
<span class="keyword control">for</span> <span class="variable declaration">e</span> <span class="keyword control">in</span> <span class="variable mutable">vec</span> <span class="brace">{</span>
<span class="comment">// Do nothing</span>
futures::<span class="macro">join!</span><span class="parenthesis">(</span>f1<span class="comma">,</span> f2<span class="parenthesis">)</span><span class="semicolon">;</span>
<span class="brace">}</span>
-<span class="keyword unsafe">unsafe</span> <span class="keyword">trait</span> <span class="trait declaration unsafe">Dangerous</span> <span class="brace">{</span><span class="brace">}</span>
-<span class="keyword">impl</span> <span class="trait unsafe">Dangerous</span> <span class="keyword">for</span> <span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
-
<span class="keyword">fn</span> <span class="function declaration">use_foo_items</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
<span class="keyword">let</span> <span class="variable declaration">bob</span> <span class="operator">=</span> <span class="module library">foo</span><span class="operator">::</span><span class="struct library">Person</span> <span class="brace">{</span>
<span class="field library">name</span><span class="colon">:</span> <span class="string_literal">"Bob"</span><span class="comma">,</span>
.function.unsafe { color: #BC8383; }
.trait.unsafe { color: #BC8383; }
.operator.unsafe { color: #BC8383; }
+.mutable.unsafe { color: #BC8383; text-decoration: underline; }
+.keyword.unsafe { color: #BC8383; font-weight: bold; }
.parameter { color: #94BFF3; }
.text { color: #DCDCCC; }
.type { color: #7CB8BB; }
.mutable { text-decoration: underline; }
.escape_sequence { color: #94BFF3; }
.keyword { color: #F0DFAF; font-weight: bold; }
-.keyword.unsafe { color: #BC8383; font-weight: bold; }
.control { font-style: italic; }
.reference { font-style: italic; font-weight: bold; }
.function.unsafe { color: #BC8383; }
.trait.unsafe { color: #BC8383; }
.operator.unsafe { color: #BC8383; }
+.mutable.unsafe { color: #BC8383; text-decoration: underline; }
+.keyword.unsafe { color: #BC8383; font-weight: bold; }
.parameter { color: #94BFF3; }
.text { color: #DCDCCC; }
.type { color: #7CB8BB; }
.mutable { text-decoration: underline; }
.escape_sequence { color: #94BFF3; }
.keyword { color: #F0DFAF; font-weight: bold; }
-.keyword.unsafe { color: #BC8383; font-weight: bold; }
.control { font-style: italic; }
.reference { font-style: italic; font-weight: bold; }
fn test_highlighting() {
check_highlighting(
r#"
+//- proc_macros: identity, mirror
//- /main.rs crate:main deps:foo
use inner::{self as inner_mod};
mod inner {}
pub trait Copy {}
}
+#[proc_macros::identity]
pub mod ops {
#[lang = "fn_once"]
pub trait FnOnce<Args> {}
pub trait Fn<Args>: FnMut<Args> {}
}
-
-struct Foo {
- pub x: i32,
- pub y: i32,
+proc_macros::mirror! {
+ {
+ ,i32 :x pub
+ ,i32 :y pub
+ } Foo struct
}
trait Bar where Self: {
str();
}
-static mut STATIC_MUT: i32 = 0;
-
fn foo<'a, T>() -> T {
foo::<'a, i32>()
}
let x = 92;
vec.push(Foo { x, y: 1 });
}
- unsafe {
- vec.set_len(0);
- STATIC_MUT = 1;
- }
for e in vec {
// Do nothing
futures::join!(f1, f2);
}
-unsafe trait Dangerous {}
-impl Dangerous for () {}
-
fn use_foo_items() {
let bob = foo::Person {
name: "Bob",
fn test_unsafe_highlighting() {
check_highlighting(
r#"
+static mut MUT_GLOBAL: Struct = Struct { field: 0 };
+static GLOBAL: Struct = Struct { field: 0 };
unsafe fn unsafe_fn() {}
union Union {
b: f32,
}
-struct HasUnsafeFn;
-
-impl HasUnsafeFn {
+struct Struct { field: i32 }
+impl Struct {
unsafe fn unsafe_method(&self) {}
}
-struct TypeForStaticMut {
- a: u8
-}
-
-static mut global_mut: TypeForStaticMut = TypeForStaticMut { a: 0 };
-
#[repr(packed)]
struct Packed {
a: u16,
unsafe trait UnsafeTrait {}
unsafe impl UnsafeTrait for Packed {}
+impl !UnsafeTrait for () {}
-fn require_unsafe_trait<T: UnsafeTrait>(_: T) {}
+fn unsafe_trait_bound<T: UnsafeTrait>(_: T) {}
trait DoTheAutoref {
fn calls_autoref(&self);
Union { b: 0 } => (),
Union { a } => (),
}
- HasUnsafeFn.unsafe_method();
+ Struct { field: 0 }.unsafe_method();
// unsafe deref
- let y = *x;
+ *x;
// unsafe access to a static mut
- let a = global_mut.a;
+ MUT_GLOBAL.field;
+ GLOBAL.field;
// unsafe ref of packed fields
let packed = Packed { a: 0 };
None
};
- let label = Label::new(label.into());
+ let label = Label::new(label);
let group = group.cloned();
self.buf.push(Assist { id, label, group, target, source_change });
Some(())
--- /dev/null
+use std::iter::{self, Peekable};
+
+use either::Either;
+use hir::{Adt, HasSource, ModuleDef, Semantics};
+use ide_db::helpers::{mod_path_to_ast, FamousDefs};
+use ide_db::RootDatabase;
+use itertools::Itertools;
+use syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat};
+
+use crate::{
+ utils::{self, render_snippet, Cursor},
+ AssistContext, AssistId, AssistKind, Assists,
+};
+
+// Assist: add_missing_match_arms
+//
+// Adds missing clauses to a `match` expression.
+//
+// ```
+// enum Action { Move { distance: u32 }, Stop }
+//
+// fn handle(action: Action) {
+// match action {
+// $0
+// }
+// }
+// ```
+// ->
+// ```
+// enum Action { Move { distance: u32 }, Stop }
+//
+// fn handle(action: Action) {
+// match action {
+// $0Action::Move { distance } => todo!(),
+// Action::Stop => todo!(),
+// }
+// }
+// ```
+pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
+ let match_expr = ctx.find_node_at_offset_with_descend::<ast::MatchExpr>()?;
+ let match_arm_list = match_expr.match_arm_list()?;
+
+ let expr = match_expr.expr()?;
+
+ let mut arms: Vec<MatchArm> = match_arm_list.arms().collect();
+ if let [arm] = arms.as_slice() {
+ if let Some(Pat::WildcardPat(..)) = arm.pat() {
+ arms.clear();
+ }
+ }
+
+ let top_lvl_pats: Vec<_> = arms
+ .iter()
+ .filter_map(ast::MatchArm::pat)
+ .flat_map(|pat| match pat {
+ // Special case OrPat as separate top-level pats
+ Pat::OrPat(or_pat) => Either::Left(or_pat.pats()),
+ _ => Either::Right(iter::once(pat)),
+ })
+ // Exclude top level wildcards so that they are expanded by this assist, retains status quo in #8129.
+ .filter(|pat| !matches!(pat, Pat::WildcardPat(_)))
+ .collect();
+
+ let module = ctx.sema.scope(expr.syntax()).module()?;
+
+ let mut missing_pats: Peekable<Box<dyn Iterator<Item = ast::Pat>>> = if let Some(enum_def) =
+ resolve_enum_def(&ctx.sema, &expr)
+ {
+ let variants = enum_def.variants(ctx.db());
+
+ let missing_pats = variants
+ .into_iter()
+ .filter_map(|variant| build_pat(ctx.db(), module, variant))
+ .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat));
+
+ let option_enum =
+ FamousDefs(&ctx.sema, Some(module.krate())).core_option_Option().map(lift_enum);
+ let missing_pats: Box<dyn Iterator<Item = _>> = if Some(enum_def) == option_enum {
+ // Match `Some` variant first.
+ cov_mark::hit!(option_order);
+ Box::new(missing_pats.rev())
+ } else {
+ Box::new(missing_pats)
+ };
+ missing_pats.peekable()
+ } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) {
+ let mut n_arms = 1;
+ let variants_of_enums: Vec<Vec<ExtendedVariant>> = enum_defs
+ .into_iter()
+ .map(|enum_def| enum_def.variants(ctx.db()))
+ .inspect(|variants| n_arms *= variants.len())
+ .collect();
+
+ // When calculating the match arms for a tuple of enums, we want
+ // to create a match arm for each possible combination of enum
+ // values. The `multi_cartesian_product` method transforms
+ // Vec<Vec<EnumVariant>> into Vec<(EnumVariant, .., EnumVariant)>
+ // where each tuple represents a proposed match arm.
+
+ // A number of arms grows very fast on even a small tuple of large enums.
+ // We skip the assist beyond an arbitrary threshold.
+ if n_arms > 256 {
+ return None;
+ }
+ let missing_pats = variants_of_enums
+ .into_iter()
+ .multi_cartesian_product()
+ .inspect(|_| cov_mark::hit!(add_missing_match_arms_lazy_computation))
+ .map(|variants| {
+ let patterns =
+ variants.into_iter().filter_map(|variant| build_pat(ctx.db(), module, variant));
+ ast::Pat::from(make::tuple_pat(patterns))
+ })
+ .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat));
+ (Box::new(missing_pats) as Box<dyn Iterator<Item = _>>).peekable()
+ } else {
+ return None;
+ };
+
+ if missing_pats.peek().is_none() {
+ return None;
+ }
+
+ let target = ctx.sema.original_range(match_expr.syntax()).range;
+ acc.add(
+ AssistId("add_missing_match_arms", AssistKind::QuickFix),
+ "Fill match arms",
+ target,
+ |builder| {
+ let new_match_arm_list = match_arm_list.clone_for_update();
+ let missing_arms = missing_pats
+ .map(|pat| make::match_arm(iter::once(pat), None, make::ext::expr_todo()))
+ .map(|it| it.clone_for_update());
+
+ let catch_all_arm = new_match_arm_list
+ .arms()
+ .find(|arm| matches!(arm.pat(), Some(ast::Pat::WildcardPat(_))));
+ if let Some(arm) = catch_all_arm {
+ let is_empty_expr = arm.expr().map_or(true, |e| match e {
+ ast::Expr::BlockExpr(b) => {
+ b.statements().next().is_none() && b.tail_expr().is_none()
+ }
+ ast::Expr::TupleExpr(t) => t.fields().next().is_none(),
+ _ => false,
+ });
+ if is_empty_expr {
+ arm.remove();
+ } else {
+ cov_mark::hit!(add_missing_match_arms_empty_expr);
+ }
+ }
+ let mut first_new_arm = None;
+ for arm in missing_arms {
+ first_new_arm.get_or_insert_with(|| arm.clone());
+ new_match_arm_list.add_arm(arm);
+ }
+
+ let old_range = ctx.sema.original_range(match_arm_list.syntax()).range;
+ match (first_new_arm, ctx.config.snippet_cap) {
+ (Some(first_new_arm), Some(cap)) => {
+ let extend_lifetime;
+ let cursor =
+ match first_new_arm.syntax().descendants().find_map(ast::WildcardPat::cast)
+ {
+ Some(it) => {
+ extend_lifetime = it.syntax().clone();
+ Cursor::Replace(&extend_lifetime)
+ }
+ None => Cursor::Before(first_new_arm.syntax()),
+ };
+ let snippet = render_snippet(cap, new_match_arm_list.syntax(), cursor);
+ builder.replace_snippet(cap, old_range, snippet);
+ }
+ _ => builder.replace(old_range, new_match_arm_list.to_string()),
+ }
+ },
+ )
+}
+
+fn is_variant_missing(existing_pats: &[Pat], var: &Pat) -> bool {
+ !existing_pats.iter().any(|pat| does_pat_match_variant(pat, var))
+}
+
+// Fixme: this is still somewhat limited, use hir_ty::diagnostics::match_check?
+fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool {
+ match (pat, var) {
+ (Pat::WildcardPat(_), _) => true,
+ (Pat::TuplePat(tpat), Pat::TuplePat(tvar)) => {
+ tpat.fields().zip(tvar.fields()).all(|(p, v)| does_pat_match_variant(&p, &v))
+ }
+ _ => utils::does_pat_match_variant(pat, var),
+ }
+}
+
+#[derive(Eq, PartialEq, Clone, Copy)]
+enum ExtendedEnum {
+ Bool,
+ Enum(hir::Enum),
+}
+
+#[derive(Eq, PartialEq, Clone, Copy)]
+enum ExtendedVariant {
+ True,
+ False,
+ Variant(hir::Variant),
+}
+
+fn lift_enum(e: hir::Enum) -> ExtendedEnum {
+ ExtendedEnum::Enum(e)
+}
+
+impl ExtendedEnum {
+ fn variants(self, db: &RootDatabase) -> Vec<ExtendedVariant> {
+ match self {
+ ExtendedEnum::Enum(e) => {
+ e.variants(db).into_iter().map(ExtendedVariant::Variant).collect::<Vec<_>>()
+ }
+ ExtendedEnum::Bool => {
+ Vec::<ExtendedVariant>::from([ExtendedVariant::True, ExtendedVariant::False])
+ }
+ }
+ }
+}
+
+fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<ExtendedEnum> {
+ sema.type_of_expr(expr)?.adjusted().autoderef(sema.db).find_map(|ty| match ty.as_adt() {
+ Some(Adt::Enum(e)) => Some(ExtendedEnum::Enum(e)),
+ _ => ty.is_bool().then(|| ExtendedEnum::Bool),
+ })
+}
+
+fn resolve_tuple_of_enum_def(
+ sema: &Semantics<RootDatabase>,
+ expr: &ast::Expr,
+) -> Option<Vec<ExtendedEnum>> {
+ sema.type_of_expr(expr)?
+ .adjusted()
+ .tuple_fields(sema.db)
+ .iter()
+ .map(|ty| {
+ ty.autoderef(sema.db).find_map(|ty| match ty.as_adt() {
+ Some(Adt::Enum(e)) => Some(lift_enum(e)),
+ // For now we only handle expansion for a tuple of enums. Here
+ // we map non-enum items to None and rely on `collect` to
+ // convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>.
+ _ => ty.is_bool().then(|| ExtendedEnum::Bool),
+ })
+ })
+ .collect()
+}
+
+fn build_pat(db: &RootDatabase, module: hir::Module, var: ExtendedVariant) -> Option<ast::Pat> {
+ match var {
+ ExtendedVariant::Variant(var) => {
+ let path = mod_path_to_ast(&module.find_use_path(db, ModuleDef::from(var))?);
+
+ // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though
+ let pat: ast::Pat = match var.source(db)?.value.kind() {
+ ast::StructKind::Tuple(field_list) => {
+ let pats =
+ iter::repeat(make::wildcard_pat().into()).take(field_list.fields().count());
+ make::tuple_struct_pat(path, pats).into()
+ }
+ ast::StructKind::Record(field_list) => {
+ let pats = field_list
+ .fields()
+ .map(|f| make::ext::simple_ident_pat(f.name().unwrap()).into());
+ make::record_pat(path, pats).into()
+ }
+ ast::StructKind::Unit => make::path_pat(path),
+ };
+
+ Some(pat)
+ }
+ ExtendedVariant::True => Some(ast::Pat::from(make::literal_pat("true"))),
+ ExtendedVariant::False => Some(ast::Pat::from(make::literal_pat("false"))),
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{
+ check_assist, check_assist_not_applicable, check_assist_target, check_assist_unresolved,
+ };
+
+ use super::add_missing_match_arms;
+
+ #[test]
+ fn all_match_arms_provided() {
+ check_assist_not_applicable(
+ add_missing_match_arms,
+ r#"
+enum A {
+ As,
+ Bs{x:i32, y:Option<i32>},
+ Cs(i32, Option<i32>),
+}
+fn main() {
+ match A::As$0 {
+ A::As,
+ A::Bs{x,y:Some(_)} => {}
+ A::Cs(_, Some(_)) => {}
+ }
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn all_boolean_match_arms_provided() {
+ check_assist_not_applicable(
+ add_missing_match_arms,
+ r#"
+fn foo(a: bool) {
+ match a$0 {
+ true => {}
+ false => {}
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn tuple_of_non_enum() {
+ // for now this case is not handled, although it potentially could be
+ // in the future
+ check_assist_not_applicable(
+ add_missing_match_arms,
+ r#"
+fn main() {
+ match (0, false)$0 {
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_boolean() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+fn foo(a: bool) {
+ match a$0 {
+ }
+}
+"#,
+ r#"
+fn foo(a: bool) {
+ match a {
+ $0true => todo!(),
+ false => todo!(),
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn partial_fill_boolean() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+fn foo(a: bool) {
+ match a$0 {
+ true => {}
+ }
+}
+"#,
+ r#"
+fn foo(a: bool) {
+ match a {
+ true => {}
+ $0false => todo!(),
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn all_boolean_tuple_arms_provided() {
+ check_assist_not_applicable(
+ add_missing_match_arms,
+ r#"
+fn foo(a: bool) {
+ match (a, a)$0 {
+ (true, true) => {}
+ (true, false) => {}
+ (false, true) => {}
+ (false, false) => {}
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn fill_boolean_tuple() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+fn foo(a: bool) {
+ match (a, a)$0 {
+ }
+}
+"#,
+ r#"
+fn foo(a: bool) {
+ match (a, a) {
+ $0(true, true) => todo!(),
+ (true, false) => todo!(),
+ (false, true) => todo!(),
+ (false, false) => todo!(),
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn partial_fill_boolean_tuple() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+fn foo(a: bool) {
+ match (a, a)$0 {
+ (false, true) => {}
+ }
+}
+"#,
+ r#"
+fn foo(a: bool) {
+ match (a, a) {
+ (false, true) => {}
+ $0(true, true) => todo!(),
+ (true, false) => todo!(),
+ (false, false) => todo!(),
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn partial_fill_record_tuple() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum A {
+ As,
+ Bs { x: i32, y: Option<i32> },
+ Cs(i32, Option<i32>),
+}
+fn main() {
+ match A::As$0 {
+ A::Bs { x, y: Some(_) } => {}
+ A::Cs(_, Some(_)) => {}
+ }
+}
+"#,
+ r#"
+enum A {
+ As,
+ Bs { x: i32, y: Option<i32> },
+ Cs(i32, Option<i32>),
+}
+fn main() {
+ match A::As {
+ A::Bs { x, y: Some(_) } => {}
+ A::Cs(_, Some(_)) => {}
+ $0A::As => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn partial_fill_option() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+//- minicore: option
+fn main() {
+ match None$0 {
+ None => {}
+ }
+}
+"#,
+ r#"
+fn main() {
+ match None {
+ None => {}
+ Some(${0:_}) => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn partial_fill_or_pat() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum A { As, Bs, Cs(Option<i32>) }
+fn main() {
+ match A::As$0 {
+ A::Cs(_) | A::Bs => {}
+ }
+}
+"#,
+ r#"
+enum A { As, Bs, Cs(Option<i32>) }
+fn main() {
+ match A::As {
+ A::Cs(_) | A::Bs => {}
+ $0A::As => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn partial_fill() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum A { As, Bs, Cs, Ds(String), Es(B) }
+enum B { Xs, Ys }
+fn main() {
+ match A::As$0 {
+ A::Bs if 0 < 1 => {}
+ A::Ds(_value) => { let x = 1; }
+ A::Es(B::Xs) => (),
+ }
+}
+"#,
+ r#"
+enum A { As, Bs, Cs, Ds(String), Es(B) }
+enum B { Xs, Ys }
+fn main() {
+ match A::As {
+ A::Bs if 0 < 1 => {}
+ A::Ds(_value) => { let x = 1; }
+ A::Es(B::Xs) => (),
+ $0A::As => todo!(),
+ A::Cs => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn partial_fill_bind_pat() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum A { As, Bs, Cs(Option<i32>) }
+fn main() {
+ match A::As$0 {
+ A::As(_) => {}
+ a @ A::Bs(_) => {}
+ }
+}
+"#,
+ r#"
+enum A { As, Bs, Cs(Option<i32>) }
+fn main() {
+ match A::As {
+ A::As(_) => {}
+ a @ A::Bs(_) => {}
+ A::Cs(${0:_}) => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_empty_body() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum A { As, Bs, Cs(String), Ds(String, String), Es { x: usize, y: usize } }
+
+fn main() {
+ let a = A::As;
+ match a$0 {}
+}
+"#,
+ r#"
+enum A { As, Bs, Cs(String), Ds(String, String), Es { x: usize, y: usize } }
+
+fn main() {
+ let a = A::As;
+ match a {
+ $0A::As => todo!(),
+ A::Bs => todo!(),
+ A::Cs(_) => todo!(),
+ A::Ds(_, _) => todo!(),
+ A::Es { x, y } => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_tuple_of_enum() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum A { One, Two }
+enum B { One, Two }
+
+fn main() {
+ let a = A::One;
+ let b = B::One;
+ match (a$0, b) {}
+}
+"#,
+ r#"
+enum A { One, Two }
+enum B { One, Two }
+
+fn main() {
+ let a = A::One;
+ let b = B::One;
+ match (a, b) {
+ $0(A::One, B::One) => todo!(),
+ (A::One, B::Two) => todo!(),
+ (A::Two, B::One) => todo!(),
+ (A::Two, B::Two) => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_tuple_of_enum_ref() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum A { One, Two }
+enum B { One, Two }
+
+fn main() {
+ let a = A::One;
+ let b = B::One;
+ match (&a$0, &b) {}
+}
+"#,
+ r#"
+enum A { One, Two }
+enum B { One, Two }
+
+fn main() {
+ let a = A::One;
+ let b = B::One;
+ match (&a, &b) {
+ $0(A::One, B::One) => todo!(),
+ (A::One, B::Two) => todo!(),
+ (A::Two, B::One) => todo!(),
+ (A::Two, B::Two) => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_tuple_of_enum_partial() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum A { One, Two }
+enum B { One, Two }
+
+fn main() {
+ let a = A::One;
+ let b = B::One;
+ match (a$0, b) {
+ (A::Two, B::One) => {}
+ }
+}
+"#,
+ r#"
+enum A { One, Two }
+enum B { One, Two }
+
+fn main() {
+ let a = A::One;
+ let b = B::One;
+ match (a, b) {
+ (A::Two, B::One) => {}
+ $0(A::One, B::One) => todo!(),
+ (A::One, B::Two) => todo!(),
+ (A::Two, B::Two) => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_tuple_of_enum_partial_with_wildcards() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+//- minicore: option
+fn main() {
+ let a = Some(1);
+ let b = Some(());
+ match (a$0, b) {
+ (Some(_), _) => {}
+ (None, Some(_)) => {}
+ }
+}
+"#,
+ r#"
+fn main() {
+ let a = Some(1);
+ let b = Some(());
+ match (a, b) {
+ (Some(_), _) => {}
+ (None, Some(_)) => {}
+ $0(None, None) => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_partial_with_deep_pattern() {
+ // Fixme: cannot handle deep patterns
+ check_assist_not_applicable(
+ add_missing_match_arms,
+ r#"
+//- minicore: option
+fn main() {
+ match $0Some(true) {
+ Some(true) => {}
+ None => {}
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_tuple_of_enum_not_applicable() {
+ check_assist_not_applicable(
+ add_missing_match_arms,
+ r#"
+enum A { One, Two }
+enum B { One, Two }
+
+fn main() {
+ let a = A::One;
+ let b = B::One;
+ match (a$0, b) {
+ (A::Two, B::One) => {}
+ (A::One, B::One) => {}
+ (A::One, B::Two) => {}
+ (A::Two, B::Two) => {}
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_single_element_tuple_of_enum() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum A { One, Two }
+
+fn main() {
+ let a = A::One;
+ match (a$0, ) {
+ }
+}
+"#,
+ r#"
+enum A { One, Two }
+
+fn main() {
+ let a = A::One;
+ match (a, ) {
+ $0(A::One,) => todo!(),
+ (A::Two,) => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_fill_match_arm_refs() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum A { As }
+
+fn foo(a: &A) {
+ match a$0 {
+ }
+}
+"#,
+ r#"
+enum A { As }
+
+fn foo(a: &A) {
+ match a {
+ $0A::As => todo!(),
+ }
+}
+"#,
+ );
+
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum A {
+ Es { x: usize, y: usize }
+}
+
+fn foo(a: &mut A) {
+ match a$0 {
+ }
+}
+"#,
+ r#"
+enum A {
+ Es { x: usize, y: usize }
+}
+
+fn foo(a: &mut A) {
+ match a {
+ $0A::Es { x, y } => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_target() {
+ check_assist_target(
+ add_missing_match_arms,
+ r#"
+enum E { X, Y }
+
+fn main() {
+ match E::X$0 {}
+}
+"#,
+ "match E::X {}",
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_trivial_arm() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum E { X, Y }
+
+fn main() {
+ match E::X {
+ $0_ => {}
+ }
+}
+"#,
+ r#"
+enum E { X, Y }
+
+fn main() {
+ match E::X {
+ $0E::X => todo!(),
+ E::Y => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_qualifies_path() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+mod foo { pub enum E { X, Y } }
+use foo::E::X;
+
+fn main() {
+ match X {
+ $0
+ }
+}
+"#,
+ r#"
+mod foo { pub enum E { X, Y } }
+use foo::E::X;
+
+fn main() {
+ match X {
+ $0X => todo!(),
+ foo::E::Y => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_preserves_comments() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum A { One, Two }
+fn foo(a: A) {
+ match a {
+ // foo bar baz$0
+ A::One => {}
+ // This is where the rest should be
+ }
+}
+"#,
+ r#"
+enum A { One, Two }
+fn foo(a: A) {
+ match a {
+ // foo bar baz
+ A::One => {}
+ $0A::Two => todo!(),
+ // This is where the rest should be
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_preserves_comments_empty() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum A { One, Two }
+fn foo(a: A) {
+ match a {
+ // foo bar baz$0
+ }
+}
+"#,
+ r#"
+enum A { One, Two }
+fn foo(a: A) {
+ match a {
+ $0A::One => todo!(),
+ A::Two => todo!(),
+ // foo bar baz
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn add_missing_match_arms_placeholder() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+enum A { One, Two, }
+fn foo(a: A) {
+ match a$0 {
+ _ => (),
+ }
+}
+"#,
+ r#"
+enum A { One, Two, }
+fn foo(a: A) {
+ match a {
+ $0A::One => todo!(),
+ A::Two => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn option_order() {
+ cov_mark::check!(option_order);
+ check_assist(
+ add_missing_match_arms,
+ r#"
+//- minicore: option
+fn foo(opt: Option<i32>) {
+ match opt$0 {
+ }
+}
+"#,
+ r#"
+fn foo(opt: Option<i32>) {
+ match opt {
+ Some(${0:_}) => todo!(),
+ None => todo!(),
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn works_inside_macro_call() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+macro_rules! m { ($expr:expr) => {$expr}}
+enum Test {
+ A,
+ B,
+ C,
+}
+
+fn foo(t: Test) {
+ m!(match t$0 {});
+}"#,
+ r#"
+macro_rules! m { ($expr:expr) => {$expr}}
+enum Test {
+ A,
+ B,
+ C,
+}
+
+fn foo(t: Test) {
+ m!(match t {
+ $0Test::A => todo!(),
+ Test::B => todo!(),
+ Test::C => todo!(),
+});
+}"#,
+ );
+ }
+
+ #[test]
+ fn lazy_computation() {
+ // Computing a single missing arm is enough to determine applicability of the assist.
+ cov_mark::check_count!(add_missing_match_arms_lazy_computation, 1);
+ check_assist_unresolved(
+ add_missing_match_arms,
+ r#"
+enum A { One, Two, }
+fn foo(tuple: (A, A)) {
+ match $0tuple {};
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn adds_comma_before_new_arms() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+fn foo(t: bool) {
+ match $0t {
+ true => 1 + 2
+ }
+}"#,
+ r#"
+fn foo(t: bool) {
+ match t {
+ true => 1 + 2,
+ $0false => todo!(),
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn does_not_add_extra_comma() {
+ check_assist(
+ add_missing_match_arms,
+ r#"
+fn foo(t: bool) {
+ match $0t {
+ true => 1 + 2,
+ }
+}"#,
+ r#"
+fn foo(t: bool) {
+ match t {
+ true => 1 + 2,
+ $0false => todo!(),
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn does_not_remove_catch_all_with_non_empty_expr() {
+ cov_mark::check!(add_missing_match_arms_empty_expr);
+ check_assist(
+ add_missing_match_arms,
+ r#"
+fn foo(t: bool) {
+ match $0t {
+ _ => 1 + 2,
+ }
+}"#,
+ r#"
+fn foo(t: bool) {
+ match t {
+ _ => 1 + 2,
+ $0true => todo!(),
+ false => todo!(),
+ }
+}"#,
+ );
+ }
+}
--- /dev/null
+use hir::HirDisplay;
+use syntax::{ast, AstNode, TextRange, TextSize};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: add_return_type
+//
+// Adds the return type to a function or closure inferred from its tail expression if it doesn't have a return
+// type specified. This assists is useable in a functions or closures tail expression or return type position.
+//
+// ```
+// fn foo() { 4$02i32 }
+// ```
+// ->
+// ```
+// fn foo() -> i32 { 42i32 }
+// ```
+pub(crate) fn add_return_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
+ let (fn_type, tail_expr, builder_edit_pos) = extract_tail(ctx)?;
+ let module = ctx.sema.scope(tail_expr.syntax()).module()?;
+ let ty = ctx.sema.type_of_expr(&tail_expr)?.adjusted();
+ if ty.is_unit() {
+ return None;
+ }
+ let ty = ty.display_source_code(ctx.db(), module.into()).ok()?;
+
+ acc.add(
+ AssistId("add_return_type", AssistKind::RefactorRewrite),
+ match fn_type {
+ FnType::Function => "Add this function's return type",
+ FnType::Closure { .. } => "Add this closure's return type",
+ },
+ tail_expr.syntax().text_range(),
+ |builder| {
+ match builder_edit_pos {
+ InsertOrReplace::Insert(insert_pos) => {
+ builder.insert(insert_pos, &format!("-> {} ", ty))
+ }
+ InsertOrReplace::Replace(text_range) => {
+ builder.replace(text_range, &format!("-> {}", ty))
+ }
+ }
+ if let FnType::Closure { wrap_expr: true } = fn_type {
+ cov_mark::hit!(wrap_closure_non_block_expr);
+ // `|x| x` becomes `|x| -> T x` which is invalid, so wrap it in a block
+ builder.replace(tail_expr.syntax().text_range(), &format!("{{{}}}", tail_expr));
+ }
+ },
+ )
+}
+
+enum InsertOrReplace {
+ Insert(TextSize),
+ Replace(TextRange),
+}
+
+/// Check the potentially already specified return type and reject it or turn it into a builder command
+/// if allowed.
+fn ret_ty_to_action(ret_ty: Option<ast::RetType>, insert_pos: TextSize) -> Option<InsertOrReplace> {
+ match ret_ty {
+ Some(ret_ty) => match ret_ty.ty() {
+ Some(ast::Type::InferType(_)) | None => {
+ cov_mark::hit!(existing_infer_ret_type);
+ cov_mark::hit!(existing_infer_ret_type_closure);
+ Some(InsertOrReplace::Replace(ret_ty.syntax().text_range()))
+ }
+ _ => {
+ cov_mark::hit!(existing_ret_type);
+ cov_mark::hit!(existing_ret_type_closure);
+ None
+ }
+ },
+ None => Some(InsertOrReplace::Insert(insert_pos + TextSize::from(1))),
+ }
+}
+
+enum FnType {
+ Function,
+ Closure { wrap_expr: bool },
+}
+
+fn extract_tail(ctx: &AssistContext) -> Option<(FnType, ast::Expr, InsertOrReplace)> {
+ let (fn_type, tail_expr, return_type_range, action) =
+ if let Some(closure) = ctx.find_node_at_offset::<ast::ClosureExpr>() {
+ let rpipe_pos = closure.param_list()?.syntax().last_token()?.text_range().end();
+ let action = ret_ty_to_action(closure.ret_type(), rpipe_pos)?;
+
+ let body = closure.body()?;
+ let body_start = body.syntax().first_token()?.text_range().start();
+ let (tail_expr, wrap_expr) = match body {
+ ast::Expr::BlockExpr(block) => (block.tail_expr()?, false),
+ body => (body, true),
+ };
+
+ let ret_range = TextRange::new(rpipe_pos, body_start);
+ (FnType::Closure { wrap_expr }, tail_expr, ret_range, action)
+ } else {
+ let func = ctx.find_node_at_offset::<ast::Fn>()?;
+ let rparen_pos = func.param_list()?.r_paren_token()?.text_range().end();
+ let action = ret_ty_to_action(func.ret_type(), rparen_pos)?;
+
+ let body = func.body()?;
+ let tail_expr = body.tail_expr()?;
+
+ let ret_range_end = body.l_curly_token()?.text_range().start();
+ let ret_range = TextRange::new(rparen_pos, ret_range_end);
+ (FnType::Function, tail_expr, ret_range, action)
+ };
+ let frange = ctx.frange.range;
+ if return_type_range.contains_range(frange) {
+ cov_mark::hit!(cursor_in_ret_position);
+ cov_mark::hit!(cursor_in_ret_position_closure);
+ } else if tail_expr.syntax().text_range().contains_range(frange) {
+ cov_mark::hit!(cursor_on_tail);
+ cov_mark::hit!(cursor_on_tail_closure);
+ } else {
+ return None;
+ }
+ Some((fn_type, tail_expr, action))
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn infer_return_type_specified_inferred() {
+ cov_mark::check!(existing_infer_ret_type);
+ check_assist(
+ add_return_type,
+ r#"fn foo() -> $0_ {
+ 45
+}"#,
+ r#"fn foo() -> i32 {
+ 45
+}"#,
+ );
+ }
+
+ #[test]
+ fn infer_return_type_specified_inferred_closure() {
+ cov_mark::check!(existing_infer_ret_type_closure);
+ check_assist(
+ add_return_type,
+ r#"fn foo() {
+ || -> _ {$045};
+}"#,
+ r#"fn foo() {
+ || -> i32 {45};
+}"#,
+ );
+ }
+
+ #[test]
+ fn infer_return_type_cursor_at_return_type_pos() {
+ cov_mark::check!(cursor_in_ret_position);
+ check_assist(
+ add_return_type,
+ r#"fn foo() $0{
+ 45
+}"#,
+ r#"fn foo() -> i32 {
+ 45
+}"#,
+ );
+ }
+
+ #[test]
+ fn infer_return_type_cursor_at_return_type_pos_closure() {
+ cov_mark::check!(cursor_in_ret_position_closure);
+ check_assist(
+ add_return_type,
+ r#"fn foo() {
+ || $045
+}"#,
+ r#"fn foo() {
+ || -> i32 {45}
+}"#,
+ );
+ }
+
+ #[test]
+ fn infer_return_type() {
+ cov_mark::check!(cursor_on_tail);
+ check_assist(
+ add_return_type,
+ r#"fn foo() {
+ 45$0
+}"#,
+ r#"fn foo() -> i32 {
+ 45
+}"#,
+ );
+ }
+
+ #[test]
+ fn infer_return_type_nested() {
+ check_assist(
+ add_return_type,
+ r#"fn foo() {
+ if true {
+ 3$0
+ } else {
+ 5
+ }
+}"#,
+ r#"fn foo() -> i32 {
+ if true {
+ 3
+ } else {
+ 5
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_ret_type_specified() {
+ cov_mark::check!(existing_ret_type);
+ check_assist_not_applicable(
+ add_return_type,
+ r#"fn foo() -> i32 {
+ ( 45$0 + 32 ) * 123
+}"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_non_tail_expr() {
+ check_assist_not_applicable(
+ add_return_type,
+ r#"fn foo() {
+ let x = $03;
+ ( 45 + 32 ) * 123
+}"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_unit_return_type() {
+ check_assist_not_applicable(
+ add_return_type,
+ r#"fn foo() {
+ ($0)
+}"#,
+ );
+ }
+
+ #[test]
+ fn infer_return_type_closure_block() {
+ cov_mark::check!(cursor_on_tail_closure);
+ check_assist(
+ add_return_type,
+ r#"fn foo() {
+ |x: i32| {
+ x$0
+ };
+}"#,
+ r#"fn foo() {
+ |x: i32| -> i32 {
+ x
+ };
+}"#,
+ );
+ }
+
+ #[test]
+ fn infer_return_type_closure() {
+ check_assist(
+ add_return_type,
+ r#"fn foo() {
+ |x: i32| { x$0 };
+}"#,
+ r#"fn foo() {
+ |x: i32| -> i32 { x };
+}"#,
+ );
+ }
+
+ #[test]
+ fn infer_return_type_closure_wrap() {
+ cov_mark::check!(wrap_closure_non_block_expr);
+ check_assist(
+ add_return_type,
+ r#"fn foo() {
+ |x: i32| x$0;
+}"#,
+ r#"fn foo() {
+ |x: i32| -> i32 {x};
+}"#,
+ );
+ }
+
+ #[test]
+ fn infer_return_type_nested_closure() {
+ check_assist(
+ add_return_type,
+ r#"fn foo() {
+ || {
+ if true {
+ 3$0
+ } else {
+ 5
+ }
+ }
+}"#,
+ r#"fn foo() {
+ || -> i32 {
+ if true {
+ 3
+ } else {
+ 5
+ }
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_ret_type_specified_closure() {
+ cov_mark::check!(existing_ret_type_closure);
+ check_assist_not_applicable(
+ add_return_type,
+ r#"fn foo() {
+ || -> i32 { 3$0 }
+}"#,
+ );
+ }
+
+ #[test]
+ fn not_applicable_non_tail_expr_closure() {
+ check_assist_not_applicable(
+ add_return_type,
+ r#"fn foo() {
+ || -> i32 {
+ let x = 3$0;
+ 6
+ }
+}"#,
+ );
+ }
+}
sema: &Semantics<RootDatabase>,
expr: &SyntaxNode,
) -> Option<(hir::Variant, hir::Variant)> {
- let fam = FamousDefs(&sema, sema.scope(expr).krate());
+ let fam = FamousDefs(sema, sema.scope(expr).krate());
let option_variants = fam.core_option_Option()?.variants(sema.db);
match &*option_variants {
&[variant0, variant1] => Some(if variant0.name(sema.db) == known::None {
invalid
});
if !invalid {
- for_each_tail_expr(&expr, &mut |e| {
+ for_each_tail_expr(expr, &mut |e| {
if invalid {
return;
}
use crate::{AssistContext, AssistId, AssistKind, Assists};
-/// Assist: line_to_block
-///
-/// Converts comments between block and single-line form
-///
-/// ```
-/// // Multi-line
-/// // comment
-/// ```
-/// ->
-/// ```
-/// /**
-/// Multi-line
-/// comment
-/// */
-/// ```
+// Assist: line_to_block
+//
+// Converts comments between block and single-line form.
+//
+// ```
+// // Multi-line$0
+// // comment
+// ```
+// ->
+// ```
+// /*
+// Multi-line
+// comment
+// */
+// ```
pub(crate) fn convert_comment_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let comment = ctx.find_token_at_offset::<ast::Comment>()?;
// Only allow comments which are alone on their line
+use hir::known;
use ide_db::helpers::FamousDefs;
+use stdx::format_to;
use syntax::{
- ast::{self, edit_in_place::Indent, make, ArgListOwner},
+ ast::{self, edit_in_place::Indent, make, ArgListOwner, LoopBodyOwner},
AstNode,
};
// }
// }
// ```
-
pub(crate) fn convert_iter_for_each_to_for(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let method = ctx.find_node_at_offset::<ast::MethodCallExpr>()?;
)
}
+// Assist: convert_for_loop_with_for_each
+//
+// Converts a for loop into a for_each loop on the Iterator.
+//
+// ```
+// fn main() {
+// let x = vec![1, 2, 3];
+// for$0 v in x {
+// let y = v * 2;
+// }
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// let x = vec![1, 2, 3];
+// x.into_iter().for_each(|v| {
+// let y = v * 2;
+// });
+// }
+// ```
+pub(crate) fn convert_for_loop_with_for_each(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
+ let for_loop = ctx.find_node_at_offset::<ast::ForExpr>()?;
+ let iterable = for_loop.iterable()?;
+ let pat = for_loop.pat()?;
+ let body = for_loop.loop_body()?;
+ if body.syntax().text_range().start() < ctx.offset() {
+ cov_mark::hit!(not_available_in_body);
+ return None;
+ }
+
+ acc.add(
+ AssistId("convert_for_loop_with_for_each", AssistKind::RefactorRewrite),
+ "Replace this for loop with `Iterator::for_each`",
+ for_loop.syntax().text_range(),
+ |builder| {
+ let mut buf = String::new();
+
+ if let Some((expr_behind_ref, method)) =
+ is_ref_and_impls_iter_method(&ctx.sema, &iterable)
+ {
+ // We have either "for x in &col" and col implements a method called iter
+ // or "for x in &mut col" and col implements a method called iter_mut
+ format_to!(buf, "{}.{}()", expr_behind_ref, method);
+ } else if let ast::Expr::RangeExpr(..) = iterable {
+ // range expressions need to be parenthesized for the syntax to be correct
+ format_to!(buf, "({})", iterable);
+ } else if impls_core_iter(&ctx.sema, &iterable) {
+ format_to!(buf, "{}", iterable);
+ } else if let ast::Expr::RefExpr(_) = iterable {
+ format_to!(buf, "({}).into_iter()", iterable);
+ } else {
+ format_to!(buf, "{}.into_iter()", iterable);
+ }
+
+ format_to!(buf, ".for_each(|{}| {});", pat, body);
+
+ builder.replace(for_loop.syntax().text_range(), buf)
+ },
+ )
+}
+
+/// If iterable is a reference where the expression behind the reference implements a method
+/// returning an Iterator called iter or iter_mut (depending on the type of reference) then return
+/// the expression behind the reference and the method name
+fn is_ref_and_impls_iter_method(
+ sema: &hir::Semantics<ide_db::RootDatabase>,
+ iterable: &ast::Expr,
+) -> Option<(ast::Expr, hir::Name)> {
+ let ref_expr = match iterable {
+ ast::Expr::RefExpr(r) => r,
+ _ => return None,
+ };
+ let wanted_method = if ref_expr.mut_token().is_some() { known::iter_mut } else { known::iter };
+ let expr_behind_ref = ref_expr.expr()?;
+ let ty = sema.type_of_expr(&expr_behind_ref)?.adjusted();
+ let scope = sema.scope(iterable.syntax());
+ let krate = scope.module()?.krate();
+ let traits_in_scope = scope.traits_in_scope();
+ let iter_trait = FamousDefs(sema, Some(krate)).core_iter_Iterator()?;
+
+ let has_wanted_method = ty
+ .iterate_method_candidates(
+ sema.db,
+ krate,
+ &traits_in_scope,
+ Some(&wanted_method),
+ |_, func| {
+ if func.ret_type(sema.db).impls_trait(sema.db, iter_trait, &[]) {
+ return Some(());
+ }
+ None
+ },
+ )
+ .is_some();
+ if !has_wanted_method {
+ return None;
+ }
+
+ Some((expr_behind_ref, wanted_method))
+}
+
+/// Whether iterable implements core::Iterator
+fn impls_core_iter(sema: &hir::Semantics<ide_db::RootDatabase>, iterable: &ast::Expr) -> bool {
+ let it_typ = match sema.type_of_expr(iterable) {
+ Some(it) => it.adjusted(),
+ None => return false,
+ };
+
+ let module = match sema.scope(iterable.syntax()).module() {
+ Some(it) => it,
+ None => return false,
+ };
+
+ let krate = module.krate();
+ match FamousDefs(sema, Some(krate)).core_iter_Iterator() {
+ Some(iter_trait) => {
+ cov_mark::hit!(test_already_impls_iterator);
+ it_typ.impls_trait(sema.db, iter_trait, &[])
+ }
+ None => false,
+ }
+}
+
fn validate_method_call_expr(
ctx: &AssistContext,
expr: ast::MethodCallExpr,
}"#,
)
}
+
+ #[test]
+ fn each_to_for_not_for() {
+ check_assist_not_applicable(
+ convert_for_loop_with_for_each,
+ r"
+let mut x = vec![1, 2, 3];
+x.iter_mut().$0for_each(|v| *v *= 2);
+ ",
+ )
+ }
+
+ #[test]
+ fn each_to_for_simple_for() {
+ check_assist(
+ convert_for_loop_with_for_each,
+ r"
+fn main() {
+ let x = vec![1, 2, 3];
+ for $0v in x {
+ v *= 2;
+ }
+}",
+ r"
+fn main() {
+ let x = vec![1, 2, 3];
+ x.into_iter().for_each(|v| {
+ v *= 2;
+ });
+}",
+ )
+ }
+
+ #[test]
+ fn each_to_for_for_in_range() {
+ check_assist(
+ convert_for_loop_with_for_each,
+ r#"
+//- minicore: range, iterators
+impl<T> core::iter::Iterator for core::ops::Range<T> {
+ type Item = T;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ None
+ }
+}
+
+fn main() {
+ for $0x in 0..92 {
+ print!("{}", x);
+ }
+}"#,
+ r#"
+impl<T> core::iter::Iterator for core::ops::Range<T> {
+ type Item = T;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ None
+ }
+}
+
+fn main() {
+ (0..92).for_each(|x| {
+ print!("{}", x);
+ });
+}"#,
+ )
+ }
+
+ #[test]
+ fn each_to_for_not_available_in_body() {
+ cov_mark::check!(not_available_in_body);
+ check_assist_not_applicable(
+ convert_for_loop_with_for_each,
+ r"
+fn main() {
+ let x = vec![1, 2, 3];
+ for v in x {
+ $0v *= 2;
+ }
+}",
+ )
+ }
+
+ #[test]
+ fn each_to_for_for_borrowed() {
+ check_assist(
+ convert_for_loop_with_for_each,
+ r#"
+//- minicore: iterators
+use core::iter::{Repeat, repeat};
+
+struct S;
+impl S {
+ fn iter(&self) -> Repeat<i32> { repeat(92) }
+ fn iter_mut(&mut self) -> Repeat<i32> { repeat(92) }
+}
+
+fn main() {
+ let x = S;
+ for $0v in &x {
+ let a = v * 2;
+ }
+}
+"#,
+ r#"
+use core::iter::{Repeat, repeat};
+
+struct S;
+impl S {
+ fn iter(&self) -> Repeat<i32> { repeat(92) }
+ fn iter_mut(&mut self) -> Repeat<i32> { repeat(92) }
+}
+
+fn main() {
+ let x = S;
+ x.iter().for_each(|v| {
+ let a = v * 2;
+ });
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn each_to_for_for_borrowed_no_iter_method() {
+ check_assist(
+ convert_for_loop_with_for_each,
+ r"
+struct NoIterMethod;
+fn main() {
+ let x = NoIterMethod;
+ for $0v in &x {
+ let a = v * 2;
+ }
+}
+",
+ r"
+struct NoIterMethod;
+fn main() {
+ let x = NoIterMethod;
+ (&x).into_iter().for_each(|v| {
+ let a = v * 2;
+ });
+}
+",
+ )
+ }
+
+ #[test]
+ fn each_to_for_for_borrowed_mut() {
+ check_assist(
+ convert_for_loop_with_for_each,
+ r#"
+//- minicore: iterators
+use core::iter::{Repeat, repeat};
+
+struct S;
+impl S {
+ fn iter(&self) -> Repeat<i32> { repeat(92) }
+ fn iter_mut(&mut self) -> Repeat<i32> { repeat(92) }
+}
+
+fn main() {
+ let x = S;
+ for $0v in &mut x {
+ let a = v * 2;
+ }
+}
+"#,
+ r#"
+use core::iter::{Repeat, repeat};
+
+struct S;
+impl S {
+ fn iter(&self) -> Repeat<i32> { repeat(92) }
+ fn iter_mut(&mut self) -> Repeat<i32> { repeat(92) }
+}
+
+fn main() {
+ let x = S;
+ x.iter_mut().for_each(|v| {
+ let a = v * 2;
+ });
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn each_to_for_for_borrowed_mut_behind_var() {
+ check_assist(
+ convert_for_loop_with_for_each,
+ r"
+fn main() {
+ let x = vec![1, 2, 3];
+ let y = &mut x;
+ for $0v in y {
+ *v *= 2;
+ }
+}",
+ r"
+fn main() {
+ let x = vec![1, 2, 3];
+ let y = &mut x;
+ y.into_iter().for_each(|v| {
+ *v *= 2;
+ });
+}",
+ )
+ }
+
+ #[test]
+ fn each_to_for_already_impls_iterator() {
+ cov_mark::check!(test_already_impls_iterator);
+ check_assist(
+ convert_for_loop_with_for_each,
+ r#"
+//- minicore: iterators
+fn main() {
+ for$0 a in core::iter::repeat(92).take(1) {
+ println!("{}", a);
+ }
+}
+"#,
+ r#"
+fn main() {
+ core::iter::repeat(92).take(1).for_each(|a| {
+ println!("{}", a);
+ });
+}
+"#,
+ );
+ }
}
} else {
edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text());
}
- strukt.semicolon_token().map(|t| edit.delete(t.text_range()));
+ if let Some(t) = strukt.semicolon_token() {
+ edit.delete(t.text_range());
+ }
} else {
edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text());
}
--- /dev/null
+use std::iter::once;
+
+use syntax::{
+ ast::{
+ self,
+ edit::{AstNodeEdit, IndentLevel},
+ make, LoopBodyOwner,
+ },
+ AstNode, T,
+};
+
+use crate::{
+ assist_context::{AssistContext, Assists},
+ utils::invert_boolean_expression,
+ AssistId, AssistKind,
+};
+
+// Assist: convert_while_to_loop
+//
+// Replace a while with a loop.
+//
+// ```
+// fn main() {
+// $0while cond {
+// foo();
+// }
+// }
+// ```
+// ->
+// ```
+// fn main() {
+// loop {
+// if !cond {
+// break;
+// }
+// foo();
+// }
+// }
+// ```
+pub(crate) fn convert_while_to_loop(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
+ let while_kw = ctx.find_token_syntax_at_offset(T![while])?;
+ let while_expr: ast::WhileExpr = while_kw.parent().and_then(ast::WhileExpr::cast)?;
+ let while_body = while_expr.loop_body()?;
+ let cond = while_expr.condition()?;
+
+ // Don't handle while let
+ if let Some(_) = cond.pat() {
+ return None;
+ };
+
+ let cond_expr = cond.expr()?;
+
+ let target = while_expr.syntax().text_range();
+ acc.add(
+ AssistId("convert_while_to_loop", AssistKind::RefactorRewrite),
+ "Convert while to loop",
+ target,
+ |edit| {
+ let while_indent_level = IndentLevel::from_node(while_expr.syntax());
+
+ let replacement = {
+ let if_expr = {
+ let cond = invert_boolean_expression(cond_expr);
+ let then_branch = make::block_expr(
+ once(make::expr_stmt(make::expr_break(None)).into()),
+ None,
+ );
+
+ make::expr_if(make::condition(cond, None), then_branch, None)
+ };
+
+ let if_expr = if_expr.indent(while_indent_level);
+ let stmts = once(make::expr_stmt(if_expr).into()).chain(while_body.statements());
+
+ let block_expr = make::block_expr(stmts, while_body.tail_expr());
+
+ let block_expr = block_expr.indent(while_indent_level);
+
+ make::expr_loop(block_expr)
+ };
+
+ edit.replace(target, replacement.syntax().text())
+ },
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn convert_inside_fn() {
+ check_assist(
+ convert_while_to_loop,
+ r#"
+fn main() {
+ while$0 cond {
+ foo();
+ }
+}
+"#,
+ r#"
+fn main() {
+ loop {
+ if !cond {
+ break;
+ }
+ foo();
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn convert_busy_wait() {
+ check_assist(
+ convert_while_to_loop,
+ r#"
+fn main() {
+ while$0 cond() {}
+}
+"#,
+ r#"
+fn main() {
+ loop {
+ if !cond() {
+ break;
+ }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn convert_trailing_expr() {
+ check_assist(
+ convert_while_to_loop,
+ r#"
+fn main() {
+ while$0 cond() {
+ bar()
+ }
+}
+"#,
+ r#"
+fn main() {
+ loop {
+ if !cond() {
+ break;
+ }
+ bar()
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn ignore_while_let() {
+ check_assist_not_applicable(
+ convert_while_to_loop,
+ r#"
+fn main() {
+ while$0 let Some(_) = foo() {
+ bar();
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn ignore_cursor_in_body() {
+ check_assist_not_applicable(
+ convert_while_to_loop,
+ r#"
+fn main() {
+ while cond {$0
+ bar();
+ }
+}
+"#,
+ );
+ }
+}
Some(
[Direction::Prev, Direction::Next]
.iter()
- .map(|dir| {
+ .flat_map(|dir| {
parent_use_item_syntax
.siblings(dir.to_owned())
.filter(|n| ast::Use::can_cast(n.kind()))
})
- .flatten()
- .filter_map(|n| Some(n.descendants().filter_map(ast::NameRef::cast)))
- .flatten()
+ .flat_map(|n| n.descendants().filter_map(ast::NameRef::cast))
.filter_map(|r| match NameRefClass::classify(&ctx.sema, &r)? {
NameRefClass::Definition(Definition::ModuleDef(def)) => Some(Def::ModuleDef(def)),
NameRefClass::Definition(Definition::Macro(def)) => Some(Def::MacroDef(def)),
/// checks if this expr requires `&mut` access, recurses on field access
fn expr_require_exclusive_access(ctx: &AssistContext, expr: &ast::Expr) -> Option<bool> {
- match expr {
- ast::Expr::MacroCall(_) => {
- // FIXME: expand macro and check output for mutable usages of the variable?
- return None;
- }
- _ => (),
+ if let ast::Expr::MacroCall(_) = expr {
+ // FIXME: expand macro and check output for mutable usages of the variable?
+ return None;
}
let parent = expr.syntax().parent()?;
let variant_attrs = attrs_and_docs(variant.syntax())
.map(|tok| match tok.kind() {
WHITESPACE => make::tokens::single_newline().into(),
- _ => tok.into(),
+ _ => tok,
})
.collect();
ted::insert_all(Position::first_child_of(strukt.syntax()), variant_attrs);
Some(gpl) => {
let gpl = gpl.clone_for_update();
gpl.generic_params().for_each(|gp| {
- match gp {
+ let tbl = match gp {
ast::GenericParam::LifetimeParam(it) => it.type_bound_list(),
ast::GenericParam::TypeParam(it) => it.type_bound_list(),
ast::GenericParam::ConstParam(_) => return,
+ };
+ if let Some(tbl) = tbl {
+ tbl.remove();
}
- .map(|it| it.remove());
});
make::ty(&format!("{}<{}>", name.text(), gpl.generic_params().join(", ")))
}
if let Anchor::Replace(stmt) = anchor {
cov_mark::hit!(test_extract_var_expr_stmt);
if stmt.semicolon_token().is_none() {
- buf.push_str(";");
+ buf.push(';');
}
match ctx.config.snippet_cap {
Some(cap) => {
return;
}
- buf.push_str(";");
+ buf.push(';');
// We want to maintain the indent level,
// but we do not want to duplicate possible
+++ /dev/null
-use std::iter::{self, Peekable};
-
-use either::Either;
-use hir::{Adt, HasSource, ModuleDef, Semantics};
-use ide_db::helpers::{mod_path_to_ast, FamousDefs};
-use ide_db::RootDatabase;
-use itertools::Itertools;
-use syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat};
-
-use crate::{
- utils::{self, render_snippet, Cursor},
- AssistContext, AssistId, AssistKind, Assists,
-};
-
-// Assist: fill_match_arms
-//
-// Adds missing clauses to a `match` expression.
-//
-// ```
-// enum Action { Move { distance: u32 }, Stop }
-//
-// fn handle(action: Action) {
-// match action {
-// $0
-// }
-// }
-// ```
-// ->
-// ```
-// enum Action { Move { distance: u32 }, Stop }
-//
-// fn handle(action: Action) {
-// match action {
-// $0Action::Move { distance } => todo!(),
-// Action::Stop => todo!(),
-// }
-// }
-// ```
-pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
- let match_expr = ctx.find_node_at_offset_with_descend::<ast::MatchExpr>()?;
- let match_arm_list = match_expr.match_arm_list()?;
-
- let expr = match_expr.expr()?;
-
- let mut arms: Vec<MatchArm> = match_arm_list.arms().collect();
- if let [arm] = arms.as_slice() {
- if let Some(Pat::WildcardPat(..)) = arm.pat() {
- arms.clear();
- }
- }
-
- let top_lvl_pats: Vec<_> = arms
- .iter()
- .filter_map(ast::MatchArm::pat)
- .flat_map(|pat| match pat {
- // Special case OrPat as separate top-level pats
- Pat::OrPat(or_pat) => Either::Left(or_pat.pats()),
- _ => Either::Right(iter::once(pat)),
- })
- // Exclude top level wildcards so that they are expanded by this assist, retains status quo in #8129.
- .filter(|pat| !matches!(pat, Pat::WildcardPat(_)))
- .collect();
-
- let module = ctx.sema.scope(expr.syntax()).module()?;
-
- let mut missing_pats: Peekable<Box<dyn Iterator<Item = ast::Pat>>> = if let Some(enum_def) =
- resolve_enum_def(&ctx.sema, &expr)
- {
- let variants = enum_def.variants(ctx.db());
-
- let missing_pats = variants
- .into_iter()
- .filter_map(|variant| build_pat(ctx.db(), module, variant))
- .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat));
-
- let option_enum =
- FamousDefs(&ctx.sema, Some(module.krate())).core_option_Option().map(lift_enum);
- let missing_pats: Box<dyn Iterator<Item = _>> = if Some(enum_def) == option_enum {
- // Match `Some` variant first.
- cov_mark::hit!(option_order);
- Box::new(missing_pats.rev())
- } else {
- Box::new(missing_pats)
- };
- missing_pats.peekable()
- } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) {
- let mut n_arms = 1;
- let variants_of_enums: Vec<Vec<ExtendedVariant>> = enum_defs
- .into_iter()
- .map(|enum_def| enum_def.variants(ctx.db()))
- .inspect(|variants| n_arms *= variants.len())
- .collect();
-
- // When calculating the match arms for a tuple of enums, we want
- // to create a match arm for each possible combination of enum
- // values. The `multi_cartesian_product` method transforms
- // Vec<Vec<EnumVariant>> into Vec<(EnumVariant, .., EnumVariant)>
- // where each tuple represents a proposed match arm.
-
- // A number of arms grows very fast on even a small tuple of large enums.
- // We skip the assist beyond an arbitrary threshold.
- if n_arms > 256 {
- return None;
- }
- let missing_pats = variants_of_enums
- .into_iter()
- .multi_cartesian_product()
- .inspect(|_| cov_mark::hit!(fill_match_arms_lazy_computation))
- .map(|variants| {
- let patterns =
- variants.into_iter().filter_map(|variant| build_pat(ctx.db(), module, variant));
- ast::Pat::from(make::tuple_pat(patterns))
- })
- .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat));
- (Box::new(missing_pats) as Box<dyn Iterator<Item = _>>).peekable()
- } else {
- return None;
- };
-
- if missing_pats.peek().is_none() {
- return None;
- }
-
- let target = ctx.sema.original_range(match_expr.syntax()).range;
- acc.add(
- AssistId("fill_match_arms", AssistKind::QuickFix),
- "Fill match arms",
- target,
- |builder| {
- let new_match_arm_list = match_arm_list.clone_for_update();
- let missing_arms = missing_pats
- .map(|pat| make::match_arm(iter::once(pat), None, make::ext::expr_todo()))
- .map(|it| it.clone_for_update());
-
- let catch_all_arm = new_match_arm_list
- .arms()
- .find(|arm| matches!(arm.pat(), Some(ast::Pat::WildcardPat(_))));
- if let Some(arm) = catch_all_arm {
- let is_empty_expr = arm.expr().map_or(true, |e| match e {
- ast::Expr::BlockExpr(b) => {
- b.statements().next().is_none() && b.tail_expr().is_none()
- }
- ast::Expr::TupleExpr(t) => t.fields().next().is_none(),
- _ => false,
- });
- if is_empty_expr {
- arm.remove();
- } else {
- cov_mark::hit!(fill_match_arms_empty_expr);
- }
- }
- let mut first_new_arm = None;
- for arm in missing_arms {
- first_new_arm.get_or_insert_with(|| arm.clone());
- new_match_arm_list.add_arm(arm);
- }
-
- let old_range = ctx.sema.original_range(match_arm_list.syntax()).range;
- match (first_new_arm, ctx.config.snippet_cap) {
- (Some(first_new_arm), Some(cap)) => {
- let extend_lifetime;
- let cursor =
- match first_new_arm.syntax().descendants().find_map(ast::WildcardPat::cast)
- {
- Some(it) => {
- extend_lifetime = it.syntax().clone();
- Cursor::Replace(&extend_lifetime)
- }
- None => Cursor::Before(first_new_arm.syntax()),
- };
- let snippet = render_snippet(cap, new_match_arm_list.syntax(), cursor);
- builder.replace_snippet(cap, old_range, snippet);
- }
- _ => builder.replace(old_range, new_match_arm_list.to_string()),
- }
- },
- )
-}
-
-fn is_variant_missing(existing_pats: &[Pat], var: &Pat) -> bool {
- !existing_pats.iter().any(|pat| does_pat_match_variant(pat, var))
-}
-
-// Fixme: this is still somewhat limited, use hir_ty::diagnostics::match_check?
-fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool {
- match (pat, var) {
- (Pat::WildcardPat(_), _) => true,
- (Pat::TuplePat(tpat), Pat::TuplePat(tvar)) => {
- tpat.fields().zip(tvar.fields()).all(|(p, v)| does_pat_match_variant(&p, &v))
- }
- _ => utils::does_pat_match_variant(pat, var),
- }
-}
-
-#[derive(Eq, PartialEq, Clone, Copy)]
-enum ExtendedEnum {
- Bool,
- Enum(hir::Enum),
-}
-
-#[derive(Eq, PartialEq, Clone, Copy)]
-enum ExtendedVariant {
- True,
- False,
- Variant(hir::Variant),
-}
-
-fn lift_enum(e: hir::Enum) -> ExtendedEnum {
- ExtendedEnum::Enum(e)
-}
-
-impl ExtendedEnum {
- fn variants(self, db: &RootDatabase) -> Vec<ExtendedVariant> {
- match self {
- ExtendedEnum::Enum(e) => {
- e.variants(db).into_iter().map(ExtendedVariant::Variant).collect::<Vec<_>>()
- }
- ExtendedEnum::Bool => {
- Vec::<ExtendedVariant>::from([ExtendedVariant::True, ExtendedVariant::False])
- }
- }
- }
-}
-
-fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<ExtendedEnum> {
- sema.type_of_expr(expr)?.adjusted().autoderef(sema.db).find_map(|ty| match ty.as_adt() {
- Some(Adt::Enum(e)) => Some(ExtendedEnum::Enum(e)),
- _ => ty.is_bool().then(|| ExtendedEnum::Bool),
- })
-}
-
-fn resolve_tuple_of_enum_def(
- sema: &Semantics<RootDatabase>,
- expr: &ast::Expr,
-) -> Option<Vec<ExtendedEnum>> {
- sema.type_of_expr(expr)?
- .adjusted()
- .tuple_fields(sema.db)
- .iter()
- .map(|ty| {
- ty.autoderef(sema.db).find_map(|ty| match ty.as_adt() {
- Some(Adt::Enum(e)) => Some(lift_enum(e)),
- // For now we only handle expansion for a tuple of enums. Here
- // we map non-enum items to None and rely on `collect` to
- // convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>.
- _ => ty.is_bool().then(|| ExtendedEnum::Bool),
- })
- })
- .collect()
-}
-
-fn build_pat(db: &RootDatabase, module: hir::Module, var: ExtendedVariant) -> Option<ast::Pat> {
- match var {
- ExtendedVariant::Variant(var) => {
- let path = mod_path_to_ast(&module.find_use_path(db, ModuleDef::from(var))?);
-
- // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though
- let pat: ast::Pat = match var.source(db)?.value.kind() {
- ast::StructKind::Tuple(field_list) => {
- let pats =
- iter::repeat(make::wildcard_pat().into()).take(field_list.fields().count());
- make::tuple_struct_pat(path, pats).into()
- }
- ast::StructKind::Record(field_list) => {
- let pats = field_list
- .fields()
- .map(|f| make::ext::simple_ident_pat(f.name().unwrap()).into());
- make::record_pat(path, pats).into()
- }
- ast::StructKind::Unit => make::path_pat(path),
- };
-
- Some(pat)
- }
- ExtendedVariant::True => Some(ast::Pat::from(make::literal_pat("true"))),
- ExtendedVariant::False => Some(ast::Pat::from(make::literal_pat("false"))),
- }
-}
-
-#[cfg(test)]
-mod tests {
- use crate::tests::{
- check_assist, check_assist_not_applicable, check_assist_target, check_assist_unresolved,
- };
-
- use super::fill_match_arms;
-
- #[test]
- fn all_match_arms_provided() {
- check_assist_not_applicable(
- fill_match_arms,
- r#"
-enum A {
- As,
- Bs{x:i32, y:Option<i32>},
- Cs(i32, Option<i32>),
-}
-fn main() {
- match A::As$0 {
- A::As,
- A::Bs{x,y:Some(_)} => {}
- A::Cs(_, Some(_)) => {}
- }
-}
- "#,
- );
- }
-
- #[test]
- fn all_boolean_match_arms_provided() {
- check_assist_not_applicable(
- fill_match_arms,
- r#"
-fn foo(a: bool) {
- match a$0 {
- true => {}
- false => {}
- }
-}
-"#,
- )
- }
-
- #[test]
- fn tuple_of_non_enum() {
- // for now this case is not handled, although it potentially could be
- // in the future
- check_assist_not_applicable(
- fill_match_arms,
- r#"
-fn main() {
- match (0, false)$0 {
- }
-}
-"#,
- );
- }
-
- #[test]
- fn fill_match_arms_boolean() {
- check_assist(
- fill_match_arms,
- r#"
-fn foo(a: bool) {
- match a$0 {
- }
-}
-"#,
- r#"
-fn foo(a: bool) {
- match a {
- $0true => todo!(),
- false => todo!(),
- }
-}
-"#,
- )
- }
-
- #[test]
- fn partial_fill_boolean() {
- check_assist(
- fill_match_arms,
- r#"
-fn foo(a: bool) {
- match a$0 {
- true => {}
- }
-}
-"#,
- r#"
-fn foo(a: bool) {
- match a {
- true => {}
- $0false => todo!(),
- }
-}
-"#,
- )
- }
-
- #[test]
- fn all_boolean_tuple_arms_provided() {
- check_assist_not_applicable(
- fill_match_arms,
- r#"
-fn foo(a: bool) {
- match (a, a)$0 {
- (true, true) => {}
- (true, false) => {}
- (false, true) => {}
- (false, false) => {}
- }
-}
-"#,
- )
- }
-
- #[test]
- fn fill_boolean_tuple() {
- check_assist(
- fill_match_arms,
- r#"
-fn foo(a: bool) {
- match (a, a)$0 {
- }
-}
-"#,
- r#"
-fn foo(a: bool) {
- match (a, a) {
- $0(true, true) => todo!(),
- (true, false) => todo!(),
- (false, true) => todo!(),
- (false, false) => todo!(),
- }
-}
-"#,
- )
- }
-
- #[test]
- fn partial_fill_boolean_tuple() {
- check_assist(
- fill_match_arms,
- r#"
-fn foo(a: bool) {
- match (a, a)$0 {
- (false, true) => {}
- }
-}
-"#,
- r#"
-fn foo(a: bool) {
- match (a, a) {
- (false, true) => {}
- $0(true, true) => todo!(),
- (true, false) => todo!(),
- (false, false) => todo!(),
- }
-}
-"#,
- )
- }
-
- #[test]
- fn partial_fill_record_tuple() {
- check_assist(
- fill_match_arms,
- r#"
-enum A {
- As,
- Bs { x: i32, y: Option<i32> },
- Cs(i32, Option<i32>),
-}
-fn main() {
- match A::As$0 {
- A::Bs { x, y: Some(_) } => {}
- A::Cs(_, Some(_)) => {}
- }
-}
-"#,
- r#"
-enum A {
- As,
- Bs { x: i32, y: Option<i32> },
- Cs(i32, Option<i32>),
-}
-fn main() {
- match A::As {
- A::Bs { x, y: Some(_) } => {}
- A::Cs(_, Some(_)) => {}
- $0A::As => todo!(),
- }
-}
-"#,
- );
- }
-
- #[test]
- fn partial_fill_option() {
- check_assist(
- fill_match_arms,
- r#"
-//- minicore: option
-fn main() {
- match None$0 {
- None => {}
- }
-}
-"#,
- r#"
-fn main() {
- match None {
- None => {}
- Some(${0:_}) => todo!(),
- }
-}
-"#,
- );
- }
-
- #[test]
- fn partial_fill_or_pat() {
- check_assist(
- fill_match_arms,
- r#"
-enum A { As, Bs, Cs(Option<i32>) }
-fn main() {
- match A::As$0 {
- A::Cs(_) | A::Bs => {}
- }
-}
-"#,
- r#"
-enum A { As, Bs, Cs(Option<i32>) }
-fn main() {
- match A::As {
- A::Cs(_) | A::Bs => {}
- $0A::As => todo!(),
- }
-}
-"#,
- );
- }
-
- #[test]
- fn partial_fill() {
- check_assist(
- fill_match_arms,
- r#"
-enum A { As, Bs, Cs, Ds(String), Es(B) }
-enum B { Xs, Ys }
-fn main() {
- match A::As$0 {
- A::Bs if 0 < 1 => {}
- A::Ds(_value) => { let x = 1; }
- A::Es(B::Xs) => (),
- }
-}
-"#,
- r#"
-enum A { As, Bs, Cs, Ds(String), Es(B) }
-enum B { Xs, Ys }
-fn main() {
- match A::As {
- A::Bs if 0 < 1 => {}
- A::Ds(_value) => { let x = 1; }
- A::Es(B::Xs) => (),
- $0A::As => todo!(),
- A::Cs => todo!(),
- }
-}
-"#,
- );
- }
-
- #[test]
- fn partial_fill_bind_pat() {
- check_assist(
- fill_match_arms,
- r#"
-enum A { As, Bs, Cs(Option<i32>) }
-fn main() {
- match A::As$0 {
- A::As(_) => {}
- a @ A::Bs(_) => {}
- }
-}
-"#,
- r#"
-enum A { As, Bs, Cs(Option<i32>) }
-fn main() {
- match A::As {
- A::As(_) => {}
- a @ A::Bs(_) => {}
- A::Cs(${0:_}) => todo!(),
- }
-}
-"#,
- );
- }
-
- #[test]
- fn fill_match_arms_empty_body() {
- check_assist(
- fill_match_arms,
- r#"
-enum A { As, Bs, Cs(String), Ds(String, String), Es { x: usize, y: usize } }
-
-fn main() {
- let a = A::As;
- match a$0 {}
-}
-"#,
- r#"
-enum A { As, Bs, Cs(String), Ds(String, String), Es { x: usize, y: usize } }
-
-fn main() {
- let a = A::As;
- match a {
- $0A::As => todo!(),
- A::Bs => todo!(),
- A::Cs(_) => todo!(),
- A::Ds(_, _) => todo!(),
- A::Es { x, y } => todo!(),
- }
-}
-"#,
- );
- }
-
- #[test]
- fn fill_match_arms_tuple_of_enum() {
- check_assist(
- fill_match_arms,
- r#"
-enum A { One, Two }
-enum B { One, Two }
-
-fn main() {
- let a = A::One;
- let b = B::One;
- match (a$0, b) {}
-}
-"#,
- r#"
-enum A { One, Two }
-enum B { One, Two }
-
-fn main() {
- let a = A::One;
- let b = B::One;
- match (a, b) {
- $0(A::One, B::One) => todo!(),
- (A::One, B::Two) => todo!(),
- (A::Two, B::One) => todo!(),
- (A::Two, B::Two) => todo!(),
- }
-}
-"#,
- );
- }
-
- #[test]
- fn fill_match_arms_tuple_of_enum_ref() {
- check_assist(
- fill_match_arms,
- r#"
-enum A { One, Two }
-enum B { One, Two }
-
-fn main() {
- let a = A::One;
- let b = B::One;
- match (&a$0, &b) {}
-}
-"#,
- r#"
-enum A { One, Two }
-enum B { One, Two }
-
-fn main() {
- let a = A::One;
- let b = B::One;
- match (&a, &b) {
- $0(A::One, B::One) => todo!(),
- (A::One, B::Two) => todo!(),
- (A::Two, B::One) => todo!(),
- (A::Two, B::Two) => todo!(),
- }
-}
-"#,
- );
- }
-
- #[test]
- fn fill_match_arms_tuple_of_enum_partial() {
- check_assist(
- fill_match_arms,
- r#"
-enum A { One, Two }
-enum B { One, Two }
-
-fn main() {
- let a = A::One;
- let b = B::One;
- match (a$0, b) {
- (A::Two, B::One) => {}
- }
-}
-"#,
- r#"
-enum A { One, Two }
-enum B { One, Two }
-
-fn main() {
- let a = A::One;
- let b = B::One;
- match (a, b) {
- (A::Two, B::One) => {}
- $0(A::One, B::One) => todo!(),
- (A::One, B::Two) => todo!(),
- (A::Two, B::Two) => todo!(),
- }
-}
-"#,
- );
- }
-
- #[test]
- fn fill_match_arms_tuple_of_enum_partial_with_wildcards() {
- check_assist(
- fill_match_arms,
- r#"
-//- minicore: option
-fn main() {
- let a = Some(1);
- let b = Some(());
- match (a$0, b) {
- (Some(_), _) => {}
- (None, Some(_)) => {}
- }
-}
-"#,
- r#"
-fn main() {
- let a = Some(1);
- let b = Some(());
- match (a, b) {
- (Some(_), _) => {}
- (None, Some(_)) => {}
- $0(None, None) => todo!(),
- }
-}
-"#,
- );
- }
-
- #[test]
- fn fill_match_arms_partial_with_deep_pattern() {
- // Fixme: cannot handle deep patterns
- check_assist_not_applicable(
- fill_match_arms,
- r#"
-//- minicore: option
-fn main() {
- match $0Some(true) {
- Some(true) => {}
- None => {}
- }
-}
-"#,
- );
- }
-
- #[test]
- fn fill_match_arms_tuple_of_enum_not_applicable() {
- check_assist_not_applicable(
- fill_match_arms,
- r#"
-enum A { One, Two }
-enum B { One, Two }
-
-fn main() {
- let a = A::One;
- let b = B::One;
- match (a$0, b) {
- (A::Two, B::One) => {}
- (A::One, B::One) => {}
- (A::One, B::Two) => {}
- (A::Two, B::Two) => {}
- }
-}
-"#,
- );
- }
-
- #[test]
- fn fill_match_arms_single_element_tuple_of_enum() {
- check_assist(
- fill_match_arms,
- r#"
-enum A { One, Two }
-
-fn main() {
- let a = A::One;
- match (a$0, ) {
- }
-}
-"#,
- r#"
-enum A { One, Two }
-
-fn main() {
- let a = A::One;
- match (a, ) {
- $0(A::One,) => todo!(),
- (A::Two,) => todo!(),
- }
-}
-"#,
- );
- }
-
- #[test]
- fn test_fill_match_arm_refs() {
- check_assist(
- fill_match_arms,
- r#"
-enum A { As }
-
-fn foo(a: &A) {
- match a$0 {
- }
-}
-"#,
- r#"
-enum A { As }
-
-fn foo(a: &A) {
- match a {
- $0A::As => todo!(),
- }
-}
-"#,
- );
-
- check_assist(
- fill_match_arms,
- r#"
-enum A {
- Es { x: usize, y: usize }
-}
-
-fn foo(a: &mut A) {
- match a$0 {
- }
-}
-"#,
- r#"
-enum A {
- Es { x: usize, y: usize }
-}
-
-fn foo(a: &mut A) {
- match a {
- $0A::Es { x, y } => todo!(),
- }
-}
-"#,
- );
- }
-
- #[test]
- fn fill_match_arms_target() {
- check_assist_target(
- fill_match_arms,
- r#"
-enum E { X, Y }
-
-fn main() {
- match E::X$0 {}
-}
-"#,
- "match E::X {}",
- );
- }
-
- #[test]
- fn fill_match_arms_trivial_arm() {
- check_assist(
- fill_match_arms,
- r#"
-enum E { X, Y }
-
-fn main() {
- match E::X {
- $0_ => {}
- }
-}
-"#,
- r#"
-enum E { X, Y }
-
-fn main() {
- match E::X {
- $0E::X => todo!(),
- E::Y => todo!(),
- }
-}
-"#,
- );
- }
-
- #[test]
- fn fill_match_arms_qualifies_path() {
- check_assist(
- fill_match_arms,
- r#"
-mod foo { pub enum E { X, Y } }
-use foo::E::X;
-
-fn main() {
- match X {
- $0
- }
-}
-"#,
- r#"
-mod foo { pub enum E { X, Y } }
-use foo::E::X;
-
-fn main() {
- match X {
- $0X => todo!(),
- foo::E::Y => todo!(),
- }
-}
-"#,
- );
- }
-
- #[test]
- fn fill_match_arms_preserves_comments() {
- check_assist(
- fill_match_arms,
- r#"
-enum A { One, Two }
-fn foo(a: A) {
- match a {
- // foo bar baz$0
- A::One => {}
- // This is where the rest should be
- }
-}
-"#,
- r#"
-enum A { One, Two }
-fn foo(a: A) {
- match a {
- // foo bar baz
- A::One => {}
- $0A::Two => todo!(),
- // This is where the rest should be
- }
-}
-"#,
- );
- }
-
- #[test]
- fn fill_match_arms_preserves_comments_empty() {
- check_assist(
- fill_match_arms,
- r#"
-enum A { One, Two }
-fn foo(a: A) {
- match a {
- // foo bar baz$0
- }
-}
-"#,
- r#"
-enum A { One, Two }
-fn foo(a: A) {
- match a {
- $0A::One => todo!(),
- A::Two => todo!(),
- // foo bar baz
- }
-}
-"#,
- );
- }
-
- #[test]
- fn fill_match_arms_placeholder() {
- check_assist(
- fill_match_arms,
- r#"
-enum A { One, Two, }
-fn foo(a: A) {
- match a$0 {
- _ => (),
- }
-}
-"#,
- r#"
-enum A { One, Two, }
-fn foo(a: A) {
- match a {
- $0A::One => todo!(),
- A::Two => todo!(),
- }
-}
-"#,
- );
- }
-
- #[test]
- fn option_order() {
- cov_mark::check!(option_order);
- check_assist(
- fill_match_arms,
- r#"
-//- minicore: option
-fn foo(opt: Option<i32>) {
- match opt$0 {
- }
-}
-"#,
- r#"
-fn foo(opt: Option<i32>) {
- match opt {
- Some(${0:_}) => todo!(),
- None => todo!(),
- }
-}
-"#,
- );
- }
-
- #[test]
- fn works_inside_macro_call() {
- check_assist(
- fill_match_arms,
- r#"
-macro_rules! m { ($expr:expr) => {$expr}}
-enum Test {
- A,
- B,
- C,
-}
-
-fn foo(t: Test) {
- m!(match t$0 {});
-}"#,
- r#"
-macro_rules! m { ($expr:expr) => {$expr}}
-enum Test {
- A,
- B,
- C,
-}
-
-fn foo(t: Test) {
- m!(match t {
- $0Test::A => todo!(),
- Test::B => todo!(),
- Test::C => todo!(),
-});
-}"#,
- );
- }
-
- #[test]
- fn lazy_computation() {
- // Computing a single missing arm is enough to determine applicability of the assist.
- cov_mark::check_count!(fill_match_arms_lazy_computation, 1);
- check_assist_unresolved(
- fill_match_arms,
- r#"
-enum A { One, Two, }
-fn foo(tuple: (A, A)) {
- match $0tuple {};
-}
-"#,
- );
- }
-
- #[test]
- fn adds_comma_before_new_arms() {
- check_assist(
- fill_match_arms,
- r#"
-fn foo(t: bool) {
- match $0t {
- true => 1 + 2
- }
-}"#,
- r#"
-fn foo(t: bool) {
- match t {
- true => 1 + 2,
- $0false => todo!(),
- }
-}"#,
- );
- }
-
- #[test]
- fn does_not_add_extra_comma() {
- check_assist(
- fill_match_arms,
- r#"
-fn foo(t: bool) {
- match $0t {
- true => 1 + 2,
- }
-}"#,
- r#"
-fn foo(t: bool) {
- match t {
- true => 1 + 2,
- $0false => todo!(),
- }
-}"#,
- );
- }
-
- #[test]
- fn does_not_remove_catch_all_with_non_empty_expr() {
- cov_mark::check!(fill_match_arms_empty_expr);
- check_assist(
- fill_match_arms,
- r#"
-fn foo(t: bool) {
- match $0t {
- _ => 1 + 2,
- }
-}"#,
- r#"
-fn foo(t: bool) {
- match t {
- _ => 1 + 2,
- $0true => todo!(),
- false => todo!(),
- }
-}"#,
- );
- }
-}
-use hir::{HasSource, HirDisplay, Module, TypeInfo};
-use ide_db::{base_db::FileId, helpers::SnippetCap};
+use hir::{HasSource, HirDisplay, Module, ModuleDef, Semantics, TypeInfo};
+use ide_db::{
+ base_db::FileId,
+ defs::{Definition, NameRefClass},
+ helpers::SnippetCap,
+ RootDatabase,
+};
use rustc_hash::{FxHashMap, FxHashSet};
use stdx::to_lower_snake_case;
use syntax::{
let path_expr: ast::PathExpr = ctx.find_node_at_offset()?;
let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
let path = path_expr.path()?;
- let fn_name = fn_name(&path)?;
+ let name_ref = path.segment()?.name_ref()?;
if ctx.sema.resolve_path(&path).is_some() {
// The function call already resolves, no need to add a function
return None;
}
+ let fn_name = &*name_ref.text();
let target_module;
let mut adt_name = None;
if current_module.krate() != module.krate() {
return None;
}
- let (impl_, file) = get_adt_source(ctx, &adt, fn_name.text().as_str())?;
+ let (impl_, file) = get_adt_source(ctx, &adt, fn_name)?;
let (target, insert_offset) = get_method_target(ctx, &module, &impl_)?;
adt_name = if impl_.is_none() { Some(adt.name(ctx.sema.db)) } else { None };
(target, file, insert_offset)
get_fn_target(ctx, &target_module, call.clone())?
}
};
- let function_builder = FunctionBuilder::from_call(ctx, &call, &path, target_module, target)?;
+ let function_builder = FunctionBuilder::from_call(ctx, &call, fn_name, target_module, target)?;
let text_range = call.syntax().text_range();
- let label = format!("Generate {} function", function_builder.fn_name.clone());
+ let label = format!("Generate {} function", function_builder.fn_name);
add_func_to_accumulator(
acc,
ctx,
FunctionBuilder::from_method_call(ctx, &call, &fn_name, target_module, target)?;
let text_range = call.syntax().text_range();
let adt_name = if impl_.is_none() { Some(adt.name(ctx.sema.db)) } else { None };
- let label = format!("Generate {} method", function_builder.fn_name.clone());
+ let label = format!("Generate {} method", function_builder.fn_name);
add_func_to_accumulator(
acc,
ctx,
fn from_call(
ctx: &AssistContext,
call: &ast::CallExpr,
- path: &ast::Path,
+ fn_name: &str,
target_module: Option<hir::Module>,
target: GeneratedFunctionTarget,
) -> Option<Self> {
let needs_pub = target_module.is_some();
let target_module = target_module.or_else(|| current_module(target.syntax(), ctx))?;
- let fn_name = fn_name(path)?;
+ let fn_name = make::name(fn_name);
let (type_params, params) = fn_args(ctx, target_module, FuncExpr::Func(call.clone()))?;
let await_expr = call.syntax().parent().and_then(ast::AwaitExpr::cast);
}
}
};
- let ret_type = ret_ty.map(|rt| make::ret_type(rt));
+ let ret_type = ret_ty.map(make::ret_type);
(ret_type, should_focus_return_type)
}
file = in_file;
target
}
- None => next_space_for_fn_after_call_site(FuncExpr::Func(call.clone()))?,
+ None => next_space_for_fn_after_call_site(FuncExpr::Func(call))?,
};
Some((target.clone(), file, get_insert_offset(&target)))
}
impl_: &Option<ast::Impl>,
) -> Option<(GeneratedFunctionTarget, TextSize)> {
let target = match impl_ {
- Some(impl_) => next_space_for_fn_in_impl(&impl_)?,
+ Some(impl_) => next_space_for_fn_in_impl(impl_)?,
None => {
next_space_for_fn_in_module(ctx.sema.db, &target_module.definition_source(ctx.sema.db))?
.1
}
}
-fn fn_name(call: &ast::Path) -> Option<ast::Name> {
- let name = call.segment()?.syntax().to_string();
- Some(make::name(&name))
-}
-
/// Computes the type variables and arguments required for the generated function
fn fn_args(
ctx: &AssistContext,
let mut arg_names = Vec::new();
let mut arg_types = Vec::new();
for arg in call.arg_list()?.args() {
- arg_names.push(match fn_arg_name(&arg) {
- Some(name) => name,
- None => String::from("arg"),
- });
+ arg_names.push(fn_arg_name(&ctx.sema, &arg));
arg_types.push(match fn_arg_type(ctx, target_module, &arg) {
Some(ty) => {
- if ty.len() > 0 && ty.starts_with('&') {
+ if !ty.is_empty() && ty.starts_with('&') {
if let Some((new_ty, _)) = useless_type_special_case("", &ty[1..].to_owned()) {
new_ty
} else {
}
}
-fn fn_arg_name(fn_arg: &ast::Expr) -> Option<String> {
- match fn_arg {
- ast::Expr::CastExpr(cast_expr) => fn_arg_name(&cast_expr.expr()?),
- _ => {
- let s = fn_arg
- .syntax()
- .descendants()
- .filter(|d| ast::NameRef::can_cast(d.kind()))
- .last()?
- .to_string();
- Some(to_lower_snake_case(&s))
+fn fn_arg_name(sema: &Semantics<RootDatabase>, arg_expr: &ast::Expr) -> String {
+ let name = (|| match arg_expr {
+ ast::Expr::CastExpr(cast_expr) => Some(fn_arg_name(sema, &cast_expr.expr()?)),
+ expr => {
+ let name_ref = expr.syntax().descendants().filter_map(ast::NameRef::cast).last()?;
+ if let Some(NameRefClass::Definition(Definition::ModuleDef(
+ ModuleDef::Const(_) | ModuleDef::Static(_),
+ ))) = NameRefClass::classify(sema, &name_ref)
+ {
+ return Some(name_ref.to_string().to_lowercase());
+ };
+ Some(to_lower_snake_case(&name_ref.to_string()))
}
+ })();
+ match name {
+ Some(mut name) if name.starts_with(|c: char| c.is_ascii_digit()) => {
+ name.insert_str(0, "arg");
+ name
+ }
+ Some(name) => name,
+ None => "arg".to_string(),
}
}
todo!()
}
}
+",
+ )
+ }
+
+ #[test]
+ fn no_panic_on_invalid_global_path() {
+ check_assist(
+ generate_function,
+ r"
+fn main() {
+ ::foo$0();
+}
+",
+ r"
+fn main() {
+ ::foo();
+}
+
+fn foo() ${0:-> _} {
+ todo!()
+}
+",
+ )
+ }
+
+ #[test]
+ fn handle_tuple_indexing() {
+ check_assist(
+ generate_function,
+ r"
+fn main() {
+ let a = ((),);
+ foo$0(a.0);
+}
+",
+ r"
+fn main() {
+ let a = ((),);
+ foo(a.0);
+}
+
+fn foo(arg0: ()) ${0:-> _} {
+ todo!()
+}
+",
+ )
+ }
+
+ #[test]
+ fn add_function_with_const_arg() {
+ check_assist(
+ generate_function,
+ r"
+const VALUE: usize = 0;
+fn main() {
+ foo$0(VALUE);
+}
+",
+ r"
+const VALUE: usize = 0;
+fn main() {
+ foo(VALUE);
+}
+
+fn foo(value: usize) ${0:-> _} {
+ todo!()
+}
+",
+ )
+ }
+
+ #[test]
+ fn add_function_with_static_arg() {
+ check_assist(
+ generate_function,
+ r"
+static VALUE: usize = 0;
+fn main() {
+ foo$0(VALUE);
+}
+",
+ r"
+static VALUE: usize = 0;
+fn main() {
+ foo(VALUE);
+}
+
+fn foo(value: usize) ${0:-> _} {
+ todo!()
+}
+",
+ )
+ }
+
+ #[test]
+ fn add_function_with_static_mut_arg() {
+ check_assist(
+ generate_function,
+ r"
+static mut VALUE: usize = 0;
+fn main() {
+ foo$0(VALUE);
+}
+",
+ r"
+static mut VALUE: usize = 0;
+fn main() {
+ foo(VALUE);
+}
+
+fn foo(value: usize) ${0:-> _} {
+ todo!()
+}
",
)
}
+++ /dev/null
-use hir::HirDisplay;
-use syntax::{ast, AstNode, TextRange, TextSize};
-
-use crate::{AssistContext, AssistId, AssistKind, Assists};
-
-// Assist: infer_function_return_type
-//
-// Adds the return type to a function or closure inferred from its tail expression if it doesn't have a return
-// type specified. This assists is useable in a functions or closures tail expression or return type position.
-//
-// ```
-// fn foo() { 4$02i32 }
-// ```
-// ->
-// ```
-// fn foo() -> i32 { 42i32 }
-// ```
-pub(crate) fn infer_function_return_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
- let (fn_type, tail_expr, builder_edit_pos) = extract_tail(ctx)?;
- let module = ctx.sema.scope(tail_expr.syntax()).module()?;
- let ty = ctx.sema.type_of_expr(&tail_expr)?.adjusted();
- if ty.is_unit() {
- return None;
- }
- let ty = ty.display_source_code(ctx.db(), module.into()).ok()?;
-
- acc.add(
- AssistId("infer_function_return_type", AssistKind::RefactorRewrite),
- match fn_type {
- FnType::Function => "Add this function's return type",
- FnType::Closure { .. } => "Add this closure's return type",
- },
- tail_expr.syntax().text_range(),
- |builder| {
- match builder_edit_pos {
- InsertOrReplace::Insert(insert_pos) => {
- builder.insert(insert_pos, &format!("-> {} ", ty))
- }
- InsertOrReplace::Replace(text_range) => {
- builder.replace(text_range, &format!("-> {}", ty))
- }
- }
- if let FnType::Closure { wrap_expr: true } = fn_type {
- cov_mark::hit!(wrap_closure_non_block_expr);
- // `|x| x` becomes `|x| -> T x` which is invalid, so wrap it in a block
- builder.replace(tail_expr.syntax().text_range(), &format!("{{{}}}", tail_expr));
- }
- },
- )
-}
-
-enum InsertOrReplace {
- Insert(TextSize),
- Replace(TextRange),
-}
-
-/// Check the potentially already specified return type and reject it or turn it into a builder command
-/// if allowed.
-fn ret_ty_to_action(ret_ty: Option<ast::RetType>, insert_pos: TextSize) -> Option<InsertOrReplace> {
- match ret_ty {
- Some(ret_ty) => match ret_ty.ty() {
- Some(ast::Type::InferType(_)) | None => {
- cov_mark::hit!(existing_infer_ret_type);
- cov_mark::hit!(existing_infer_ret_type_closure);
- Some(InsertOrReplace::Replace(ret_ty.syntax().text_range()))
- }
- _ => {
- cov_mark::hit!(existing_ret_type);
- cov_mark::hit!(existing_ret_type_closure);
- None
- }
- },
- None => Some(InsertOrReplace::Insert(insert_pos + TextSize::from(1))),
- }
-}
-
-enum FnType {
- Function,
- Closure { wrap_expr: bool },
-}
-
-fn extract_tail(ctx: &AssistContext) -> Option<(FnType, ast::Expr, InsertOrReplace)> {
- let (fn_type, tail_expr, return_type_range, action) =
- if let Some(closure) = ctx.find_node_at_offset::<ast::ClosureExpr>() {
- let rpipe_pos = closure.param_list()?.syntax().last_token()?.text_range().end();
- let action = ret_ty_to_action(closure.ret_type(), rpipe_pos)?;
-
- let body = closure.body()?;
- let body_start = body.syntax().first_token()?.text_range().start();
- let (tail_expr, wrap_expr) = match body {
- ast::Expr::BlockExpr(block) => (block.tail_expr()?, false),
- body => (body, true),
- };
-
- let ret_range = TextRange::new(rpipe_pos, body_start);
- (FnType::Closure { wrap_expr }, tail_expr, ret_range, action)
- } else {
- let func = ctx.find_node_at_offset::<ast::Fn>()?;
- let rparen_pos = func.param_list()?.r_paren_token()?.text_range().end();
- let action = ret_ty_to_action(func.ret_type(), rparen_pos)?;
-
- let body = func.body()?;
- let tail_expr = body.tail_expr()?;
-
- let ret_range_end = body.l_curly_token()?.text_range().start();
- let ret_range = TextRange::new(rparen_pos, ret_range_end);
- (FnType::Function, tail_expr, ret_range, action)
- };
- let frange = ctx.frange.range;
- if return_type_range.contains_range(frange) {
- cov_mark::hit!(cursor_in_ret_position);
- cov_mark::hit!(cursor_in_ret_position_closure);
- } else if tail_expr.syntax().text_range().contains_range(frange) {
- cov_mark::hit!(cursor_on_tail);
- cov_mark::hit!(cursor_on_tail_closure);
- } else {
- return None;
- }
- Some((fn_type, tail_expr, action))
-}
-
-#[cfg(test)]
-mod tests {
- use crate::tests::{check_assist, check_assist_not_applicable};
-
- use super::*;
-
- #[test]
- fn infer_return_type_specified_inferred() {
- cov_mark::check!(existing_infer_ret_type);
- check_assist(
- infer_function_return_type,
- r#"fn foo() -> $0_ {
- 45
-}"#,
- r#"fn foo() -> i32 {
- 45
-}"#,
- );
- }
-
- #[test]
- fn infer_return_type_specified_inferred_closure() {
- cov_mark::check!(existing_infer_ret_type_closure);
- check_assist(
- infer_function_return_type,
- r#"fn foo() {
- || -> _ {$045};
-}"#,
- r#"fn foo() {
- || -> i32 {45};
-}"#,
- );
- }
-
- #[test]
- fn infer_return_type_cursor_at_return_type_pos() {
- cov_mark::check!(cursor_in_ret_position);
- check_assist(
- infer_function_return_type,
- r#"fn foo() $0{
- 45
-}"#,
- r#"fn foo() -> i32 {
- 45
-}"#,
- );
- }
-
- #[test]
- fn infer_return_type_cursor_at_return_type_pos_closure() {
- cov_mark::check!(cursor_in_ret_position_closure);
- check_assist(
- infer_function_return_type,
- r#"fn foo() {
- || $045
-}"#,
- r#"fn foo() {
- || -> i32 {45}
-}"#,
- );
- }
-
- #[test]
- fn infer_return_type() {
- cov_mark::check!(cursor_on_tail);
- check_assist(
- infer_function_return_type,
- r#"fn foo() {
- 45$0
-}"#,
- r#"fn foo() -> i32 {
- 45
-}"#,
- );
- }
-
- #[test]
- fn infer_return_type_nested() {
- check_assist(
- infer_function_return_type,
- r#"fn foo() {
- if true {
- 3$0
- } else {
- 5
- }
-}"#,
- r#"fn foo() -> i32 {
- if true {
- 3
- } else {
- 5
- }
-}"#,
- );
- }
-
- #[test]
- fn not_applicable_ret_type_specified() {
- cov_mark::check!(existing_ret_type);
- check_assist_not_applicable(
- infer_function_return_type,
- r#"fn foo() -> i32 {
- ( 45$0 + 32 ) * 123
-}"#,
- );
- }
-
- #[test]
- fn not_applicable_non_tail_expr() {
- check_assist_not_applicable(
- infer_function_return_type,
- r#"fn foo() {
- let x = $03;
- ( 45 + 32 ) * 123
-}"#,
- );
- }
-
- #[test]
- fn not_applicable_unit_return_type() {
- check_assist_not_applicable(
- infer_function_return_type,
- r#"fn foo() {
- ($0)
-}"#,
- );
- }
-
- #[test]
- fn infer_return_type_closure_block() {
- cov_mark::check!(cursor_on_tail_closure);
- check_assist(
- infer_function_return_type,
- r#"fn foo() {
- |x: i32| {
- x$0
- };
-}"#,
- r#"fn foo() {
- |x: i32| -> i32 {
- x
- };
-}"#,
- );
- }
-
- #[test]
- fn infer_return_type_closure() {
- check_assist(
- infer_function_return_type,
- r#"fn foo() {
- |x: i32| { x$0 };
-}"#,
- r#"fn foo() {
- |x: i32| -> i32 { x };
-}"#,
- );
- }
-
- #[test]
- fn infer_return_type_closure_wrap() {
- cov_mark::check!(wrap_closure_non_block_expr);
- check_assist(
- infer_function_return_type,
- r#"fn foo() {
- |x: i32| x$0;
-}"#,
- r#"fn foo() {
- |x: i32| -> i32 {x};
-}"#,
- );
- }
-
- #[test]
- fn infer_return_type_nested_closure() {
- check_assist(
- infer_function_return_type,
- r#"fn foo() {
- || {
- if true {
- 3$0
- } else {
- 5
- }
- }
-}"#,
- r#"fn foo() {
- || -> i32 {
- if true {
- 3
- } else {
- 5
- }
- }
-}"#,
- );
- }
-
- #[test]
- fn not_applicable_ret_type_specified_closure() {
- cov_mark::check!(existing_ret_type_closure);
- check_assist_not_applicable(
- infer_function_return_type,
- r#"fn foo() {
- || -> i32 { 3$0 }
-}"#,
- );
- }
-
- #[test]
- fn not_applicable_non_tail_expr_closure() {
- check_assist_not_applicable(
- infer_function_return_type,
- r#"fn foo() {
- || -> i32 {
- let x = 3$0;
- 6
- }
-}"#,
- );
- }
-}
.sema
.type_of_expr(&expr)
.filter(TypeInfo::has_adjustment)
- .and_then(|_| param_ty);
+ .and(param_ty);
body.push_front(
make::let_stmt(pat, ty, Some(expr)).clone_for_update().into(),
)
| ast::Expr::BreakExpr(_)
| ast::Expr::ReturnExpr(_)
| ast::Expr::MatchExpr(_)
+ | ast::Expr::BlockExpr(_)
);
Some((range, name_ref, !(initializer || parent)))
})
--- /dev/null
+use syntax::{
+ ast::{self, edit_in_place::GenericParamsOwnerEdit, make, AstNode},
+ ted,
+};
+
+use crate::{utils::suggest_name, AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: introduce_named_generic
+//
+// Replaces `impl Trait` function argument with the named generic.
+//
+// ```
+// fn foo(bar: $0impl Bar) {}
+// ```
+// ->
+// ```
+// fn foo<B: Bar>(bar: B) {}
+// ```
+pub(crate) fn introduce_named_generic(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
+ let impl_trait_type = ctx.find_node_at_offset::<ast::ImplTraitType>()?;
+ let param = impl_trait_type.syntax().parent().and_then(ast::Param::cast)?;
+ let fn_ = param.syntax().ancestors().find_map(ast::Fn::cast)?;
+
+ let type_bound_list = impl_trait_type.type_bound_list()?;
+
+ let target = fn_.syntax().text_range();
+ acc.add(
+ AssistId("introduce_named_generic", AssistKind::RefactorRewrite),
+ "Replace impl trait with generic",
+ target,
+ |edit| {
+ let impl_trait_type = edit.make_mut(impl_trait_type);
+ let fn_ = edit.make_mut(fn_);
+
+ let type_param_name = suggest_name::for_generic_parameter(&impl_trait_type);
+
+ let type_param = make::type_param(make::name(&type_param_name), Some(type_bound_list))
+ .clone_for_update();
+ let new_ty = make::ty(&type_param_name).clone_for_update();
+
+ ted::replace(impl_trait_type.syntax(), new_ty.syntax());
+ fn_.get_or_create_generic_param_list().add_generic_param(type_param.into())
+ },
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::tests::check_assist;
+
+ #[test]
+ fn introduce_named_generic_params() {
+ check_assist(
+ introduce_named_generic,
+ r#"fn foo<G>(bar: $0impl Bar) {}"#,
+ r#"fn foo<G, B: Bar>(bar: B) {}"#,
+ );
+ }
+
+ #[test]
+ fn replace_impl_trait_without_generic_params() {
+ check_assist(
+ introduce_named_generic,
+ r#"fn foo(bar: $0impl Bar) {}"#,
+ r#"fn foo<B: Bar>(bar: B) {}"#,
+ );
+ }
+
+ #[test]
+ fn replace_two_impl_trait_with_generic_params() {
+ check_assist(
+ introduce_named_generic,
+ r#"fn foo<G>(foo: impl Foo, bar: $0impl Bar) {}"#,
+ r#"fn foo<G, B: Bar>(foo: impl Foo, bar: B) {}"#,
+ );
+ }
+
+ #[test]
+ fn replace_impl_trait_with_empty_generic_params() {
+ check_assist(
+ introduce_named_generic,
+ r#"fn foo<>(bar: $0impl Bar) {}"#,
+ r#"fn foo<B: Bar>(bar: B) {}"#,
+ );
+ }
+
+ #[test]
+ fn replace_impl_trait_with_empty_multiline_generic_params() {
+ check_assist(
+ introduce_named_generic,
+ r#"
+fn foo<
+>(bar: $0impl Bar) {}
+"#,
+ r#"
+fn foo<B: Bar
+>(bar: B) {}
+"#,
+ );
+ }
+
+ #[test]
+ fn replace_impl_trait_with_exist_generic_letter() {
+ // FIXME: This is wrong, we should pick a different name if the one we
+ // want is already bound.
+ check_assist(
+ introduce_named_generic,
+ r#"fn foo<B>(bar: $0impl Bar) {}"#,
+ r#"fn foo<B, B: Bar>(bar: B) {}"#,
+ );
+ }
+
+ #[test]
+ fn replace_impl_trait_with_multiline_generic_params() {
+ check_assist(
+ introduce_named_generic,
+ r#"
+fn foo<
+ G: Foo,
+ F,
+ H,
+>(bar: $0impl Bar) {}
+"#,
+ r#"
+fn foo<
+ G: Foo,
+ F,
+ H, B: Bar,
+>(bar: B) {}
+"#,
+ );
+ }
+
+ #[test]
+ fn replace_impl_trait_multiple() {
+ check_assist(
+ introduce_named_generic,
+ r#"fn foo(bar: $0impl Foo + Bar) {}"#,
+ r#"fn foo<F: Foo + Bar>(bar: F) {}"#,
+ );
+ }
+}
})
.collect();
match fn_params_without_lifetime.len() {
- 1 => Some(fn_params_without_lifetime.into_iter().nth(0)?),
+ 1 => Some(fn_params_without_lifetime.into_iter().next()?),
0 => None,
// multiple unnnamed is invalid. assist is not applicable
_ => return None,
make::lifetime_param(new_lifetime_param.clone()).clone_for_update().into(),
);
ted::replace(lifetime.syntax(), new_lifetime_param.clone_for_update().syntax());
- loc_needing_lifetime
- .map(|position| ted::insert(position, new_lifetime_param.clone_for_update().syntax()));
+ if let Some(position) = loc_needing_lifetime {
+ ted::insert(position, new_lifetime_param.clone_for_update().syntax());
+ }
})
}
pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let match_arm = ctx.find_node_at_offset::<MatchArm>()?;
let guard = match_arm.guard()?;
+ if ctx.offset() > guard.syntax().text_range().end() {
+ cov_mark::hit!(move_guard_unapplicable_in_arm_body);
+ return None;
+ }
let space_before_guard = guard.syntax().prev_sibling_or_token();
// FIXME: support `if let` guards too
use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
+ #[test]
+ fn move_guard_to_arm_body_range() {
+ cov_mark::check!(move_guard_unapplicable_in_arm_body);
+ check_assist_not_applicable(
+ move_guard_to_arm_body,
+ r#"
+fn main() {
+ match 92 {
+ x if x > 10 => $0false,
+ _ => true
+ }
+}
+"#,
+ );
+ }
#[test]
fn move_guard_to_arm_body_target() {
check_assist_target(
if matches!(value, Cow::Borrowed(_)) {
// Avoid replacing the whole string to better position the cursor.
edit.insert(token.syntax().text_range().start(), format!("r{}", hashes));
- edit.insert(token.syntax().text_range().end(), format!("{}", hashes));
+ edit.insert(token.syntax().text_range().end(), hashes);
} else {
edit.replace(
token.syntax().text_range(),
return Some(range_to_remove(arg.syntax()));
}
- return None;
+ None
}
fn range_to_remove(node: &SyntaxNode) -> TextRange {
+++ /dev/null
-use ast::LoopBodyOwner;
-use hir::known;
-use ide_db::helpers::FamousDefs;
-use stdx::format_to;
-use syntax::{ast, AstNode};
-
-use crate::{AssistContext, AssistId, AssistKind, Assists};
-
-// Assist: replace_for_loop_with_for_each
-//
-// Converts a for loop into a for_each loop on the Iterator.
-//
-// ```
-// fn main() {
-// let x = vec![1, 2, 3];
-// for$0 v in x {
-// let y = v * 2;
-// }
-// }
-// ```
-// ->
-// ```
-// fn main() {
-// let x = vec![1, 2, 3];
-// x.into_iter().for_each(|v| {
-// let y = v * 2;
-// });
-// }
-// ```
-pub(crate) fn replace_for_loop_with_for_each(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
- let for_loop = ctx.find_node_at_offset::<ast::ForExpr>()?;
- let iterable = for_loop.iterable()?;
- let pat = for_loop.pat()?;
- let body = for_loop.loop_body()?;
- if body.syntax().text_range().start() < ctx.offset() {
- cov_mark::hit!(not_available_in_body);
- return None;
- }
-
- acc.add(
- AssistId("replace_for_loop_with_for_each", AssistKind::RefactorRewrite),
- "Replace this for loop with `Iterator::for_each`",
- for_loop.syntax().text_range(),
- |builder| {
- let mut buf = String::new();
-
- if let Some((expr_behind_ref, method)) =
- is_ref_and_impls_iter_method(&ctx.sema, &iterable)
- {
- // We have either "for x in &col" and col implements a method called iter
- // or "for x in &mut col" and col implements a method called iter_mut
- format_to!(buf, "{}.{}()", expr_behind_ref, method);
- } else if let ast::Expr::RangeExpr(..) = iterable {
- // range expressions need to be parenthesized for the syntax to be correct
- format_to!(buf, "({})", iterable);
- } else if impls_core_iter(&ctx.sema, &iterable) {
- format_to!(buf, "{}", iterable);
- } else if let ast::Expr::RefExpr(_) = iterable {
- format_to!(buf, "({}).into_iter()", iterable);
- } else {
- format_to!(buf, "{}.into_iter()", iterable);
- }
-
- format_to!(buf, ".for_each(|{}| {});", pat, body);
-
- builder.replace(for_loop.syntax().text_range(), buf)
- },
- )
-}
-
-/// If iterable is a reference where the expression behind the reference implements a method
-/// returning an Iterator called iter or iter_mut (depending on the type of reference) then return
-/// the expression behind the reference and the method name
-fn is_ref_and_impls_iter_method(
- sema: &hir::Semantics<ide_db::RootDatabase>,
- iterable: &ast::Expr,
-) -> Option<(ast::Expr, hir::Name)> {
- let ref_expr = match iterable {
- ast::Expr::RefExpr(r) => r,
- _ => return None,
- };
- let wanted_method = if ref_expr.mut_token().is_some() { known::iter_mut } else { known::iter };
- let expr_behind_ref = ref_expr.expr()?;
- let ty = sema.type_of_expr(&expr_behind_ref)?.adjusted();
- let scope = sema.scope(iterable.syntax());
- let krate = scope.module()?.krate();
- let traits_in_scope = scope.traits_in_scope();
- let iter_trait = FamousDefs(sema, Some(krate)).core_iter_Iterator()?;
-
- let has_wanted_method = ty
- .iterate_method_candidates(
- sema.db,
- krate,
- &traits_in_scope,
- Some(&wanted_method),
- |_, func| {
- if func.ret_type(sema.db).impls_trait(sema.db, iter_trait, &[]) {
- return Some(());
- }
- None
- },
- )
- .is_some();
- if !has_wanted_method {
- return None;
- }
-
- Some((expr_behind_ref, wanted_method))
-}
-
-/// Whether iterable implements core::Iterator
-fn impls_core_iter(sema: &hir::Semantics<ide_db::RootDatabase>, iterable: &ast::Expr) -> bool {
- let it_typ = match sema.type_of_expr(iterable) {
- Some(it) => it.adjusted(),
- None => return false,
- };
-
- let module = match sema.scope(iterable.syntax()).module() {
- Some(it) => it,
- None => return false,
- };
-
- let krate = module.krate();
- match FamousDefs(sema, Some(krate)).core_iter_Iterator() {
- Some(iter_trait) => {
- cov_mark::hit!(test_already_impls_iterator);
- it_typ.impls_trait(sema.db, iter_trait, &[])
- }
- None => false,
- }
-}
-
-#[cfg(test)]
-mod tests {
- use crate::tests::{check_assist, check_assist_not_applicable};
-
- use super::*;
-
- #[test]
- fn test_not_for() {
- check_assist_not_applicable(
- replace_for_loop_with_for_each,
- r"
-let mut x = vec![1, 2, 3];
-x.iter_mut().$0for_each(|v| *v *= 2);
- ",
- )
- }
-
- #[test]
- fn test_simple_for() {
- check_assist(
- replace_for_loop_with_for_each,
- r"
-fn main() {
- let x = vec![1, 2, 3];
- for $0v in x {
- v *= 2;
- }
-}",
- r"
-fn main() {
- let x = vec![1, 2, 3];
- x.into_iter().for_each(|v| {
- v *= 2;
- });
-}",
- )
- }
-
- #[test]
- fn test_for_in_range() {
- check_assist(
- replace_for_loop_with_for_each,
- r#"
-//- minicore: range, iterators
-impl<T> core::iter::Iterator for core::ops::Range<T> {
- type Item = T;
-
- fn next(&mut self) -> Option<Self::Item> {
- None
- }
-}
-
-fn main() {
- for $0x in 0..92 {
- print!("{}", x);
- }
-}"#,
- r#"
-impl<T> core::iter::Iterator for core::ops::Range<T> {
- type Item = T;
-
- fn next(&mut self) -> Option<Self::Item> {
- None
- }
-}
-
-fn main() {
- (0..92).for_each(|x| {
- print!("{}", x);
- });
-}"#,
- )
- }
-
- #[test]
- fn not_available_in_body() {
- cov_mark::check!(not_available_in_body);
- check_assist_not_applicable(
- replace_for_loop_with_for_each,
- r"
-fn main() {
- let x = vec![1, 2, 3];
- for v in x {
- $0v *= 2;
- }
-}",
- )
- }
-
- #[test]
- fn test_for_borrowed() {
- check_assist(
- replace_for_loop_with_for_each,
- r#"
-//- minicore: iterators
-use core::iter::{Repeat, repeat};
-
-struct S;
-impl S {
- fn iter(&self) -> Repeat<i32> { repeat(92) }
- fn iter_mut(&mut self) -> Repeat<i32> { repeat(92) }
-}
-
-fn main() {
- let x = S;
- for $0v in &x {
- let a = v * 2;
- }
-}
-"#,
- r#"
-use core::iter::{Repeat, repeat};
-
-struct S;
-impl S {
- fn iter(&self) -> Repeat<i32> { repeat(92) }
- fn iter_mut(&mut self) -> Repeat<i32> { repeat(92) }
-}
-
-fn main() {
- let x = S;
- x.iter().for_each(|v| {
- let a = v * 2;
- });
-}
-"#,
- )
- }
-
- #[test]
- fn test_for_borrowed_no_iter_method() {
- check_assist(
- replace_for_loop_with_for_each,
- r"
-struct NoIterMethod;
-fn main() {
- let x = NoIterMethod;
- for $0v in &x {
- let a = v * 2;
- }
-}
-",
- r"
-struct NoIterMethod;
-fn main() {
- let x = NoIterMethod;
- (&x).into_iter().for_each(|v| {
- let a = v * 2;
- });
-}
-",
- )
- }
-
- #[test]
- fn test_for_borrowed_mut() {
- check_assist(
- replace_for_loop_with_for_each,
- r#"
-//- minicore: iterators
-use core::iter::{Repeat, repeat};
-
-struct S;
-impl S {
- fn iter(&self) -> Repeat<i32> { repeat(92) }
- fn iter_mut(&mut self) -> Repeat<i32> { repeat(92) }
-}
-
-fn main() {
- let x = S;
- for $0v in &mut x {
- let a = v * 2;
- }
-}
-"#,
- r#"
-use core::iter::{Repeat, repeat};
-
-struct S;
-impl S {
- fn iter(&self) -> Repeat<i32> { repeat(92) }
- fn iter_mut(&mut self) -> Repeat<i32> { repeat(92) }
-}
-
-fn main() {
- let x = S;
- x.iter_mut().for_each(|v| {
- let a = v * 2;
- });
-}
-"#,
- )
- }
-
- #[test]
- fn test_for_borrowed_mut_behind_var() {
- check_assist(
- replace_for_loop_with_for_each,
- r"
-fn main() {
- let x = vec![1, 2, 3];
- let y = &mut x;
- for $0v in y {
- *v *= 2;
- }
-}",
- r"
-fn main() {
- let x = vec![1, 2, 3];
- let y = &mut x;
- y.into_iter().for_each(|v| {
- *v *= 2;
- });
-}",
- )
- }
-
- #[test]
- fn test_already_impls_iterator() {
- cov_mark::check!(test_already_impls_iterator);
- check_assist(
- replace_for_loop_with_for_each,
- r#"
-//- minicore: iterators
-fn main() {
- for$0 a in core::iter::repeat(92).take(1) {
- println!("{}", a);
- }
-}
-"#,
- r#"
-fn main() {
- core::iter::repeat(92).take(1).for_each(|a| {
- println!("{}", a);
- });
-}
-"#,
- );
- }
-}
edit::{AstNodeEdit, IndentLevel},
make, NameOwner,
},
- AstNode,
+ AstNode, TextRange,
};
use crate::{
// ```
pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
+ let available_range = TextRange::new(
+ if_expr.syntax().text_range().start(),
+ if_expr.then_branch()?.syntax().text_range().start(),
+ );
+ let cursor_in_range = available_range.contains_range(ctx.frange.range);
+ if !cursor_in_range {
+ return None;
+ }
let mut else_block = None;
let if_exprs = successors(Some(if_expr.clone()), |expr| match expr.else_branch()? {
ast::ElseBranch::IfExpr(expr) => Some(expr),
return None;
}
- let target = if_expr.syntax().text_range();
acc.add(
AssistId("replace_if_let_with_match", AssistKind::RefactorRewrite),
"Replace if let with match",
- target,
+ available_range,
move |edit| {
let match_expr = {
let else_arm = make_else_arm(ctx, else_block, &cond_bodies);
if let Some(else_block) = else_block {
let pattern = if let [(Either::Left(pat), _)] = conditionals {
ctx.sema
- .type_of_pat(&pat)
+ .type_of_pat(pat)
.and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty.adjusted()))
.zip(Some(pat))
} else {
};
let pattern = match pattern {
Some((it, pat)) => {
- if does_pat_match_variant(&pat, &it.sad_pattern()) {
+ if does_pat_match_variant(pat, &it.sad_pattern()) {
it.happy_pattern()
} else {
it.sad_pattern()
};
make::match_arm(iter::once(pattern), None, unwrap_trivial_block(else_block))
} else {
- make::match_arm(iter::once(make::wildcard_pat().into()), None, make::expr_unit().into())
+ make::match_arm(iter::once(make::wildcard_pat().into()), None, make::expr_unit())
}
}
}
fn binds_name(sema: &hir::Semantics<RootDatabase>, pat: &ast::Pat) -> bool {
- let binds_name_v = |pat| binds_name(&sema, &pat);
+ let binds_name_v = |pat| binds_name(sema, &pat);
match pat {
ast::Pat::IdentPat(pat) => !matches!(
pat.name().and_then(|name| NameClass::classify(sema, &name)),
)
}
+ #[test]
+ fn test_if_let_with_match_available_range_left() {
+ check_assist_not_applicable(
+ replace_if_let_with_match,
+ r#"
+impl VariantData {
+ pub fn foo(&self) {
+ $0 if let VariantData::Struct(..) = *self {
+ self.foo();
+ }
+ }
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn test_if_let_with_match_available_range_right() {
+ check_assist_not_applicable(
+ replace_if_let_with_match,
+ r#"
+impl VariantData {
+ pub fn foo(&self) {
+ if let VariantData::Struct(..) = *self {$0
+ self.foo();
+ }
+ }
+}
+"#,
+ )
+ }
+
#[test]
fn test_if_let_with_match_basic() {
check_assist(
+++ /dev/null
-use syntax::{
- ast::{self, edit_in_place::GenericParamsOwnerEdit, make, AstNode},
- ted,
-};
-
-use crate::{utils::suggest_name, AssistContext, AssistId, AssistKind, Assists};
-
-// Assist: replace_impl_trait_with_generic
-//
-// Replaces `impl Trait` function argument with the named generic.
-//
-// ```
-// fn foo(bar: $0impl Bar) {}
-// ```
-// ->
-// ```
-// fn foo<B: Bar>(bar: B) {}
-// ```
-pub(crate) fn replace_impl_trait_with_generic(
- acc: &mut Assists,
- ctx: &AssistContext,
-) -> Option<()> {
- let impl_trait_type = ctx.find_node_at_offset::<ast::ImplTraitType>()?;
- let param = impl_trait_type.syntax().parent().and_then(ast::Param::cast)?;
- let fn_ = param.syntax().ancestors().find_map(ast::Fn::cast)?;
-
- let type_bound_list = impl_trait_type.type_bound_list()?;
-
- let target = fn_.syntax().text_range();
- acc.add(
- AssistId("replace_impl_trait_with_generic", AssistKind::RefactorRewrite),
- "Replace impl trait with generic",
- target,
- |edit| {
- let impl_trait_type = edit.make_mut(impl_trait_type);
- let fn_ = edit.make_mut(fn_);
-
- let type_param_name = suggest_name::for_generic_parameter(&impl_trait_type);
-
- let type_param = make::type_param(make::name(&type_param_name), Some(type_bound_list))
- .clone_for_update();
- let new_ty = make::ty(&type_param_name).clone_for_update();
-
- ted::replace(impl_trait_type.syntax(), new_ty.syntax());
- fn_.get_or_create_generic_param_list().add_generic_param(type_param.into())
- },
- )
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- use crate::tests::check_assist;
-
- #[test]
- fn replace_impl_trait_with_generic_params() {
- check_assist(
- replace_impl_trait_with_generic,
- r#"fn foo<G>(bar: $0impl Bar) {}"#,
- r#"fn foo<G, B: Bar>(bar: B) {}"#,
- );
- }
-
- #[test]
- fn replace_impl_trait_without_generic_params() {
- check_assist(
- replace_impl_trait_with_generic,
- r#"fn foo(bar: $0impl Bar) {}"#,
- r#"fn foo<B: Bar>(bar: B) {}"#,
- );
- }
-
- #[test]
- fn replace_two_impl_trait_with_generic_params() {
- check_assist(
- replace_impl_trait_with_generic,
- r#"fn foo<G>(foo: impl Foo, bar: $0impl Bar) {}"#,
- r#"fn foo<G, B: Bar>(foo: impl Foo, bar: B) {}"#,
- );
- }
-
- #[test]
- fn replace_impl_trait_with_empty_generic_params() {
- check_assist(
- replace_impl_trait_with_generic,
- r#"fn foo<>(bar: $0impl Bar) {}"#,
- r#"fn foo<B: Bar>(bar: B) {}"#,
- );
- }
-
- #[test]
- fn replace_impl_trait_with_empty_multiline_generic_params() {
- check_assist(
- replace_impl_trait_with_generic,
- r#"
-fn foo<
->(bar: $0impl Bar) {}
-"#,
- r#"
-fn foo<B: Bar
->(bar: B) {}
-"#,
- );
- }
-
- #[test]
- fn replace_impl_trait_with_exist_generic_letter() {
- // FIXME: This is wrong, we should pick a different name if the one we
- // want is already bound.
- check_assist(
- replace_impl_trait_with_generic,
- r#"fn foo<B>(bar: $0impl Bar) {}"#,
- r#"fn foo<B, B: Bar>(bar: B) {}"#,
- );
- }
-
- #[test]
- fn replace_impl_trait_with_multiline_generic_params() {
- check_assist(
- replace_impl_trait_with_generic,
- r#"
-fn foo<
- G: Foo,
- F,
- H,
->(bar: $0impl Bar) {}
-"#,
- r#"
-fn foo<
- G: Foo,
- F,
- H, B: Bar,
->(bar: B) {}
-"#,
- );
- }
-
- #[test]
- fn replace_impl_trait_multiple() {
- check_assist(
- replace_impl_trait_with_generic,
- r#"fn foo(bar: $0impl Foo + Bar) {}"#,
- r#"fn foo<F: Foo + Bar>(bar: F) {}"#,
- );
- }
-}
&& lhs
.name_ref()
.zip(rhs.name_ref())
- .map_or(false, |(lhs, rhs)| lhs.text() == rhs.text()) =>
- {
- ()
- }
+ .map_or(false, |(lhs, rhs)| lhs.text() == rhs.text()) => {}
_ => return false,
}
AssistId("toggle_ignore", AssistKind::None),
"Ignore this test",
attr.syntax().text_range(),
- |builder| builder.insert(attr.syntax().text_range().end(), &format!("\n#[ignore]")),
+ |builder| builder.insert(attr.syntax().text_range().end(), "\n#[ignore]"),
),
Some(ignore_attr) => acc.add(
AssistId("toggle_ignore", AssistKind::None),
-//! `assists` crate provides a bunch of code assists, also known as code
-//! actions (in LSP) or intentions (in IntelliJ).
+//! `assists` crate provides a bunch of code assists, also known as code actions
+//! (in LSP) or intentions (in IntelliJ).
//!
//! An assist is a micro-refactoring, which is automatically activated in
//! certain context. For example, if the cursor is over `,`, a "swap `,`" assist
//! becomes available.
-
+//!
+//! ## Assists Guidelines
+//!
+//! Assists are the main mechanism to deliver advanced IDE features to the user,
+//! so we should pay extra attention to the UX.
+//!
+//! The power of assists comes from their context-awareness. The main problem
+//! with IDE features is that there are a lot of them, and it's hard to teach
+//! the user what's available. Assists solve this problem nicely: 💡 signifies
+//! that *something* is possible, and clicking on it reveals a *short* list of
+//! actions. Contrast it with Emacs `M-x`, which just spits an infinite list of
+//! all the features.
+//!
+//! Here are some considerations when creating a new assist:
+//!
+//! * It's good to preserve semantics, and it's good to keep the code compiling,
+//! but it isn't necessary. Example: "flip binary operation" might change
+//! semantics.
+//! * Assist shouldn't necessary make the code "better". A lot of assist come in
+//! pairs: "if let <-> match".
+//! * Assists should have as narrow scope as possible. Each new assists greatly
+//! improves UX for cases where the user actually invokes it, but it makes UX
+//! worse for every case where the user clicks 💡 to invoke some *other*
+//! assist. So, a rarely useful assist which is always applicable can be a net
+//! negative.
+//! * Rarely useful actions are tricky. Sometimes there are features which are
+//! clearly useful to some users, but are just noise most of the time. We
+//! don't have a good solution here, our current approach is to make this
+//! functionality available only if assist is applicable to the whole
+//! selection. Example: `sort_items` sorts items alphabetically. Naively, it
+//! should be available more or less everywhere, which isn't useful. So
+//! instead we only show it if the user *selects* the items they want to sort.
+//! * Consider grouping related assists together (see [`Assists::add_group`]).
+//! * Make assists robust. If the assist depends on results of type-inference to
+//! much, it might only fire in fully-correct code. This makes assist less
+//! useful and (worse) less predictable. The user should have a clear
+//! intuition when each particular assist is available.
+//! * Make small assists, which compose. Example: rather than auto-importing
+//! enums in `fill_match_arms`, we use fully-qualified names. There's a
+//! separate assist to shorten a fully-qualified name.
+//! * Distinguish between assists and fixits for diagnostics. Internally, fixits
+//! and assists are equivalent. They have the same "show a list + invoke a
+//! single element" workflow, and both use [`Assist`] data structure. The main
+//! difference is in the UX: while 💡 looks only at the cursor position,
+//! diagnostics squigglies and fixits are calculated for the whole file and
+//! are presented to the user eagerly. So, diagnostics should be fixable
+//! errors, while assists can be just suggestions for an alternative way to do
+//! something. If something *could* be a diagnostic, it should be a
+//! diagnostic. Conversely, it might be valuable to turn a diagnostic with a
+//! lot of false errors into an assist.
+//! *
+//!
+//! See also this post:
+//! <https://rust-analyzer.github.io/blog/2020/09/28/how-to-make-a-light-bulb.html>
#[allow(unused)]
macro_rules! eprintln {
($($tt:tt)*) => { stdx::eprintln!($($tt)*) };
};
/// Return all the assists applicable at the given position.
+///
+// NOTE: We don't have a `Feature: ` section for assists, they are special-cased
+// in the manual.
pub fn assists(
db: &RootDatabase,
config: &AssistConfig,
mod convert_iter_for_each_to_for;
mod convert_tuple_struct_to_named_struct;
mod convert_to_guarded_return;
+ mod convert_while_to_loop;
mod destructure_tuple_binding;
mod expand_glob_import;
mod extract_function;
mod extract_struct_from_enum_variant;
mod extract_type_alias;
mod extract_variable;
- mod fill_match_arms;
+ mod add_missing_match_arms;
mod fix_visibility;
mod flip_binexpr;
mod flip_comma;
mod generate_is_empty_from_len;
mod generate_new;
mod generate_setter;
- mod infer_function_return_type;
+ mod add_return_type;
mod inline_call;
mod inline_local_variable;
mod introduce_named_lifetime;
mod reorder_fields;
mod reorder_impl;
mod replace_derive_with_manual_impl;
- mod replace_for_loop_with_for_each;
mod replace_if_let_with_match;
- mod replace_impl_trait_with_generic;
+ mod introduce_named_generic;
mod replace_let_with_if_let;
mod replace_qualified_name_with_use;
mod replace_string_with_char;
&[
// These are alphabetic for the foolish consistency
add_explicit_type::add_explicit_type,
+ add_missing_match_arms::add_missing_match_arms,
add_lifetime_to_type::add_lifetime_to_type,
+ add_return_type::add_return_type,
add_turbo_fish::add_turbo_fish,
apply_demorgan::apply_demorgan,
auto_import::auto_import,
convert_integer_literal::convert_integer_literal,
convert_into_to_from::convert_into_to_from,
convert_iter_for_each_to_for::convert_iter_for_each_to_for,
+ convert_iter_for_each_to_for::convert_for_loop_with_for_each,
convert_to_guarded_return::convert_to_guarded_return,
convert_tuple_struct_to_named_struct::convert_tuple_struct_to_named_struct,
+ convert_while_to_loop::convert_while_to_loop,
destructure_tuple_binding::destructure_tuple_binding,
expand_glob_import::expand_glob_import,
extract_struct_from_enum_variant::extract_struct_from_enum_variant,
extract_type_alias::extract_type_alias,
- fill_match_arms::fill_match_arms,
fix_visibility::fix_visibility,
flip_binexpr::flip_binexpr,
flip_comma::flip_comma,
generate_impl::generate_impl,
generate_is_empty_from_len::generate_is_empty_from_len,
generate_new::generate_new,
- infer_function_return_type::infer_function_return_type,
inline_call::inline_call,
inline_local_variable::inline_local_variable,
+ introduce_named_generic::introduce_named_generic,
introduce_named_lifetime::introduce_named_lifetime,
invert_if::invert_if,
merge_imports::merge_imports,
reorder_fields::reorder_fields,
reorder_impl::reorder_impl,
replace_derive_with_manual_impl::replace_derive_with_manual_impl,
- replace_for_loop_with_for_each::replace_for_loop_with_for_each,
replace_if_let_with_match::replace_if_let_with_match,
replace_if_let_with_match::replace_match_with_if_let,
- replace_impl_trait_with_generic::replace_impl_trait_with_generic,
replace_let_with_if_let::replace_let_with_if_let,
replace_qualified_name_with_use::replace_qualified_name_with_use,
sort_items::sort_items,
)
}
+#[test]
+fn doctest_add_missing_match_arms() {
+ check_doc_test(
+ "add_missing_match_arms",
+ r#####"
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ match action {
+ $0
+ }
+}
+"#####,
+ r#####"
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+ match action {
+ $0Action::Move { distance } => todo!(),
+ Action::Stop => todo!(),
+ }
+}
+"#####,
+ )
+}
+
+#[test]
+fn doctest_add_return_type() {
+ check_doc_test(
+ "add_return_type",
+ r#####"
+fn foo() { 4$02i32 }
+"#####,
+ r#####"
+fn foo() -> i32 { 42i32 }
+"#####,
+ )
+}
+
#[test]
fn doctest_add_turbo_fish() {
check_doc_test(
)
}
+#[test]
+fn doctest_convert_for_loop_with_for_each() {
+ check_doc_test(
+ "convert_for_loop_with_for_each",
+ r#####"
+fn main() {
+ let x = vec![1, 2, 3];
+ for$0 v in x {
+ let y = v * 2;
+ }
+}
+"#####,
+ r#####"
+fn main() {
+ let x = vec![1, 2, 3];
+ x.into_iter().for_each(|v| {
+ let y = v * 2;
+ });
+}
+"#####,
+ )
+}
+
#[test]
fn doctest_convert_if_to_bool_then() {
check_doc_test(
)
}
+#[test]
+fn doctest_convert_while_to_loop() {
+ check_doc_test(
+ "convert_while_to_loop",
+ r#####"
+fn main() {
+ $0while cond {
+ foo();
+ }
+}
+"#####,
+ r#####"
+fn main() {
+ loop {
+ if !cond {
+ break;
+ }
+ foo();
+ }
+}
+"#####,
+ )
+}
+
#[test]
fn doctest_destructure_tuple_binding() {
check_doc_test(
)
}
-#[test]
-fn doctest_fill_match_arms() {
- check_doc_test(
- "fill_match_arms",
- r#####"
-enum Action { Move { distance: u32 }, Stop }
-
-fn handle(action: Action) {
- match action {
- $0
- }
-}
-"#####,
- r#####"
-enum Action { Move { distance: u32 }, Stop }
-
-fn handle(action: Action) {
- match action {
- $0Action::Move { distance } => todo!(),
- Action::Stop => todo!(),
- }
-}
-"#####,
- )
-}
-
#[test]
fn doctest_fix_visibility() {
check_doc_test(
)
}
-#[test]
-fn doctest_infer_function_return_type() {
- check_doc_test(
- "infer_function_return_type",
- r#####"
-fn foo() { 4$02i32 }
-"#####,
- r#####"
-fn foo() -> i32 { 42i32 }
-"#####,
- )
-}
-
#[test]
fn doctest_inline_call() {
check_doc_test(
)
}
+#[test]
+fn doctest_introduce_named_generic() {
+ check_doc_test(
+ "introduce_named_generic",
+ r#####"
+fn foo(bar: $0impl Bar) {}
+"#####,
+ r#####"
+fn foo<B: Bar>(bar: B) {}
+"#####,
+ )
+}
+
#[test]
fn doctest_introduce_named_lifetime() {
check_doc_test(
)
}
+#[test]
+fn doctest_line_to_block() {
+ check_doc_test(
+ "line_to_block",
+ r#####"
+ // Multi-line$0
+ // comment
+"#####,
+ r#####"
+ /*
+ Multi-line
+ comment
+ */
+"#####,
+ )
+}
+
#[test]
fn doctest_make_raw_string() {
check_doc_test(
)
}
-#[test]
-fn doctest_replace_for_loop_with_for_each() {
- check_doc_test(
- "replace_for_loop_with_for_each",
- r#####"
-fn main() {
- let x = vec![1, 2, 3];
- for$0 v in x {
- let y = v * 2;
- }
-}
-"#####,
- r#####"
-fn main() {
- let x = vec![1, 2, 3];
- x.into_iter().for_each(|v| {
- let y = v * 2;
- });
-}
-"#####,
- )
-}
-
#[test]
fn doctest_replace_if_let_with_match() {
check_doc_test(
)
}
-#[test]
-fn doctest_replace_impl_trait_with_generic() {
- check_doc_test(
- "replace_impl_trait_with_generic",
- r#####"
-fn foo(bar: $0impl Bar) {}
-"#####,
- r#####"
-fn foo<B: Bar>(bar: B) {}
-"#####,
- )
-}
-
#[test]
fn doctest_replace_let_with_if_let() {
check_doc_test(
[dependencies]
cov-mark = "2.0.0-pre.1"
itertools = "0.10.0"
-tracing = "0.1"
rustc-hash = "1.1.0"
either = "1.6.1"
once_cell = "1.7"
pub(crate) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
let attribute = ctx.attribute_under_caret.as_ref()?;
- match (attribute.path().and_then(|p| p.as_single_name_ref()), attribute.token_tree()) {
+ let name_ref = match attribute.path() {
+ Some(p) => Some(p.as_single_name_ref()?),
+ None => None,
+ };
+ match (name_ref, attribute.token_tree()) {
(Some(path), Some(token_tree)) => match path.text().as_str() {
"derive" => derive::complete_derive(acc, ctx, token_tree),
"repr" => repr::complete_repr(acc, ctx, token_tree),
-//! Feature: completion with imports-on-the-fly
-//!
-//! When completing names in the current scope, proposes additional imports from other modules or crates,
-//! if they can be qualified in the scope, and their name contains all symbols from the completion input.
-//!
-//! To be considered applicable, the name must contain all input symbols in the given order, not necessarily adjacent.
-//! If any input symbol is not lowercased, the name must contain all symbols in exact case; otherwise the containing is checked case-insensitively.
-//!
-//! ```
-//! fn main() {
-//! pda$0
-//! }
-//! # pub mod std { pub mod marker { pub struct PhantomData { } } }
-//! ```
-//! ->
-//! ```
-//! use std::marker::PhantomData;
-//!
-//! fn main() {
-//! PhantomData
-//! }
-//! # pub mod std { pub mod marker { pub struct PhantomData { } } }
-//! ```
-//!
-//! Also completes associated items, that require trait imports.
-//! If any unresolved and/or partially-qualified path precedes the input, it will be taken into account.
-//! Currently, only the imports with their import path ending with the whole qualifier will be proposed
-//! (no fuzzy matching for qualifier).
-//!
-//! ```
-//! mod foo {
-//! pub mod bar {
-//! pub struct Item;
-//!
-//! impl Item {
-//! pub const TEST_ASSOC: usize = 3;
-//! }
-//! }
-//! }
-//!
-//! fn main() {
-//! bar::Item::TEST_A$0
-//! }
-//! ```
-//! ->
-//! ```
-//! use foo::bar;
-//!
-//! mod foo {
-//! pub mod bar {
-//! pub struct Item;
-//!
-//! impl Item {
-//! pub const TEST_ASSOC: usize = 3;
-//! }
-//! }
-//! }
-//!
-//! fn main() {
-//! bar::Item::TEST_ASSOC
-//! }
-//! ```
-//!
-//! NOTE: currently, if an assoc item comes from a trait that's not currently imported, and it also has an unresolved and/or partially-qualified path,
-//! no imports will be proposed.
-//!
-//! .Fuzzy search details
-//!
-//! To avoid an excessive amount of the results returned, completion input is checked for inclusion in the names only
-//! (i.e. in `HashMap` in the `std::collections::HashMap` path).
-//! For the same reasons, avoids searching for any path imports for inputs with their length less than 2 symbols
-//! (but shows all associated items for any input length).
-//!
-//! .Import configuration
-//!
-//! It is possible to configure how use-trees are merged with the `importMergeBehavior` setting.
-//! Mimics the corresponding behavior of the `Auto Import` feature.
-//!
-//! .LSP and performance implications
-//!
-//! The feature is enabled only if the LSP client supports LSP protocol version 3.16+ and reports the `additionalTextEdits`
-//! (case-sensitive) resolve client capability in its client capabilities.
-//! This way the server is able to defer the costly computations, doing them for a selected completion item only.
-//! For clients with no such support, all edits have to be calculated on the completion request, including the fuzzy search completion ones,
-//! which might be slow ergo the feature is automatically disabled.
-//!
-//! .Feature toggle
-//!
-//! The feature can be forcefully turned off in the settings with the `rust-analyzer.completion.autoimport.enable` flag.
-//! Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corresponding
-//! capability enabled.
-
+//! See [`import_on_the_fly`].
use ide_db::helpers::{
import_assets::{ImportAssets, ImportCandidate},
insert_use::ImportScope,
use super::Completions;
+// Feature: Completion With Autoimport
+//
+// When completing names in the current scope, proposes additional imports from other modules or crates,
+// if they can be qualified in the scope, and their name contains all symbols from the completion input.
+//
+// To be considered applicable, the name must contain all input symbols in the given order, not necessarily adjacent.
+// If any input symbol is not lowercased, the name must contain all symbols in exact case; otherwise the containing is checked case-insensitively.
+//
+// ```
+// fn main() {
+// pda$0
+// }
+// # pub mod std { pub mod marker { pub struct PhantomData { } } }
+// ```
+// ->
+// ```
+// use std::marker::PhantomData;
+//
+// fn main() {
+// PhantomData
+// }
+// # pub mod std { pub mod marker { pub struct PhantomData { } } }
+// ```
+//
+// Also completes associated items, that require trait imports.
+// If any unresolved and/or partially-qualified path precedes the input, it will be taken into account.
+// Currently, only the imports with their import path ending with the whole qualifier will be proposed
+// (no fuzzy matching for qualifier).
+//
+// ```
+// mod foo {
+// pub mod bar {
+// pub struct Item;
+//
+// impl Item {
+// pub const TEST_ASSOC: usize = 3;
+// }
+// }
+// }
+//
+// fn main() {
+// bar::Item::TEST_A$0
+// }
+// ```
+// ->
+// ```
+// use foo::bar;
+//
+// mod foo {
+// pub mod bar {
+// pub struct Item;
+//
+// impl Item {
+// pub const TEST_ASSOC: usize = 3;
+// }
+// }
+// }
+//
+// fn main() {
+// bar::Item::TEST_ASSOC
+// }
+// ```
+//
+// NOTE: currently, if an assoc item comes from a trait that's not currently imported, and it also has an unresolved and/or partially-qualified path,
+// no imports will be proposed.
+//
+// .Fuzzy search details
+//
+// To avoid an excessive amount of the results returned, completion input is checked for inclusion in the names only
+// (i.e. in `HashMap` in the `std::collections::HashMap` path).
+// For the same reasons, avoids searching for any path imports for inputs with their length less than 2 symbols
+// (but shows all associated items for any input length).
+//
+// .Import configuration
+//
+// It is possible to configure how use-trees are merged with the `importMergeBehavior` setting.
+// Mimics the corresponding behavior of the `Auto Import` feature.
+//
+// .LSP and performance implications
+//
+// The feature is enabled only if the LSP client supports LSP protocol version 3.16+ and reports the `additionalTextEdits`
+// (case-sensitive) resolve client capability in its client capabilities.
+// This way the server is able to defer the costly computations, doing them for a selected completion item only.
+// For clients with no such support, all edits have to be calculated on the completion request, including the fuzzy search completion ones,
+// which might be slow ergo the feature is automatically disabled.
+//
+// .Feature toggle
+//
+// The feature can be forcefully turned off in the settings with the `rust-analyzer.completion.autoimport.enable` flag.
+// Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corresponding
+// capability enabled.
pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
if !ctx.config.enable_imports_on_the_fly {
return None;
-//! See `complete_fn_param`.
+//! See [`complete_fn_param`].
use rustc_hash::FxHashMap;
use syntax::{
pub(super) pattern_ctx: Option<PatternContext>,
pub(super) path_context: Option<PathCompletionContext>,
- pub(super) active_parameter: Option<ActiveParameter>,
pub(super) locals: Vec<(String, Local)>,
pub(super) incomplete_let: bool,
attribute_under_caret: None,
previous_token: None,
path_context: None,
- active_parameter: ActiveParameter::at(db, position),
locals,
incomplete_let: false,
no_completion_required: false,
};
+ ctx.expand_and_fill(
+ original_file.syntax().clone(),
+ file_with_fake_ident.syntax().clone(),
+ position.offset,
+ fake_ident_token,
+ );
+ Some(ctx)
+ }
- let mut original_file = original_file.syntax().clone();
- let mut speculative_file = file_with_fake_ident.syntax().clone();
- let mut offset = position.offset;
- let mut fake_ident_token = fake_ident_token;
-
- // Are we inside a macro call?
- while let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = (
- find_node_at_offset::<ast::MacroCall>(&original_file, offset),
- find_node_at_offset::<ast::MacroCall>(&speculative_file, offset),
- ) {
- if actual_macro_call.path().as_ref().map(|s| s.syntax().text())
- != macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text())
- {
- break;
+ fn expand_and_fill(
+ &mut self,
+ mut original_file: SyntaxNode,
+ mut speculative_file: SyntaxNode,
+ mut offset: TextSize,
+ mut fake_ident_token: SyntaxToken,
+ ) {
+ loop {
+ // Expand attributes
+ if let (Some(actual_item), Some(item_with_fake_ident)) = (
+ find_node_at_offset::<ast::Item>(&original_file, offset),
+ find_node_at_offset::<ast::Item>(&speculative_file, offset),
+ ) {
+ match (
+ self.sema.expand_attr_macro(&actual_item),
+ self.sema.speculative_expand_attr_macro(
+ &actual_item,
+ &item_with_fake_ident,
+ fake_ident_token.clone(),
+ ),
+ ) {
+ (Some(actual_expansion), Some(speculative_expansion)) => {
+ let new_offset = speculative_expansion.1.text_range().start();
+ if new_offset > actual_expansion.text_range().end() {
+ break;
+ }
+ original_file = actual_expansion;
+ speculative_file = speculative_expansion.0;
+ fake_ident_token = speculative_expansion.1;
+ offset = new_offset;
+ continue;
+ }
+ (None, None) => (),
+ _ => break,
+ }
}
- let speculative_args = match macro_call_with_fake_ident.token_tree() {
- Some(tt) => tt,
- None => break,
- };
- if let (Some(actual_expansion), Some(speculative_expansion)) = (
- ctx.sema.expand(&actual_macro_call),
- ctx.sema.speculative_expand(
- &actual_macro_call,
- &speculative_args,
- fake_ident_token,
- ),
+
+ // Expand fn-like macro calls
+ if let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = (
+ find_node_at_offset::<ast::MacroCall>(&original_file, offset),
+ find_node_at_offset::<ast::MacroCall>(&speculative_file, offset),
) {
- let new_offset = speculative_expansion.1.text_range().start();
- if new_offset > actual_expansion.text_range().end() {
+ let mac_call_path0 = actual_macro_call.path().as_ref().map(|s| s.syntax().text());
+ let mac_call_path1 =
+ macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text());
+ if mac_call_path0 != mac_call_path1 {
+ break;
+ }
+ let speculative_args = match macro_call_with_fake_ident.token_tree() {
+ Some(tt) => tt,
+ None => break,
+ };
+
+ if let (Some(actual_expansion), Some(speculative_expansion)) = (
+ self.sema.expand(&actual_macro_call),
+ self.sema.speculative_expand(
+ &actual_macro_call,
+ &speculative_args,
+ fake_ident_token,
+ ),
+ ) {
+ let new_offset = speculative_expansion.1.text_range().start();
+ if new_offset > actual_expansion.text_range().end() {
+ break;
+ }
+ original_file = actual_expansion;
+ speculative_file = speculative_expansion.0;
+ fake_ident_token = speculative_expansion.1;
+ offset = new_offset;
+ } else {
break;
}
- original_file = actual_expansion;
- speculative_file = speculative_expansion.0;
- fake_ident_token = speculative_expansion.1;
- offset = new_offset;
} else {
break;
}
}
- ctx.fill(&original_file, speculative_file, offset);
- Some(ctx)
+
+ self.fill(&original_file, speculative_file, offset);
}
/// Checks whether completions in that particular case don't make much sense.
}
}
+ pub(crate) fn is_immediately_after_macro_bang(&self) -> bool {
+ self.token.kind() == BANG && self.token.parent().map_or(false, |it| it.kind() == MACRO_CALL)
+ }
+
/// A version of [`SemanticsScope::process_all_names`] that filters out `#[doc(hidden)]` items.
pub(crate) fn process_all_names(&self, f: &mut dyn FnMut(Name, ScopeDef)) {
self.scope.process_all_names(&mut |name, def| {
#[derive(Debug)]
struct EnumRender<'a> {
ctx: RenderContext<'a>,
- name: hir::Name,
variant: hir::Variant,
path: Option<hir::ModPath>,
qualified_name: hir::ModPath,
),
};
- EnumRender { ctx, name, variant, path, qualified_name, short_qualified_name, variant_kind }
+ EnumRender { ctx, variant, path, qualified_name, short_qualified_name, variant_kind }
}
fn render(self, import_to_add: Option<ImportEdit>) -> CompletionItem {
let mut item = CompletionItem::new(
}
fn render(&self, import_to_add: Option<ImportEdit>) -> Option<CompletionItem> {
- let mut item =
- CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), &self.label());
+ let source_range = if self.ctx.completion.is_immediately_after_macro_bang() {
+ cov_mark::hit!(completes_macro_call_if_cursor_at_bang_token);
+ self.ctx.completion.token.parent().map(|it| it.text_range())
+ } else {
+ Some(self.ctx.source_range())
+ }?;
+ let mut item = CompletionItem::new(CompletionKind::Reference, source_range, &self.label());
item.kind(SymbolKind::Macro)
.set_documentation(self.docs.clone())
.set_deprecated(self.ctx.is_deprecated(self.macro_))
"#,
)
}
+
+ #[test]
+ fn completes_macro_call_if_cursor_at_bang_token() {
+ // Regression test for https://github.com/rust-analyzer/rust-analyzer/issues/9904
+ cov_mark::check!(completes_macro_call_if_cursor_at_bang_token);
+ check_edit(
+ "foo!",
+ r#"
+macro_rules! foo {
+ () => {}
+}
+
+fn main() {
+ foo!$0
+}
+"#,
+ r#"
+macro_rules! foo {
+ () => {}
+}
+
+fn main() {
+ foo!($0)
+}
+"#,
+ );
+ }
}
mod item;
mod pattern;
mod predicate;
+mod proc_macros;
mod record;
mod sourcegen;
mod type_pos;
use std::mem;
-use hir::{PrefixKind, Semantics};
+use hir::{db::DefDatabase, PrefixKind, Semantics};
use ide_db::{
base_db::{fixture::ChangeFixture, FileLoader, FilePosition},
helpers::{
pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
let change_fixture = ChangeFixture::parse(ra_fixture);
let mut database = RootDatabase::default();
+ database.set_enable_proc_attr_macros(true);
database.apply_change(change_fixture.change);
let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
let offset = range_or_offset.expect_offset();
)
}
+#[test]
+fn doesnt_complete_qualified() {
+ check(
+ r#"
+struct Foo;
+#[foo::$0]
+use self as this;
+"#,
+ expect![[r#""#]],
+ )
+}
+
#[test]
fn inside_nested_attr() {
check(r#"#[cfg($0)]"#, expect![[]])
--- /dev/null
+//! Completion tests for expressions.
+use expect_test::{expect, Expect};
+
+use crate::tests::completion_list;
+
+fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual)
+}
+
+#[test]
+fn complete_dot_in_attr() {
+ check(
+ r#"
+//- proc_macros: identity
+pub struct Foo;
+impl Foo {
+ fn foo(&self) {}
+}
+
+#[proc_macros::identity]
+fn main() {
+ Foo.$0
+}
+"#,
+ expect![[r#"
+ me foo() fn(&self)
+ sn ref &expr
+ sn refm &mut expr
+ sn match match expr {}
+ sn box Box::new(expr)
+ sn ok Ok(expr)
+ sn err Err(expr)
+ sn some Some(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn call function(expr)
+ sn let let
+ sn letm let mut
+ "#]],
+ )
+}
+
+#[test]
+fn complete_dot_in_attr2() {
+ check(
+ r#"
+//- proc_macros: identity
+pub struct Foo;
+impl Foo {
+ fn foo(&self) {}
+}
+
+#[proc_macros::identity]
+fn main() {
+ Foo.f$0
+}
+"#,
+ expect![[r#"
+ me foo() fn(&self)
+ sn ref &expr
+ sn refm &mut expr
+ sn match match expr {}
+ sn box Box::new(expr)
+ sn ok Ok(expr)
+ sn err Err(expr)
+ sn some Some(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn call function(expr)
+ sn let let
+ sn letm let mut
+ "#]],
+ )
+}
+
+#[test]
+fn complete_dot_in_attr_input() {
+ check(
+ r#"
+//- proc_macros: input_replace
+pub struct Foo;
+impl Foo {
+ fn foo(&self) {}
+}
+
+#[proc_macros::input_replace(
+ fn suprise() {
+ Foo.$0
+ }
+)]
+fn main() {}
+"#,
+ expect![[r#"
+ me foo() fn(&self)
+ sn ref &expr
+ sn refm &mut expr
+ sn match match expr {}
+ sn box Box::new(expr)
+ sn ok Ok(expr)
+ sn err Err(expr)
+ sn some Some(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn call function(expr)
+ sn let let
+ sn letm let mut
+ "#]],
+ )
+}
+
+#[test]
+fn complete_dot_in_attr_input2() {
+ check(
+ r#"
+//- proc_macros: input_replace
+pub struct Foo;
+impl Foo {
+ fn foo(&self) {}
+}
+
+#[proc_macros::input_replace(
+ fn suprise() {
+ Foo.f$0
+ }
+)]
+fn main() {}
+"#,
+ expect![[r#"
+ me foo() fn(&self)
+ sn ref &expr
+ sn refm &mut expr
+ sn match match expr {}
+ sn box Box::new(expr)
+ sn ok Ok(expr)
+ sn err Err(expr)
+ sn some Some(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn call function(expr)
+ sn let let
+ sn letm let mut
+ "#]],
+ )
+}
once_cell = "1.3.1"
either = "1.6.1"
itertools = "0.10.0"
+arrayvec = "0.7"
stdx = { path = "../stdx", version = "0.0.0" }
syntax = { path = "../syntax", version = "0.0.0" }
}
impl ActiveParameter {
- pub fn at(db: &RootDatabase, position: FilePosition) -> Option<Self> {
- let sema = Semantics::new(db);
- let file = sema.parse(position.file_id);
- let file = file.syntax();
- let token = file.token_at_offset(position.offset).next()?;
- let token = sema.descend_into_macros(token);
- Self::at_token(&sema, token)
- }
-
pub fn at_token(sema: &Semantics<RootDatabase>, token: SyntaxToken) -> Option<Self> {
let (signature, active_parameter) = call_info_impl(sema, token)?;
// FIXME: this badly needs rename/rewrite (matklad, 2020-02-06).
+use arrayvec::ArrayVec;
use hir::{
Field, GenericParam, HasVisibility, Impl, Label, Local, MacroDef, Module, ModuleDef, Name,
PathResolution, Semantics, Visibility,
};
use syntax::{
ast::{self, AstNode},
- match_ast, SyntaxKind,
+ match_ast, SyntaxKind, SyntaxNode, SyntaxToken,
};
-use crate::RootDatabase;
+use crate::{helpers::try_resolve_derive_input_at, RootDatabase};
// FIXME: a more precise name would probably be `Symbol`?
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
}
impl Definition {
+ pub fn from_token(
+ sema: &Semantics<RootDatabase>,
+ token: &SyntaxToken,
+ ) -> ArrayVec<Definition, 2> {
+ let parent = match token.parent() {
+ Some(parent) => parent,
+ None => return Default::default(),
+ };
+ let attr = parent
+ .ancestors()
+ .find_map(ast::TokenTree::cast)
+ .and_then(|tt| tt.parent_meta())
+ .and_then(|meta| meta.parent_attr());
+ if let Some(attr) = attr {
+ try_resolve_derive_input_at(&sema, &attr, &token)
+ .map(Definition::Macro)
+ .into_iter()
+ .collect()
+ } else {
+ Self::from_node(sema, &parent)
+ }
+ }
+
+ pub fn from_node(sema: &Semantics<RootDatabase>, node: &SyntaxNode) -> ArrayVec<Definition, 2> {
+ let mut res = ArrayVec::new();
+ (|| {
+ match_ast! {
+ match node {
+ ast::Name(name) => {
+ match NameClass::classify(&sema, &name)? {
+ NameClass::Definition(it) | NameClass::ConstReference(it) => res.push(it),
+ NameClass::PatFieldShorthand { local_def, field_ref } => {
+ res.push(Definition::Local(local_def));
+ res.push(Definition::Field(field_ref));
+ }
+ }
+ },
+ ast::NameRef(name_ref) => {
+ match NameRefClass::classify(sema, &name_ref)? {
+ NameRefClass::Definition(it) => res.push(it),
+ NameRefClass::FieldShorthand { local_ref, field_ref } => {
+ res.push(Definition::Local(local_ref));
+ res.push(Definition::Field(field_ref));
+ }
+ }
+ },
+ ast::Lifetime(lifetime) => {
+ let def = if let Some(x) = NameClass::classify_lifetime(&sema, &lifetime) {
+ NameClass::defined(x)
+ } else {
+ NameRefClass::classify_lifetime(&sema, &lifetime).and_then(|class| match class {
+ NameRefClass::Definition(it) => Some(it),
+ _ => None,
+ })
+ };
+ if let Some(def) = def {
+ res.push(def);
+ }
+ },
+ _ => (),
+ }
+ }
+ Some(())
+ })();
+ res
+ }
+
pub fn module(&self, db: &RootDatabase) -> Option<Module> {
match self {
Definition::Macro(it) => it.module(db),
let file_id = file_id.original_file(db);
if let Definition::Local(var) = self {
- let range = match var.parent(db) {
- DefWithBody::Function(f) => f.source(db).map(|src| src.value.syntax().text_range()),
- DefWithBody::Const(c) => c.source(db).map(|src| src.value.syntax().text_range()),
- DefWithBody::Static(s) => s.source(db).map(|src| src.value.syntax().text_range()),
+ let def = match var.parent(db) {
+ DefWithBody::Function(f) => f.source(db).map(|src| src.syntax().cloned()),
+ DefWithBody::Const(c) => c.source(db).map(|src| src.syntax().cloned()),
+ DefWithBody::Static(s) => s.source(db).map(|src| src.syntax().cloned()),
};
- return match range {
- Some(range) => SearchScope::file_range(FileRange { file_id, range }),
+ return match def {
+ Some(def) => SearchScope::file_range(def.as_ref().original_file_range(db)),
None => SearchScope::single_file(file_id),
};
}
if let Definition::SelfType(impl_) = self {
- return match impl_.source(db).map(|src| src.value.syntax().text_range()) {
- Some(range) => SearchScope::file_range(FileRange { file_id, range }),
+ return match impl_.source(db).map(|src| src.syntax().cloned()) {
+ Some(def) => SearchScope::file_range(def.as_ref().original_file_range(db)),
None => SearchScope::single_file(file_id),
};
}
if let Definition::GenericParam(hir::GenericParam::LifetimeParam(param)) = self {
- let range = match param.parent(db) {
- hir::GenericDef::Function(it) => {
- it.source(db).map(|src| src.value.syntax().text_range())
- }
- hir::GenericDef::Adt(it) => {
- it.source(db).map(|src| src.value.syntax().text_range())
- }
- hir::GenericDef::Trait(it) => {
- it.source(db).map(|src| src.value.syntax().text_range())
- }
- hir::GenericDef::TypeAlias(it) => {
- it.source(db).map(|src| src.value.syntax().text_range())
- }
- hir::GenericDef::Impl(it) => {
- it.source(db).map(|src| src.value.syntax().text_range())
- }
- hir::GenericDef::Variant(it) => {
- it.source(db).map(|src| src.value.syntax().text_range())
- }
- hir::GenericDef::Const(it) => {
- it.source(db).map(|src| src.value.syntax().text_range())
- }
+ let def = match param.parent(db) {
+ hir::GenericDef::Function(it) => it.source(db).map(|src| src.syntax().cloned()),
+ hir::GenericDef::Adt(it) => it.source(db).map(|src| src.syntax().cloned()),
+ hir::GenericDef::Trait(it) => it.source(db).map(|src| src.syntax().cloned()),
+ hir::GenericDef::TypeAlias(it) => it.source(db).map(|src| src.syntax().cloned()),
+ hir::GenericDef::Impl(it) => it.source(db).map(|src| src.syntax().cloned()),
+ hir::GenericDef::Variant(it) => it.source(db).map(|src| src.syntax().cloned()),
+ hir::GenericDef::Const(it) => it.source(db).map(|src| src.syntax().cloned()),
};
- return match range {
- Some(range) => SearchScope::file_range(FileRange { file_id, range }),
+ return match def {
+ Some(def) => SearchScope::file_range(def.as_ref().original_file_range(db)),
None => SearchScope::single_file(file_id),
};
}
let source_root = ctx.sema.db.source_root(ctx.sema.db.file_source_root(file_id));
let our_path = source_root.path_for_file(&file_id)?;
- let (module_name, _) = our_path.name_and_extension()?;
+ let (mut module_name, _) = our_path.name_and_extension()?;
// Candidates to look for:
- // - `mod.rs` in the same folder
- // - we also check `main.rs` and `lib.rs`
+ // - `mod.rs`, `main.rs` and `lib.rs` in the same folder
// - `$dir.rs` in the parent folder, where `$dir` is the directory containing `self.file_id`
let parent = our_path.parent()?;
- let mut paths = vec![parent.join("mod.rs")?, parent.join("lib.rs")?, parent.join("main.rs")?];
-
- // `submod/bla.rs` -> `submod.rs`
- let parent_mod = (|| {
- let (name, _) = parent.name_and_extension()?;
- parent.parent()?.join(&format!("{}.rs", name))
- })();
- paths.extend(parent_mod);
+ let paths = {
+ let temp;
+ let parent = if module_name == "mod" {
+ // for mod.rs we need to actually look up one higher
+ // and take the parent as our to be module name
+ let (name, _) = parent.name_and_extension()?;
+ module_name = name;
+ temp = parent.parent()?;
+ &temp
+ } else {
+ &parent
+ };
+ let mut paths =
+ vec![parent.join("mod.rs")?, parent.join("lib.rs")?, parent.join("main.rs")?];
+
+ // `submod/bla.rs` -> `submod.rs`
+ let parent_mod = (|| {
+ let (name, _) = parent.name_and_extension()?;
+ parent.parent()?.join(&format!("{}.rs", name))
+ })();
+ paths.extend(parent_mod);
+ paths
+ };
for &parent_id in paths.iter().filter_map(|path| source_root.file_for_path(path)) {
for &krate in ctx.sema.db.relevant_crates(parent_id).iter() {
#[cfg(test)]
mod tests {
+
use crate::tests::{check_diagnostics, check_fix, check_fixes, check_no_fix};
#[test]
);
}
+ #[test]
+ fn unlinked_file_insert_in_empty_file_mod_file() {
+ check_fix(
+ r#"
+//- /main.rs
+//- /foo/mod.rs
+$0
+"#,
+ r#"
+mod foo;
+"#,
+ );
+ check_fix(
+ r#"
+//- /main.rs
+mod bar;
+//- /bar.rs
+// bar module
+//- /bar/foo/mod.rs
+$0
+"#,
+ r#"
+// bar module
+mod foo;
+"#,
+ );
+ }
+
#[test]
fn unlinked_file_old_style_modrs() {
check_fix(
#[derive(Debug)]
pub struct SsrPattern {
- raw: parsing::RawPattern,
parsed_rules: Vec<parsing::ParsedRule>,
}
for m in self.matches().matches {
matches_by_file
.entry(m.range.file_id)
- .or_insert_with(|| SsrMatches::default())
+ .or_insert_with(SsrMatches::default)
.matches
.push(m);
}
fn flatten_into(self, out: &mut SsrMatches) {
for mut m in self.matches {
for p in m.placeholder_values.values_mut() {
- std::mem::replace(&mut p.inner_matches, SsrMatches::default()).flatten_into(out);
+ std::mem::take(&mut p.inner_matches).flatten_into(out);
}
out.matches.push(m);
}
/// Information about a placeholder bound in a match.
#[derive(Debug)]
pub(crate) struct PlaceholderMatch {
- /// The node that the placeholder matched to. If set, then we'll search for further matches
- /// within this node. It isn't set when we match tokens within a macro call's token tree.
- pub(crate) node: Option<SyntaxNode>,
pub(crate) range: FileRange,
/// More matches, found within `node`.
pub(crate) inner_matches: SsrMatches,
self.validate_range(&original_range)?;
matches_out.placeholder_values.insert(
placeholder.ident.clone(),
- PlaceholderMatch::new(Some(code), original_range),
+ PlaceholderMatch::from_range(original_range),
);
}
return Ok(());
let mut last_matched_token = child;
// Read code tokens util we reach one equal to the next token from our pattern
// or we reach the end of the token tree.
- while let Some(next) = children.next() {
+ for next in &mut children {
match &next {
SyntaxElement::Token(t) => {
if Some(t.to_string()) == next_pattern_token {
}
impl PlaceholderMatch {
- fn new(node: Option<&SyntaxNode>, range: FileRange) -> Self {
+ fn from_range(range: FileRange) -> Self {
Self {
- node: node.cloned(),
range,
inner_matches: SsrMatches::default(),
autoderef_count: 0,
autoref_kind: ast::SelfParamKind::Owned,
}
}
-
- fn from_range(range: FileRange) -> Self {
- Self::new(None, range)
- }
}
impl NodeKind {
type Item = SyntaxElement;
fn next(&mut self) -> Option<SyntaxElement> {
- while let Some(element) = self.iter.next() {
+ for element in &mut self.iter {
if !element.kind().is_trivia() {
return Some(element);
}
#[cfg(test)]
mod tests {
- use super::*;
use crate::{MatchFinder, SsrRule};
#[test]
assert_eq!(matches.matches.len(), 1);
assert_eq!(matches.matches[0].matched_node.text(), "foo(1+2)");
assert_eq!(matches.matches[0].placeholder_values.len(), 1);
- assert_eq!(
- matches.matches[0].placeholder_values[&Var("x".to_string())]
- .node
- .as_ref()
- .unwrap()
- .text(),
- "1+2"
- );
let edits = match_finder.edits();
assert_eq!(edits.len(), 1);
// will have 0 and a few will have 1. More than that should hopefully be
// exceptional.
let mut collector = MatchCollector::default();
- for m in std::mem::replace(&mut p.inner_matches.matches, Vec::new()) {
+ for m in std::mem::take(&mut p.inner_matches.matches) {
collector.matches_by_node.insert(m.matched_node.clone(), m);
}
collector.add_match(m, sema);
fn from_str(pattern_str: &str) -> Result<SsrPattern, SsrError> {
let raw_pattern = pattern_str.parse()?;
let parsed_rules = ParsedRule::new(&raw_pattern, None)?;
- Ok(SsrPattern { raw: raw_pattern, parsed_rules })
+ Ok(SsrPattern { parsed_rules })
}
}
use syntax::ast::AstNode;
if let Some(path) = ast::Path::cast(path.clone()) {
if let Some(qualifier) = path.qualifier() {
- if let Some(resolved_qualifier) = self.resolve_path(&qualifier) {
- if let hir::PathResolution::Def(hir::ModuleDef::Adt(adt)) = resolved_qualifier {
- return Some(adt.ty(self.scope.db));
- }
+ if let Some(hir::PathResolution::Def(hir::ModuleDef::Adt(adt))) =
+ self.resolve_path(&qualifier)
+ {
+ return Some(adt.ty(self.scope.db));
}
}
}
if let Some(path) =
self.sema.find_node_at_offset_with_descend::<ast::Path>(file.syntax(), offset)
{
- self.sema.ancestors_with_macros(path.syntax().clone()).skip(depth).next()
+ self.sema.ancestors_with_macros(path.syntax().clone()).nth(depth)
} else if let Some(path) =
self.sema.find_node_at_offset_with_descend::<ast::MethodCallExpr>(file.syntax(), offset)
{
}
self.sema
.ancestors_with_macros(path.syntax().clone())
- .skip(depth - PATH_DEPTH_IN_CALL_EXPR)
- .next()
+ .nth(depth - PATH_DEPTH_IN_CALL_EXPR)
} else {
None
}
stdx = { path = "../stdx", version = "0.0.0" }
[dev-dependencies]
-profile = { path = "../profile" }
test_utils = { path = "../test_utils" }
//! Conversions between [`SyntaxNode`] and [`tt::TokenTree`].
-use std::iter;
-
use parser::{ParseError, TreeSink};
-use rustc_hash::FxHashMap;
+use rustc_hash::{FxHashMap, FxHashSet};
use syntax::{
ast::{self, make::tokens::doc_comment},
- tokenize, AstToken, Parse, SmolStr, SyntaxKind,
+ tokenize, AstToken, Parse, PreorderWithTokens, SmolStr, SyntaxElement, SyntaxKind,
SyntaxKind::*,
- SyntaxNode, SyntaxToken, SyntaxTreeBuilder, TextRange, TextSize, Token as RawToken, T,
+ SyntaxNode, SyntaxToken, SyntaxTreeBuilder, TextRange, TextSize, Token as RawToken, WalkEvent,
+ T,
};
use tt::buffer::{Cursor, TokenBuffer};
/// Convert the syntax node to a `TokenTree` (what macro
/// will consume).
pub fn syntax_node_to_token_tree(node: &SyntaxNode) -> (tt::Subtree, TokenMap) {
- syntax_node_to_token_tree_censored(node, None)
+ syntax_node_to_token_tree_censored(node, &Default::default())
}
/// Convert the syntax node to a `TokenTree` (what macro will consume)
/// with the censored range excluded.
pub fn syntax_node_to_token_tree_censored(
node: &SyntaxNode,
- censor: Option<TextRange>,
+ censor: &FxHashSet<SyntaxNode>,
) -> (tt::Subtree, TokenMap) {
let global_offset = node.text_range().start();
let mut c = Convertor::new(node, global_offset, censor);
Some((subtree, conv.id_alloc.map))
}
-/// Split token tree with seperate expr: $($e:expr)SEP*
+/// Split token tree with separate expr: $($e:expr)SEP*
pub fn parse_exprs_with_sep(tt: &tt::Subtree, sep: char) -> Vec<tt::Subtree> {
if tt.token_trees.is_empty() {
return Vec::new();
while iter.peek_n(0).is_some() {
let expanded = iter.expect_fragment(ParserEntryPoint::Expr);
- if expanded.err.is_some() {
- break;
- }
res.push(match expanded.value {
None => break,
let k: SyntaxKind = token.kind();
if k == COMMENT {
if let Some(tokens) = conv.convert_doc_comment(&token) {
- result.extend(tokens);
+ // FIXME: There has to be a better way to do this
+ // Add the comments token id to the converted doc string
+ let id = conv.id_alloc().alloc(range);
+ result.extend(tokens.into_iter().map(|mut tt| {
+ if let tt::TokenTree::Subtree(sub) = &mut tt {
+ if let tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) = &mut sub.token_trees[2]
+ {
+ lit.id = id
+ }
+ }
+ tt
+ }));
}
continue;
}
}
}
-impl RawConvertor<'_> {}
-
impl<'a> TokenConvertor for RawConvertor<'a> {
type Token = (&'a RawToken, &'a str);
}
}
-struct Convertor {
+struct Convertor<'c> {
id_alloc: TokenIdAlloc,
current: Option<SyntaxToken>,
- censor: Option<TextRange>,
+ preorder: PreorderWithTokens,
+ censor: &'c FxHashSet<SyntaxNode>,
range: TextRange,
punct_offset: Option<(SyntaxToken, TextSize)>,
}
-impl Convertor {
- fn new(node: &SyntaxNode, global_offset: TextSize, censor: Option<TextRange>) -> Convertor {
- let first = node.first_token();
- let current = match censor {
- Some(censor) => iter::successors(first, |token| token.next_token())
- .find(|token| !censor.contains_range(token.text_range())),
- None => first,
- };
+impl<'c> Convertor<'c> {
+ fn new(
+ node: &SyntaxNode,
+ global_offset: TextSize,
+ censor: &'c FxHashSet<SyntaxNode>,
+ ) -> Convertor<'c> {
+ let range = node.text_range();
+ let mut preorder = node.preorder_with_tokens();
+ let first = Self::next_token(&mut preorder, censor);
Convertor {
id_alloc: { TokenIdAlloc { map: TokenMap::default(), global_offset, next_id: 0 } },
- current,
- range: node.text_range(),
+ current: first,
+ preorder,
+ range,
censor,
punct_offset: None,
}
}
+
+ fn next_token(
+ preorder: &mut PreorderWithTokens,
+ censor: &FxHashSet<SyntaxNode>,
+ ) -> Option<SyntaxToken> {
+ while let Some(ev) = preorder.next() {
+ let ele = match ev {
+ WalkEvent::Enter(ele) => ele,
+ _ => continue,
+ };
+ match ele {
+ SyntaxElement::Token(t) => return Some(t),
+ SyntaxElement::Node(node) if censor.contains(&node) => preorder.skip_subtree(),
+ SyntaxElement::Node(_) => (),
+ }
+ }
+ None
+ }
}
#[derive(Debug)]
}
}
-impl TokenConvertor for Convertor {
+impl TokenConvertor for Convertor<'_> {
type Token = SynToken;
fn convert_doc_comment(&self, token: &Self::Token) -> Option<Vec<tt::TokenTree>> {
convert_doc_comment(token.token())
if !&self.range.contains_range(curr.text_range()) {
return None;
}
- self.current = match self.censor {
- Some(censor) => iter::successors(curr.next_token(), |token| token.next_token())
- .find(|token| !censor.contains_range(token.text_range())),
- None => curr.next_token(),
- };
+ self.current = Self::next_token(&mut self.preorder, self.censor);
let token = if curr.kind().is_punct() {
let range = curr.text_range();
let range = TextRange::at(range.start(), TextSize::of('.'));
mod expand;
mod rule;
-use std::fmt::Write;
+use std::{fmt::Write, iter};
use syntax::{ast, AstNode, NodeOrToken, SyntaxNode, WalkEvent};
use test_utils::assert_eq_text;
let item = source_file.items().next().unwrap();
let attr = item.attrs().nth(1).unwrap();
- let (tt, _) =
- syntax_node_to_token_tree_censored(item.syntax(), Some(attr.syntax().text_range()));
+ let (tt, _) = syntax_node_to_token_tree_censored(
+ item.syntax(),
+ &iter::once(attr.syntax().clone()).collect(),
+ );
expect_test::expect![[r##"# [attr0] # [attr2] struct Struct {field : ()}"##]]
.assert_eq(&tt.to_string());
let source = r##"
+#[attr0]
#[derive(Derive0)]
+#[attr1]
#[derive(Derive1)]
+#[attr2]
#[derive(Derive2)]
+#[attr3]
struct Struct {
field: ()
}
"##;
let source_file = ast::SourceFile::parse(source).ok().unwrap();
let item = source_file.items().next().unwrap();
- let attr = item.attrs().nth(1).unwrap();
-
- let (tt, _) = syntax_node_to_token_tree_censored(
- item.syntax(),
- Some(attr.syntax().text_range().cover_offset(0.into())),
- );
- expect_test::expect![[r##"# [derive (Derive2)] struct Struct {field : ()}"##]]
+ let derive_attr_index = 3;
+ let censor = item
+ .attrs()
+ .take(derive_attr_index as usize + 1)
+ .filter(|attr| attr.simple_name().as_deref() == Some("derive"))
+ .map(|it| it.syntax().clone())
+ .collect();
+
+ let (tt, _) = syntax_node_to_token_tree_censored(item.syntax(), &censor);
+ expect_test::expect![[r##"# [attr0] # [attr1] # [attr2] # [derive (Derive2)] # [attr3] struct Struct {field : ()}"##]]
.assert_eq(&tt.to_string());
}
mod params;
mod paths;
mod patterns;
-mod type_args;
-mod type_params;
+mod generic_args;
+mod generic_params;
mod types;
use crate::{
// struct B(pub (super::A));
// struct B(pub (crate::A,));
T![crate] | T![self] | T![super] | T![ident] if p.nth(2) != T![:] => {
- p.bump_any();
- let path_m = p.start();
- let path_segment_m = p.start();
- let name_ref_m = p.start();
- p.bump_any();
- name_ref_m.complete(p, NAME_REF);
- path_segment_m.complete(p, PATH_SEGMENT);
- path_m.complete(p, PATH);
+ p.bump(T!['(']);
+ paths::use_path(p);
p.expect(T![')']);
}
// test crate_visibility_in
// pub(in super::A) struct S;
// pub(in crate) struct S;
T![in] => {
- p.bump_any();
- p.bump_any();
+ p.bump(T!['(']);
+ p.bump(T![in]);
paths::use_path(p);
p.expect(T![')']);
}
}
}
m.complete(p, VISIBILITY);
+ true
}
// test crate_keyword_vis
// crate fn main() { }
// struct S { crate field: u32 }
// struct T(crate u32);
- //
- // test crate_keyword_path
- // fn foo() { crate::foo(); }
- T![crate] if !p.nth_at(1, T![::]) => {
+ T![crate] => {
+ if p.nth_at(1, T![::]) {
+ // test crate_keyword_path
+ // fn foo() { crate::foo(); }
+ return false;
+ }
let m = p.start();
p.bump(T![crate]);
m.complete(p, VISIBILITY);
+ true
}
- _ => return false,
+ _ => false,
}
- true
}
fn opt_rename(p: &mut Parser) {
}
}
-pub(super) fn meta(p: &mut Parser) {
- let meta = p.start();
- paths::use_path(p);
-
- match p.current() {
- T![=] => {
- p.bump(T![=]);
- if expressions::expr(p).0.is_none() {
- p.error("expected expression");
- }
- }
- T!['('] | T!['['] | T!['{'] => items::token_tree(p),
- _ => {}
- }
-
- meta.complete(p, META);
-}
-
fn attr(p: &mut Parser, inner: bool) {
- let attr = p.start();
assert!(p.at(T![#]));
+
+ let attr = p.start();
p.bump(T![#]);
if inner {
- assert!(p.at(T![!]));
p.bump(T![!]);
}
}
attr.complete(p, ATTR);
}
+
+pub(super) fn meta(p: &mut Parser) {
+ let meta = p.start();
+ paths::use_path(p);
+
+ match p.current() {
+ T![=] => {
+ p.bump(T![=]);
+ if expressions::expr(p).0.is_none() {
+ p.error("expected expression");
+ }
+ }
+ T!['('] | T!['['] | T!['{'] => items::token_tree(p),
+ _ => {}
+ }
+
+ meta.complete(p, META);
+}
// test block_items
// fn a() { fn b() {} }
- let m = match items::maybe_item(p, m) {
+ let m = match items::opt_item(p, m) {
Ok(()) => return,
Err(m) => m,
};
// let mut p = F{x: 5};
// {p}.x = 10;
// }
- //
let (lhs, blocklike) = atom::atom_expr(p, r)?;
return Some(postfix_expr(p, lhs, blocklike, !(r.prefer_stmt && blocklike.is_block())));
}
let m = lhs.precede(p);
p.bump_any();
name_ref(p);
- type_args::opt_generic_arg_list(p, true);
+ generic_args::opt_generic_arg_list(p, true);
if p.at(T!['(']) {
arg_list(p);
}
--- /dev/null
+use super::*;
+
+pub(super) fn opt_generic_arg_list(p: &mut Parser, colon_colon_required: bool) {
+ let m;
+ if p.at(T![::]) && p.nth(2) == T![<] {
+ m = p.start();
+ p.bump(T![::]);
+ p.bump(T![<]);
+ } else if !colon_colon_required && p.at(T![<]) && p.nth(1) != T![=] {
+ m = p.start();
+ p.bump(T![<]);
+ } else {
+ return;
+ }
+
+ while !p.at(EOF) && !p.at(T![>]) {
+ generic_arg(p);
+ if !p.at(T![>]) && !p.expect(T![,]) {
+ break;
+ }
+ }
+ p.expect(T![>]);
+ m.complete(p, GENERIC_ARG_LIST);
+}
+
+// test generic_arg
+// type T = S<i32>;
+fn generic_arg(p: &mut Parser) {
+ match p.current() {
+ LIFETIME_IDENT => lifetime_arg(p),
+ T!['{'] | T![true] | T![false] | T![-] => const_arg(p),
+ k if k.is_literal() => const_arg(p),
+ // test associated_type_bounds
+ // fn print_all<T: Iterator<Item, Item::Item, Item::<true>, Item: Display, Item<'a> = Item>>(printables: T) {}
+ IDENT if [T![<], T![=], T![:]].contains(&p.nth(1)) => {
+ let m = p.start();
+ name_ref(p);
+ opt_generic_arg_list(p, false);
+ match p.current() {
+ // test assoc_type_eq
+ // type T = StreamingIterator<Item<'a> = &'a T>;
+ T![=] => {
+ p.bump_any();
+ types::type_(p);
+ m.complete(p, ASSOC_TYPE_ARG);
+ }
+ // test assoc_type_bound
+ // type T = StreamingIterator<Item<'a>: Clone>;
+ T![:] if !p.at(T![::]) => {
+ generic_params::bounds(p);
+ m.complete(p, ASSOC_TYPE_ARG);
+ }
+ _ => {
+ let m = m.complete(p, PATH_SEGMENT).precede(p).complete(p, PATH);
+ let m = paths::type_path_for_qualifier(p, m);
+ m.precede(p).complete(p, PATH_TYPE).precede(p).complete(p, TYPE_ARG);
+ }
+ }
+ }
+ _ => type_arg(p),
+ }
+}
+
+// test lifetime_arg
+// type T = S<'static>;
+fn lifetime_arg(p: &mut Parser) {
+ let m = p.start();
+ lifetime(p);
+ m.complete(p, LIFETIME_ARG);
+}
+
+// test const_arg
+// type T = S<92>;
+pub(super) fn const_arg(p: &mut Parser) {
+ let m = p.start();
+ match p.current() {
+ // test const_arg_block
+ // type T = S<{90 + 2}>;
+ T!['{'] => {
+ expressions::block_expr(p);
+ m.complete(p, CONST_ARG);
+ }
+ // test const_arg_literal
+ // type T = S<"hello", 0xdeadbeef>;
+ k if k.is_literal() => {
+ expressions::literal(p);
+ m.complete(p, CONST_ARG);
+ }
+ // test const_arg_bool_literal
+ // type T = S<true>;
+ T![true] | T![false] => {
+ expressions::literal(p);
+ m.complete(p, CONST_ARG);
+ }
+ // test const_arg_negative_number
+ // type T = S<-92>;
+ T![-] => {
+ let lm = p.start();
+ p.bump(T![-]);
+ expressions::literal(p);
+ lm.complete(p, PREFIX_EXPR);
+ m.complete(p, CONST_ARG);
+ }
+ // test const_arg_path
+ // struct S<const N: u32 = u32::MAX>;
+ _ => {
+ let lm = p.start();
+ paths::use_path(p);
+ lm.complete(p, PATH_EXPR);
+ m.complete(p, CONST_ARG);
+ }
+ }
+}
+
+fn type_arg(p: &mut Parser) {
+ let m = p.start();
+ types::type_(p);
+ m.complete(p, TYPE_ARG);
+}
--- /dev/null
+use super::*;
+
+pub(super) fn opt_generic_param_list(p: &mut Parser) {
+ if p.at(T![<]) {
+ generic_param_list(p);
+ }
+}
+
+// test generic_param_list
+// fn f<T: Clone>() {}
+fn generic_param_list(p: &mut Parser) {
+ assert!(p.at(T![<]));
+ let m = p.start();
+ p.bump(T![<]);
+
+ while !p.at(EOF) && !p.at(T![>]) {
+ generic_param(p);
+ if !p.at(T![>]) && !p.expect(T![,]) {
+ break;
+ }
+ }
+ p.expect(T![>]);
+ m.complete(p, GENERIC_PARAM_LIST);
+}
+
+fn generic_param(p: &mut Parser) {
+ let m = p.start();
+ // test generic_param_attribute
+ // fn foo<#[lt_attr] 'a, #[t_attr] T>() {}
+ attributes::outer_attrs(p);
+ match p.current() {
+ LIFETIME_IDENT => lifetime_param(p, m),
+ IDENT => type_param(p, m),
+ T![const] => const_param(p, m),
+ _ => {
+ m.abandon(p);
+ p.err_and_bump("expected type parameter")
+ }
+ }
+}
+
+// test lifetime_param
+// fn f<'a: 'b>() {}
+fn lifetime_param(p: &mut Parser, m: Marker) {
+ assert!(p.at(LIFETIME_IDENT));
+ lifetime(p);
+ if p.at(T![:]) {
+ lifetime_bounds(p);
+ }
+ m.complete(p, LIFETIME_PARAM);
+}
+
+// test type_param
+// fn f<T: Clone>() {}
+fn type_param(p: &mut Parser, m: Marker) {
+ assert!(p.at(IDENT));
+ name(p);
+ if p.at(T![:]) {
+ bounds(p);
+ }
+ if p.at(T![=]) {
+ // test type_param_default
+ // struct S<T = i32>;
+ p.bump(T![=]);
+ types::type_(p)
+ }
+ m.complete(p, TYPE_PARAM);
+}
+
+// test const_param
+// struct S<const N: u32>;
+fn const_param(p: &mut Parser, m: Marker) {
+ p.bump(T![const]);
+ name(p);
+ if p.at(T![:]) {
+ types::ascription(p);
+ } else {
+ p.error("missing type for const parameter");
+ }
+
+ if p.at(T![=]) {
+ // test const_param_defaults
+ // struct A<const N: i32 = -1>;
+ p.bump(T![=]);
+ generic_args::const_arg(p);
+ }
+
+ m.complete(p, CONST_PARAM);
+}
+
+fn lifetime_bounds(p: &mut Parser) {
+ assert!(p.at(T![:]));
+ p.bump(T![:]);
+ while p.at(LIFETIME_IDENT) {
+ lifetime(p);
+ if !p.eat(T![+]) {
+ break;
+ }
+ }
+}
+
+// test type_param_bounds
+// struct S<T: 'a + ?Sized + (Copy)>;
+pub(super) fn bounds(p: &mut Parser) {
+ assert!(p.at(T![:]));
+ p.bump(T![:]);
+ bounds_without_colon(p);
+}
+
+pub(super) fn bounds_without_colon(p: &mut Parser) {
+ let m = p.start();
+ bounds_without_colon_m(p, m);
+}
+
+pub(super) fn bounds_without_colon_m(p: &mut Parser, marker: Marker) -> CompletedMarker {
+ while type_bound(p) {
+ if !p.eat(T![+]) {
+ break;
+ }
+ }
+ marker.complete(p, TYPE_BOUND_LIST)
+}
+
+fn type_bound(p: &mut Parser) -> bool {
+ let m = p.start();
+ let has_paren = p.eat(T!['(']);
+ p.eat(T![?]);
+ match p.current() {
+ LIFETIME_IDENT => lifetime(p),
+ T![for] => types::for_type(p, false),
+ _ if paths::is_use_path_start(p) => types::path_type_(p, false),
+ _ => {
+ m.abandon(p);
+ return false;
+ }
+ }
+ if has_paren {
+ p.expect(T![')']);
+ }
+ m.complete(p, TYPE_BOUND);
+
+ true
+}
+
+// test where_clause
+// fn foo()
+// where
+// 'a: 'b + 'c,
+// T: Clone + Copy + 'static,
+// Iterator::Item: 'a,
+// <T as Iterator>::Item: 'a
+// {}
+pub(super) fn opt_where_clause(p: &mut Parser) {
+ if !p.at(T![where]) {
+ return;
+ }
+ let m = p.start();
+ p.bump(T![where]);
+
+ while is_where_predicate(p) {
+ where_predicate(p);
+
+ let comma = p.eat(T![,]);
+
+ match p.current() {
+ T!['{'] | T![;] | T![=] => break,
+ _ => (),
+ }
+
+ if !comma {
+ p.error("expected comma");
+ }
+ }
+
+ m.complete(p, WHERE_CLAUSE);
+
+ fn is_where_predicate(p: &mut Parser) -> bool {
+ match p.current() {
+ LIFETIME_IDENT => true,
+ T![impl] => false,
+ token => types::TYPE_FIRST.contains(token),
+ }
+ }
+}
+
+fn where_predicate(p: &mut Parser) {
+ let m = p.start();
+ match p.current() {
+ LIFETIME_IDENT => {
+ lifetime(p);
+ if p.at(T![:]) {
+ bounds(p);
+ } else {
+ p.error("expected colon");
+ }
+ }
+ T![impl] => {
+ p.error("expected lifetime or type");
+ }
+ _ => {
+ if p.at(T![for]) {
+ // test where_pred_for
+ // fn for_trait<F>()
+ // where
+ // for<'a> F: Fn(&'a str)
+ // { }
+ types::for_binder(p);
+ }
+
+ types::type_(p);
+
+ if p.at(T![:]) {
+ bounds(p);
+ } else {
+ p.error("expected colon");
+ }
+ }
+ }
+ m.complete(p, WHERE_PRED);
+}
// struct S;
pub(super) fn mod_contents(p: &mut Parser, stop_on_r_curly: bool) {
attributes::inner_attrs(p);
- while !(stop_on_r_curly && p.at(T!['}']) || p.at(EOF)) {
+ while !p.at(EOF) && !(p.at(T!['}']) && stop_on_r_curly) {
item_or_macro(p, stop_on_r_curly)
}
}
pub(super) fn item_or_macro(p: &mut Parser, stop_on_r_curly: bool) {
let m = p.start();
attributes::outer_attrs(p);
- let m = match maybe_item(p, m) {
+
+ let m = match opt_item(p, m) {
Ok(()) => {
if p.at(T![;]) {
p.err_and_bump(
}
Err(m) => m,
};
+
if paths::is_use_path_start(p) {
match macro_call(p) {
BlockLike::Block => (),
}
}
m.complete(p, MACRO_CALL);
- } else {
- m.abandon(p);
- if p.at(T!['{']) {
- error_block(p, "expected an item");
- } else if p.at(T!['}']) && !stop_on_r_curly {
+ return;
+ }
+
+ m.abandon(p);
+ match p.current() {
+ T!['{'] => error_block(p, "expected an item"),
+ T!['}'] if !stop_on_r_curly => {
let e = p.start();
p.error("unmatched `}`");
p.bump(T!['}']);
e.complete(p, ERROR);
- } else if !p.at(EOF) && !p.at(T!['}']) {
- p.err_and_bump("expected an item");
- } else {
- p.error("expected an item");
}
+ EOF | T!['}'] => p.error("expected an item"),
+ _ => p.err_and_bump("expected an item"),
}
}
/// Try to parse an item, completing `m` in case of success.
-pub(super) fn maybe_item(p: &mut Parser, m: Marker) -> Result<(), Marker> {
+pub(super) fn opt_item(p: &mut Parser, m: Marker) -> Result<(), Marker> {
// test_err pub_expr
// fn foo() { pub 92; }
let has_visibility = opt_visibility(p);
- let m = match items_without_modifiers(p, m) {
+ let m = match opt_item_without_modifiers(p, m) {
Ok(()) => return Ok(()),
Err(m) => m,
};
p.bump_remap(T![default]);
has_mods = true;
}
- T![unsafe] => {
- // test default_unsafe_item
- // default unsafe impl T for Foo {
- // default unsafe fn foo() {}
- // }
- if matches!(p.nth(2), T![impl] | T![fn]) {
- p.bump_remap(T![default]);
- p.bump(T![unsafe]);
- has_mods = true;
- }
+ // test default_unsafe_item
+ // default unsafe impl T for Foo {
+ // default unsafe fn foo() {}
+ // }
+ T![unsafe] if matches!(p.nth(2), T![impl] | T![fn]) => {
+ p.bump_remap(T![default]);
+ p.bump(T![unsafe]);
+ has_mods = true;
}
+ // test default_async_fn
+ // impl T for Foo {
+ // default async fn foo() {}
+ // }
T![async] => {
- // test default_async_fn
- // impl T for Foo {
- // default async fn foo() {}
- // }
-
- // test default_async_unsafe_fn
- // impl T for Foo {
- // default async unsafe fn foo() {}
- // }
let mut maybe_fn = p.nth(2);
let is_unsafe = if matches!(maybe_fn, T![unsafe]) {
+ // test default_async_unsafe_fn
+ // impl T for Foo {
+ // default async unsafe fn foo() {}
+ // }
maybe_fn = p.nth(3);
true
} else {
// items
match p.current() {
- // test fn
- // fn foo() {}
- T![fn] => {
- fn_(p);
- m.complete(p, FN);
- }
-
- // test trait
- // trait T {}
- T![trait] => {
- traits::trait_(p);
- m.complete(p, TRAIT);
- }
+ T![fn] => fn_(p, m),
- T![const] if p.nth(1) != T!['{'] => {
- consts::konst(p, m);
- }
+ T![const] if p.nth(1) != T!['{'] => consts::konst(p, m),
- // test impl
- // impl T for S {}
- T![impl] => {
- traits::impl_(p);
- m.complete(p, IMPL);
- }
+ T![trait] => traits::trait_(p, m),
+ T![impl] => traits::impl_(p, m),
- T![type] => {
- type_alias(p, m);
- }
+ T![type] => type_alias(p, m),
// test extern_block
// unsafe extern "C" {}
Ok(())
}
-fn items_without_modifiers(p: &mut Parser, m: Marker) -> Result<(), Marker> {
+fn opt_item_without_modifiers(p: &mut Parser, m: Marker) -> Result<(), Marker> {
let la = p.nth(1);
match p.current() {
- // test extern_crate
- // extern crate foo;
T![extern] if la == T![crate] => extern_crate(p, m),
T![use] => use_item::use_(p, m),
T![mod] => mod_item(p, m),
T![type] => type_alias(p, m),
-
- T![struct] => {
- // test struct_items
- // struct Foo;
- // struct Foo {}
- // struct Foo();
- // struct Foo(String, usize);
- // struct Foo {
- // a: i32,
- // b: f32,
- // }
- adt::strukt(p, m);
- }
+ T![struct] => adt::strukt(p, m),
T![enum] => adt::enum_(p, m),
- IDENT if p.at_contextual_kw("union") && p.nth(1) == IDENT => {
- // test union_items
- // union Foo {}
- // union Foo {
- // a: i32,
- // b: f32,
- // }
- adt::union(p, m);
- }
+ IDENT if p.at_contextual_kw("union") && p.nth(1) == IDENT => adt::union(p, m),
- // test pub_macro_def
- // pub macro m($:ident) {}
- T![macro] => {
- macro_def(p, m);
- }
- IDENT if p.at_contextual_kw("macro_rules") && p.nth(1) == BANG => {
- macro_rules(p, m);
- }
+ T![macro] => macro_def(p, m),
+ IDENT if p.at_contextual_kw("macro_rules") && p.nth(1) == BANG => macro_rules(p, m),
T![const] if (la == IDENT || la == T![_] || la == T![mut]) => consts::konst(p, m),
T![static] => consts::static_(p, m),
Ok(())
}
+// test extern_crate
+// extern crate foo;
fn extern_crate(p: &mut Parser, m: Marker) {
- assert!(p.at(T![extern]));
p.bump(T![extern]);
-
- assert!(p.at(T![crate]));
p.bump(T![crate]);
if p.at(T![self]) {
+ // test extern_crate_self
+ // extern crate self;
let m = p.start();
p.bump(T![self]);
m.complete(p, NAME_REF);
name_ref(p);
}
+ // test extern_crate_rename
+ // extern crate foo as bar;
opt_rename(p);
p.expect(T![;]);
m.complete(p, EXTERN_CRATE);
}
-pub(crate) fn extern_item_list(p: &mut Parser) {
- assert!(p.at(T!['{']));
- let m = p.start();
- p.bump(T!['{']);
- mod_contents(p, true);
- p.expect(T!['}']);
- m.complete(p, EXTERN_ITEM_LIST);
-}
-
-fn fn_(p: &mut Parser) {
- assert!(p.at(T![fn]));
- p.bump(T![fn]);
-
- name_r(p, ITEM_RECOVERY_SET);
- // test function_type_params
- // fn foo<T: Clone + Copy>(){}
- type_params::opt_generic_param_list(p);
-
- if p.at(T!['(']) {
- params::param_list_fn_def(p);
- } else {
- p.error("expected function arguments");
- }
- // test function_ret_type
- // fn foo() {}
- // fn bar() -> () {}
- opt_ret_type(p);
-
- // test function_where_clause
- // fn foo<T>() where T: Copy {}
- type_params::opt_where_clause(p);
-
- // test fn_decl
- // trait T { fn foo(); }
- if p.at(T![;]) {
- p.bump(T![;]);
- } else {
- expressions::block_expr(p)
+// test mod_item
+// mod a;
+pub(crate) fn mod_item(p: &mut Parser, m: Marker) {
+ p.bump(T![mod]);
+ name(p);
+ if p.at(T!['{']) {
+ // test mod_item_curly
+ // mod b { }
+ item_list(p);
+ } else if !p.eat(T![;]) {
+ p.error("expected `;` or `{`");
}
+ m.complete(p, MODULE);
}
-// test type_item
+// test type_alias
// type Foo = Bar;
fn type_alias(p: &mut Parser, m: Marker) {
- assert!(p.at(T![type]));
p.bump(T![type]);
name(p);
// test type_item_type_params
// type Result<T> = ();
- type_params::opt_generic_param_list(p);
+ generic_params::opt_generic_param_list(p);
if p.at(T![:]) {
- type_params::bounds(p);
+ generic_params::bounds(p);
}
// test type_item_where_clause
// type Foo where Foo: Copy = ();
- type_params::opt_where_clause(p);
+ generic_params::opt_where_clause(p);
if p.eat(T![=]) {
types::type_(p);
}
m.complete(p, TYPE_ALIAS);
}
-pub(crate) fn mod_item(p: &mut Parser, m: Marker) {
- assert!(p.at(T![mod]));
- p.bump(T![mod]);
-
- name(p);
- if p.at(T!['{']) {
- item_list(p);
- } else if !p.eat(T![;]) {
- p.error("expected `;` or `{`");
- }
- m.complete(p, MODULE);
-}
-
pub(crate) fn item_list(p: &mut Parser) {
assert!(p.at(T!['{']));
let m = p.start();
m.complete(p, ITEM_LIST);
}
+pub(crate) fn extern_item_list(p: &mut Parser) {
+ assert!(p.at(T!['{']));
+ let m = p.start();
+ p.bump(T!['{']);
+ mod_contents(p, true);
+ p.expect(T!['}']);
+ m.complete(p, EXTERN_ITEM_LIST);
+}
+
fn macro_rules(p: &mut Parser, m: Marker) {
assert!(p.at_contextual_kw("macro_rules"));
p.bump_remap(T![macro_rules]);
}
// test macro_def
-// macro m { ($i:ident) => {} }
// macro m($i:ident) {}
fn macro_def(p: &mut Parser, m: Marker) {
p.expect(T![macro]);
name_r(p, ITEM_RECOVERY_SET);
if p.at(T!['{']) {
+ // test macro_def_curly
+ // macro m { ($i:ident) => {} }
token_tree(p);
- } else if !p.at(T!['(']) {
- p.error("unmatched `(`");
- } else {
+ } else if p.at(T!['(']) {
let m = p.start();
token_tree(p);
match p.current() {
_ => p.error("expected `{`, `[`, `(`"),
}
m.complete(p, TOKEN_TREE);
+ } else {
+ p.error("unmatched `(`");
}
m.complete(p, MACRO_DEF);
}
+// test fn
+// fn foo() {}
+fn fn_(p: &mut Parser, m: Marker) {
+ p.bump(T![fn]);
+
+ name_r(p, ITEM_RECOVERY_SET);
+ // test function_type_params
+ // fn foo<T: Clone + Copy>(){}
+ generic_params::opt_generic_param_list(p);
+
+ if p.at(T!['(']) {
+ params::param_list_fn_def(p);
+ } else {
+ p.error("expected function arguments");
+ }
+ // test function_ret_type
+ // fn foo() {}
+ // fn bar() -> () {}
+ opt_ret_type(p);
+
+ // test function_where_clause
+ // fn foo<T>() where T: Copy {}
+ generic_params::opt_where_clause(p);
+
+ if p.at(T![;]) {
+ // test fn_decl
+ // trait T { fn foo(); }
+ p.bump(T![;]);
+ } else {
+ expressions::block_expr(p)
+ }
+ m.complete(p, FN);
+}
+
fn macro_call(p: &mut Parser) -> BlockLike {
assert!(paths::is_use_path_start(p));
paths::use_path(p);
use super::*;
+// test struct_item
+// struct S {}
pub(super) fn strukt(p: &mut Parser, m: Marker) {
- assert!(p.at(T![struct]));
p.bump(T![struct]);
- struct_or_union(p, m, T![struct], STRUCT);
+ struct_or_union(p, m, true);
}
+// test union_item
+// struct U { i: i32, f: f32 }
pub(super) fn union(p: &mut Parser, m: Marker) {
assert!(p.at_contextual_kw("union"));
p.bump_remap(T![union]);
- struct_or_union(p, m, T![union], UNION);
+ struct_or_union(p, m, false);
}
-fn struct_or_union(p: &mut Parser, m: Marker, kw: SyntaxKind, def: SyntaxKind) {
+fn struct_or_union(p: &mut Parser, m: Marker, is_struct: bool) {
name_r(p, ITEM_RECOVERY_SET);
- type_params::opt_generic_param_list(p);
+ generic_params::opt_generic_param_list(p);
match p.current() {
T![where] => {
- type_params::opt_where_clause(p);
+ generic_params::opt_where_clause(p);
match p.current() {
- T![;] => {
- p.bump(T![;]);
- }
+ T![;] => p.bump(T![;]),
T!['{'] => record_field_list(p),
_ => {
//FIXME: special case `(` error message
}
}
}
- T![;] if kw == T![struct] => {
+ T!['{'] => record_field_list(p),
+ // test unit_struct
+ // struct S;
+ T![;] if is_struct => {
p.bump(T![;]);
}
- T!['{'] => record_field_list(p),
- T!['('] if kw == T![struct] => {
+ // test tuple_struct
+ // struct S(String, usize);
+ T!['('] if is_struct => {
tuple_field_list(p);
// test tuple_struct_where
- // struct Test<T>(T) where T: Clone;
- // struct Test<T>(T);
- type_params::opt_where_clause(p);
+ // struct S<T>(T) where T: Clone;
+ generic_params::opt_where_clause(p);
p.expect(T![;]);
}
- _ if kw == T![struct] => {
- p.error("expected `;`, `{`, or `(`");
- }
- _ => {
- p.error("expected `{`");
- }
+ _ => p.error(if is_struct { "expected `;`, `{`, or `(`" } else { "expected `{`" }),
}
- m.complete(p, def);
+ m.complete(p, if is_struct { STRUCT } else { UNION });
}
pub(super) fn enum_(p: &mut Parser, m: Marker) {
- assert!(p.at(T![enum]));
p.bump(T![enum]);
name_r(p, ITEM_RECOVERY_SET);
- type_params::opt_generic_param_list(p);
- type_params::opt_where_clause(p);
+ generic_params::opt_generic_param_list(p);
+ generic_params::opt_where_clause(p);
if p.at(T!['{']) {
variant_list(p);
} else {
error_block(p, "expected enum variant");
continue;
}
- let var = p.start();
+ variant(p);
+ if !p.at(T!['}']) {
+ p.expect(T![,]);
+ }
+ }
+ p.expect(T!['}']);
+ m.complete(p, VARIANT_LIST);
+
+ fn variant(p: &mut Parser) {
+ let m = p.start();
attributes::outer_attrs(p);
if p.at(IDENT) {
name(p);
if p.eat(T![=]) {
expressions::expr(p);
}
- var.complete(p, VARIANT);
+ m.complete(p, VARIANT);
} else {
- var.abandon(p);
+ m.abandon(p);
p.err_and_bump("expected enum variant");
}
- if !p.at(T!['}']) {
- p.expect(T![,]);
- }
}
- p.expect(T!['}']);
- m.complete(p, VARIANT_LIST);
}
+// test record_field_list
+// struct S { a: i32, b: f32 }
pub(crate) fn record_field_list(p: &mut Parser) {
assert!(p.at(T!['{']));
let m = p.start();
error_block(p, "expected field");
continue;
}
- record_field_def(p);
+ record_field(p);
if !p.at(T!['}']) {
p.expect(T![,]);
}
p.expect(T!['}']);
m.complete(p, RECORD_FIELD_LIST);
- fn record_field_def(p: &mut Parser) {
+ fn record_field(p: &mut Parser) {
let m = p.start();
// test record_field_attrs
- // struct S {
- // #[serde(with = "url_serde")]
- // pub uri: Uri,
- // }
+ // struct S { #[attr] f: f32 }
attributes::outer_attrs(p);
opt_visibility(p);
if p.at(IDENT) {
fn tuple_field_list(p: &mut Parser) {
assert!(p.at(T!['(']));
let m = p.start();
- if !p.expect(T!['(']) {
- return;
- }
+ p.bump(T!['(']);
while !p.at(T![')']) && !p.at(EOF) {
let m = p.start();
// test tuple_field_attrs
- // struct S (
- // #[serde(with = "url_serde")]
- // pub Uri,
- // );
- //
- // enum S {
- // Uri(#[serde(with = "url_serde")] Uri),
- // }
+ // struct S (#[attr] f32);
attributes::outer_attrs(p);
opt_visibility(p);
if !p.at_ts(types::TYPE_FIRST) {
use super::*;
-pub(super) fn static_(p: &mut Parser, m: Marker) {
- const_or_static(p, m, T![static], STATIC)
+// test const_item
+// const C: u32 = 92;
+pub(super) fn konst(p: &mut Parser, m: Marker) {
+ p.bump(T![const]);
+ const_or_static(p, m, true)
}
-pub(super) fn konst(p: &mut Parser, m: Marker) {
- const_or_static(p, m, T![const], CONST)
+pub(super) fn static_(p: &mut Parser, m: Marker) {
+ p.bump(T![static]);
+ const_or_static(p, m, false)
}
-fn const_or_static(p: &mut Parser, m: Marker, kw: SyntaxKind, def: SyntaxKind) {
- assert!(p.at(kw));
- p.bump(kw);
+fn const_or_static(p: &mut Parser, m: Marker, is_const: bool) {
p.eat(T![mut]);
- // Allow `_` in place of an identifier in a `const`.
- let is_const_underscore = kw == T![const] && p.eat(T![_]);
- if !is_const_underscore {
+ if is_const && p.eat(T![_]) {
+ // test anonymous_const
+ // const _: u32 = 0;
+ } else {
+ // test_err anonymous_static
+ // static _: i32 = 5;
name(p);
}
- // test_err static_underscore
- // static _: i32 = 5;
if p.at(T![:]) {
types::ascription(p);
} else {
expressions::expr(p);
}
p.expect(T![;]);
- m.complete(p, def);
+ m.complete(p, if is_const { CONST } else { STATIC });
}
use super::*;
// test trait_item
-// trait T<U>: Hash + Clone where U: Copy {}
-// trait X<U: Debug + Display>: Hash + Clone where U: Copy {}
-pub(super) fn trait_(p: &mut Parser) {
- assert!(p.at(T![trait]));
+// trait T { fn new() -> Self; }
+pub(super) fn trait_(p: &mut Parser, m: Marker) {
p.bump(T![trait]);
name_r(p, ITEM_RECOVERY_SET);
- type_params::opt_generic_param_list(p);
- // test trait_alias
- // trait Z<U> = T<U>;
- // trait Z<U> = T<U> where U: Copy;
- // trait Z<U> = where Self: T<U>;
+
+ // test trait_item_generic_params
+ // trait X<U: Debug + Display> {}
+ generic_params::opt_generic_param_list(p);
+
if p.eat(T![=]) {
- type_params::bounds_without_colon(p);
- type_params::opt_where_clause(p);
+ // test trait_alias
+ // trait Z<U> = T<U>;
+ generic_params::bounds_without_colon(p);
+
+ // test trait_alias_where_clause
+ // trait Z<U> = T<U> where U: Copy;
+ // trait Z<U> = where Self: T<U>;
+ generic_params::opt_where_clause(p);
p.expect(T![;]);
+ m.complete(p, TRAIT);
return;
}
+
if p.at(T![:]) {
- type_params::bounds(p);
+ // test trait_item_bounds
+ // trait T: Hash + Clone {}
+ generic_params::bounds(p);
}
- type_params::opt_where_clause(p);
+
+ // test trait_item_where_clause
+ // trait T where Self: Copy {}
+ generic_params::opt_where_clause(p);
+
if p.at(T!['{']) {
assoc_item_list(p);
} else {
p.error("expected `{`");
}
+ m.complete(p, TRAIT);
}
-// test impl_def
-// impl Foo {}
-pub(super) fn impl_(p: &mut Parser) {
- assert!(p.at(T![impl]));
+// test impl_item
+// impl S {}
+pub(super) fn impl_(p: &mut Parser, m: Marker) {
p.bump(T![impl]);
- if choose_type_params_over_qpath(p) {
- type_params::opt_generic_param_list(p);
+ if p.at(T![<]) && not_a_qualified_path(p) {
+ generic_params::opt_generic_param_list(p);
}
- // test impl_def_const
- // impl const Send for X {}
+ // test impl_item_const
+ // impl const Send for S {}
p.eat(T![const]);
// FIXME: never type
// impl ! {}
- // test impl_def_neg
- // impl !Send for X {}
+ // test impl_item_neg
+ // impl !Send for S {}
p.eat(T![!]);
impl_type(p);
if p.eat(T![for]) {
impl_type(p);
}
- type_params::opt_where_clause(p);
+ generic_params::opt_where_clause(p);
if p.at(T!['{']) {
assoc_item_list(p);
} else {
p.error("expected `{`");
}
+ m.complete(p, IMPL);
}
-// test impl_item_list
+// test assoc_item_list
// impl F {
// type A = i32;
// const B: i32 = 92;
// }
pub(crate) fn assoc_item_list(p: &mut Parser) {
assert!(p.at(T!['{']));
+
let m = p.start();
p.bump(T!['{']);
- // test impl_inner_attributes
- // enum F{}
- // impl F {
- // //! This is a doc comment
- // #![doc("This is also a doc comment")]
- // }
+ // test assoc_item_list_inner_attrs
+ // impl S { #![attr] }
attributes::inner_attrs(p);
while !p.at(EOF) && !p.at(T!['}']) {
// test impl_type_params
// impl<const N: u32> Bar<N> {}
-fn choose_type_params_over_qpath(p: &Parser) -> bool {
+fn not_a_qualified_path(p: &Parser) -> bool {
// There's an ambiguity between generic parameters and qualified paths in impls.
// If we see `<` it may start both, so we have to inspect some following tokens.
// The following combinations can only start generics,
// we disambiguate it in favor of generics (`impl<T> ::absolute::Path<T> { ... }`)
// because this is what almost always expected in practice, qualified paths in impls
// (`impl <Type>::AssocTy { ... }`) aren't even allowed by type checker at the moment.
- if !p.at(T![<]) {
- return false;
- }
if p.nth(1) == T![#] || p.nth(1) == T![>] || p.nth(1) == T![const] {
return true;
}
use super::*;
+// test use_item
+// use std::collections;
pub(super) fn use_(p: &mut Parser, m: Marker) {
- assert!(p.at(T![use]));
p.bump(T![use]);
use_tree(p, true);
p.expect(T![;]);
m.complete(p, USE);
}
-/// Parse a use 'tree', such as `some::path` in `use some::path;`
-/// Note that this is called both by `use_item` and `use_tree_list`,
-/// so handles both `some::path::{inner::path}` and `inner::path` in
-/// `use some::path::{inner::path};`
+// test use_tree
+// use outer::tree::{inner::tree};
fn use_tree(p: &mut Parser, top_level: bool) {
let m = p.start();
match p.current() {
- // Finish the use_tree for cases of e.g.
- // `use some::path::{self, *};` or `use *;`
- // This does not handle cases such as `use some::path::*`
- // N.B. in Rust 2015 `use *;` imports all from crate root
- // however in Rust 2018 `use *;` errors: ('cannot glob-import all possible crates')
- // FIXME: Add this error (if not out of scope)
-
- // test use_star
+ // test use_tree_star
// use *;
- // use ::*;
- // use some::path::{*};
- // use some::path::{::*};
+ // use std::{*};
T![*] => p.bump(T![*]),
+ // test use_tree_abs_star
+ // use ::*;
+ // use std::{::*};
T![:] if p.at(T![::]) && p.nth(2) == T![*] => {
- // Parse `use ::*;`, which imports all from the crate root in Rust 2015
- // This is invalid inside a use_tree_list, (e.g. `use some::path::{::*}`)
- // but still parses and errors later: ('crate root in paths can only be used in start position')
- // FIXME: Add this error (if not out of scope)
- // In Rust 2018, it is always invalid (see above)
p.bump(T![::]);
p.bump(T![*]);
}
- // Open a use tree list
- // Handles cases such as `use {some::path};` or `{inner::path}` in
- // `use some::path::{{inner::path}, other::path}`
-
- // test use_tree_list
- // use {crate::path::from::root, or::path::from::crate_name}; // Rust 2018 (with a crate named `or`)
- // use {path::from::root}; // Rust 2015
- // use ::{some::arbitrary::path}; // Rust 2015
- // use ::{{{root::export}}}; // Nonsensical but perfectly legal nesting
- T!['{'] => {
- use_tree_list(p);
- }
+ T!['{'] => use_tree_list(p),
T![:] if p.at(T![::]) && p.nth(2) == T!['{'] => {
p.bump(T![::]);
use_tree_list(p);
}
- // Parse a 'standard' path.
- // Also handles aliases (e.g. `use something as something_else`)
- // test use_path
- // use ::crate_name; // Rust 2018 - All flavours
- // use crate_name; // Rust 2018 - Anchored paths
- // use item_in_scope_or_crate_name; // Rust 2018 - Uniform Paths
+ // test use_tree_path
+ // use ::std;
+ // use std::collections;
//
- // use self::module::Item;
- // use crate::Item;
- // use self::some::Struct;
- // use crate_name::some_item;
+ // use self::m;
+ // use super::m;
+ // use crate::m;
_ if paths::is_use_path_start(p) => {
paths::use_path(p);
match p.current() {
- T![as] => {
- // test use_alias
- // use some::path as some_name;
- // use some::{
- // other::path as some_other_name,
- // different::path as different_name,
- // yet::another::path,
- // running::out::of::synonyms::for_::different::*
- // };
- // use Trait as _;
- opt_rename(p);
- }
+ // test use_tree_alias
+ // use std as stdlib;
+ // use Trait as _;
+ T![as] => opt_rename(p),
T![:] if p.at(T![::]) => {
p.bump(T![::]);
match p.current() {
- T![*] => {
- p.bump(T![*]);
- }
- // test use_tree_list_after_path
- // use crate::{Item};
- // use self::{Item};
+ // test use_tree_path_star
+ // use std::*;
+ T![*] => p.bump(T![*]),
+ // test use_tree_path_use_tree
+ // use std::{collections};
T!['{'] => use_tree_list(p),
- _ => {
- // is this unreachable?
- p.error("expected `{` or `*`");
- }
+ _ => p.error("expected `{` or `*`"),
}
}
_ => (),
m.complete(p, USE_TREE);
}
+// test use_tree_list
+// use {a, b, c};
pub(crate) fn use_tree_list(p: &mut Parser) {
assert!(p.at(T!['{']));
let m = p.start();
path(p, Mode::Expr)
}
-pub(crate) fn type_path_for_qualifier(p: &mut Parser, qual: CompletedMarker) {
+pub(crate) fn type_path_for_qualifier(p: &mut Parser, qual: CompletedMarker) -> CompletedMarker {
path_for_qualifier(p, Mode::Type, qual)
}
let path = p.start();
path_segment(p, mode, true);
let qual = path.complete(p, PATH);
- path_for_qualifier(p, mode, qual)
+ path_for_qualifier(p, mode, qual);
}
-fn path_for_qualifier(p: &mut Parser, mode: Mode, mut qual: CompletedMarker) {
+fn path_for_qualifier(p: &mut Parser, mode: Mode, mut qual: CompletedMarker) -> CompletedMarker {
loop {
let use_tree = matches!(p.nth(2), T![*] | T!['{']);
if p.at(T![::]) && !use_tree {
let path = path.complete(p, PATH);
qual = path;
} else {
- break;
+ return qual;
}
}
}
params::param_list_fn_trait(p);
opt_ret_type(p);
} else {
- type_args::opt_generic_arg_list(p, false)
+ generic_args::opt_generic_arg_list(p, false)
}
}
- Mode::Expr => type_args::opt_generic_arg_list(p, true),
+ Mode::Expr => generic_args::opt_generic_arg_list(p, true),
}
}
pattern_r(p, PAT_RECOVERY_SET);
}
-/// Parses a pattern list separated by pipes `|`
+/// Parses a pattern list separated by pipes `|`.
pub(super) fn pattern_top(p: &mut Parser) {
pattern_top_r(p, PAT_RECOVERY_SET)
}
}
/// Parses a pattern list separated by pipes `|`
-/// using the given `recovery_set`
+/// using the given `recovery_set`.
pub(super) fn pattern_top_r(p: &mut Parser, recovery_set: TokenSet) {
p.eat(T![|]);
pattern_r(p, recovery_set);
}
/// Parses a pattern list separated by pipes `|`, with no leading `|`,using the
-/// given `recovery_set`
+/// given `recovery_set`.
+
// test or_pattern
// fn main() {
// match () {
+++ /dev/null
-use super::*;
-
-pub(super) fn opt_generic_arg_list(p: &mut Parser, colon_colon_required: bool) {
- let m;
- if p.at(T![::]) && p.nth(2) == T![<] {
- m = p.start();
- p.bump(T![::]);
- p.bump(T![<]);
- } else if !colon_colon_required && p.at(T![<]) && p.nth(1) != T![=] {
- m = p.start();
- p.bump(T![<]);
- } else {
- return;
- }
-
- while !p.at(EOF) && !p.at(T![>]) {
- generic_arg(p);
- if !p.at(T![>]) && !p.expect(T![,]) {
- break;
- }
- }
- p.expect(T![>]);
- m.complete(p, GENERIC_ARG_LIST);
-}
-
-pub(super) fn const_arg(p: &mut Parser) {
- let m = p.start();
- // FIXME: duplicates the code below
- match p.current() {
- T!['{'] => {
- expressions::block_expr(p);
- m.complete(p, CONST_ARG);
- }
- k if k.is_literal() => {
- expressions::literal(p);
- m.complete(p, CONST_ARG);
- }
- T![true] | T![false] => {
- expressions::literal(p);
- m.complete(p, CONST_ARG);
- }
- T![-] => {
- let lm = p.start();
- p.bump(T![-]);
- expressions::literal(p);
- lm.complete(p, PREFIX_EXPR);
- m.complete(p, CONST_ARG);
- }
- _ => {
- let lm = p.start();
- paths::use_path(p);
- lm.complete(p, PATH_EXPR);
- m.complete(p, CONST_ARG);
- }
- }
-}
-
-// test type_arg
-// type A = B<'static, i32, 1, { 2 }, Item=u64, true, false>;
-fn generic_arg(p: &mut Parser) {
- let m = p.start();
- match p.current() {
- LIFETIME_IDENT => {
- lifetime(p);
- m.complete(p, LIFETIME_ARG);
- }
- // test associated_type_bounds
- // fn print_all<T: Iterator<Item, Item::Item, Item::<true>, Item: Display, Item<'a> = Item>>(printables: T) {}
- IDENT if [T![<], T![=], T![:]].contains(&p.nth(1)) => {
- let path_ty = p.start();
- let path = p.start();
- let path_seg = p.start();
- name_ref(p);
- opt_generic_arg_list(p, false);
- match p.current() {
- // NameRef<...> =
- T![=] => {
- p.bump_any();
- types::type_(p);
-
- path_seg.abandon(p);
- path.abandon(p);
- path_ty.abandon(p);
- m.complete(p, ASSOC_TYPE_ARG);
- }
- T![:] if p.nth(1) == T![:] => {
- // NameRef::, this is a path type
- path_seg.complete(p, PATH_SEGMENT);
- let qual = path.complete(p, PATH);
- opt_generic_arg_list(p, false);
- paths::type_path_for_qualifier(p, qual);
- path_ty.complete(p, PATH_TYPE);
- m.complete(p, TYPE_ARG);
- }
- // NameRef<...>:
- T![:] => {
- type_params::bounds(p);
-
- path_seg.abandon(p);
- path.abandon(p);
- path_ty.abandon(p);
- m.complete(p, ASSOC_TYPE_ARG);
- }
- // NameRef, this is a single segment path type
- _ => {
- path_seg.complete(p, PATH_SEGMENT);
- path.complete(p, PATH);
- path_ty.complete(p, PATH_TYPE);
- m.complete(p, TYPE_ARG);
- }
- }
- }
- T!['{'] => {
- expressions::block_expr(p);
- m.complete(p, CONST_ARG);
- }
- k if k.is_literal() => {
- expressions::literal(p);
- m.complete(p, CONST_ARG);
- }
- T![true] | T![false] => {
- expressions::literal(p);
- m.complete(p, CONST_ARG);
- }
- // test const_generic_negated_literal
- // fn f() { S::<-1> }
- T![-] => {
- let lm = p.start();
- p.bump(T![-]);
- expressions::literal(p);
- lm.complete(p, PREFIX_EXPR);
- m.complete(p, CONST_ARG);
- }
- _ => {
- types::type_(p);
- m.complete(p, TYPE_ARG);
- }
- }
-}
+++ /dev/null
-use super::*;
-
-pub(super) fn opt_generic_param_list(p: &mut Parser) {
- if !p.at(T![<]) {
- return;
- }
- generic_param_list(p);
-}
-
-fn generic_param_list(p: &mut Parser) {
- assert!(p.at(T![<]));
- let m = p.start();
- p.bump(T![<]);
-
- while !p.at(EOF) && !p.at(T![>]) {
- let m = p.start();
-
- // test generic_lifetime_type_attribute
- // fn foo<#[derive(Lifetime)] 'a, #[derive(Type)] T>(_: &'a T) {
- // }
- attributes::outer_attrs(p);
-
- match p.current() {
- LIFETIME_IDENT => lifetime_param(p, m),
- IDENT => type_param(p, m),
- T![const] => const_param(p, m),
- _ => {
- m.abandon(p);
- p.err_and_bump("expected type parameter")
- }
- }
- if !p.at(T![>]) && !p.expect(T![,]) {
- break;
- }
- }
- p.expect(T![>]);
- m.complete(p, GENERIC_PARAM_LIST);
-}
-
-fn lifetime_param(p: &mut Parser, m: Marker) {
- assert!(p.at(LIFETIME_IDENT));
- lifetime(p);
- if p.at(T![:]) {
- lifetime_bounds(p);
- }
- m.complete(p, LIFETIME_PARAM);
-}
-
-fn type_param(p: &mut Parser, m: Marker) {
- assert!(p.at(IDENT));
- name(p);
- if p.at(T![:]) {
- bounds(p);
- }
- // test type_param_default
- // struct S<T = i32>;
- if p.at(T![=]) {
- p.bump(T![=]);
- types::type_(p)
- }
- m.complete(p, TYPE_PARAM);
-}
-
-// test const_param
-// struct S<const N: u32>;
-fn const_param(p: &mut Parser, m: Marker) {
- assert!(p.at(T![const]));
- p.bump(T![const]);
- name(p);
- if p.at(T![:]) {
- types::ascription(p);
- } else {
- p.error("missing type for const parameter");
- }
-
- // test const_param_defaults
- // struct A<const N: i32 = -1>;
- // struct B<const N: i32 = {}>;
- // struct C<const N: i32 = some::CONST>;
- if p.at(T![=]) {
- p.bump(T![=]);
- type_args::const_arg(p);
- }
-
- m.complete(p, CONST_PARAM);
-}
-
-// test type_param_bounds
-// struct S<T: 'a + ?Sized + (Copy)>;
-pub(super) fn bounds(p: &mut Parser) {
- assert!(p.at(T![:]));
- p.bump(T![:]);
- bounds_without_colon(p);
-}
-
-fn lifetime_bounds(p: &mut Parser) {
- assert!(p.at(T![:]));
- p.bump(T![:]);
- while p.at(LIFETIME_IDENT) {
- lifetime(p);
- if !p.eat(T![+]) {
- break;
- }
- }
-}
-
-pub(super) fn bounds_without_colon_m(p: &mut Parser, marker: Marker) -> CompletedMarker {
- while type_bound(p) {
- if !p.eat(T![+]) {
- break;
- }
- }
-
- marker.complete(p, TYPE_BOUND_LIST)
-}
-
-pub(super) fn bounds_without_colon(p: &mut Parser) {
- let m = p.start();
- bounds_without_colon_m(p, m);
-}
-
-fn type_bound(p: &mut Parser) -> bool {
- let m = p.start();
- let has_paren = p.eat(T!['(']);
- p.eat(T![?]);
- match p.current() {
- LIFETIME_IDENT => lifetime(p),
- T![for] => types::for_type(p, false),
- _ if paths::is_use_path_start(p) => types::path_type_(p, false),
- _ => {
- m.abandon(p);
- return false;
- }
- }
- if has_paren {
- p.expect(T![')']);
- }
- m.complete(p, TYPE_BOUND);
-
- true
-}
-
-// test where_clause
-// fn foo()
-// where
-// 'a: 'b + 'c,
-// T: Clone + Copy + 'static,
-// Iterator::Item: 'a,
-// <T as Iterator>::Item: 'a
-// {}
-pub(super) fn opt_where_clause(p: &mut Parser) {
- if !p.at(T![where]) {
- return;
- }
- let m = p.start();
- p.bump(T![where]);
-
- while is_where_predicate(p) {
- where_predicate(p);
-
- let comma = p.eat(T![,]);
-
- if is_where_clause_end(p) {
- break;
- }
-
- if !comma {
- p.error("expected comma");
- }
- }
-
- m.complete(p, WHERE_CLAUSE);
-}
-
-fn is_where_predicate(p: &mut Parser) -> bool {
- match p.current() {
- LIFETIME_IDENT => true,
- T![impl] => false,
- token => types::TYPE_FIRST.contains(token),
- }
-}
-
-fn is_where_clause_end(p: &mut Parser) -> bool {
- matches!(p.current(), T!['{'] | T![;] | T![=])
-}
-
-fn where_predicate(p: &mut Parser) {
- let m = p.start();
- match p.current() {
- LIFETIME_IDENT => {
- lifetime(p);
- if p.at(T![:]) {
- bounds(p);
- } else {
- p.error("expected colon");
- }
- }
- T![impl] => {
- p.error("expected lifetime or type");
- }
- _ => {
- // test where_pred_for
- // 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![:]) {
- bounds(p);
- } else {
- p.error("expected colon");
- }
- }
- }
- m.complete(p, WHERE_PRED);
-}
assert!(p.at(T![for]));
p.bump(T![for]);
if p.at(T![<]) {
- type_params::opt_generic_param_list(p);
+ generic_params::opt_generic_param_list(p);
} else {
p.error("expected `<`");
}
assert!(p.at(T![impl]));
let m = p.start();
p.bump(T![impl]);
- type_params::bounds_without_colon(p);
+ generic_params::bounds_without_colon(p);
m.complete(p, IMPL_TRAIT_TYPE);
}
assert!(p.at(T![dyn]));
let m = p.start();
p.bump(T![dyn]);
- type_params::bounds_without_colon(p);
+ generic_params::bounds_without_colon(p);
m.complete(p, DYN_TRAIT_TYPE);
}
p.eat(T![+]);
// Parse rest of the bounds into the TYPE_BOUND_LIST
- let m = type_params::bounds_without_colon_m(p, m);
+ let m = generic_params::bounds_without_colon_m(p, m);
// Finally precede everything with DYN_TRAIT_TYPE
m.precede(p).complete(p, DYN_TRAIT_TYPE);
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = ["unbounded_depth"] }
tracing = "0.1"
-crossbeam-channel = "0.5.0"
-jod-thread = "0.1.1"
memmap2 = "0.3.0"
snap = "1.0"
.into_iter()
.map(|(name, kind)| ProcMacro {
process: self.process.clone(),
- name: name.into(),
+ name,
kind,
dylib_path: dylib.path.clone(),
})
fn enqueue(&mut self, subtree: &'a tt::Subtree) -> u32 {
let idx = self.subtree.len();
- let delimiter_id = subtree.delimiter.map(|it| it.id).unwrap_or(TokenId::unspecified());
+ let delimiter_id = subtree.delimiter.map(|it| it.id).unwrap_or_else(TokenId::unspecified);
let delimiter_kind = subtree.delimiter.map(|it| it.kind);
self.subtree.push(SubtreeRepr { id: delimiter_id, kind: delimiter_kind, tt: [!0, !0] });
self.work.push_back((idx, subtree));
})
.collect(),
};
- res[i] = Some(s.into())
+ res[i] = Some(s)
}
res[0].take().unwrap()
#[derive(Debug)]
pub(crate) struct ProcMacroProcessSrv {
- process: Process,
+ _process: Process,
stdin: ChildStdin,
stdout: BufReader<ChildStdout>,
}
let mut process = Process::run(process_path, args)?;
let (stdin, stdout) = process.stdio().expect("couldn't access child stdio");
- let srv = ProcMacroProcessSrv { process, stdin, stdout };
+ let srv = ProcMacroProcessSrv { _process: process, stdin, stdout };
Ok(srv)
}
proc_macro_api = { path = "../proc_macro_api", version = "0.0.0" }
[dev-dependencies]
-test_utils = { path = "../test_utils" }
-toolchain = { path = "../toolchain" }
-cargo_metadata = "0.14"
expect-test = "1.1.0"
# used as proc macro test targets
UnsetTestCrates::Only(unset_test_crates) => CfgOverrides::Selective(
unset_test_crates
.iter()
- .map(|name| name.clone())
+ .cloned()
.zip(iter::repeat_with(|| {
cfg::CfgDiff::new(Vec::new(), vec![cfg::CfgAtom::Flag("test".into())])
.unwrap()
pub manifest: ManifestPath,
/// Targets provided by the crate (lib, bin, example, test, ...)
pub targets: Vec<Target>,
- /// Is this package a member of the current workspace
- pub is_member: bool,
+ /// Does this package come from the local filesystem (and is editable)?
+ pub is_local: bool,
/// List of packages this package depends on
pub dependencies: Vec<PackageDependency>,
/// Rust edition for this package
let mut packages = Arena::default();
let mut targets = Arena::default();
- let ws_members = &meta.workspace_members;
-
meta.packages.sort_by(|a, b| a.id.cmp(&b.id));
for meta_pkg in &meta.packages {
let cargo_metadata::Package {
id, edition, name, manifest_path, version, metadata, ..
} = meta_pkg;
let meta = from_value::<PackageMetadata>(metadata.clone()).unwrap_or_default();
- let is_member = ws_members.contains(id);
let edition = edition.parse::<Edition>().unwrap_or_else(|err| {
tracing::error!("Failed to parse edition {}", err);
Edition::CURRENT
});
+ // We treat packages without source as "local" packages. That includes all members of
+ // the current workspace, as well as any path dependency outside the workspace.
+ let is_local = meta_pkg.source.is_none();
let pkg = packages.alloc(PackageData {
id: id.repr.clone(),
version: version.clone(),
manifest: AbsPathBuf::assert(PathBuf::from(&manifest_path)).try_into().unwrap(),
targets: Vec::new(),
- is_member,
+ is_local,
edition,
dependencies: Vec::new(),
features: meta_pkg.features.clone().into_iter().collect(),
fixup_paths(&mut json);
return serde_json::from_value(json).unwrap();
- fn fixup_paths(val: &mut serde_json::Value) -> () {
+ fn fixup_paths(val: &mut serde_json::Value) {
match val {
serde_json::Value::String(s) => replace_root(s, true),
serde_json::Value::Array(vals) => vals.iter_mut().for_each(fixup_paths),
serde_json::Value::Object(kvals) => kvals.values_mut().for_each(fixup_paths),
serde_json::Value::Null | serde_json::Value::Bool(_) | serde_json::Value::Number(_) => {
- ()
}
}
}
/// the current workspace.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct PackageRoot {
- /// Is a member of the current workspace
- pub is_member: bool,
+ /// Is from the local filesystem and may be edited
+ pub is_local: bool,
pub include: Vec<AbsPathBuf>,
pub exclude: Vec<AbsPathBuf>,
}
Some(rustc_dir) => Some({
let meta = CargoWorkspace::fetch_metadata(&rustc_dir, config, progress)
.with_context(|| {
- format!("Failed to read Cargo metadata for Rust sources")
+ "Failed to read Cargo metadata for Rust sources".to_string()
})?;
CargoWorkspace::new(meta)
}),
ProjectWorkspace::Json { project, sysroot, rustc_cfg: _ } => project
.crates()
.map(|(_, krate)| PackageRoot {
- is_member: krate.is_workspace_member,
+ is_local: krate.is_workspace_member,
include: krate.include.clone(),
exclude: krate.exclude.clone(),
})
.into_iter()
.chain(sysroot.as_ref().into_iter().flat_map(|sysroot| {
sysroot.crates().map(move |krate| PackageRoot {
- is_member: false,
+ is_local: false,
include: vec![sysroot[krate].root.parent().to_path_buf()],
exclude: Vec::new(),
})
cargo
.packages()
.map(|pkg| {
- let is_member = cargo[pkg].is_member;
+ let is_local = cargo[pkg].is_local;
let pkg_root = cargo[pkg].manifest.parent().to_path_buf();
let mut include = vec![pkg_root.clone()];
include.extend(extra_targets);
let mut exclude = vec![pkg_root.join(".git")];
- if is_member {
+ if is_local {
exclude.push(pkg_root.join("target"));
} else {
exclude.push(pkg_root.join("tests"));
exclude.push(pkg_root.join("examples"));
exclude.push(pkg_root.join("benches"));
}
- PackageRoot { is_member, include, exclude }
+ PackageRoot { is_local, include, exclude }
})
- .chain(sysroot.into_iter().map(|sysroot| PackageRoot {
- is_member: false,
+ .chain(sysroot.iter().map(|sysroot| PackageRoot {
+ is_local: false,
include: vec![sysroot.root().to_path_buf()],
exclude: Vec::new(),
}))
- .chain(rustc.into_iter().flat_map(|rustc| {
+ .chain(rustc.iter().flat_map(|rustc| {
rustc.packages().map(move |krate| PackageRoot {
- is_member: false,
+ is_local: false,
include: vec![rustc[krate].manifest.parent().to_path_buf()],
exclude: Vec::new(),
})
.collect()
}
ProjectWorkspace::DetachedFiles { files, sysroot, .. } => files
- .into_iter()
+ .iter()
.map(|detached_file| PackageRoot {
- is_member: true,
+ is_local: true,
include: vec![detached_file.clone()],
exclude: Vec::new(),
})
.chain(sysroot.crates().map(|krate| PackageRoot {
- is_member: false,
+ is_local: false,
include: vec![sysroot[krate].root.parent().to_path_buf()],
exclude: Vec::new(),
}))
&mut crate_graph,
&cargo[pkg],
build_scripts.outputs.get(pkg),
- &cfg_options,
+ cfg_options,
load_proc_macro,
file_id,
&cargo[tgt].name,
.map(|feat| CfgFlag::KeyValue { key: "feature".into(), value: feat.0.into() }),
);
- let crate_id = crate_graph.add_crate_root(
+ crate_graph.add_crate_root(
file_id,
edition,
Some(display_name),
potential_cfg_options,
env,
proc_macro,
- );
-
- crate_id
+ )
}
fn sysroot_to_crate_graph(
// CARGO_BIN_NAME, CARGO_BIN_EXE_<name>
let manifest_dir = package.manifest.parent();
- env.set("CARGO_MANIFEST_DIR".into(), manifest_dir.as_os_str().to_string_lossy().into_owned());
+ env.set("CARGO_MANIFEST_DIR", manifest_dir.as_os_str().to_string_lossy().into_owned());
// Not always right, but works for common cases.
- env.set("CARGO".into(), "cargo".into());
+ env.set("CARGO", "cargo".into());
- env.set("CARGO_PKG_VERSION".into(), package.version.to_string());
- env.set("CARGO_PKG_VERSION_MAJOR".into(), package.version.major.to_string());
- env.set("CARGO_PKG_VERSION_MINOR".into(), package.version.minor.to_string());
- env.set("CARGO_PKG_VERSION_PATCH".into(), package.version.patch.to_string());
- env.set("CARGO_PKG_VERSION_PRE".into(), package.version.pre.to_string());
+ env.set("CARGO_PKG_VERSION", package.version.to_string());
+ env.set("CARGO_PKG_VERSION_MAJOR", package.version.major.to_string());
+ env.set("CARGO_PKG_VERSION_MINOR", package.version.minor.to_string());
+ env.set("CARGO_PKG_VERSION_PATCH", package.version.patch.to_string());
+ env.set("CARGO_PKG_VERSION_PRE", package.version.pre.to_string());
- env.set("CARGO_PKG_AUTHORS".into(), String::new());
+ env.set("CARGO_PKG_AUTHORS", String::new());
- env.set("CARGO_PKG_NAME".into(), package.name.clone());
+ env.set("CARGO_PKG_NAME", package.name.clone());
// FIXME: This isn't really correct (a package can have many crates with different names), but
// it's better than leaving the variable unset.
- env.set("CARGO_CRATE_NAME".into(), CrateName::normalize_dashes(&package.name).to_string());
- env.set("CARGO_PKG_DESCRIPTION".into(), String::new());
- env.set("CARGO_PKG_HOMEPAGE".into(), String::new());
- env.set("CARGO_PKG_REPOSITORY".into(), String::new());
- env.set("CARGO_PKG_LICENSE".into(), String::new());
+ env.set("CARGO_CRATE_NAME", CrateName::normalize_dashes(&package.name).to_string());
+ env.set("CARGO_PKG_DESCRIPTION", String::new());
+ env.set("CARGO_PKG_HOMEPAGE", String::new());
+ env.set("CARGO_PKG_REPOSITORY", String::new());
+ env.set("CARGO_PKG_LICENSE", String::new());
- env.set("CARGO_PKG_LICENSE_FILE".into(), String::new());
+ env.set("CARGO_PKG_LICENSE_FILE", String::new());
}
profile = { path = "../profile", version = "0.0.0" }
project_model = { path = "../project_model", version = "0.0.0" }
syntax = { path = "../syntax", version = "0.0.0" }
-text_edit = { path = "../text_edit", version = "0.0.0" }
vfs = { path = "../vfs", version = "0.0.0" }
vfs-notify = { path = "../vfs-notify", version = "0.0.0" }
cfg = { path = "../cfg", version = "0.0.0" }
test_utils = { path = "../test_utils" }
sourcegen = { path = "../sourcegen" }
mbe = { path = "../mbe" }
-tt = { path = "../tt" }
[features]
jemalloc = ["jemallocator", "profile/jemalloc"]
}
fn setup_logging(log_file: Option<&Path>) -> Result<()> {
- env::set_var("RUST_BACKTRACE", "short");
+ if env::var("RUST_BACKTRACE").is_err() {
+ env::set_var("RUST_BACKTRACE", "short");
+ }
let log_file = match log_file {
Some(path) => {
assist_importEnforceGranularity: bool = "false",
/// The path structure for newly inserted paths to use.
assist_importPrefix: ImportPrefixDef = "\"plain\"",
- /// Group inserted imports by the [following order](https://rust-analyzer.github.io/manual.html#auto-import). Groups are separated by newlines.
+ /// Group inserted imports by the https://rust-analyzer.github.io/manual.html#auto-import[following order]. Groups are separated by newlines.
assist_importGroup: bool = "true",
/// Whether to allow import insertion to merge new imports into single path glob imports like `use std::fmt::*;`.
assist_allowMergingIntoGlobImports: bool = "true",
main_loop::Task,
mem_docs::MemDocs,
op_queue::OpQueue,
- reload::SourceRootConfig,
+ reload::{self, SourceRootConfig},
thread_pool::TaskPool,
to_proto::url_from_abs_path,
Result,
pub(crate) fn process_changes(&mut self) -> bool {
let _p = profile::span("GlobalState::process_changes");
let mut fs_changes = Vec::new();
- let mut has_fs_changes = false;
+ // A file was added or deleted
+ let mut has_structure_changes = false;
let change = {
let mut change = Change::new();
}
for file in changed_files {
- if file.is_created_or_deleted() {
- if let Some(path) = vfs.file_path(file.file_id).as_path() {
- fs_changes.push((path.to_path_buf(), file.change_kind));
- has_fs_changes = true;
+ if let Some(path) = vfs.file_path(file.file_id).as_path() {
+ let path = path.to_path_buf();
+ if reload::should_refresh_for_change(&path, file.change_kind) {
+ self.fetch_workspaces_queue.request_op();
+ }
+ fs_changes.push((path, file.change_kind));
+ if file.is_created_or_deleted() {
+ has_structure_changes = true;
}
}
};
change.change_file(file.file_id, text);
}
- if has_fs_changes {
+ if has_structure_changes {
let roots = self.source_root_config.partition(vfs);
change.set_roots(roots);
}
};
self.analysis_host.apply_change(change);
- self.maybe_refresh(&fs_changes);
true
}
use project_model::TargetKind;
use serde_json::json;
use stdx::{format_to, never};
-use syntax::{algo, ast, AstNode, TextRange, TextSize};
+use syntax::{algo, ast, AstNode, TextRange, TextSize, T};
use crate::{
cargo_target_spec::CargoTargetSpec,
let completion_triggered_after_single_colon = {
let mut res = false;
if let Some(ctx) = params.context {
- if ctx.trigger_character.unwrap_or_default() == ":" {
+ if ctx.trigger_character.as_deref() == Some(":") {
let source_file = snap.analysis.parse(position.file_id)?;
- let syntax = source_file.syntax();
- let text = syntax.text();
- if let Some(next_char) = text.char_at(position.offset) {
- let diff = TextSize::of(next_char) + TextSize::of(':');
- let prev_char = position.offset - diff;
- if text.char_at(prev_char) != Some(':') {
- res = true;
- }
+ let left_token =
+ source_file.syntax().token_at_offset(position.offset).left_biased();
+ match left_token {
+ Some(left_token) => res = left_token.kind() == T![:],
+ None => res = true,
}
}
}
handlers, lsp_ext,
lsp_utils::{apply_document_changes, is_cancelled, notification_is, Progress},
mem_docs::DocumentData,
- reload::{BuildDataProgress, ProjectWorkspaceProgress},
+ reload::{self, BuildDataProgress, ProjectWorkspaceProgress},
Result,
};
flycheck.update();
}
if let Ok(abs_path) = from_proto::abs_path(¶ms.text_document.uri) {
- this.maybe_refresh(&[(abs_path, ChangeKind::Modify)]);
+ if reload::should_refresh_for_change(&abs_path, ChangeKind::Modify) {
+ this.fetch_workspaces_queue.request_op();
+ }
}
Ok(())
})?
.raw_database_mut()
.set_enable_proc_attr_macros(self.config.expand_proc_attr_macros());
}
- pub(crate) fn maybe_refresh(&mut self, changes: &[(AbsPathBuf, ChangeKind)]) {
- if !changes.iter().any(|(path, kind)| is_interesting(path, *kind)) {
- return;
- }
- tracing::info!(
- "Requesting workspace reload because of the following changes: {}",
- itertools::join(
- changes
- .iter()
- .filter(|(path, kind)| is_interesting(path, *kind))
- .map(|(path, kind)| format!("{}: {:?}", path.display(), kind)),
- ", "
- )
- );
- self.fetch_workspaces_queue.request_op();
-
- fn is_interesting(path: &AbsPath, change_kind: ChangeKind) -> bool {
- const IMPLICIT_TARGET_FILES: &[&str] = &["build.rs", "src/main.rs", "src/lib.rs"];
- const IMPLICIT_TARGET_DIRS: &[&str] = &["src/bin", "examples", "tests", "benches"];
- let file_name = path.file_name().unwrap_or_default();
-
- if file_name == "Cargo.toml" || file_name == "Cargo.lock" {
- return true;
- }
- if change_kind == ChangeKind::Modify {
- return false;
- }
- if path.extension().unwrap_or_default() != "rs" {
- return false;
- }
- if IMPLICIT_TARGET_FILES.iter().any(|it| path.as_ref().ends_with(it)) {
- return true;
- }
- let parent = match path.parent() {
- Some(it) => it,
- None => return false,
- };
- if IMPLICIT_TARGET_DIRS.iter().any(|it| parent.as_ref().ends_with(it)) {
- return true;
- }
- if file_name == "main.rs" {
- let grand_parent = match parent.parent() {
- Some(it) => it,
- None => return false,
- };
- if IMPLICIT_TARGET_DIRS.iter().any(|it| grand_parent.as_ref().ends_with(it)) {
- return true;
- }
- }
- false
- }
- }
pub(crate) fn current_status(&self) -> lsp_ext::ServerStatusParams {
let mut status = lsp_ext::ServerStatusParams {
.workspaces
.iter()
.flat_map(|ws| ws.to_roots())
- .filter(|it| it.is_member)
+ .filter(|it| it.is_local)
.flat_map(|root| {
root.include.into_iter().flat_map(|it| {
[
vfs::loader::Entry::Directories(dirs)
};
- if root.is_member {
+ if root.is_local {
res.watch.push(res.load.len());
}
res.load.push(entry);
- if root.is_member {
+ if root.is_local {
local_filesets.push(fsc.len());
}
fsc.add_file_set(file_set_roots)
}
}
}
+
+pub(crate) fn should_refresh_for_change(path: &AbsPath, change_kind: ChangeKind) -> bool {
+ const IMPLICIT_TARGET_FILES: &[&str] = &["build.rs", "src/main.rs", "src/lib.rs"];
+ const IMPLICIT_TARGET_DIRS: &[&str] = &["src/bin", "examples", "tests", "benches"];
+ let file_name = path.file_name().unwrap_or_default();
+
+ if file_name == "Cargo.toml" || file_name == "Cargo.lock" {
+ return true;
+ }
+ if change_kind == ChangeKind::Modify {
+ return false;
+ }
+ if path.extension().unwrap_or_default() != "rs" {
+ return false;
+ }
+ if IMPLICIT_TARGET_FILES.iter().any(|it| path.as_ref().ends_with(it)) {
+ return true;
+ }
+ let parent = match path.parent() {
+ Some(it) => it,
+ None => return false,
+ };
+ if IMPLICIT_TARGET_DIRS.iter().any(|it| parent.as_ref().ends_with(it)) {
+ return true;
+ }
+ if file_name == "main.rs" {
+ let grand_parent = match parent.parent() {
+ Some(it) => it,
+ None => return false,
+ };
+ if IMPLICIT_TARGET_DIRS.iter().any(|it| grand_parent.as_ref().ends_with(it)) {
+ return true;
+ }
+ }
+ false
+}
profile::init_from(crate::PROFILE);
});
- let (mini_core, fixtures) = Fixture::parse(self.fixture);
+ let (mini_core, proc_macros, fixtures) = Fixture::parse(self.fixture);
+ assert!(proc_macros.is_empty());
assert!(mini_core.is_none());
for entry in fixtures {
let path = tmp_dir.path().join(&entry.path['/'.len_utf8()..]);
// The documentation in string literals may contain anything for its own purposes
"ide_db/src/helpers/generated_lints.rs",
// The tests test clippy lint hovers
- "ide/src/hover.rs",
+ "ide/src/hover/tests.rs",
];
if ignore.iter().any(|p| path.ends_with(p)) {
return;
// Some of our assists generate `todo!()`.
"handlers/add_turbo_fish.rs",
"handlers/generate_function.rs",
- "handlers/fill_match_arms.rs",
+ "handlers/add_missing_match_arms.rs",
"handlers/replace_derive_with_manual_impl.rs",
// To support generating `todo!()` in assists, we have `expr_todo()` in
// `ast::make`.
"handlers/remove_dbg.rs",
// We have .dbg postfix
"ide_completion/src/completions/postfix.rs",
+ "ide_completion/src/tests/proc_macros.rs",
// The documentation in string literals may contain anything for its own purposes
"ide_completion/src/lib.rs",
"ide_db/src/helpers/generated_lints.rs",
res
}
+#[derive(Clone)]
pub struct CommentBlock {
pub id: String,
pub line: usize,
pub contents: Vec<String>,
+ is_doc: bool,
}
impl CommentBlock {
assert!(tag.starts_with(char::is_uppercase));
let tag = format!("{}:", tag);
- let mut res = Vec::new();
- for (line, mut block) in do_extract_comment_blocks(text, true) {
- let first = block.remove(0);
- if let Some(id) = first.strip_prefix(&tag) {
- let id = id.trim().to_string();
- let block = CommentBlock { id, line, contents: block };
- res.push(block);
- }
- }
- res
+ // Would be nice if we had `.retain_mut` here!
+ CommentBlock::extract_untagged(text)
+ .into_iter()
+ .filter_map(|mut block| {
+ let first = block.contents.remove(0);
+ first.strip_prefix(&tag).map(|id| {
+ if block.is_doc {
+ panic!(
+ "Use plain (non-doc) comments with tags like {}:\n {}",
+ tag, first
+ )
+ }
+
+ block.id = id.trim().to_string();
+ block
+ })
+ })
+ .collect()
}
pub fn extract_untagged(text: &str) -> Vec<CommentBlock> {
let mut res = Vec::new();
- for (line, block) in do_extract_comment_blocks(text, false) {
- let id = String::new();
- let block = CommentBlock { id, line, contents: block };
- res.push(block);
- }
- res
- }
-}
-
-fn do_extract_comment_blocks(
- text: &str,
- allow_blocks_with_empty_lines: bool,
-) -> Vec<(usize, Vec<String>)> {
- let mut res = Vec::new();
- let prefix = "// ";
- let lines = text.lines().map(str::trim_start);
-
- let mut block = (0, vec![]);
- for (line_num, line) in lines.enumerate() {
- if line == "//" && allow_blocks_with_empty_lines {
- block.1.push(String::new());
- continue;
- }
-
- let is_comment = line.starts_with(prefix);
- if is_comment {
- block.1.push(line[prefix.len()..].to_string());
- } else {
- if !block.1.is_empty() {
- res.push(mem::take(&mut block));
+ let lines = text.lines().map(str::trim_start);
+
+ let dummy_block =
+ CommentBlock { id: String::new(), line: 0, contents: Vec::new(), is_doc: false };
+ let mut block = dummy_block.clone();
+ for (line_num, line) in lines.enumerate() {
+ match line.strip_prefix("//") {
+ Some(mut contents) => {
+ if let Some('/' | '!') = contents.chars().next() {
+ contents = &contents[1..];
+ block.is_doc = true;
+ }
+ if let Some(' ') = contents.chars().next() {
+ contents = &contents[1..];
+ }
+ block.contents.push(contents.to_string());
+ }
+ None => {
+ if !block.contents.is_empty() {
+ let block = mem::replace(&mut block, dummy_block.clone());
+ res.push(block);
+ }
+ block.line = line_num + 2;
+ }
}
- block.0 = line_num + 2;
}
+ if !block.contents.is_empty() {
+ res.push(block)
+ }
+ res
}
- if !block.1.is_empty() {
- res.push(block)
- }
- res
}
#[derive(Debug)]
}
/// Prints backtrace to stderr, useful for debugging.
-#[cfg(feature = "backtrace")]
-pub fn print_backtrace() {
- let bt = backtrace::Backtrace::new();
- eprintln!("{:?}", bt);
-}
-#[cfg(not(feature = "backtrace"))]
pub fn print_backtrace() {
+ #[cfg(feature = "backtrace")]
+ eprintln!("{:?}", backtrace::Backtrace::new());
+
+ #[cfg(not(feature = "backtrace"))]
eprintln!(
r#"Enable the backtrace feature.
Uncomment `default = [ "backtrace" ]` in `crates/stdx/Cargo.toml`.
[dependencies]
cov-mark = "2.0.0-pre.1"
itertools = "0.10.0"
-rowan = "0.13.0"
+rowan = "0.14.0"
rustc_lexer = { version = "725.0.0", package = "rustc-ap-rustc_lexer" }
rustc-hash = "1.1.0"
-arrayvec = "0.7"
once_cell = "1.3.1"
indexmap = "1.4.0"
smol_str = "0.1.15"
look_ahead_scratch.push(rhs_ele.clone());
let mut rhs_children_clone = rhs_children.clone();
let mut insert = false;
- while let Some(rhs_child) = rhs_children_clone.next() {
+ for rhs_child in &mut rhs_children_clone {
if syntax_element_eq(&lhs_ele, &rhs_child) {
cov_mark::hit!(diff_insertions);
insert = true;
expr_ext::{ArrayExprKind, Effect, ElseBranch, LiteralKind},
generated::{nodes::*, tokens::*},
node_ext::{
- AttrKind, AttrsOwnerNode, FieldKind, Macro, NameLike, NameOrNameRef, PathSegmentKind,
- SelfParamKind, SlicePatComponents, StructKind, TypeBoundKind, VisibilityKind,
+ AttrKind, FieldKind, Macro, NameLike, NameOrNameRef, PathSegmentKind, SelfParamKind,
+ SlicePatComponents, StructKind, TypeBoundKind, VisibilityKind,
},
operators::{ArithOp, BinaryOp, CmpOp, LogicOp, Ordering, RangeOp, UnaryOp},
token_ext::{
TypeParam(TypeParam),
}
impl ast::AttrsOwner for GenericParam {}
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct DynArgListOwner {
+ pub(crate) syntax: SyntaxNode,
+}
+impl ast::ArgListOwner for DynArgListOwner {}
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct DynAttrsOwner {
+ pub(crate) syntax: SyntaxNode,
+}
+impl ast::AttrsOwner for DynAttrsOwner {}
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct DynGenericParamsOwner {
+ pub(crate) syntax: SyntaxNode,
+}
+impl ast::GenericParamsOwner for DynGenericParamsOwner {}
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct DynLoopBodyOwner {
+ pub(crate) syntax: SyntaxNode,
+}
+impl ast::LoopBodyOwner for DynLoopBodyOwner {}
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct DynModuleItemOwner {
+ pub(crate) syntax: SyntaxNode,
+}
+impl ast::ModuleItemOwner for DynModuleItemOwner {}
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct DynNameOwner {
+ pub(crate) syntax: SyntaxNode,
+}
+impl ast::NameOwner for DynNameOwner {}
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct DynTypeBoundsOwner {
+ pub(crate) syntax: SyntaxNode,
+}
+impl ast::TypeBoundsOwner for DynTypeBoundsOwner {}
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct DynVisibilityOwner {
+ pub(crate) syntax: SyntaxNode,
+}
+impl ast::VisibilityOwner for DynVisibilityOwner {}
impl AstNode for Name {
fn can_cast(kind: SyntaxKind) -> bool { kind == NAME }
fn cast(syntax: SyntaxNode) -> Option<Self> {
}
}
}
+impl DynArgListOwner {
+ #[inline]
+ pub fn new<T: ast::ArgListOwner>(node: T) -> DynArgListOwner {
+ DynArgListOwner { syntax: node.syntax().clone() }
+ }
+}
+impl AstNode for DynArgListOwner {
+ fn can_cast(kind: SyntaxKind) -> bool {
+ match kind {
+ CALL_EXPR | METHOD_CALL_EXPR => true,
+ _ => false,
+ }
+ }
+ fn cast(syntax: SyntaxNode) -> Option<Self> {
+ Self::can_cast(syntax.kind()).then(|| DynArgListOwner { syntax })
+ }
+ fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl DynAttrsOwner {
+ #[inline]
+ pub fn new<T: ast::AttrsOwner>(node: T) -> DynAttrsOwner {
+ DynAttrsOwner { syntax: node.syntax().clone() }
+ }
+}
+impl AstNode for DynAttrsOwner {
+ fn can_cast(kind: SyntaxKind) -> bool {
+ match kind {
+ MACRO_CALL
+ | SOURCE_FILE
+ | CONST
+ | ENUM
+ | EXTERN_BLOCK
+ | EXTERN_CRATE
+ | FN
+ | IMPL
+ | MACRO_RULES
+ | MACRO_DEF
+ | MODULE
+ | STATIC
+ | STRUCT
+ | TRAIT
+ | TYPE_ALIAS
+ | UNION
+ | USE
+ | ITEM_LIST
+ | BLOCK_EXPR
+ | SELF_PARAM
+ | PARAM
+ | RECORD_FIELD
+ | TUPLE_FIELD
+ | VARIANT
+ | ASSOC_ITEM_LIST
+ | EXTERN_ITEM_LIST
+ | CONST_PARAM
+ | LIFETIME_PARAM
+ | TYPE_PARAM
+ | EXPR_STMT
+ | LET_STMT
+ | ARRAY_EXPR
+ | AWAIT_EXPR
+ | BIN_EXPR
+ | BOX_EXPR
+ | BREAK_EXPR
+ | CALL_EXPR
+ | CAST_EXPR
+ | CLOSURE_EXPR
+ | CONTINUE_EXPR
+ | EFFECT_EXPR
+ | FIELD_EXPR
+ | FOR_EXPR
+ | IF_EXPR
+ | INDEX_EXPR
+ | LITERAL
+ | LOOP_EXPR
+ | MATCH_EXPR
+ | METHOD_CALL_EXPR
+ | PAREN_EXPR
+ | PATH_EXPR
+ | PREFIX_EXPR
+ | RANGE_EXPR
+ | REF_EXPR
+ | RETURN_EXPR
+ | TRY_EXPR
+ | TUPLE_EXPR
+ | WHILE_EXPR
+ | YIELD_EXPR
+ | RECORD_EXPR_FIELD_LIST
+ | RECORD_EXPR_FIELD
+ | MATCH_ARM_LIST
+ | MATCH_ARM
+ | IDENT_PAT
+ | RECORD_PAT_FIELD => true,
+ _ => false,
+ }
+ }
+ fn cast(syntax: SyntaxNode) -> Option<Self> {
+ Self::can_cast(syntax.kind()).then(|| DynAttrsOwner { syntax })
+ }
+ fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl DynGenericParamsOwner {
+ #[inline]
+ pub fn new<T: ast::GenericParamsOwner>(node: T) -> DynGenericParamsOwner {
+ DynGenericParamsOwner { syntax: node.syntax().clone() }
+ }
+}
+impl AstNode for DynGenericParamsOwner {
+ fn can_cast(kind: SyntaxKind) -> bool {
+ match kind {
+ ENUM | FN | IMPL | STRUCT | TRAIT | TYPE_ALIAS | UNION => true,
+ _ => false,
+ }
+ }
+ fn cast(syntax: SyntaxNode) -> Option<Self> {
+ Self::can_cast(syntax.kind()).then(|| DynGenericParamsOwner { syntax })
+ }
+ fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl DynLoopBodyOwner {
+ #[inline]
+ pub fn new<T: ast::LoopBodyOwner>(node: T) -> DynLoopBodyOwner {
+ DynLoopBodyOwner { syntax: node.syntax().clone() }
+ }
+}
+impl AstNode for DynLoopBodyOwner {
+ fn can_cast(kind: SyntaxKind) -> bool {
+ match kind {
+ FOR_EXPR | LOOP_EXPR | WHILE_EXPR => true,
+ _ => false,
+ }
+ }
+ fn cast(syntax: SyntaxNode) -> Option<Self> {
+ Self::can_cast(syntax.kind()).then(|| DynLoopBodyOwner { syntax })
+ }
+ fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl DynModuleItemOwner {
+ #[inline]
+ pub fn new<T: ast::ModuleItemOwner>(node: T) -> DynModuleItemOwner {
+ DynModuleItemOwner { syntax: node.syntax().clone() }
+ }
+}
+impl AstNode for DynModuleItemOwner {
+ fn can_cast(kind: SyntaxKind) -> bool {
+ match kind {
+ MACRO_ITEMS | SOURCE_FILE | ITEM_LIST => true,
+ _ => false,
+ }
+ }
+ fn cast(syntax: SyntaxNode) -> Option<Self> {
+ Self::can_cast(syntax.kind()).then(|| DynModuleItemOwner { syntax })
+ }
+ fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl DynNameOwner {
+ #[inline]
+ pub fn new<T: ast::NameOwner>(node: T) -> DynNameOwner {
+ DynNameOwner { syntax: node.syntax().clone() }
+ }
+}
+impl AstNode for DynNameOwner {
+ fn can_cast(kind: SyntaxKind) -> bool {
+ match kind {
+ CONST | ENUM | FN | MACRO_RULES | MACRO_DEF | MODULE | STATIC | STRUCT | TRAIT
+ | TYPE_ALIAS | UNION | RENAME | SELF_PARAM | RECORD_FIELD | VARIANT | CONST_PARAM
+ | TYPE_PARAM | IDENT_PAT => true,
+ _ => false,
+ }
+ }
+ fn cast(syntax: SyntaxNode) -> Option<Self> {
+ Self::can_cast(syntax.kind()).then(|| DynNameOwner { syntax })
+ }
+ fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl DynTypeBoundsOwner {
+ #[inline]
+ pub fn new<T: ast::TypeBoundsOwner>(node: T) -> DynTypeBoundsOwner {
+ DynTypeBoundsOwner { syntax: node.syntax().clone() }
+ }
+}
+impl AstNode for DynTypeBoundsOwner {
+ fn can_cast(kind: SyntaxKind) -> bool {
+ match kind {
+ ASSOC_TYPE_ARG | TRAIT | TYPE_ALIAS | LIFETIME_PARAM | TYPE_PARAM | WHERE_PRED => true,
+ _ => false,
+ }
+ }
+ fn cast(syntax: SyntaxNode) -> Option<Self> {
+ Self::can_cast(syntax.kind()).then(|| DynTypeBoundsOwner { syntax })
+ }
+ fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl DynVisibilityOwner {
+ #[inline]
+ pub fn new<T: ast::VisibilityOwner>(node: T) -> DynVisibilityOwner {
+ DynVisibilityOwner { syntax: node.syntax().clone() }
+ }
+}
+impl AstNode for DynVisibilityOwner {
+ fn can_cast(kind: SyntaxKind) -> bool {
+ match kind {
+ CONST | ENUM | EXTERN_CRATE | FN | IMPL | MACRO_RULES | MACRO_DEF | MODULE | STATIC
+ | STRUCT | TRAIT | TYPE_ALIAS | UNION | USE | RECORD_FIELD | TUPLE_FIELD | VARIANT => {
+ true
+ }
+ _ => false,
+ }
+ }
+ fn cast(syntax: SyntaxNode) -> Option<Self> {
+ Self::can_cast(syntax.kind()).then(|| DynVisibilityOwner { syntax })
+ }
+ fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
impl std::fmt::Display for GenericArg {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f)
pub fn expr_for_loop(pat: ast::Pat, expr: ast::Expr, block: ast::BlockExpr) -> ast::Expr {
expr_from_text(&format!("for {} in {} {}", pat, expr, block))
}
+
+pub fn expr_loop(block: ast::BlockExpr) -> ast::Expr {
+ expr_from_text(&format!("loop {}", block))
+}
+
pub fn expr_prefix(op: SyntaxKind, expr: ast::Expr) -> ast::Expr {
let token = token(op);
expr_from_text(&format!("{}{}", token, expr))
}
pub fn self_param() -> ast::SelfParam {
- ast_from_text(&format!("fn f(&self) {{ }}"))
+ ast_from_text("fn f(&self) { }")
}
pub fn ret_type(ty: ast::Type) -> ast::RetType {
impl AttrsOwner for Macro {}
-/// Basically an owned `dyn AttrsOwner` without extra boxing.
-pub struct AttrsOwnerNode {
- node: SyntaxNode,
-}
-
-impl AttrsOwnerNode {
- pub fn new<N: AttrsOwner>(node: N) -> Self {
- AttrsOwnerNode { node: node.syntax().clone() }
- }
-}
-
-impl AttrsOwner for AttrsOwnerNode {}
-impl AstNode for AttrsOwnerNode {
- fn can_cast(_: SyntaxKind) -> bool
- where
- Self: Sized,
- {
- false
- }
- fn cast(_: SyntaxNode) -> Option<Self>
- where
- Self: Sized,
- {
- None
- }
- fn syntax(&self) -> &SyntaxNode {
- &self.node
- }
-}
-
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AttrKind {
Inner,
impl ast::Item {
pub fn generic_param_list(&self) -> Option<ast::GenericParamList> {
- match self {
- ast::Item::Enum(it) => it.generic_param_list(),
- ast::Item::Fn(it) => it.generic_param_list(),
- ast::Item::Impl(it) => it.generic_param_list(),
- ast::Item::Struct(it) => it.generic_param_list(),
- ast::Item::Trait(it) => it.generic_param_list(),
- ast::Item::TypeAlias(it) => it.generic_param_list(),
- ast::Item::Union(it) => it.generic_param_list(),
- _ => None,
- }
+ ast::DynGenericParamsOwner::cast(self.syntax().clone())?.generic_param_list()
}
}
.into_token()
.filter(|it| matches!(it.kind(), T!['}'] | T![')'] | T![']']))
}
+
+ pub fn parent_meta(&self) -> Option<ast::Meta> {
+ self.syntax().parent().and_then(ast::Meta::cast)
+ }
+}
+
+impl ast::Meta {
+ pub fn parent_attr(&self) -> Option<ast::Attr> {
+ self.syntax().parent().and_then(ast::Attr::cast)
+ }
}
impl ast::GenericParamList {
ptr::{AstPtr, SyntaxNodePtr},
syntax_error::SyntaxError,
syntax_node::{
- SyntaxElement, SyntaxElementChildren, SyntaxNode, SyntaxNodeChildren, SyntaxToken,
- SyntaxTreeBuilder,
+ PreorderWithTokens, SyntaxElement, SyntaxElementChildren, SyntaxNode, SyntaxNodeChildren,
+ SyntaxToken, SyntaxTreeBuilder,
},
token_text::TokenText,
};
pub type SyntaxElement = rowan::SyntaxElement<RustLanguage>;
pub type SyntaxNodeChildren = rowan::SyntaxNodeChildren<RustLanguage>;
pub type SyntaxElementChildren = rowan::SyntaxElementChildren<RustLanguage>;
+pub type PreorderWithTokens = rowan::api::PreorderWithTokens<RustLanguage>;
#[derive(Default)]
pub struct SyntaxTreeBuilder {
fmt::Write,
};
+use itertools::Itertools;
use proc_macro2::{Punct, Spacing};
use quote::{format_ident, quote};
use ungrammar::{rust_grammar, Grammar, Rule};
})
.unzip();
+ let (dyn_node_defs, dyn_node_boilerplate_impls): (Vec<_>, Vec<_>) = grammar
+ .nodes
+ .iter()
+ .flat_map(|node| node.traits.iter().map(move |t| (t, node)))
+ .into_group_map()
+ .into_iter()
+ .sorted_by_key(|(k, _)| k.clone())
+ .map(|(trait_name, nodes)| {
+ let name = format_ident!("Dyn{}", trait_name);
+ let trait_name = format_ident!("{}", trait_name);
+ let kinds: Vec<_> = nodes
+ .iter()
+ .map(|name| format_ident!("{}", to_upper_snake_case(&name.name.to_string())))
+ .collect();
+
+ (
+ quote! {
+ #[pretty_doc_comment_placeholder_workaround]
+ #[derive(Debug, Clone, PartialEq, Eq, Hash)]
+ pub struct #name {
+ pub(crate) syntax: SyntaxNode,
+ }
+ impl ast::#trait_name for #name {}
+ },
+ quote! {
+ impl #name {
+ #[inline]
+ pub fn new<T: ast::#trait_name>(node: T) -> #name {
+ #name {
+ syntax: node.syntax().clone()
+ }
+ }
+ }
+ impl AstNode for #name {
+ fn can_cast(kind: SyntaxKind) -> bool {
+ match kind {
+ #(#kinds)|* => true,
+ _ => false,
+ }
+ }
+ fn cast(syntax: SyntaxNode) -> Option<Self> {
+ Self::can_cast(syntax.kind()).then(|| #name { syntax })
+ }
+ fn syntax(&self) -> &SyntaxNode {
+ &self.syntax
+ }
+ }
+ },
+ )
+ })
+ .unzip();
+
let enum_names = grammar.enums.iter().map(|it| &it.name);
let node_names = grammar.nodes.iter().map(|it| &it.name);
#(#node_defs)*
#(#enum_defs)*
+ #(#dyn_node_defs)*
#(#node_boilerplate_impls)*
#(#enum_boilerplate_impls)*
+ #(#dyn_node_boilerplate_impls)*
#(#display_impls)*
};
}
pub fn as_str(&self) -> &str {
- match self.0 {
- Repr::Borrowed(it) => it,
- Repr::Owned(ref green) => green.text(),
+ match &self.0 {
+ &Repr::Borrowed(it) => it,
+ Repr::Owned(green) => green.text(),
}
}
}
}
fn validate_visibility(vis: ast::Visibility, errors: &mut Vec<SyntaxError>) {
- if vis.in_token().is_none() {
- if vis.path().and_then(|p| p.as_single_name_ref()).and_then(|n| n.ident_token()).is_some() {
- errors.push(SyntaxError::new(
- "incorrect visibility restriction",
- vis.syntax.text_range(),
- ));
- }
+ let path_without_in_token = vis.in_token().is_none()
+ && vis.path().and_then(|p| p.as_single_name_ref()).and_then(|n| n.ident_token()).is_some();
+ if path_without_in_token {
+ errors.push(SyntaxError::new("incorrect visibility restriction", vis.syntax.text_range()));
}
let parent = match vis.syntax().parent() {
Some(it) => it,
--- /dev/null
+SOURCE_FILE@0..90
+ FN@0..40
+ FN_KW@0..2 "fn"
+ WHITESPACE@2..3 " "
+ NAME@3..4
+ IDENT@3..4 "f"
+ PARAM_LIST@4..6
+ L_PAREN@4..5 "("
+ R_PAREN@5..6 ")"
+ WHITESPACE@6..7 " "
+ BLOCK_EXPR@7..40
+ L_CURLY@7..8 "{"
+ WHITESPACE@8..13 "\n "
+ EXPR_STMT@13..31
+ PATH_EXPR@13..31
+ PATH@13..31
+ PATH_SEGMENT@13..31
+ NAME_REF@13..14
+ IDENT@13..14 "S"
+ GENERIC_ARG_LIST@14..31
+ COLON2@14..16 "::"
+ L_ANGLE@16..17 "<"
+ TYPE_ARG@17..31
+ PATH_TYPE@17..31
+ PATH@17..31
+ PATH@17..28
+ PATH_SEGMENT@17..28
+ NAME_REF@17..21
+ IDENT@17..21 "Item"
+ GENERIC_ARG_LIST@21..28
+ COLON2@21..23 "::"
+ L_ANGLE@23..24 "<"
+ TYPE_ARG@24..27
+ PATH_TYPE@24..27
+ PATH@24..27
+ PATH_SEGMENT@24..27
+ NAME_REF@24..27
+ IDENT@24..27 "lol"
+ R_ANGLE@27..28 ">"
+ COLON2@28..30 "::"
+ ERROR@30..31
+ L_ANGLE@30..31 "<"
+ BIN_EXPR@31..38
+ PATH_EXPR@31..35
+ PATH@31..35
+ PATH_SEGMENT@31..35
+ NAME_REF@31..35
+ IDENT@31..35 "nope"
+ SHR@35..37 ">>"
+ ERROR@37..38
+ SEMICOLON@37..38 ";"
+ WHITESPACE@38..39 "\n"
+ R_CURLY@39..40 "}"
+ WHITESPACE@40..42 "\n\n"
+ FN@42..89
+ FN_KW@42..44 "fn"
+ WHITESPACE@44..45 " "
+ NAME@45..46
+ IDENT@45..46 "g"
+ PARAM_LIST@46..48
+ L_PAREN@46..47 "("
+ R_PAREN@47..48 ")"
+ WHITESPACE@48..49 " "
+ BLOCK_EXPR@49..89
+ L_CURLY@49..50 "{"
+ WHITESPACE@50..55 "\n "
+ LET_STMT@55..76
+ LET_KW@55..58 "let"
+ WHITESPACE@58..59 " "
+ WILDCARD_PAT@59..60
+ UNDERSCORE@59..60 "_"
+ COLON@60..61 ":"
+ WHITESPACE@61..62 " "
+ PATH_TYPE@62..76
+ PATH@62..76
+ PATH@62..73
+ PATH_SEGMENT@62..73
+ NAME_REF@62..66
+ IDENT@62..66 "Item"
+ GENERIC_ARG_LIST@66..73
+ COLON2@66..68 "::"
+ L_ANGLE@68..69 "<"
+ TYPE_ARG@69..72
+ PATH_TYPE@69..72
+ PATH@69..72
+ PATH_SEGMENT@69..72
+ NAME_REF@69..72
+ IDENT@69..72 "lol"
+ R_ANGLE@72..73 ">"
+ COLON2@73..75 "::"
+ ERROR@75..76
+ L_ANGLE@75..76 "<"
+ EXPR_STMT@76..83
+ BIN_EXPR@76..83
+ PATH_EXPR@76..80
+ PATH@76..80
+ PATH_SEGMENT@76..80
+ NAME_REF@76..80
+ IDENT@76..80 "nope"
+ R_ANGLE@80..81 ">"
+ WHITESPACE@81..82 " "
+ ERROR@82..83
+ EQ@82..83 "="
+ WHITESPACE@83..84 " "
+ EXPR_STMT@84..87
+ TUPLE_EXPR@84..86
+ L_PAREN@84..85 "("
+ R_PAREN@85..86 ")"
+ SEMICOLON@86..87 ";"
+ WHITESPACE@87..88 "\n"
+ R_CURLY@88..89 "}"
+ WHITESPACE@89..90 "\n"
+error 30..30: expected identifier
+error 31..31: expected COMMA
+error 31..31: expected R_ANGLE
+error 31..31: expected SEMICOLON
+error 37..37: expected expression
+error 75..75: expected identifier
+error 76..76: expected SEMICOLON
+error 82..82: expected expression
+error 83..83: expected SEMICOLON
--- /dev/null
+fn f() {
+ S::<Item::<lol>::<nope>>;
+}
+
+fn g() {
+ let _: Item::<lol>::<nope> = ();
+}
--- /dev/null
+SOURCE_FILE@0..19
+ STATIC@0..18
+ STATIC_KW@0..6 "static"
+ WHITESPACE@6..7 " "
+ ERROR@7..8
+ UNDERSCORE@7..8 "_"
+ COLON@8..9 ":"
+ WHITESPACE@9..10 " "
+ PATH_TYPE@10..13
+ PATH@10..13
+ PATH_SEGMENT@10..13
+ NAME_REF@10..13
+ IDENT@10..13 "i32"
+ WHITESPACE@13..14 " "
+ EQ@14..15 "="
+ WHITESPACE@15..16 " "
+ LITERAL@16..17
+ INT_NUMBER@16..17 "5"
+ SEMICOLON@17..18 ";"
+ WHITESPACE@18..19 "\n"
+error 7..7: expected a name
--- /dev/null
+static _: i32 = 5;
+++ /dev/null
-SOURCE_FILE@0..19
- STATIC@0..18
- STATIC_KW@0..6 "static"
- WHITESPACE@6..7 " "
- ERROR@7..8
- UNDERSCORE@7..8 "_"
- COLON@8..9 ":"
- WHITESPACE@9..10 " "
- PATH_TYPE@10..13
- PATH@10..13
- PATH_SEGMENT@10..13
- NAME_REF@10..13
- IDENT@10..13 "i32"
- WHITESPACE@13..14 " "
- EQ@14..15 "="
- WHITESPACE@15..16 " "
- LITERAL@16..17
- INT_NUMBER@16..17 "5"
- SEMICOLON@17..18 ";"
- WHITESPACE@18..19 "\n"
-error 7..7: expected a name
+++ /dev/null
-static _: i32 = 5;
-SOURCE_FILE@0..248
- USE@0..58
+SOURCE_FILE@0..15
+ USE@0..14
USE_KW@0..3 "use"
WHITESPACE@3..4 " "
- USE_TREE@4..57
- USE_TREE_LIST@4..57
+ USE_TREE@4..13
+ USE_TREE_LIST@4..13
L_CURLY@4..5 "{"
- USE_TREE@5..28
- PATH@5..28
- PATH@5..22
- PATH@5..16
- PATH@5..10
- PATH_SEGMENT@5..10
- NAME_REF@5..10
- CRATE_KW@5..10 "crate"
- COLON2@10..12 "::"
- PATH_SEGMENT@12..16
- NAME_REF@12..16
- IDENT@12..16 "path"
- COLON2@16..18 "::"
- PATH_SEGMENT@18..22
- NAME_REF@18..22
- IDENT@18..22 "from"
- COLON2@22..24 "::"
- PATH_SEGMENT@24..28
- NAME_REF@24..28
- IDENT@24..28 "root"
- COMMA@28..29 ","
- WHITESPACE@29..30 " "
- USE_TREE@30..56
- PATH@30..56
- PATH@30..44
- PATH@30..38
- PATH@30..32
- PATH_SEGMENT@30..32
- NAME_REF@30..32
- IDENT@30..32 "or"
- COLON2@32..34 "::"
- PATH_SEGMENT@34..38
- NAME_REF@34..38
- IDENT@34..38 "path"
- COLON2@38..40 "::"
- PATH_SEGMENT@40..44
- NAME_REF@40..44
- IDENT@40..44 "from"
- COLON2@44..46 "::"
- PATH_SEGMENT@46..56
- NAME_REF@46..56
- IDENT@46..56 "crate_name"
- R_CURLY@56..57 "}"
- SEMICOLON@57..58 ";"
- WHITESPACE@58..59 " "
- USE@59..121
- COMMENT@59..97 "// Rust 2018 (with a ..."
- WHITESPACE@97..98 "\n"
- USE_KW@98..101 "use"
- WHITESPACE@101..102 " "
- USE_TREE@102..120
- USE_TREE_LIST@102..120
- L_CURLY@102..103 "{"
- USE_TREE@103..119
- PATH@103..119
- PATH@103..113
- PATH@103..107
- PATH_SEGMENT@103..107
- NAME_REF@103..107
- IDENT@103..107 "path"
- COLON2@107..109 "::"
- PATH_SEGMENT@109..113
- NAME_REF@109..113
- IDENT@109..113 "from"
- COLON2@113..115 "::"
- PATH_SEGMENT@115..119
- NAME_REF@115..119
- IDENT@115..119 "root"
- R_CURLY@119..120 "}"
- SEMICOLON@120..121 ";"
- WHITESPACE@121..122 " "
- USE@122..165
- COMMENT@122..134 "// Rust 2015"
- WHITESPACE@134..135 "\n"
- USE_KW@135..138 "use"
- WHITESPACE@138..139 " "
- USE_TREE@139..164
- COLON2@139..141 "::"
- USE_TREE_LIST@141..164
- L_CURLY@141..142 "{"
- USE_TREE@142..163
- PATH@142..163
- PATH@142..157
- PATH@142..146
- PATH_SEGMENT@142..146
- NAME_REF@142..146
- IDENT@142..146 "some"
- COLON2@146..148 "::"
- PATH_SEGMENT@148..157
- NAME_REF@148..157
- IDENT@148..157 "arbitrary"
- COLON2@157..159 "::"
- PATH_SEGMENT@159..163
- NAME_REF@159..163
- IDENT@159..163 "path"
- R_CURLY@163..164 "}"
- SEMICOLON@164..165 ";"
- WHITESPACE@165..166 " "
- USE@166..204
- COMMENT@166..178 "// Rust 2015"
- WHITESPACE@178..179 "\n"
- USE_KW@179..182 "use"
- WHITESPACE@182..183 " "
- USE_TREE@183..203
- COLON2@183..185 "::"
- USE_TREE_LIST@185..203
- L_CURLY@185..186 "{"
- USE_TREE@186..202
- USE_TREE_LIST@186..202
- L_CURLY@186..187 "{"
- USE_TREE@187..201
- USE_TREE_LIST@187..201
- L_CURLY@187..188 "{"
- USE_TREE@188..200
- PATH@188..200
- PATH@188..192
- PATH_SEGMENT@188..192
- NAME_REF@188..192
- IDENT@188..192 "root"
- COLON2@192..194 "::"
- PATH_SEGMENT@194..200
- NAME_REF@194..200
- IDENT@194..200 "export"
- R_CURLY@200..201 "}"
- R_CURLY@201..202 "}"
- R_CURLY@202..203 "}"
- SEMICOLON@203..204 ";"
- WHITESPACE@204..205 " "
- COMMENT@205..247 "// Nonsensical but pe ..."
- WHITESPACE@247..248 "\n"
+ USE_TREE@5..6
+ PATH@5..6
+ PATH_SEGMENT@5..6
+ NAME_REF@5..6
+ IDENT@5..6 "a"
+ COMMA@6..7 ","
+ WHITESPACE@7..8 " "
+ USE_TREE@8..9
+ PATH@8..9
+ PATH_SEGMENT@8..9
+ NAME_REF@8..9
+ IDENT@8..9 "b"
+ COMMA@9..10 ","
+ WHITESPACE@10..11 " "
+ USE_TREE@11..12
+ PATH@11..12
+ PATH_SEGMENT@11..12
+ NAME_REF@11..12
+ IDENT@11..12 "c"
+ R_CURLY@12..13 "}"
+ SEMICOLON@13..14 ";"
+ WHITESPACE@14..15 "\n"
-use {crate::path::from::root, or::path::from::crate_name}; // Rust 2018 (with a crate named `or`)
-use {path::from::root}; // Rust 2015
-use ::{some::arbitrary::path}; // Rust 2015
-use ::{{{root::export}}}; // Nonsensical but perfectly legal nesting
+use {a, b, c};
+++ /dev/null
-SOURCE_FILE@0..60
- USE@0..6
- USE_KW@0..3 "use"
- WHITESPACE@3..4 " "
- USE_TREE@4..5
- STAR@4..5 "*"
- SEMICOLON@5..6 ";"
- WHITESPACE@6..7 "\n"
- USE@7..15
- USE_KW@7..10 "use"
- WHITESPACE@10..11 " "
- USE_TREE@11..14
- COLON2@11..13 "::"
- STAR@13..14 "*"
- SEMICOLON@14..15 ";"
- WHITESPACE@15..16 "\n"
- USE@16..36
- USE_KW@16..19 "use"
- WHITESPACE@19..20 " "
- USE_TREE@20..35
- PATH@20..30
- PATH@20..24
- PATH_SEGMENT@20..24
- NAME_REF@20..24
- IDENT@20..24 "some"
- COLON2@24..26 "::"
- PATH_SEGMENT@26..30
- NAME_REF@26..30
- IDENT@26..30 "path"
- COLON2@30..32 "::"
- USE_TREE_LIST@32..35
- L_CURLY@32..33 "{"
- USE_TREE@33..34
- STAR@33..34 "*"
- R_CURLY@34..35 "}"
- SEMICOLON@35..36 ";"
- WHITESPACE@36..37 "\n"
- USE@37..59
- USE_KW@37..40 "use"
- WHITESPACE@40..41 " "
- USE_TREE@41..58
- PATH@41..51
- PATH@41..45
- PATH_SEGMENT@41..45
- NAME_REF@41..45
- IDENT@41..45 "some"
- COLON2@45..47 "::"
- PATH_SEGMENT@47..51
- NAME_REF@47..51
- IDENT@47..51 "path"
- COLON2@51..53 "::"
- USE_TREE_LIST@53..58
- L_CURLY@53..54 "{"
- USE_TREE@54..57
- COLON2@54..56 "::"
- STAR@56..57 "*"
- R_CURLY@57..58 "}"
- SEMICOLON@58..59 ";"
- WHITESPACE@59..60 "\n"
+++ /dev/null
-use *;
-use ::*;
-use some::path::{*};
-use some::path::{::*};
--- /dev/null
+SOURCE_FILE@0..89
+ IMPL@0..88
+ IMPL_KW@0..4 "impl"
+ WHITESPACE@4..5 " "
+ PATH_TYPE@5..6
+ PATH@5..6
+ PATH_SEGMENT@5..6
+ NAME_REF@5..6
+ IDENT@5..6 "F"
+ WHITESPACE@6..7 " "
+ ASSOC_ITEM_LIST@7..88
+ L_CURLY@7..8 "{"
+ WHITESPACE@8..13 "\n "
+ TYPE_ALIAS@13..26
+ TYPE_KW@13..17 "type"
+ WHITESPACE@17..18 " "
+ NAME@18..19
+ IDENT@18..19 "A"
+ WHITESPACE@19..20 " "
+ EQ@20..21 "="
+ WHITESPACE@21..22 " "
+ PATH_TYPE@22..25
+ PATH@22..25
+ PATH_SEGMENT@22..25
+ NAME_REF@22..25
+ IDENT@22..25 "i32"
+ SEMICOLON@25..26 ";"
+ WHITESPACE@26..31 "\n "
+ CONST@31..49
+ CONST_KW@31..36 "const"
+ WHITESPACE@36..37 " "
+ NAME@37..38
+ IDENT@37..38 "B"
+ COLON@38..39 ":"
+ WHITESPACE@39..40 " "
+ PATH_TYPE@40..43
+ PATH@40..43
+ PATH_SEGMENT@40..43
+ NAME_REF@40..43
+ IDENT@40..43 "i32"
+ WHITESPACE@43..44 " "
+ EQ@44..45 "="
+ WHITESPACE@45..46 " "
+ LITERAL@46..48
+ INT_NUMBER@46..48 "92"
+ SEMICOLON@48..49 ";"
+ WHITESPACE@49..54 "\n "
+ FN@54..65
+ FN_KW@54..56 "fn"
+ WHITESPACE@56..57 " "
+ NAME@57..60
+ IDENT@57..60 "foo"
+ PARAM_LIST@60..62
+ L_PAREN@60..61 "("
+ R_PAREN@61..62 ")"
+ WHITESPACE@62..63 " "
+ BLOCK_EXPR@63..65
+ L_CURLY@63..64 "{"
+ R_CURLY@64..65 "}"
+ WHITESPACE@65..70 "\n "
+ FN@70..86
+ FN_KW@70..72 "fn"
+ WHITESPACE@72..73 " "
+ NAME@73..76
+ IDENT@73..76 "bar"
+ PARAM_LIST@76..83
+ L_PAREN@76..77 "("
+ SELF_PARAM@77..82
+ AMP@77..78 "&"
+ NAME@78..82
+ SELF_KW@78..82 "self"
+ R_PAREN@82..83 ")"
+ WHITESPACE@83..84 " "
+ BLOCK_EXPR@84..86
+ L_CURLY@84..85 "{"
+ R_CURLY@85..86 "}"
+ WHITESPACE@86..87 "\n"
+ R_CURLY@87..88 "}"
+ WHITESPACE@88..89 "\n"
--- /dev/null
+impl F {
+ type A = i32;
+ const B: i32 = 92;
+ fn foo() {}
+ fn bar(&self) {}
+}
+++ /dev/null
-SOURCE_FILE@0..89
- IMPL@0..88
- IMPL_KW@0..4 "impl"
- WHITESPACE@4..5 " "
- PATH_TYPE@5..6
- PATH@5..6
- PATH_SEGMENT@5..6
- NAME_REF@5..6
- IDENT@5..6 "F"
- WHITESPACE@6..7 " "
- ASSOC_ITEM_LIST@7..88
- L_CURLY@7..8 "{"
- WHITESPACE@8..13 "\n "
- TYPE_ALIAS@13..26
- TYPE_KW@13..17 "type"
- WHITESPACE@17..18 " "
- NAME@18..19
- IDENT@18..19 "A"
- WHITESPACE@19..20 " "
- EQ@20..21 "="
- WHITESPACE@21..22 " "
- PATH_TYPE@22..25
- PATH@22..25
- PATH_SEGMENT@22..25
- NAME_REF@22..25
- IDENT@22..25 "i32"
- SEMICOLON@25..26 ";"
- WHITESPACE@26..31 "\n "
- CONST@31..49
- CONST_KW@31..36 "const"
- WHITESPACE@36..37 " "
- NAME@37..38
- IDENT@37..38 "B"
- COLON@38..39 ":"
- WHITESPACE@39..40 " "
- PATH_TYPE@40..43
- PATH@40..43
- PATH_SEGMENT@40..43
- NAME_REF@40..43
- IDENT@40..43 "i32"
- WHITESPACE@43..44 " "
- EQ@44..45 "="
- WHITESPACE@45..46 " "
- LITERAL@46..48
- INT_NUMBER@46..48 "92"
- SEMICOLON@48..49 ";"
- WHITESPACE@49..54 "\n "
- FN@54..65
- FN_KW@54..56 "fn"
- WHITESPACE@56..57 " "
- NAME@57..60
- IDENT@57..60 "foo"
- PARAM_LIST@60..62
- L_PAREN@60..61 "("
- R_PAREN@61..62 ")"
- WHITESPACE@62..63 " "
- BLOCK_EXPR@63..65
- L_CURLY@63..64 "{"
- R_CURLY@64..65 "}"
- WHITESPACE@65..70 "\n "
- FN@70..86
- FN_KW@70..72 "fn"
- WHITESPACE@72..73 " "
- NAME@73..76
- IDENT@73..76 "bar"
- PARAM_LIST@76..83
- L_PAREN@76..77 "("
- SELF_PARAM@77..82
- AMP@77..78 "&"
- NAME@78..82
- SELF_KW@78..82 "self"
- R_PAREN@82..83 ")"
- WHITESPACE@83..84 " "
- BLOCK_EXPR@84..86
- L_CURLY@84..85 "{"
- R_CURLY@85..86 "}"
- WHITESPACE@86..87 "\n"
- R_CURLY@87..88 "}"
- WHITESPACE@88..89 "\n"
+++ /dev/null
-impl F {
- type A = i32;
- const B: i32 = 92;
- fn foo() {}
- fn bar(&self) {}
-}
+++ /dev/null
-SOURCE_FILE@0..59
- TYPE_ALIAS@0..58
- TYPE_KW@0..4 "type"
- WHITESPACE@4..5 " "
- NAME@5..6
- IDENT@5..6 "A"
- WHITESPACE@6..7 " "
- EQ@7..8 "="
- WHITESPACE@8..9 " "
- PATH_TYPE@9..57
- PATH@9..57
- PATH_SEGMENT@9..57
- NAME_REF@9..10
- IDENT@9..10 "B"
- GENERIC_ARG_LIST@10..57
- L_ANGLE@10..11 "<"
- LIFETIME_ARG@11..18
- LIFETIME@11..18
- LIFETIME_IDENT@11..18 "'static"
- COMMA@18..19 ","
- WHITESPACE@19..20 " "
- TYPE_ARG@20..23
- PATH_TYPE@20..23
- PATH@20..23
- PATH_SEGMENT@20..23
- NAME_REF@20..23
- IDENT@20..23 "i32"
- COMMA@23..24 ","
- WHITESPACE@24..25 " "
- CONST_ARG@25..26
- LITERAL@25..26
- INT_NUMBER@25..26 "1"
- COMMA@26..27 ","
- WHITESPACE@27..28 " "
- CONST_ARG@28..33
- BLOCK_EXPR@28..33
- L_CURLY@28..29 "{"
- WHITESPACE@29..30 " "
- LITERAL@30..31
- INT_NUMBER@30..31 "2"
- WHITESPACE@31..32 " "
- R_CURLY@32..33 "}"
- COMMA@33..34 ","
- WHITESPACE@34..35 " "
- ASSOC_TYPE_ARG@35..43
- NAME_REF@35..39
- IDENT@35..39 "Item"
- EQ@39..40 "="
- PATH_TYPE@40..43
- PATH@40..43
- PATH_SEGMENT@40..43
- NAME_REF@40..43
- IDENT@40..43 "u64"
- COMMA@43..44 ","
- WHITESPACE@44..45 " "
- CONST_ARG@45..49
- LITERAL@45..49
- TRUE_KW@45..49 "true"
- COMMA@49..50 ","
- WHITESPACE@50..51 " "
- CONST_ARG@51..56
- LITERAL@51..56
- FALSE_KW@51..56 "false"
- R_ANGLE@56..57 ">"
- SEMICOLON@57..58 ";"
- WHITESPACE@58..59 "\n"
+++ /dev/null
-type A = B<'static, i32, 1, { 2 }, Item=u64, true, false>;
-SOURCE_FILE@0..101
- TRAIT@0..41
+SOURCE_FILE@0..30
+ TRAIT@0..29
TRAIT_KW@0..5 "trait"
WHITESPACE@5..6 " "
NAME@6..7
IDENT@6..7 "T"
- GENERIC_PARAM_LIST@7..10
- L_ANGLE@7..8 "<"
- TYPE_PARAM@8..9
- NAME@8..9
- IDENT@8..9 "U"
- R_ANGLE@9..10 ">"
- COLON@10..11 ":"
- WHITESPACE@11..12 " "
- TYPE_BOUND_LIST@12..24
- TYPE_BOUND@12..16
- PATH_TYPE@12..16
- PATH@12..16
- PATH_SEGMENT@12..16
- NAME_REF@12..16
- IDENT@12..16 "Hash"
- WHITESPACE@16..17 " "
- PLUS@17..18 "+"
- WHITESPACE@18..19 " "
- TYPE_BOUND@19..24
- PATH_TYPE@19..24
- PATH@19..24
- PATH_SEGMENT@19..24
- NAME_REF@19..24
- IDENT@19..24 "Clone"
- WHITESPACE@24..25 " "
- WHERE_CLAUSE@25..38
- WHERE_KW@25..30 "where"
- WHITESPACE@30..31 " "
- WHERE_PRED@31..38
- PATH_TYPE@31..32
- PATH@31..32
- PATH_SEGMENT@31..32
- NAME_REF@31..32
- IDENT@31..32 "U"
- COLON@32..33 ":"
- WHITESPACE@33..34 " "
- TYPE_BOUND_LIST@34..38
- TYPE_BOUND@34..38
- PATH_TYPE@34..38
- PATH@34..38
- PATH_SEGMENT@34..38
- NAME_REF@34..38
- IDENT@34..38 "Copy"
- WHITESPACE@38..39 " "
- ASSOC_ITEM_LIST@39..41
- L_CURLY@39..40 "{"
- R_CURLY@40..41 "}"
- WHITESPACE@41..42 "\n"
- TRAIT@42..100
- TRAIT_KW@42..47 "trait"
- WHITESPACE@47..48 " "
- NAME@48..49
- IDENT@48..49 "X"
- GENERIC_PARAM_LIST@49..69
- L_ANGLE@49..50 "<"
- TYPE_PARAM@50..68
- NAME@50..51
- IDENT@50..51 "U"
- COLON@51..52 ":"
- WHITESPACE@52..53 " "
- TYPE_BOUND_LIST@53..68
- TYPE_BOUND@53..58
- PATH_TYPE@53..58
- PATH@53..58
- PATH_SEGMENT@53..58
- NAME_REF@53..58
- IDENT@53..58 "Debug"
- WHITESPACE@58..59 " "
- PLUS@59..60 "+"
- WHITESPACE@60..61 " "
- TYPE_BOUND@61..68
- PATH_TYPE@61..68
- PATH@61..68
- PATH_SEGMENT@61..68
- NAME_REF@61..68
- IDENT@61..68 "Display"
- R_ANGLE@68..69 ">"
- COLON@69..70 ":"
- WHITESPACE@70..71 " "
- TYPE_BOUND_LIST@71..83
- TYPE_BOUND@71..75
- PATH_TYPE@71..75
- PATH@71..75
- PATH_SEGMENT@71..75
- NAME_REF@71..75
- IDENT@71..75 "Hash"
- WHITESPACE@75..76 " "
- PLUS@76..77 "+"
- WHITESPACE@77..78 " "
- TYPE_BOUND@78..83
- PATH_TYPE@78..83
- PATH@78..83
- PATH_SEGMENT@78..83
- NAME_REF@78..83
- IDENT@78..83 "Clone"
- WHITESPACE@83..84 " "
- WHERE_CLAUSE@84..97
- WHERE_KW@84..89 "where"
- WHITESPACE@89..90 " "
- WHERE_PRED@90..97
- PATH_TYPE@90..91
- PATH@90..91
- PATH_SEGMENT@90..91
- NAME_REF@90..91
- IDENT@90..91 "U"
- COLON@91..92 ":"
- WHITESPACE@92..93 " "
- TYPE_BOUND_LIST@93..97
- TYPE_BOUND@93..97
- PATH_TYPE@93..97
- PATH@93..97
- PATH_SEGMENT@93..97
- NAME_REF@93..97
- IDENT@93..97 "Copy"
- WHITESPACE@97..98 " "
- ASSOC_ITEM_LIST@98..100
- L_CURLY@98..99 "{"
- R_CURLY@99..100 "}"
- WHITESPACE@100..101 "\n"
+ WHITESPACE@7..8 " "
+ ASSOC_ITEM_LIST@8..29
+ L_CURLY@8..9 "{"
+ WHITESPACE@9..10 " "
+ FN@10..27
+ FN_KW@10..12 "fn"
+ WHITESPACE@12..13 " "
+ NAME@13..16
+ IDENT@13..16 "new"
+ PARAM_LIST@16..18
+ L_PAREN@16..17 "("
+ R_PAREN@17..18 ")"
+ WHITESPACE@18..19 " "
+ RET_TYPE@19..26
+ THIN_ARROW@19..21 "->"
+ WHITESPACE@21..22 " "
+ PATH_TYPE@22..26
+ PATH@22..26
+ PATH_SEGMENT@22..26
+ NAME_REF@22..26
+ IDENT@22..26 "Self"
+ SEMICOLON@26..27 ";"
+ WHITESPACE@27..28 " "
+ R_CURLY@28..29 "}"
+ WHITESPACE@29..30 "\n"
-trait T<U>: Hash + Clone where U: Copy {}
-trait X<U: Debug + Display>: Hash + Clone where U: Copy {}
+trait T { fn new() -> Self; }
+++ /dev/null
-SOURCE_FILE@0..198
- USE@0..28
- USE_KW@0..3 "use"
- WHITESPACE@3..4 " "
- USE_TREE@4..27
- PATH@4..14
- PATH@4..8
- PATH_SEGMENT@4..8
- NAME_REF@4..8
- IDENT@4..8 "some"
- COLON2@8..10 "::"
- PATH_SEGMENT@10..14
- NAME_REF@10..14
- IDENT@10..14 "path"
- WHITESPACE@14..15 " "
- RENAME@15..27
- AS_KW@15..17 "as"
- WHITESPACE@17..18 " "
- NAME@18..27
- IDENT@18..27 "some_name"
- SEMICOLON@27..28 ";"
- WHITESPACE@28..29 "\n"
- USE@29..181
- USE_KW@29..32 "use"
- WHITESPACE@32..33 " "
- USE_TREE@33..180
- PATH@33..37
- PATH_SEGMENT@33..37
- NAME_REF@33..37
- IDENT@33..37 "some"
- COLON2@37..39 "::"
- USE_TREE_LIST@39..180
- L_CURLY@39..40 "{"
- WHITESPACE@40..42 "\n "
- USE_TREE@42..72
- PATH@42..53
- PATH@42..47
- PATH_SEGMENT@42..47
- NAME_REF@42..47
- IDENT@42..47 "other"
- COLON2@47..49 "::"
- PATH_SEGMENT@49..53
- NAME_REF@49..53
- IDENT@49..53 "path"
- WHITESPACE@53..54 " "
- RENAME@54..72
- AS_KW@54..56 "as"
- WHITESPACE@56..57 " "
- NAME@57..72
- IDENT@57..72 "some_other_name"
- COMMA@72..73 ","
- WHITESPACE@73..75 "\n "
- USE_TREE@75..108
- PATH@75..90
- PATH@75..84
- PATH_SEGMENT@75..84
- NAME_REF@75..84
- IDENT@75..84 "different"
- COLON2@84..86 "::"
- PATH_SEGMENT@86..90
- NAME_REF@86..90
- IDENT@86..90 "path"
- WHITESPACE@90..91 " "
- RENAME@91..108
- AS_KW@91..93 "as"
- WHITESPACE@93..94 " "
- NAME@94..108
- IDENT@94..108 "different_name"
- COMMA@108..109 ","
- WHITESPACE@109..111 "\n "
- USE_TREE@111..129
- PATH@111..129
- PATH@111..123
- PATH@111..114
- PATH_SEGMENT@111..114
- NAME_REF@111..114
- IDENT@111..114 "yet"
- COLON2@114..116 "::"
- PATH_SEGMENT@116..123
- NAME_REF@116..123
- IDENT@116..123 "another"
- COLON2@123..125 "::"
- PATH_SEGMENT@125..129
- NAME_REF@125..129
- IDENT@125..129 "path"
- COMMA@129..130 ","
- WHITESPACE@130..132 "\n "
- USE_TREE@132..178
- PATH@132..175
- PATH@132..164
- PATH@132..158
- PATH@132..148
- PATH@132..144
- PATH@132..139
- PATH_SEGMENT@132..139
- NAME_REF@132..139
- IDENT@132..139 "running"
- COLON2@139..141 "::"
- PATH_SEGMENT@141..144
- NAME_REF@141..144
- IDENT@141..144 "out"
- COLON2@144..146 "::"
- PATH_SEGMENT@146..148
- NAME_REF@146..148
- IDENT@146..148 "of"
- COLON2@148..150 "::"
- PATH_SEGMENT@150..158
- NAME_REF@150..158
- IDENT@150..158 "synonyms"
- COLON2@158..160 "::"
- PATH_SEGMENT@160..164
- NAME_REF@160..164
- IDENT@160..164 "for_"
- COLON2@164..166 "::"
- PATH_SEGMENT@166..175
- NAME_REF@166..175
- IDENT@166..175 "different"
- COLON2@175..177 "::"
- STAR@177..178 "*"
- WHITESPACE@178..179 "\n"
- R_CURLY@179..180 "}"
- SEMICOLON@180..181 ";"
- WHITESPACE@181..182 "\n"
- USE@182..197
- USE_KW@182..185 "use"
- WHITESPACE@185..186 " "
- USE_TREE@186..196
- PATH@186..191
- PATH_SEGMENT@186..191
- NAME_REF@186..191
- IDENT@186..191 "Trait"
- WHITESPACE@191..192 " "
- RENAME@192..196
- AS_KW@192..194 "as"
- WHITESPACE@194..195 " "
- UNDERSCORE@195..196 "_"
- SEMICOLON@196..197 ";"
- WHITESPACE@197..198 "\n"
+++ /dev/null
-use some::path as some_name;
-use some::{
- other::path as some_other_name,
- different::path as different_name,
- yet::another::path,
- running::out::of::synonyms::for_::different::*
-};
-use Trait as _;
-SOURCE_FILE@0..64
- STRUCT@0..63
+SOURCE_FILE@0..28
+ STRUCT@0..27
STRUCT_KW@0..6 "struct"
WHITESPACE@6..7 " "
NAME@7..8
IDENT@7..8 "S"
WHITESPACE@8..9 " "
- RECORD_FIELD_LIST@9..63
+ RECORD_FIELD_LIST@9..27
L_CURLY@9..10 "{"
- WHITESPACE@10..15 "\n "
- RECORD_FIELD@15..60
- ATTR@15..43
- POUND@15..16 "#"
- L_BRACK@16..17 "["
- META@17..42
- PATH@17..22
- PATH_SEGMENT@17..22
- NAME_REF@17..22
- IDENT@17..22 "serde"
- TOKEN_TREE@22..42
- L_PAREN@22..23 "("
- IDENT@23..27 "with"
- WHITESPACE@27..28 " "
- EQ@28..29 "="
- WHITESPACE@29..30 " "
- STRING@30..41 "\"url_serde\""
- R_PAREN@41..42 ")"
- R_BRACK@42..43 "]"
- WHITESPACE@43..48 "\n "
- VISIBILITY@48..51
- PUB_KW@48..51 "pub"
- WHITESPACE@51..52 " "
- NAME@52..55
- IDENT@52..55 "uri"
- COLON@55..56 ":"
- WHITESPACE@56..57 " "
- PATH_TYPE@57..60
- PATH@57..60
- PATH_SEGMENT@57..60
- NAME_REF@57..60
- IDENT@57..60 "Uri"
- COMMA@60..61 ","
- WHITESPACE@61..62 "\n"
- R_CURLY@62..63 "}"
- WHITESPACE@63..64 "\n"
+ WHITESPACE@10..11 " "
+ RECORD_FIELD@11..25
+ ATTR@11..18
+ POUND@11..12 "#"
+ L_BRACK@12..13 "["
+ META@13..17
+ PATH@13..17
+ PATH_SEGMENT@13..17
+ NAME_REF@13..17
+ IDENT@13..17 "attr"
+ R_BRACK@17..18 "]"
+ WHITESPACE@18..19 " "
+ NAME@19..20
+ IDENT@19..20 "f"
+ COLON@20..21 ":"
+ WHITESPACE@21..22 " "
+ PATH_TYPE@22..25
+ PATH@22..25
+ PATH_SEGMENT@22..25
+ NAME_REF@22..25
+ IDENT@22..25 "f32"
+ WHITESPACE@25..26 " "
+ R_CURLY@26..27 "}"
+ WHITESPACE@27..28 "\n"
-struct S {
- #[serde(with = "url_serde")]
- pub uri: Uri,
-}
+struct S { #[attr] f: f32 }
+++ /dev/null
-SOURCE_FILE@0..20
- IMPL@0..19
- IMPL_KW@0..4 "impl"
- WHITESPACE@4..5 " "
- BANG@5..6 "!"
- PATH_TYPE@6..10
- PATH@6..10
- PATH_SEGMENT@6..10
- NAME_REF@6..10
- IDENT@6..10 "Send"
- WHITESPACE@10..11 " "
- FOR_KW@11..14 "for"
- WHITESPACE@14..15 " "
- PATH_TYPE@15..16
- PATH@15..16
- PATH_SEGMENT@15..16
- NAME_REF@15..16
- IDENT@15..16 "X"
- WHITESPACE@16..17 " "
- ASSOC_ITEM_LIST@17..19
- L_CURLY@17..18 "{"
- R_CURLY@18..19 "}"
- WHITESPACE@19..20 "\n"
+++ /dev/null
-impl !Send for X {}
--- /dev/null
+SOURCE_FILE@0..20
+ IMPL@0..19
+ IMPL_KW@0..4 "impl"
+ WHITESPACE@4..5 " "
+ BANG@5..6 "!"
+ PATH_TYPE@6..10
+ PATH@6..10
+ PATH_SEGMENT@6..10
+ NAME_REF@6..10
+ IDENT@6..10 "Send"
+ WHITESPACE@10..11 " "
+ FOR_KW@11..14 "for"
+ WHITESPACE@14..15 " "
+ PATH_TYPE@15..16
+ PATH@15..16
+ PATH_SEGMENT@15..16
+ NAME_REF@15..16
+ IDENT@15..16 "S"
+ WHITESPACE@16..17 " "
+ ASSOC_ITEM_LIST@17..19
+ L_CURLY@17..18 "{"
+ R_CURLY@18..19 "}"
+ WHITESPACE@19..20 "\n"
--- /dev/null
+impl !Send for S {}
+++ /dev/null
-SOURCE_FILE@0..51
- UNION@0..12
- UNION_KW@0..5 "union"
- WHITESPACE@5..6 " "
- NAME@6..9
- IDENT@6..9 "Foo"
- WHITESPACE@9..10 " "
- RECORD_FIELD_LIST@10..12
- L_CURLY@10..11 "{"
- R_CURLY@11..12 "}"
- WHITESPACE@12..13 "\n"
- UNION@13..50
- UNION_KW@13..18 "union"
- WHITESPACE@18..19 " "
- NAME@19..22
- IDENT@19..22 "Foo"
- WHITESPACE@22..23 " "
- RECORD_FIELD_LIST@23..50
- L_CURLY@23..24 "{"
- WHITESPACE@24..29 "\n "
- RECORD_FIELD@29..35
- NAME@29..30
- IDENT@29..30 "a"
- COLON@30..31 ":"
- WHITESPACE@31..32 " "
- PATH_TYPE@32..35
- PATH@32..35
- PATH_SEGMENT@32..35
- NAME_REF@32..35
- IDENT@32..35 "i32"
- COMMA@35..36 ","
- WHITESPACE@36..41 "\n "
- RECORD_FIELD@41..47
- NAME@41..42
- IDENT@41..42 "b"
- COLON@42..43 ":"
- WHITESPACE@43..44 " "
- PATH_TYPE@44..47
- PATH@44..47
- PATH_SEGMENT@44..47
- NAME_REF@44..47
- IDENT@44..47 "f32"
- COMMA@47..48 ","
- WHITESPACE@48..49 "\n"
- R_CURLY@49..50 "}"
- WHITESPACE@50..51 "\n"
+++ /dev/null
-union Foo {}
-union Foo {
- a: i32,
- b: f32,
-}
+++ /dev/null
-SOURCE_FILE@0..37
- USE@0..18
- USE_KW@0..3 "use"
- WHITESPACE@3..4 " "
- USE_TREE@4..17
- PATH@4..9
- PATH_SEGMENT@4..9
- NAME_REF@4..9
- CRATE_KW@4..9 "crate"
- COLON2@9..11 "::"
- USE_TREE_LIST@11..17
- L_CURLY@11..12 "{"
- USE_TREE@12..16
- PATH@12..16
- PATH_SEGMENT@12..16
- NAME_REF@12..16
- IDENT@12..16 "Item"
- R_CURLY@16..17 "}"
- SEMICOLON@17..18 ";"
- WHITESPACE@18..19 "\n"
- USE@19..36
- USE_KW@19..22 "use"
- WHITESPACE@22..23 " "
- USE_TREE@23..35
- PATH@23..27
- PATH_SEGMENT@23..27
- NAME_REF@23..27
- SELF_KW@23..27 "self"
- COLON2@27..29 "::"
- USE_TREE_LIST@29..35
- L_CURLY@29..30 "{"
- USE_TREE@30..34
- PATH@30..34
- PATH_SEGMENT@30..34
- NAME_REF@30..34
- IDENT@30..34 "Item"
- R_CURLY@34..35 "}"
- SEMICOLON@35..36 ";"
- WHITESPACE@36..37 "\n"
+++ /dev/null
-use crate::{Item};
-use self::{Item};
--- /dev/null
+SOURCE_FILE@0..16
+ TYPE_ALIAS@0..15
+ TYPE_KW@0..4 "type"
+ WHITESPACE@4..5 " "
+ NAME@5..8
+ IDENT@5..8 "Foo"
+ WHITESPACE@8..9 " "
+ EQ@9..10 "="
+ WHITESPACE@10..11 " "
+ PATH_TYPE@11..14
+ PATH@11..14
+ PATH_SEGMENT@11..14
+ NAME_REF@11..14
+ IDENT@11..14 "Bar"
+ SEMICOLON@14..15 ";"
+ WHITESPACE@15..16 "\n"
--- /dev/null
+type Foo = Bar;
+++ /dev/null
-SOURCE_FILE@0..16
- TYPE_ALIAS@0..15
- TYPE_KW@0..4 "type"
- WHITESPACE@4..5 " "
- NAME@5..8
- IDENT@5..8 "Foo"
- WHITESPACE@8..9 " "
- EQ@9..10 "="
- WHITESPACE@10..11 " "
- PATH_TYPE@11..14
- PATH@11..14
- PATH_SEGMENT@11..14
- NAME_REF@11..14
- IDENT@11..14 "Bar"
- SEMICOLON@14..15 ";"
- WHITESPACE@15..16 "\n"
+++ /dev/null
-type Foo = Bar;
+++ /dev/null
-SOURCE_FILE@0..12
- IMPL@0..11
- IMPL_KW@0..4 "impl"
- WHITESPACE@4..5 " "
- PATH_TYPE@5..8
- PATH@5..8
- PATH_SEGMENT@5..8
- NAME_REF@5..8
- IDENT@5..8 "Foo"
- WHITESPACE@8..9 " "
- ASSOC_ITEM_LIST@9..11
- L_CURLY@9..10 "{"
- R_CURLY@10..11 "}"
- WHITESPACE@11..12 "\n"
+++ /dev/null
-impl Foo {}
--- /dev/null
+SOURCE_FILE@0..10
+ IMPL@0..9
+ IMPL_KW@0..4 "impl"
+ WHITESPACE@4..5 " "
+ PATH_TYPE@5..6
+ PATH@5..6
+ PATH_SEGMENT@5..6
+ NAME_REF@5..6
+ IDENT@5..6 "S"
+ WHITESPACE@6..7 " "
+ ASSOC_ITEM_LIST@7..9
+ L_CURLY@7..8 "{"
+ R_CURLY@8..9 "}"
+ WHITESPACE@9..10 "\n"
+++ /dev/null
-struct Foo;
-struct Foo {}
-struct Foo();
-struct Foo(String, usize);
-struct Foo {
- a: i32,
- b: f32,
-}
+++ /dev/null
-SOURCE_FILE@0..154
- USE@0..17
- USE_KW@0..3 "use"
- WHITESPACE@3..4 " "
- USE_TREE@4..16
- PATH@4..16
- PATH_SEGMENT@4..16
- COLON2@4..6 "::"
- NAME_REF@6..16
- IDENT@6..16 "crate_name"
- SEMICOLON@16..17 ";"
- WHITESPACE@17..18 " "
- USE@18..61
- COMMENT@18..45 "// Rust 2018 - All fl ..."
- WHITESPACE@45..46 "\n"
- USE_KW@46..49 "use"
- WHITESPACE@49..50 " "
- USE_TREE@50..60
- PATH@50..60
- PATH_SEGMENT@50..60
- NAME_REF@50..60
- IDENT@50..60 "crate_name"
- SEMICOLON@60..61 ";"
- WHITESPACE@61..62 " "
- USE@62..124
- COMMENT@62..91 "// Rust 2018 - Anchor ..."
- WHITESPACE@91..92 "\n"
- USE_KW@92..95 "use"
- WHITESPACE@95..96 " "
- USE_TREE@96..123
- PATH@96..123
- PATH_SEGMENT@96..123
- NAME_REF@96..123
- IDENT@96..123 "item_in_scope_or_crat ..."
- SEMICOLON@123..124 ";"
- WHITESPACE@124..125 " "
- COMMENT@125..153 "// Rust 2018 - Unifor ..."
- WHITESPACE@153..154 "\n"
+++ /dev/null
-use ::crate_name; // Rust 2018 - All flavours
-use crate_name; // Rust 2018 - Anchored paths
-use item_in_scope_or_crate_name; // Rust 2018 - Uniform Paths
-SOURCE_FILE@0..53
- STRUCT@0..33
+SOURCE_FILE@0..31
+ STRUCT@0..30
STRUCT_KW@0..6 "struct"
WHITESPACE@6..7 " "
- NAME@7..11
- IDENT@7..11 "Test"
- GENERIC_PARAM_LIST@11..14
- L_ANGLE@11..12 "<"
- TYPE_PARAM@12..13
- NAME@12..13
- IDENT@12..13 "T"
- R_ANGLE@13..14 ">"
- TUPLE_FIELD_LIST@14..17
- L_PAREN@14..15 "("
- TUPLE_FIELD@15..16
- PATH_TYPE@15..16
- PATH@15..16
- PATH_SEGMENT@15..16
- NAME_REF@15..16
- IDENT@15..16 "T"
- R_PAREN@16..17 ")"
- WHITESPACE@17..18 " "
- WHERE_CLAUSE@18..32
- WHERE_KW@18..23 "where"
- WHITESPACE@23..24 " "
- WHERE_PRED@24..32
- PATH_TYPE@24..25
- PATH@24..25
- PATH_SEGMENT@24..25
- NAME_REF@24..25
- IDENT@24..25 "T"
- COLON@25..26 ":"
- WHITESPACE@26..27 " "
- TYPE_BOUND_LIST@27..32
- TYPE_BOUND@27..32
- PATH_TYPE@27..32
- PATH@27..32
- PATH_SEGMENT@27..32
- NAME_REF@27..32
- IDENT@27..32 "Clone"
- SEMICOLON@32..33 ";"
- WHITESPACE@33..34 "\n"
- STRUCT@34..52
- STRUCT_KW@34..40 "struct"
- WHITESPACE@40..41 " "
- NAME@41..45
- IDENT@41..45 "Test"
- GENERIC_PARAM_LIST@45..48
- L_ANGLE@45..46 "<"
- TYPE_PARAM@46..47
- NAME@46..47
- IDENT@46..47 "T"
- R_ANGLE@47..48 ">"
- TUPLE_FIELD_LIST@48..51
- L_PAREN@48..49 "("
- TUPLE_FIELD@49..50
- PATH_TYPE@49..50
- PATH@49..50
- PATH_SEGMENT@49..50
- NAME_REF@49..50
- IDENT@49..50 "T"
- R_PAREN@50..51 ")"
- SEMICOLON@51..52 ";"
- WHITESPACE@52..53 "\n"
+ NAME@7..8
+ IDENT@7..8 "S"
+ GENERIC_PARAM_LIST@8..11
+ L_ANGLE@8..9 "<"
+ TYPE_PARAM@9..10
+ NAME@9..10
+ IDENT@9..10 "T"
+ R_ANGLE@10..11 ">"
+ TUPLE_FIELD_LIST@11..14
+ L_PAREN@11..12 "("
+ TUPLE_FIELD@12..13
+ PATH_TYPE@12..13
+ PATH@12..13
+ PATH_SEGMENT@12..13
+ NAME_REF@12..13
+ IDENT@12..13 "T"
+ R_PAREN@13..14 ")"
+ WHITESPACE@14..15 " "
+ WHERE_CLAUSE@15..29
+ WHERE_KW@15..20 "where"
+ WHITESPACE@20..21 " "
+ WHERE_PRED@21..29
+ PATH_TYPE@21..22
+ PATH@21..22
+ PATH_SEGMENT@21..22
+ NAME_REF@21..22
+ IDENT@21..22 "T"
+ COLON@22..23 ":"
+ WHITESPACE@23..24 " "
+ TYPE_BOUND_LIST@24..29
+ TYPE_BOUND@24..29
+ PATH_TYPE@24..29
+ PATH@24..29
+ PATH_SEGMENT@24..29
+ NAME_REF@24..29
+ IDENT@24..29 "Clone"
+ SEMICOLON@29..30 ";"
+ WHITESPACE@30..31 "\n"
-struct Test<T>(T) where T: Clone;
-struct Test<T>(T);
+struct S<T>(T) where T: Clone;
-SOURCE_FILE@0..60
- STRUCT@0..59
+SOURCE_FILE@0..24
+ STRUCT@0..23
STRUCT_KW@0..6 "struct"
WHITESPACE@6..7 " "
NAME@7..8
IDENT@7..8 "S"
WHITESPACE@8..9 " "
- TUPLE_FIELD_LIST@9..58
+ TUPLE_FIELD_LIST@9..22
L_PAREN@9..10 "("
- WHITESPACE@10..15 "\n "
- TUPLE_FIELD@15..55
- ATTR@15..43
- POUND@15..16 "#"
- L_BRACK@16..17 "["
- META@17..42
- PATH@17..22
- PATH_SEGMENT@17..22
- NAME_REF@17..22
- IDENT@17..22 "serde"
- TOKEN_TREE@22..42
- L_PAREN@22..23 "("
- IDENT@23..27 "with"
- WHITESPACE@27..28 " "
- EQ@28..29 "="
- WHITESPACE@29..30 " "
- STRING@30..41 "\"url_serde\""
- R_PAREN@41..42 ")"
- R_BRACK@42..43 "]"
- WHITESPACE@43..48 "\n "
- VISIBILITY@48..51
- PUB_KW@48..51 "pub"
- WHITESPACE@51..52 " "
- PATH_TYPE@52..55
- PATH@52..55
- PATH_SEGMENT@52..55
- NAME_REF@52..55
- IDENT@52..55 "Uri"
- COMMA@55..56 ","
- WHITESPACE@56..57 "\n"
- R_PAREN@57..58 ")"
- SEMICOLON@58..59 ";"
- WHITESPACE@59..60 "\n"
+ TUPLE_FIELD@10..21
+ ATTR@10..17
+ POUND@10..11 "#"
+ L_BRACK@11..12 "["
+ META@12..16
+ PATH@12..16
+ PATH_SEGMENT@12..16
+ NAME_REF@12..16
+ IDENT@12..16 "attr"
+ R_BRACK@16..17 "]"
+ WHITESPACE@17..18 " "
+ PATH_TYPE@18..21
+ PATH@18..21
+ PATH_SEGMENT@18..21
+ NAME_REF@18..21
+ IDENT@18..21 "f32"
+ R_PAREN@21..22 ")"
+ SEMICOLON@22..23 ";"
+ WHITESPACE@23..24 "\n"
-struct S (
- #[serde(with = "url_serde")]
- pub Uri,
-);
+struct S (#[attr] f32);
+++ /dev/null
-SOURCE_FILE@0..94
- ENUM@0..8
- ENUM_KW@0..4 "enum"
- WHITESPACE@4..5 " "
- NAME@5..6
- IDENT@5..6 "F"
- VARIANT_LIST@6..8
- L_CURLY@6..7 "{"
- R_CURLY@7..8 "}"
- WHITESPACE@8..9 "\n"
- IMPL@9..93
- IMPL_KW@9..13 "impl"
- WHITESPACE@13..14 " "
- PATH_TYPE@14..15
- PATH@14..15
- PATH_SEGMENT@14..15
- NAME_REF@14..15
- IDENT@14..15 "F"
- WHITESPACE@15..16 " "
- ASSOC_ITEM_LIST@16..93
- L_CURLY@16..17 "{"
- WHITESPACE@17..23 "\n "
- COMMENT@23..48 "//! This is a doc com ..."
- WHITESPACE@48..54 "\n "
- ATTR@54..91
- POUND@54..55 "#"
- BANG@55..56 "!"
- L_BRACK@56..57 "["
- META@57..90
- PATH@57..60
- PATH_SEGMENT@57..60
- NAME_REF@57..60
- IDENT@57..60 "doc"
- TOKEN_TREE@60..90
- L_PAREN@60..61 "("
- STRING@61..89 "\"This is also a doc c ..."
- R_PAREN@89..90 ")"
- R_BRACK@90..91 "]"
- WHITESPACE@91..92 "\n"
- R_CURLY@92..93 "}"
- WHITESPACE@93..94 "\n"
+++ /dev/null
-enum F{}
-impl F {
- //! This is a doc comment
- #![doc("This is also a doc comment")]
-}
+++ /dev/null
-SOURCE_FILE@0..64
- FN@0..63
- FN_KW@0..2 "fn"
- WHITESPACE@2..3 " "
- NAME@3..6
- IDENT@3..6 "foo"
- GENERIC_PARAM_LIST@6..49
- L_ANGLE@6..7 "<"
- LIFETIME_PARAM@7..29
- ATTR@7..26
- POUND@7..8 "#"
- L_BRACK@8..9 "["
- META@9..25
- PATH@9..15
- PATH_SEGMENT@9..15
- NAME_REF@9..15
- IDENT@9..15 "derive"
- TOKEN_TREE@15..25
- L_PAREN@15..16 "("
- IDENT@16..24 "Lifetime"
- R_PAREN@24..25 ")"
- R_BRACK@25..26 "]"
- WHITESPACE@26..27 " "
- LIFETIME@27..29
- LIFETIME_IDENT@27..29 "'a"
- COMMA@29..30 ","
- WHITESPACE@30..31 " "
- TYPE_PARAM@31..48
- ATTR@31..46
- POUND@31..32 "#"
- L_BRACK@32..33 "["
- META@33..45
- PATH@33..39
- PATH_SEGMENT@33..39
- NAME_REF@33..39
- IDENT@33..39 "derive"
- TOKEN_TREE@39..45
- L_PAREN@39..40 "("
- IDENT@40..44 "Type"
- R_PAREN@44..45 ")"
- R_BRACK@45..46 "]"
- WHITESPACE@46..47 " "
- NAME@47..48
- IDENT@47..48 "T"
- R_ANGLE@48..49 ">"
- PARAM_LIST@49..59
- L_PAREN@49..50 "("
- PARAM@50..58
- WILDCARD_PAT@50..51
- UNDERSCORE@50..51 "_"
- COLON@51..52 ":"
- WHITESPACE@52..53 " "
- REF_TYPE@53..58
- AMP@53..54 "&"
- LIFETIME@54..56
- LIFETIME_IDENT@54..56 "'a"
- WHITESPACE@56..57 " "
- PATH_TYPE@57..58
- PATH@57..58
- PATH_SEGMENT@57..58
- NAME_REF@57..58
- IDENT@57..58 "T"
- R_PAREN@58..59 ")"
- WHITESPACE@59..60 " "
- BLOCK_EXPR@60..63
- L_CURLY@60..61 "{"
- WHITESPACE@61..62 "\n"
- R_CURLY@62..63 "}"
- WHITESPACE@63..64 "\n"
+++ /dev/null
-fn foo<#[derive(Lifetime)] 'a, #[derive(Type)] T>(_: &'a T) {
-}
-SOURCE_FILE@0..50
- MACRO_DEF@0..28
+SOURCE_FILE@0..21
+ MACRO_DEF@0..20
MACRO_KW@0..5 "macro"
WHITESPACE@5..6 " "
NAME@6..7
IDENT@6..7 "m"
- WHITESPACE@7..8 " "
- TOKEN_TREE@8..28
- L_CURLY@8..9 "{"
- WHITESPACE@9..10 " "
- TOKEN_TREE@10..20
- L_PAREN@10..11 "("
- DOLLAR@11..12 "$"
- IDENT@12..13 "i"
- COLON@13..14 ":"
- IDENT@14..19 "ident"
- R_PAREN@19..20 ")"
- WHITESPACE@20..21 " "
- EQ@21..22 "="
- R_ANGLE@22..23 ">"
- WHITESPACE@23..24 " "
- TOKEN_TREE@24..26
- L_CURLY@24..25 "{"
- R_CURLY@25..26 "}"
- WHITESPACE@26..27 " "
- R_CURLY@27..28 "}"
- WHITESPACE@28..29 "\n"
- MACRO_DEF@29..49
- MACRO_KW@29..34 "macro"
- WHITESPACE@34..35 " "
- NAME@35..36
- IDENT@35..36 "m"
- TOKEN_TREE@36..49
- TOKEN_TREE@36..46
- L_PAREN@36..37 "("
- DOLLAR@37..38 "$"
- IDENT@38..39 "i"
- COLON@39..40 ":"
- IDENT@40..45 "ident"
- R_PAREN@45..46 ")"
- WHITESPACE@46..47 " "
- TOKEN_TREE@47..49
- L_CURLY@47..48 "{"
- R_CURLY@48..49 "}"
- WHITESPACE@49..50 "\n"
+ TOKEN_TREE@7..20
+ TOKEN_TREE@7..17
+ L_PAREN@7..8 "("
+ DOLLAR@8..9 "$"
+ IDENT@9..10 "i"
+ COLON@10..11 ":"
+ IDENT@11..16 "ident"
+ R_PAREN@16..17 ")"
+ WHITESPACE@17..18 " "
+ TOKEN_TREE@18..20
+ L_CURLY@18..19 "{"
+ R_CURLY@19..20 "}"
+ WHITESPACE@20..21 "\n"
-macro m { ($i:ident) => {} }
macro m($i:ident) {}
+++ /dev/null
-SOURCE_FILE@0..24
- MACRO_DEF@0..23
- VISIBILITY@0..3
- PUB_KW@0..3 "pub"
- WHITESPACE@3..4 " "
- MACRO_KW@4..9 "macro"
- WHITESPACE@9..10 " "
- NAME@10..11
- IDENT@10..11 "m"
- TOKEN_TREE@11..23
- TOKEN_TREE@11..20
- L_PAREN@11..12 "("
- DOLLAR@12..13 "$"
- COLON@13..14 ":"
- IDENT@14..19 "ident"
- R_PAREN@19..20 ")"
- WHITESPACE@20..21 " "
- TOKEN_TREE@21..23
- L_CURLY@21..22 "{"
- R_CURLY@22..23 "}"
- WHITESPACE@23..24 "\n"
+++ /dev/null
-pub macro m($:ident) {}
-SOURCE_FILE@0..83
+SOURCE_FILE@0..19
TRAIT@0..18
TRAIT_KW@0..5 "trait"
WHITESPACE@5..6 " "
R_ANGLE@16..17 ">"
SEMICOLON@17..18 ";"
WHITESPACE@18..19 "\n"
- TRAIT@19..51
- TRAIT_KW@19..24 "trait"
- WHITESPACE@24..25 " "
- NAME@25..26
- IDENT@25..26 "Z"
- GENERIC_PARAM_LIST@26..29
- L_ANGLE@26..27 "<"
- TYPE_PARAM@27..28
- NAME@27..28
- IDENT@27..28 "U"
- R_ANGLE@28..29 ">"
- WHITESPACE@29..30 " "
- EQ@30..31 "="
- WHITESPACE@31..32 " "
- TYPE_BOUND_LIST@32..36
- TYPE_BOUND@32..36
- PATH_TYPE@32..36
- PATH@32..36
- PATH_SEGMENT@32..36
- NAME_REF@32..33
- IDENT@32..33 "T"
- GENERIC_ARG_LIST@33..36
- L_ANGLE@33..34 "<"
- TYPE_ARG@34..35
- PATH_TYPE@34..35
- PATH@34..35
- PATH_SEGMENT@34..35
- NAME_REF@34..35
- IDENT@34..35 "U"
- R_ANGLE@35..36 ">"
- WHITESPACE@36..37 " "
- WHERE_CLAUSE@37..50
- WHERE_KW@37..42 "where"
- WHITESPACE@42..43 " "
- WHERE_PRED@43..50
- PATH_TYPE@43..44
- PATH@43..44
- PATH_SEGMENT@43..44
- NAME_REF@43..44
- IDENT@43..44 "U"
- COLON@44..45 ":"
- WHITESPACE@45..46 " "
- TYPE_BOUND_LIST@46..50
- TYPE_BOUND@46..50
- PATH_TYPE@46..50
- PATH@46..50
- PATH_SEGMENT@46..50
- NAME_REF@46..50
- IDENT@46..50 "Copy"
- SEMICOLON@50..51 ";"
- WHITESPACE@51..52 "\n"
- TRAIT@52..82
- TRAIT_KW@52..57 "trait"
- WHITESPACE@57..58 " "
- NAME@58..59
- IDENT@58..59 "Z"
- GENERIC_PARAM_LIST@59..62
- L_ANGLE@59..60 "<"
- TYPE_PARAM@60..61
- NAME@60..61
- IDENT@60..61 "U"
- R_ANGLE@61..62 ">"
- WHITESPACE@62..63 " "
- EQ@63..64 "="
- WHITESPACE@64..65 " "
- TYPE_BOUND_LIST@65..65
- WHERE_CLAUSE@65..81
- WHERE_KW@65..70 "where"
- WHITESPACE@70..71 " "
- WHERE_PRED@71..81
- PATH_TYPE@71..75
- PATH@71..75
- PATH_SEGMENT@71..75
- NAME_REF@71..75
- IDENT@71..75 "Self"
- COLON@75..76 ":"
- WHITESPACE@76..77 " "
- TYPE_BOUND_LIST@77..81
- TYPE_BOUND@77..81
- PATH_TYPE@77..81
- PATH@77..81
- PATH_SEGMENT@77..81
- NAME_REF@77..78
- IDENT@77..78 "T"
- GENERIC_ARG_LIST@78..81
- L_ANGLE@78..79 "<"
- TYPE_ARG@79..80
- PATH_TYPE@79..80
- PATH@79..80
- PATH_SEGMENT@79..80
- NAME_REF@79..80
- IDENT@79..80 "U"
- R_ANGLE@80..81 ">"
- SEMICOLON@81..82 ";"
- WHITESPACE@82..83 "\n"
trait Z<U> = T<U>;
-trait Z<U> = T<U> where U: Copy;
-trait Z<U> = where Self: T<U>;
+++ /dev/null
-SOURCE_FILE@0..16
- IMPL@0..15
- IMPL_KW@0..4 "impl"
- WHITESPACE@4..5 " "
- PATH_TYPE@5..6
- PATH@5..6
- PATH_SEGMENT@5..6
- NAME_REF@5..6
- IDENT@5..6 "T"
- WHITESPACE@6..7 " "
- FOR_KW@7..10 "for"
- WHITESPACE@10..11 " "
- PATH_TYPE@11..12
- PATH@11..12
- PATH_SEGMENT@11..12
- NAME_REF@11..12
- IDENT@11..12 "S"
- WHITESPACE@12..13 " "
- ASSOC_ITEM_LIST@13..15
- L_CURLY@13..14 "{"
- R_CURLY@14..15 "}"
- WHITESPACE@15..16 "\n"
+++ /dev/null
-impl T for S {}
+++ /dev/null
-SOURCE_FILE@0..11
- TRAIT@0..10
- TRAIT_KW@0..5 "trait"
- WHITESPACE@5..6 " "
- NAME@6..7
- IDENT@6..7 "T"
- WHITESPACE@7..8 " "
- ASSOC_ITEM_LIST@8..10
- L_CURLY@8..9 "{"
- R_CURLY@9..10 "}"
- WHITESPACE@10..11 "\n"
+++ /dev/null
-trait T {}
+++ /dev/null
-SOURCE_FILE@0..25
- IMPL@0..24
- IMPL_KW@0..4 "impl"
- WHITESPACE@4..5 " "
- CONST_KW@5..10 "const"
- WHITESPACE@10..11 " "
- PATH_TYPE@11..15
- PATH@11..15
- PATH_SEGMENT@11..15
- NAME_REF@11..15
- IDENT@11..15 "Send"
- WHITESPACE@15..16 " "
- FOR_KW@16..19 "for"
- WHITESPACE@19..20 " "
- PATH_TYPE@20..21
- PATH@20..21
- PATH_SEGMENT@20..21
- NAME_REF@20..21
- IDENT@20..21 "X"
- WHITESPACE@21..22 " "
- ASSOC_ITEM_LIST@22..24
- L_CURLY@22..23 "{"
- R_CURLY@23..24 "}"
- WHITESPACE@24..25 "\n"
+++ /dev/null
-impl const Send for X {}
--- /dev/null
+SOURCE_FILE@0..25
+ IMPL@0..24
+ IMPL_KW@0..4 "impl"
+ WHITESPACE@4..5 " "
+ CONST_KW@5..10 "const"
+ WHITESPACE@10..11 " "
+ PATH_TYPE@11..15
+ PATH@11..15
+ PATH_SEGMENT@11..15
+ NAME_REF@11..15
+ IDENT@11..15 "Send"
+ WHITESPACE@15..16 " "
+ FOR_KW@16..19 "for"
+ WHITESPACE@19..20 " "
+ PATH_TYPE@20..21
+ PATH@20..21
+ PATH_SEGMENT@20..21
+ NAME_REF@20..21
+ IDENT@20..21 "S"
+ WHITESPACE@21..22 " "
+ ASSOC_ITEM_LIST@22..24
+ L_CURLY@22..23 "{"
+ R_CURLY@23..24 "}"
+ WHITESPACE@24..25 "\n"
--- /dev/null
+impl const Send for S {}
+++ /dev/null
-SOURCE_FILE@0..19
- FN@0..18
- FN_KW@0..2 "fn"
- WHITESPACE@2..3 " "
- NAME@3..4
- IDENT@3..4 "f"
- PARAM_LIST@4..6
- L_PAREN@4..5 "("
- R_PAREN@5..6 ")"
- WHITESPACE@6..7 " "
- BLOCK_EXPR@7..18
- L_CURLY@7..8 "{"
- WHITESPACE@8..9 " "
- PATH_EXPR@9..16
- PATH@9..16
- PATH_SEGMENT@9..16
- NAME_REF@9..10
- IDENT@9..10 "S"
- GENERIC_ARG_LIST@10..16
- COLON2@10..12 "::"
- L_ANGLE@12..13 "<"
- CONST_ARG@13..15
- PREFIX_EXPR@13..15
- MINUS@13..14 "-"
- LITERAL@14..15
- INT_NUMBER@14..15 "1"
- R_ANGLE@15..16 ">"
- WHITESPACE@16..17 " "
- R_CURLY@17..18 "}"
- WHITESPACE@18..19 "\n"
+++ /dev/null
-fn f() { S::<-1> }
-SOURCE_FILE@0..96
+SOURCE_FILE@0..29
STRUCT@0..28
STRUCT_KW@0..6 "struct"
WHITESPACE@6..7 " "
R_ANGLE@26..27 ">"
SEMICOLON@27..28 ";"
WHITESPACE@28..29 "\n"
- STRUCT@29..57
- STRUCT_KW@29..35 "struct"
- WHITESPACE@35..36 " "
- NAME@36..37
- IDENT@36..37 "B"
- GENERIC_PARAM_LIST@37..56
- L_ANGLE@37..38 "<"
- CONST_PARAM@38..55
- CONST_KW@38..43 "const"
- WHITESPACE@43..44 " "
- NAME@44..45
- IDENT@44..45 "N"
- COLON@45..46 ":"
- WHITESPACE@46..47 " "
- PATH_TYPE@47..50
- PATH@47..50
- PATH_SEGMENT@47..50
- NAME_REF@47..50
- IDENT@47..50 "i32"
- WHITESPACE@50..51 " "
- EQ@51..52 "="
- WHITESPACE@52..53 " "
- CONST_ARG@53..55
- BLOCK_EXPR@53..55
- L_CURLY@53..54 "{"
- R_CURLY@54..55 "}"
- R_ANGLE@55..56 ">"
- SEMICOLON@56..57 ";"
- WHITESPACE@57..58 "\n"
- STRUCT@58..95
- STRUCT_KW@58..64 "struct"
- WHITESPACE@64..65 " "
- NAME@65..66
- IDENT@65..66 "C"
- GENERIC_PARAM_LIST@66..94
- L_ANGLE@66..67 "<"
- CONST_PARAM@67..93
- CONST_KW@67..72 "const"
- WHITESPACE@72..73 " "
- NAME@73..74
- IDENT@73..74 "N"
- COLON@74..75 ":"
- WHITESPACE@75..76 " "
- PATH_TYPE@76..79
- PATH@76..79
- PATH_SEGMENT@76..79
- NAME_REF@76..79
- IDENT@76..79 "i32"
- WHITESPACE@79..80 " "
- EQ@80..81 "="
- WHITESPACE@81..82 " "
- CONST_ARG@82..93
- PATH_EXPR@82..93
- PATH@82..93
- PATH@82..86
- PATH_SEGMENT@82..86
- NAME_REF@82..86
- IDENT@82..86 "some"
- COLON2@86..88 "::"
- PATH_SEGMENT@88..93
- NAME_REF@88..93
- IDENT@88..93 "CONST"
- R_ANGLE@93..94 ">"
- SEMICOLON@94..95 ";"
- WHITESPACE@95..96 "\n"
struct A<const N: i32 = -1>;
-struct B<const N: i32 = {}>;
-struct C<const N: i32 = some::CONST>;
--- /dev/null
+SOURCE_FILE@0..25
+ EXTERN_CRATE@0..24
+ EXTERN_KW@0..6 "extern"
+ WHITESPACE@6..7 " "
+ CRATE_KW@7..12 "crate"
+ WHITESPACE@12..13 " "
+ NAME_REF@13..16
+ IDENT@13..16 "foo"
+ WHITESPACE@16..17 " "
+ RENAME@17..23
+ AS_KW@17..19 "as"
+ WHITESPACE@19..20 " "
+ NAME@20..23
+ IDENT@20..23 "bar"
+ SEMICOLON@23..24 ";"
+ WHITESPACE@24..25 "\n"
--- /dev/null
+extern crate foo as bar;
--- /dev/null
+SOURCE_FILE@0..19
+ EXTERN_CRATE@0..18
+ EXTERN_KW@0..6 "extern"
+ WHITESPACE@6..7 " "
+ CRATE_KW@7..12 "crate"
+ WHITESPACE@12..13 " "
+ NAME_REF@13..17
+ SELF_KW@13..17 "self"
+ SEMICOLON@17..18 ";"
+ WHITESPACE@18..19 "\n"
--- /dev/null
+extern crate self;
--- /dev/null
+SOURCE_FILE@0..7
+ MODULE@0..6
+ MOD_KW@0..3 "mod"
+ WHITESPACE@3..4 " "
+ NAME@4..5
+ IDENT@4..5 "a"
+ SEMICOLON@5..6 ";"
+ WHITESPACE@6..7 "\n"
--- /dev/null
+SOURCE_FILE@0..10
+ MODULE@0..9
+ MOD_KW@0..3 "mod"
+ WHITESPACE@3..4 " "
+ NAME@4..5
+ IDENT@4..5 "b"
+ WHITESPACE@5..6 " "
+ ITEM_LIST@6..9
+ L_CURLY@6..7 "{"
+ WHITESPACE@7..8 " "
+ R_CURLY@8..9 "}"
+ WHITESPACE@9..10 "\n"
--- /dev/null
+SOURCE_FILE@0..25
+ STRUCT@0..24
+ STRUCT_KW@0..6 "struct"
+ WHITESPACE@6..7 " "
+ NAME@7..8
+ IDENT@7..8 "S"
+ TUPLE_FIELD_LIST@8..23
+ L_PAREN@8..9 "("
+ TUPLE_FIELD@9..15
+ PATH_TYPE@9..15
+ PATH@9..15
+ PATH_SEGMENT@9..15
+ NAME_REF@9..15
+ IDENT@9..15 "String"
+ COMMA@15..16 ","
+ WHITESPACE@16..17 " "
+ TUPLE_FIELD@17..22
+ PATH_TYPE@17..22
+ PATH@17..22
+ PATH_SEGMENT@17..22
+ NAME_REF@17..22
+ IDENT@17..22 "usize"
+ R_PAREN@22..23 ")"
+ SEMICOLON@23..24 ";"
+ WHITESPACE@24..25 "\n"
--- /dev/null
+struct S(String, usize);
--- /dev/null
+SOURCE_FILE@0..12
+ STRUCT@0..11
+ STRUCT_KW@0..6 "struct"
+ WHITESPACE@6..7 " "
+ NAME@7..8
+ IDENT@7..8 "S"
+ WHITESPACE@8..9 " "
+ RECORD_FIELD_LIST@9..11
+ L_CURLY@9..10 "{"
+ R_CURLY@10..11 "}"
+ WHITESPACE@11..12 "\n"
--- /dev/null
+struct S {}
--- /dev/null
+SOURCE_FILE@0..19
+ CONST@0..18
+ CONST_KW@0..5 "const"
+ WHITESPACE@5..6 " "
+ NAME@6..7
+ IDENT@6..7 "C"
+ COLON@7..8 ":"
+ WHITESPACE@8..9 " "
+ PATH_TYPE@9..12
+ PATH@9..12
+ PATH_SEGMENT@9..12
+ NAME_REF@9..12
+ IDENT@9..12 "u32"
+ WHITESPACE@12..13 " "
+ EQ@13..14 "="
+ WHITESPACE@14..15 " "
+ LITERAL@15..17
+ INT_NUMBER@15..17 "92"
+ SEMICOLON@17..18 ";"
+ WHITESPACE@18..19 "\n"
--- /dev/null
+const C: u32 = 92;
--- /dev/null
+SOURCE_FILE@0..28
+ STRUCT@0..27
+ STRUCT_KW@0..6 "struct"
+ WHITESPACE@6..7 " "
+ NAME@7..8
+ IDENT@7..8 "S"
+ WHITESPACE@8..9 " "
+ RECORD_FIELD_LIST@9..27
+ L_CURLY@9..10 "{"
+ WHITESPACE@10..11 " "
+ RECORD_FIELD@11..17
+ NAME@11..12
+ IDENT@11..12 "a"
+ COLON@12..13 ":"
+ WHITESPACE@13..14 " "
+ PATH_TYPE@14..17
+ PATH@14..17
+ PATH_SEGMENT@14..17
+ NAME_REF@14..17
+ IDENT@14..17 "i32"
+ COMMA@17..18 ","
+ WHITESPACE@18..19 " "
+ RECORD_FIELD@19..25
+ NAME@19..20
+ IDENT@19..20 "b"
+ COLON@20..21 ":"
+ WHITESPACE@21..22 " "
+ PATH_TYPE@22..25
+ PATH@22..25
+ PATH_SEGMENT@22..25
+ NAME_REF@22..25
+ IDENT@22..25 "f32"
+ WHITESPACE@25..26 " "
+ R_CURLY@26..27 "}"
+ WHITESPACE@27..28 "\n"
--- /dev/null
+struct S { a: i32, b: f32 }
--- /dev/null
+SOURCE_FILE@0..18
+ CONST@0..17
+ CONST_KW@0..5 "const"
+ WHITESPACE@5..6 " "
+ UNDERSCORE@6..7 "_"
+ COLON@7..8 ":"
+ WHITESPACE@8..9 " "
+ PATH_TYPE@9..12
+ PATH@9..12
+ PATH_SEGMENT@9..12
+ NAME_REF@9..12
+ IDENT@9..12 "u32"
+ WHITESPACE@12..13 " "
+ EQ@13..14 "="
+ WHITESPACE@14..15 " "
+ LITERAL@15..16
+ INT_NUMBER@15..16 "0"
+ SEMICOLON@16..17 ";"
+ WHITESPACE@17..18 "\n"
--- /dev/null
+const _: u32 = 0;
--- /dev/null
+SOURCE_FILE@0..29
+ MACRO_DEF@0..28
+ MACRO_KW@0..5 "macro"
+ WHITESPACE@5..6 " "
+ NAME@6..7
+ IDENT@6..7 "m"
+ WHITESPACE@7..8 " "
+ TOKEN_TREE@8..28
+ L_CURLY@8..9 "{"
+ WHITESPACE@9..10 " "
+ TOKEN_TREE@10..20
+ L_PAREN@10..11 "("
+ DOLLAR@11..12 "$"
+ IDENT@12..13 "i"
+ COLON@13..14 ":"
+ IDENT@14..19 "ident"
+ R_PAREN@19..20 ")"
+ WHITESPACE@20..21 " "
+ EQ@21..22 "="
+ R_ANGLE@22..23 ">"
+ WHITESPACE@23..24 " "
+ TOKEN_TREE@24..26
+ L_CURLY@24..25 "{"
+ R_CURLY@25..26 "}"
+ WHITESPACE@26..27 " "
+ R_CURLY@27..28 "}"
+ WHITESPACE@28..29 "\n"
--- /dev/null
+macro m { ($i:ident) => {} }
--- /dev/null
+SOURCE_FILE@0..28
+ STRUCT@0..27
+ STRUCT_KW@0..6 "struct"
+ WHITESPACE@6..7 " "
+ NAME@7..8
+ IDENT@7..8 "U"
+ WHITESPACE@8..9 " "
+ RECORD_FIELD_LIST@9..27
+ L_CURLY@9..10 "{"
+ WHITESPACE@10..11 " "
+ RECORD_FIELD@11..17
+ NAME@11..12
+ IDENT@11..12 "i"
+ COLON@12..13 ":"
+ WHITESPACE@13..14 " "
+ PATH_TYPE@14..17
+ PATH@14..17
+ PATH_SEGMENT@14..17
+ NAME_REF@14..17
+ IDENT@14..17 "i32"
+ COMMA@17..18 ","
+ WHITESPACE@18..19 " "
+ RECORD_FIELD@19..25
+ NAME@19..20
+ IDENT@19..20 "f"
+ COLON@20..21 ":"
+ WHITESPACE@21..22 " "
+ PATH_TYPE@22..25
+ PATH@22..25
+ PATH_SEGMENT@22..25
+ NAME_REF@22..25
+ IDENT@22..25 "f32"
+ WHITESPACE@25..26 " "
+ R_CURLY@26..27 "}"
+ WHITESPACE@27..28 "\n"
--- /dev/null
+struct U { i: i32, f: f32 }
--- /dev/null
+SOURCE_FILE@0..31
+ TRAIT@0..30
+ TRAIT_KW@0..5 "trait"
+ WHITESPACE@5..6 " "
+ NAME@6..7
+ IDENT@6..7 "X"
+ GENERIC_PARAM_LIST@7..27
+ L_ANGLE@7..8 "<"
+ TYPE_PARAM@8..26
+ NAME@8..9
+ IDENT@8..9 "U"
+ COLON@9..10 ":"
+ WHITESPACE@10..11 " "
+ TYPE_BOUND_LIST@11..26
+ TYPE_BOUND@11..16
+ PATH_TYPE@11..16
+ PATH@11..16
+ PATH_SEGMENT@11..16
+ NAME_REF@11..16
+ IDENT@11..16 "Debug"
+ WHITESPACE@16..17 " "
+ PLUS@17..18 "+"
+ WHITESPACE@18..19 " "
+ TYPE_BOUND@19..26
+ PATH_TYPE@19..26
+ PATH@19..26
+ PATH_SEGMENT@19..26
+ NAME_REF@19..26
+ IDENT@19..26 "Display"
+ R_ANGLE@26..27 ">"
+ WHITESPACE@27..28 " "
+ ASSOC_ITEM_LIST@28..30
+ L_CURLY@28..29 "{"
+ R_CURLY@29..30 "}"
+ WHITESPACE@30..31 "\n"
--- /dev/null
+trait X<U: Debug + Display> {}
--- /dev/null
+SOURCE_FILE@0..10
+ STRUCT@0..9
+ STRUCT_KW@0..6 "struct"
+ WHITESPACE@6..7 " "
+ NAME@7..8
+ IDENT@7..8 "S"
+ SEMICOLON@8..9 ";"
+ WHITESPACE@9..10 "\n"
--- /dev/null
+SOURCE_FILE@0..21
+ USE@0..6
+ USE_KW@0..3 "use"
+ WHITESPACE@3..4 " "
+ USE_TREE@4..5
+ STAR@4..5 "*"
+ SEMICOLON@5..6 ";"
+ WHITESPACE@6..7 "\n"
+ USE@7..20
+ USE_KW@7..10 "use"
+ WHITESPACE@10..11 " "
+ USE_TREE@11..19
+ PATH@11..14
+ PATH_SEGMENT@11..14
+ NAME_REF@11..14
+ IDENT@11..14 "std"
+ COLON2@14..16 "::"
+ USE_TREE_LIST@16..19
+ L_CURLY@16..17 "{"
+ USE_TREE@17..18
+ STAR@17..18 "*"
+ R_CURLY@18..19 "}"
+ SEMICOLON@19..20 ";"
+ WHITESPACE@20..21 "\n"
--- /dev/null
+use *;
+use std::{*};
--- /dev/null
+SOURCE_FILE@0..25
+ TRAIT@0..24
+ TRAIT_KW@0..5 "trait"
+ WHITESPACE@5..6 " "
+ NAME@6..7
+ IDENT@6..7 "T"
+ COLON@7..8 ":"
+ WHITESPACE@8..9 " "
+ TYPE_BOUND_LIST@9..21
+ TYPE_BOUND@9..13
+ PATH_TYPE@9..13
+ PATH@9..13
+ PATH_SEGMENT@9..13
+ NAME_REF@9..13
+ IDENT@9..13 "Hash"
+ WHITESPACE@13..14 " "
+ PLUS@14..15 "+"
+ WHITESPACE@15..16 " "
+ TYPE_BOUND@16..21
+ PATH_TYPE@16..21
+ PATH@16..21
+ PATH_SEGMENT@16..21
+ NAME_REF@16..21
+ IDENT@16..21 "Clone"
+ WHITESPACE@21..22 " "
+ ASSOC_ITEM_LIST@22..24
+ L_CURLY@22..23 "{"
+ R_CURLY@23..24 "}"
+ WHITESPACE@24..25 "\n"
--- /dev/null
+trait T: Hash + Clone {}
--- /dev/null
+SOURCE_FILE@0..28
+ TRAIT@0..27
+ TRAIT_KW@0..5 "trait"
+ WHITESPACE@5..6 " "
+ NAME@6..7
+ IDENT@6..7 "T"
+ WHITESPACE@7..8 " "
+ WHERE_CLAUSE@8..24
+ WHERE_KW@8..13 "where"
+ WHITESPACE@13..14 " "
+ WHERE_PRED@14..24
+ PATH_TYPE@14..18
+ PATH@14..18
+ PATH_SEGMENT@14..18
+ NAME_REF@14..18
+ IDENT@14..18 "Self"
+ COLON@18..19 ":"
+ WHITESPACE@19..20 " "
+ TYPE_BOUND_LIST@20..24
+ TYPE_BOUND@20..24
+ PATH_TYPE@20..24
+ PATH@20..24
+ PATH_SEGMENT@20..24
+ NAME_REF@20..24
+ IDENT@20..24 "Copy"
+ WHITESPACE@24..25 " "
+ ASSOC_ITEM_LIST@25..27
+ L_CURLY@25..26 "{"
+ R_CURLY@26..27 "}"
+ WHITESPACE@27..28 "\n"
--- /dev/null
+trait T where Self: Copy {}
--- /dev/null
+SOURCE_FILE@0..35
+ USE@0..18
+ USE_KW@0..3 "use"
+ WHITESPACE@3..4 " "
+ USE_TREE@4..17
+ PATH@4..7
+ PATH_SEGMENT@4..7
+ NAME_REF@4..7
+ IDENT@4..7 "std"
+ WHITESPACE@7..8 " "
+ RENAME@8..17
+ AS_KW@8..10 "as"
+ WHITESPACE@10..11 " "
+ NAME@11..17
+ IDENT@11..17 "stdlib"
+ SEMICOLON@17..18 ";"
+ WHITESPACE@18..19 "\n"
+ USE@19..34
+ USE_KW@19..22 "use"
+ WHITESPACE@22..23 " "
+ USE_TREE@23..33
+ PATH@23..28
+ PATH_SEGMENT@23..28
+ NAME_REF@23..28
+ IDENT@23..28 "Trait"
+ WHITESPACE@28..29 " "
+ RENAME@29..33
+ AS_KW@29..31 "as"
+ WHITESPACE@31..32 " "
+ UNDERSCORE@32..33 "_"
+ SEMICOLON@33..34 ";"
+ WHITESPACE@34..35 "\n"
--- /dev/null
+use std as stdlib;
+use Trait as _;
--- /dev/null
+SOURCE_FILE@0..20
+ IMPL@0..19
+ IMPL_KW@0..4 "impl"
+ WHITESPACE@4..5 " "
+ PATH_TYPE@5..6
+ PATH@5..6
+ PATH_SEGMENT@5..6
+ NAME_REF@5..6
+ IDENT@5..6 "S"
+ WHITESPACE@6..7 " "
+ ASSOC_ITEM_LIST@7..19
+ L_CURLY@7..8 "{"
+ WHITESPACE@8..9 " "
+ ATTR@9..17
+ POUND@9..10 "#"
+ BANG@10..11 "!"
+ L_BRACK@11..12 "["
+ META@12..16
+ PATH@12..16
+ PATH_SEGMENT@12..16
+ NAME_REF@12..16
+ IDENT@12..16 "attr"
+ R_BRACK@16..17 "]"
+ WHITESPACE@17..18 " "
+ R_CURLY@18..19 "}"
+ WHITESPACE@19..20 "\n"
--- /dev/null
+impl S { #![attr] }
--- /dev/null
+SOURCE_FILE@0..64
+ TRAIT@0..32
+ TRAIT_KW@0..5 "trait"
+ WHITESPACE@5..6 " "
+ NAME@6..7
+ IDENT@6..7 "Z"
+ GENERIC_PARAM_LIST@7..10
+ L_ANGLE@7..8 "<"
+ TYPE_PARAM@8..9
+ NAME@8..9
+ IDENT@8..9 "U"
+ R_ANGLE@9..10 ">"
+ WHITESPACE@10..11 " "
+ EQ@11..12 "="
+ WHITESPACE@12..13 " "
+ TYPE_BOUND_LIST@13..17
+ TYPE_BOUND@13..17
+ PATH_TYPE@13..17
+ PATH@13..17
+ PATH_SEGMENT@13..17
+ NAME_REF@13..14
+ IDENT@13..14 "T"
+ GENERIC_ARG_LIST@14..17
+ L_ANGLE@14..15 "<"
+ TYPE_ARG@15..16
+ PATH_TYPE@15..16
+ PATH@15..16
+ PATH_SEGMENT@15..16
+ NAME_REF@15..16
+ IDENT@15..16 "U"
+ R_ANGLE@16..17 ">"
+ WHITESPACE@17..18 " "
+ WHERE_CLAUSE@18..31
+ WHERE_KW@18..23 "where"
+ WHITESPACE@23..24 " "
+ WHERE_PRED@24..31
+ PATH_TYPE@24..25
+ PATH@24..25
+ PATH_SEGMENT@24..25
+ NAME_REF@24..25
+ IDENT@24..25 "U"
+ COLON@25..26 ":"
+ WHITESPACE@26..27 " "
+ TYPE_BOUND_LIST@27..31
+ TYPE_BOUND@27..31
+ PATH_TYPE@27..31
+ PATH@27..31
+ PATH_SEGMENT@27..31
+ NAME_REF@27..31
+ IDENT@27..31 "Copy"
+ SEMICOLON@31..32 ";"
+ WHITESPACE@32..33 "\n"
+ TRAIT@33..63
+ TRAIT_KW@33..38 "trait"
+ WHITESPACE@38..39 " "
+ NAME@39..40
+ IDENT@39..40 "Z"
+ GENERIC_PARAM_LIST@40..43
+ L_ANGLE@40..41 "<"
+ TYPE_PARAM@41..42
+ NAME@41..42
+ IDENT@41..42 "U"
+ R_ANGLE@42..43 ">"
+ WHITESPACE@43..44 " "
+ EQ@44..45 "="
+ WHITESPACE@45..46 " "
+ TYPE_BOUND_LIST@46..46
+ WHERE_CLAUSE@46..62
+ WHERE_KW@46..51 "where"
+ WHITESPACE@51..52 " "
+ WHERE_PRED@52..62
+ PATH_TYPE@52..56
+ PATH@52..56
+ PATH_SEGMENT@52..56
+ NAME_REF@52..56
+ IDENT@52..56 "Self"
+ COLON@56..57 ":"
+ WHITESPACE@57..58 " "
+ TYPE_BOUND_LIST@58..62
+ TYPE_BOUND@58..62
+ PATH_TYPE@58..62
+ PATH@58..62
+ PATH_SEGMENT@58..62
+ NAME_REF@58..59
+ IDENT@58..59 "T"
+ GENERIC_ARG_LIST@59..62
+ L_ANGLE@59..60 "<"
+ TYPE_ARG@60..61
+ PATH_TYPE@60..61
+ PATH@60..61
+ PATH_SEGMENT@60..61
+ NAME_REF@60..61
+ IDENT@60..61 "U"
+ R_ANGLE@61..62 ">"
+ SEMICOLON@62..63 ";"
+ WHITESPACE@63..64 "\n"
--- /dev/null
+trait Z<U> = T<U> where U: Copy;
+trait Z<U> = where Self: T<U>;
--- /dev/null
+SOURCE_FILE@0..32
+ USE@0..31
+ USE_KW@0..3 "use"
+ WHITESPACE@3..4 " "
+ USE_TREE@4..30
+ PATH@4..15
+ PATH@4..9
+ PATH_SEGMENT@4..9
+ NAME_REF@4..9
+ IDENT@4..9 "outer"
+ COLON2@9..11 "::"
+ PATH_SEGMENT@11..15
+ NAME_REF@11..15
+ IDENT@11..15 "tree"
+ COLON2@15..17 "::"
+ USE_TREE_LIST@17..30
+ L_CURLY@17..18 "{"
+ USE_TREE@18..29
+ PATH@18..29
+ PATH@18..23
+ PATH_SEGMENT@18..23
+ NAME_REF@18..23
+ IDENT@18..23 "inner"
+ COLON2@23..25 "::"
+ PATH_SEGMENT@25..29
+ NAME_REF@25..29
+ IDENT@25..29 "tree"
+ R_CURLY@29..30 "}"
+ SEMICOLON@30..31 ";"
+ WHITESPACE@31..32 "\n"
--- /dev/null
+use outer::tree::{inner::tree};
--- /dev/null
+SOURCE_FILE@0..75
+ USE@0..10
+ USE_KW@0..3 "use"
+ WHITESPACE@3..4 " "
+ USE_TREE@4..9
+ PATH@4..9
+ PATH_SEGMENT@4..9
+ COLON2@4..6 "::"
+ NAME_REF@6..9
+ IDENT@6..9 "std"
+ SEMICOLON@9..10 ";"
+ WHITESPACE@10..11 "\n"
+ USE@11..32
+ USE_KW@11..14 "use"
+ WHITESPACE@14..15 " "
+ USE_TREE@15..31
+ PATH@15..31
+ PATH@15..18
+ PATH_SEGMENT@15..18
+ NAME_REF@15..18
+ IDENT@15..18 "std"
+ COLON2@18..20 "::"
+ PATH_SEGMENT@20..31
+ NAME_REF@20..31
+ IDENT@20..31 "collections"
+ SEMICOLON@31..32 ";"
+ WHITESPACE@32..34 "\n\n"
+ USE@34..46
+ USE_KW@34..37 "use"
+ WHITESPACE@37..38 " "
+ USE_TREE@38..45
+ PATH@38..45
+ PATH@38..42
+ PATH_SEGMENT@38..42
+ NAME_REF@38..42
+ SELF_KW@38..42 "self"
+ COLON2@42..44 "::"
+ PATH_SEGMENT@44..45
+ NAME_REF@44..45
+ IDENT@44..45 "m"
+ SEMICOLON@45..46 ";"
+ WHITESPACE@46..47 "\n"
+ USE@47..60
+ USE_KW@47..50 "use"
+ WHITESPACE@50..51 " "
+ USE_TREE@51..59
+ PATH@51..59
+ PATH@51..56
+ PATH_SEGMENT@51..56
+ NAME_REF@51..56
+ SUPER_KW@51..56 "super"
+ COLON2@56..58 "::"
+ PATH_SEGMENT@58..59
+ NAME_REF@58..59
+ IDENT@58..59 "m"
+ SEMICOLON@59..60 ";"
+ WHITESPACE@60..61 "\n"
+ USE@61..74
+ USE_KW@61..64 "use"
+ WHITESPACE@64..65 " "
+ USE_TREE@65..73
+ PATH@65..73
+ PATH@65..70
+ PATH_SEGMENT@65..70
+ NAME_REF@65..70
+ CRATE_KW@65..70 "crate"
+ COLON2@70..72 "::"
+ PATH_SEGMENT@72..73
+ NAME_REF@72..73
+ IDENT@72..73 "m"
+ SEMICOLON@73..74 ";"
+ WHITESPACE@74..75 "\n"
--- /dev/null
+use ::std;
+use std::collections;
+
+use self::m;
+use super::m;
+use crate::m;
--- /dev/null
+SOURCE_FILE@0..24
+ USE@0..23
+ USE_KW@0..3 "use"
+ WHITESPACE@3..4 " "
+ USE_TREE@4..22
+ PATH@4..7
+ PATH_SEGMENT@4..7
+ NAME_REF@4..7
+ IDENT@4..7 "std"
+ COLON2@7..9 "::"
+ USE_TREE_LIST@9..22
+ L_CURLY@9..10 "{"
+ USE_TREE@10..21
+ PATH@10..21
+ PATH_SEGMENT@10..21
+ NAME_REF@10..21
+ IDENT@10..21 "collections"
+ R_CURLY@21..22 "}"
+ SEMICOLON@22..23 ";"
+ WHITESPACE@23..24 "\n"
--- /dev/null
+use std::{collections};
--- /dev/null
+SOURCE_FILE@0..25
+ USE@0..8
+ USE_KW@0..3 "use"
+ WHITESPACE@3..4 " "
+ USE_TREE@4..7
+ COLON2@4..6 "::"
+ STAR@6..7 "*"
+ SEMICOLON@7..8 ";"
+ WHITESPACE@8..9 "\n"
+ USE@9..24
+ USE_KW@9..12 "use"
+ WHITESPACE@12..13 " "
+ USE_TREE@13..23
+ PATH@13..16
+ PATH_SEGMENT@13..16
+ NAME_REF@13..16
+ IDENT@13..16 "std"
+ COLON2@16..18 "::"
+ USE_TREE_LIST@18..23
+ L_CURLY@18..19 "{"
+ USE_TREE@19..22
+ COLON2@19..21 "::"
+ STAR@21..22 "*"
+ R_CURLY@22..23 "}"
+ SEMICOLON@23..24 ";"
+ WHITESPACE@24..25 "\n"
--- /dev/null
+use ::*;
+use std::{::*};
--- /dev/null
+SOURCE_FILE@0..12
+ USE@0..11
+ USE_KW@0..3 "use"
+ WHITESPACE@3..4 " "
+ USE_TREE@4..10
+ PATH@4..7
+ PATH_SEGMENT@4..7
+ NAME_REF@4..7
+ IDENT@4..7 "std"
+ COLON2@7..9 "::"
+ STAR@9..10 "*"
+ SEMICOLON@10..11 ";"
+ WHITESPACE@11..12 "\n"
--- /dev/null
+use std::*;
--- /dev/null
+SOURCE_FILE@0..40
+ FN@0..39
+ FN_KW@0..2 "fn"
+ WHITESPACE@2..3 " "
+ NAME@3..6
+ IDENT@3..6 "foo"
+ GENERIC_PARAM_LIST@6..34
+ L_ANGLE@6..7 "<"
+ LIFETIME_PARAM@7..20
+ ATTR@7..17
+ POUND@7..8 "#"
+ L_BRACK@8..9 "["
+ META@9..16
+ PATH@9..16
+ PATH_SEGMENT@9..16
+ NAME_REF@9..16
+ IDENT@9..16 "lt_attr"
+ R_BRACK@16..17 "]"
+ WHITESPACE@17..18 " "
+ LIFETIME@18..20
+ LIFETIME_IDENT@18..20 "'a"
+ COMMA@20..21 ","
+ WHITESPACE@21..22 " "
+ TYPE_PARAM@22..33
+ ATTR@22..31
+ POUND@22..23 "#"
+ L_BRACK@23..24 "["
+ META@24..30
+ PATH@24..30
+ PATH_SEGMENT@24..30
+ NAME_REF@24..30
+ IDENT@24..30 "t_attr"
+ R_BRACK@30..31 "]"
+ WHITESPACE@31..32 " "
+ NAME@32..33
+ IDENT@32..33 "T"
+ R_ANGLE@33..34 ">"
+ PARAM_LIST@34..36
+ L_PAREN@34..35 "("
+ R_PAREN@35..36 ")"
+ WHITESPACE@36..37 " "
+ BLOCK_EXPR@37..39
+ L_CURLY@37..38 "{"
+ R_CURLY@38..39 "}"
+ WHITESPACE@39..40 "\n"
--- /dev/null
+fn foo<#[lt_attr] 'a, #[t_attr] T>() {}
--- /dev/null
+SOURCE_FILE@0..22
+ USE@0..21
+ USE_KW@0..3 "use"
+ WHITESPACE@3..4 " "
+ USE_TREE@4..20
+ PATH@4..20
+ PATH@4..7
+ PATH_SEGMENT@4..7
+ NAME_REF@4..7
+ IDENT@4..7 "std"
+ COLON2@7..9 "::"
+ PATH_SEGMENT@9..20
+ NAME_REF@9..20
+ IDENT@9..20 "collections"
+ SEMICOLON@20..21 ";"
+ WHITESPACE@21..22 "\n"
--- /dev/null
+use std::collections;
--- /dev/null
+SOURCE_FILE@0..18
+ FN@0..17
+ FN_KW@0..2 "fn"
+ WHITESPACE@2..3 " "
+ NAME@3..4
+ IDENT@3..4 "f"
+ GENERIC_PARAM_LIST@4..12
+ L_ANGLE@4..5 "<"
+ LIFETIME_PARAM@5..11
+ LIFETIME@5..7
+ LIFETIME_IDENT@5..7 "'a"
+ COLON@7..8 ":"
+ WHITESPACE@8..9 " "
+ LIFETIME@9..11
+ LIFETIME_IDENT@9..11 "'b"
+ R_ANGLE@11..12 ">"
+ PARAM_LIST@12..14
+ L_PAREN@12..13 "("
+ R_PAREN@13..14 ")"
+ WHITESPACE@14..15 " "
+ BLOCK_EXPR@15..17
+ L_CURLY@15..16 "{"
+ R_CURLY@16..17 "}"
+ WHITESPACE@17..18 "\n"
--- /dev/null
+fn f<'a: 'b>() {}
--- /dev/null
+SOURCE_FILE@0..22
+ TYPE_ALIAS@0..21
+ TYPE_KW@0..4 "type"
+ WHITESPACE@4..5 " "
+ NAME@5..6
+ IDENT@5..6 "T"
+ WHITESPACE@6..7 " "
+ EQ@7..8 "="
+ WHITESPACE@8..9 " "
+ PATH_TYPE@9..20
+ PATH@9..20
+ PATH_SEGMENT@9..20
+ NAME_REF@9..10
+ IDENT@9..10 "S"
+ GENERIC_ARG_LIST@10..20
+ L_ANGLE@10..11 "<"
+ CONST_ARG@11..19
+ BLOCK_EXPR@11..19
+ L_CURLY@11..12 "{"
+ BIN_EXPR@12..18
+ LITERAL@12..14
+ INT_NUMBER@12..14 "90"
+ WHITESPACE@14..15 " "
+ PLUS@15..16 "+"
+ WHITESPACE@16..17 " "
+ LITERAL@17..18
+ INT_NUMBER@17..18 "2"
+ R_CURLY@18..19 "}"
+ R_ANGLE@19..20 ">"
+ SEMICOLON@20..21 ";"
+ WHITESPACE@21..22 "\n"
--- /dev/null
+type T = S<{90 + 2}>;
--- /dev/null
+SOURCE_FILE@0..20
+ FN@0..19
+ FN_KW@0..2 "fn"
+ WHITESPACE@2..3 " "
+ NAME@3..4
+ IDENT@3..4 "f"
+ GENERIC_PARAM_LIST@4..14
+ L_ANGLE@4..5 "<"
+ TYPE_PARAM@5..13
+ NAME@5..6
+ IDENT@5..6 "T"
+ COLON@6..7 ":"
+ WHITESPACE@7..8 " "
+ TYPE_BOUND_LIST@8..13
+ TYPE_BOUND@8..13
+ PATH_TYPE@8..13
+ PATH@8..13
+ PATH_SEGMENT@8..13
+ NAME_REF@8..13
+ IDENT@8..13 "Clone"
+ R_ANGLE@13..14 ">"
+ PARAM_LIST@14..16
+ L_PAREN@14..15 "("
+ R_PAREN@15..16 ")"
+ WHITESPACE@16..17 " "
+ BLOCK_EXPR@17..19
+ L_CURLY@17..18 "{"
+ R_CURLY@18..19 "}"
+ WHITESPACE@19..20 "\n"
--- /dev/null
+fn f<T: Clone>() {}
--- /dev/null
+SOURCE_FILE@0..16
+ TYPE_ALIAS@0..15
+ TYPE_KW@0..4 "type"
+ WHITESPACE@4..5 " "
+ NAME@5..6
+ IDENT@5..6 "T"
+ WHITESPACE@6..7 " "
+ EQ@7..8 "="
+ WHITESPACE@8..9 " "
+ PATH_TYPE@9..14
+ PATH@9..14
+ PATH_SEGMENT@9..14
+ NAME_REF@9..10
+ IDENT@9..10 "S"
+ GENERIC_ARG_LIST@10..14
+ L_ANGLE@10..11 "<"
+ CONST_ARG@11..13
+ LITERAL@11..13
+ INT_NUMBER@11..13 "92"
+ R_ANGLE@13..14 ">"
+ SEMICOLON@14..15 ";"
+ WHITESPACE@15..16 "\n"
--- /dev/null
+type T = S<92>;
--- /dev/null
+SOURCE_FILE@0..20
+ FN@0..19
+ FN_KW@0..2 "fn"
+ WHITESPACE@2..3 " "
+ NAME@3..4
+ IDENT@3..4 "f"
+ GENERIC_PARAM_LIST@4..14
+ L_ANGLE@4..5 "<"
+ TYPE_PARAM@5..13
+ NAME@5..6
+ IDENT@5..6 "T"
+ COLON@6..7 ":"
+ WHITESPACE@7..8 " "
+ TYPE_BOUND_LIST@8..13
+ TYPE_BOUND@8..13
+ PATH_TYPE@8..13
+ PATH@8..13
+ PATH_SEGMENT@8..13
+ NAME_REF@8..13
+ IDENT@8..13 "Clone"
+ R_ANGLE@13..14 ">"
+ PARAM_LIST@14..16
+ L_PAREN@14..15 "("
+ R_PAREN@15..16 ")"
+ WHITESPACE@16..17 " "
+ BLOCK_EXPR@17..19
+ L_CURLY@17..18 "{"
+ R_CURLY@18..19 "}"
+ WHITESPACE@19..20 "\n"
--- /dev/null
+fn f<T: Clone>() {}
--- /dev/null
+SOURCE_FILE@0..45
+ TYPE_ALIAS@0..44
+ TYPE_KW@0..4 "type"
+ WHITESPACE@4..5 " "
+ NAME@5..6
+ IDENT@5..6 "T"
+ WHITESPACE@6..7 " "
+ EQ@7..8 "="
+ WHITESPACE@8..9 " "
+ PATH_TYPE@9..43
+ PATH@9..43
+ PATH_SEGMENT@9..43
+ NAME_REF@9..26
+ IDENT@9..26 "StreamingIterator"
+ GENERIC_ARG_LIST@26..43
+ L_ANGLE@26..27 "<"
+ ASSOC_TYPE_ARG@27..42
+ NAME_REF@27..31
+ IDENT@27..31 "Item"
+ GENERIC_ARG_LIST@31..35
+ L_ANGLE@31..32 "<"
+ LIFETIME_ARG@32..34
+ LIFETIME@32..34
+ LIFETIME_IDENT@32..34 "'a"
+ R_ANGLE@34..35 ">"
+ COLON@35..36 ":"
+ WHITESPACE@36..37 " "
+ TYPE_BOUND_LIST@37..42
+ TYPE_BOUND@37..42
+ PATH_TYPE@37..42
+ PATH@37..42
+ PATH_SEGMENT@37..42
+ NAME_REF@37..42
+ IDENT@37..42 "Clone"
+ R_ANGLE@42..43 ">"
+ SEMICOLON@43..44 ";"
+ WHITESPACE@44..45 "\n"
--- /dev/null
+type T = StreamingIterator<Item<'a>: Clone>;
--- /dev/null
+SOURCE_FILE@0..21
+ TYPE_ALIAS@0..20
+ TYPE_KW@0..4 "type"
+ WHITESPACE@4..5 " "
+ NAME@5..6
+ IDENT@5..6 "T"
+ WHITESPACE@6..7 " "
+ EQ@7..8 "="
+ WHITESPACE@8..9 " "
+ PATH_TYPE@9..19
+ PATH@9..19
+ PATH_SEGMENT@9..19
+ NAME_REF@9..10
+ IDENT@9..10 "S"
+ GENERIC_ARG_LIST@10..19
+ L_ANGLE@10..11 "<"
+ LIFETIME_ARG@11..18
+ LIFETIME@11..18
+ LIFETIME_IDENT@11..18 "'static"
+ R_ANGLE@18..19 ">"
+ SEMICOLON@19..20 ";"
+ WHITESPACE@20..21 "\n"
--- /dev/null
+type T = S<'static>;
--- /dev/null
+SOURCE_FILE@0..46
+ TYPE_ALIAS@0..45
+ TYPE_KW@0..4 "type"
+ WHITESPACE@4..5 " "
+ NAME@5..6
+ IDENT@5..6 "T"
+ WHITESPACE@6..7 " "
+ EQ@7..8 "="
+ WHITESPACE@8..9 " "
+ PATH_TYPE@9..44
+ PATH@9..44
+ PATH_SEGMENT@9..44
+ NAME_REF@9..26
+ IDENT@9..26 "StreamingIterator"
+ GENERIC_ARG_LIST@26..44
+ L_ANGLE@26..27 "<"
+ ASSOC_TYPE_ARG@27..43
+ NAME_REF@27..31
+ IDENT@27..31 "Item"
+ GENERIC_ARG_LIST@31..35
+ L_ANGLE@31..32 "<"
+ LIFETIME_ARG@32..34
+ LIFETIME@32..34
+ LIFETIME_IDENT@32..34 "'a"
+ R_ANGLE@34..35 ">"
+ WHITESPACE@35..36 " "
+ EQ@36..37 "="
+ WHITESPACE@37..38 " "
+ REF_TYPE@38..43
+ AMP@38..39 "&"
+ LIFETIME@39..41
+ LIFETIME_IDENT@39..41 "'a"
+ WHITESPACE@41..42 " "
+ PATH_TYPE@42..43
+ PATH@42..43
+ PATH_SEGMENT@42..43
+ NAME_REF@42..43
+ IDENT@42..43 "T"
+ R_ANGLE@43..44 ">"
+ SEMICOLON@44..45 ";"
+ WHITESPACE@45..46 "\n"
--- /dev/null
+type T = StreamingIterator<Item<'a> = &'a T>;
--- /dev/null
+SOURCE_FILE@0..35
+ STRUCT@0..34
+ STRUCT_KW@0..6 "struct"
+ WHITESPACE@6..7 " "
+ NAME@7..8
+ IDENT@7..8 "S"
+ GENERIC_PARAM_LIST@8..33
+ L_ANGLE@8..9 "<"
+ CONST_PARAM@9..32
+ CONST_KW@9..14 "const"
+ WHITESPACE@14..15 " "
+ NAME@15..16
+ IDENT@15..16 "N"
+ COLON@16..17 ":"
+ WHITESPACE@17..18 " "
+ PATH_TYPE@18..21
+ PATH@18..21
+ PATH_SEGMENT@18..21
+ NAME_REF@18..21
+ IDENT@18..21 "u32"
+ WHITESPACE@21..22 " "
+ EQ@22..23 "="
+ WHITESPACE@23..24 " "
+ CONST_ARG@24..32
+ PATH_EXPR@24..32
+ PATH@24..32
+ PATH@24..27
+ PATH_SEGMENT@24..27
+ NAME_REF@24..27
+ IDENT@24..27 "u32"
+ COLON2@27..29 "::"
+ PATH_SEGMENT@29..32
+ NAME_REF@29..32
+ IDENT@29..32 "MAX"
+ R_ANGLE@32..33 ">"
+ SEMICOLON@33..34 ";"
+ WHITESPACE@34..35 "\n"
--- /dev/null
+struct S<const N: u32 = u32::MAX>;
--- /dev/null
+SOURCE_FILE@0..33
+ TYPE_ALIAS@0..32
+ TYPE_KW@0..4 "type"
+ WHITESPACE@4..5 " "
+ NAME@5..6
+ IDENT@5..6 "T"
+ WHITESPACE@6..7 " "
+ EQ@7..8 "="
+ WHITESPACE@8..9 " "
+ PATH_TYPE@9..31
+ PATH@9..31
+ PATH_SEGMENT@9..31
+ NAME_REF@9..10
+ IDENT@9..10 "S"
+ GENERIC_ARG_LIST@10..31
+ L_ANGLE@10..11 "<"
+ CONST_ARG@11..18
+ LITERAL@11..18
+ STRING@11..18 "\"hello\""
+ COMMA@18..19 ","
+ WHITESPACE@19..20 " "
+ CONST_ARG@20..30
+ LITERAL@20..30
+ INT_NUMBER@20..30 "0xdeadbeef"
+ R_ANGLE@30..31 ">"
+ SEMICOLON@31..32 ";"
+ WHITESPACE@32..33 "\n"
--- /dev/null
+type T = S<"hello", 0xdeadbeef>;
--- /dev/null
+SOURCE_FILE@0..17
+ TYPE_ALIAS@0..16
+ TYPE_KW@0..4 "type"
+ WHITESPACE@4..5 " "
+ NAME@5..6
+ IDENT@5..6 "T"
+ WHITESPACE@6..7 " "
+ EQ@7..8 "="
+ WHITESPACE@8..9 " "
+ PATH_TYPE@9..15
+ PATH@9..15
+ PATH_SEGMENT@9..15
+ NAME_REF@9..10
+ IDENT@9..10 "S"
+ GENERIC_ARG_LIST@10..15
+ L_ANGLE@10..11 "<"
+ TYPE_ARG@11..14
+ PATH_TYPE@11..14
+ PATH@11..14
+ PATH_SEGMENT@11..14
+ NAME_REF@11..14
+ IDENT@11..14 "i32"
+ R_ANGLE@14..15 ">"
+ SEMICOLON@15..16 ";"
+ WHITESPACE@16..17 "\n"
--- /dev/null
+type T = S<i32>;
--- /dev/null
+SOURCE_FILE@0..17
+ TYPE_ALIAS@0..16
+ TYPE_KW@0..4 "type"
+ WHITESPACE@4..5 " "
+ NAME@5..6
+ IDENT@5..6 "T"
+ WHITESPACE@6..7 " "
+ EQ@7..8 "="
+ WHITESPACE@8..9 " "
+ PATH_TYPE@9..15
+ PATH@9..15
+ PATH_SEGMENT@9..15
+ NAME_REF@9..10
+ IDENT@9..10 "S"
+ GENERIC_ARG_LIST@10..15
+ L_ANGLE@10..11 "<"
+ CONST_ARG@11..14
+ PREFIX_EXPR@11..14
+ MINUS@11..12 "-"
+ LITERAL@12..14
+ INT_NUMBER@12..14 "92"
+ R_ANGLE@14..15 ">"
+ SEMICOLON@15..16 ";"
+ WHITESPACE@16..17 "\n"
--- /dev/null
+type T = S<-92>;
--- /dev/null
+SOURCE_FILE@0..18
+ TYPE_ALIAS@0..17
+ TYPE_KW@0..4 "type"
+ WHITESPACE@4..5 " "
+ NAME@5..6
+ IDENT@5..6 "T"
+ WHITESPACE@6..7 " "
+ EQ@7..8 "="
+ WHITESPACE@8..9 " "
+ PATH_TYPE@9..16
+ PATH@9..16
+ PATH_SEGMENT@9..16
+ NAME_REF@9..10
+ IDENT@9..10 "S"
+ GENERIC_ARG_LIST@10..16
+ L_ANGLE@10..11 "<"
+ CONST_ARG@11..15
+ LITERAL@11..15
+ TRUE_KW@11..15 "true"
+ R_ANGLE@15..16 ">"
+ SEMICOLON@16..17 ";"
+ WHITESPACE@17..18 "\n"
--- /dev/null
+type T = S<true>;
-SOURCE_FILE@0..118
- MODULE@0..6
+SOURCE_FILE@0..100
+ MODULE@0..46
MOD_KW@0..3 "mod"
WHITESPACE@3..4 " "
NAME@4..5
- IDENT@4..5 "a"
- SEMICOLON@5..6 ";"
- WHITESPACE@6..8 "\n\n"
- MODULE@8..17
- MOD_KW@8..11 "mod"
- WHITESPACE@11..12 " "
- NAME@12..13
- IDENT@12..13 "b"
- WHITESPACE@13..14 " "
- ITEM_LIST@14..17
- L_CURLY@14..15 "{"
- WHITESPACE@15..16 "\n"
- R_CURLY@16..17 "}"
- WHITESPACE@17..19 "\n\n"
- MODULE@19..65
- MOD_KW@19..22 "mod"
- WHITESPACE@22..23 " "
- NAME@23..24
- IDENT@23..24 "c"
- WHITESPACE@24..25 " "
- ITEM_LIST@25..65
- L_CURLY@25..26 "{"
- WHITESPACE@26..31 "\n "
- FN@31..47
- FN_KW@31..33 "fn"
- WHITESPACE@33..34 " "
- NAME@34..37
- IDENT@34..37 "foo"
- PARAM_LIST@37..39
- L_PAREN@37..38 "("
- R_PAREN@38..39 ")"
+ IDENT@4..5 "c"
+ WHITESPACE@5..6 " "
+ ITEM_LIST@6..46
+ L_CURLY@6..7 "{"
+ WHITESPACE@7..12 "\n "
+ FN@12..28
+ FN_KW@12..14 "fn"
+ WHITESPACE@14..15 " "
+ NAME@15..18
+ IDENT@15..18 "foo"
+ PARAM_LIST@18..20
+ L_PAREN@18..19 "("
+ R_PAREN@19..20 ")"
+ WHITESPACE@20..21 " "
+ BLOCK_EXPR@21..28
+ L_CURLY@21..22 "{"
+ WHITESPACE@22..27 "\n "
+ R_CURLY@27..28 "}"
+ WHITESPACE@28..33 "\n "
+ STRUCT@33..44
+ STRUCT_KW@33..39 "struct"
WHITESPACE@39..40 " "
- BLOCK_EXPR@40..47
- L_CURLY@40..41 "{"
- WHITESPACE@41..46 "\n "
- R_CURLY@46..47 "}"
- WHITESPACE@47..52 "\n "
- STRUCT@52..63
- STRUCT_KW@52..58 "struct"
- WHITESPACE@58..59 " "
- NAME@59..60
- IDENT@59..60 "S"
- WHITESPACE@60..61 " "
- RECORD_FIELD_LIST@61..63
- L_CURLY@61..62 "{"
- R_CURLY@62..63 "}"
- WHITESPACE@63..64 "\n"
- R_CURLY@64..65 "}"
- WHITESPACE@65..67 "\n\n"
- MODULE@67..118
- MOD_KW@67..70 "mod"
- WHITESPACE@70..71 " "
- NAME@71..72
- IDENT@71..72 "d"
- WHITESPACE@72..73 " "
- ITEM_LIST@73..118
- L_CURLY@73..74 "{"
- WHITESPACE@74..79 "\n "
- ATTR@79..87
- POUND@79..80 "#"
- BANG@80..81 "!"
- L_BRACK@81..82 "["
- META@82..86
- PATH@82..86
- PATH_SEGMENT@82..86
- NAME_REF@82..86
- IDENT@82..86 "attr"
- R_BRACK@86..87 "]"
- WHITESPACE@87..92 "\n "
- MODULE@92..98
- MOD_KW@92..95 "mod"
- WHITESPACE@95..96 " "
- NAME@96..97
- IDENT@96..97 "e"
- SEMICOLON@97..98 ";"
- WHITESPACE@98..103 "\n "
- MODULE@103..116
- MOD_KW@103..106 "mod"
- WHITESPACE@106..107 " "
- NAME@107..108
- IDENT@107..108 "f"
- WHITESPACE@108..109 " "
- ITEM_LIST@109..116
- L_CURLY@109..110 "{"
- WHITESPACE@110..115 "\n "
- R_CURLY@115..116 "}"
- WHITESPACE@116..117 "\n"
- R_CURLY@117..118 "}"
+ NAME@40..41
+ IDENT@40..41 "S"
+ WHITESPACE@41..42 " "
+ RECORD_FIELD_LIST@42..44
+ L_CURLY@42..43 "{"
+ R_CURLY@43..44 "}"
+ WHITESPACE@44..45 "\n"
+ R_CURLY@45..46 "}"
+ WHITESPACE@46..48 "\n\n"
+ MODULE@48..99
+ MOD_KW@48..51 "mod"
+ WHITESPACE@51..52 " "
+ NAME@52..53
+ IDENT@52..53 "d"
+ WHITESPACE@53..54 " "
+ ITEM_LIST@54..99
+ L_CURLY@54..55 "{"
+ WHITESPACE@55..60 "\n "
+ ATTR@60..68
+ POUND@60..61 "#"
+ BANG@61..62 "!"
+ L_BRACK@62..63 "["
+ META@63..67
+ PATH@63..67
+ PATH_SEGMENT@63..67
+ NAME_REF@63..67
+ IDENT@63..67 "attr"
+ R_BRACK@67..68 "]"
+ WHITESPACE@68..73 "\n "
+ MODULE@73..79
+ MOD_KW@73..76 "mod"
+ WHITESPACE@76..77 " "
+ NAME@77..78
+ IDENT@77..78 "e"
+ SEMICOLON@78..79 ";"
+ WHITESPACE@79..84 "\n "
+ MODULE@84..97
+ MOD_KW@84..87 "mod"
+ WHITESPACE@87..88 " "
+ NAME@88..89
+ IDENT@88..89 "f"
+ WHITESPACE@89..90 " "
+ ITEM_LIST@90..97
+ L_CURLY@90..91 "{"
+ WHITESPACE@91..96 "\n "
+ R_CURLY@96..97 "}"
+ WHITESPACE@97..98 "\n"
+ R_CURLY@98..99 "}"
+ WHITESPACE@99..100 "\n"
-mod a;
-
-mod b {
-}
-
mod c {
fn foo() {
}
mod e;
mod f {
}
-}
\ No newline at end of file
+}
-SOURCE_FILE@0..98
+SOURCE_FILE@0..122
FN@0..9
FN_KW@0..2 "fn"
WHITESPACE@2..3 " "
L_CURLY@21..22 "{"
R_CURLY@22..23 "}"
WHITESPACE@23..24 "\n"
- FN@24..44
- VISIBILITY@24..34
+ MACRO_DEF@24..47
+ VISIBILITY@24..27
PUB_KW@24..27 "pub"
- L_PAREN@27..28 "("
- PATH@28..33
- PATH_SEGMENT@28..33
- NAME_REF@28..33
- CRATE_KW@28..33 "crate"
- R_PAREN@33..34 ")"
- WHITESPACE@34..35 " "
- FN_KW@35..37 "fn"
- WHITESPACE@37..38 " "
- NAME@38..39
- IDENT@38..39 "c"
- PARAM_LIST@39..41
- L_PAREN@39..40 "("
- R_PAREN@40..41 ")"
- WHITESPACE@41..42 " "
- BLOCK_EXPR@42..44
- L_CURLY@42..43 "{"
- R_CURLY@43..44 "}"
- WHITESPACE@44..45 "\n"
- FN@45..65
- VISIBILITY@45..55
- PUB_KW@45..48 "pub"
- L_PAREN@48..49 "("
- PATH@49..54
- PATH_SEGMENT@49..54
- NAME_REF@49..54
- SUPER_KW@49..54 "super"
- R_PAREN@54..55 ")"
- WHITESPACE@55..56 " "
- FN_KW@56..58 "fn"
+ WHITESPACE@27..28 " "
+ MACRO_KW@28..33 "macro"
+ WHITESPACE@33..34 " "
+ NAME@34..35
+ IDENT@34..35 "m"
+ TOKEN_TREE@35..47
+ TOKEN_TREE@35..44
+ L_PAREN@35..36 "("
+ DOLLAR@36..37 "$"
+ COLON@37..38 ":"
+ IDENT@38..43 "ident"
+ R_PAREN@43..44 ")"
+ WHITESPACE@44..45 " "
+ TOKEN_TREE@45..47
+ L_CURLY@45..46 "{"
+ R_CURLY@46..47 "}"
+ WHITESPACE@47..48 "\n"
+ FN@48..68
+ VISIBILITY@48..58
+ PUB_KW@48..51 "pub"
+ L_PAREN@51..52 "("
+ PATH@52..57
+ PATH_SEGMENT@52..57
+ NAME_REF@52..57
+ CRATE_KW@52..57 "crate"
+ R_PAREN@57..58 ")"
WHITESPACE@58..59 " "
- NAME@59..60
- IDENT@59..60 "d"
- PARAM_LIST@60..62
- L_PAREN@60..61 "("
- R_PAREN@61..62 ")"
- WHITESPACE@62..63 " "
- BLOCK_EXPR@63..65
- L_CURLY@63..64 "{"
- R_CURLY@64..65 "}"
- WHITESPACE@65..66 "\n"
- FN@66..97
- VISIBILITY@66..87
- PUB_KW@66..69 "pub"
- L_PAREN@69..70 "("
- IN_KW@70..72 "in"
- WHITESPACE@72..73 " "
- PATH@73..86
- PATH@73..81
- PATH@73..76
- PATH_SEGMENT@73..76
- NAME_REF@73..76
- IDENT@73..76 "foo"
- COLON2@76..78 "::"
- PATH_SEGMENT@78..81
- NAME_REF@78..81
- IDENT@78..81 "bar"
- COLON2@81..83 "::"
- PATH_SEGMENT@83..86
- NAME_REF@83..86
- IDENT@83..86 "baz"
- R_PAREN@86..87 ")"
- WHITESPACE@87..88 " "
- FN_KW@88..90 "fn"
- WHITESPACE@90..91 " "
- NAME@91..92
- IDENT@91..92 "e"
- PARAM_LIST@92..94
- L_PAREN@92..93 "("
- R_PAREN@93..94 ")"
- WHITESPACE@94..95 " "
- BLOCK_EXPR@95..97
- L_CURLY@95..96 "{"
- R_CURLY@96..97 "}"
- WHITESPACE@97..98 "\n"
+ FN_KW@59..61 "fn"
+ WHITESPACE@61..62 " "
+ NAME@62..63
+ IDENT@62..63 "c"
+ PARAM_LIST@63..65
+ L_PAREN@63..64 "("
+ R_PAREN@64..65 ")"
+ WHITESPACE@65..66 " "
+ BLOCK_EXPR@66..68
+ L_CURLY@66..67 "{"
+ R_CURLY@67..68 "}"
+ WHITESPACE@68..69 "\n"
+ FN@69..89
+ VISIBILITY@69..79
+ PUB_KW@69..72 "pub"
+ L_PAREN@72..73 "("
+ PATH@73..78
+ PATH_SEGMENT@73..78
+ NAME_REF@73..78
+ SUPER_KW@73..78 "super"
+ R_PAREN@78..79 ")"
+ WHITESPACE@79..80 " "
+ FN_KW@80..82 "fn"
+ WHITESPACE@82..83 " "
+ NAME@83..84
+ IDENT@83..84 "d"
+ PARAM_LIST@84..86
+ L_PAREN@84..85 "("
+ R_PAREN@85..86 ")"
+ WHITESPACE@86..87 " "
+ BLOCK_EXPR@87..89
+ L_CURLY@87..88 "{"
+ R_CURLY@88..89 "}"
+ WHITESPACE@89..90 "\n"
+ FN@90..121
+ VISIBILITY@90..111
+ PUB_KW@90..93 "pub"
+ L_PAREN@93..94 "("
+ IN_KW@94..96 "in"
+ WHITESPACE@96..97 " "
+ PATH@97..110
+ PATH@97..105
+ PATH@97..100
+ PATH_SEGMENT@97..100
+ NAME_REF@97..100
+ IDENT@97..100 "foo"
+ COLON2@100..102 "::"
+ PATH_SEGMENT@102..105
+ NAME_REF@102..105
+ IDENT@102..105 "bar"
+ COLON2@105..107 "::"
+ PATH_SEGMENT@107..110
+ NAME_REF@107..110
+ IDENT@107..110 "baz"
+ R_PAREN@110..111 ")"
+ WHITESPACE@111..112 " "
+ FN_KW@112..114 "fn"
+ WHITESPACE@114..115 " "
+ NAME@115..116
+ IDENT@115..116 "e"
+ PARAM_LIST@116..118
+ L_PAREN@116..117 "("
+ R_PAREN@117..118 ")"
+ WHITESPACE@118..119 " "
+ BLOCK_EXPR@119..121
+ L_CURLY@119..120 "{"
+ R_CURLY@120..121 "}"
+ WHITESPACE@121..122 "\n"
fn a() {}
pub fn b() {}
+pub macro m($:ident) {}
pub(crate) fn c() {}
pub(super) fn d() {}
pub(in foo::bar::baz) fn e() {}
-SOURCE_FILE@0..39
- CONST@0..17
- CONST_KW@0..5 "const"
- WHITESPACE@5..6 " "
- UNDERSCORE@6..7 "_"
- COLON@7..8 ":"
- WHITESPACE@8..9 " "
- PATH_TYPE@9..12
- PATH@9..12
- PATH_SEGMENT@9..12
- NAME_REF@9..12
- IDENT@9..12 "u32"
- WHITESPACE@12..13 " "
- EQ@13..14 "="
- WHITESPACE@14..15 " "
- LITERAL@15..16
- INT_NUMBER@15..16 "0"
- SEMICOLON@16..17 ";"
- WHITESPACE@17..18 "\n"
- CONST@18..38
- CONST_KW@18..23 "const"
- WHITESPACE@23..24 " "
- NAME@24..27
- IDENT@24..27 "FOO"
- COLON@27..28 ":"
- WHITESPACE@28..29 " "
- PATH_TYPE@29..32
- PATH@29..32
- PATH_SEGMENT@29..32
- NAME_REF@29..32
- IDENT@29..32 "u32"
- WHITESPACE@32..33 " "
- EQ@33..34 "="
- WHITESPACE@34..35 " "
- LITERAL@35..37
- INT_NUMBER@35..37 "92"
- SEMICOLON@37..38 ";"
- WHITESPACE@38..39 "\n"
+SOURCE_FILE@0..0
-const _: u32 = 0;
-const FOO: u32 = 92;
/// //- other meta
/// ```
///
- /// Fixture can also start with a minicore declaration:
+ /// Fixture can also start with a proc_macros and minicore declaration(in that order):
///
/// ```
+ /// //- proc_macros: identity
/// //- minicore: sized
/// ```
///
- /// That will include a subset of `libcore` into the fixture, see
+ /// That will include predefined proc macros and a subset of `libcore` into the fixture, see
/// `minicore.rs` for what's available.
- pub fn parse(ra_fixture: &str) -> (Option<MiniCore>, Vec<Fixture>) {
+ pub fn parse(ra_fixture: &str) -> (Option<MiniCore>, Vec<String>, Vec<Fixture>) {
let fixture = trim_indent(ra_fixture);
let mut fixture = fixture.as_str();
let mut mini_core = None;
let mut res: Vec<Fixture> = Vec::new();
+ let mut test_proc_macros = vec![];
+
+ if fixture.starts_with("//- proc_macros:") {
+ let first_line = fixture.split_inclusive('\n').next().unwrap();
+ test_proc_macros = first_line
+ .strip_prefix("//- proc_macros:")
+ .unwrap()
+ .split(',')
+ .map(|it| it.trim().to_string())
+ .collect();
+ fixture = &fixture[first_line.len()..];
+ }
if fixture.starts_with("//- minicore:") {
let first_line = fixture.split_inclusive('\n').next().unwrap();
}
}
- (mini_core, res)
+ (mini_core, test_proc_macros, res)
}
//- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b env:OUTDIR=path/to,OTHER=foo
panic!("unused minicore flag: {:?}", flag);
}
}
- format!("{}", buf);
buf
}
}
#[test]
fn parse_fixture_gets_full_meta() {
- let (mini_core, parsed) = Fixture::parse(
+ let (mini_core, proc_macros, parsed) = Fixture::parse(
r#"
+//- proc_macros: identity
//- minicore: coerce_unsized
//- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b,atom env:OUTDIR=path/to,OTHER=foo
mod m;
"#,
);
+ assert_eq!(proc_macros, vec!["identity".to_string()]);
assert_eq!(mini_core.unwrap().activated_flags, vec!["coerce_unsized".to_string()]);
assert_eq!(1, parsed.len());
[dependencies]
tracing = "0.1"
-rustc-hash = "1.0"
jod-thread = "0.1.0"
walkdir = "2.3.1"
crossbeam-channel = "0.5.0"
pub struct NotifyHandle {
// Relative order of fields below is significant.
sender: Sender<Message>,
- thread: jod_thread::JoinHandle,
+ _thread: jod_thread::JoinHandle,
}
#[derive(Debug)]
.name("VfsLoader".to_owned())
.spawn(move || actor.run(receiver))
.expect("failed to spawn thread");
- NotifyHandle { sender, thread }
+ NotifyHandle { sender, _thread: thread }
}
fn set_config(&mut self, config: loader::Config) {
self.sender.send(Message::Config(config)).unwrap()
[[rust-analyzer.assist.importGroup]]rust-analyzer.assist.importGroup (default: `true`)::
+
--
-Group inserted imports by the [following order](https://rust-analyzer.github.io/manual.html#auto-import). Groups are separated by newlines.
+Group inserted imports by the https://rust-analyzer.github.io/manual.html#auto-import[following order]. Groups are separated by newlines.
--
[[rust-analyzer.assist.allowMergingIntoGlobImports]]rust-analyzer.assist.allowMergingIntoGlobImports (default: `true`)::
+
:source-highlighter: rouge
:experimental:
+////
+IMPORTANT: the master copy of this document lives in the https://github.com/rust-analyzer/rust-analyzer repository
+////
+
At its core, rust-analyzer is a *library* for semantic analysis of Rust code as it changes over time.
This manual focuses on a specific usage of the library -- running it as part of a server that implements the
https://microsoft.github.io/language-server-protocol/[Language Server Protocol] (LSP).
target?: string;
/// Environment variables, used for
/// the `env!` macro
- env: : { [key: string]: string; },
+ env: { [key: string]: string; },
/// Whether the crate is a proc-macro crate.
- is_proc_macro: bool;
+ is_proc_macro: boolean;
/// For proc-macro crates, path to compiled
/// proc-macro (.so file).
proc_macro_dylib_path?: string;
{ "rust-analyzer.checkOnSave.overrideCommand": ["cargo", "check", "--message-format=json"] }
----
-The `checkOnSave.overrideCommand` requires the command specified to output json error messages for rust-analyzer to consume. The `--message-format=json` flag does this for `cargo check` so whichever command you use must also output errors in this format. See the <<Configuration>> section for more information.
+The `checkOnSave.overrideCommand` requires the command specified to output json error messages for rust-analyzer to consume. The `--message-format=json` flag does this for `cargo check` so whichever command you use must also output errors in this format. See the <<Configuration>> section for more information.
== Security
]
},
"rust-analyzer.assist.importGroup": {
- "markdownDescription": "Group inserted imports by the [following order](https://rust-analyzer.github.io/manual.html#auto-import). Groups are separated by newlines.",
+ "markdownDescription": "Group inserted imports by the https://rust-analyzer.github.io/manual.html#auto-import[following order]. Groups are separated by newlines.",
"default": true,
"type": "boolean"
},
await vscode
.workspace
.getConfiguration(`${ctx.config.rootSection}.inlayHints`)
- .update('enable', !ctx.config.inlayHints.enable, vscode.ConfigurationTarget.Workspace);
+ .update('enable', !ctx.config.inlayHints.enable, vscode.ConfigurationTarget.Global);
};
}
log.info(`Renamed old server binary ${opts.dest.fsPath} to ${oldServerPath.fsPath}`);
} catch (err) {
const fsErr = err as vscode.FileSystemError;
- // This is supposed to return `FileNotFound`, alas...
- if (!fsErr.code || fsErr.code !== "FileNotFound" && fsErr.code !== "EntryNotFound") {
- log.error(`Cannot rename existing server instance: ${err}`);
+
+ // This is supposed to return `FileNotFound` (spelled as `EntryNotFound`)
+ // but instead `code` is `Unknown` and `name` is `EntryNotFound (FileSystemError) (FileSystemError)`.
+ // https://github.com/rust-analyzer/rust-analyzer/pull/10222
+ if (!fsErr.code || fsErr.code !== "EntryNotFound" && fsErr.name.indexOf("EntryNotFound") === -1) {
+ log.error(`Cannot rename existing server instance: ${err}"`);
}
}
try {
use crate::flags;
// Latest stable, feel free to send a PR if this lags behind.
-const REQUIRED_RUST_VERSION: u32 = 53;
+const REQUIRED_RUST_VERSION: u32 = 55;
impl flags::Install {
pub(crate) fn run(self) -> Result<()> {
let src = project_root().join("./docs/user/").join(adoc);
let dst = website_root.join(adoc);
- let contents = read_file(src)?.replace("\n\n===", "\n\n// IMPORTANT: master copy of this document lives in the https://github.com/rust-analyzer/rust-analyzer repository\n\n==");
+ let contents = read_file(src)?;
write_file(dst, contents)?;
}