⬆️ rust-analyzer

This commit is contained in:
Laurențiu Nicola 2023-02-20 10:14:12 +02:00
parent bc45c7659a
commit 7e711da2f0
98 changed files with 1801 additions and 943 deletions

6
Cargo.lock generated
View file

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

View file

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

View file

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

View file

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

View file

@ -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, &macro_call); let result = self.within_limit(db, |this| {
let macro_call = InFile::new(this.current_file_id, &macro_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)]

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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;
} }
} }
} }
} }
}
_ => {} _ => {}
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,5 @@
fn main() {
foo(bar::);
foo(bar:);
foo(bar+);
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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