diff options
29 files changed, 756 insertions, 77 deletions
diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index 6df73c2fd1b..5df48778e3d 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -1047,6 +1047,7 @@ dependencies = [ "expect-test", "intern", "parser", + "ra-ap-rustc_lexer", "rustc-hash", "smallvec", "span", diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/metavar_expr.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/metavar_expr.rs index bf701198387..1430e2a9a99 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/metavar_expr.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/metavar_expr.rs @@ -311,3 +311,150 @@ fn test() { "#]], ); } + +#[test] +fn concat() { + // FIXME: Should this error? rustc currently accepts it. + check( + r#" +macro_rules! m { + ( $a:ident, $b:literal ) => { + let ${concat($a, _, "123", _foo, $b, _, 123)}; + }; +} + +fn test() { + m!( abc, 456 ); + m!( def, "hello" ); +} +"#, + expect![[r#" +macro_rules! m { + ( $a:ident, $b:literal ) => { + let ${concat($a, _, "123", _foo, $b, _, 123)}; + }; +} + +fn test() { + let abc_123_foo456_123;; + let def_123_foohello_123;; +} +"#]], + ); +} + +#[test] +fn concat_less_than_two_elements() { + // FIXME: Should this error? rustc currently accepts it. + check( + r#" +macro_rules! m { + () => { + let ${concat(abc)}; + }; +} + +fn test() { + m!() +} +"#, + expect![[r#" +macro_rules! m { + () => { + let ${concat(abc)}; + }; +} + +fn test() { + /* error: macro definition has parse errors */ +} +"#]], + ); +} + +#[test] +fn concat_invalid_ident() { + // FIXME: Should this error? rustc currently accepts it. + check( + r#" +macro_rules! m { + () => { + let ${concat(abc, '"')}; + }; +} + +fn test() { + m!() +} +"#, + expect![[r#" +macro_rules! m { + () => { + let ${concat(abc, '"')}; + }; +} + +fn test() { + /* error: `${concat(..)}` is not generating a valid identifier */let __ra_concat_dummy; +} +"#]], + ); +} + +#[test] +fn concat_invalid_fragment() { + // FIXME: Should this error? rustc currently accepts it. + check( + r#" +macro_rules! m { + ( $e:expr ) => { + let ${concat(abc, $e)}; + }; +} + +fn test() { + m!(()) +} +"#, + expect![[r#" +macro_rules! m { + ( $e:expr ) => { + let ${concat(abc, $e)}; + }; +} + +fn test() { + /* error: metavariables of `${concat(..)}` must be of type `ident`, `literal` or `tt` */let abc; +} +"#]], + ); +} + +#[test] +fn concat_repetition() { + // FIXME: Should this error? rustc currently accepts it. + check( + r#" +macro_rules! m { + ( $($i:ident)* ) => { + let ${concat(abc, $i)}; + }; +} + +fn test() { + m!(a b c) +} +"#, + expect![[r#" +macro_rules! m { + ( $($i:ident)* ) => { + let ${concat(abc, $i)}; + }; +} + +fn test() { + /* error: expected simple binding, found nested binding `i` */let abc; +} +"#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs index 894ef1d73e2..1bbed01443d 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs @@ -1144,3 +1144,27 @@ mod any { "#]], ); } + +#[test] +fn regression_18148() { + check( + r#" +macro_rules! m { + ( $e:expr ) => {}; +} + +fn foo() { + m!(r#const); +} +"#, + expect![[r#" +macro_rules! m { + ( $e:expr ) => {}; +} + +fn foo() { + ; +} +"#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs index 96db3db8f0d..e09ef4f205d 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs @@ -221,7 +221,7 @@ struct DefCollector<'a> { deps: FxHashMap<Name, Dependency>, glob_imports: FxHashMap<LocalModuleId, Vec<(LocalModuleId, Visibility, UseId)>>, unresolved_imports: Vec<ImportDirective>, - indeterminate_imports: Vec<ImportDirective>, + indeterminate_imports: Vec<(ImportDirective, PerNs)>, unresolved_macros: Vec<MacroDirective>, mod_dirs: FxHashMap<LocalModuleId, ModDir>, cfg_options: &'a CfgOptions, @@ -415,16 +415,6 @@ impl DefCollector<'_> { self.resolution_loop(); - // Resolve all indeterminate resolved imports again - // As some of the macros will expand newly import shadowing partial resolved imports - // FIXME: We maybe could skip this, if we handle the indeterminate imports in `resolve_imports` - // correctly - let partial_resolved = self.indeterminate_imports.drain(..).map(|directive| { - ImportDirective { status: PartialResolvedImport::Unresolved, ..directive } - }); - self.unresolved_imports.extend(partial_resolved); - self.resolve_imports(); - let unresolved_imports = mem::take(&mut self.unresolved_imports); // show unresolved imports in completion, etc for directive in &unresolved_imports { @@ -749,9 +739,9 @@ impl DefCollector<'_> { .filter_map(|mut directive| { directive.status = self.resolve_import(directive.module_id, &directive.import); match directive.status { - PartialResolvedImport::Indeterminate(_) => { + PartialResolvedImport::Indeterminate(resolved) => { self.record_resolved_import(&directive); - self.indeterminate_imports.push(directive); + self.indeterminate_imports.push((directive, resolved)); res = ReachedFixedPoint::No; None } @@ -764,6 +754,33 @@ impl DefCollector<'_> { } }) .collect(); + + // Resolve all indeterminate resolved imports again + // As some of the macros will expand newly import shadowing partial resolved imports + // FIXME: We maybe could skip this, if we handle the indeterminate imports in `resolve_imports` + // correctly + let mut indeterminate_imports = std::mem::take(&mut self.indeterminate_imports); + indeterminate_imports.retain_mut(|(directive, partially_resolved)| { + let partially_resolved = partially_resolved.availability(); + directive.status = self.resolve_import(directive.module_id, &directive.import); + match directive.status { + PartialResolvedImport::Indeterminate(import) + if partially_resolved != import.availability() => + { + self.record_resolved_import(directive); + res = ReachedFixedPoint::No; + false + } + PartialResolvedImport::Resolved(_) => { + self.record_resolved_import(directive); + res = ReachedFixedPoint::No; + false + } + _ => true, + } + }); + self.indeterminate_imports = indeterminate_imports; + res } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/per_ns.rs b/src/tools/rust-analyzer/crates/hir-def/src/per_ns.rs index 19485c476f7..3f3b98c6b5b 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/per_ns.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/per_ns.rs @@ -3,6 +3,8 @@ //! //! `PerNs` (per namespace) captures this. +use bitflags::bitflags; + use crate::{ item_scope::{ImportId, ImportOrExternCrate, ItemInNs}, visibility::Visibility, @@ -16,6 +18,16 @@ pub enum Namespace { Macros, } +bitflags! { + /// Describes only the presence/absence of each namespace, without its value. + #[derive(Debug, PartialEq, Eq)] + pub(crate) struct NsAvailability : u32 { + const TYPES = 1 << 0; + const VALUES = 1 << 1; + const MACROS = 1 << 2; + } +} + #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] pub struct PerNs { pub types: Option<(ModuleDefId, Visibility, Option<ImportOrExternCrate>)>, @@ -24,6 +36,14 @@ pub struct PerNs { } impl PerNs { + pub(crate) fn availability(&self) -> NsAvailability { + let mut result = NsAvailability::empty(); + result.set(NsAvailability::TYPES, self.types.is_some()); + result.set(NsAvailability::VALUES, self.values.is_some()); + result.set(NsAvailability::MACROS, self.macros.is_some()); + result + } + pub fn none() -> PerNs { PerNs { types: None, values: None, macros: None } } diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/cfg_process.rs b/src/tools/rust-analyzer/crates/hir-expand/src/cfg_process.rs index 147cf912da1..01a3103af82 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/cfg_process.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/cfg_process.rs @@ -6,7 +6,7 @@ use cfg::{CfgAtom, CfgExpr}; use intern::{sym, Symbol}; use rustc_hash::FxHashSet; use syntax::{ - ast::{self, Attr, HasAttrs, Meta, VariantList}, + ast::{self, Attr, HasAttrs, Meta, TokenTree, VariantList}, AstNode, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, T, }; use tracing::{debug, warn}; @@ -17,7 +17,7 @@ fn check_cfg(db: &dyn ExpandDatabase, attr: &Attr, krate: CrateId) -> Option<boo if !attr.simple_name().as_deref().map(|v| v == "cfg")? { return None; } - let cfg = parse_from_attr_meta(attr.meta()?)?; + let cfg = parse_from_attr_token_tree(&attr.meta()?.token_tree()?)?; let enabled = db.crate_graph()[krate].cfg_options.check(&cfg) != Some(false); Some(enabled) } @@ -26,7 +26,15 @@ fn check_cfg_attr(db: &dyn ExpandDatabase, attr: &Attr, krate: CrateId) -> Optio if !attr.simple_name().as_deref().map(|v| v == "cfg_attr")? { return None; } - let cfg_expr = parse_from_attr_meta(attr.meta()?)?; + check_cfg_attr_value(db, &attr.token_tree()?, krate) +} + +pub fn check_cfg_attr_value( + db: &dyn ExpandDatabase, + attr: &TokenTree, + krate: CrateId, +) -> Option<bool> { + let cfg_expr = parse_from_attr_token_tree(attr)?; let enabled = db.crate_graph()[krate].cfg_options.check(&cfg_expr) != Some(false); Some(enabled) } @@ -238,8 +246,7 @@ pub(crate) fn process_cfg_attrs( Some(remove) } /// Parses a `cfg` attribute from the meta -fn parse_from_attr_meta(meta: Meta) -> Option<CfgExpr> { - let tt = meta.token_tree()?; +fn parse_from_attr_token_tree(tt: &TokenTree) -> Option<CfgExpr> { let mut iter = tt .token_trees_and_tokens() .filter(is_not_whitespace) @@ -328,7 +335,7 @@ mod tests { use expect_test::{expect, Expect}; use syntax::{ast::Attr, AstNode, SourceFile}; - use crate::cfg_process::parse_from_attr_meta; + use crate::cfg_process::parse_from_attr_token_tree; fn check_dnf_from_syntax(input: &str, expect: Expect) { let parse = SourceFile::parse(input, span::Edition::CURRENT); @@ -342,7 +349,7 @@ mod tests { let node = node.clone_subtree(); assert_eq!(node.syntax().text_range().start(), 0.into()); - let cfg = parse_from_attr_meta(node.meta().unwrap()).unwrap(); + let cfg = parse_from_attr_token_tree(&node.meta().unwrap().token_tree().unwrap()).unwrap(); let actual = format!("#![cfg({})]", DnfExpr::new(&cfg)); expect.assert_eq(&actual); } diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs index 7cf4fb5cef2..95380979492 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs @@ -53,6 +53,7 @@ use crate::{ }; pub use crate::{ + cfg_process::check_cfg_attr_value, files::{AstId, ErasedAstId, FileRange, InFile, InMacroFile, InRealFile}, prettify_macro_expansion_::prettify_macro_expansion, }; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/object_safety.rs b/src/tools/rust-analyzer/crates/hir-ty/src/object_safety.rs index 89bf3619a0c..a4c66268555 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/object_safety.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/object_safety.rs @@ -349,7 +349,7 @@ where ControlFlow::Continue(()) } else { let generic_params = db.generic_params(item.into()); - if generic_params.len_type_or_consts() > 0 { + if !generic_params.is_empty() { cb(ObjectSafetyViolation::GAT(it)) } else { ControlFlow::Continue(()) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/object_safety/tests.rs b/src/tools/rust-analyzer/crates/hir-ty/src/object_safety/tests.rs index 3dc08c4619e..c2a9117c5be 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/object_safety/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/object_safety/tests.rs @@ -378,3 +378,16 @@ pub trait Error: core::fmt::Debug + core::fmt::Display { [("Error", vec![])], ); } + +#[test] +fn lifetime_gat_is_object_unsafe() { + check_object_safety( + r#" +//- minicore: dispatch_from_dyn +trait Foo { + type Bar<'a>; +} +"#, + [("Foo", vec![ObjectSafetyViolationKind::GAT])], + ); +} diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs index dcaff044420..fa14b53dbc3 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs @@ -386,6 +386,19 @@ impl<'db> SemanticsImpl<'db> { Some(node) } + pub fn check_cfg_attr(&self, attr: &ast::TokenTree) -> Option<bool> { + let file_id = self.find_file(attr.syntax()).file_id; + let krate = match file_id.repr() { + HirFileIdRepr::FileId(file_id) => { + self.file_to_module_defs(file_id.file_id()).next()?.krate().id + } + HirFileIdRepr::MacroFile(macro_file) => { + self.db.lookup_intern_macro_call(macro_file.macro_call_id).krate + } + }; + hir_expand::check_cfg_attr_value(self.db.upcast(), attr, krate) + } + /// Expands the macro if it isn't one of the built-in ones that expand to custom syntax or dummy /// expansions. pub fn expand_allowed_builtins(&self, macro_call: &ast::MacroCall) -> Option<SyntaxNode> { diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs index 9821fb4a2fa..d0b489c4e83 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs @@ -56,7 +56,7 @@ pub(crate) fn complete_known_attribute_input( &parse_tt_as_comma_sep_paths(tt, ctx.edition)?, FEATURES, ), - "allow" | "warn" | "deny" | "forbid" => { + "allow" | "expect" | "deny" | "forbid" | "warn" => { let existing_lints = parse_tt_as_comma_sep_paths(tt, ctx.edition)?; let lints: Vec<Lint> = CLIPPY_LINT_GROUPS @@ -222,7 +222,7 @@ macro_rules! attrs { [@ {} {$($tt:tt)*}] => { &[$($tt)*] as _ }; // starting matcher [$($tt:tt),*] => { - attrs!(@ { $($tt)* } { "allow", "cfg", "cfg_attr", "deny", "forbid", "warn" }) + attrs!(@ { $($tt)* } { "allow", "cfg", "cfg_attr", "deny", "expect", "forbid", "warn" }) }; } @@ -303,6 +303,7 @@ const ATTRIBUTES: &[AttrCompletion] = &[ attr(r#"doc = "…""#, Some("doc"), Some(r#"doc = "${0:docs}""#)), attr(r#"doc(alias = "…")"#, Some("docalias"), Some(r#"doc(alias = "${0:docs}")"#)), attr(r#"doc(hidden)"#, Some("dochidden"), Some(r#"doc(hidden)"#)), + attr("expect(…)", Some("expect"), Some("expect(${0:lint})")), attr( r#"export_name = "…""#, Some("export_name"), diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs index a632f148936..d3579fd8cc6 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs @@ -294,6 +294,18 @@ fn include_references(initial_element: &ast::Expr) -> (ast::Expr, ast::Expr) { let mut new_element_opt = initial_element.clone(); + while let Some(parent_deref_element) = + resulting_element.syntax().parent().and_then(ast::PrefixExpr::cast) + { + if parent_deref_element.op_kind() != Some(ast::UnaryOp::Deref) { + break; + } + + resulting_element = ast::Expr::from(parent_deref_element); + + new_element_opt = make::expr_prefix(syntax::T![*], new_element_opt); + } + if let Some(first_ref_expr) = resulting_element.syntax().parent().and_then(ast::RefExpr::cast) { if let Some(expr) = first_ref_expr.expr() { resulting_element = expr; @@ -302,9 +314,10 @@ fn include_references(initial_element: &ast::Expr) -> (ast::Expr, ast::Expr) { while let Some(parent_ref_element) = resulting_element.syntax().parent().and_then(ast::RefExpr::cast) { + let exclusive = parent_ref_element.mut_token().is_some(); resulting_element = ast::Expr::from(parent_ref_element); - new_element_opt = make::expr_ref(new_element_opt, false); + new_element_opt = make::expr_ref(new_element_opt, exclusive); } } else { // If we do not find any ref expressions, restore @@ -855,4 +868,42 @@ fn test() { expect![[r#""#]], ); } + + #[test] + fn mut_ref_consuming() { + check_edit( + "call", + r#" +fn main() { + let mut x = &mut 2; + &mut x.$0; +} +"#, + r#" +fn main() { + let mut x = &mut 2; + ${1}(&mut x); +} +"#, + ); + } + + #[test] + fn deref_consuming() { + check_edit( + "call", + r#" +fn main() { + let mut x = &mut 2; + &mut *x.$0; +} +"#, + r#" +fn main() { + let mut x = &mut 2; + ${1}(&mut *x); +} +"#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs index d457ba32bf0..192f1b43fac 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs @@ -4,7 +4,7 @@ mod analysis; #[cfg(test)] mod tests; -use std::iter; +use std::{iter, ops::ControlFlow}; use hir::{ HasAttrs, Local, Name, PathResolution, ScopeDef, Semantics, SemanticsScope, Type, TypeInfo, @@ -15,7 +15,7 @@ use ide_db::{ }; use syntax::{ ast::{self, AttrKind, NameOrNameRef}, - AstNode, Edition, SmolStr, + match_ast, AstNode, Edition, SmolStr, SyntaxKind::{self, *}, SyntaxToken, TextRange, TextSize, T, }; @@ -26,7 +26,7 @@ use crate::{ CompletionConfig, }; -const COMPLETION_MARKER: &str = "intellijRulezz"; +const COMPLETION_MARKER: &str = "raCompletionMarker"; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub(crate) enum PatternRefutability { @@ -457,6 +457,16 @@ pub(crate) struct CompletionContext<'a> { /// /// Here depth will be 2 pub(crate) depth_from_crate_root: usize, + + /// Whether and how to complete semicolon for unit-returning functions. + pub(crate) complete_semicolon: CompleteSemicolon, +} + +#[derive(Debug)] +pub(crate) enum CompleteSemicolon { + DoNotComplete, + CompleteSemi, + CompleteComma, } impl CompletionContext<'_> { @@ -735,6 +745,53 @@ impl<'a> CompletionContext<'a> { let depth_from_crate_root = iter::successors(module.parent(db), |m| m.parent(db)).count(); + let complete_semicolon = if config.add_semicolon_to_unit { + let inside_closure_ret = token.parent_ancestors().try_for_each(|ancestor| { + match_ast! { + match ancestor { + ast::BlockExpr(_) => ControlFlow::Break(false), + ast::ClosureExpr(_) => ControlFlow::Break(true), + _ => ControlFlow::Continue(()) + } + } + }); + + if inside_closure_ret == ControlFlow::Break(true) { + CompleteSemicolon::DoNotComplete + } else { + let next_non_trivia_token = + std::iter::successors(token.next_token(), |it| it.next_token()) + .find(|it| !it.kind().is_trivia()); + let in_match_arm = token.parent_ancestors().try_for_each(|ancestor| { + if ast::MatchArm::can_cast(ancestor.kind()) { + ControlFlow::Break(true) + } else if matches!( + ancestor.kind(), + SyntaxKind::EXPR_STMT | SyntaxKind::BLOCK_EXPR + ) { + ControlFlow::Break(false) + } else { + ControlFlow::Continue(()) + } + }); + // FIXME: This will assume expr macros are not inside match, we need to somehow go to the "parent" of the root node. + let in_match_arm = match in_match_arm { + ControlFlow::Continue(()) => false, + ControlFlow::Break(it) => it, + }; + let complete_token = if in_match_arm { T![,] } else { T![;] }; + if next_non_trivia_token.map(|it| it.kind()) == Some(complete_token) { + CompleteSemicolon::DoNotComplete + } else if in_match_arm { + CompleteSemicolon::CompleteComma + } else { + CompleteSemicolon::CompleteSemi + } + } + } else { + CompleteSemicolon::DoNotComplete + }; + let ctx = CompletionContext { sema, scope, @@ -752,6 +809,7 @@ impl<'a> CompletionContext<'a> { qualifier_ctx, locals, depth_from_crate_root, + complete_semicolon, }; Some((ctx, analysis)) } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs index 7e5e69665f1..a859d79e243 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs @@ -1,15 +1,15 @@ //! Renderer for function calls. -use std::ops::ControlFlow; - use hir::{db::HirDatabase, AsAssocItem, HirDisplay}; use ide_db::{SnippetCap, SymbolKind}; use itertools::Itertools; use stdx::{format_to, to_lower_snake_case}; -use syntax::{ast, format_smolstr, AstNode, Edition, SmolStr, SyntaxKind, ToSmolStr, T}; +use syntax::{format_smolstr, AstNode, Edition, SmolStr, ToSmolStr}; use crate::{ - context::{CompletionContext, DotAccess, DotAccessKind, PathCompletionCtx, PathKind}, + context::{ + CompleteSemicolon, CompletionContext, DotAccess, DotAccessKind, PathCompletionCtx, PathKind, + }, item::{ Builder, CompletionItem, CompletionItemKind, CompletionRelevance, CompletionRelevanceFn, CompletionRelevanceReturnType, CompletionRelevanceTraitInfo, @@ -277,32 +277,21 @@ pub(super) fn add_call_parens<'b>( (snippet, "(…)") }; - if ret_type.is_unit() && ctx.config.add_semicolon_to_unit { - let next_non_trivia_token = - std::iter::successors(ctx.token.next_token(), |it| it.next_token()) - .find(|it| !it.kind().is_trivia()); - let in_match_arm = ctx.token.parent_ancestors().try_for_each(|ancestor| { - if ast::MatchArm::can_cast(ancestor.kind()) { - ControlFlow::Break(true) - } else if matches!(ancestor.kind(), SyntaxKind::EXPR_STMT | SyntaxKind::BLOCK_EXPR) { - ControlFlow::Break(false) - } else { - ControlFlow::Continue(()) - } - }); - // FIXME: This will assume expr macros are not inside match, we need to somehow go to the "parent" of the root node. - let in_match_arm = match in_match_arm { - ControlFlow::Continue(()) => false, - ControlFlow::Break(it) => it, - }; - let complete_token = if in_match_arm { T![,] } else { T![;] }; - if next_non_trivia_token.map(|it| it.kind()) != Some(complete_token) { - cov_mark::hit!(complete_semicolon); - let ch = if in_match_arm { ',' } else { ';' }; - if snippet.ends_with("$0") { - snippet.insert(snippet.len() - "$0".len(), ch); - } else { - snippet.push(ch); + if ret_type.is_unit() { + match ctx.complete_semicolon { + CompleteSemicolon::DoNotComplete => {} + CompleteSemicolon::CompleteSemi | CompleteSemicolon::CompleteComma => { + cov_mark::hit!(complete_semicolon); + let ch = if matches!(ctx.complete_semicolon, CompleteSemicolon::CompleteComma) { + ',' + } else { + ';' + }; + if snippet.ends_with("$0") { + snippet.insert(snippet.len() - "$0".len(), ch); + } else { + snippet.push(ch); + } } } } @@ -889,4 +878,25 @@ fn bar() { "#, ); } + + #[test] + fn no_semicolon_in_closure_ret() { + check_edit( + r#"foo"#, + r#" +fn foo() {} +fn baz(_: impl FnOnce()) {} +fn bar() { + baz(|| fo$0); +} +"#, + r#" +fn foo() {} +fn baz(_: impl FnOnce()) {} +fn bar() { + baz(|| foo()$0); +} +"#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs index 351abe9850b..1bbe097cc6c 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs @@ -26,6 +26,7 @@ struct Foo; at doc = "…" at doc(alias = "…") at doc(hidden) + at expect(…) at forbid(…) at must_use at no_mangle @@ -75,6 +76,7 @@ fn with_existing_attr() { at cfg(…) at cfg_attr(…) at deny(…) + at expect(…) at forbid(…) at warn(…) kw crate:: @@ -97,6 +99,7 @@ fn attr_on_source_file() { at doc = "…" at doc(alias = "…") at doc(hidden) + at expect(…) at feature(…) at forbid(…) at must_use @@ -127,6 +130,7 @@ fn attr_on_module() { at doc = "…" at doc(alias = "…") at doc(hidden) + at expect(…) at forbid(…) at macro_use at must_use @@ -149,6 +153,7 @@ fn attr_on_module() { at doc = "…" at doc(alias = "…") at doc(hidden) + at expect(…) at forbid(…) at must_use at no_implicit_prelude @@ -174,6 +179,7 @@ fn attr_on_macro_rules() { at doc = "…" at doc(alias = "…") at doc(hidden) + at expect(…) at forbid(…) at macro_export at macro_use @@ -199,6 +205,7 @@ fn attr_on_macro_def() { at doc = "…" at doc(alias = "…") at doc(hidden) + at expect(…) at forbid(…) at must_use at no_mangle @@ -222,6 +229,7 @@ fn attr_on_extern_crate() { at doc = "…" at doc(alias = "…") at doc(hidden) + at expect(…) at forbid(…) at macro_use at must_use @@ -246,6 +254,7 @@ fn attr_on_use() { at doc = "…" at doc(alias = "…") at doc(hidden) + at expect(…) at forbid(…) at must_use at no_mangle @@ -269,6 +278,7 @@ fn attr_on_type_alias() { at doc = "…" at doc(alias = "…") at doc(hidden) + at expect(…) at forbid(…) at must_use at no_mangle @@ -299,6 +309,7 @@ struct Foo; at doc = "…" at doc(alias = "…") at doc(hidden) + at expect(…) at forbid(…) at must_use at no_mangle @@ -326,6 +337,7 @@ fn attr_on_enum() { at doc = "…" at doc(alias = "…") at doc(hidden) + at expect(…) at forbid(…) at must_use at no_mangle @@ -351,6 +363,7 @@ fn attr_on_const() { at doc = "…" at doc(alias = "…") at doc(hidden) + at expect(…) at forbid(…) at must_use at no_mangle @@ -374,6 +387,7 @@ fn attr_on_static() { at doc = "…" at doc(alias = "…") at doc(hidden) + at expect(…) at export_name = "…" at forbid(…) at global_allocator @@ -402,6 +416,7 @@ fn attr_on_trait() { at doc = "…" at doc(alias = "…") at doc(hidden) + at expect(…) at forbid(…) at must_use at must_use @@ -427,6 +442,7 @@ fn attr_on_impl() { at doc = "…" at doc(alias = "…") at doc(hidden) + at expect(…) at forbid(…) at must_use at no_mangle @@ -446,6 +462,7 @@ fn attr_on_impl() { at doc = "…" at doc(alias = "…") at doc(hidden) + at expect(…) at forbid(…) at must_use at no_mangle @@ -469,6 +486,7 @@ fn attr_on_extern_block() { at doc = "…" at doc(alias = "…") at doc(hidden) + at expect(…) at forbid(…) at link at must_use @@ -489,6 +507,7 @@ fn attr_on_extern_block() { at doc = "…" at doc(alias = "…") at doc(hidden) + at expect(…) at forbid(…) at link at must_use @@ -509,6 +528,7 @@ fn attr_on_variant() { at cfg(…) at cfg_attr(…) at deny(…) + at expect(…) at forbid(…) at non_exhaustive at warn(…) @@ -532,6 +552,7 @@ fn attr_on_fn() { at doc = "…" at doc(alias = "…") at doc(hidden) + at expect(…) at export_name = "…" at forbid(…) at ignore = "…" @@ -572,6 +593,7 @@ fn attr_in_source_file_end() { at doc = "…" at doc(alias = "…") at doc(hidden) + at expect(…) at export_name = "…" at forbid(…) at global_allocator diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs index c4c28953175..83a1eb44a61 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs @@ -998,6 +998,28 @@ fn BAR() { } #[test] + fn cfged_lint_attrs() { + check_diagnostics( + r#" +//- /lib.rs cfg:feature=cool_feature +#[cfg_attr(any(), allow(non_snake_case))] +fn FOO() {} +// ^^^ 💡 warn: Function `FOO` should have snake_case name, e.g. `foo` + +#[cfg_attr(non_existent, allow(non_snake_case))] +fn BAR() {} +// ^^^ 💡 warn: Function `BAR` should have snake_case name, e.g. `bar` + +#[cfg_attr(feature = "cool_feature", allow(non_snake_case))] +fn BAZ() {} + +#[cfg_attr(feature = "cool_feature", cfg_attr ( all ( ) , allow ( non_snake_case ) ) ) ] +fn QUX() {} + "#, + ); + } + + #[test] fn allow_with_comment() { check_diagnostics( r#" diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs index d46d099f6fe..45c723d09d4 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs @@ -76,8 +76,9 @@ mod handlers { #[cfg(test)] mod tests; -use std::{collections::hash_map, sync::LazyLock}; +use std::{collections::hash_map, iter, sync::LazyLock}; +use either::Either; use hir::{db::ExpandDatabase, diagnostics::AnyDiagnostic, Crate, HirFileId, InFile, Semantics}; use ide_db::{ assists::{Assist, AssistId, AssistKind, AssistResolveStrategy}, @@ -92,7 +93,7 @@ use ide_db::{ use itertools::Itertools; use syntax::{ ast::{self, AstNode, HasAttrs}, - AstPtr, Edition, SmolStr, SyntaxNode, SyntaxNodePtr, TextRange, + AstPtr, Edition, NodeOrToken, SmolStr, SyntaxKind, SyntaxNode, SyntaxNodePtr, TextRange, T, }; // FIXME: Make this an enum @@ -647,6 +648,7 @@ fn find_outline_mod_lint_severity( let mut result = None; let lint_groups = lint_groups(&diag.code); lint_attrs( + sema, ast::AnyHasAttrs::cast(module_source_file.value).expect("SourceFile always has attrs"), edition, ) @@ -763,7 +765,7 @@ fn fill_lint_attrs( if let Some(ancestor) = ast::AnyHasAttrs::cast(ancestor) { // Insert this node's attributes into any outline descendant, including this node. - lint_attrs(ancestor, edition).for_each(|(lint, severity)| { + lint_attrs(sema, ancestor, edition).for_each(|(lint, severity)| { if diag_severity.is_none() && lint_groups(&diag.code).contains(&&*lint) { diag_severity = Some(severity); } @@ -793,7 +795,7 @@ fn fill_lint_attrs( cache_stack.pop(); return diag_severity; } else if let Some(ancestor) = ast::AnyHasAttrs::cast(ancestor) { - lint_attrs(ancestor, edition).for_each(|(lint, severity)| { + lint_attrs(sema, ancestor, edition).for_each(|(lint, severity)| { if diag_severity.is_none() && lint_groups(&diag.code).contains(&&*lint) { diag_severity = Some(severity); } @@ -809,20 +811,27 @@ fn fill_lint_attrs( } } -fn lint_attrs( +fn lint_attrs<'a>( + sema: &'a Semantics<'a, RootDatabase>, ancestor: ast::AnyHasAttrs, edition: Edition, -) -> impl Iterator<Item = (SmolStr, Severity)> { +) -> impl Iterator<Item = (SmolStr, Severity)> + 'a { ancestor .attrs_including_inner() .filter_map(|attr| { attr.as_simple_call().and_then(|(name, value)| match &*name { - "allow" | "expect" => Some((Severity::Allow, value)), - "warn" => Some((Severity::Warning, value)), - "forbid" | "deny" => Some((Severity::Error, value)), + "allow" | "expect" => Some(Either::Left(iter::once((Severity::Allow, value)))), + "warn" => Some(Either::Left(iter::once((Severity::Warning, value)))), + "forbid" | "deny" => Some(Either::Left(iter::once((Severity::Error, value)))), + "cfg_attr" => { + let mut lint_attrs = Vec::new(); + cfg_attr_lint_attrs(sema, &value, &mut lint_attrs); + Some(Either::Right(lint_attrs.into_iter())) + } _ => None, }) }) + .flatten() .flat_map(move |(severity, lints)| { parse_tt_as_comma_sep_paths(lints, edition).into_iter().flat_map(move |lints| { // Rejoin the idents with `::`, so we have no spaces in between. @@ -836,6 +845,58 @@ fn lint_attrs( }) } +fn cfg_attr_lint_attrs( + sema: &Semantics<'_, RootDatabase>, + value: &ast::TokenTree, + lint_attrs: &mut Vec<(Severity, ast::TokenTree)>, +) { + let prev_len = lint_attrs.len(); + + let mut iter = value.token_trees_and_tokens().filter(|it| match it { + NodeOrToken::Node(_) => true, + NodeOrToken::Token(it) => !it.kind().is_trivia(), + }); + + // Skip the condition. + for value in &mut iter { + if value.as_token().is_some_and(|it| it.kind() == T![,]) { + break; + } + } + + while let Some(value) = iter.next() { + if let Some(token) = value.as_token() { + if token.kind() == SyntaxKind::IDENT { + let severity = match token.text() { + "allow" | "expect" => Some(Severity::Allow), + "warn" => Some(Severity::Warning), + "forbid" | "deny" => Some(Severity::Error), + "cfg_attr" => { + if let Some(NodeOrToken::Node(value)) = iter.next() { + cfg_attr_lint_attrs(sema, &value, lint_attrs); + } + None + } + _ => None, + }; + if let Some(severity) = severity { + let lints = iter.next(); + if let Some(NodeOrToken::Node(lints)) = lints { + lint_attrs.push((severity, lints)); + } + } + } + } + } + + if prev_len != lint_attrs.len() { + if let Some(false) | None = sema.check_cfg_attr(value) { + // Discard the attributes when the condition is false. + lint_attrs.truncate(prev_len); + } + } +} + fn lint_groups(lint: &DiagnosticCode) -> &'static [&'static str] { match lint { DiagnosticCode::RustcLint(name) => { diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs index 971cd3ef585..8836166d969 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs @@ -2750,4 +2750,36 @@ fn foo() { "#, ); } + + #[test] + fn issue_18138() { + check( + r#" +mod foo { + macro_rules! x { + () => { + pub struct Foo; + // ^^^ + }; + } + pub(crate) use x as m; +} + +mod bar { + use crate::m; + + m!(); + // ^^^^^ + + fn qux() { + Foo$0; + } +} + +mod m {} + +use foo::m; +"#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs index b02e9b0f073..83adf6548a8 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs @@ -329,7 +329,7 @@ pub(super) fn try_for_lint(attr: &ast::Attr, token: &SyntaxToken) -> Option<Hove } let (is_clippy, lints) = match &*path { "feature" => (false, FEATURES), - "allow" | "deny" | "forbid" | "warn" => { + "allow" | "deny" | "expect" | "forbid" | "warn" => { let is_clippy = algo::non_trivia_sibling(token.clone().into(), Direction::Prev) .filter(|t| t.kind() == T![:]) .and_then(|t| algo::non_trivia_sibling(t, Direction::Prev)) diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs index 8805ead818a..cca62d2181f 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -6336,7 +6336,19 @@ fn hover_lint() { arithmetic operation overflows "#]], - ) + ); + check( + r#"#![expect(arithmetic_overflow$0)]"#, + expect![[r#" + *arithmetic_overflow* + ``` + arithmetic_overflow + ``` + ___ + + arithmetic operation overflows + "#]], + ); } #[test] @@ -6352,7 +6364,19 @@ fn hover_clippy_lint() { Checks for `foo = bar; bar = foo` sequences. "#]], - ) + ); + check( + r#"#![expect(clippy::almost_swapped$0)]"#, + expect![[r#" + *almost_swapped* + ``` + clippy::almost_swapped + ``` + ___ + + Checks for `foo = bar; bar = foo` sequences. + "#]], + ); } #[test] diff --git a/src/tools/rust-analyzer/crates/mbe/Cargo.toml b/src/tools/rust-analyzer/crates/mbe/Cargo.toml index 413a0254a61..b37d57f2801 100644 --- a/src/tools/rust-analyzer/crates/mbe/Cargo.toml +++ b/src/tools/rust-analyzer/crates/mbe/Cargo.toml @@ -18,6 +18,7 @@ rustc-hash.workspace = true smallvec.workspace = true tracing.workspace = true arrayvec.workspace = true +ra-ap-rustc_lexer.workspace = true # local deps syntax.workspace = true diff --git a/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs b/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs index f8c1d027c60..9e4ef140740 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs @@ -216,7 +216,11 @@ fn invocation_fixtures( token_trees.push(subtree.into()); } - Op::Ignore { .. } | Op::Index { .. } | Op::Count { .. } | Op::Len { .. } => {} + Op::Ignore { .. } + | Op::Index { .. } + | Op::Count { .. } + | Op::Len { .. } + | Op::Concat { .. } => {} }; // Simple linear congruential generator for deterministic result diff --git a/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs b/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs index 90f56cc31da..95641abc0f3 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs @@ -584,7 +584,11 @@ fn match_loop_inner<'t>( error_items.push(item); } OpDelimited::Op( - Op::Ignore { .. } | Op::Index { .. } | Op::Count { .. } | Op::Len { .. }, + Op::Ignore { .. } + | Op::Index { .. } + | Op::Count { .. } + | Op::Len { .. } + | Op::Concat { .. }, ) => { stdx::never!("metavariable expression in lhs found"); } @@ -780,7 +784,7 @@ fn match_meta_var( // [1]: https://github.com/rust-lang/rust/blob/f0c4da499/compiler/rustc_expand/src/mbe/macro_parser.rs#L576 match input.peek_n(0) { Some(tt::TokenTree::Leaf(tt::Leaf::Ident(it))) => { - let is_err = if matches!(expr, ExprKind::Expr2021) { + let is_err = if it.is_raw.no() && matches!(expr, ExprKind::Expr2021) { it.sym == sym::underscore || it.sym == sym::let_ || it.sym == sym::const_ } else { it.sym == sym::let_ @@ -879,7 +883,11 @@ fn collect_vars(collector_fun: &mut impl FnMut(Symbol), pattern: &MetaTemplate) Op::Subtree { tokens, .. } => collect_vars(collector_fun, tokens), Op::Repeat { tokens, .. } => collect_vars(collector_fun, tokens), Op::Literal(_) | Op::Ident(_) | Op::Punct(_) => {} - Op::Ignore { .. } | Op::Index { .. } | Op::Count { .. } | Op::Len { .. } => { + Op::Ignore { .. } + | Op::Index { .. } + | Op::Count { .. } + | Op::Len { .. } + | Op::Concat { .. } => { stdx::never!("metavariable expression in lhs found"); } } diff --git a/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs b/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs index 00844115355..1db2f35d262 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs @@ -2,12 +2,12 @@ //! `$ident => foo`, interpolates variables in the template, to get `fn foo() {}` use intern::{sym, Symbol}; -use span::Span; +use span::{Edition, Span}; use tt::Delimiter; use crate::{ expander::{Binding, Bindings, Fragment}, - parser::{MetaVarKind, Op, RepeatKind, Separator}, + parser::{ConcatMetaVarExprElem, MetaVarKind, Op, RepeatKind, Separator}, ExpandError, ExpandErrorKind, ExpandResult, MetaTemplate, }; @@ -312,6 +312,82 @@ fn expand_subtree( .into(), ); } + Op::Concat { elements, span: concat_span } => { + let mut concatenated = String::new(); + for element in elements { + match element { + ConcatMetaVarExprElem::Ident(ident) => { + concatenated.push_str(ident.sym.as_str()) + } + ConcatMetaVarExprElem::Literal(lit) => { + // FIXME: This isn't really correct wrt. escaping, but that's what rustc does and anyway + // escaping is used most of the times for characters that are invalid in identifiers. + concatenated.push_str(lit.symbol.as_str()) + } + ConcatMetaVarExprElem::Var(var) => { + // Handling of repetitions in `${concat}` isn't fleshed out in rustc, so we currently + // err at it. + // FIXME: Do what rustc does for repetitions. + let var_value = match ctx.bindings.get_fragment( + &var.sym, + var.span, + &mut ctx.nesting, + marker, + ) { + Ok(var) => var, + Err(e) => { + if err.is_none() { + err = Some(e); + }; + continue; + } + }; + let value = match &var_value { + Fragment::Tokens(tt::TokenTree::Leaf(tt::Leaf::Ident(ident))) => { + ident.sym.as_str() + } + Fragment::Tokens(tt::TokenTree::Leaf(tt::Leaf::Literal(lit))) => { + lit.symbol.as_str() + } + _ => { + if err.is_none() { + err = Some(ExpandError::binding_error(var.span, "metavariables of `${concat(..)}` must be of type `ident`, `literal` or `tt`")) + } + continue; + } + }; + concatenated.push_str(value); + } + } + } + + // `${concat}` span comes from the macro (at least for now). + // See https://github.com/rust-lang/rust/blob/b0af276da341/compiler/rustc_expand/src/mbe/transcribe.rs#L724-L726. + let mut result_span = *concat_span; + marker(&mut result_span); + + // FIXME: NFC normalize the result. + if !rustc_lexer::is_ident(&concatenated) { + if err.is_none() { + err = Some(ExpandError::binding_error( + *concat_span, + "`${concat(..)}` is not generating a valid identifier", + )); + } + // Insert a dummy identifier for better parsing. + concatenated.clear(); + concatenated.push_str("__ra_concat_dummy"); + } + + let needs_raw = + parser::SyntaxKind::from_keyword(&concatenated, Edition::LATEST).is_some(); + let is_raw = if needs_raw { tt::IdentIsRaw::Yes } else { tt::IdentIsRaw::No }; + arena.push(tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { + is_raw, + span: result_span, + sym: Symbol::intern(&concatenated), + }))); + } } } // drain the elements added in this instance of expand_subtree diff --git a/src/tools/rust-analyzer/crates/mbe/src/lib.rs b/src/tools/rust-analyzer/crates/mbe/src/lib.rs index f7b6e8f54b5..dd71e46db36 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/lib.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/lib.rs @@ -6,6 +6,11 @@ //! The tests for this functionality live in another crate: //! `hir_def::macro_expansion_tests::mbe`. +#[cfg(not(feature = "in-rust-tree"))] +extern crate ra_ap_rustc_lexer as rustc_lexer; +#[cfg(feature = "in-rust-tree")] +extern crate rustc_lexer; + mod expander; mod parser; diff --git a/src/tools/rust-analyzer/crates/mbe/src/parser.rs b/src/tools/rust-analyzer/crates/mbe/src/parser.rs index 2efe318f614..b55edf4a5e0 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/parser.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/parser.rs @@ -84,6 +84,10 @@ pub(crate) enum Op { // FIXME: `usize`` once we drop support for 1.76 depth: Option<usize>, }, + Concat { + elements: Box<[ConcatMetaVarExprElem]>, + span: Span, + }, Repeat { tokens: MetaTemplate, kind: RepeatKind, @@ -98,6 +102,18 @@ pub(crate) enum Op { Ident(tt::Ident<Span>), } +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) enum ConcatMetaVarExprElem { + /// There is NO preceding dollar sign, which means that this identifier should be interpreted + /// as a literal. + Ident(tt::Ident<Span>), + /// There is a preceding dollar sign, which means that this identifier should be expanded + /// and interpreted as a variable. + Var(tt::Ident<Span>), + /// For example, a number or a string. + Literal(tt::Literal<Span>), +} + #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub(crate) enum RepeatKind { ZeroOrMore, @@ -384,6 +400,32 @@ fn parse_metavar_expr(src: &mut TtIter<'_, Span>) -> Result<Op, ()> { let depth = if try_eat_comma(&mut args) { Some(parse_depth(&mut args)?) } else { None }; Op::Count { name: ident.sym.clone(), depth } } + s if sym::concat == *s => { + let mut elements = Vec::new(); + while let Some(next) = args.peek_n(0) { + let element = if let tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) = next { + args.next().expect("already peeked"); + ConcatMetaVarExprElem::Literal(lit.clone()) + } else { + let is_var = try_eat_dollar(&mut args); + let ident = args.expect_ident_or_underscore()?.clone(); + + if is_var { + ConcatMetaVarExprElem::Var(ident) + } else { + ConcatMetaVarExprElem::Ident(ident) + } + }; + elements.push(element); + if args.peek_n(0).is_some() { + args.expect_comma()?; + } + } + if elements.len() < 2 { + return Err(()); + } + Op::Concat { elements: elements.into_boxed_slice(), span: func.span } + } _ => return Err(()), }; @@ -414,3 +456,11 @@ fn try_eat_comma(src: &mut TtIter<'_, Span>) -> bool { } false } + +fn try_eat_dollar(src: &mut TtIter<'_, Span>) -> bool { + if let Some(tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char: '$', .. }))) = src.peek_n(0) { + let _ = src.next(); + return true; + } + false +} diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs index 44e56645ba3..2134b033f3f 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -77,7 +77,11 @@ impl flags::AnalysisStats { let metadata_time = db_load_sw.elapsed(); let load_cargo_config = LoadCargoConfig { load_out_dirs_from_check: !self.disable_build_scripts, - with_proc_macro_server: ProcMacroServerChoice::Sysroot, + with_proc_macro_server: if self.disable_proc_macros { + ProcMacroServerChoice::None + } else { + ProcMacroServerChoice::Sysroot + }, prefill_caches: false, }; diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs index e8d1a7e4df6..49b1ba32a79 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs @@ -357,7 +357,7 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool { .targets .iter() .any(|&it| crate_root_paths.contains(&cargo[it].root.as_path())); - has_target_with_root.then(|| cargo[pkg].name.clone()) + has_target_with_root.then(|| cargo.package_flag(&cargo[pkg])) }), project_model::ProjectWorkspaceKind::Json(project) => { if !project.crates().any(|(_, krate)| { diff --git a/src/tools/rust-analyzer/crates/tt/src/iter.rs b/src/tools/rust-analyzer/crates/tt/src/iter.rs index e96bed0319e..587b903aa97 100644 --- a/src/tools/rust-analyzer/crates/tt/src/iter.rs +++ b/src/tools/rust-analyzer/crates/tt/src/iter.rs @@ -57,6 +57,13 @@ impl<'a, S: Copy> TtIter<'a, S> { } } + pub fn expect_comma(&mut self) -> Result<(), ()> { + match self.expect_leaf()? { + Leaf::Punct(Punct { char: ',', .. }) => Ok(()), + _ => Err(()), + } + } + pub fn expect_ident(&mut self) -> Result<&'a Ident<S>, ()> { match self.expect_leaf()? { Leaf::Ident(it) if it.sym != sym::underscore => Ok(it), |
