mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-28 05:53:45 +00:00
⬆️ rust-analyzer
This commit is contained in:
parent
bc45c7659a
commit
7e711da2f0
98 changed files with 1801 additions and 943 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
@ -711,6 +711,7 @@ dependencies = [
|
||||||
"limit",
|
"limit",
|
||||||
"memchr",
|
"memchr",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"oorandom",
|
||||||
"parser",
|
"parser",
|
||||||
"profile",
|
"profile",
|
||||||
"rayon",
|
"rayon",
|
||||||
|
@ -932,9 +933,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lsp-types"
|
name = "lsp-types"
|
||||||
version = "0.93.2"
|
version = "0.94.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9be6e9c7e2d18f651974370d7aff703f9513e0df6e464fd795660edc77e6ca51"
|
checksum = "0b63735a13a1f9cd4f4835223d828ed9c2e35c8c5e61837774399f558b6a1237"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -1173,6 +1174,7 @@ dependencies = [
|
||||||
"limit",
|
"limit",
|
||||||
"rustc-ap-rustc_lexer",
|
"rustc-ap-rustc_lexer",
|
||||||
"sourcegen",
|
"sourcegen",
|
||||||
|
"stdx",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -74,3 +74,5 @@ toolchain = { path = "./crates/toolchain", version = "0.0.0" }
|
||||||
tt = { path = "./crates/tt", version = "0.0.0" }
|
tt = { path = "./crates/tt", version = "0.0.0" }
|
||||||
vfs-notify = { path = "./crates/vfs-notify", version = "0.0.0" }
|
vfs-notify = { path = "./crates/vfs-notify", version = "0.0.0" }
|
||||||
vfs = { path = "./crates/vfs", version = "0.0.0" }
|
vfs = { path = "./crates/vfs", version = "0.0.0" }
|
||||||
|
# non-local crates
|
||||||
|
smallvec = { version = "1.10.0", features = ["const_new", "union", "const_generics"] }
|
||||||
|
|
|
@ -27,7 +27,7 @@ itertools = "0.10.5"
|
||||||
la-arena = { version = "0.3.0", path = "../../lib/la-arena" }
|
la-arena = { version = "0.3.0", path = "../../lib/la-arena" }
|
||||||
once_cell = "1.17.0"
|
once_cell = "1.17.0"
|
||||||
rustc-hash = "1.1.0"
|
rustc-hash = "1.1.0"
|
||||||
smallvec = "1.10.0"
|
smallvec.workspace = true
|
||||||
tracing = "0.1.35"
|
tracing = "0.1.35"
|
||||||
|
|
||||||
rustc_abi = { version = "0.0.20221221", package = "hkalbasi-rustc-ap-rustc_abi", default-features = false }
|
rustc_abi = { version = "0.0.20221221", package = "hkalbasi-rustc-ap-rustc_abi", default-features = false }
|
||||||
|
|
|
@ -2,9 +2,10 @@
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::tt::{Delimiter, DelimiterKind, Leaf, Subtree, TokenTree};
|
|
||||||
use base_db::CrateId;
|
use base_db::CrateId;
|
||||||
|
use cfg::CfgOptions;
|
||||||
use either::Either;
|
use either::Either;
|
||||||
|
|
||||||
use hir_expand::{
|
use hir_expand::{
|
||||||
name::{AsName, Name},
|
name::{AsName, Name},
|
||||||
HirFileId, InFile,
|
HirFileId, InFile,
|
||||||
|
@ -24,12 +25,12 @@ use crate::{
|
||||||
src::HasChildSource,
|
src::HasChildSource,
|
||||||
src::HasSource,
|
src::HasSource,
|
||||||
trace::Trace,
|
trace::Trace,
|
||||||
|
tt::{Delimiter, DelimiterKind, Leaf, Subtree, TokenTree},
|
||||||
type_ref::TypeRef,
|
type_ref::TypeRef,
|
||||||
visibility::RawVisibility,
|
visibility::RawVisibility,
|
||||||
EnumId, LocalEnumVariantId, LocalFieldId, LocalModuleId, Lookup, ModuleId, StructId, UnionId,
|
EnumId, LocalEnumVariantId, LocalFieldId, LocalModuleId, Lookup, ModuleId, StructId, UnionId,
|
||||||
VariantId,
|
VariantId,
|
||||||
};
|
};
|
||||||
use cfg::CfgOptions;
|
|
||||||
|
|
||||||
/// Note that we use `StructData` for unions as well!
|
/// Note that we use `StructData` for unions as well!
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
|
|
@ -19,7 +19,7 @@ use la_arena::{Arena, ArenaMap};
|
||||||
use limit::Limit;
|
use limit::Limit;
|
||||||
use profile::Count;
|
use profile::Count;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use syntax::{ast, AstPtr, SyntaxNodePtr};
|
use syntax::{ast, AstPtr, SyntaxNode, SyntaxNodePtr};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
attr::Attrs,
|
attr::Attrs,
|
||||||
|
@ -51,7 +51,8 @@ pub struct Expander {
|
||||||
def_map: Arc<DefMap>,
|
def_map: Arc<DefMap>,
|
||||||
current_file_id: HirFileId,
|
current_file_id: HirFileId,
|
||||||
module: LocalModuleId,
|
module: LocalModuleId,
|
||||||
recursion_limit: usize,
|
/// `recursion_depth == usize::MAX` indicates that the recursion limit has been reached.
|
||||||
|
recursion_depth: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CfgExpander {
|
impl CfgExpander {
|
||||||
|
@ -84,7 +85,7 @@ impl Expander {
|
||||||
def_map,
|
def_map,
|
||||||
current_file_id,
|
current_file_id,
|
||||||
module: module.local_id,
|
module: module.local_id,
|
||||||
recursion_limit: 0,
|
recursion_depth: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,31 +94,37 @@ impl Expander {
|
||||||
db: &dyn DefDatabase,
|
db: &dyn DefDatabase,
|
||||||
macro_call: ast::MacroCall,
|
macro_call: ast::MacroCall,
|
||||||
) -> Result<ExpandResult<Option<(Mark, T)>>, UnresolvedMacro> {
|
) -> Result<ExpandResult<Option<(Mark, T)>>, UnresolvedMacro> {
|
||||||
if self.recursion_limit(db).check(self.recursion_limit + 1).is_err() {
|
let mut unresolved_macro_err = None;
|
||||||
cov_mark::hit!(your_stack_belongs_to_me);
|
|
||||||
return Ok(ExpandResult::only_err(ExpandError::Other(
|
|
||||||
"reached recursion limit during macro expansion".into(),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let macro_call = InFile::new(self.current_file_id, ¯o_call);
|
let result = self.within_limit(db, |this| {
|
||||||
|
let macro_call = InFile::new(this.current_file_id, ¯o_call);
|
||||||
|
|
||||||
let resolver =
|
let resolver =
|
||||||
|path| self.resolve_path_as_macro(db, &path).map(|it| macro_id_to_def_id(db, it));
|
|path| this.resolve_path_as_macro(db, &path).map(|it| macro_id_to_def_id(db, it));
|
||||||
|
|
||||||
let mut err = None;
|
let mut err = None;
|
||||||
let call_id =
|
let call_id = match macro_call.as_call_id_with_errors(
|
||||||
macro_call.as_call_id_with_errors(db, self.def_map.krate(), resolver, &mut |e| {
|
db,
|
||||||
|
this.def_map.krate(),
|
||||||
|
resolver,
|
||||||
|
&mut |e| {
|
||||||
err.get_or_insert(e);
|
err.get_or_insert(e);
|
||||||
})?;
|
},
|
||||||
let call_id = match call_id {
|
) {
|
||||||
Ok(it) => it,
|
Ok(call_id) => call_id,
|
||||||
Err(_) => {
|
Err(resolve_err) => {
|
||||||
return Ok(ExpandResult { value: None, err });
|
unresolved_macro_err = Some(resolve_err);
|
||||||
|
return ExpandResult { value: None, err: None };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
ExpandResult { value: call_id.ok(), err }
|
||||||
|
});
|
||||||
|
|
||||||
Ok(self.enter_expand_inner(db, call_id, err))
|
if let Some(err) = unresolved_macro_err {
|
||||||
|
Err(err)
|
||||||
|
} else {
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enter_expand_id<T: ast::AstNode>(
|
pub fn enter_expand_id<T: ast::AstNode>(
|
||||||
|
@ -125,15 +132,14 @@ impl Expander {
|
||||||
db: &dyn DefDatabase,
|
db: &dyn DefDatabase,
|
||||||
call_id: MacroCallId,
|
call_id: MacroCallId,
|
||||||
) -> ExpandResult<Option<(Mark, T)>> {
|
) -> ExpandResult<Option<(Mark, T)>> {
|
||||||
self.enter_expand_inner(db, call_id, None)
|
self.within_limit(db, |_this| ExpandResult::ok(Some(call_id)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enter_expand_inner<T: ast::AstNode>(
|
fn enter_expand_inner(
|
||||||
&mut self,
|
|
||||||
db: &dyn DefDatabase,
|
db: &dyn DefDatabase,
|
||||||
call_id: MacroCallId,
|
call_id: MacroCallId,
|
||||||
mut err: Option<ExpandError>,
|
mut err: Option<ExpandError>,
|
||||||
) -> ExpandResult<Option<(Mark, T)>> {
|
) -> ExpandResult<Option<(HirFileId, SyntaxNode)>> {
|
||||||
if err.is_none() {
|
if err.is_none() {
|
||||||
err = db.macro_expand_error(call_id);
|
err = db.macro_expand_error(call_id);
|
||||||
}
|
}
|
||||||
|
@ -154,29 +160,21 @@ impl Expander {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let node = match T::cast(raw_node) {
|
ExpandResult { value: Some((file_id, raw_node)), err }
|
||||||
Some(it) => it,
|
|
||||||
None => {
|
|
||||||
// This can happen without being an error, so only forward previous errors.
|
|
||||||
return ExpandResult { value: None, err };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
tracing::debug!("macro expansion {:#?}", node.syntax());
|
|
||||||
|
|
||||||
self.recursion_limit += 1;
|
|
||||||
let mark =
|
|
||||||
Mark { file_id: self.current_file_id, bomb: DropBomb::new("expansion mark dropped") };
|
|
||||||
self.cfg_expander.hygiene = Hygiene::new(db.upcast(), file_id);
|
|
||||||
self.current_file_id = file_id;
|
|
||||||
|
|
||||||
ExpandResult { value: Some((mark, node)), err }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exit(&mut self, db: &dyn DefDatabase, mut mark: Mark) {
|
pub fn exit(&mut self, db: &dyn DefDatabase, mut mark: Mark) {
|
||||||
self.cfg_expander.hygiene = Hygiene::new(db.upcast(), mark.file_id);
|
self.cfg_expander.hygiene = Hygiene::new(db.upcast(), mark.file_id);
|
||||||
self.current_file_id = mark.file_id;
|
self.current_file_id = mark.file_id;
|
||||||
self.recursion_limit -= 1;
|
if self.recursion_depth == usize::MAX {
|
||||||
|
// Recursion limit has been reached somewhere in the macro expansion tree. Reset the
|
||||||
|
// depth only when we get out of the tree.
|
||||||
|
if !self.current_file_id.is_macro() {
|
||||||
|
self.recursion_depth = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.recursion_depth -= 1;
|
||||||
|
}
|
||||||
mark.bomb.defuse();
|
mark.bomb.defuse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,6 +213,50 @@ impl Expander {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
return Limit::new(std::cmp::min(32, limit));
|
return Limit::new(std::cmp::min(32, limit));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn within_limit<F, T: ast::AstNode>(
|
||||||
|
&mut self,
|
||||||
|
db: &dyn DefDatabase,
|
||||||
|
op: F,
|
||||||
|
) -> ExpandResult<Option<(Mark, T)>>
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut Self) -> ExpandResult<Option<MacroCallId>>,
|
||||||
|
{
|
||||||
|
if self.recursion_depth == usize::MAX {
|
||||||
|
// Recursion limit has been reached somewhere in the macro expansion tree. We should
|
||||||
|
// stop expanding other macro calls in this tree, or else this may result in
|
||||||
|
// exponential number of macro expansions, leading to a hang.
|
||||||
|
//
|
||||||
|
// The overflow error should have been reported when it occurred (see the next branch),
|
||||||
|
// so don't return overflow error here to avoid diagnostics duplication.
|
||||||
|
cov_mark::hit!(overflow_but_not_me);
|
||||||
|
return ExpandResult::only_err(ExpandError::RecursionOverflowPosioned);
|
||||||
|
} else if self.recursion_limit(db).check(self.recursion_depth + 1).is_err() {
|
||||||
|
self.recursion_depth = usize::MAX;
|
||||||
|
cov_mark::hit!(your_stack_belongs_to_me);
|
||||||
|
return ExpandResult::only_err(ExpandError::Other(
|
||||||
|
"reached recursion limit during macro expansion".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let ExpandResult { value, err } = op(self);
|
||||||
|
let Some(call_id) = value else {
|
||||||
|
return ExpandResult { value: None, err };
|
||||||
|
};
|
||||||
|
|
||||||
|
Self::enter_expand_inner(db, call_id, err).map(|value| {
|
||||||
|
value.and_then(|(new_file_id, node)| {
|
||||||
|
let node = T::cast(node)?;
|
||||||
|
|
||||||
|
self.recursion_depth += 1;
|
||||||
|
self.cfg_expander.hygiene = Hygiene::new(db.upcast(), new_file_id);
|
||||||
|
let old_file_id = std::mem::replace(&mut self.current_file_id, new_file_id);
|
||||||
|
let mark =
|
||||||
|
Mark { file_id: old_file_id, bomb: DropBomb::new("expansion mark dropped") };
|
||||||
|
Some((mark, node))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
@ -624,6 +624,10 @@ impl ExprCollector<'_> {
|
||||||
krate: *krate,
|
krate: *krate,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Some(ExpandError::RecursionOverflowPosioned) => {
|
||||||
|
// Recursion limit has been reached in the macro expansion tree, but not in
|
||||||
|
// this very macro call. Don't add diagnostics to avoid duplication.
|
||||||
|
}
|
||||||
Some(err) => {
|
Some(err) => {
|
||||||
self.source_map.diagnostics.push(BodyDiagnostic::MacroError {
|
self.source_map.diagnostics.push(BodyDiagnostic::MacroError {
|
||||||
node: InFile::new(outer_file, syntax_ptr),
|
node: InFile::new(outer_file, syntax_ptr),
|
||||||
|
@ -636,6 +640,8 @@ impl ExprCollector<'_> {
|
||||||
|
|
||||||
match res.value {
|
match res.value {
|
||||||
Some((mark, expansion)) => {
|
Some((mark, expansion)) => {
|
||||||
|
// Keep collecting even with expansion errors so we can provide completions and
|
||||||
|
// other services in incomplete macro expressions.
|
||||||
self.source_map.expansions.insert(macro_call_ptr, self.expander.current_file_id);
|
self.source_map.expansions.insert(macro_call_ptr, self.expander.current_file_id);
|
||||||
let prev_ast_id_map = mem::replace(
|
let prev_ast_id_map = mem::replace(
|
||||||
&mut self.ast_id_map,
|
&mut self.ast_id_map,
|
||||||
|
|
|
@ -61,6 +61,19 @@ fn main() { n_nuple!(1,2,3); }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn your_stack_belongs_to_me2() {
|
||||||
|
cov_mark::check!(overflow_but_not_me);
|
||||||
|
lower(
|
||||||
|
r#"
|
||||||
|
macro_rules! foo {
|
||||||
|
() => {{ foo!(); foo!(); }}
|
||||||
|
}
|
||||||
|
fn main() { foo!(); }
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn recursion_limit() {
|
fn recursion_limit() {
|
||||||
cov_mark::check!(your_stack_belongs_to_me);
|
cov_mark::check!(your_stack_belongs_to_me);
|
||||||
|
|
|
@ -659,15 +659,16 @@ fn desugar_future_path(orig: TypeRef) -> Path {
|
||||||
let path = path![core::future::Future];
|
let path = path![core::future::Future];
|
||||||
let mut generic_args: Vec<_> =
|
let mut generic_args: Vec<_> =
|
||||||
std::iter::repeat(None).take(path.segments().len() - 1).collect();
|
std::iter::repeat(None).take(path.segments().len() - 1).collect();
|
||||||
let mut last = GenericArgs::empty();
|
|
||||||
let binding = AssociatedTypeBinding {
|
let binding = AssociatedTypeBinding {
|
||||||
name: name![Output],
|
name: name![Output],
|
||||||
args: None,
|
args: None,
|
||||||
type_ref: Some(orig),
|
type_ref: Some(orig),
|
||||||
bounds: Vec::new(),
|
bounds: Box::default(),
|
||||||
};
|
};
|
||||||
last.bindings.push(binding);
|
generic_args.push(Some(Interned::new(GenericArgs {
|
||||||
generic_args.push(Some(Interned::new(last)));
|
bindings: Box::new([binding]),
|
||||||
|
..GenericArgs::empty()
|
||||||
|
})));
|
||||||
|
|
||||||
Path::from_known_path(path, generic_args)
|
Path::from_known_path(path, generic_args)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1476,7 +1476,7 @@ macro_rules! m {
|
||||||
/* parse error: expected identifier */
|
/* parse error: expected identifier */
|
||||||
/* parse error: expected SEMICOLON */
|
/* parse error: expected SEMICOLON */
|
||||||
/* parse error: expected SEMICOLON */
|
/* parse error: expected SEMICOLON */
|
||||||
/* parse error: expected expression */
|
/* parse error: expected expression, item or let statement */
|
||||||
fn f() {
|
fn f() {
|
||||||
K::(C("0"));
|
K::(C("0"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -830,8 +830,7 @@ macro_rules! rgb_color {
|
||||||
/* parse error: expected COMMA */
|
/* parse error: expected COMMA */
|
||||||
/* parse error: expected R_ANGLE */
|
/* parse error: expected R_ANGLE */
|
||||||
/* parse error: expected SEMICOLON */
|
/* parse error: expected SEMICOLON */
|
||||||
/* parse error: expected SEMICOLON */
|
/* parse error: expected expression, item or let statement */
|
||||||
/* parse error: expected expression */
|
|
||||||
pub fn new() {
|
pub fn new() {
|
||||||
let _ = 0as u32<<(8+8);
|
let _ = 0as u32<<(8+8);
|
||||||
}
|
}
|
||||||
|
@ -848,21 +847,21 @@ pub fn new() {
|
||||||
// BLOCK_EXPR@10..31
|
// BLOCK_EXPR@10..31
|
||||||
// STMT_LIST@10..31
|
// STMT_LIST@10..31
|
||||||
// L_CURLY@10..11 "{"
|
// L_CURLY@10..11 "{"
|
||||||
// LET_STMT@11..27
|
// LET_STMT@11..28
|
||||||
// LET_KW@11..14 "let"
|
// LET_KW@11..14 "let"
|
||||||
// WILDCARD_PAT@14..15
|
// WILDCARD_PAT@14..15
|
||||||
// UNDERSCORE@14..15 "_"
|
// UNDERSCORE@14..15 "_"
|
||||||
// EQ@15..16 "="
|
// EQ@15..16 "="
|
||||||
// CAST_EXPR@16..27
|
// CAST_EXPR@16..28
|
||||||
// LITERAL@16..17
|
// LITERAL@16..17
|
||||||
// INT_NUMBER@16..17 "0"
|
// INT_NUMBER@16..17 "0"
|
||||||
// AS_KW@17..19 "as"
|
// AS_KW@17..19 "as"
|
||||||
// PATH_TYPE@19..27
|
// PATH_TYPE@19..28
|
||||||
// PATH@19..27
|
// PATH@19..28
|
||||||
// PATH_SEGMENT@19..27
|
// PATH_SEGMENT@19..28
|
||||||
// NAME_REF@19..22
|
// NAME_REF@19..22
|
||||||
// IDENT@19..22 "u32"
|
// IDENT@19..22 "u32"
|
||||||
// GENERIC_ARG_LIST@22..27
|
// GENERIC_ARG_LIST@22..28
|
||||||
// L_ANGLE@22..23 "<"
|
// L_ANGLE@22..23 "<"
|
||||||
// TYPE_ARG@23..27
|
// TYPE_ARG@23..27
|
||||||
// DYN_TRAIT_TYPE@23..27
|
// DYN_TRAIT_TYPE@23..27
|
||||||
|
@ -877,7 +876,7 @@ pub fn new() {
|
||||||
// ERROR@25..26
|
// ERROR@25..26
|
||||||
// INT_NUMBER@25..26 "8"
|
// INT_NUMBER@25..26 "8"
|
||||||
// PLUS@26..27 "+"
|
// PLUS@26..27 "+"
|
||||||
// EXPR_STMT@27..28
|
// CONST_ARG@27..28
|
||||||
// LITERAL@27..28
|
// LITERAL@27..28
|
||||||
// INT_NUMBER@27..28 "8"
|
// INT_NUMBER@27..28 "8"
|
||||||
// ERROR@28..29
|
// ERROR@28..29
|
||||||
|
|
|
@ -38,18 +38,18 @@ impl Display for ImportAlias {
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct Path {
|
pub struct Path {
|
||||||
/// Type based path like `<T>::foo`.
|
/// Type based path like `<T>::foo`.
|
||||||
/// Note that paths like `<Type as Trait>::foo` are desugard to `Trait::<Self=Type>::foo`.
|
/// Note that paths like `<Type as Trait>::foo` are desugared to `Trait::<Self=Type>::foo`.
|
||||||
type_anchor: Option<Interned<TypeRef>>,
|
type_anchor: Option<Interned<TypeRef>>,
|
||||||
mod_path: Interned<ModPath>,
|
mod_path: Interned<ModPath>,
|
||||||
/// Invariant: the same len as `self.mod_path.segments`
|
/// Invariant: the same len as `self.mod_path.segments` or `None` if all segments are `None`.
|
||||||
generic_args: Box<[Option<Interned<GenericArgs>>]>,
|
generic_args: Option<Box<[Option<Interned<GenericArgs>>]>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generic arguments to a path segment (e.g. the `i32` in `Option<i32>`). This
|
/// Generic arguments to a path segment (e.g. the `i32` in `Option<i32>`). This
|
||||||
/// also includes bindings of associated types, like in `Iterator<Item = Foo>`.
|
/// also includes bindings of associated types, like in `Iterator<Item = Foo>`.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct GenericArgs {
|
pub struct GenericArgs {
|
||||||
pub args: Vec<GenericArg>,
|
pub args: Box<[GenericArg]>,
|
||||||
/// This specifies whether the args contain a Self type as the first
|
/// This specifies whether the args contain a Self type as the first
|
||||||
/// element. This is the case for path segments like `<T as Trait>`, where
|
/// element. This is the case for path segments like `<T as Trait>`, where
|
||||||
/// `T` is actually a type parameter for the path `Trait` specifying the
|
/// `T` is actually a type parameter for the path `Trait` specifying the
|
||||||
|
@ -57,7 +57,7 @@ pub struct GenericArgs {
|
||||||
/// is left out.
|
/// is left out.
|
||||||
pub has_self_type: bool,
|
pub has_self_type: bool,
|
||||||
/// Associated type bindings like in `Iterator<Item = T>`.
|
/// Associated type bindings like in `Iterator<Item = T>`.
|
||||||
pub bindings: Vec<AssociatedTypeBinding>,
|
pub bindings: Box<[AssociatedTypeBinding]>,
|
||||||
/// Whether these generic args were desugared from `Trait(Arg) -> Output`
|
/// Whether these generic args were desugared from `Trait(Arg) -> Output`
|
||||||
/// parenthesis notation typically used for the `Fn` traits.
|
/// parenthesis notation typically used for the `Fn` traits.
|
||||||
pub desugared_from_fn: bool,
|
pub desugared_from_fn: bool,
|
||||||
|
@ -77,7 +77,7 @@ pub struct AssociatedTypeBinding {
|
||||||
/// Bounds for the associated type, like in `Iterator<Item:
|
/// Bounds for the associated type, like in `Iterator<Item:
|
||||||
/// SomeOtherTrait>`. (This is the unstable `associated_type_bounds`
|
/// SomeOtherTrait>`. (This is the unstable `associated_type_bounds`
|
||||||
/// feature.)
|
/// feature.)
|
||||||
pub bounds: Vec<Interned<TypeBound>>,
|
pub bounds: Box<[Interned<TypeBound>]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A single generic argument.
|
/// A single generic argument.
|
||||||
|
@ -102,7 +102,7 @@ impl Path {
|
||||||
) -> Path {
|
) -> Path {
|
||||||
let generic_args = generic_args.into();
|
let generic_args = generic_args.into();
|
||||||
assert_eq!(path.len(), generic_args.len());
|
assert_eq!(path.len(), generic_args.len());
|
||||||
Path { type_anchor: None, mod_path: Interned::new(path), generic_args }
|
Path { type_anchor: None, mod_path: Interned::new(path), generic_args: Some(generic_args) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn kind(&self) -> &PathKind {
|
pub fn kind(&self) -> &PathKind {
|
||||||
|
@ -114,7 +114,14 @@ impl Path {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn segments(&self) -> PathSegments<'_> {
|
pub fn segments(&self) -> PathSegments<'_> {
|
||||||
PathSegments { segments: self.mod_path.segments(), generic_args: &self.generic_args }
|
let s = PathSegments {
|
||||||
|
segments: self.mod_path.segments(),
|
||||||
|
generic_args: self.generic_args.as_deref(),
|
||||||
|
};
|
||||||
|
if let Some(generic_args) = s.generic_args {
|
||||||
|
assert_eq!(s.segments.len(), generic_args.len());
|
||||||
|
}
|
||||||
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mod_path(&self) -> &ModPath {
|
pub fn mod_path(&self) -> &ModPath {
|
||||||
|
@ -131,13 +138,15 @@ impl Path {
|
||||||
self.mod_path.kind,
|
self.mod_path.kind,
|
||||||
self.mod_path.segments()[..self.mod_path.segments().len() - 1].iter().cloned(),
|
self.mod_path.segments()[..self.mod_path.segments().len() - 1].iter().cloned(),
|
||||||
)),
|
)),
|
||||||
generic_args: self.generic_args[..self.generic_args.len() - 1].to_vec().into(),
|
generic_args: self.generic_args.as_ref().map(|it| it[..it.len() - 1].to_vec().into()),
|
||||||
};
|
};
|
||||||
Some(res)
|
Some(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_self_type(&self) -> bool {
|
pub fn is_self_type(&self) -> bool {
|
||||||
self.type_anchor.is_none() && *self.generic_args == [None] && self.mod_path.is_Self()
|
self.type_anchor.is_none()
|
||||||
|
&& self.generic_args.as_deref().is_none()
|
||||||
|
&& self.mod_path.is_Self()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,11 +158,11 @@ pub struct PathSegment<'a> {
|
||||||
|
|
||||||
pub struct PathSegments<'a> {
|
pub struct PathSegments<'a> {
|
||||||
segments: &'a [Name],
|
segments: &'a [Name],
|
||||||
generic_args: &'a [Option<Interned<GenericArgs>>],
|
generic_args: Option<&'a [Option<Interned<GenericArgs>>]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PathSegments<'a> {
|
impl<'a> PathSegments<'a> {
|
||||||
pub const EMPTY: PathSegments<'static> = PathSegments { segments: &[], generic_args: &[] };
|
pub const EMPTY: PathSegments<'static> = PathSegments { segments: &[], generic_args: None };
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.len() == 0
|
self.len() == 0
|
||||||
}
|
}
|
||||||
|
@ -167,26 +176,29 @@ impl<'a> PathSegments<'a> {
|
||||||
self.get(self.len().checked_sub(1)?)
|
self.get(self.len().checked_sub(1)?)
|
||||||
}
|
}
|
||||||
pub fn get(&self, idx: usize) -> Option<PathSegment<'a>> {
|
pub fn get(&self, idx: usize) -> Option<PathSegment<'a>> {
|
||||||
assert_eq!(self.segments.len(), self.generic_args.len());
|
|
||||||
let res = PathSegment {
|
let res = PathSegment {
|
||||||
name: self.segments.get(idx)?,
|
name: self.segments.get(idx)?,
|
||||||
args_and_bindings: self.generic_args.get(idx).unwrap().as_ref().map(|it| &**it),
|
args_and_bindings: self.generic_args.and_then(|it| it.get(idx)?.as_deref()),
|
||||||
};
|
};
|
||||||
Some(res)
|
Some(res)
|
||||||
}
|
}
|
||||||
pub fn skip(&self, len: usize) -> PathSegments<'a> {
|
pub fn skip(&self, len: usize) -> PathSegments<'a> {
|
||||||
assert_eq!(self.segments.len(), self.generic_args.len());
|
PathSegments {
|
||||||
PathSegments { segments: &self.segments[len..], generic_args: &self.generic_args[len..] }
|
segments: &self.segments.get(len..).unwrap_or(&[]),
|
||||||
|
generic_args: self.generic_args.and_then(|it| it.get(len..)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub fn take(&self, len: usize) -> PathSegments<'a> {
|
pub fn take(&self, len: usize) -> PathSegments<'a> {
|
||||||
assert_eq!(self.segments.len(), self.generic_args.len());
|
PathSegments {
|
||||||
PathSegments { segments: &self.segments[..len], generic_args: &self.generic_args[..len] }
|
segments: &self.segments.get(..len).unwrap_or(&self.segments),
|
||||||
|
generic_args: self.generic_args.map(|it| it.get(..len).unwrap_or(it)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub fn iter(&self) -> impl Iterator<Item = PathSegment<'a>> {
|
pub fn iter(&self) -> impl Iterator<Item = PathSegment<'a>> {
|
||||||
self.segments.iter().zip(self.generic_args.iter()).map(|(name, args)| PathSegment {
|
self.segments
|
||||||
name,
|
.iter()
|
||||||
args_and_bindings: args.as_ref().map(|it| &**it),
|
.zip(self.generic_args.into_iter().flatten().chain(iter::repeat(&None)))
|
||||||
})
|
.map(|(name, args)| PathSegment { name, args_and_bindings: args.as_deref() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,9 +212,9 @@ impl GenericArgs {
|
||||||
|
|
||||||
pub(crate) fn empty() -> GenericArgs {
|
pub(crate) fn empty() -> GenericArgs {
|
||||||
GenericArgs {
|
GenericArgs {
|
||||||
args: Vec::new(),
|
args: Box::default(),
|
||||||
has_self_type: false,
|
has_self_type: false,
|
||||||
bindings: Vec::new(),
|
bindings: Box::default(),
|
||||||
desugared_from_fn: false,
|
desugared_from_fn: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -213,7 +225,7 @@ impl From<Name> for Path {
|
||||||
Path {
|
Path {
|
||||||
type_anchor: None,
|
type_anchor: None,
|
||||||
mod_path: Interned::new(ModPath::from_segments(PathKind::Plain, iter::once(name))),
|
mod_path: Interned::new(ModPath::from_segments(PathKind::Plain, iter::once(name))),
|
||||||
generic_args: Box::new([None]),
|
generic_args: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
//! Transforms syntax into `Path` objects, ideally with accounting for hygiene
|
//! Transforms syntax into `Path` objects, ideally with accounting for hygiene
|
||||||
|
|
||||||
|
use std::iter;
|
||||||
|
|
||||||
use crate::type_ref::ConstScalarOrPath;
|
use crate::type_ref::ConstScalarOrPath;
|
||||||
|
|
||||||
use either::Either;
|
use either::Either;
|
||||||
|
@ -45,8 +47,11 @@ pub(super) fn lower_path(mut path: ast::Path, ctx: &LowerCtx<'_>) -> Option<Path
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.map(Interned::new);
|
.map(Interned::new);
|
||||||
|
if let Some(_) = args {
|
||||||
|
generic_args.resize(segments.len(), None);
|
||||||
|
generic_args.push(args);
|
||||||
|
}
|
||||||
segments.push(name);
|
segments.push(name);
|
||||||
generic_args.push(args)
|
|
||||||
}
|
}
|
||||||
Either::Right(crate_id) => {
|
Either::Right(crate_id) => {
|
||||||
kind = PathKind::DollarCrate(crate_id);
|
kind = PathKind::DollarCrate(crate_id);
|
||||||
|
@ -56,7 +61,6 @@ pub(super) fn lower_path(mut path: ast::Path, ctx: &LowerCtx<'_>) -> Option<Path
|
||||||
}
|
}
|
||||||
ast::PathSegmentKind::SelfTypeKw => {
|
ast::PathSegmentKind::SelfTypeKw => {
|
||||||
segments.push(name![Self]);
|
segments.push(name![Self]);
|
||||||
generic_args.push(None)
|
|
||||||
}
|
}
|
||||||
ast::PathSegmentKind::Type { type_ref, trait_ref } => {
|
ast::PathSegmentKind::Type { type_ref, trait_ref } => {
|
||||||
assert!(path.qualifier().is_none()); // this can only occur at the first segment
|
assert!(path.qualifier().is_none()); // this can only occur at the first segment
|
||||||
|
@ -77,18 +81,33 @@ pub(super) fn lower_path(mut path: ast::Path, ctx: &LowerCtx<'_>) -> Option<Path
|
||||||
kind = mod_path.kind;
|
kind = mod_path.kind;
|
||||||
|
|
||||||
segments.extend(mod_path.segments().iter().cloned().rev());
|
segments.extend(mod_path.segments().iter().cloned().rev());
|
||||||
|
if let Some(path_generic_args) = path_generic_args {
|
||||||
|
generic_args.resize(segments.len() - num_segments, None);
|
||||||
generic_args.extend(Vec::from(path_generic_args).into_iter().rev());
|
generic_args.extend(Vec::from(path_generic_args).into_iter().rev());
|
||||||
|
} else {
|
||||||
|
generic_args.resize(segments.len(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let self_type = GenericArg::Type(self_type);
|
||||||
|
|
||||||
// Insert the type reference (T in the above example) as Self parameter for the trait
|
// Insert the type reference (T in the above example) as Self parameter for the trait
|
||||||
let last_segment =
|
let last_segment = generic_args.get_mut(segments.len() - num_segments)?;
|
||||||
generic_args.iter_mut().rev().nth(num_segments.saturating_sub(1))?;
|
*last_segment = Some(Interned::new(match last_segment.take() {
|
||||||
let mut args_inner = match last_segment {
|
Some(it) => GenericArgs {
|
||||||
Some(it) => it.as_ref().clone(),
|
args: iter::once(self_type)
|
||||||
None => GenericArgs::empty(),
|
.chain(it.args.iter().cloned())
|
||||||
};
|
.collect(),
|
||||||
args_inner.has_self_type = true;
|
|
||||||
args_inner.args.insert(0, GenericArg::Type(self_type));
|
has_self_type: true,
|
||||||
*last_segment = Some(Interned::new(args_inner));
|
bindings: it.bindings.clone(),
|
||||||
|
desugared_from_fn: it.desugared_from_fn,
|
||||||
|
},
|
||||||
|
None => GenericArgs {
|
||||||
|
args: Box::new([self_type]),
|
||||||
|
has_self_type: true,
|
||||||
|
..GenericArgs::empty()
|
||||||
|
},
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,7 +134,10 @@ pub(super) fn lower_path(mut path: ast::Path, ctx: &LowerCtx<'_>) -> Option<Path
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
segments.reverse();
|
segments.reverse();
|
||||||
|
if !generic_args.is_empty() {
|
||||||
|
generic_args.resize(segments.len(), None);
|
||||||
generic_args.reverse();
|
generic_args.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
if segments.is_empty() && kind == PathKind::Plain && type_anchor.is_none() {
|
if segments.is_empty() && kind == PathKind::Plain && type_anchor.is_none() {
|
||||||
// plain empty paths don't exist, this means we got a single `self` segment as our path
|
// plain empty paths don't exist, this means we got a single `self` segment as our path
|
||||||
|
@ -135,7 +157,11 @@ pub(super) fn lower_path(mut path: ast::Path, ctx: &LowerCtx<'_>) -> Option<Path
|
||||||
}
|
}
|
||||||
|
|
||||||
let mod_path = Interned::new(ModPath::from_segments(kind, segments));
|
let mod_path = Interned::new(ModPath::from_segments(kind, segments));
|
||||||
return Some(Path { type_anchor, mod_path, generic_args: generic_args.into() });
|
return Some(Path {
|
||||||
|
type_anchor,
|
||||||
|
mod_path,
|
||||||
|
generic_args: if generic_args.is_empty() { None } else { Some(generic_args.into()) },
|
||||||
|
});
|
||||||
|
|
||||||
fn qualifier(path: &ast::Path) -> Option<ast::Path> {
|
fn qualifier(path: &ast::Path) -> Option<ast::Path> {
|
||||||
if let Some(q) = path.qualifier() {
|
if let Some(q) = path.qualifier() {
|
||||||
|
@ -174,7 +200,7 @@ pub(super) fn lower_generic_args(
|
||||||
.map(|it| Interned::new(TypeBound::from_ast(lower_ctx, it)))
|
.map(|it| Interned::new(TypeBound::from_ast(lower_ctx, it)))
|
||||||
.collect()
|
.collect()
|
||||||
} else {
|
} else {
|
||||||
Vec::new()
|
Box::default()
|
||||||
};
|
};
|
||||||
bindings.push(AssociatedTypeBinding { name, args, type_ref, bounds });
|
bindings.push(AssociatedTypeBinding { name, args, type_ref, bounds });
|
||||||
}
|
}
|
||||||
|
@ -195,7 +221,12 @@ pub(super) fn lower_generic_args(
|
||||||
if args.is_empty() && bindings.is_empty() {
|
if args.is_empty() && bindings.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
Some(GenericArgs { args, has_self_type: false, bindings, desugared_from_fn: false })
|
Some(GenericArgs {
|
||||||
|
args: args.into_boxed_slice(),
|
||||||
|
has_self_type: false,
|
||||||
|
bindings: bindings.into_boxed_slice(),
|
||||||
|
desugared_from_fn: false,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collect `GenericArgs` from the parts of a fn-like path, i.e. `Fn(X, Y)
|
/// Collect `GenericArgs` from the parts of a fn-like path, i.e. `Fn(X, Y)
|
||||||
|
@ -205,33 +236,30 @@ fn lower_generic_args_from_fn_path(
|
||||||
params: Option<ast::ParamList>,
|
params: Option<ast::ParamList>,
|
||||||
ret_type: Option<ast::RetType>,
|
ret_type: Option<ast::RetType>,
|
||||||
) -> Option<GenericArgs> {
|
) -> Option<GenericArgs> {
|
||||||
let mut args = Vec::new();
|
|
||||||
let mut bindings = Vec::new();
|
|
||||||
let params = params?;
|
let params = params?;
|
||||||
let mut param_types = Vec::new();
|
let mut param_types = Vec::new();
|
||||||
for param in params.params() {
|
for param in params.params() {
|
||||||
let type_ref = TypeRef::from_ast_opt(ctx, param.ty());
|
let type_ref = TypeRef::from_ast_opt(ctx, param.ty());
|
||||||
param_types.push(type_ref);
|
param_types.push(type_ref);
|
||||||
}
|
}
|
||||||
let arg = GenericArg::Type(TypeRef::Tuple(param_types));
|
let args = Box::new([GenericArg::Type(TypeRef::Tuple(param_types))]);
|
||||||
args.push(arg);
|
let bindings = if let Some(ret_type) = ret_type {
|
||||||
if let Some(ret_type) = ret_type {
|
|
||||||
let type_ref = TypeRef::from_ast_opt(ctx, ret_type.ty());
|
let type_ref = TypeRef::from_ast_opt(ctx, ret_type.ty());
|
||||||
bindings.push(AssociatedTypeBinding {
|
Box::new([AssociatedTypeBinding {
|
||||||
name: name![Output],
|
name: name![Output],
|
||||||
args: None,
|
args: None,
|
||||||
type_ref: Some(type_ref),
|
type_ref: Some(type_ref),
|
||||||
bounds: Vec::new(),
|
bounds: Box::default(),
|
||||||
});
|
}])
|
||||||
} else {
|
} else {
|
||||||
// -> ()
|
// -> ()
|
||||||
let type_ref = TypeRef::Tuple(Vec::new());
|
let type_ref = TypeRef::Tuple(Vec::new());
|
||||||
bindings.push(AssociatedTypeBinding {
|
Box::new([AssociatedTypeBinding {
|
||||||
name: name![Output],
|
name: name![Output],
|
||||||
args: None,
|
args: None,
|
||||||
type_ref: Some(type_ref),
|
type_ref: Some(type_ref),
|
||||||
bounds: Vec::new(),
|
bounds: Box::default(),
|
||||||
});
|
}])
|
||||||
}
|
};
|
||||||
Some(GenericArgs { args, has_self_type: false, bindings, desugared_from_fn: true })
|
Some(GenericArgs { args, has_self_type: false, bindings, desugared_from_fn: true })
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,7 @@ pub(crate) fn print_generic_args(generics: &GenericArgs, buf: &mut dyn Write) ->
|
||||||
first = false;
|
first = false;
|
||||||
print_generic_arg(arg, buf)?;
|
print_generic_arg(arg, buf)?;
|
||||||
}
|
}
|
||||||
for binding in &generics.bindings {
|
for binding in generics.bindings.iter() {
|
||||||
if !first {
|
if !first {
|
||||||
write!(buf, ", ")?;
|
write!(buf, ", ")?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -292,7 +292,7 @@ impl TypeRef {
|
||||||
}
|
}
|
||||||
for segment in path.segments().iter() {
|
for segment in path.segments().iter() {
|
||||||
if let Some(args_and_bindings) = segment.args_and_bindings {
|
if let Some(args_and_bindings) = segment.args_and_bindings {
|
||||||
for arg in &args_and_bindings.args {
|
for arg in args_and_bindings.args.iter() {
|
||||||
match arg {
|
match arg {
|
||||||
crate::path::GenericArg::Type(type_ref) => {
|
crate::path::GenericArg::Type(type_ref) => {
|
||||||
go(type_ref, f);
|
go(type_ref, f);
|
||||||
|
@ -301,11 +301,11 @@ impl TypeRef {
|
||||||
| crate::path::GenericArg::Lifetime(_) => {}
|
| crate::path::GenericArg::Lifetime(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for binding in &args_and_bindings.bindings {
|
for binding in args_and_bindings.bindings.iter() {
|
||||||
if let Some(type_ref) = &binding.type_ref {
|
if let Some(type_ref) = &binding.type_ref {
|
||||||
go(type_ref, f);
|
go(type_ref, f);
|
||||||
}
|
}
|
||||||
for bound in &binding.bounds {
|
for bound in binding.bounds.iter() {
|
||||||
match bound.as_ref() {
|
match bound.as_ref() {
|
||||||
TypeBound::Path(path, _) | TypeBound::ForLifetime(_, path) => {
|
TypeBound::Path(path, _) | TypeBound::ForLifetime(_, path) => {
|
||||||
go_path(path, f)
|
go_path(path, f)
|
||||||
|
|
|
@ -21,7 +21,7 @@ itertools = "0.10.5"
|
||||||
hashbrown = { version = "0.12.1", features = [
|
hashbrown = { version = "0.12.1", features = [
|
||||||
"inline-more",
|
"inline-more",
|
||||||
], default-features = false }
|
], default-features = false }
|
||||||
smallvec = { version = "1.10.0", features = ["const_new"] }
|
smallvec.workspace = true
|
||||||
|
|
||||||
# local deps
|
# local deps
|
||||||
stdx.workspace = true
|
stdx.workspace = true
|
||||||
|
|
|
@ -55,6 +55,7 @@ pub type ExpandResult<T> = ValueResult<T, ExpandError>;
|
||||||
pub enum ExpandError {
|
pub enum ExpandError {
|
||||||
UnresolvedProcMacro(CrateId),
|
UnresolvedProcMacro(CrateId),
|
||||||
Mbe(mbe::ExpandError),
|
Mbe(mbe::ExpandError),
|
||||||
|
RecursionOverflowPosioned,
|
||||||
Other(Box<str>),
|
Other(Box<str>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,6 +70,9 @@ impl fmt::Display for ExpandError {
|
||||||
match self {
|
match self {
|
||||||
ExpandError::UnresolvedProcMacro(_) => f.write_str("unresolved proc-macro"),
|
ExpandError::UnresolvedProcMacro(_) => f.write_str("unresolved proc-macro"),
|
||||||
ExpandError::Mbe(it) => it.fmt(f),
|
ExpandError::Mbe(it) => it.fmt(f),
|
||||||
|
ExpandError::RecursionOverflowPosioned => {
|
||||||
|
f.write_str("overflow expanding the original macro")
|
||||||
|
}
|
||||||
ExpandError::Other(it) => f.write_str(it),
|
ExpandError::Other(it) => f.write_str(it),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ cov-mark = "2.0.0-pre.1"
|
||||||
itertools = "0.10.5"
|
itertools = "0.10.5"
|
||||||
arrayvec = "0.7.2"
|
arrayvec = "0.7.2"
|
||||||
bitflags = "1.3.2"
|
bitflags = "1.3.2"
|
||||||
smallvec = "1.10.0"
|
smallvec.workspace = true
|
||||||
ena = "0.14.0"
|
ena = "0.14.0"
|
||||||
tracing = "0.1.35"
|
tracing = "0.1.35"
|
||||||
rustc-hash = "1.1.0"
|
rustc-hash = "1.1.0"
|
||||||
|
|
|
@ -1419,7 +1419,7 @@ impl HirDisplay for Path {
|
||||||
|
|
||||||
write!(f, "<")?;
|
write!(f, "<")?;
|
||||||
let mut first = true;
|
let mut first = true;
|
||||||
for arg in &generic_args.args {
|
for arg in generic_args.args.iter() {
|
||||||
if first {
|
if first {
|
||||||
first = false;
|
first = false;
|
||||||
if generic_args.has_self_type {
|
if generic_args.has_self_type {
|
||||||
|
@ -1431,7 +1431,7 @@ impl HirDisplay for Path {
|
||||||
}
|
}
|
||||||
arg.hir_fmt(f)?;
|
arg.hir_fmt(f)?;
|
||||||
}
|
}
|
||||||
for binding in &generic_args.bindings {
|
for binding in generic_args.bindings.iter() {
|
||||||
if first {
|
if first {
|
||||||
first = false;
|
first = false;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1445,7 +1445,7 @@ impl HirDisplay for Path {
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
write!(f, ": ")?;
|
write!(f, ": ")?;
|
||||||
f.write_joined(&binding.bounds, " + ")?;
|
f.write_joined(binding.bounds.iter(), " + ")?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1025,7 +1025,7 @@ impl<'a> TyLoweringContext<'a> {
|
||||||
last_segment
|
last_segment
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|segment| segment.args_and_bindings)
|
.filter_map(|segment| segment.args_and_bindings)
|
||||||
.flat_map(|args_and_bindings| &args_and_bindings.bindings)
|
.flat_map(|args_and_bindings| args_and_bindings.bindings.iter())
|
||||||
.flat_map(move |binding| {
|
.flat_map(move |binding| {
|
||||||
let found = associated_type_by_name_including_super_traits(
|
let found = associated_type_by_name_including_super_traits(
|
||||||
self.db,
|
self.db,
|
||||||
|
@ -1068,7 +1068,7 @@ impl<'a> TyLoweringContext<'a> {
|
||||||
AliasEq { alias: AliasTy::Projection(projection_ty.clone()), ty };
|
AliasEq { alias: AliasTy::Projection(projection_ty.clone()), ty };
|
||||||
preds.push(crate::wrap_empty_binders(WhereClause::AliasEq(alias_eq)));
|
preds.push(crate::wrap_empty_binders(WhereClause::AliasEq(alias_eq)));
|
||||||
}
|
}
|
||||||
for bound in &binding.bounds {
|
for bound in binding.bounds.iter() {
|
||||||
preds.extend(self.lower_type_bound(
|
preds.extend(self.lower_type_bound(
|
||||||
bound,
|
bound,
|
||||||
TyKind::Alias(AliasTy::Projection(projection_ty.clone())).intern(Interner),
|
TyKind::Alias(AliasTy::Projection(projection_ty.clone())).intern(Interner),
|
||||||
|
|
|
@ -16,7 +16,7 @@ rustc-hash = "1.1.0"
|
||||||
either = "1.7.0"
|
either = "1.7.0"
|
||||||
arrayvec = "0.7.2"
|
arrayvec = "0.7.2"
|
||||||
itertools = "0.10.5"
|
itertools = "0.10.5"
|
||||||
smallvec = "1.10.0"
|
smallvec.workspace = true
|
||||||
once_cell = "1.17.0"
|
once_cell = "1.17.0"
|
||||||
|
|
||||||
# local deps
|
# local deps
|
||||||
|
|
|
@ -16,7 +16,7 @@ cov-mark = "2.0.0-pre.1"
|
||||||
|
|
||||||
itertools = "0.10.5"
|
itertools = "0.10.5"
|
||||||
either = "1.7.0"
|
either = "1.7.0"
|
||||||
smallvec = "1.10.0"
|
smallvec.workspace = true
|
||||||
|
|
||||||
# local deps
|
# local deps
|
||||||
stdx.workspace = true
|
stdx.workspace = true
|
||||||
|
|
|
@ -180,8 +180,10 @@ pub(crate) fn generate_getter_impl(
|
||||||
|
|
||||||
// Insert `$0` only for last getter we generate
|
// Insert `$0` only for last getter we generate
|
||||||
if i == record_fields_count - 1 {
|
if i == record_fields_count - 1 {
|
||||||
|
if ctx.config.snippet_cap.is_some() {
|
||||||
getter_buf = getter_buf.replacen("fn ", "fn $0", 1);
|
getter_buf = getter_buf.replacen("fn ", "fn $0", 1);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// For first element we do not merge with '\n', as
|
// For first element we do not merge with '\n', as
|
||||||
// that can be inserted by impl_def check defined
|
// that can be inserted by impl_def check defined
|
||||||
|
@ -330,7 +332,7 @@ fn parse_record_field(record_field: ast::RecordField, mutable: bool) -> Option<R
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::tests::{check_assist, check_assist_not_applicable};
|
use crate::tests::{check_assist, check_assist_no_snippet_cap, check_assist_not_applicable};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
@ -377,6 +379,49 @@ impl Context {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_generate_getter_from_field_no_snippet_cap() {
|
||||||
|
check_assist_no_snippet_cap(
|
||||||
|
generate_getter,
|
||||||
|
r#"
|
||||||
|
struct Context {
|
||||||
|
dat$0a: Data,
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
struct Context {
|
||||||
|
data: Data,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Context {
|
||||||
|
fn data(&self) -> &Data {
|
||||||
|
&self.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
check_assist_no_snippet_cap(
|
||||||
|
generate_getter_mut,
|
||||||
|
r#"
|
||||||
|
struct Context {
|
||||||
|
dat$0a: Data,
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
struct Context {
|
||||||
|
data: Data,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Context {
|
||||||
|
fn data_mut(&mut self) -> &mut Data {
|
||||||
|
&mut self.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_generate_getter_already_implemented() {
|
fn test_generate_getter_already_implemented() {
|
||||||
check_assist_not_applicable(
|
check_assist_not_applicable(
|
||||||
|
@ -433,6 +478,29 @@ impl Context {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_generate_getter_from_field_with_visibility_marker_no_snippet_cap() {
|
||||||
|
check_assist_no_snippet_cap(
|
||||||
|
generate_getter,
|
||||||
|
r#"
|
||||||
|
pub(crate) struct Context {
|
||||||
|
dat$0a: Data,
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
pub(crate) struct Context {
|
||||||
|
data: Data,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Context {
|
||||||
|
pub(crate) fn data(&self) -> &Data {
|
||||||
|
&self.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_multiple_generate_getter() {
|
fn test_multiple_generate_getter() {
|
||||||
check_assist(
|
check_assist(
|
||||||
|
@ -468,6 +536,41 @@ impl Context {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multiple_generate_getter_no_snippet_cap() {
|
||||||
|
check_assist_no_snippet_cap(
|
||||||
|
generate_getter,
|
||||||
|
r#"
|
||||||
|
struct Context {
|
||||||
|
data: Data,
|
||||||
|
cou$0nt: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Context {
|
||||||
|
fn data(&self) -> &Data {
|
||||||
|
&self.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
struct Context {
|
||||||
|
data: Data,
|
||||||
|
count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Context {
|
||||||
|
fn data(&self) -> &Data {
|
||||||
|
&self.data
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count(&self) -> &usize {
|
||||||
|
&self.count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_not_a_special_case() {
|
fn test_not_a_special_case() {
|
||||||
cov_mark::check_count!(convert_reference_type, 0);
|
cov_mark::check_count!(convert_reference_type, 0);
|
||||||
|
|
|
@ -33,6 +33,20 @@ pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig {
|
||||||
assist_emit_must_use: false,
|
assist_emit_must_use: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub(crate) const TEST_CONFIG_NO_SNIPPET_CAP: AssistConfig = AssistConfig {
|
||||||
|
snippet_cap: None,
|
||||||
|
allowed: None,
|
||||||
|
insert_use: InsertUseConfig {
|
||||||
|
granularity: ImportGranularity::Crate,
|
||||||
|
prefix_kind: hir::PrefixKind::Plain,
|
||||||
|
enforce_granularity: true,
|
||||||
|
group: true,
|
||||||
|
skip_glob_imports: true,
|
||||||
|
},
|
||||||
|
prefer_no_std: false,
|
||||||
|
assist_emit_must_use: false,
|
||||||
|
};
|
||||||
|
|
||||||
pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
|
pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
|
||||||
RootDatabase::with_single_file(text)
|
RootDatabase::with_single_file(text)
|
||||||
}
|
}
|
||||||
|
@ -43,6 +57,22 @@ pub(crate) fn check_assist(assist: Handler, ra_fixture_before: &str, ra_fixture_
|
||||||
check(assist, ra_fixture_before, ExpectedResult::After(&ra_fixture_after), None);
|
check(assist, ra_fixture_before, ExpectedResult::After(&ra_fixture_after), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
pub(crate) fn check_assist_no_snippet_cap(
|
||||||
|
assist: Handler,
|
||||||
|
ra_fixture_before: &str,
|
||||||
|
ra_fixture_after: &str,
|
||||||
|
) {
|
||||||
|
let ra_fixture_after = trim_indent(ra_fixture_after);
|
||||||
|
check_with_config(
|
||||||
|
TEST_CONFIG_NO_SNIPPET_CAP,
|
||||||
|
assist,
|
||||||
|
ra_fixture_before,
|
||||||
|
ExpectedResult::After(&ra_fixture_after),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// There is no way to choose what assist within a group you want to test against,
|
// There is no way to choose what assist within a group you want to test against,
|
||||||
// so this is here to allow you choose.
|
// so this is here to allow you choose.
|
||||||
pub(crate) fn check_assist_by_label(
|
pub(crate) fn check_assist_by_label(
|
||||||
|
@ -119,6 +149,17 @@ enum ExpectedResult<'a> {
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn check(handler: Handler, before: &str, expected: ExpectedResult<'_>, assist_label: Option<&str>) {
|
fn check(handler: Handler, before: &str, expected: ExpectedResult<'_>, assist_label: Option<&str>) {
|
||||||
|
check_with_config(TEST_CONFIG, handler, before, expected, assist_label);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn check_with_config(
|
||||||
|
config: AssistConfig,
|
||||||
|
handler: Handler,
|
||||||
|
before: &str,
|
||||||
|
expected: ExpectedResult<'_>,
|
||||||
|
assist_label: Option<&str>,
|
||||||
|
) {
|
||||||
let (mut db, file_with_caret_id, range_or_offset) = RootDatabase::with_range_or_offset(before);
|
let (mut db, file_with_caret_id, range_or_offset) = RootDatabase::with_range_or_offset(before);
|
||||||
db.set_enable_proc_attr_macros(true);
|
db.set_enable_proc_attr_macros(true);
|
||||||
let text_without_caret = db.file_text(file_with_caret_id).to_string();
|
let text_without_caret = db.file_text(file_with_caret_id).to_string();
|
||||||
|
@ -126,7 +167,6 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult<'_>, assist_la
|
||||||
let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() };
|
let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() };
|
||||||
|
|
||||||
let sema = Semantics::new(&db);
|
let sema = Semantics::new(&db);
|
||||||
let config = TEST_CONFIG;
|
|
||||||
let ctx = AssistContext::new(sema, &config, frange);
|
let ctx = AssistContext::new(sema, &config, frange);
|
||||||
let resolve = match expected {
|
let resolve = match expected {
|
||||||
ExpectedResult::Unresolved => AssistResolveStrategy::None,
|
ExpectedResult::Unresolved => AssistResolveStrategy::None,
|
||||||
|
|
|
@ -16,7 +16,7 @@ cov-mark = "2.0.0-pre.1"
|
||||||
itertools = "0.10.5"
|
itertools = "0.10.5"
|
||||||
|
|
||||||
once_cell = "1.17.0"
|
once_cell = "1.17.0"
|
||||||
smallvec = "1.10.0"
|
smallvec.workspace = true
|
||||||
|
|
||||||
|
|
||||||
# local deps
|
# local deps
|
||||||
|
|
|
@ -747,4 +747,16 @@ fn main() {
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_postfix_completions_in_if_block_that_has_an_else() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn test() {
|
||||||
|
if true {}.$0 else {}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#""#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -571,8 +571,7 @@ impl<'a> CompletionContext<'a> {
|
||||||
|
|
||||||
// try to skip completions on path with invalid colons
|
// try to skip completions on path with invalid colons
|
||||||
// this approach works in normal path and inside token tree
|
// this approach works in normal path and inside token tree
|
||||||
match original_token.kind() {
|
if original_token.kind() == T![:] {
|
||||||
T![:] => {
|
|
||||||
// return if no prev token before colon
|
// return if no prev token before colon
|
||||||
let prev_token = original_token.prev_token()?;
|
let prev_token = original_token.prev_token()?;
|
||||||
|
|
||||||
|
@ -592,8 +591,6 @@ impl<'a> CompletionContext<'a> {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let AnalysisResult {
|
let AnalysisResult {
|
||||||
analysis,
|
analysis,
|
||||||
|
|
|
@ -29,6 +29,7 @@ pub(super) struct AnalysisResult {
|
||||||
pub(super) analysis: CompletionAnalysis,
|
pub(super) analysis: CompletionAnalysis,
|
||||||
pub(super) expected: (Option<Type>, Option<ast::NameOrNameRef>),
|
pub(super) expected: (Option<Type>, Option<ast::NameOrNameRef>),
|
||||||
pub(super) qualifier_ctx: QualifierCtx,
|
pub(super) qualifier_ctx: QualifierCtx,
|
||||||
|
/// the original token of the expanded file
|
||||||
pub(super) token: SyntaxToken,
|
pub(super) token: SyntaxToken,
|
||||||
pub(super) offset: TextSize,
|
pub(super) offset: TextSize,
|
||||||
}
|
}
|
||||||
|
@ -213,15 +214,6 @@ fn analyze(
|
||||||
let _p = profile::span("CompletionContext::analyze");
|
let _p = profile::span("CompletionContext::analyze");
|
||||||
let ExpansionResult { original_file, speculative_file, offset, fake_ident_token, derive_ctx } =
|
let ExpansionResult { original_file, speculative_file, offset, fake_ident_token, derive_ctx } =
|
||||||
expansion_result;
|
expansion_result;
|
||||||
let syntax_element = NodeOrToken::Token(fake_ident_token);
|
|
||||||
if is_in_token_of_for_loop(syntax_element.clone()) {
|
|
||||||
// for pat $0
|
|
||||||
// there is nothing to complete here except `in` keyword
|
|
||||||
// don't bother populating the context
|
|
||||||
// FIXME: the completion calculations should end up good enough
|
|
||||||
// such that this special case becomes unnecessary
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overwrite the path kind for derives
|
// Overwrite the path kind for derives
|
||||||
if let Some((original_file, file_with_fake_ident, offset, origin_attr)) = derive_ctx {
|
if let Some((original_file, file_with_fake_ident, offset, origin_attr)) = derive_ctx {
|
||||||
|
@ -249,9 +241,7 @@ fn analyze(
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let name_like = match find_node_at_offset(&speculative_file, offset) {
|
let Some(name_like) = find_node_at_offset(&speculative_file, offset) else {
|
||||||
Some(it) => it,
|
|
||||||
None => {
|
|
||||||
let analysis = if let Some(original) = ast::String::cast(original_token.clone()) {
|
let analysis = if let Some(original) = ast::String::cast(original_token.clone()) {
|
||||||
CompletionAnalysis::String {
|
CompletionAnalysis::String {
|
||||||
original,
|
original,
|
||||||
|
@ -268,8 +258,8 @@ fn analyze(
|
||||||
let colon_prefix = previous_non_trivia_token(self_token.clone())
|
let colon_prefix = previous_non_trivia_token(self_token.clone())
|
||||||
.map_or(false, |it| T![:] == it.kind());
|
.map_or(false, |it| T![:] == it.kind());
|
||||||
CompletionAnalysis::UnexpandedAttrTT {
|
CompletionAnalysis::UnexpandedAttrTT {
|
||||||
fake_attribute_under_caret: syntax_element
|
fake_attribute_under_caret: fake_ident_token
|
||||||
.ancestors()
|
.parent_ancestors()
|
||||||
.find_map(ast::Attr::cast),
|
.find_map(ast::Attr::cast),
|
||||||
colon_prefix,
|
colon_prefix,
|
||||||
}
|
}
|
||||||
|
@ -278,8 +268,8 @@ fn analyze(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return Some((analysis, (None, None), QualifierCtx::default()));
|
return Some((analysis, (None, None), QualifierCtx::default()));
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let expected = expected_type_and_name(sema, self_token, &name_like);
|
let expected = expected_type_and_name(sema, self_token, &name_like);
|
||||||
let mut qual_ctx = QualifierCtx::default();
|
let mut qual_ctx = QualifierCtx::default();
|
||||||
let analysis = match name_like {
|
let analysis = match name_like {
|
||||||
|
@ -290,6 +280,22 @@ fn analyze(
|
||||||
let parent = name_ref.syntax().parent()?;
|
let parent = name_ref.syntax().parent()?;
|
||||||
let (nameref_ctx, qualifier_ctx) =
|
let (nameref_ctx, qualifier_ctx) =
|
||||||
classify_name_ref(sema, &original_file, name_ref, parent)?;
|
classify_name_ref(sema, &original_file, name_ref, parent)?;
|
||||||
|
|
||||||
|
if let NameRefContext {
|
||||||
|
kind:
|
||||||
|
NameRefKind::Path(PathCompletionCtx { kind: PathKind::Expr { .. }, path, .. }, ..),
|
||||||
|
..
|
||||||
|
} = &nameref_ctx
|
||||||
|
{
|
||||||
|
if is_in_token_of_for_loop(path) {
|
||||||
|
// for pat $0
|
||||||
|
// there is nothing to complete here except `in` keyword
|
||||||
|
// don't bother populating the context
|
||||||
|
// Ideally this special casing wouldn't be needed, but the parser recovers
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
qual_ctx = qualifier_ctx;
|
qual_ctx = qualifier_ctx;
|
||||||
CompletionAnalysis::NameRef(nameref_ctx)
|
CompletionAnalysis::NameRef(nameref_ctx)
|
||||||
}
|
}
|
||||||
|
@ -323,16 +329,14 @@ fn expected_type_and_name(
|
||||||
ast::FieldExpr(e) => e
|
ast::FieldExpr(e) => e
|
||||||
.syntax()
|
.syntax()
|
||||||
.ancestors()
|
.ancestors()
|
||||||
.map_while(ast::FieldExpr::cast)
|
.take_while(|it| ast::FieldExpr::can_cast(it.kind()))
|
||||||
.last()
|
.last(),
|
||||||
.map(|it| it.syntax().clone()),
|
|
||||||
ast::PathSegment(e) => e
|
ast::PathSegment(e) => e
|
||||||
.syntax()
|
.syntax()
|
||||||
.ancestors()
|
.ancestors()
|
||||||
.skip(1)
|
.skip(1)
|
||||||
.take_while(|it| ast::Path::can_cast(it.kind()) || ast::PathExpr::can_cast(it.kind()))
|
.take_while(|it| ast::Path::can_cast(it.kind()) || ast::PathExpr::can_cast(it.kind()))
|
||||||
.find_map(ast::PathExpr::cast)
|
.find(|it| ast::PathExpr::can_cast(it.kind())),
|
||||||
.map(|it| it.syntax().clone()),
|
|
||||||
_ => None
|
_ => None
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -605,6 +609,18 @@ fn classify_name_ref(
|
||||||
},
|
},
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let reciever_is_part_of_indivisible_expression = match &receiver {
|
||||||
|
Some(ast::Expr::IfExpr(_)) => {
|
||||||
|
let next_token_kind = next_non_trivia_token(name_ref.syntax().clone()).map(|t| t.kind());
|
||||||
|
next_token_kind == Some(SyntaxKind::ELSE_KW)
|
||||||
|
},
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
if reciever_is_part_of_indivisible_expression {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
let kind = NameRefKind::DotAccess(DotAccess {
|
let kind = NameRefKind::DotAccess(DotAccess {
|
||||||
receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)),
|
receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)),
|
||||||
kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal },
|
kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal },
|
||||||
|
@ -656,8 +672,15 @@ fn classify_name_ref(
|
||||||
};
|
};
|
||||||
let after_if_expr = |node: SyntaxNode| {
|
let after_if_expr = |node: SyntaxNode| {
|
||||||
let prev_expr = (|| {
|
let prev_expr = (|| {
|
||||||
|
let node = match node.parent().and_then(ast::ExprStmt::cast) {
|
||||||
|
Some(stmt) => stmt.syntax().clone(),
|
||||||
|
None => node,
|
||||||
|
};
|
||||||
let prev_sibling = non_trivia_sibling(node.into(), Direction::Prev)?.into_node()?;
|
let prev_sibling = non_trivia_sibling(node.into(), Direction::Prev)?.into_node()?;
|
||||||
ast::ExprStmt::cast(prev_sibling)?.expr()
|
|
||||||
|
ast::ExprStmt::cast(prev_sibling.clone())
|
||||||
|
.and_then(|it| it.expr())
|
||||||
|
.or_else(|| ast::Expr::cast(prev_sibling))
|
||||||
})();
|
})();
|
||||||
matches!(prev_expr, Some(ast::Expr::IfExpr(_)))
|
matches!(prev_expr, Some(ast::Expr::IfExpr(_)))
|
||||||
};
|
};
|
||||||
|
@ -1251,40 +1274,29 @@ fn path_or_use_tree_qualifier(path: &ast::Path) -> Option<(ast::Path, bool)> {
|
||||||
Some((use_tree.path()?, true))
|
Some((use_tree.path()?, true))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_in_token_of_for_loop(element: SyntaxElement) -> bool {
|
fn is_in_token_of_for_loop(path: &ast::Path) -> bool {
|
||||||
// oh my ...
|
// oh my ...
|
||||||
(|| {
|
(|| {
|
||||||
let syntax_token = element.into_token()?;
|
let expr = path.syntax().parent().and_then(ast::PathExpr::cast)?;
|
||||||
let range = syntax_token.text_range();
|
let for_expr = expr.syntax().parent().and_then(ast::ForExpr::cast)?;
|
||||||
let for_expr = syntax_token.parent_ancestors().find_map(ast::ForExpr::cast)?;
|
if for_expr.in_token().is_some() {
|
||||||
|
return Some(false);
|
||||||
// check if the current token is the `in` token of a for loop
|
|
||||||
if let Some(token) = for_expr.in_token() {
|
|
||||||
return Some(syntax_token == token);
|
|
||||||
}
|
}
|
||||||
let pat = for_expr.pat()?;
|
let pat = for_expr.pat()?;
|
||||||
if range.end() < pat.syntax().text_range().end() {
|
|
||||||
// if we are inside or before the pattern we can't be at the `in` token position
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let next_sibl = next_non_trivia_sibling(pat.syntax().clone().into())?;
|
let next_sibl = next_non_trivia_sibling(pat.syntax().clone().into())?;
|
||||||
Some(match next_sibl {
|
Some(match next_sibl {
|
||||||
// the loop body is some node, if our token is at the start we are at the `in` position,
|
syntax::NodeOrToken::Node(n) => {
|
||||||
// otherwise we could be in a recovered expression, we don't wanna ruin completions there
|
n.text_range().start() == path.syntax().text_range().start()
|
||||||
syntax::NodeOrToken::Node(n) => n.text_range().start() == range.start(),
|
}
|
||||||
// the loop body consists of a single token, if we are this we are certainly at the `in` token position
|
syntax::NodeOrToken::Token(t) => {
|
||||||
syntax::NodeOrToken::Token(t) => t == syntax_token,
|
t.text_range().start() == path.syntax().text_range().start()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})()
|
})()
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
fn is_in_loop_body(node: &SyntaxNode) -> bool {
|
||||||
fn test_for_is_prev2() {
|
|
||||||
crate::tests::check_pattern_is_applicable(r"fn __() { for i i$0 }", is_in_token_of_for_loop);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn is_in_loop_body(node: &SyntaxNode) -> bool {
|
|
||||||
node.ancestors()
|
node.ancestors()
|
||||||
.take_while(|it| it.kind() != SyntaxKind::FN && it.kind() != SyntaxKind::CLOSURE_EXPR)
|
.take_while(|it| it.kind() != SyntaxKind::FN && it.kind() != SyntaxKind::CLOSURE_EXPR)
|
||||||
.find_map(|it| {
|
.find_map(|it| {
|
||||||
|
@ -1317,6 +1329,22 @@ fn previous_non_trivia_token(e: impl Into<SyntaxElement>) -> Option<SyntaxToken>
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn next_non_trivia_token(e: impl Into<SyntaxElement>) -> Option<SyntaxToken> {
|
||||||
|
let mut token = match e.into() {
|
||||||
|
SyntaxElement::Node(n) => n.last_token()?,
|
||||||
|
SyntaxElement::Token(t) => t,
|
||||||
|
}
|
||||||
|
.next_token();
|
||||||
|
while let Some(inner) = token {
|
||||||
|
if !inner.kind().is_trivia() {
|
||||||
|
return Some(inner);
|
||||||
|
} else {
|
||||||
|
token = inner.next_token();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn next_non_trivia_sibling(ele: SyntaxElement) -> Option<SyntaxElement> {
|
fn next_non_trivia_sibling(ele: SyntaxElement) -> Option<SyntaxElement> {
|
||||||
let mut e = ele.next_sibling_or_token();
|
let mut e = ele.next_sibling_or_token();
|
||||||
while let Some(inner) = e {
|
while let Some(inner) = e {
|
||||||
|
|
|
@ -14,13 +14,14 @@ use crate::{
|
||||||
render::{render_path_resolution, RenderContext},
|
render::{render_path_resolution, RenderContext},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// `CompletionItem` describes a single completion variant in the editor pop-up.
|
/// `CompletionItem` describes a single completion entity which expands to 1 or more entries in the
|
||||||
/// It is basically a POD with various properties. To construct a
|
/// editor pop-up. It is basically a POD with various properties. To construct a
|
||||||
/// `CompletionItem`, use `new` method and the `Builder` struct.
|
/// [`CompletionItem`], use [`Builder::new`] method and the [`Builder`] struct.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
#[non_exhaustive]
|
||||||
pub struct CompletionItem {
|
pub struct CompletionItem {
|
||||||
/// Label in the completion pop up which identifies completion.
|
/// Label in the completion pop up which identifies completion.
|
||||||
label: SmolStr,
|
pub label: SmolStr,
|
||||||
/// Range of identifier that is being completed.
|
/// Range of identifier that is being completed.
|
||||||
///
|
///
|
||||||
/// It should be used primarily for UI, but we also use this to convert
|
/// It should be used primarily for UI, but we also use this to convert
|
||||||
|
@ -29,33 +30,33 @@ pub struct CompletionItem {
|
||||||
/// `source_range` must contain the completion offset. `text_edit` should
|
/// `source_range` must contain the completion offset. `text_edit` should
|
||||||
/// start with what `source_range` points to, or VSCode will filter out the
|
/// start with what `source_range` points to, or VSCode will filter out the
|
||||||
/// completion silently.
|
/// completion silently.
|
||||||
source_range: TextRange,
|
pub source_range: TextRange,
|
||||||
/// What happens when user selects this item.
|
/// What happens when user selects this item.
|
||||||
///
|
///
|
||||||
/// Typically, replaces `source_range` with new identifier.
|
/// Typically, replaces `source_range` with new identifier.
|
||||||
text_edit: TextEdit,
|
pub text_edit: TextEdit,
|
||||||
is_snippet: bool,
|
pub is_snippet: bool,
|
||||||
|
|
||||||
/// What item (struct, function, etc) are we completing.
|
/// What item (struct, function, etc) are we completing.
|
||||||
kind: CompletionItemKind,
|
pub kind: CompletionItemKind,
|
||||||
|
|
||||||
/// Lookup is used to check if completion item indeed can complete current
|
/// Lookup is used to check if completion item indeed can complete current
|
||||||
/// ident.
|
/// ident.
|
||||||
///
|
///
|
||||||
/// That is, in `foo.bar$0` lookup of `abracadabra` will be accepted (it
|
/// That is, in `foo.bar$0` lookup of `abracadabra` will be accepted (it
|
||||||
/// contains `bar` sub sequence), and `quux` will rejected.
|
/// contains `bar` sub sequence), and `quux` will rejected.
|
||||||
lookup: Option<SmolStr>,
|
pub lookup: Option<SmolStr>,
|
||||||
|
|
||||||
/// Additional info to show in the UI pop up.
|
/// Additional info to show in the UI pop up.
|
||||||
detail: Option<String>,
|
pub detail: Option<String>,
|
||||||
documentation: Option<Documentation>,
|
pub documentation: Option<Documentation>,
|
||||||
|
|
||||||
/// Whether this item is marked as deprecated
|
/// Whether this item is marked as deprecated
|
||||||
deprecated: bool,
|
pub deprecated: bool,
|
||||||
|
|
||||||
/// If completing a function call, ask the editor to show parameter popup
|
/// If completing a function call, ask the editor to show parameter popup
|
||||||
/// after completion.
|
/// after completion.
|
||||||
trigger_call_info: bool,
|
pub trigger_call_info: bool,
|
||||||
|
|
||||||
/// We use this to sort completion. Relevance records facts like "do the
|
/// We use this to sort completion. Relevance records facts like "do the
|
||||||
/// types align precisely?". We can't sort by relevances directly, they are
|
/// types align precisely?". We can't sort by relevances directly, they are
|
||||||
|
@ -64,36 +65,39 @@ pub struct CompletionItem {
|
||||||
/// Note that Relevance ignores fuzzy match score. We compute Relevance for
|
/// Note that Relevance ignores fuzzy match score. We compute Relevance for
|
||||||
/// all possible items, and then separately build an ordered completion list
|
/// all possible items, and then separately build an ordered completion list
|
||||||
/// based on relevance and fuzzy matching with the already typed identifier.
|
/// based on relevance and fuzzy matching with the already typed identifier.
|
||||||
relevance: CompletionRelevance,
|
pub relevance: CompletionRelevance,
|
||||||
|
|
||||||
/// Indicates that a reference or mutable reference to this variable is a
|
/// Indicates that a reference or mutable reference to this variable is a
|
||||||
/// possible match.
|
/// possible match.
|
||||||
ref_match: Option<(Mutability, TextSize)>,
|
// FIXME: We shouldn't expose Mutability here (that is HIR types at all), its fine for now though
|
||||||
|
// until we have more splitting completions in which case we should think about
|
||||||
|
// generalizing this. See https://github.com/rust-lang/rust-analyzer/issues/12571
|
||||||
|
pub ref_match: Option<(Mutability, TextSize)>,
|
||||||
|
|
||||||
/// The import data to add to completion's edits.
|
/// The import data to add to completion's edits.
|
||||||
import_to_add: SmallVec<[LocatedImport; 1]>,
|
pub import_to_add: SmallVec<[LocatedImport; 1]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// We use custom debug for CompletionItem to make snapshot tests more readable.
|
// We use custom debug for CompletionItem to make snapshot tests more readable.
|
||||||
impl fmt::Debug for CompletionItem {
|
impl fmt::Debug for CompletionItem {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let mut s = f.debug_struct("CompletionItem");
|
let mut s = f.debug_struct("CompletionItem");
|
||||||
s.field("label", &self.label()).field("source_range", &self.source_range());
|
s.field("label", &self.label).field("source_range", &self.source_range);
|
||||||
if self.text_edit().len() == 1 {
|
if self.text_edit.len() == 1 {
|
||||||
let atom = &self.text_edit().iter().next().unwrap();
|
let atom = &self.text_edit.iter().next().unwrap();
|
||||||
s.field("delete", &atom.delete);
|
s.field("delete", &atom.delete);
|
||||||
s.field("insert", &atom.insert);
|
s.field("insert", &atom.insert);
|
||||||
} else {
|
} else {
|
||||||
s.field("text_edit", &self.text_edit);
|
s.field("text_edit", &self.text_edit);
|
||||||
}
|
}
|
||||||
s.field("kind", &self.kind());
|
s.field("kind", &self.kind);
|
||||||
if self.lookup() != self.label() {
|
if self.lookup() != self.label {
|
||||||
s.field("lookup", &self.lookup());
|
s.field("lookup", &self.lookup());
|
||||||
}
|
}
|
||||||
if let Some(detail) = self.detail() {
|
if let Some(detail) = &self.detail {
|
||||||
s.field("detail", &detail);
|
s.field("detail", &detail);
|
||||||
}
|
}
|
||||||
if let Some(documentation) = self.documentation() {
|
if let Some(documentation) = &self.documentation {
|
||||||
s.field("documentation", &documentation);
|
s.field("documentation", &documentation);
|
||||||
}
|
}
|
||||||
if self.deprecated {
|
if self.deprecated {
|
||||||
|
@ -351,63 +355,25 @@ impl CompletionItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What user sees in pop-up in the UI.
|
|
||||||
pub fn label(&self) -> &str {
|
|
||||||
&self.label
|
|
||||||
}
|
|
||||||
pub fn source_range(&self) -> TextRange {
|
|
||||||
self.source_range
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn text_edit(&self) -> &TextEdit {
|
|
||||||
&self.text_edit
|
|
||||||
}
|
|
||||||
/// Whether `text_edit` is a snippet (contains `$0` markers).
|
|
||||||
pub fn is_snippet(&self) -> bool {
|
|
||||||
self.is_snippet
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Short one-line additional information, like a type
|
|
||||||
pub fn detail(&self) -> Option<&str> {
|
|
||||||
self.detail.as_deref()
|
|
||||||
}
|
|
||||||
/// A doc-comment
|
|
||||||
pub fn documentation(&self) -> Option<Documentation> {
|
|
||||||
self.documentation.clone()
|
|
||||||
}
|
|
||||||
/// What string is used for filtering.
|
/// What string is used for filtering.
|
||||||
pub fn lookup(&self) -> &str {
|
pub fn lookup(&self) -> &str {
|
||||||
self.lookup.as_deref().unwrap_or(&self.label)
|
self.lookup.as_deref().unwrap_or(&self.label)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn kind(&self) -> CompletionItemKind {
|
pub fn ref_match(&self) -> Option<(String, text_edit::Indel, CompletionRelevance)> {
|
||||||
self.kind
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deprecated(&self) -> bool {
|
|
||||||
self.deprecated
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn relevance(&self) -> CompletionRelevance {
|
|
||||||
self.relevance
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn trigger_call_info(&self) -> bool {
|
|
||||||
self.trigger_call_info
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ref_match(&self) -> Option<(Mutability, TextSize, CompletionRelevance)> {
|
|
||||||
// Relevance of the ref match should be the same as the original
|
// Relevance of the ref match should be the same as the original
|
||||||
// match, but with exact type match set because self.ref_match
|
// match, but with exact type match set because self.ref_match
|
||||||
// is only set if there is an exact type match.
|
// is only set if there is an exact type match.
|
||||||
let mut relevance = self.relevance;
|
let mut relevance = self.relevance;
|
||||||
relevance.type_match = Some(CompletionRelevanceTypeMatch::Exact);
|
relevance.type_match = Some(CompletionRelevanceTypeMatch::Exact);
|
||||||
|
|
||||||
self.ref_match.map(|(mutability, offset)| (mutability, offset, relevance))
|
self.ref_match.map(|(mutability, offset)| {
|
||||||
}
|
(
|
||||||
|
format!("&{}{}", mutability.as_keyword_for_ref(), self.label),
|
||||||
pub fn imports_to_add(&self) -> &[LocatedImport] {
|
text_edit::Indel::insert(offset, format!("&{}", mutability.as_keyword_for_ref())),
|
||||||
&self.import_to_add
|
relevance,
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -156,21 +156,23 @@ pub fn completions(
|
||||||
|
|
||||||
// prevent `(` from triggering unwanted completion noise
|
// prevent `(` from triggering unwanted completion noise
|
||||||
if trigger_character == Some('(') {
|
if trigger_character == Some('(') {
|
||||||
if let CompletionAnalysis::NameRef(NameRefContext { kind, .. }) = &analysis {
|
if let CompletionAnalysis::NameRef(NameRefContext {
|
||||||
if let NameRefKind::Path(
|
kind:
|
||||||
|
NameRefKind::Path(
|
||||||
path_ctx @ PathCompletionCtx { kind: PathKind::Vis { has_in_token }, .. },
|
path_ctx @ PathCompletionCtx { kind: PathKind::Vis { has_in_token }, .. },
|
||||||
) = kind
|
),
|
||||||
|
..
|
||||||
|
}) = analysis
|
||||||
{
|
{
|
||||||
completions::vis::complete_vis_path(&mut completions, ctx, path_ctx, has_in_token);
|
completions::vis::complete_vis_path(&mut completions, ctx, path_ctx, has_in_token);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return Some(completions.into());
|
return Some(completions.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let acc = &mut completions;
|
let acc = &mut completions;
|
||||||
|
|
||||||
match &analysis {
|
match analysis {
|
||||||
CompletionAnalysis::Name(name_ctx) => completions::complete_name(acc, ctx, name_ctx),
|
CompletionAnalysis::Name(name_ctx) => completions::complete_name(acc, ctx, name_ctx),
|
||||||
CompletionAnalysis::NameRef(name_ref_ctx) => {
|
CompletionAnalysis::NameRef(name_ref_ctx) => {
|
||||||
completions::complete_name_ref(acc, ctx, name_ref_ctx)
|
completions::complete_name_ref(acc, ctx, name_ref_ctx)
|
||||||
|
|
|
@ -503,18 +503,18 @@ mod tests {
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn check_relevance_for_kinds(ra_fixture: &str, kinds: &[CompletionItemKind], expect: Expect) {
|
fn check_relevance_for_kinds(ra_fixture: &str, kinds: &[CompletionItemKind], expect: Expect) {
|
||||||
let mut actual = get_all_items(TEST_CONFIG, ra_fixture, None);
|
let mut actual = get_all_items(TEST_CONFIG, ra_fixture, None);
|
||||||
actual.retain(|it| kinds.contains(&it.kind()));
|
actual.retain(|it| kinds.contains(&it.kind));
|
||||||
actual.sort_by_key(|it| cmp::Reverse(it.relevance().score()));
|
actual.sort_by_key(|it| cmp::Reverse(it.relevance.score()));
|
||||||
check_relevance_(actual, expect);
|
check_relevance_(actual, expect);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn check_relevance(ra_fixture: &str, expect: Expect) {
|
fn check_relevance(ra_fixture: &str, expect: Expect) {
|
||||||
let mut actual = get_all_items(TEST_CONFIG, ra_fixture, None);
|
let mut actual = get_all_items(TEST_CONFIG, ra_fixture, None);
|
||||||
actual.retain(|it| it.kind() != CompletionItemKind::Snippet);
|
actual.retain(|it| it.kind != CompletionItemKind::Snippet);
|
||||||
actual.retain(|it| it.kind() != CompletionItemKind::Keyword);
|
actual.retain(|it| it.kind != CompletionItemKind::Keyword);
|
||||||
actual.retain(|it| it.kind() != CompletionItemKind::BuiltinType);
|
actual.retain(|it| it.kind != CompletionItemKind::BuiltinType);
|
||||||
actual.sort_by_key(|it| cmp::Reverse(it.relevance().score()));
|
actual.sort_by_key(|it| cmp::Reverse(it.relevance.score()));
|
||||||
check_relevance_(actual, expect);
|
check_relevance_(actual, expect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -525,12 +525,11 @@ mod tests {
|
||||||
.flat_map(|it| {
|
.flat_map(|it| {
|
||||||
let mut items = vec![];
|
let mut items = vec![];
|
||||||
|
|
||||||
let tag = it.kind().tag();
|
let tag = it.kind.tag();
|
||||||
let relevance = display_relevance(it.relevance());
|
let relevance = display_relevance(it.relevance);
|
||||||
items.push(format!("{tag} {} {relevance}\n", it.label()));
|
items.push(format!("{tag} {} {relevance}\n", it.label));
|
||||||
|
|
||||||
if let Some((mutability, _offset, relevance)) = it.ref_match() {
|
if let Some((label, _indel, relevance)) = it.ref_match() {
|
||||||
let label = format!("&{}{}", mutability.as_keyword_for_ref(), it.label());
|
|
||||||
let relevance = display_relevance(relevance);
|
let relevance = display_relevance(relevance);
|
||||||
|
|
||||||
items.push(format!("{tag} {label} {relevance}\n"));
|
items.push(format!("{tag} {label} {relevance}\n"));
|
||||||
|
@ -587,6 +586,7 @@ fn main() { Foo::Fo$0 }
|
||||||
),
|
),
|
||||||
lookup: "Foo{}",
|
lookup: "Foo{}",
|
||||||
detail: "Foo { x: i32, y: i32 }",
|
detail: "Foo { x: i32, y: i32 }",
|
||||||
|
trigger_call_info: true,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
"#]],
|
"#]],
|
||||||
|
@ -614,6 +614,7 @@ fn main() { Foo::Fo$0 }
|
||||||
),
|
),
|
||||||
lookup: "Foo()",
|
lookup: "Foo()",
|
||||||
detail: "Foo(i32, i32)",
|
detail: "Foo(i32, i32)",
|
||||||
|
trigger_call_info: true,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
"#]],
|
"#]],
|
||||||
|
@ -679,6 +680,7 @@ fn main() { Foo::Fo$0 }
|
||||||
Variant,
|
Variant,
|
||||||
),
|
),
|
||||||
detail: "Foo",
|
detail: "Foo",
|
||||||
|
trigger_call_info: true,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
"#]],
|
"#]],
|
||||||
|
@ -745,6 +747,7 @@ fn main() { let _: m::Spam = S$0 }
|
||||||
postfix_match: None,
|
postfix_match: None,
|
||||||
is_definite: false,
|
is_definite: false,
|
||||||
},
|
},
|
||||||
|
trigger_call_info: true,
|
||||||
},
|
},
|
||||||
CompletionItem {
|
CompletionItem {
|
||||||
label: "m::Spam::Foo",
|
label: "m::Spam::Foo",
|
||||||
|
@ -770,6 +773,7 @@ fn main() { let _: m::Spam = S$0 }
|
||||||
postfix_match: None,
|
postfix_match: None,
|
||||||
is_definite: false,
|
is_definite: false,
|
||||||
},
|
},
|
||||||
|
trigger_call_info: true,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
"#]],
|
"#]],
|
||||||
|
@ -942,6 +946,7 @@ use self::E::*;
|
||||||
documentation: Documentation(
|
documentation: Documentation(
|
||||||
"variant docs",
|
"variant docs",
|
||||||
),
|
),
|
||||||
|
trigger_call_info: true,
|
||||||
},
|
},
|
||||||
CompletionItem {
|
CompletionItem {
|
||||||
label: "E",
|
label: "E",
|
||||||
|
|
|
@ -113,7 +113,7 @@ fn render(
|
||||||
item.detail(rendered.detail);
|
item.detail(rendered.detail);
|
||||||
|
|
||||||
match snippet_cap {
|
match snippet_cap {
|
||||||
Some(snippet_cap) => item.insert_snippet(snippet_cap, rendered.literal),
|
Some(snippet_cap) => item.insert_snippet(snippet_cap, rendered.literal).trigger_call_info(),
|
||||||
None => item.insert_text(rendered.literal),
|
None => item.insert_text(rendered.literal),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,7 @@ pub(crate) fn render_union_literal(
|
||||||
.set_relevance(ctx.completion_relevance());
|
.set_relevance(ctx.completion_relevance());
|
||||||
|
|
||||||
match ctx.snippet_cap() {
|
match ctx.snippet_cap() {
|
||||||
Some(snippet_cap) => item.insert_snippet(snippet_cap, literal),
|
Some(snippet_cap) => item.insert_snippet(snippet_cap, literal).trigger_call_info(),
|
||||||
None => item.insert_text(literal),
|
None => item.insert_text(literal),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ mod type_pos;
|
||||||
mod use_tree;
|
mod use_tree;
|
||||||
mod visibility;
|
mod visibility;
|
||||||
|
|
||||||
use hir::{db::DefDatabase, PrefixKind, Semantics};
|
use hir::{db::DefDatabase, PrefixKind};
|
||||||
use ide_db::{
|
use ide_db::{
|
||||||
base_db::{fixture::ChangeFixture, FileLoader, FilePosition},
|
base_db::{fixture::ChangeFixture, FileLoader, FilePosition},
|
||||||
imports::insert_use::{ImportGranularity, InsertUseConfig},
|
imports::insert_use::{ImportGranularity, InsertUseConfig},
|
||||||
|
@ -31,7 +31,6 @@ use ide_db::{
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use stdx::{format_to, trim_indent};
|
use stdx::{format_to, trim_indent};
|
||||||
use syntax::{AstNode, NodeOrToken, SyntaxElement};
|
|
||||||
use test_utils::assert_eq_text;
|
use test_utils::assert_eq_text;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -109,10 +108,10 @@ fn completion_list_with_config(
|
||||||
let items = get_all_items(config, ra_fixture, trigger_character);
|
let items = get_all_items(config, ra_fixture, trigger_character);
|
||||||
let items = items
|
let items = items
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|it| it.kind() != CompletionItemKind::BuiltinType || it.label() == "u32")
|
.filter(|it| it.kind != CompletionItemKind::BuiltinType || it.label == "u32")
|
||||||
.filter(|it| include_keywords || it.kind() != CompletionItemKind::Keyword)
|
.filter(|it| include_keywords || it.kind != CompletionItemKind::Keyword)
|
||||||
.filter(|it| include_keywords || it.kind() != CompletionItemKind::Snippet)
|
.filter(|it| include_keywords || it.kind != CompletionItemKind::Snippet)
|
||||||
.sorted_by_key(|it| (it.kind(), it.label().to_owned(), it.detail().map(ToOwned::to_owned)))
|
.sorted_by_key(|it| (it.kind, it.label.clone(), it.detail.as_ref().map(ToOwned::to_owned)))
|
||||||
.collect();
|
.collect();
|
||||||
render_completion_list(items)
|
render_completion_list(items)
|
||||||
}
|
}
|
||||||
|
@ -139,8 +138,8 @@ pub(crate) fn do_completion_with_config(
|
||||||
) -> Vec<CompletionItem> {
|
) -> Vec<CompletionItem> {
|
||||||
get_all_items(config, code, None)
|
get_all_items(config, code, None)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|c| c.kind() == kind)
|
.filter(|c| c.kind == kind)
|
||||||
.sorted_by(|l, r| l.label().cmp(r.label()))
|
.sorted_by(|l, r| l.label.cmp(&r.label))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,18 +148,18 @@ fn render_completion_list(completions: Vec<CompletionItem>) -> String {
|
||||||
s.chars().count()
|
s.chars().count()
|
||||||
}
|
}
|
||||||
let label_width =
|
let label_width =
|
||||||
completions.iter().map(|it| monospace_width(it.label())).max().unwrap_or_default().min(22);
|
completions.iter().map(|it| monospace_width(&it.label)).max().unwrap_or_default().min(22);
|
||||||
completions
|
completions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|it| {
|
.map(|it| {
|
||||||
let tag = it.kind().tag();
|
let tag = it.kind.tag();
|
||||||
let var_name = format!("{tag} {}", it.label());
|
let var_name = format!("{tag} {}", it.label);
|
||||||
let mut buf = var_name;
|
let mut buf = var_name;
|
||||||
if let Some(detail) = it.detail() {
|
if let Some(detail) = it.detail {
|
||||||
let width = label_width.saturating_sub(monospace_width(it.label()));
|
let width = label_width.saturating_sub(monospace_width(&it.label));
|
||||||
format_to!(buf, "{:width$} {}", "", detail, width = width);
|
format_to!(buf, "{:width$} {}", "", detail, width = width);
|
||||||
}
|
}
|
||||||
if it.deprecated() {
|
if it.deprecated {
|
||||||
format_to!(buf, " DEPRECATED");
|
format_to!(buf, " DEPRECATED");
|
||||||
}
|
}
|
||||||
format_to!(buf, "\n");
|
format_to!(buf, "\n");
|
||||||
|
@ -192,13 +191,13 @@ pub(crate) fn check_edit_with_config(
|
||||||
.unwrap_or_else(|| panic!("can't find {what:?} completion in {completions:#?}"));
|
.unwrap_or_else(|| panic!("can't find {what:?} completion in {completions:#?}"));
|
||||||
let mut actual = db.file_text(position.file_id).to_string();
|
let mut actual = db.file_text(position.file_id).to_string();
|
||||||
|
|
||||||
let mut combined_edit = completion.text_edit().to_owned();
|
let mut combined_edit = completion.text_edit.clone();
|
||||||
|
|
||||||
resolve_completion_edits(
|
resolve_completion_edits(
|
||||||
&db,
|
&db,
|
||||||
&config,
|
&config,
|
||||||
position,
|
position,
|
||||||
completion.imports_to_add().iter().filter_map(|import_edit| {
|
completion.import_to_add.iter().filter_map(|import_edit| {
|
||||||
let import_path = &import_edit.import_path;
|
let import_path = &import_edit.import_path;
|
||||||
let import_name = import_path.segments().last()?;
|
let import_name = import_path.segments().last()?;
|
||||||
Some((import_path.to_string(), import_name.to_string()))
|
Some((import_path.to_string(), import_name.to_string()))
|
||||||
|
@ -216,15 +215,6 @@ pub(crate) fn check_edit_with_config(
|
||||||
assert_eq_text!(&ra_fixture_after, &actual)
|
assert_eq_text!(&ra_fixture_after, &actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn check_pattern_is_applicable(code: &str, check: impl FnOnce(SyntaxElement) -> bool) {
|
|
||||||
let (db, pos) = position(code);
|
|
||||||
|
|
||||||
let sema = Semantics::new(&db);
|
|
||||||
let original_file = sema.parse(pos.file_id);
|
|
||||||
let token = original_file.syntax().token_at_offset(pos.offset).left_biased().unwrap();
|
|
||||||
assert!(check(NodeOrToken::Token(token)));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn get_all_items(
|
pub(crate) fn get_all_items(
|
||||||
config: CompletionConfig,
|
config: CompletionConfig,
|
||||||
code: &str,
|
code: &str,
|
||||||
|
@ -235,7 +225,7 @@ pub(crate) fn get_all_items(
|
||||||
.map_or_else(Vec::default, Into::into);
|
.map_or_else(Vec::default, Into::into);
|
||||||
// validate
|
// validate
|
||||||
res.iter().for_each(|it| {
|
res.iter().for_each(|it| {
|
||||||
let sr = it.source_range();
|
let sr = it.source_range;
|
||||||
assert!(
|
assert!(
|
||||||
sr.contains_inclusive(position.offset),
|
sr.contains_inclusive(position.offset),
|
||||||
"source range {sr:?} does not contain the offset {:?} of the completion request: {it:?}",
|
"source range {sr:?} does not contain the offset {:?} of the completion request: {it:?}",
|
||||||
|
@ -246,8 +236,9 @@ pub(crate) fn get_all_items(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_no_completions_required() {
|
fn test_no_completions_in_for_loop_in_kw_pos() {
|
||||||
assert_eq!(completion_list(r#"fn foo() { for i i$0 }"#), String::new());
|
assert_eq!(completion_list(r#"fn foo() { for i i$0 }"#), String::new());
|
||||||
|
assert_eq!(completion_list(r#"fn foo() { for i in$0 }"#), String::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -745,3 +745,255 @@ fn return_value_no_block() {
|
||||||
r#"fn f() -> i32 { match () { () => return $0 } }"#,
|
r#"fn f() -> i32 { match () { () => return $0 } }"#,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn else_completion_after_if() {
|
||||||
|
check_empty(
|
||||||
|
r#"
|
||||||
|
fn foo() { if foo {} $0 }
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn foo() fn()
|
||||||
|
bt u32
|
||||||
|
kw const
|
||||||
|
kw crate::
|
||||||
|
kw else
|
||||||
|
kw else if
|
||||||
|
kw enum
|
||||||
|
kw extern
|
||||||
|
kw false
|
||||||
|
kw fn
|
||||||
|
kw for
|
||||||
|
kw if
|
||||||
|
kw if let
|
||||||
|
kw impl
|
||||||
|
kw let
|
||||||
|
kw loop
|
||||||
|
kw match
|
||||||
|
kw mod
|
||||||
|
kw return
|
||||||
|
kw self::
|
||||||
|
kw static
|
||||||
|
kw struct
|
||||||
|
kw trait
|
||||||
|
kw true
|
||||||
|
kw type
|
||||||
|
kw union
|
||||||
|
kw unsafe
|
||||||
|
kw use
|
||||||
|
kw while
|
||||||
|
kw while let
|
||||||
|
sn macro_rules
|
||||||
|
sn pd
|
||||||
|
sn ppd
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
check_empty(
|
||||||
|
r#"
|
||||||
|
fn foo() { if foo {} el$0 }
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn foo() fn()
|
||||||
|
bt u32
|
||||||
|
kw const
|
||||||
|
kw crate::
|
||||||
|
kw else
|
||||||
|
kw else if
|
||||||
|
kw enum
|
||||||
|
kw extern
|
||||||
|
kw false
|
||||||
|
kw fn
|
||||||
|
kw for
|
||||||
|
kw if
|
||||||
|
kw if let
|
||||||
|
kw impl
|
||||||
|
kw let
|
||||||
|
kw loop
|
||||||
|
kw match
|
||||||
|
kw mod
|
||||||
|
kw return
|
||||||
|
kw self::
|
||||||
|
kw static
|
||||||
|
kw struct
|
||||||
|
kw trait
|
||||||
|
kw true
|
||||||
|
kw type
|
||||||
|
kw union
|
||||||
|
kw unsafe
|
||||||
|
kw use
|
||||||
|
kw while
|
||||||
|
kw while let
|
||||||
|
sn macro_rules
|
||||||
|
sn pd
|
||||||
|
sn ppd
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
check_empty(
|
||||||
|
r#"
|
||||||
|
fn foo() { bar(if foo {} $0) }
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn foo() fn()
|
||||||
|
bt u32
|
||||||
|
kw crate::
|
||||||
|
kw else
|
||||||
|
kw else if
|
||||||
|
kw false
|
||||||
|
kw for
|
||||||
|
kw if
|
||||||
|
kw if let
|
||||||
|
kw loop
|
||||||
|
kw match
|
||||||
|
kw return
|
||||||
|
kw self::
|
||||||
|
kw true
|
||||||
|
kw unsafe
|
||||||
|
kw while
|
||||||
|
kw while let
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
check_empty(
|
||||||
|
r#"
|
||||||
|
fn foo() { bar(if foo {} el$0) }
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn foo() fn()
|
||||||
|
bt u32
|
||||||
|
kw crate::
|
||||||
|
kw else
|
||||||
|
kw else if
|
||||||
|
kw false
|
||||||
|
kw for
|
||||||
|
kw if
|
||||||
|
kw if let
|
||||||
|
kw loop
|
||||||
|
kw match
|
||||||
|
kw return
|
||||||
|
kw self::
|
||||||
|
kw true
|
||||||
|
kw unsafe
|
||||||
|
kw while
|
||||||
|
kw while let
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
check_empty(
|
||||||
|
r#"
|
||||||
|
fn foo() { if foo {} $0 let x = 92; }
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn foo() fn()
|
||||||
|
bt u32
|
||||||
|
kw const
|
||||||
|
kw crate::
|
||||||
|
kw else
|
||||||
|
kw else if
|
||||||
|
kw enum
|
||||||
|
kw extern
|
||||||
|
kw false
|
||||||
|
kw fn
|
||||||
|
kw for
|
||||||
|
kw if
|
||||||
|
kw if let
|
||||||
|
kw impl
|
||||||
|
kw let
|
||||||
|
kw loop
|
||||||
|
kw match
|
||||||
|
kw mod
|
||||||
|
kw return
|
||||||
|
kw self::
|
||||||
|
kw static
|
||||||
|
kw struct
|
||||||
|
kw trait
|
||||||
|
kw true
|
||||||
|
kw type
|
||||||
|
kw union
|
||||||
|
kw unsafe
|
||||||
|
kw use
|
||||||
|
kw while
|
||||||
|
kw while let
|
||||||
|
sn macro_rules
|
||||||
|
sn pd
|
||||||
|
sn ppd
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
check_empty(
|
||||||
|
r#"
|
||||||
|
fn foo() { if foo {} el$0 let x = 92; }
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn foo() fn()
|
||||||
|
bt u32
|
||||||
|
kw const
|
||||||
|
kw crate::
|
||||||
|
kw else
|
||||||
|
kw else if
|
||||||
|
kw enum
|
||||||
|
kw extern
|
||||||
|
kw false
|
||||||
|
kw fn
|
||||||
|
kw for
|
||||||
|
kw if
|
||||||
|
kw if let
|
||||||
|
kw impl
|
||||||
|
kw let
|
||||||
|
kw loop
|
||||||
|
kw match
|
||||||
|
kw mod
|
||||||
|
kw return
|
||||||
|
kw self::
|
||||||
|
kw static
|
||||||
|
kw struct
|
||||||
|
kw trait
|
||||||
|
kw true
|
||||||
|
kw type
|
||||||
|
kw union
|
||||||
|
kw unsafe
|
||||||
|
kw use
|
||||||
|
kw while
|
||||||
|
kw while let
|
||||||
|
sn macro_rules
|
||||||
|
sn pd
|
||||||
|
sn ppd
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
check_empty(
|
||||||
|
r#"
|
||||||
|
fn foo() { if foo {} el$0 { let x = 92; } }
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn foo() fn()
|
||||||
|
bt u32
|
||||||
|
kw const
|
||||||
|
kw crate::
|
||||||
|
kw else
|
||||||
|
kw else if
|
||||||
|
kw enum
|
||||||
|
kw extern
|
||||||
|
kw false
|
||||||
|
kw fn
|
||||||
|
kw for
|
||||||
|
kw if
|
||||||
|
kw if let
|
||||||
|
kw impl
|
||||||
|
kw let
|
||||||
|
kw loop
|
||||||
|
kw match
|
||||||
|
kw mod
|
||||||
|
kw return
|
||||||
|
kw self::
|
||||||
|
kw static
|
||||||
|
kw struct
|
||||||
|
kw trait
|
||||||
|
kw true
|
||||||
|
kw type
|
||||||
|
kw union
|
||||||
|
kw unsafe
|
||||||
|
kw use
|
||||||
|
kw while
|
||||||
|
kw while let
|
||||||
|
sn macro_rules
|
||||||
|
sn pd
|
||||||
|
sn ppd
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -2,10 +2,17 @@
|
||||||
|
|
||||||
use expect_test::{expect, Expect};
|
use expect_test::{expect, Expect};
|
||||||
|
|
||||||
use crate::tests::{check_edit, completion_list_no_kw, completion_list_with_trigger_character};
|
use crate::tests::{
|
||||||
|
check_edit, completion_list, completion_list_no_kw, completion_list_with_trigger_character,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn check_no_kw(ra_fixture: &str, expect: Expect) {
|
||||||
|
let actual = completion_list_no_kw(ra_fixture);
|
||||||
|
expect.assert_eq(&actual)
|
||||||
|
}
|
||||||
|
|
||||||
fn check(ra_fixture: &str, expect: Expect) {
|
fn check(ra_fixture: &str, expect: Expect) {
|
||||||
let actual = completion_list_no_kw(ra_fixture);
|
let actual = completion_list(ra_fixture);
|
||||||
expect.assert_eq(&actual)
|
expect.assert_eq(&actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +66,7 @@ fn _alpha() {}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn completes_prelude() {
|
fn completes_prelude() {
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
//- /main.rs edition:2018 crate:main deps:std
|
//- /main.rs edition:2018 crate:main deps:std
|
||||||
fn foo() { let x: $0 }
|
fn foo() { let x: $0 }
|
||||||
|
@ -81,7 +88,7 @@ pub mod prelude {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn completes_prelude_macros() {
|
fn completes_prelude_macros() {
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
//- /main.rs edition:2018 crate:main deps:std
|
//- /main.rs edition:2018 crate:main deps:std
|
||||||
fn f() {$0}
|
fn f() {$0}
|
||||||
|
@ -110,7 +117,7 @@ mod macros {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn completes_std_prelude_if_core_is_defined() {
|
fn completes_std_prelude_if_core_is_defined() {
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
//- /main.rs crate:main deps:core,std
|
//- /main.rs crate:main deps:core,std
|
||||||
fn foo() { let x: $0 }
|
fn foo() { let x: $0 }
|
||||||
|
@ -140,7 +147,7 @@ pub mod prelude {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn respects_doc_hidden() {
|
fn respects_doc_hidden() {
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
//- /lib.rs crate:lib deps:std
|
//- /lib.rs crate:lib deps:std
|
||||||
fn f() {
|
fn f() {
|
||||||
|
@ -168,7 +175,7 @@ pub mod prelude {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn respects_doc_hidden_in_assoc_item_list() {
|
fn respects_doc_hidden_in_assoc_item_list() {
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
//- /lib.rs crate:lib deps:std
|
//- /lib.rs crate:lib deps:std
|
||||||
struct S;
|
struct S;
|
||||||
|
@ -195,7 +202,7 @@ pub mod prelude {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn associated_item_visibility() {
|
fn associated_item_visibility() {
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
//- /lib.rs crate:lib new_source_root:library
|
//- /lib.rs crate:lib new_source_root:library
|
||||||
pub struct S;
|
pub struct S;
|
||||||
|
@ -222,7 +229,7 @@ fn foo() { let _ = lib::S::$0 }
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn completes_union_associated_method() {
|
fn completes_union_associated_method() {
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
union U {};
|
union U {};
|
||||||
impl U { fn m() { } }
|
impl U { fn m() { } }
|
||||||
|
@ -237,7 +244,7 @@ fn foo() { let _ = U::$0 }
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn completes_trait_associated_method_1() {
|
fn completes_trait_associated_method_1() {
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
trait Trait { fn m(); }
|
trait Trait { fn m(); }
|
||||||
|
|
||||||
|
@ -251,7 +258,7 @@ fn foo() { let _ = Trait::$0 }
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn completes_trait_associated_method_2() {
|
fn completes_trait_associated_method_2() {
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
trait Trait { fn m(); }
|
trait Trait { fn m(); }
|
||||||
|
|
||||||
|
@ -268,7 +275,7 @@ fn foo() { let _ = S::$0 }
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn completes_trait_associated_method_3() {
|
fn completes_trait_associated_method_3() {
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
trait Trait { fn m(); }
|
trait Trait { fn m(); }
|
||||||
|
|
||||||
|
@ -285,7 +292,7 @@ fn foo() { let _ = <S as Trait>::$0 }
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn completes_ty_param_assoc_ty() {
|
fn completes_ty_param_assoc_ty() {
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
trait Super {
|
trait Super {
|
||||||
type Ty;
|
type Ty;
|
||||||
|
@ -318,7 +325,7 @@ fn foo<T: Sub>() { T::$0 }
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn completes_self_param_assoc_ty() {
|
fn completes_self_param_assoc_ty() {
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
trait Super {
|
trait Super {
|
||||||
type Ty;
|
type Ty;
|
||||||
|
@ -358,7 +365,7 @@ impl<T> Sub for Wrap<T> {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn completes_type_alias() {
|
fn completes_type_alias() {
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
struct S;
|
struct S;
|
||||||
impl S { fn foo() {} }
|
impl S { fn foo() {} }
|
||||||
|
@ -376,7 +383,7 @@ fn main() { T::$0; }
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn completes_qualified_macros() {
|
fn completes_qualified_macros() {
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! foo { () => {} }
|
macro_rules! foo { () => {} }
|
||||||
|
@ -392,7 +399,7 @@ fn main() { let _ = crate::$0 }
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn does_not_complete_non_fn_macros() {
|
fn does_not_complete_non_fn_macros() {
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
mod m {
|
mod m {
|
||||||
#[rustc_builtin_macro]
|
#[rustc_builtin_macro]
|
||||||
|
@ -403,7 +410,7 @@ fn f() {m::$0}
|
||||||
"#,
|
"#,
|
||||||
expect![[r#""#]],
|
expect![[r#""#]],
|
||||||
);
|
);
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
mod m {
|
mod m {
|
||||||
#[rustc_builtin_macro]
|
#[rustc_builtin_macro]
|
||||||
|
@ -418,7 +425,7 @@ fn f() {m::$0}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn completes_reexported_items_under_correct_name() {
|
fn completes_reexported_items_under_correct_name() {
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
fn foo() { self::m::$0 }
|
fn foo() { self::m::$0 }
|
||||||
|
|
||||||
|
@ -475,7 +482,7 @@ mod p {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn completes_in_simple_macro_call() {
|
fn completes_in_simple_macro_call() {
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
macro_rules! m { ($e:expr) => { $e } }
|
macro_rules! m { ($e:expr) => { $e } }
|
||||||
fn main() { m!(self::f$0); }
|
fn main() { m!(self::f$0); }
|
||||||
|
@ -490,7 +497,7 @@ fn foo() {}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn function_mod_share_name() {
|
fn function_mod_share_name() {
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
fn foo() { self::m::$0 }
|
fn foo() { self::m::$0 }
|
||||||
|
|
||||||
|
@ -508,7 +515,7 @@ mod m {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn completes_hashmap_new() {
|
fn completes_hashmap_new() {
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
struct RandomState;
|
struct RandomState;
|
||||||
struct HashMap<K, V, S = RandomState> {}
|
struct HashMap<K, V, S = RandomState> {}
|
||||||
|
@ -529,7 +536,7 @@ fn foo() {
|
||||||
#[test]
|
#[test]
|
||||||
fn completes_variant_through_self() {
|
fn completes_variant_through_self() {
|
||||||
cov_mark::check!(completes_variant_through_self);
|
cov_mark::check!(completes_variant_through_self);
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
enum Foo {
|
enum Foo {
|
||||||
Bar,
|
Bar,
|
||||||
|
@ -552,7 +559,7 @@ impl Foo {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn completes_non_exhaustive_variant_within_the_defining_crate() {
|
fn completes_non_exhaustive_variant_within_the_defining_crate() {
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
enum Foo {
|
enum Foo {
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
@ -570,7 +577,7 @@ fn foo(self) {
|
||||||
"#]],
|
"#]],
|
||||||
);
|
);
|
||||||
|
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
//- /main.rs crate:main deps:e
|
//- /main.rs crate:main deps:e
|
||||||
fn foo(self) {
|
fn foo(self) {
|
||||||
|
@ -593,7 +600,7 @@ enum Foo {
|
||||||
#[test]
|
#[test]
|
||||||
fn completes_primitive_assoc_const() {
|
fn completes_primitive_assoc_const() {
|
||||||
cov_mark::check!(completes_primitive_assoc_const);
|
cov_mark::check!(completes_primitive_assoc_const);
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
//- /lib.rs crate:lib deps:core
|
//- /lib.rs crate:lib deps:core
|
||||||
fn f() {
|
fn f() {
|
||||||
|
@ -618,7 +625,7 @@ impl u8 {
|
||||||
#[test]
|
#[test]
|
||||||
fn completes_variant_through_alias() {
|
fn completes_variant_through_alias() {
|
||||||
cov_mark::check!(completes_variant_through_alias);
|
cov_mark::check!(completes_variant_through_alias);
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
enum Foo {
|
enum Foo {
|
||||||
Bar
|
Bar
|
||||||
|
@ -636,7 +643,7 @@ fn main() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn respects_doc_hidden2() {
|
fn respects_doc_hidden2() {
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
//- /lib.rs crate:lib deps:dep
|
//- /lib.rs crate:lib deps:dep
|
||||||
fn f() {
|
fn f() {
|
||||||
|
@ -665,7 +672,7 @@ pub mod m {}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn type_anchor_empty() {
|
fn type_anchor_empty() {
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
trait Foo {
|
trait Foo {
|
||||||
fn foo() -> Self;
|
fn foo() -> Self;
|
||||||
|
@ -688,7 +695,7 @@ fn bar() -> Bar {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn type_anchor_type() {
|
fn type_anchor_type() {
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
trait Foo {
|
trait Foo {
|
||||||
fn foo() -> Self;
|
fn foo() -> Self;
|
||||||
|
@ -715,7 +722,7 @@ fn bar() -> Bar {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn type_anchor_type_trait() {
|
fn type_anchor_type_trait() {
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
trait Foo {
|
trait Foo {
|
||||||
fn foo() -> Self;
|
fn foo() -> Self;
|
||||||
|
@ -741,7 +748,7 @@ fn bar() -> Bar {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn completes_fn_in_pub_trait_generated_by_macro() {
|
fn completes_fn_in_pub_trait_generated_by_macro() {
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
mod other_mod {
|
mod other_mod {
|
||||||
macro_rules! make_method {
|
macro_rules! make_method {
|
||||||
|
@ -775,7 +782,7 @@ fn main() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn completes_fn_in_pub_trait_generated_by_recursive_macro() {
|
fn completes_fn_in_pub_trait_generated_by_recursive_macro() {
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
mod other_mod {
|
mod other_mod {
|
||||||
macro_rules! make_method {
|
macro_rules! make_method {
|
||||||
|
@ -815,7 +822,7 @@ fn main() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn completes_const_in_pub_trait_generated_by_macro() {
|
fn completes_const_in_pub_trait_generated_by_macro() {
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
mod other_mod {
|
mod other_mod {
|
||||||
macro_rules! make_const {
|
macro_rules! make_const {
|
||||||
|
@ -847,7 +854,7 @@ fn main() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn completes_locals_from_macros() {
|
fn completes_locals_from_macros() {
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
|
|
||||||
macro_rules! x {
|
macro_rules! x {
|
||||||
|
@ -875,7 +882,7 @@ fn main() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn regression_12644() {
|
fn regression_12644() {
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
macro_rules! __rust_force_expr {
|
macro_rules! __rust_force_expr {
|
||||||
($e:expr) => {
|
($e:expr) => {
|
||||||
|
@ -974,7 +981,7 @@ fn foo { crate:::$0 }
|
||||||
"#,
|
"#,
|
||||||
expect![""],
|
expect![""],
|
||||||
);
|
);
|
||||||
check(
|
check_no_kw(
|
||||||
r#"
|
r#"
|
||||||
fn foo { crate::::$0 }
|
fn foo { crate::::$0 }
|
||||||
"#,
|
"#,
|
||||||
|
|
|
@ -37,8 +37,9 @@ text-edit.workspace = true
|
||||||
hir.workspace = true
|
hir.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
xshell = "0.2.2"
|
|
||||||
expect-test = "1.4.0"
|
expect-test = "1.4.0"
|
||||||
|
oorandom = "11.1.3"
|
||||||
|
xshell = "0.2.2"
|
||||||
|
|
||||||
# local deps
|
# local deps
|
||||||
test-utils.workspace = true
|
test-utils.workspace = true
|
||||||
|
|
|
@ -2,9 +2,10 @@
|
||||||
|
|
||||||
use either::Either;
|
use either::Either;
|
||||||
use hir::{Semantics, Type};
|
use hir::{Semantics, Type};
|
||||||
|
use parser::T;
|
||||||
use syntax::{
|
use syntax::{
|
||||||
ast::{self, HasArgList, HasName},
|
ast::{self, HasArgList, HasName},
|
||||||
AstNode, SyntaxToken,
|
match_ast, AstNode, NodeOrToken, SyntaxToken,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::RootDatabase;
|
use crate::RootDatabase;
|
||||||
|
@ -58,7 +59,7 @@ pub fn callable_for_node(
|
||||||
calling_node: &ast::CallableExpr,
|
calling_node: &ast::CallableExpr,
|
||||||
token: &SyntaxToken,
|
token: &SyntaxToken,
|
||||||
) -> Option<(hir::Callable, Option<usize>)> {
|
) -> Option<(hir::Callable, Option<usize>)> {
|
||||||
let callable = match &calling_node {
|
let callable = match calling_node {
|
||||||
ast::CallableExpr::Call(call) => {
|
ast::CallableExpr::Call(call) => {
|
||||||
let expr = call.expr()?;
|
let expr = call.expr()?;
|
||||||
sema.type_of_expr(&expr)?.adjusted().as_callable(sema.db)
|
sema.type_of_expr(&expr)?.adjusted().as_callable(sema.db)
|
||||||
|
@ -66,13 +67,78 @@ pub fn callable_for_node(
|
||||||
ast::CallableExpr::MethodCall(call) => sema.resolve_method_call_as_callable(call),
|
ast::CallableExpr::MethodCall(call) => sema.resolve_method_call_as_callable(call),
|
||||||
}?;
|
}?;
|
||||||
let active_param = if let Some(arg_list) = calling_node.arg_list() {
|
let active_param = if let Some(arg_list) = calling_node.arg_list() {
|
||||||
let param = arg_list
|
Some(
|
||||||
.args()
|
arg_list
|
||||||
.take_while(|arg| arg.syntax().text_range().end() <= token.text_range().start())
|
.syntax()
|
||||||
.count();
|
.children_with_tokens()
|
||||||
Some(param)
|
.filter_map(NodeOrToken::into_token)
|
||||||
|
.filter(|t| t.kind() == T![,])
|
||||||
|
.take_while(|t| t.text_range().start() <= token.text_range().start())
|
||||||
|
.count(),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
Some((callable, active_param))
|
Some((callable, active_param))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn generic_def_for_node(
|
||||||
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
|
generic_arg_list: &ast::GenericArgList,
|
||||||
|
token: &SyntaxToken,
|
||||||
|
) -> Option<(hir::GenericDef, usize, bool)> {
|
||||||
|
let parent = generic_arg_list.syntax().parent()?;
|
||||||
|
let def = match_ast! {
|
||||||
|
match parent {
|
||||||
|
ast::PathSegment(ps) => {
|
||||||
|
let res = sema.resolve_path(&ps.parent_path())?;
|
||||||
|
let generic_def: hir::GenericDef = match res {
|
||||||
|
hir::PathResolution::Def(hir::ModuleDef::Adt(it)) => it.into(),
|
||||||
|
hir::PathResolution::Def(hir::ModuleDef::Function(it)) => it.into(),
|
||||||
|
hir::PathResolution::Def(hir::ModuleDef::Trait(it)) => it.into(),
|
||||||
|
hir::PathResolution::Def(hir::ModuleDef::TypeAlias(it)) => it.into(),
|
||||||
|
hir::PathResolution::Def(hir::ModuleDef::Variant(it)) => it.into(),
|
||||||
|
hir::PathResolution::Def(hir::ModuleDef::BuiltinType(_))
|
||||||
|
| hir::PathResolution::Def(hir::ModuleDef::Const(_))
|
||||||
|
| hir::PathResolution::Def(hir::ModuleDef::Macro(_))
|
||||||
|
| hir::PathResolution::Def(hir::ModuleDef::Module(_))
|
||||||
|
| hir::PathResolution::Def(hir::ModuleDef::Static(_)) => return None,
|
||||||
|
hir::PathResolution::BuiltinAttr(_)
|
||||||
|
| hir::PathResolution::ToolModule(_)
|
||||||
|
| hir::PathResolution::Local(_)
|
||||||
|
| hir::PathResolution::TypeParam(_)
|
||||||
|
| hir::PathResolution::ConstParam(_)
|
||||||
|
| hir::PathResolution::SelfType(_)
|
||||||
|
| hir::PathResolution::DeriveHelper(_) => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
generic_def
|
||||||
|
},
|
||||||
|
ast::AssocTypeArg(_) => {
|
||||||
|
// FIXME: We don't record the resolutions for this anywhere atm
|
||||||
|
return None;
|
||||||
|
},
|
||||||
|
ast::MethodCallExpr(mcall) => {
|
||||||
|
// recv.method::<$0>()
|
||||||
|
let method = sema.resolve_method_call(&mcall)?;
|
||||||
|
method.into()
|
||||||
|
},
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let active_param = generic_arg_list
|
||||||
|
.syntax()
|
||||||
|
.children_with_tokens()
|
||||||
|
.filter_map(NodeOrToken::into_token)
|
||||||
|
.filter(|t| t.kind() == T![,])
|
||||||
|
.take_while(|t| t.text_range().start() <= token.text_range().start())
|
||||||
|
.count();
|
||||||
|
|
||||||
|
let first_arg_is_non_lifetime = generic_arg_list
|
||||||
|
.generic_args()
|
||||||
|
.next()
|
||||||
|
.map_or(false, |arg| !matches!(arg, ast::GenericArg::LifetimeArg(_)));
|
||||||
|
|
||||||
|
Some((def, active_param, first_arg_is_non_lifetime))
|
||||||
|
}
|
||||||
|
|
|
@ -7,20 +7,13 @@ use syntax::{TextRange, TextSize};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct LineIndex {
|
pub struct LineIndex {
|
||||||
/// Offset the the beginning of each line, zero-based
|
/// Offset the beginning of each line, zero-based.
|
||||||
pub(crate) newlines: Vec<TextSize>,
|
pub(crate) newlines: Vec<TextSize>,
|
||||||
/// List of non-ASCII characters on each line
|
/// List of non-ASCII characters on each line.
|
||||||
pub(crate) utf16_lines: NoHashHashMap<u32, Vec<Utf16Char>>,
|
pub(crate) line_wide_chars: NoHashHashMap<u32, Vec<WideChar>>,
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub struct LineColUtf16 {
|
|
||||||
/// Zero-based
|
|
||||||
pub line: u32,
|
|
||||||
/// Zero-based
|
|
||||||
pub col: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Line/Column information in native, utf8 format.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct LineCol {
|
pub struct LineCol {
|
||||||
/// Zero-based
|
/// Zero-based
|
||||||
|
@ -29,34 +22,57 @@ pub struct LineCol {
|
||||||
pub col: u32,
|
pub col: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum WideEncoding {
|
||||||
|
Utf16,
|
||||||
|
Utf32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Line/Column information in legacy encodings.
|
||||||
|
///
|
||||||
|
/// Deliberately not a generic type and different from `LineCol`.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct WideLineCol {
|
||||||
|
/// Zero-based
|
||||||
|
pub line: u32,
|
||||||
|
/// Zero-based
|
||||||
|
pub col: u32,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
pub(crate) struct Utf16Char {
|
pub(crate) struct WideChar {
|
||||||
/// Start offset of a character inside a line, zero-based
|
/// Start offset of a character inside a line, zero-based
|
||||||
pub(crate) start: TextSize,
|
pub(crate) start: TextSize,
|
||||||
/// End offset of a character inside a line, zero-based
|
/// End offset of a character inside a line, zero-based
|
||||||
pub(crate) end: TextSize,
|
pub(crate) end: TextSize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Utf16Char {
|
impl WideChar {
|
||||||
/// Returns the length in 8-bit UTF-8 code units.
|
/// Returns the length in 8-bit UTF-8 code units.
|
||||||
fn len(&self) -> TextSize {
|
fn len(&self) -> TextSize {
|
||||||
self.end - self.start
|
self.end - self.start
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the length in 16-bit UTF-16 code units.
|
/// Returns the length in UTF-16 or UTF-32 code units.
|
||||||
fn len_utf16(&self) -> usize {
|
fn wide_len(&self, enc: WideEncoding) -> usize {
|
||||||
|
match enc {
|
||||||
|
WideEncoding::Utf16 => {
|
||||||
if self.len() == TextSize::from(4) {
|
if self.len() == TextSize::from(4) {
|
||||||
2
|
2
|
||||||
} else {
|
} else {
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WideEncoding::Utf32 => 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LineIndex {
|
impl LineIndex {
|
||||||
pub fn new(text: &str) -> LineIndex {
|
pub fn new(text: &str) -> LineIndex {
|
||||||
let mut utf16_lines = NoHashHashMap::default();
|
let mut line_wide_chars = NoHashHashMap::default();
|
||||||
let mut utf16_chars = Vec::new();
|
let mut wide_chars = Vec::new();
|
||||||
|
|
||||||
let mut newlines = Vec::with_capacity(16);
|
let mut newlines = Vec::with_capacity(16);
|
||||||
newlines.push(TextSize::from(0));
|
newlines.push(TextSize::from(0));
|
||||||
|
@ -71,8 +87,8 @@ impl LineIndex {
|
||||||
newlines.push(curr_row);
|
newlines.push(curr_row);
|
||||||
|
|
||||||
// Save any utf-16 characters seen in the previous line
|
// Save any utf-16 characters seen in the previous line
|
||||||
if !utf16_chars.is_empty() {
|
if !wide_chars.is_empty() {
|
||||||
utf16_lines.insert(line, mem::take(&mut utf16_chars));
|
line_wide_chars.insert(line, mem::take(&mut wide_chars));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare for processing the next line
|
// Prepare for processing the next line
|
||||||
|
@ -82,18 +98,18 @@ impl LineIndex {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !c.is_ascii() {
|
if !c.is_ascii() {
|
||||||
utf16_chars.push(Utf16Char { start: curr_col, end: curr_col + c_len });
|
wide_chars.push(WideChar { start: curr_col, end: curr_col + c_len });
|
||||||
}
|
}
|
||||||
|
|
||||||
curr_col += c_len;
|
curr_col += c_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save any utf-16 characters seen in the last line
|
// Save any utf-16 characters seen in the last line
|
||||||
if !utf16_chars.is_empty() {
|
if !wide_chars.is_empty() {
|
||||||
utf16_lines.insert(line, utf16_chars);
|
line_wide_chars.insert(line, wide_chars);
|
||||||
}
|
}
|
||||||
|
|
||||||
LineIndex { newlines, utf16_lines }
|
LineIndex { newlines, line_wide_chars }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn line_col(&self, offset: TextSize) -> LineCol {
|
pub fn line_col(&self, offset: TextSize) -> LineCol {
|
||||||
|
@ -109,13 +125,13 @@ impl LineIndex {
|
||||||
.map(|offset| offset + TextSize::from(line_col.col))
|
.map(|offset| offset + TextSize::from(line_col.col))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_utf16(&self, line_col: LineCol) -> LineColUtf16 {
|
pub fn to_wide(&self, enc: WideEncoding, line_col: LineCol) -> WideLineCol {
|
||||||
let col = self.utf8_to_utf16_col(line_col.line, line_col.col.into());
|
let col = self.utf8_to_wide_col(enc, line_col.line, line_col.col.into());
|
||||||
LineColUtf16 { line: line_col.line, col: col as u32 }
|
WideLineCol { line: line_col.line, col: col as u32 }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_utf8(&self, line_col: LineColUtf16) -> LineCol {
|
pub fn to_utf8(&self, enc: WideEncoding, line_col: WideLineCol) -> LineCol {
|
||||||
let col = self.utf16_to_utf8_col(line_col.line, line_col.col);
|
let col = self.wide_to_utf8_col(enc, line_col.line, line_col.col);
|
||||||
LineCol { line: line_col.line, col: col.into() }
|
LineCol { line: line_col.line, col: col.into() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,12 +148,12 @@ impl LineIndex {
|
||||||
.filter(|it| !it.is_empty())
|
.filter(|it| !it.is_empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn utf8_to_utf16_col(&self, line: u32, col: TextSize) -> usize {
|
fn utf8_to_wide_col(&self, enc: WideEncoding, line: u32, col: TextSize) -> usize {
|
||||||
let mut res: usize = col.into();
|
let mut res: usize = col.into();
|
||||||
if let Some(utf16_chars) = self.utf16_lines.get(&line) {
|
if let Some(wide_chars) = self.line_wide_chars.get(&line) {
|
||||||
for c in utf16_chars {
|
for c in wide_chars {
|
||||||
if c.end <= col {
|
if c.end <= col {
|
||||||
res -= usize::from(c.len()) - c.len_utf16();
|
res -= usize::from(c.len()) - c.wide_len(enc);
|
||||||
} else {
|
} else {
|
||||||
// From here on, all utf16 characters come *after* the character we are mapping,
|
// From here on, all utf16 characters come *after* the character we are mapping,
|
||||||
// so we don't need to take them into account
|
// so we don't need to take them into account
|
||||||
|
@ -148,11 +164,11 @@ impl LineIndex {
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
fn utf16_to_utf8_col(&self, line: u32, mut col: u32) -> TextSize {
|
fn wide_to_utf8_col(&self, enc: WideEncoding, line: u32, mut col: u32) -> TextSize {
|
||||||
if let Some(utf16_chars) = self.utf16_lines.get(&line) {
|
if let Some(wide_chars) = self.line_wide_chars.get(&line) {
|
||||||
for c in utf16_chars {
|
for c in wide_chars {
|
||||||
if col > u32::from(c.start) {
|
if col > u32::from(c.start) {
|
||||||
col += u32::from(c.len()) - c.len_utf16() as u32;
|
col += u32::from(c.len()) - c.wide_len(enc) as u32;
|
||||||
} else {
|
} else {
|
||||||
// From here on, all utf16 characters come *after* the character we are mapping,
|
// From here on, all utf16 characters come *after* the character we are mapping,
|
||||||
// so we don't need to take them into account
|
// so we don't need to take them into account
|
||||||
|
@ -167,6 +183,9 @@ impl LineIndex {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use test_utils::skip_slow_tests;
|
||||||
|
|
||||||
|
use super::WideEncoding::{Utf16, Utf32};
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -210,67 +229,59 @@ mod tests {
|
||||||
const C: char = 'x';
|
const C: char = 'x';
|
||||||
",
|
",
|
||||||
);
|
);
|
||||||
assert_eq!(col_index.utf16_lines.len(), 0);
|
assert_eq!(col_index.line_wide_chars.len(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_single_char() {
|
fn test_every_chars() {
|
||||||
let col_index = LineIndex::new(
|
if skip_slow_tests() {
|
||||||
"
|
return;
|
||||||
const C: char = 'メ';
|
|
||||||
",
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(col_index.utf16_lines.len(), 1);
|
|
||||||
assert_eq!(col_index.utf16_lines[&1].len(), 1);
|
|
||||||
assert_eq!(col_index.utf16_lines[&1][0], Utf16Char { start: 17.into(), end: 20.into() });
|
|
||||||
|
|
||||||
// UTF-8 to UTF-16, no changes
|
|
||||||
assert_eq!(col_index.utf8_to_utf16_col(1, 15.into()), 15);
|
|
||||||
|
|
||||||
// UTF-8 to UTF-16
|
|
||||||
assert_eq!(col_index.utf8_to_utf16_col(1, 22.into()), 20);
|
|
||||||
|
|
||||||
// UTF-16 to UTF-8, no changes
|
|
||||||
assert_eq!(col_index.utf16_to_utf8_col(1, 15), TextSize::from(15));
|
|
||||||
|
|
||||||
// UTF-16 to UTF-8
|
|
||||||
assert_eq!(col_index.utf16_to_utf8_col(1, 19), TextSize::from(21));
|
|
||||||
|
|
||||||
let col_index = LineIndex::new("a𐐏b");
|
|
||||||
assert_eq!(col_index.utf16_to_utf8_col(0, 3), TextSize::from(5));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
let text: String = {
|
||||||
fn test_string() {
|
let mut chars: Vec<char> = ((0 as char)..char::MAX).collect(); // Neat!
|
||||||
let col_index = LineIndex::new(
|
chars.extend("\n".repeat(chars.len() / 16).chars());
|
||||||
"
|
let mut rng = oorandom::Rand32::new(stdx::rand::seed());
|
||||||
const C: char = \"メ メ\";
|
stdx::rand::shuffle(&mut chars, |i| rng.rand_range(0..i as u32) as usize);
|
||||||
",
|
chars.into_iter().collect()
|
||||||
);
|
};
|
||||||
|
assert!(text.contains('💩')); // Sanity check.
|
||||||
|
|
||||||
assert_eq!(col_index.utf16_lines.len(), 1);
|
let line_index = LineIndex::new(&text);
|
||||||
assert_eq!(col_index.utf16_lines[&1].len(), 2);
|
|
||||||
assert_eq!(col_index.utf16_lines[&1][0], Utf16Char { start: 17.into(), end: 20.into() });
|
|
||||||
assert_eq!(col_index.utf16_lines[&1][1], Utf16Char { start: 21.into(), end: 24.into() });
|
|
||||||
|
|
||||||
// UTF-8 to UTF-16
|
let mut lin_col = LineCol { line: 0, col: 0 };
|
||||||
assert_eq!(col_index.utf8_to_utf16_col(1, 15.into()), 15);
|
let mut col_utf16 = 0;
|
||||||
|
let mut col_utf32 = 0;
|
||||||
|
for (offset, c) in text.char_indices() {
|
||||||
|
let got_offset = line_index.offset(lin_col).unwrap();
|
||||||
|
assert_eq!(usize::from(got_offset), offset);
|
||||||
|
|
||||||
assert_eq!(col_index.utf8_to_utf16_col(1, 21.into()), 19);
|
let got_lin_col = line_index.line_col(got_offset);
|
||||||
assert_eq!(col_index.utf8_to_utf16_col(1, 25.into()), 21);
|
assert_eq!(got_lin_col, lin_col);
|
||||||
|
|
||||||
assert!(col_index.utf8_to_utf16_col(2, 15.into()) == 15);
|
for enc in [Utf16, Utf32] {
|
||||||
|
let wide_lin_col = line_index.to_wide(enc, lin_col);
|
||||||
|
let got_lin_col = line_index.to_utf8(enc, wide_lin_col);
|
||||||
|
assert_eq!(got_lin_col, lin_col);
|
||||||
|
|
||||||
// UTF-16 to UTF-8
|
let want_col = match enc {
|
||||||
assert_eq!(col_index.utf16_to_utf8_col(1, 15), TextSize::from(15));
|
Utf16 => col_utf16,
|
||||||
|
Utf32 => col_utf32,
|
||||||
|
};
|
||||||
|
assert_eq!(wide_lin_col.col, want_col)
|
||||||
|
}
|
||||||
|
|
||||||
// メ UTF-8: 0xE3 0x83 0xA1, UTF-16: 0x30E1
|
if c == '\n' {
|
||||||
assert_eq!(col_index.utf16_to_utf8_col(1, 17), TextSize::from(17)); // first メ at 17..20
|
lin_col.line += 1;
|
||||||
assert_eq!(col_index.utf16_to_utf8_col(1, 18), TextSize::from(20)); // space
|
lin_col.col = 0;
|
||||||
assert_eq!(col_index.utf16_to_utf8_col(1, 19), TextSize::from(21)); // second メ at 21..24
|
col_utf16 = 0;
|
||||||
|
col_utf32 = 0;
|
||||||
assert_eq!(col_index.utf16_to_utf8_col(2, 15), TextSize::from(15));
|
} else {
|
||||||
|
lin_col.col += c.len_utf8() as u32;
|
||||||
|
col_utf16 += c.len_utf16() as u32;
|
||||||
|
col_utf32 += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -455,7 +455,13 @@ impl<'a> FindUsages<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let find_nodes = move |name: &str, node: &syntax::SyntaxNode, offset: TextSize| {
|
let find_nodes = move |name: &str, node: &syntax::SyntaxNode, offset: TextSize| {
|
||||||
node.token_at_offset(offset).find(|it| it.text() == name).map(|token| {
|
node.token_at_offset(offset)
|
||||||
|
.find(|it| {
|
||||||
|
// `name` is stripped of raw ident prefix. See the comment on name retrieval above.
|
||||||
|
it.text().trim_start_matches("r#") == name
|
||||||
|
})
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|token| {
|
||||||
// FIXME: There should be optimization potential here
|
// FIXME: There should be optimization potential here
|
||||||
// Currently we try to descend everything we find which
|
// Currently we try to descend everything we find which
|
||||||
// means we call `Semantics::descend_into_macros` on
|
// means we call `Semantics::descend_into_macros` on
|
||||||
|
@ -471,27 +477,21 @@ impl<'a> FindUsages<'a> {
|
||||||
|
|
||||||
// Search for occurrences of the items name
|
// Search for occurrences of the items name
|
||||||
for offset in match_indices(&text, finder, search_range) {
|
for offset in match_indices(&text, finder, search_range) {
|
||||||
if let Some(iter) = find_nodes(name, &tree, offset) {
|
for name in find_nodes(name, &tree, offset).filter_map(ast::NameLike::cast) {
|
||||||
for name in iter.filter_map(ast::NameLike::cast) {
|
|
||||||
if match name {
|
if match name {
|
||||||
ast::NameLike::NameRef(name_ref) => {
|
ast::NameLike::NameRef(name_ref) => self.found_name_ref(&name_ref, sink),
|
||||||
self.found_name_ref(&name_ref, sink)
|
|
||||||
}
|
|
||||||
ast::NameLike::Name(name) => self.found_name(&name, sink),
|
ast::NameLike::Name(name) => self.found_name(&name, sink),
|
||||||
ast::NameLike::Lifetime(lifetime) => {
|
ast::NameLike::Lifetime(lifetime) => self.found_lifetime(&lifetime, sink),
|
||||||
self.found_lifetime(&lifetime, sink)
|
|
||||||
}
|
|
||||||
} {
|
} {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// Search for occurrences of the `Self` referring to our type
|
// Search for occurrences of the `Self` referring to our type
|
||||||
if let Some((self_ty, finder)) = &include_self_kw_refs {
|
if let Some((self_ty, finder)) = &include_self_kw_refs {
|
||||||
for offset in match_indices(&text, finder, search_range) {
|
for offset in match_indices(&text, finder, search_range) {
|
||||||
if let Some(iter) = find_nodes("Self", &tree, offset) {
|
for name_ref in find_nodes("Self", &tree, offset).filter_map(ast::NameRef::cast)
|
||||||
for name_ref in iter.filter_map(ast::NameRef::cast) {
|
{
|
||||||
if self.found_self_ty_name_ref(self_ty, &name_ref, sink) {
|
if self.found_self_ty_name_ref(self_ty, &name_ref, sink) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -499,7 +499,6 @@ impl<'a> FindUsages<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Search for `super` and `crate` resolving to our module
|
// Search for `super` and `crate` resolving to our module
|
||||||
if let Definition::Module(module) = self.def {
|
if let Definition::Module(module) = self.def {
|
||||||
|
@ -513,18 +512,19 @@ impl<'a> FindUsages<'a> {
|
||||||
let tree = Lazy::new(move || sema.parse(file_id).syntax().clone());
|
let tree = Lazy::new(move || sema.parse(file_id).syntax().clone());
|
||||||
|
|
||||||
for offset in match_indices(&text, finder, search_range) {
|
for offset in match_indices(&text, finder, search_range) {
|
||||||
if let Some(iter) = find_nodes("super", &tree, offset) {
|
for name_ref in
|
||||||
for name_ref in iter.filter_map(ast::NameRef::cast) {
|
find_nodes("super", &tree, offset).filter_map(ast::NameRef::cast)
|
||||||
|
{
|
||||||
if self.found_name_ref(&name_ref, sink) {
|
if self.found_name_ref(&name_ref, sink) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if let Some(finder) = &is_crate_root {
|
if let Some(finder) = &is_crate_root {
|
||||||
for offset in match_indices(&text, finder, search_range) {
|
for offset in match_indices(&text, finder, search_range) {
|
||||||
if let Some(iter) = find_nodes("crate", &tree, offset) {
|
for name_ref in
|
||||||
for name_ref in iter.filter_map(ast::NameRef::cast) {
|
find_nodes("crate", &tree, offset).filter_map(ast::NameRef::cast)
|
||||||
|
{
|
||||||
if self.found_name_ref(&name_ref, sink) {
|
if self.found_name_ref(&name_ref, sink) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -533,7 +533,6 @@ impl<'a> FindUsages<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// search for module `self` references in our module's definition source
|
// search for module `self` references in our module's definition source
|
||||||
match self.def {
|
match self.def {
|
||||||
|
@ -566,15 +565,14 @@ impl<'a> FindUsages<'a> {
|
||||||
let finder = &Finder::new("self");
|
let finder = &Finder::new("self");
|
||||||
|
|
||||||
for offset in match_indices(&text, finder, search_range) {
|
for offset in match_indices(&text, finder, search_range) {
|
||||||
if let Some(iter) = find_nodes("self", &tree, offset) {
|
for name_ref in find_nodes("self", &tree, offset).filter_map(ast::NameRef::cast)
|
||||||
for name_ref in iter.filter_map(ast::NameRef::cast) {
|
{
|
||||||
if self.found_self_module_name_ref(&name_ref, sink) {
|
if self.found_self_module_name_ref(&name_ref, sink) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ pulldown-cmark-to-cmark = "10.0.4"
|
||||||
pulldown-cmark = { version = "0.9.1", default-features = false }
|
pulldown-cmark = { version = "0.9.1", default-features = false }
|
||||||
url = "2.3.1"
|
url = "2.3.1"
|
||||||
dot = "0.1.4"
|
dot = "0.1.4"
|
||||||
smallvec = "1.10.0"
|
smallvec.workspace = true
|
||||||
|
|
||||||
# local deps
|
# local deps
|
||||||
cfg.workspace = true
|
cfg.workspace = true
|
||||||
|
|
|
@ -201,6 +201,23 @@ fn hover_simple(
|
||||||
|
|
||||||
Some(render::struct_rest_pat(sema, config, &record_pat))
|
Some(render::struct_rest_pat(sema, config, &record_pat))
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
// try () call hovers
|
||||||
|
.or_else(|| {
|
||||||
|
descended().find_map(|token| {
|
||||||
|
if token.kind() != T!['('] && token.kind() != T![')'] {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let arg_list = token.parent().and_then(ast::ArgList::cast)?.syntax().parent()?;
|
||||||
|
let call_expr = syntax::match_ast! {
|
||||||
|
match arg_list {
|
||||||
|
ast::CallExpr(expr) => expr.into(),
|
||||||
|
ast::MethodCallExpr(expr) => expr.into(),
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
render::type_info_of(sema, config, &Either::Left(call_expr))
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
result.map(|mut res: HoverResult| {
|
result.map(|mut res: HoverResult| {
|
||||||
|
|
|
@ -5612,3 +5612,38 @@ fn main() {
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hover_call_parens() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn foo() -> i32 {}
|
||||||
|
fn main() {
|
||||||
|
foo($0);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
*)*
|
||||||
|
```rust
|
||||||
|
i32
|
||||||
|
```
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
struct S;
|
||||||
|
impl S {
|
||||||
|
fn foo(self) -> i32 {}
|
||||||
|
}
|
||||||
|
fn main() {
|
||||||
|
S.foo($0);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
*)*
|
||||||
|
```rust
|
||||||
|
i32
|
||||||
|
```
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -29,8 +29,17 @@ pub(super) fn hints(
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.last();
|
.last();
|
||||||
let range =
|
let range = outer_paren_pat.as_ref().map_or_else(
|
||||||
outer_paren_pat.as_ref().map_or_else(|| pat.syntax(), |it| it.syntax()).text_range();
|
|| match pat {
|
||||||
|
// for ident patterns that @ bind a name, render the un-ref patterns in front of the inner pattern
|
||||||
|
// instead of the name as that makes it more clear and doesn't really change the outcome
|
||||||
|
ast::Pat::IdentPat(it) => {
|
||||||
|
it.pat().map_or_else(|| it.syntax().text_range(), |it| it.syntax().text_range())
|
||||||
|
}
|
||||||
|
it => it.syntax().text_range(),
|
||||||
|
},
|
||||||
|
|it| it.syntax().text_range(),
|
||||||
|
);
|
||||||
let pattern_adjustments = sema.pattern_adjustments(pat);
|
let pattern_adjustments = sema.pattern_adjustments(pat);
|
||||||
pattern_adjustments.iter().for_each(|ty| {
|
pattern_adjustments.iter().for_each(|ty| {
|
||||||
let reference = ty.is_reference();
|
let reference = ty.is_reference();
|
||||||
|
@ -123,4 +132,20 @@ fn __(
|
||||||
}"#,
|
}"#,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hints_binding_modes_complex_ident_pat() {
|
||||||
|
check_with_config(
|
||||||
|
InlayHintsConfig { binding_mode_hints: true, ..DISABLED_CONFIG },
|
||||||
|
r#"
|
||||||
|
struct Struct {
|
||||||
|
field: &'static str,
|
||||||
|
}
|
||||||
|
fn foo(s @ Struct { field, .. }: &Struct) {}
|
||||||
|
//^^^^^^^^^^^^^^^^^^^^^^^^ref
|
||||||
|
//^^^^^^^^^^^^^^^^^^^^&
|
||||||
|
//^^^^^ref
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,7 +115,7 @@ pub use ide_db::{
|
||||||
SourceRoot, SourceRootId,
|
SourceRoot, SourceRootId,
|
||||||
},
|
},
|
||||||
label::Label,
|
label::Label,
|
||||||
line_index::{LineCol, LineColUtf16, LineIndex},
|
line_index::{LineCol, LineIndex},
|
||||||
search::{ReferenceCategory, SearchScope},
|
search::{ReferenceCategory, SearchScope},
|
||||||
source_change::{FileSystemEdit, SourceChange},
|
source_change::{FileSystemEdit, SourceChange},
|
||||||
symbol_index::Query,
|
symbol_index::Query,
|
||||||
|
|
|
@ -2016,4 +2016,19 @@ fn method$0() {}
|
||||||
"#]],
|
"#]],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn raw_identifier() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn r#fn$0() {}
|
||||||
|
fn main() { r#fn(); }
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
r#fn Function FileId(0) 0..12 3..7
|
||||||
|
|
||||||
|
FileId(0) 25..29
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1371,7 +1371,6 @@ pub fn baz() {}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_rename_mod_from_raw_ident() {
|
fn test_rename_mod_from_raw_ident() {
|
||||||
// FIXME: `r#fn` in path expression is not renamed.
|
|
||||||
check_expect(
|
check_expect(
|
||||||
"foo",
|
"foo",
|
||||||
r#"
|
r#"
|
||||||
|
@ -1397,6 +1396,10 @@ pub fn baz() {}
|
||||||
insert: "foo",
|
insert: "foo",
|
||||||
delete: 4..8,
|
delete: 4..8,
|
||||||
},
|
},
|
||||||
|
Indel {
|
||||||
|
insert: "foo",
|
||||||
|
delete: 23..27,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -18,7 +18,9 @@ pub(crate) fn shuffle_crate_graph(db: &mut RootDatabase) {
|
||||||
let crate_graph = db.crate_graph();
|
let crate_graph = db.crate_graph();
|
||||||
|
|
||||||
let mut shuffled_ids = crate_graph.iter().collect::<Vec<_>>();
|
let mut shuffled_ids = crate_graph.iter().collect::<Vec<_>>();
|
||||||
shuffle(&mut shuffled_ids);
|
|
||||||
|
let mut rng = oorandom::Rand32::new(stdx::rand::seed());
|
||||||
|
stdx::rand::shuffle(&mut shuffled_ids, |i| rng.rand_range(0..i as u32) as usize);
|
||||||
|
|
||||||
let mut new_graph = CrateGraph::default();
|
let mut new_graph = CrateGraph::default();
|
||||||
|
|
||||||
|
@ -52,21 +54,3 @@ pub(crate) fn shuffle_crate_graph(db: &mut RootDatabase) {
|
||||||
|
|
||||||
db.set_crate_graph_with_durability(Arc::new(new_graph), Durability::HIGH);
|
db.set_crate_graph_with_durability(Arc::new(new_graph), Durability::HIGH);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shuffle<T>(slice: &mut [T]) {
|
|
||||||
let mut rng = oorandom::Rand32::new(seed());
|
|
||||||
|
|
||||||
let mut remaining = slice.len() - 1;
|
|
||||||
while remaining > 0 {
|
|
||||||
let index = rng.rand_range(0..remaining as u32);
|
|
||||||
slice.swap(remaining, index as usize);
|
|
||||||
remaining -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn seed() -> u64 {
|
|
||||||
use std::collections::hash_map::RandomState;
|
|
||||||
use std::hash::{BuildHasher, Hasher};
|
|
||||||
|
|
||||||
RandomState::new().build_hasher().finish()
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,12 +7,16 @@ use either::Either;
|
||||||
use hir::{
|
use hir::{
|
||||||
AssocItem, GenericParam, HasAttrs, HirDisplay, ModuleDef, PathResolution, Semantics, Trait,
|
AssocItem, GenericParam, HasAttrs, HirDisplay, ModuleDef, PathResolution, Semantics, Trait,
|
||||||
};
|
};
|
||||||
use ide_db::{active_parameter::callable_for_node, base_db::FilePosition, FxIndexMap};
|
use ide_db::{
|
||||||
|
active_parameter::{callable_for_node, generic_def_for_node},
|
||||||
|
base_db::FilePosition,
|
||||||
|
FxIndexMap,
|
||||||
|
};
|
||||||
use stdx::format_to;
|
use stdx::format_to;
|
||||||
use syntax::{
|
use syntax::{
|
||||||
algo,
|
algo,
|
||||||
ast::{self, HasArgList},
|
ast::{self, HasArgList},
|
||||||
match_ast, AstNode, Direction, SyntaxKind, SyntaxToken, TextRange, TextSize,
|
match_ast, AstNode, Direction, SyntaxToken, TextRange, TextSize,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::RootDatabase;
|
use crate::RootDatabase;
|
||||||
|
@ -105,10 +109,10 @@ pub(crate) fn signature_help(db: &RootDatabase, position: FilePosition) -> Optio
|
||||||
// Stop at multi-line expressions, since the signature of the outer call is not very
|
// Stop at multi-line expressions, since the signature of the outer call is not very
|
||||||
// helpful inside them.
|
// helpful inside them.
|
||||||
if let Some(expr) = ast::Expr::cast(node.clone()) {
|
if let Some(expr) = ast::Expr::cast(node.clone()) {
|
||||||
if expr.syntax().text().contains_char('\n')
|
if !matches!(expr, ast::Expr::RecordExpr(..))
|
||||||
&& expr.syntax().kind() != SyntaxKind::RECORD_EXPR
|
&& expr.syntax().text().contains_char('\n')
|
||||||
{
|
{
|
||||||
return None;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,18 +126,16 @@ fn signature_help_for_call(
|
||||||
token: SyntaxToken,
|
token: SyntaxToken,
|
||||||
) -> Option<SignatureHelp> {
|
) -> Option<SignatureHelp> {
|
||||||
// Find the calling expression and its NameRef
|
// Find the calling expression and its NameRef
|
||||||
let mut node = arg_list.syntax().parent()?;
|
let mut nodes = arg_list.syntax().ancestors().skip(1);
|
||||||
let calling_node = loop {
|
let calling_node = loop {
|
||||||
if let Some(callable) = ast::CallableExpr::cast(node.clone()) {
|
if let Some(callable) = ast::CallableExpr::cast(nodes.next()?) {
|
||||||
if callable
|
let inside_callable = callable
|
||||||
.arg_list()
|
.arg_list()
|
||||||
.map_or(false, |it| it.syntax().text_range().contains(token.text_range().start()))
|
.map_or(false, |it| it.syntax().text_range().contains(token.text_range().start()));
|
||||||
{
|
if inside_callable {
|
||||||
break callable;
|
break callable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
node = node.parent()?;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let (callable, active_parameter) = callable_for_node(sema, &calling_node, &token)?;
|
let (callable, active_parameter) = callable_for_node(sema, &calling_node, &token)?;
|
||||||
|
@ -216,59 +218,11 @@ fn signature_help_for_call(
|
||||||
|
|
||||||
fn signature_help_for_generics(
|
fn signature_help_for_generics(
|
||||||
sema: &Semantics<'_, RootDatabase>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
garg_list: ast::GenericArgList,
|
arg_list: ast::GenericArgList,
|
||||||
token: SyntaxToken,
|
token: SyntaxToken,
|
||||||
) -> Option<SignatureHelp> {
|
) -> Option<SignatureHelp> {
|
||||||
let arg_list = garg_list
|
let (mut generics_def, mut active_parameter, first_arg_is_non_lifetime) =
|
||||||
.syntax()
|
generic_def_for_node(sema, &arg_list, &token)?;
|
||||||
.ancestors()
|
|
||||||
.filter_map(ast::GenericArgList::cast)
|
|
||||||
.find(|list| list.syntax().text_range().contains(token.text_range().start()))?;
|
|
||||||
|
|
||||||
let mut active_parameter = arg_list
|
|
||||||
.generic_args()
|
|
||||||
.take_while(|arg| arg.syntax().text_range().end() <= token.text_range().start())
|
|
||||||
.count();
|
|
||||||
|
|
||||||
let first_arg_is_non_lifetime = arg_list
|
|
||||||
.generic_args()
|
|
||||||
.next()
|
|
||||||
.map_or(false, |arg| !matches!(arg, ast::GenericArg::LifetimeArg(_)));
|
|
||||||
|
|
||||||
let mut generics_def = if let Some(path) =
|
|
||||||
arg_list.syntax().ancestors().find_map(ast::Path::cast)
|
|
||||||
{
|
|
||||||
let res = sema.resolve_path(&path)?;
|
|
||||||
let generic_def: hir::GenericDef = match res {
|
|
||||||
hir::PathResolution::Def(hir::ModuleDef::Adt(it)) => it.into(),
|
|
||||||
hir::PathResolution::Def(hir::ModuleDef::Function(it)) => it.into(),
|
|
||||||
hir::PathResolution::Def(hir::ModuleDef::Trait(it)) => it.into(),
|
|
||||||
hir::PathResolution::Def(hir::ModuleDef::TypeAlias(it)) => it.into(),
|
|
||||||
hir::PathResolution::Def(hir::ModuleDef::Variant(it)) => it.into(),
|
|
||||||
hir::PathResolution::Def(hir::ModuleDef::BuiltinType(_))
|
|
||||||
| hir::PathResolution::Def(hir::ModuleDef::Const(_))
|
|
||||||
| hir::PathResolution::Def(hir::ModuleDef::Macro(_))
|
|
||||||
| hir::PathResolution::Def(hir::ModuleDef::Module(_))
|
|
||||||
| hir::PathResolution::Def(hir::ModuleDef::Static(_)) => return None,
|
|
||||||
hir::PathResolution::BuiltinAttr(_)
|
|
||||||
| hir::PathResolution::ToolModule(_)
|
|
||||||
| hir::PathResolution::Local(_)
|
|
||||||
| hir::PathResolution::TypeParam(_)
|
|
||||||
| hir::PathResolution::ConstParam(_)
|
|
||||||
| hir::PathResolution::SelfType(_)
|
|
||||||
| hir::PathResolution::DeriveHelper(_) => return None,
|
|
||||||
};
|
|
||||||
|
|
||||||
generic_def
|
|
||||||
} else if let Some(method_call) = arg_list.syntax().parent().and_then(ast::MethodCallExpr::cast)
|
|
||||||
{
|
|
||||||
// recv.method::<$0>()
|
|
||||||
let method = sema.resolve_method_call(&method_call)?;
|
|
||||||
method.into()
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut res = SignatureHelp {
|
let mut res = SignatureHelp {
|
||||||
doc: None,
|
doc: None,
|
||||||
signature: String::new(),
|
signature: String::new(),
|
||||||
|
@ -307,9 +261,9 @@ fn signature_help_for_generics(
|
||||||
// eg. `None::<u8>`
|
// eg. `None::<u8>`
|
||||||
// We'll use the signature of the enum, but include the docs of the variant.
|
// We'll use the signature of the enum, but include the docs of the variant.
|
||||||
res.doc = it.docs(db).map(|it| it.into());
|
res.doc = it.docs(db).map(|it| it.into());
|
||||||
let it = it.parent_enum(db);
|
let enum_ = it.parent_enum(db);
|
||||||
format_to!(res.signature, "enum {}", it.name(db));
|
format_to!(res.signature, "enum {}", enum_.name(db));
|
||||||
generics_def = it.into();
|
generics_def = enum_.into();
|
||||||
}
|
}
|
||||||
// These don't have generic args that can be specified
|
// These don't have generic args that can be specified
|
||||||
hir::GenericDef::Impl(_) | hir::GenericDef::Const(_) => return None,
|
hir::GenericDef::Impl(_) | hir::GenericDef::Const(_) => return None,
|
||||||
|
@ -388,16 +342,13 @@ fn signature_help_for_record_lit(
|
||||||
record: ast::RecordExpr,
|
record: ast::RecordExpr,
|
||||||
token: SyntaxToken,
|
token: SyntaxToken,
|
||||||
) -> Option<SignatureHelp> {
|
) -> Option<SignatureHelp> {
|
||||||
let arg_list = record
|
let active_parameter = record
|
||||||
.syntax()
|
|
||||||
.ancestors()
|
|
||||||
.filter_map(ast::RecordExpr::cast)
|
|
||||||
.find(|list| list.syntax().text_range().contains(token.text_range().start()))?;
|
|
||||||
|
|
||||||
let active_parameter = arg_list
|
|
||||||
.record_expr_field_list()?
|
.record_expr_field_list()?
|
||||||
.fields()
|
.syntax()
|
||||||
.take_while(|arg| arg.syntax().text_range().end() <= token.text_range().start())
|
.children_with_tokens()
|
||||||
|
.filter_map(syntax::NodeOrToken::into_token)
|
||||||
|
.filter(|t| t.kind() == syntax::T![,])
|
||||||
|
.take_while(|t| t.text_range().start() <= token.text_range().start())
|
||||||
.count();
|
.count();
|
||||||
|
|
||||||
let mut res = SignatureHelp {
|
let mut res = SignatureHelp {
|
||||||
|
@ -1594,4 +1545,27 @@ impl S {
|
||||||
"#]],
|
"#]],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_enum_in_nested_method_in_lambda() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
enum A {
|
||||||
|
A,
|
||||||
|
B
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bar(_: A) { }
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let foo = Foo;
|
||||||
|
std::thread::spawn(move || { bar(A:$0) } );
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn bar(_: A)
|
||||||
|
^^^^
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1126,5 +1126,5 @@ fn benchmark_syntax_highlighting_parser() {
|
||||||
.filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Function))
|
.filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Function))
|
||||||
.count()
|
.count()
|
||||||
};
|
};
|
||||||
assert_eq!(hash, 1609);
|
assert_eq!(hash, 1608);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ doctest = false
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cov-mark = "2.0.0-pre.1"
|
cov-mark = "2.0.0-pre.1"
|
||||||
rustc-hash = "1.1.0"
|
rustc-hash = "1.1.0"
|
||||||
smallvec = "1.10.0"
|
smallvec.workspace = true
|
||||||
tracing = "0.1.35"
|
tracing = "0.1.35"
|
||||||
|
|
||||||
# local deps
|
# local deps
|
||||||
|
|
|
@ -20,4 +20,5 @@ limit.workspace = true
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
expect-test = "1.4.0"
|
expect-test = "1.4.0"
|
||||||
|
|
||||||
|
stdx.workspace = true
|
||||||
sourcegen.workspace = true
|
sourcegen.workspace = true
|
||||||
|
|
|
@ -200,6 +200,8 @@ impl BlockLike {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const VISIBILITY_FIRST: TokenSet = TokenSet::new(&[T![pub], T![crate]]);
|
||||||
|
|
||||||
fn opt_visibility(p: &mut Parser<'_>, in_tuple_field: bool) -> bool {
|
fn opt_visibility(p: &mut Parser<'_>, in_tuple_field: bool) -> bool {
|
||||||
match p.current() {
|
match p.current() {
|
||||||
T![pub] => {
|
T![pub] => {
|
||||||
|
@ -340,3 +342,31 @@ fn error_block(p: &mut Parser<'_>, message: &str) {
|
||||||
p.eat(T!['}']);
|
p.eat(T!['}']);
|
||||||
m.complete(p, ERROR);
|
m.complete(p, ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The `parser` passed this is required to at least consume one token if it returns `true`.
|
||||||
|
/// If the `parser` returns false, parsing will stop.
|
||||||
|
fn delimited(
|
||||||
|
p: &mut Parser<'_>,
|
||||||
|
bra: SyntaxKind,
|
||||||
|
ket: SyntaxKind,
|
||||||
|
delim: SyntaxKind,
|
||||||
|
first_set: TokenSet,
|
||||||
|
mut parser: impl FnMut(&mut Parser<'_>) -> bool,
|
||||||
|
) {
|
||||||
|
p.bump(bra);
|
||||||
|
while !p.at(ket) && !p.at(EOF) {
|
||||||
|
if !parser(p) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if !p.at(delim) {
|
||||||
|
if p.at_ts(first_set) {
|
||||||
|
p.error(format!("expected {:?}", delim));
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p.bump(delim);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.expect(ket);
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
pub(super) const ATTRIBUTE_FIRST: TokenSet = TokenSet::new(&[T![#]]);
|
||||||
|
|
||||||
pub(super) fn inner_attrs(p: &mut Parser<'_>) {
|
pub(super) fn inner_attrs(p: &mut Parser<'_>) {
|
||||||
while p.at(T![#]) && p.nth(1) == T![!] {
|
while p.at(T![#]) && p.nth(1) == T![!] {
|
||||||
attr(p, true);
|
attr(p, true);
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
mod atom;
|
mod atom;
|
||||||
|
|
||||||
|
use crate::grammar::attributes::ATTRIBUTE_FIRST;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub(crate) use self::atom::{block_expr, match_arm_list};
|
pub(crate) use self::atom::{block_expr, match_arm_list};
|
||||||
|
@ -68,6 +70,12 @@ pub(super) fn stmt(p: &mut Parser<'_>, semicolon: Semicolon) {
|
||||||
Err(m) => m,
|
Err(m) => m,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if !p.at_ts(EXPR_FIRST) {
|
||||||
|
p.err_and_bump("expected expression, item or let statement");
|
||||||
|
m.abandon(p);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some((cm, blocklike)) = expr_stmt(p, Some(m)) {
|
if let Some((cm, blocklike)) = expr_stmt(p, Some(m)) {
|
||||||
if !(p.at(T!['}']) || (semicolon != Semicolon::Required && p.at(EOF))) {
|
if !(p.at(T!['}']) || (semicolon != Semicolon::Required && p.at(EOF))) {
|
||||||
// test no_semi_after_block
|
// test no_semi_after_block
|
||||||
|
@ -227,6 +235,12 @@ fn expr_bp(
|
||||||
attributes::outer_attrs(p);
|
attributes::outer_attrs(p);
|
||||||
m
|
m
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if !p.at_ts(EXPR_FIRST) {
|
||||||
|
p.err_recover("expected expression", atom::EXPR_RECOVERY_SET);
|
||||||
|
m.abandon(p);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let mut lhs = match lhs(p, r) {
|
let mut lhs = match lhs(p, r) {
|
||||||
Some((lhs, blocklike)) => {
|
Some((lhs, blocklike)) => {
|
||||||
let lhs = lhs.extend_to(p, m);
|
let lhs = lhs.extend_to(p, m);
|
||||||
|
@ -551,23 +565,20 @@ fn cast_expr(p: &mut Parser<'_>, lhs: CompletedMarker) -> CompletedMarker {
|
||||||
m.complete(p, CAST_EXPR)
|
m.complete(p, CAST_EXPR)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// test_err arg_list_recovery
|
||||||
|
// fn main() {
|
||||||
|
// foo(bar::);
|
||||||
|
// foo(bar:);
|
||||||
|
// foo(bar+);
|
||||||
|
// }
|
||||||
fn arg_list(p: &mut Parser<'_>) {
|
fn arg_list(p: &mut Parser<'_>) {
|
||||||
assert!(p.at(T!['(']));
|
assert!(p.at(T!['(']));
|
||||||
let m = p.start();
|
let m = p.start();
|
||||||
p.bump(T!['(']);
|
|
||||||
while !p.at(T![')']) && !p.at(EOF) {
|
|
||||||
// test arg_with_attr
|
// test arg_with_attr
|
||||||
// fn main() {
|
// fn main() {
|
||||||
// foo(#[attr] 92)
|
// foo(#[attr] 92)
|
||||||
// }
|
// }
|
||||||
if !expr(p) {
|
delimited(p, T!['('], T![')'], T![,], EXPR_FIRST.union(ATTRIBUTE_FIRST), expr);
|
||||||
break;
|
|
||||||
}
|
|
||||||
if !p.at(T![')']) && !p.expect(T![,]) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.eat(T![')']);
|
|
||||||
m.complete(p, ARG_LIST);
|
m.complete(p, ARG_LIST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,26 +40,28 @@ pub(super) const ATOM_EXPR_FIRST: TokenSet =
|
||||||
T!['{'],
|
T!['{'],
|
||||||
T!['['],
|
T!['['],
|
||||||
T![|],
|
T![|],
|
||||||
T![move],
|
|
||||||
T![box],
|
|
||||||
T![if],
|
|
||||||
T![while],
|
|
||||||
T![match],
|
|
||||||
T![unsafe],
|
|
||||||
T![return],
|
|
||||||
T![yield],
|
|
||||||
T![do],
|
|
||||||
T![break],
|
|
||||||
T![continue],
|
|
||||||
T![async],
|
T![async],
|
||||||
T![try],
|
T![box],
|
||||||
|
T![break],
|
||||||
T![const],
|
T![const],
|
||||||
T![loop],
|
T![continue],
|
||||||
|
T![do],
|
||||||
T![for],
|
T![for],
|
||||||
|
T![if],
|
||||||
|
T![let],
|
||||||
|
T![loop],
|
||||||
|
T![match],
|
||||||
|
T![move],
|
||||||
|
T![return],
|
||||||
|
T![static],
|
||||||
|
T![try],
|
||||||
|
T![unsafe],
|
||||||
|
T![while],
|
||||||
|
T![yield],
|
||||||
LIFETIME_IDENT,
|
LIFETIME_IDENT,
|
||||||
]));
|
]));
|
||||||
|
|
||||||
const EXPR_RECOVERY_SET: TokenSet = TokenSet::new(&[T![let]]);
|
pub(super) const EXPR_RECOVERY_SET: TokenSet = TokenSet::new(&[T![')'], T![']']]);
|
||||||
|
|
||||||
pub(super) fn atom_expr(
|
pub(super) fn atom_expr(
|
||||||
p: &mut Parser<'_>,
|
p: &mut Parser<'_>,
|
||||||
|
@ -116,7 +118,7 @@ pub(super) fn atom_expr(
|
||||||
// fn main() {
|
// fn main() {
|
||||||
// 'loop: impl
|
// 'loop: impl
|
||||||
// }
|
// }
|
||||||
p.error("expected a loop");
|
p.error("expected a loop or block");
|
||||||
m.complete(p, ERROR);
|
m.complete(p, ERROR);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
@ -157,7 +159,7 @@ pub(super) fn atom_expr(
|
||||||
T![for] => for_expr(p, None),
|
T![for] => for_expr(p, None),
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
p.err_recover("expected expression", EXPR_RECOVERY_SET);
|
p.err_and_bump("expected expression");
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,27 +5,35 @@ pub(super) fn opt_generic_arg_list(p: &mut Parser<'_>, colon_colon_required: boo
|
||||||
if p.at(T![::]) && p.nth(2) == T![<] {
|
if p.at(T![::]) && p.nth(2) == T![<] {
|
||||||
m = p.start();
|
m = p.start();
|
||||||
p.bump(T![::]);
|
p.bump(T![::]);
|
||||||
p.bump(T![<]);
|
|
||||||
} else if !colon_colon_required && p.at(T![<]) && p.nth(1) != T![=] {
|
} else if !colon_colon_required && p.at(T![<]) && p.nth(1) != T![=] {
|
||||||
m = p.start();
|
m = p.start();
|
||||||
p.bump(T![<]);
|
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
while !p.at(EOF) && !p.at(T![>]) {
|
delimited(p, T![<], T![>], T![,], GENERIC_ARG_FIRST, generic_arg);
|
||||||
generic_arg(p);
|
|
||||||
if !p.at(T![>]) && !p.expect(T![,]) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.expect(T![>]);
|
|
||||||
m.complete(p, GENERIC_ARG_LIST);
|
m.complete(p, GENERIC_ARG_LIST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const GENERIC_ARG_FIRST: TokenSet = TokenSet::new(&[
|
||||||
|
LIFETIME_IDENT,
|
||||||
|
IDENT,
|
||||||
|
T!['{'],
|
||||||
|
T![true],
|
||||||
|
T![false],
|
||||||
|
T![-],
|
||||||
|
INT_NUMBER,
|
||||||
|
FLOAT_NUMBER,
|
||||||
|
CHAR,
|
||||||
|
BYTE,
|
||||||
|
STRING,
|
||||||
|
BYTE_STRING,
|
||||||
|
])
|
||||||
|
.union(types::TYPE_FIRST);
|
||||||
|
|
||||||
// test generic_arg
|
// test generic_arg
|
||||||
// type T = S<i32>;
|
// type T = S<i32>;
|
||||||
fn generic_arg(p: &mut Parser<'_>) {
|
fn generic_arg(p: &mut Parser<'_>) -> bool {
|
||||||
match p.current() {
|
match p.current() {
|
||||||
LIFETIME_IDENT => lifetime_arg(p),
|
LIFETIME_IDENT => lifetime_arg(p),
|
||||||
T!['{'] | T![true] | T![false] | T![-] => const_arg(p),
|
T!['{'] | T![true] | T![false] | T![-] => const_arg(p),
|
||||||
|
@ -68,8 +76,10 @@ fn generic_arg(p: &mut Parser<'_>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => type_arg(p),
|
_ if p.at_ts(types::TYPE_FIRST) => type_arg(p),
|
||||||
|
_ => return false,
|
||||||
}
|
}
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
// test lifetime_arg
|
// test lifetime_arg
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use crate::grammar::attributes::ATTRIBUTE_FIRST;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub(super) fn opt_generic_param_list(p: &mut Parser<'_>) {
|
pub(super) fn opt_generic_param_list(p: &mut Parser<'_>) {
|
||||||
|
@ -11,32 +13,31 @@ pub(super) fn opt_generic_param_list(p: &mut Parser<'_>) {
|
||||||
fn generic_param_list(p: &mut Parser<'_>) {
|
fn generic_param_list(p: &mut Parser<'_>) {
|
||||||
assert!(p.at(T![<]));
|
assert!(p.at(T![<]));
|
||||||
let m = p.start();
|
let m = p.start();
|
||||||
p.bump(T![<]);
|
delimited(p, T![<], T![>], T![,], GENERIC_PARAM_FIRST.union(ATTRIBUTE_FIRST), |p| {
|
||||||
|
// test generic_param_attribute
|
||||||
|
// fn foo<#[lt_attr] 'a, #[t_attr] T>() {}
|
||||||
|
let m = p.start();
|
||||||
|
attributes::outer_attrs(p);
|
||||||
|
generic_param(p, m)
|
||||||
|
});
|
||||||
|
|
||||||
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);
|
m.complete(p, GENERIC_PARAM_LIST);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generic_param(p: &mut Parser<'_>) {
|
const GENERIC_PARAM_FIRST: TokenSet = TokenSet::new(&[IDENT, LIFETIME_IDENT, T![const]]);
|
||||||
let m = p.start();
|
|
||||||
// test generic_param_attribute
|
fn generic_param(p: &mut Parser<'_>, m: Marker) -> bool {
|
||||||
// fn foo<#[lt_attr] 'a, #[t_attr] T>() {}
|
|
||||||
attributes::outer_attrs(p);
|
|
||||||
match p.current() {
|
match p.current() {
|
||||||
LIFETIME_IDENT => lifetime_param(p, m),
|
LIFETIME_IDENT => lifetime_param(p, m),
|
||||||
IDENT => type_param(p, m),
|
IDENT => type_param(p, m),
|
||||||
T![const] => const_param(p, m),
|
T![const] => const_param(p, m),
|
||||||
_ => {
|
_ => {
|
||||||
m.abandon(p);
|
m.abandon(p);
|
||||||
p.err_and_bump("expected type parameter");
|
p.err_and_bump("expected generic parameter");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
// test lifetime_param
|
// test lifetime_param
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use crate::grammar::attributes::ATTRIBUTE_FIRST;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
// test struct_item
|
// test struct_item
|
||||||
|
@ -141,28 +143,31 @@ pub(crate) fn record_field_list(p: &mut Parser<'_>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TUPLE_FIELD_FIRST: TokenSet =
|
||||||
|
types::TYPE_FIRST.union(ATTRIBUTE_FIRST).union(VISIBILITY_FIRST);
|
||||||
|
|
||||||
fn tuple_field_list(p: &mut Parser<'_>) {
|
fn tuple_field_list(p: &mut Parser<'_>) {
|
||||||
assert!(p.at(T!['(']));
|
assert!(p.at(T!['(']));
|
||||||
let m = p.start();
|
let m = p.start();
|
||||||
p.bump(T!['(']);
|
delimited(p, T!['('], T![')'], T![,], TUPLE_FIELD_FIRST, |p| {
|
||||||
while !p.at(T![')']) && !p.at(EOF) {
|
|
||||||
let m = p.start();
|
let m = p.start();
|
||||||
// test tuple_field_attrs
|
// test tuple_field_attrs
|
||||||
// struct S (#[attr] f32);
|
// struct S (#[attr] f32);
|
||||||
attributes::outer_attrs(p);
|
attributes::outer_attrs(p);
|
||||||
opt_visibility(p, true);
|
let has_vis = opt_visibility(p, true);
|
||||||
if !p.at_ts(types::TYPE_FIRST) {
|
if !p.at_ts(types::TYPE_FIRST) {
|
||||||
p.error("expected a type");
|
p.error("expected a type");
|
||||||
|
if has_vis {
|
||||||
m.complete(p, ERROR);
|
m.complete(p, ERROR);
|
||||||
break;
|
} else {
|
||||||
|
m.abandon(p);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
types::type_(p);
|
types::type_(p);
|
||||||
m.complete(p, TUPLE_FIELD);
|
m.complete(p, TUPLE_FIELD);
|
||||||
|
true
|
||||||
|
});
|
||||||
|
|
||||||
if !p.at(T![')']) {
|
|
||||||
p.expect(T![,]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.expect(T![')']);
|
|
||||||
m.complete(p, TUPLE_FIELD_LIST);
|
m.complete(p, TUPLE_FIELD_LIST);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use crate::grammar::attributes::ATTRIBUTE_FIRST;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
// test param_list
|
// test param_list
|
||||||
|
@ -66,14 +68,20 @@ fn list_(p: &mut Parser<'_>, flavor: Flavor) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if !p.at_ts(PARAM_FIRST) {
|
if !p.at_ts(PARAM_FIRST.union(ATTRIBUTE_FIRST)) {
|
||||||
p.error("expected value parameter");
|
p.error("expected value parameter");
|
||||||
m.abandon(p);
|
m.abandon(p);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
param(p, m, flavor);
|
param(p, m, flavor);
|
||||||
if !p.at(ket) {
|
if !p.at(T![,]) {
|
||||||
p.expect(T![,]);
|
if p.at_ts(PARAM_FIRST.union(ATTRIBUTE_FIRST)) {
|
||||||
|
p.error("expected `,`");
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p.bump(T![,]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,10 @@ fn path_for_qualifier(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const EXPR_PATH_SEGMENT_RECOVERY_SET: TokenSet =
|
||||||
|
items::ITEM_RECOVERY_SET.union(TokenSet::new(&[T![')'], T![,], T![let]]));
|
||||||
|
const TYPE_PATH_SEGMENT_RECOVERY_SET: TokenSet = types::TYPE_RECOVERY_SET;
|
||||||
|
|
||||||
fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) {
|
fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) {
|
||||||
let m = p.start();
|
let m = p.start();
|
||||||
// test qual_paths
|
// test qual_paths
|
||||||
|
@ -102,7 +106,12 @@ fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) {
|
||||||
m.complete(p, NAME_REF);
|
m.complete(p, NAME_REF);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
p.err_recover("expected identifier", items::ITEM_RECOVERY_SET);
|
let recover_set = match mode {
|
||||||
|
Mode::Use => items::ITEM_RECOVERY_SET,
|
||||||
|
Mode::Type => TYPE_PATH_SEGMENT_RECOVERY_SET,
|
||||||
|
Mode::Expr => EXPR_PATH_SEGMENT_RECOVERY_SET,
|
||||||
|
};
|
||||||
|
p.err_recover("expected identifier", recover_set);
|
||||||
if empty {
|
if empty {
|
||||||
// test_err empty_segment
|
// test_err empty_segment
|
||||||
// use crate::;
|
// use crate::;
|
||||||
|
|
|
@ -17,8 +17,9 @@ pub(super) const TYPE_FIRST: TokenSet = paths::PATH_FIRST.union(TokenSet::new(&[
|
||||||
T![Self],
|
T![Self],
|
||||||
]));
|
]));
|
||||||
|
|
||||||
const TYPE_RECOVERY_SET: TokenSet = TokenSet::new(&[
|
pub(super) const TYPE_RECOVERY_SET: TokenSet = TokenSet::new(&[
|
||||||
T![')'],
|
T![')'],
|
||||||
|
T![>],
|
||||||
T![,],
|
T![,],
|
||||||
// test_err struct_field_recover
|
// test_err struct_field_recover
|
||||||
// struct S { f pub g: () }
|
// struct S { f pub g: () }
|
||||||
|
|
|
@ -15,6 +15,7 @@ use crate::{LexedStr, TopEntryPoint};
|
||||||
#[test]
|
#[test]
|
||||||
fn lex_ok() {
|
fn lex_ok() {
|
||||||
for case in TestCase::list("lexer/ok") {
|
for case in TestCase::list("lexer/ok") {
|
||||||
|
let _guard = stdx::panic_context::enter(format!("{:?}", case.rs));
|
||||||
let actual = lex(&case.text);
|
let actual = lex(&case.text);
|
||||||
expect_file![case.rast].assert_eq(&actual)
|
expect_file![case.rast].assert_eq(&actual)
|
||||||
}
|
}
|
||||||
|
@ -23,6 +24,7 @@ fn lex_ok() {
|
||||||
#[test]
|
#[test]
|
||||||
fn lex_err() {
|
fn lex_err() {
|
||||||
for case in TestCase::list("lexer/err") {
|
for case in TestCase::list("lexer/err") {
|
||||||
|
let _guard = stdx::panic_context::enter(format!("{:?}", case.rs));
|
||||||
let actual = lex(&case.text);
|
let actual = lex(&case.text);
|
||||||
expect_file![case.rast].assert_eq(&actual)
|
expect_file![case.rast].assert_eq(&actual)
|
||||||
}
|
}
|
||||||
|
@ -46,6 +48,7 @@ fn lex(text: &str) -> String {
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_ok() {
|
fn parse_ok() {
|
||||||
for case in TestCase::list("parser/ok") {
|
for case in TestCase::list("parser/ok") {
|
||||||
|
let _guard = stdx::panic_context::enter(format!("{:?}", case.rs));
|
||||||
let (actual, errors) = parse(TopEntryPoint::SourceFile, &case.text);
|
let (actual, errors) = parse(TopEntryPoint::SourceFile, &case.text);
|
||||||
assert!(!errors, "errors in an OK file {}:\n{actual}", case.rs.display());
|
assert!(!errors, "errors in an OK file {}:\n{actual}", case.rs.display());
|
||||||
expect_file![case.rast].assert_eq(&actual);
|
expect_file![case.rast].assert_eq(&actual);
|
||||||
|
@ -55,6 +58,7 @@ fn parse_ok() {
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_inline_ok() {
|
fn parse_inline_ok() {
|
||||||
for case in TestCase::list("parser/inline/ok") {
|
for case in TestCase::list("parser/inline/ok") {
|
||||||
|
let _guard = stdx::panic_context::enter(format!("{:?}", case.rs));
|
||||||
let (actual, errors) = parse(TopEntryPoint::SourceFile, &case.text);
|
let (actual, errors) = parse(TopEntryPoint::SourceFile, &case.text);
|
||||||
assert!(!errors, "errors in an OK file {}:\n{actual}", case.rs.display());
|
assert!(!errors, "errors in an OK file {}:\n{actual}", case.rs.display());
|
||||||
expect_file![case.rast].assert_eq(&actual);
|
expect_file![case.rast].assert_eq(&actual);
|
||||||
|
@ -64,6 +68,7 @@ fn parse_inline_ok() {
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_err() {
|
fn parse_err() {
|
||||||
for case in TestCase::list("parser/err") {
|
for case in TestCase::list("parser/err") {
|
||||||
|
let _guard = stdx::panic_context::enter(format!("{:?}", case.rs));
|
||||||
let (actual, errors) = parse(TopEntryPoint::SourceFile, &case.text);
|
let (actual, errors) = parse(TopEntryPoint::SourceFile, &case.text);
|
||||||
assert!(errors, "no errors in an ERR file {}:\n{actual}", case.rs.display());
|
assert!(errors, "no errors in an ERR file {}:\n{actual}", case.rs.display());
|
||||||
expect_file![case.rast].assert_eq(&actual)
|
expect_file![case.rast].assert_eq(&actual)
|
||||||
|
@ -73,6 +78,7 @@ fn parse_err() {
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_inline_err() {
|
fn parse_inline_err() {
|
||||||
for case in TestCase::list("parser/inline/err") {
|
for case in TestCase::list("parser/inline/err") {
|
||||||
|
let _guard = stdx::panic_context::enter(format!("{:?}", case.rs));
|
||||||
let (actual, errors) = parse(TopEntryPoint::SourceFile, &case.text);
|
let (actual, errors) = parse(TopEntryPoint::SourceFile, &case.text);
|
||||||
assert!(errors, "no errors in an ERR file {}:\n{actual}", case.rs.display());
|
assert!(errors, "no errors in an ERR file {}:\n{actual}", case.rs.display());
|
||||||
expect_file![case.rast].assert_eq(&actual)
|
expect_file![case.rast].assert_eq(&actual)
|
||||||
|
|
|
@ -65,7 +65,7 @@ fn macro_stmt() {
|
||||||
MACRO_STMTS
|
MACRO_STMTS
|
||||||
ERROR
|
ERROR
|
||||||
SHEBANG "#!/usr/bin/rust"
|
SHEBANG "#!/usr/bin/rust"
|
||||||
error 0: expected expression
|
error 0: expected expression, item or let statement
|
||||||
"##]],
|
"##]],
|
||||||
);
|
);
|
||||||
check(
|
check(
|
||||||
|
|
|
@ -44,8 +44,7 @@ SOURCE_FILE
|
||||||
IDENT "T"
|
IDENT "T"
|
||||||
SEMICOLON ";"
|
SEMICOLON ";"
|
||||||
WHITESPACE "\n"
|
WHITESPACE "\n"
|
||||||
error 9: expected type parameter
|
error 9: expected generic parameter
|
||||||
error 11: expected COMMA
|
|
||||||
error 11: expected R_ANGLE
|
error 11: expected R_ANGLE
|
||||||
error 11: expected `;`, `{`, or `(`
|
error 11: expected `;`, `{`, or `(`
|
||||||
error 12: expected an item
|
error 12: expected an item
|
||||||
|
|
|
@ -43,18 +43,15 @@ SOURCE_FILE
|
||||||
IDENT "Box"
|
IDENT "Box"
|
||||||
GENERIC_ARG_LIST
|
GENERIC_ARG_LIST
|
||||||
L_ANGLE "<"
|
L_ANGLE "<"
|
||||||
TYPE_ARG
|
|
||||||
ERROR
|
ERROR
|
||||||
AT "@"
|
AT "@"
|
||||||
WHITESPACE " "
|
WHITESPACE " "
|
||||||
TUPLE_FIELD
|
MACRO_CALL
|
||||||
PATH_TYPE
|
|
||||||
PATH
|
PATH
|
||||||
PATH_SEGMENT
|
PATH_SEGMENT
|
||||||
NAME_REF
|
NAME_REF
|
||||||
IDENT "Any"
|
IDENT "Any"
|
||||||
ERROR
|
ERROR
|
||||||
ERROR
|
|
||||||
R_ANGLE ">"
|
R_ANGLE ">"
|
||||||
ERROR
|
ERROR
|
||||||
COMMA ","
|
COMMA ","
|
||||||
|
@ -69,17 +66,14 @@ SOURCE_FILE
|
||||||
ERROR
|
ERROR
|
||||||
SEMICOLON ";"
|
SEMICOLON ";"
|
||||||
WHITESPACE "\n\n"
|
WHITESPACE "\n\n"
|
||||||
error 67: expected type
|
error 67: expected R_ANGLE
|
||||||
error 68: expected COMMA
|
error 67: expected R_ANGLE
|
||||||
error 68: expected R_ANGLE
|
error 67: expected R_ANGLE
|
||||||
error 68: expected COMMA
|
error 67: expected R_PAREN
|
||||||
error 68: expected R_ANGLE
|
error 67: expected SEMICOLON
|
||||||
error 68: expected COMMA
|
error 67: expected an item
|
||||||
error 68: expected R_ANGLE
|
error 72: expected BANG
|
||||||
error 68: expected COMMA
|
error 72: expected `{`, `[`, `(`
|
||||||
error 72: expected COMMA
|
|
||||||
error 72: expected a type
|
|
||||||
error 72: expected R_PAREN
|
|
||||||
error 72: expected SEMICOLON
|
error 72: expected SEMICOLON
|
||||||
error 72: expected an item
|
error 72: expected an item
|
||||||
error 73: expected an item
|
error 73: expected an item
|
||||||
|
|
|
@ -145,27 +145,29 @@ SOURCE_FILE
|
||||||
error 16: expected expression
|
error 16: expected expression
|
||||||
error 17: expected R_BRACK
|
error 17: expected R_BRACK
|
||||||
error 17: expected SEMICOLON
|
error 17: expected SEMICOLON
|
||||||
error 17: expected expression
|
error 17: expected expression, item or let statement
|
||||||
error 25: expected a name
|
error 25: expected a name
|
||||||
error 26: expected `;`, `{`, or `(`
|
error 26: expected `;`, `{`, or `(`
|
||||||
error 30: expected pattern
|
error 30: expected pattern
|
||||||
error 31: expected SEMICOLON
|
error 31: expected SEMICOLON
|
||||||
error 53: expected expression
|
error 53: expected expression
|
||||||
|
error 54: expected R_PAREN
|
||||||
error 54: expected SEMICOLON
|
error 54: expected SEMICOLON
|
||||||
error 54: expected expression
|
error 54: expected expression, item or let statement
|
||||||
error 60: expected type
|
error 60: expected type
|
||||||
error 60: expected `{`
|
error 60: expected `{`
|
||||||
error 60: expected expression
|
error 60: expected expression, item or let statement
|
||||||
error 65: expected pattern
|
error 65: expected pattern
|
||||||
error 65: expected SEMICOLON
|
error 65: expected SEMICOLON
|
||||||
error 65: expected expression
|
error 65: expected expression, item or let statement
|
||||||
error 92: expected expression
|
error 92: expected expression
|
||||||
|
error 93: expected R_PAREN
|
||||||
error 93: expected SEMICOLON
|
error 93: expected SEMICOLON
|
||||||
error 93: expected expression
|
error 93: expected expression, item or let statement
|
||||||
error 95: expected expression
|
error 95: expected expression, item or let statement
|
||||||
error 96: expected expression
|
error 96: expected expression, item or let statement
|
||||||
error 103: expected a name
|
error 103: expected a name
|
||||||
error 104: expected `{`
|
error 104: expected `{`
|
||||||
error 108: expected pattern
|
error 108: expected pattern
|
||||||
error 108: expected SEMICOLON
|
error 108: expected SEMICOLON
|
||||||
error 108: expected expression
|
error 108: expected expression, item or let statement
|
||||||
|
|
|
@ -168,8 +168,8 @@ SOURCE_FILE
|
||||||
L_PAREN "("
|
L_PAREN "("
|
||||||
ERROR
|
ERROR
|
||||||
QUESTION "?"
|
QUESTION "?"
|
||||||
EXPR_STMT
|
TYPE_ARG
|
||||||
PATH_EXPR
|
PATH_TYPE
|
||||||
PATH
|
PATH
|
||||||
PATH_SEGMENT
|
PATH_SEGMENT
|
||||||
NAME_REF
|
NAME_REF
|
||||||
|
@ -180,6 +180,9 @@ SOURCE_FILE
|
||||||
ERROR
|
ERROR
|
||||||
PLUS "+"
|
PLUS "+"
|
||||||
WHITESPACE " "
|
WHITESPACE " "
|
||||||
|
EXPR_STMT
|
||||||
|
BIN_EXPR
|
||||||
|
BIN_EXPR
|
||||||
TUPLE_EXPR
|
TUPLE_EXPR
|
||||||
L_PAREN "("
|
L_PAREN "("
|
||||||
CLOSURE_EXPR
|
CLOSURE_EXPR
|
||||||
|
@ -191,8 +194,6 @@ SOURCE_FILE
|
||||||
LIFETIME_IDENT "'a"
|
LIFETIME_IDENT "'a"
|
||||||
R_ANGLE ">"
|
R_ANGLE ">"
|
||||||
WHITESPACE " "
|
WHITESPACE " "
|
||||||
BIN_EXPR
|
|
||||||
BIN_EXPR
|
|
||||||
BIN_EXPR
|
BIN_EXPR
|
||||||
BIN_EXPR
|
BIN_EXPR
|
||||||
PATH_EXPR
|
PATH_EXPR
|
||||||
|
@ -204,7 +205,6 @@ SOURCE_FILE
|
||||||
ERROR
|
ERROR
|
||||||
LIFETIME_IDENT "'a"
|
LIFETIME_IDENT "'a"
|
||||||
R_ANGLE ">"
|
R_ANGLE ">"
|
||||||
ERROR
|
|
||||||
R_PAREN ")"
|
R_PAREN ")"
|
||||||
WHITESPACE " "
|
WHITESPACE " "
|
||||||
PLUS "+"
|
PLUS "+"
|
||||||
|
@ -221,25 +221,27 @@ SOURCE_FILE
|
||||||
ERROR
|
ERROR
|
||||||
SEMICOLON ";"
|
SEMICOLON ";"
|
||||||
WHITESPACE "\n "
|
WHITESPACE "\n "
|
||||||
LET_EXPR
|
LET_STMT
|
||||||
LET_KW "let"
|
LET_KW "let"
|
||||||
WHITESPACE " "
|
WHITESPACE " "
|
||||||
WILDCARD_PAT
|
WILDCARD_PAT
|
||||||
UNDERSCORE "_"
|
UNDERSCORE "_"
|
||||||
ERROR
|
|
||||||
COLON ":"
|
COLON ":"
|
||||||
WHITESPACE " "
|
WHITESPACE " "
|
||||||
BIN_EXPR
|
DYN_TRAIT_TYPE
|
||||||
BIN_EXPR
|
TYPE_BOUND_LIST
|
||||||
PATH_EXPR
|
TYPE_BOUND
|
||||||
|
PATH_TYPE
|
||||||
PATH
|
PATH
|
||||||
PATH_SEGMENT
|
PATH_SEGMENT
|
||||||
NAME_REF
|
NAME_REF
|
||||||
IDENT "Box"
|
IDENT "Box"
|
||||||
|
GENERIC_ARG_LIST
|
||||||
L_ANGLE "<"
|
L_ANGLE "<"
|
||||||
TUPLE_EXPR
|
TYPE_ARG
|
||||||
|
PAREN_TYPE
|
||||||
L_PAREN "("
|
L_PAREN "("
|
||||||
CLOSURE_EXPR
|
FOR_TYPE
|
||||||
FOR_KW "for"
|
FOR_KW "for"
|
||||||
GENERIC_PARAM_LIST
|
GENERIC_PARAM_LIST
|
||||||
L_ANGLE "<"
|
L_ANGLE "<"
|
||||||
|
@ -248,27 +250,24 @@ SOURCE_FILE
|
||||||
LIFETIME_IDENT "'a"
|
LIFETIME_IDENT "'a"
|
||||||
R_ANGLE ">"
|
R_ANGLE ">"
|
||||||
WHITESPACE " "
|
WHITESPACE " "
|
||||||
BIN_EXPR
|
PATH_TYPE
|
||||||
BIN_EXPR
|
|
||||||
BIN_EXPR
|
|
||||||
BIN_EXPR
|
|
||||||
PATH_EXPR
|
|
||||||
PATH
|
PATH
|
||||||
PATH_SEGMENT
|
PATH_SEGMENT
|
||||||
NAME_REF
|
NAME_REF
|
||||||
IDENT "Trait"
|
IDENT "Trait"
|
||||||
|
GENERIC_ARG_LIST
|
||||||
L_ANGLE "<"
|
L_ANGLE "<"
|
||||||
ERROR
|
LIFETIME_ARG
|
||||||
|
LIFETIME
|
||||||
LIFETIME_IDENT "'a"
|
LIFETIME_IDENT "'a"
|
||||||
R_ANGLE ">"
|
R_ANGLE ">"
|
||||||
ERROR
|
|
||||||
R_PAREN ")"
|
R_PAREN ")"
|
||||||
WHITESPACE " "
|
WHITESPACE " "
|
||||||
PLUS "+"
|
PLUS "+"
|
||||||
WHITESPACE " "
|
WHITESPACE " "
|
||||||
PAREN_EXPR
|
TYPE_BOUND
|
||||||
L_PAREN "("
|
L_PAREN "("
|
||||||
PATH_EXPR
|
PATH_TYPE
|
||||||
PATH
|
PATH
|
||||||
PATH_SEGMENT
|
PATH_SEGMENT
|
||||||
NAME_REF
|
NAME_REF
|
||||||
|
@ -277,51 +276,37 @@ SOURCE_FILE
|
||||||
WHITESPACE " "
|
WHITESPACE " "
|
||||||
PLUS "+"
|
PLUS "+"
|
||||||
WHITESPACE " "
|
WHITESPACE " "
|
||||||
PAREN_EXPR
|
TYPE_BOUND
|
||||||
L_PAREN "("
|
L_PAREN "("
|
||||||
ERROR
|
|
||||||
QUESTION "?"
|
QUESTION "?"
|
||||||
PATH_EXPR
|
PATH_TYPE
|
||||||
PATH
|
PATH
|
||||||
PATH_SEGMENT
|
PATH_SEGMENT
|
||||||
NAME_REF
|
NAME_REF
|
||||||
IDENT "Sized"
|
IDENT "Sized"
|
||||||
R_PAREN ")"
|
R_PAREN ")"
|
||||||
R_ANGLE ">"
|
|
||||||
ERROR
|
ERROR
|
||||||
|
R_ANGLE ">"
|
||||||
SEMICOLON ";"
|
SEMICOLON ";"
|
||||||
WHITESPACE "\n"
|
WHITESPACE "\n"
|
||||||
R_CURLY "}"
|
R_CURLY "}"
|
||||||
WHITESPACE "\n"
|
WHITESPACE "\n"
|
||||||
error 88: expected COMMA
|
|
||||||
error 88: expected R_ANGLE
|
error 88: expected R_ANGLE
|
||||||
error 121: expected SEMICOLON
|
error 121: expected SEMICOLON
|
||||||
error 121: expected expression
|
error 121: expected expression, item or let statement
|
||||||
error 140: expected type
|
error 140: expected type
|
||||||
error 141: expected R_PAREN
|
error 141: expected R_PAREN
|
||||||
error 141: expected COMMA
|
error 141: expected COMMA
|
||||||
error 141: expected R_ANGLE
|
error 146: expected R_ANGLE
|
||||||
error 141: expected SEMICOLON
|
|
||||||
error 146: expected SEMICOLON
|
error 146: expected SEMICOLON
|
||||||
error 146: expected expression
|
error 146: expected expression, item or let statement
|
||||||
error 148: expected expression
|
error 148: expected expression, item or let statement
|
||||||
error 158: expected `|`
|
error 158: expected `|`
|
||||||
error 158: expected COMMA
|
error 158: expected COMMA
|
||||||
error 165: expected expression
|
error 165: expected expression
|
||||||
error 168: expected expression
|
error 168: expected expression
|
||||||
error 179: expected expression
|
error 179: expected expression
|
||||||
error 180: expected COMMA
|
error 180: expected SEMICOLON
|
||||||
error 190: expected EQ
|
error 215: expected R_ANGLE
|
||||||
error 190: expected expression
|
error 235: expected SEMICOLON
|
||||||
error 191: expected COMMA
|
error 235: expected expression, item or let statement
|
||||||
error 204: expected `|`
|
|
||||||
error 204: expected COMMA
|
|
||||||
error 211: expected expression
|
|
||||||
error 214: expected expression
|
|
||||||
error 228: expected expression
|
|
||||||
error 229: expected R_PAREN
|
|
||||||
error 229: expected COMMA
|
|
||||||
error 236: expected expression
|
|
||||||
error 237: expected COMMA
|
|
||||||
error 237: expected expression
|
|
||||||
error 237: expected R_PAREN
|
|
||||||
|
|
|
@ -158,7 +158,6 @@ SOURCE_FILE
|
||||||
IDENT "i32"
|
IDENT "i32"
|
||||||
WHITESPACE " "
|
WHITESPACE " "
|
||||||
ERROR
|
ERROR
|
||||||
ERROR
|
|
||||||
L_CURLY "{"
|
L_CURLY "{"
|
||||||
R_CURLY "}"
|
R_CURLY "}"
|
||||||
ERROR
|
ERROR
|
||||||
|
@ -199,10 +198,8 @@ error 95: expected type
|
||||||
error 95: expected COMMA
|
error 95: expected COMMA
|
||||||
error 96: expected field
|
error 96: expected field
|
||||||
error 98: expected field declaration
|
error 98: expected field declaration
|
||||||
|
error 371: expected R_PAREN
|
||||||
error 371: expected COMMA
|
error 371: expected COMMA
|
||||||
error 372: expected a type
|
|
||||||
error 372: expected R_PAREN
|
|
||||||
error 372: expected COMMA
|
|
||||||
error 372: expected enum variant
|
error 372: expected enum variant
|
||||||
error 374: expected enum variant
|
error 374: expected enum variant
|
||||||
error 494: expected pattern
|
error 494: expected pattern
|
||||||
|
|
|
@ -72,4 +72,4 @@ SOURCE_FILE
|
||||||
error 24: expected existential, fn, trait or impl
|
error 24: expected existential, fn, trait or impl
|
||||||
error 41: expected existential, fn, trait or impl
|
error 41: expected existential, fn, trait or impl
|
||||||
error 56: expected a block
|
error 56: expected a block
|
||||||
error 75: expected a loop
|
error 75: expected a loop or block
|
||||||
|
|
|
@ -12,7 +12,7 @@ SOURCE_FILE
|
||||||
STMT_LIST
|
STMT_LIST
|
||||||
L_CURLY "{"
|
L_CURLY "{"
|
||||||
WHITESPACE "\n "
|
WHITESPACE "\n "
|
||||||
EXPR_STMT
|
BIN_EXPR
|
||||||
PATH_EXPR
|
PATH_EXPR
|
||||||
PATH
|
PATH
|
||||||
PATH_SEGMENT
|
PATH_SEGMENT
|
||||||
|
@ -41,13 +41,14 @@ SOURCE_FILE
|
||||||
COLON2 "::"
|
COLON2 "::"
|
||||||
ERROR
|
ERROR
|
||||||
L_ANGLE "<"
|
L_ANGLE "<"
|
||||||
BIN_EXPR
|
TYPE_ARG
|
||||||
PATH_EXPR
|
PATH_TYPE
|
||||||
PATH
|
PATH
|
||||||
PATH_SEGMENT
|
PATH_SEGMENT
|
||||||
NAME_REF
|
NAME_REF
|
||||||
IDENT "nope"
|
IDENT "nope"
|
||||||
SHR ">>"
|
R_ANGLE ">"
|
||||||
|
R_ANGLE ">"
|
||||||
ERROR
|
ERROR
|
||||||
SEMICOLON ";"
|
SEMICOLON ";"
|
||||||
WHITESPACE "\n"
|
WHITESPACE "\n"
|
||||||
|
@ -114,8 +115,6 @@ SOURCE_FILE
|
||||||
WHITESPACE "\n"
|
WHITESPACE "\n"
|
||||||
error 30: expected identifier
|
error 30: expected identifier
|
||||||
error 31: expected COMMA
|
error 31: expected COMMA
|
||||||
error 31: expected R_ANGLE
|
|
||||||
error 31: expected SEMICOLON
|
|
||||||
error 37: expected expression
|
error 37: expected expression
|
||||||
error 75: expected identifier
|
error 75: expected identifier
|
||||||
error 76: expected SEMICOLON
|
error 76: expected SEMICOLON
|
||||||
|
|
|
@ -23,6 +23,6 @@ SOURCE_FILE
|
||||||
WHITESPACE "\n"
|
WHITESPACE "\n"
|
||||||
R_CURLY "}"
|
R_CURLY "}"
|
||||||
WHITESPACE "\n"
|
WHITESPACE "\n"
|
||||||
error 22: expected a loop
|
error 22: expected a loop or block
|
||||||
error 27: expected type
|
error 27: expected type
|
||||||
error 27: expected `{`
|
error 27: expected `{`
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
SOURCE_FILE
|
||||||
|
FN
|
||||||
|
FN_KW "fn"
|
||||||
|
WHITESPACE " "
|
||||||
|
NAME
|
||||||
|
IDENT "main"
|
||||||
|
PARAM_LIST
|
||||||
|
L_PAREN "("
|
||||||
|
R_PAREN ")"
|
||||||
|
WHITESPACE " "
|
||||||
|
BLOCK_EXPR
|
||||||
|
STMT_LIST
|
||||||
|
L_CURLY "{"
|
||||||
|
WHITESPACE "\n "
|
||||||
|
EXPR_STMT
|
||||||
|
CALL_EXPR
|
||||||
|
PATH_EXPR
|
||||||
|
PATH
|
||||||
|
PATH_SEGMENT
|
||||||
|
NAME_REF
|
||||||
|
IDENT "foo"
|
||||||
|
ARG_LIST
|
||||||
|
L_PAREN "("
|
||||||
|
PATH_EXPR
|
||||||
|
PATH
|
||||||
|
PATH
|
||||||
|
PATH_SEGMENT
|
||||||
|
NAME_REF
|
||||||
|
IDENT "bar"
|
||||||
|
COLON2 "::"
|
||||||
|
R_PAREN ")"
|
||||||
|
SEMICOLON ";"
|
||||||
|
WHITESPACE "\n "
|
||||||
|
EXPR_STMT
|
||||||
|
CALL_EXPR
|
||||||
|
PATH_EXPR
|
||||||
|
PATH
|
||||||
|
PATH_SEGMENT
|
||||||
|
NAME_REF
|
||||||
|
IDENT "foo"
|
||||||
|
ARG_LIST
|
||||||
|
L_PAREN "("
|
||||||
|
PATH_EXPR
|
||||||
|
PATH
|
||||||
|
PATH_SEGMENT
|
||||||
|
NAME_REF
|
||||||
|
IDENT "bar"
|
||||||
|
ERROR
|
||||||
|
COLON ":"
|
||||||
|
R_PAREN ")"
|
||||||
|
SEMICOLON ";"
|
||||||
|
WHITESPACE "\n "
|
||||||
|
EXPR_STMT
|
||||||
|
CALL_EXPR
|
||||||
|
PATH_EXPR
|
||||||
|
PATH
|
||||||
|
PATH_SEGMENT
|
||||||
|
NAME_REF
|
||||||
|
IDENT "foo"
|
||||||
|
ARG_LIST
|
||||||
|
L_PAREN "("
|
||||||
|
BIN_EXPR
|
||||||
|
PATH_EXPR
|
||||||
|
PATH
|
||||||
|
PATH_SEGMENT
|
||||||
|
NAME_REF
|
||||||
|
IDENT "bar"
|
||||||
|
PLUS "+"
|
||||||
|
R_PAREN ")"
|
||||||
|
SEMICOLON ";"
|
||||||
|
WHITESPACE "\n"
|
||||||
|
R_CURLY "}"
|
||||||
|
WHITESPACE "\n"
|
||||||
|
error 25: expected identifier
|
||||||
|
error 39: expected COMMA
|
||||||
|
error 39: expected expression
|
||||||
|
error 55: expected expression
|
|
@ -0,0 +1,5 @@
|
||||||
|
fn main() {
|
||||||
|
foo(bar::);
|
||||||
|
foo(bar:);
|
||||||
|
foo(bar+);
|
||||||
|
}
|
|
@ -49,5 +49,5 @@ SOURCE_FILE
|
||||||
R_CURLY "}"
|
R_CURLY "}"
|
||||||
WHITESPACE "\n"
|
WHITESPACE "\n"
|
||||||
error 6: missing type for function parameter
|
error 6: missing type for function parameter
|
||||||
error 6: expected COMMA
|
error 6: expected `,`
|
||||||
error 16: missing type for function parameter
|
error 16: missing type for function parameter
|
||||||
|
|
|
@ -120,17 +120,20 @@ pub fn read_version(dylib_path: &AbsPath) -> io::Result<String> {
|
||||||
let version = u32::from_be_bytes([dot_rustc[4], dot_rustc[5], dot_rustc[6], dot_rustc[7]]);
|
let version = u32::from_be_bytes([dot_rustc[4], dot_rustc[5], dot_rustc[6], dot_rustc[7]]);
|
||||||
// Last supported version is:
|
// Last supported version is:
|
||||||
// https://github.com/rust-lang/rust/commit/0696e79f2740ad89309269b460579e548a5cd632
|
// https://github.com/rust-lang/rust/commit/0696e79f2740ad89309269b460579e548a5cd632
|
||||||
match version {
|
let snappy_portion = match version {
|
||||||
5 | 6 => {}
|
5 | 6 => &dot_rustc[8..],
|
||||||
|
7 => {
|
||||||
|
let len_bytes = &dot_rustc[8..12];
|
||||||
|
let data_len = u32::from_be_bytes(len_bytes.try_into().unwrap()) as usize;
|
||||||
|
&dot_rustc[12..data_len + 12]
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(io::Error::new(
|
return Err(io::Error::new(
|
||||||
io::ErrorKind::InvalidData,
|
io::ErrorKind::InvalidData,
|
||||||
format!("unsupported metadata version {version}"),
|
format!("unsupported metadata version {version}"),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
let snappy_portion = &dot_rustc[8..];
|
|
||||||
|
|
||||||
let mut snappy_decoder = SnapDecoder::new(snappy_portion);
|
let mut snappy_decoder = SnapDecoder::new(snappy_portion);
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ crossbeam-channel = "0.5.5"
|
||||||
dissimilar = "1.0.4"
|
dissimilar = "1.0.4"
|
||||||
itertools = "0.10.5"
|
itertools = "0.10.5"
|
||||||
scip = "0.1.1"
|
scip = "0.1.1"
|
||||||
lsp-types = { version = "=0.93.2", features = ["proposed"] }
|
lsp-types = { version = "=0.94", features = ["proposed"] }
|
||||||
parking_lot = "0.12.1"
|
parking_lot = "0.12.1"
|
||||||
xflags = "0.3.0"
|
xflags = "0.3.0"
|
||||||
oorandom = "11.1.3"
|
oorandom = "11.1.3"
|
||||||
|
|
BIN
crates/rust-analyzer/default_12483297303756020505_0.profraw
Normal file
BIN
crates/rust-analyzer/default_12483297303756020505_0.profraw
Normal file
Binary file not shown.
|
@ -1,4 +1,5 @@
|
||||||
//! Advertises the capabilities of the LSP Server.
|
//! Advertises the capabilities of the LSP Server.
|
||||||
|
use ide_db::line_index::WideEncoding;
|
||||||
use lsp_types::{
|
use lsp_types::{
|
||||||
CallHierarchyServerCapability, ClientCapabilities, CodeActionKind, CodeActionOptions,
|
CallHierarchyServerCapability, ClientCapabilities, CodeActionKind, CodeActionOptions,
|
||||||
CodeActionProviderCapability, CodeLensOptions, CompletionOptions,
|
CodeActionProviderCapability, CodeLensOptions, CompletionOptions,
|
||||||
|
@ -16,16 +17,19 @@ use lsp_types::{
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
use crate::config::{Config, RustfmtConfig};
|
use crate::config::{Config, RustfmtConfig};
|
||||||
use crate::lsp_ext::supports_utf8;
|
use crate::line_index::PositionEncoding;
|
||||||
|
use crate::lsp_ext::negotiated_encoding;
|
||||||
use crate::semantic_tokens;
|
use crate::semantic_tokens;
|
||||||
|
|
||||||
pub fn server_capabilities(config: &Config) -> ServerCapabilities {
|
pub fn server_capabilities(config: &Config) -> ServerCapabilities {
|
||||||
ServerCapabilities {
|
ServerCapabilities {
|
||||||
position_encoding: if supports_utf8(config.caps()) {
|
position_encoding: Some(match negotiated_encoding(config.caps()) {
|
||||||
Some(PositionEncodingKind::UTF8)
|
PositionEncoding::Utf8 => PositionEncodingKind::UTF8,
|
||||||
} else {
|
PositionEncoding::Wide(wide) => match wide {
|
||||||
None
|
WideEncoding::Utf16 => PositionEncodingKind::UTF16,
|
||||||
|
WideEncoding::Utf32 => PositionEncodingKind::UTF32,
|
||||||
},
|
},
|
||||||
|
}),
|
||||||
text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions {
|
text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions {
|
||||||
open_close: Some(true),
|
open_close: Some(true),
|
||||||
change: Some(TextDocumentSyncKind::INCREMENTAL),
|
change: Some(TextDocumentSyncKind::INCREMENTAL),
|
||||||
|
@ -134,6 +138,7 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities {
|
||||||
resolve_provider: Some(true),
|
resolve_provider: Some(true),
|
||||||
},
|
},
|
||||||
))),
|
))),
|
||||||
|
inline_value_provider: None,
|
||||||
experimental: Some(json!({
|
experimental: Some(json!({
|
||||||
"externalDocs": true,
|
"externalDocs": true,
|
||||||
"hoverRange": true,
|
"hoverRange": true,
|
||||||
|
|
|
@ -11,6 +11,7 @@ use ide::{
|
||||||
use ide_db::LineIndexDatabase;
|
use ide_db::LineIndexDatabase;
|
||||||
|
|
||||||
use ide_db::base_db::salsa::{self, ParallelDatabase};
|
use ide_db::base_db::salsa::{self, ParallelDatabase};
|
||||||
|
use ide_db::line_index::WideEncoding;
|
||||||
use lsp_types::{self, lsif};
|
use lsp_types::{self, lsif};
|
||||||
use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace};
|
use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace};
|
||||||
use vfs::{AbsPathBuf, Vfs};
|
use vfs::{AbsPathBuf, Vfs};
|
||||||
|
@ -127,7 +128,7 @@ impl LsifManager<'_> {
|
||||||
let line_index = self.db.line_index(file_id);
|
let line_index = self.db.line_index(file_id);
|
||||||
let line_index = LineIndex {
|
let line_index = LineIndex {
|
||||||
index: line_index,
|
index: line_index,
|
||||||
encoding: PositionEncoding::Utf16,
|
encoding: PositionEncoding::Wide(WideEncoding::Utf16),
|
||||||
endings: LineEndings::Unix,
|
endings: LineEndings::Unix,
|
||||||
};
|
};
|
||||||
let range_id = self.add_vertex(lsif::Vertex::Range {
|
let range_id = self.add_vertex(lsif::Vertex::Range {
|
||||||
|
@ -249,7 +250,7 @@ impl LsifManager<'_> {
|
||||||
let line_index = self.db.line_index(file_id);
|
let line_index = self.db.line_index(file_id);
|
||||||
let line_index = LineIndex {
|
let line_index = LineIndex {
|
||||||
index: line_index,
|
index: line_index,
|
||||||
encoding: PositionEncoding::Utf16,
|
encoding: PositionEncoding::Wide(WideEncoding::Utf16),
|
||||||
endings: LineEndings::Unix,
|
endings: LineEndings::Unix,
|
||||||
};
|
};
|
||||||
let result = folds
|
let result = folds
|
||||||
|
|
|
@ -33,7 +33,7 @@ use crate::{
|
||||||
caps::completion_item_edit_resolve,
|
caps::completion_item_edit_resolve,
|
||||||
diagnostics::DiagnosticsMapConfig,
|
diagnostics::DiagnosticsMapConfig,
|
||||||
line_index::PositionEncoding,
|
line_index::PositionEncoding,
|
||||||
lsp_ext::{self, supports_utf8, WorkspaceSymbolSearchKind, WorkspaceSymbolSearchScope},
|
lsp_ext::{self, negotiated_encoding, WorkspaceSymbolSearchKind, WorkspaceSymbolSearchScope},
|
||||||
};
|
};
|
||||||
|
|
||||||
mod patch_old_style;
|
mod patch_old_style;
|
||||||
|
@ -999,11 +999,7 @@ impl Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn position_encoding(&self) -> PositionEncoding {
|
pub fn position_encoding(&self) -> PositionEncoding {
|
||||||
if supports_utf8(&self.caps) {
|
negotiated_encoding(&self.caps)
|
||||||
PositionEncoding::Utf8
|
|
||||||
} else {
|
|
||||||
PositionEncoding::Utf16
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn experimental(&self, index: &'static str) -> bool {
|
fn experimental(&self, index: &'static str) -> bool {
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use flycheck::{Applicability, DiagnosticLevel, DiagnosticSpan};
|
use flycheck::{Applicability, DiagnosticLevel, DiagnosticSpan};
|
||||||
|
use ide_db::line_index::WideEncoding;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use stdx::format_to;
|
use stdx::format_to;
|
||||||
use vfs::{AbsPath, AbsPathBuf};
|
use vfs::{AbsPath, AbsPathBuf};
|
||||||
|
@ -95,7 +96,8 @@ fn position(
|
||||||
let mut char_offset = 0;
|
let mut char_offset = 0;
|
||||||
let len_func = match position_encoding {
|
let len_func = match position_encoding {
|
||||||
PositionEncoding::Utf8 => char::len_utf8,
|
PositionEncoding::Utf8 => char::len_utf8,
|
||||||
PositionEncoding::Utf16 => char::len_utf16,
|
PositionEncoding::Wide(WideEncoding::Utf16) => char::len_utf16,
|
||||||
|
PositionEncoding::Wide(WideEncoding::Utf32) => |_| 1,
|
||||||
};
|
};
|
||||||
for c in line.text.chars() {
|
for c in line.text.chars() {
|
||||||
char_offset += 1;
|
char_offset += 1;
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
//! Conversion lsp_types types to rust-analyzer specific ones.
|
//! Conversion lsp_types types to rust-analyzer specific ones.
|
||||||
use anyhow::format_err;
|
use anyhow::format_err;
|
||||||
use ide::{Annotation, AnnotationKind, AssistKind, LineCol, LineColUtf16};
|
use ide::{Annotation, AnnotationKind, AssistKind, LineCol};
|
||||||
use ide_db::base_db::{FileId, FilePosition, FileRange};
|
use ide_db::{
|
||||||
|
base_db::{FileId, FilePosition, FileRange},
|
||||||
|
line_index::WideLineCol,
|
||||||
|
};
|
||||||
use syntax::{TextRange, TextSize};
|
use syntax::{TextRange, TextSize};
|
||||||
use vfs::AbsPathBuf;
|
use vfs::AbsPathBuf;
|
||||||
|
|
||||||
|
@ -26,9 +29,9 @@ pub(crate) fn vfs_path(url: &lsp_types::Url) -> Result<vfs::VfsPath> {
|
||||||
pub(crate) fn offset(line_index: &LineIndex, position: lsp_types::Position) -> Result<TextSize> {
|
pub(crate) fn offset(line_index: &LineIndex, position: lsp_types::Position) -> Result<TextSize> {
|
||||||
let line_col = match line_index.encoding {
|
let line_col = match line_index.encoding {
|
||||||
PositionEncoding::Utf8 => LineCol { line: position.line, col: position.character },
|
PositionEncoding::Utf8 => LineCol { line: position.line, col: position.character },
|
||||||
PositionEncoding::Utf16 => {
|
PositionEncoding::Wide(enc) => {
|
||||||
let line_col = LineColUtf16 { line: position.line, col: position.character };
|
let line_col = WideLineCol { line: position.line, col: position.character };
|
||||||
line_index.index.to_utf8(line_col)
|
line_index.index.to_utf8(enc, line_col)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let text_size =
|
let text_size =
|
||||||
|
|
|
@ -7,9 +7,12 @@
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use ide_db::line_index::WideEncoding;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
pub enum PositionEncoding {
|
pub enum PositionEncoding {
|
||||||
Utf8,
|
Utf8,
|
||||||
Utf16,
|
Wide(WideEncoding),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct LineIndex {
|
pub(crate) struct LineIndex {
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
use std::{collections::HashMap, path::PathBuf};
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
|
|
||||||
|
use ide_db::line_index::WideEncoding;
|
||||||
use lsp_types::request::Request;
|
use lsp_types::request::Request;
|
||||||
use lsp_types::PositionEncodingKind;
|
use lsp_types::PositionEncodingKind;
|
||||||
use lsp_types::{
|
use lsp_types::{
|
||||||
|
@ -10,6 +11,8 @@ use lsp_types::{
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::line_index::PositionEncoding;
|
||||||
|
|
||||||
pub enum AnalyzerStatus {}
|
pub enum AnalyzerStatus {}
|
||||||
|
|
||||||
impl Request for AnalyzerStatus {
|
impl Request for AnalyzerStatus {
|
||||||
|
@ -481,16 +484,22 @@ pub(crate) enum CodeLensResolveData {
|
||||||
References(lsp_types::TextDocumentPositionParams),
|
References(lsp_types::TextDocumentPositionParams),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn supports_utf8(caps: &lsp_types::ClientCapabilities) -> bool {
|
pub fn negotiated_encoding(caps: &lsp_types::ClientCapabilities) -> PositionEncoding {
|
||||||
match &caps.general {
|
let client_encodings = match &caps.general {
|
||||||
Some(general) => general
|
Some(general) => general.position_encodings.as_deref().unwrap_or_default(),
|
||||||
.position_encodings
|
None => &[],
|
||||||
.as_deref()
|
};
|
||||||
.unwrap_or_default()
|
|
||||||
.iter()
|
for enc in client_encodings {
|
||||||
.any(|it| it == &PositionEncodingKind::UTF8),
|
if enc == &PositionEncodingKind::UTF8 {
|
||||||
_ => false,
|
return PositionEncoding::Utf8;
|
||||||
|
} else if enc == &PositionEncodingKind::UTF32 {
|
||||||
|
return PositionEncoding::Wide(WideEncoding::Utf32);
|
||||||
}
|
}
|
||||||
|
// NB: intentionally prefer just about anything else to utf-16.
|
||||||
|
}
|
||||||
|
|
||||||
|
PositionEncoding::Wide(WideEncoding::Utf16)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum MoveItem {}
|
pub enum MoveItem {}
|
||||||
|
|
|
@ -161,6 +161,7 @@ impl GlobalState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn apply_document_changes(
|
pub(crate) fn apply_document_changes(
|
||||||
|
encoding: PositionEncoding,
|
||||||
file_contents: impl FnOnce() -> String,
|
file_contents: impl FnOnce() -> String,
|
||||||
mut content_changes: Vec<lsp_types::TextDocumentContentChangeEvent>,
|
mut content_changes: Vec<lsp_types::TextDocumentContentChangeEvent>,
|
||||||
) -> String {
|
) -> String {
|
||||||
|
@ -192,9 +193,9 @@ pub(crate) fn apply_document_changes(
|
||||||
let mut line_index = LineIndex {
|
let mut line_index = LineIndex {
|
||||||
// the index will be overwritten in the bottom loop's first iteration
|
// the index will be overwritten in the bottom loop's first iteration
|
||||||
index: Arc::new(ide::LineIndex::new(&text)),
|
index: Arc::new(ide::LineIndex::new(&text)),
|
||||||
// We don't care about line endings or offset encoding here.
|
// We don't care about line endings here.
|
||||||
endings: LineEndings::Unix,
|
endings: LineEndings::Unix,
|
||||||
encoding: PositionEncoding::Utf16,
|
encoding,
|
||||||
};
|
};
|
||||||
|
|
||||||
// The changes we got must be applied sequentially, but can cross lines so we
|
// The changes we got must be applied sequentially, but can cross lines so we
|
||||||
|
@ -256,6 +257,7 @@ pub(crate) fn all_edits_are_disjoint(
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use ide_db::line_index::WideEncoding;
|
||||||
use lsp_types::{
|
use lsp_types::{
|
||||||
CompletionItem, CompletionTextEdit, InsertReplaceEdit, Position, Range,
|
CompletionItem, CompletionTextEdit, InsertReplaceEdit, Position, Range,
|
||||||
TextDocumentContentChangeEvent,
|
TextDocumentContentChangeEvent,
|
||||||
|
@ -278,9 +280,11 @@ mod tests {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let text = apply_document_changes(|| String::new(), vec![]);
|
let encoding = PositionEncoding::Wide(WideEncoding::Utf16);
|
||||||
|
let text = apply_document_changes(encoding, || String::new(), vec![]);
|
||||||
assert_eq!(text, "");
|
assert_eq!(text, "");
|
||||||
let text = apply_document_changes(
|
let text = apply_document_changes(
|
||||||
|
encoding,
|
||||||
|| text,
|
|| text,
|
||||||
vec![TextDocumentContentChangeEvent {
|
vec![TextDocumentContentChangeEvent {
|
||||||
range: None,
|
range: None,
|
||||||
|
@ -289,39 +293,49 @@ mod tests {
|
||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
assert_eq!(text, "the");
|
assert_eq!(text, "the");
|
||||||
let text = apply_document_changes(|| text, c![0, 3; 0, 3 => " quick"]);
|
let text = apply_document_changes(encoding, || text, c![0, 3; 0, 3 => " quick"]);
|
||||||
assert_eq!(text, "the quick");
|
assert_eq!(text, "the quick");
|
||||||
let text = apply_document_changes(|| text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]);
|
let text =
|
||||||
|
apply_document_changes(encoding, || text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]);
|
||||||
assert_eq!(text, "quick foxes");
|
assert_eq!(text, "quick foxes");
|
||||||
let text = apply_document_changes(|| text, c![0, 11; 0, 11 => "\ndream"]);
|
let text = apply_document_changes(encoding, || text, c![0, 11; 0, 11 => "\ndream"]);
|
||||||
assert_eq!(text, "quick foxes\ndream");
|
assert_eq!(text, "quick foxes\ndream");
|
||||||
let text = apply_document_changes(|| text, c![1, 0; 1, 0 => "have "]);
|
let text = apply_document_changes(encoding, || text, c![1, 0; 1, 0 => "have "]);
|
||||||
assert_eq!(text, "quick foxes\nhave dream");
|
assert_eq!(text, "quick foxes\nhave dream");
|
||||||
let text = apply_document_changes(
|
let text = apply_document_changes(
|
||||||
|
encoding,
|
||||||
|| text,
|
|| text,
|
||||||
c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"],
|
c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"],
|
||||||
);
|
);
|
||||||
assert_eq!(text, "the quick foxes\nhave quiet dreams\n");
|
assert_eq!(text, "the quick foxes\nhave quiet dreams\n");
|
||||||
let text = apply_document_changes(|| text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]);
|
let text = apply_document_changes(
|
||||||
|
encoding,
|
||||||
|
|| text,
|
||||||
|
c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"],
|
||||||
|
);
|
||||||
assert_eq!(text, "the quick foxes\n\nhave quiet dreams\n\n");
|
assert_eq!(text, "the quick foxes\n\nhave quiet dreams\n\n");
|
||||||
let text = apply_document_changes(
|
let text = apply_document_changes(
|
||||||
|
encoding,
|
||||||
|| text,
|
|| text,
|
||||||
c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"],
|
c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"],
|
||||||
);
|
);
|
||||||
assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n");
|
assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n");
|
||||||
let text = apply_document_changes(|| text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]);
|
let text =
|
||||||
|
apply_document_changes(encoding, || text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]);
|
||||||
assert_eq!(text, "the quick \nthey have quiet dreams\n");
|
assert_eq!(text, "the quick \nthey have quiet dreams\n");
|
||||||
|
|
||||||
let text = String::from("❤️");
|
let text = String::from("❤️");
|
||||||
let text = apply_document_changes(|| text, c![0, 0; 0, 0 => "a"]);
|
let text = apply_document_changes(encoding, || text, c![0, 0; 0, 0 => "a"]);
|
||||||
assert_eq!(text, "a❤️");
|
assert_eq!(text, "a❤️");
|
||||||
|
|
||||||
let text = String::from("a\nb");
|
let text = String::from("a\nb");
|
||||||
let text = apply_document_changes(|| text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]);
|
let text =
|
||||||
|
apply_document_changes(encoding, || text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]);
|
||||||
assert_eq!(text, "adcb");
|
assert_eq!(text, "adcb");
|
||||||
|
|
||||||
let text = String::from("a\nb");
|
let text = String::from("a\nb");
|
||||||
let text = apply_document_changes(|| text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]);
|
let text =
|
||||||
|
apply_document_changes(encoding, || text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]);
|
||||||
assert_eq!(text, "ațc\ncb");
|
assert_eq!(text, "ațc\ncb");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -831,6 +831,7 @@ impl GlobalState {
|
||||||
let vfs = &mut this.vfs.write().0;
|
let vfs = &mut this.vfs.write().0;
|
||||||
let file_id = vfs.file_id(&path).unwrap();
|
let file_id = vfs.file_id(&path).unwrap();
|
||||||
let text = apply_document_changes(
|
let text = apply_document_changes(
|
||||||
|
this.config.position_encoding(),
|
||||||
|| std::str::from_utf8(vfs.file_contents(file_id)).unwrap().into(),
|
|| std::str::from_utf8(vfs.file_contents(file_id)).unwrap().into(),
|
||||||
params.content_changes,
|
params.content_changes,
|
||||||
);
|
);
|
||||||
|
|
|
@ -268,7 +268,10 @@ impl GlobalState {
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.map(|glob_pattern| lsp_types::FileSystemWatcher { glob_pattern, kind: None })
|
.map(|glob_pattern| lsp_types::FileSystemWatcher {
|
||||||
|
glob_pattern: lsp_types::GlobPattern::String(glob_pattern),
|
||||||
|
kind: None,
|
||||||
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
};
|
};
|
||||||
let registration = lsp_types::Registration {
|
let registration = lsp_types::Registration {
|
||||||
|
|
|
@ -31,8 +31,8 @@ pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::P
|
||||||
let line_col = line_index.index.line_col(offset);
|
let line_col = line_index.index.line_col(offset);
|
||||||
match line_index.encoding {
|
match line_index.encoding {
|
||||||
PositionEncoding::Utf8 => lsp_types::Position::new(line_col.line, line_col.col),
|
PositionEncoding::Utf8 => lsp_types::Position::new(line_col.line, line_col.col),
|
||||||
PositionEncoding::Utf16 => {
|
PositionEncoding::Wide(enc) => {
|
||||||
let line_col = line_index.index.to_utf16(line_col);
|
let line_col = line_index.index.to_wide(enc, line_col);
|
||||||
lsp_types::Position::new(line_col.line, line_col.col)
|
lsp_types::Position::new(line_col.line, line_col.col)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -212,7 +212,7 @@ pub(crate) fn completion_items(
|
||||||
tdpp: lsp_types::TextDocumentPositionParams,
|
tdpp: lsp_types::TextDocumentPositionParams,
|
||||||
items: Vec<CompletionItem>,
|
items: Vec<CompletionItem>,
|
||||||
) -> Vec<lsp_types::CompletionItem> {
|
) -> Vec<lsp_types::CompletionItem> {
|
||||||
let max_relevance = items.iter().map(|it| it.relevance().score()).max().unwrap_or_default();
|
let max_relevance = items.iter().map(|it| it.relevance.score()).max().unwrap_or_default();
|
||||||
let mut res = Vec::with_capacity(items.len());
|
let mut res = Vec::with_capacity(items.len());
|
||||||
for item in items {
|
for item in items {
|
||||||
completion_item(&mut res, config, line_index, &tdpp, max_relevance, item);
|
completion_item(&mut res, config, line_index, &tdpp, max_relevance, item);
|
||||||
|
@ -235,22 +235,26 @@ fn completion_item(
|
||||||
item: CompletionItem,
|
item: CompletionItem,
|
||||||
) {
|
) {
|
||||||
let insert_replace_support = config.insert_replace_support().then_some(tdpp.position);
|
let insert_replace_support = config.insert_replace_support().then_some(tdpp.position);
|
||||||
|
let ref_match = item.ref_match();
|
||||||
|
let lookup = item.lookup().to_string();
|
||||||
|
|
||||||
let mut additional_text_edits = Vec::new();
|
let mut additional_text_edits = Vec::new();
|
||||||
|
|
||||||
// LSP does not allow arbitrary edits in completion, so we have to do a
|
// LSP does not allow arbitrary edits in completion, so we have to do a
|
||||||
// non-trivial mapping here.
|
// non-trivial mapping here.
|
||||||
let text_edit = {
|
let text_edit = {
|
||||||
let mut text_edit = None;
|
let mut text_edit = None;
|
||||||
let source_range = item.source_range();
|
let source_range = item.source_range;
|
||||||
for indel in item.text_edit().iter() {
|
for indel in item.text_edit {
|
||||||
if indel.delete.contains_range(source_range) {
|
if indel.delete.contains_range(source_range) {
|
||||||
|
// Extract this indel as the main edit
|
||||||
text_edit = Some(if indel.delete == source_range {
|
text_edit = Some(if indel.delete == source_range {
|
||||||
self::completion_text_edit(line_index, insert_replace_support, indel.clone())
|
self::completion_text_edit(line_index, insert_replace_support, indel.clone())
|
||||||
} else {
|
} else {
|
||||||
assert!(source_range.end() == indel.delete.end());
|
assert!(source_range.end() == indel.delete.end());
|
||||||
let range1 = TextRange::new(indel.delete.start(), source_range.start());
|
let range1 = TextRange::new(indel.delete.start(), source_range.start());
|
||||||
let range2 = source_range;
|
let range2 = source_range;
|
||||||
let indel1 = Indel::replace(range1, String::new());
|
let indel1 = Indel::delete(range1);
|
||||||
let indel2 = Indel::replace(range2, indel.insert.clone());
|
let indel2 = Indel::replace(range2, indel.insert.clone());
|
||||||
additional_text_edits.push(self::text_edit(line_index, indel1));
|
additional_text_edits.push(self::text_edit(line_index, indel1));
|
||||||
self::completion_text_edit(line_index, insert_replace_support, indel2)
|
self::completion_text_edit(line_index, insert_replace_support, indel2)
|
||||||
|
@ -264,23 +268,23 @@ fn completion_item(
|
||||||
text_edit.unwrap()
|
text_edit.unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
let insert_text_format = item.is_snippet().then_some(lsp_types::InsertTextFormat::SNIPPET);
|
let insert_text_format = item.is_snippet.then_some(lsp_types::InsertTextFormat::SNIPPET);
|
||||||
let tags = item.deprecated().then(|| vec![lsp_types::CompletionItemTag::DEPRECATED]);
|
let tags = item.deprecated.then(|| vec![lsp_types::CompletionItemTag::DEPRECATED]);
|
||||||
let command = if item.trigger_call_info() && config.client_commands().trigger_parameter_hints {
|
let command = if item.trigger_call_info && config.client_commands().trigger_parameter_hints {
|
||||||
Some(command::trigger_parameter_hints())
|
Some(command::trigger_parameter_hints())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut lsp_item = lsp_types::CompletionItem {
|
let mut lsp_item = lsp_types::CompletionItem {
|
||||||
label: item.label().to_string(),
|
label: item.label.to_string(),
|
||||||
detail: item.detail().map(|it| it.to_string()),
|
detail: item.detail.map(|it| it.to_string()),
|
||||||
filter_text: Some(item.lookup().to_string()),
|
filter_text: Some(lookup),
|
||||||
kind: Some(completion_item_kind(item.kind())),
|
kind: Some(completion_item_kind(item.kind)),
|
||||||
text_edit: Some(text_edit),
|
text_edit: Some(text_edit),
|
||||||
additional_text_edits: Some(additional_text_edits),
|
additional_text_edits: Some(additional_text_edits),
|
||||||
documentation: item.documentation().map(documentation),
|
documentation: item.documentation.map(documentation),
|
||||||
deprecated: Some(item.deprecated()),
|
deprecated: Some(item.deprecated),
|
||||||
tags,
|
tags,
|
||||||
command,
|
command,
|
||||||
insert_text_format,
|
insert_text_format,
|
||||||
|
@ -294,12 +298,13 @@ fn completion_item(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
set_score(&mut lsp_item, max_relevance, item.relevance());
|
set_score(&mut lsp_item, max_relevance, item.relevance);
|
||||||
|
|
||||||
if config.completion().enable_imports_on_the_fly {
|
if config.completion().enable_imports_on_the_fly {
|
||||||
if let imports @ [_, ..] = item.imports_to_add() {
|
if !item.import_to_add.is_empty() {
|
||||||
let imports: Vec<_> = imports
|
let imports: Vec<_> = item
|
||||||
.iter()
|
.import_to_add
|
||||||
|
.into_iter()
|
||||||
.filter_map(|import_edit| {
|
.filter_map(|import_edit| {
|
||||||
let import_path = &import_edit.import_path;
|
let import_path = &import_edit.import_path;
|
||||||
let import_name = import_path.segments().last()?;
|
let import_name = import_path.segments().last()?;
|
||||||
|
@ -316,18 +321,13 @@ fn completion_item(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((mutability, offset, relevance)) = item.ref_match() {
|
if let Some((label, indel, relevance)) = ref_match {
|
||||||
let mut lsp_item_with_ref = lsp_item.clone();
|
let mut lsp_item_with_ref = lsp_types::CompletionItem { label, ..lsp_item.clone() };
|
||||||
|
lsp_item_with_ref
|
||||||
|
.additional_text_edits
|
||||||
|
.get_or_insert_with(Default::default)
|
||||||
|
.push(self::text_edit(line_index, indel));
|
||||||
set_score(&mut lsp_item_with_ref, max_relevance, relevance);
|
set_score(&mut lsp_item_with_ref, max_relevance, relevance);
|
||||||
lsp_item_with_ref.label =
|
|
||||||
format!("&{}{}", mutability.as_keyword_for_ref(), lsp_item_with_ref.label);
|
|
||||||
lsp_item_with_ref.additional_text_edits.get_or_insert_with(Default::default).push(
|
|
||||||
self::text_edit(
|
|
||||||
line_index,
|
|
||||||
Indel::insert(offset, format!("&{}", mutability.as_keyword_for_ref())),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
acc.push(lsp_item_with_ref);
|
acc.push(lsp_item_with_ref);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -766,6 +766,7 @@ pub(crate) fn folding_range(
|
||||||
end_line,
|
end_line,
|
||||||
end_character: None,
|
end_character: None,
|
||||||
kind,
|
kind,
|
||||||
|
collapsed_text: None,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
lsp_types::FoldingRange {
|
lsp_types::FoldingRange {
|
||||||
|
@ -774,6 +775,7 @@ pub(crate) fn folding_range(
|
||||||
end_line: range.end.line,
|
end_line: range.end.line,
|
||||||
end_character: Some(range.end.character),
|
end_character: Some(range.end.character),
|
||||||
kind,
|
kind,
|
||||||
|
collapsed_text: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1360,7 +1362,7 @@ pub(crate) mod command {
|
||||||
pub(crate) fn trigger_parameter_hints() -> lsp_types::Command {
|
pub(crate) fn trigger_parameter_hints() -> lsp_types::Command {
|
||||||
lsp_types::Command {
|
lsp_types::Command {
|
||||||
title: "triggerParameterHints".into(),
|
title: "triggerParameterHints".into(),
|
||||||
command: "editor.action.triggerParameterHints".into(),
|
command: "rust-analyzer.triggerParameterHints".into(),
|
||||||
arguments: None,
|
arguments: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1429,7 +1431,7 @@ fn main() {
|
||||||
let line_index = LineIndex {
|
let line_index = LineIndex {
|
||||||
index: Arc::new(ide::LineIndex::new(text)),
|
index: Arc::new(ide::LineIndex::new(text)),
|
||||||
endings: LineEndings::Unix,
|
endings: LineEndings::Unix,
|
||||||
encoding: PositionEncoding::Utf16,
|
encoding: PositionEncoding::Utf8,
|
||||||
};
|
};
|
||||||
let converted: Vec<lsp_types::FoldingRange> =
|
let converted: Vec<lsp_types::FoldingRange> =
|
||||||
folds.into_iter().map(|it| folding_range(text, &line_index, true, it)).collect();
|
folds.into_iter().map(|it| folding_range(text, &line_index, true, it)).collect();
|
||||||
|
|
|
@ -22,7 +22,7 @@ use lsp_types::{
|
||||||
notification::DidOpenTextDocument,
|
notification::DidOpenTextDocument,
|
||||||
request::{
|
request::{
|
||||||
CodeActionRequest, Completion, Formatting, GotoTypeDefinition, HoverRequest,
|
CodeActionRequest, Completion, Formatting, GotoTypeDefinition, HoverRequest,
|
||||||
WillRenameFiles, WorkspaceSymbol,
|
WillRenameFiles, WorkspaceSymbolRequest,
|
||||||
},
|
},
|
||||||
CodeActionContext, CodeActionParams, CompletionParams, DidOpenTextDocumentParams,
|
CodeActionContext, CodeActionParams, CompletionParams, DidOpenTextDocumentParams,
|
||||||
DocumentFormattingParams, FileRename, FormattingOptions, GotoDefinitionParams, HoverParams,
|
DocumentFormattingParams, FileRename, FormattingOptions, GotoDefinitionParams, HoverParams,
|
||||||
|
@ -1095,5 +1095,5 @@ pub fn bar() {}
|
||||||
.server()
|
.server()
|
||||||
.wait_until_workspace_is_loaded();
|
.wait_until_workspace_is_loaded();
|
||||||
|
|
||||||
server.request::<WorkspaceSymbol>(Default::default(), json!([]));
|
server.request::<WorkspaceSymbolRequest>(Default::default(), json!([]));
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,6 +107,7 @@ impl<'a> Project<'a> {
|
||||||
did_change_watched_files: Some(
|
did_change_watched_files: Some(
|
||||||
lsp_types::DidChangeWatchedFilesClientCapabilities {
|
lsp_types::DidChangeWatchedFilesClientCapabilities {
|
||||||
dynamic_registration: Some(true),
|
dynamic_registration: Some(true),
|
||||||
|
relative_pattern_support: None,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
|
@ -11,6 +11,7 @@ pub mod hash;
|
||||||
pub mod process;
|
pub mod process;
|
||||||
pub mod panic_context;
|
pub mod panic_context;
|
||||||
pub mod non_empty_vec;
|
pub mod non_empty_vec;
|
||||||
|
pub mod rand;
|
||||||
|
|
||||||
pub use always_assert::{always, never};
|
pub use always_assert::{always, never};
|
||||||
|
|
||||||
|
|
21
crates/stdx/src/rand.rs
Normal file
21
crates/stdx/src/rand.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
//! We don't use `rand`, as that's too many things for us.
|
||||||
|
//!
|
||||||
|
//! We currently use oorandom instead, but it's missing these two utilities.
|
||||||
|
//! Perhaps we should switch to `fastrand`, or our own small PRNG, it's not like
|
||||||
|
//! we need anything more complicated than xor-shift.
|
||||||
|
|
||||||
|
pub fn shuffle<T>(slice: &mut [T], mut rand_index: impl FnMut(usize) -> usize) {
|
||||||
|
let mut remaining = slice.len() - 1;
|
||||||
|
while remaining > 0 {
|
||||||
|
let index = rand_index(remaining);
|
||||||
|
slice.swap(remaining, index);
|
||||||
|
remaining -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn seed() -> u64 {
|
||||||
|
use std::collections::hash_map::RandomState;
|
||||||
|
use std::hash::{BuildHasher, Hasher};
|
||||||
|
|
||||||
|
RandomState::new().build_hasher().finish()
|
||||||
|
}
|
|
@ -186,7 +186,7 @@ impl SourceFile {
|
||||||
/// ```
|
/// ```
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! match_ast {
|
macro_rules! match_ast {
|
||||||
(match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
|
(match $node:ident { $($tt:tt)* }) => { $crate::match_ast!(match ($node) { $($tt)* }) };
|
||||||
|
|
||||||
(match ($node:expr) {
|
(match ($node:expr) {
|
||||||
$( $( $path:ident )::+ ($it:pat) => $res:expr, )*
|
$( $( $path:ident )::+ ($it:pat) => $res:expr, )*
|
||||||
|
|
|
@ -119,7 +119,7 @@ See [#93](https://github.com/rust-lang/rust-analyzer/pull/93) for an example PR
|
||||||
**Architecture Invariant:** `syntax` crate is completely independent from the rest of rust-analyzer. It knows nothing about salsa or LSP.
|
**Architecture Invariant:** `syntax` crate is completely independent from the rest of rust-analyzer. It knows nothing about salsa or LSP.
|
||||||
This is important because it is possible to make useful tooling using only the syntax tree.
|
This is important because it is possible to make useful tooling using only the syntax tree.
|
||||||
Without semantic information, you don't need to be able to _build_ code, which makes the tooling more robust.
|
Without semantic information, you don't need to be able to _build_ code, which makes the tooling more robust.
|
||||||
See also https://web.stanford.edu/~mlfbrown/paper.pdf.
|
See also https://mlfbrown.com/paper.pdf.
|
||||||
You can view the `syntax` crate as an entry point to rust-analyzer.
|
You can view the `syntax` crate as an entry point to rust-analyzer.
|
||||||
`syntax` crate is an **API Boundary**.
|
`syntax` crate is an **API Boundary**.
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<!---
|
<!---
|
||||||
lsp_ext.rs hash: ec29403e67dfd15b
|
lsp_ext.rs hash: d87477896dfe41d4
|
||||||
|
|
||||||
If you need to change the above hash to make the test pass, please check if you
|
If you need to change the above hash to make the test pass, please check if you
|
||||||
need to adjust this doc as well and ping this issue:
|
need to adjust this doc as well and ping this issue:
|
||||||
|
|
|
@ -87,6 +87,12 @@ export function shuffleCrateGraph(ctx: CtxInit): Cmd {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function triggerParameterHints(_: CtxInit): Cmd {
|
||||||
|
return async () => {
|
||||||
|
await vscode.commands.executeCommand("editor.action.triggerParameterHints");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function matchingBrace(ctx: CtxInit): Cmd {
|
export function matchingBrace(ctx: CtxInit): Cmd {
|
||||||
return async () => {
|
return async () => {
|
||||||
const editor = ctx.activeRustEditor;
|
const editor = ctx.activeRustEditor;
|
||||||
|
|
|
@ -186,5 +186,6 @@ function createCommands(): Record<string, CommandFactory> {
|
||||||
resolveCodeAction: { enabled: commands.resolveCodeAction },
|
resolveCodeAction: { enabled: commands.resolveCodeAction },
|
||||||
runSingle: { enabled: commands.runSingle },
|
runSingle: { enabled: commands.runSingle },
|
||||||
showReferences: { enabled: commands.showReferences },
|
showReferences: { enabled: commands.showReferences },
|
||||||
|
triggerParameterHints: { enabled: commands.triggerParameterHints },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,4 +13,4 @@ serde = { version = "1.0.144", features = ["derive"] }
|
||||||
crossbeam-channel = "0.5.6"
|
crossbeam-channel = "0.5.6"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
lsp-types = "=0.93.2"
|
lsp-types = "=0.94"
|
||||||
|
|
Loading…
Reference in a new issue