diff options
Diffstat (limited to 'src/tools/rust-analyzer/crates')
262 files changed, 9281 insertions, 3899 deletions
diff --git a/src/tools/rust-analyzer/crates/base-db/src/fixture.rs b/src/tools/rust-analyzer/crates/base-db/src/fixture.rs index 323ee4260..3f5ccb621 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/fixture.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/fixture.rs @@ -130,6 +130,7 @@ impl ChangeFixture { let mut default_crate_root: Option<FileId> = None; let mut default_target_data_layout: Option<String> = None; let mut default_cfg = CfgOptions::default(); + let mut default_env = Env::new_for_test_fixture(); let mut file_set = FileSet::default(); let mut current_source_root_kind = SourceRootKind::Local; @@ -178,8 +179,8 @@ impl ChangeFixture { meta.edition, Some(crate_name.clone().into()), version, - meta.cfg, - Default::default(), + meta.cfg.clone(), + Some(meta.cfg), meta.env, false, origin, @@ -199,7 +200,8 @@ impl ChangeFixture { } else if meta.path == "/main.rs" || meta.path == "/lib.rs" { assert!(default_crate_root.is_none()); default_crate_root = Some(file_id); - default_cfg = meta.cfg; + default_cfg.extend(meta.cfg.into_iter()); + default_env.extend(meta.env.iter().map(|(x, y)| (x.to_owned(), y.to_owned()))); default_target_data_layout = meta.target_data_layout; } @@ -218,9 +220,9 @@ impl ChangeFixture { Edition::CURRENT, Some(CrateName::new("test").unwrap().into()), None, - default_cfg, - Default::default(), - Env::new_for_test_fixture(), + default_cfg.clone(), + Some(default_cfg), + default_env, false, CrateOrigin::Local { repo: None, name: None }, default_target_data_layout diff --git a/src/tools/rust-analyzer/crates/base-db/src/input.rs b/src/tools/rust-analyzer/crates/base-db/src/input.rs index c47799f13..b75c7079b 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/input.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/input.rs @@ -686,6 +686,12 @@ impl fmt::Display for Edition { } } +impl Extend<(String, String)> for Env { + fn extend<T: IntoIterator<Item = (String, String)>>(&mut self, iter: T) { + self.entries.extend(iter); + } +} + impl FromIterator<(String, String)> for Env { fn from_iter<T: IntoIterator<Item = (String, String)>>(iter: T) -> Self { Env { entries: FromIterator::from_iter(iter) } diff --git a/src/tools/rust-analyzer/crates/cfg/src/lib.rs b/src/tools/rust-analyzer/crates/cfg/src/lib.rs index 183b9b7d2..0aeb0b050 100644 --- a/src/tools/rust-analyzer/crates/cfg/src/lib.rs +++ b/src/tools/rust-analyzer/crates/cfg/src/lib.rs @@ -86,6 +86,32 @@ impl CfgOptions { } } +impl Extend<CfgAtom> for CfgOptions { + fn extend<T: IntoIterator<Item = CfgAtom>>(&mut self, iter: T) { + iter.into_iter().for_each(|cfg_flag| _ = self.enabled.insert(cfg_flag)); + } +} + +impl IntoIterator for CfgOptions { + type Item = <FxHashSet<CfgAtom> as IntoIterator>::Item; + + type IntoIter = <FxHashSet<CfgAtom> as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + <FxHashSet<CfgAtom> as IntoIterator>::into_iter(self.enabled) + } +} + +impl<'a> IntoIterator for &'a CfgOptions { + type Item = <&'a FxHashSet<CfgAtom> as IntoIterator>::Item; + + type IntoIter = <&'a FxHashSet<CfgAtom> as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + <&FxHashSet<CfgAtom> as IntoIterator>::into_iter(&self.enabled) + } +} + #[derive(Default, Clone, Debug, PartialEq, Eq)] pub struct CfgDiff { // Invariants: No duplicates, no atom that's both in `enable` and `disable`. diff --git a/src/tools/rust-analyzer/crates/flycheck/src/lib.rs b/src/tools/rust-analyzer/crates/flycheck/src/lib.rs index fbb943ccb..2de719af9 100644 --- a/src/tools/rust-analyzer/crates/flycheck/src/lib.rs +++ b/src/tools/rust-analyzer/crates/flycheck/src/lib.rs @@ -5,7 +5,9 @@ #![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)] use std::{ + ffi::OsString, fmt, io, + path::PathBuf, process::{ChildStderr, ChildStdout, Command, Stdio}, time::Duration, }; @@ -168,7 +170,7 @@ struct FlycheckActor { /// doesn't provide a way to read sub-process output without blocking, so we /// have to wrap sub-processes output handling in a thread and pass messages /// back over a channel. - cargo_handle: Option<CargoHandle>, + command_handle: Option<CommandHandle>, } enum Event { @@ -184,7 +186,7 @@ impl FlycheckActor { workspace_root: AbsPathBuf, ) -> FlycheckActor { tracing::info!(%id, ?workspace_root, "Spawning flycheck"); - FlycheckActor { id, sender, config, root: workspace_root, cargo_handle: None } + FlycheckActor { id, sender, config, root: workspace_root, command_handle: None } } fn report_progress(&self, progress: Progress) { @@ -192,7 +194,7 @@ impl FlycheckActor { } fn next_event(&self, inbox: &Receiver<StateChange>) -> Option<Event> { - let check_chan = self.cargo_handle.as_ref().map(|cargo| &cargo.receiver); + let check_chan = self.command_handle.as_ref().map(|cargo| &cargo.receiver); if let Ok(msg) = inbox.try_recv() { // give restarts a preference so check outputs don't block a restart or stop return Some(Event::RequestStateChange(msg)); @@ -221,21 +223,19 @@ impl FlycheckActor { } let command = self.check_command(); + let formatted_command = format!("{:?}", command); + tracing::debug!(?command, "will restart flycheck"); - match CargoHandle::spawn(command) { - Ok(cargo_handle) => { - tracing::debug!( - command = ?self.check_command(), - "did restart flycheck" - ); - self.cargo_handle = Some(cargo_handle); + match CommandHandle::spawn(command) { + Ok(command_handle) => { + tracing::debug!(command = formatted_command, "did restart flycheck"); + self.command_handle = Some(command_handle); self.report_progress(Progress::DidStart); } Err(error) => { self.report_progress(Progress::DidFailToRestart(format!( - "Failed to run the following command: {:?} error={}", - self.check_command(), - error + "Failed to run the following command: {} error={}", + formatted_command, error ))); } } @@ -244,12 +244,14 @@ impl FlycheckActor { tracing::debug!(flycheck_id = self.id, "flycheck finished"); // Watcher finished - let cargo_handle = self.cargo_handle.take().unwrap(); - let res = cargo_handle.join(); + let command_handle = self.command_handle.take().unwrap(); + let formatted_handle = format!("{:?}", command_handle); + + let res = command_handle.join(); if res.is_err() { tracing::error!( - "Flycheck failed to run the following command: {:?}", - self.check_command() + "Flycheck failed to run the following command: {}", + formatted_handle ); } self.report_progress(Progress::DidFinish(res)); @@ -284,12 +286,12 @@ impl FlycheckActor { } fn cancel_check_process(&mut self) { - if let Some(cargo_handle) = self.cargo_handle.take() { + if let Some(command_handle) = self.command_handle.take() { tracing::debug!( - command = ?self.check_command(), + command = ?command_handle, "did cancel flycheck" ); - cargo_handle.cancel(); + command_handle.cancel(); self.report_progress(Progress::DidCancel); } } @@ -391,19 +393,36 @@ impl Drop for JodGroupChild { } /// A handle to a cargo process used for fly-checking. -struct CargoHandle { +struct CommandHandle { /// The handle to the actual cargo process. As we cannot cancel directly from with /// a read syscall dropping and therefore terminating the process is our best option. child: JodGroupChild, thread: stdx::thread::JoinHandle<io::Result<(bool, String)>>, receiver: Receiver<CargoMessage>, + program: OsString, + arguments: Vec<OsString>, + current_dir: Option<PathBuf>, } -impl CargoHandle { - fn spawn(mut command: Command) -> std::io::Result<CargoHandle> { +impl fmt::Debug for CommandHandle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("CommandHandle") + .field("program", &self.program) + .field("arguments", &self.arguments) + .field("current_dir", &self.current_dir) + .finish() + } +} + +impl CommandHandle { + fn spawn(mut command: Command) -> std::io::Result<CommandHandle> { command.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null()); let mut child = command.group_spawn().map(JodGroupChild)?; + let program = command.get_program().into(); + let arguments = command.get_args().map(|arg| arg.into()).collect::<Vec<OsString>>(); + let current_dir = command.get_current_dir().map(|arg| arg.to_path_buf()); + let stdout = child.0.inner().stdout.take().unwrap(); let stderr = child.0.inner().stderr.take().unwrap(); @@ -413,7 +432,7 @@ impl CargoHandle { .name("CargoHandle".to_owned()) .spawn(move || actor.run()) .expect("failed to spawn thread"); - Ok(CargoHandle { child, thread, receiver }) + Ok(CommandHandle { program, arguments, current_dir, child, thread, receiver }) } fn cancel(mut self) { diff --git a/src/tools/rust-analyzer/crates/hir-def/Cargo.toml b/src/tools/rust-analyzer/crates/hir-def/Cargo.toml index 30307deb7..8cf61ee04 100644 --- a/src/tools/rust-analyzer/crates/hir-def/Cargo.toml +++ b/src/tools/rust-analyzer/crates/hir-def/Cargo.toml @@ -31,8 +31,10 @@ smallvec.workspace = true hashbrown.workspace = true triomphe.workspace = true -rustc_abi = { version = "0.0.20221221", package = "hkalbasi-rustc-ap-rustc_abi", default-features = false } -rustc_index = { version = "0.0.20221221", package = "hkalbasi-rustc-ap-rustc_index", default-features = false } +rustc_abi.workspace = true +rustc_index.workspace = true +rustc_parse_format.workspace = true + # local deps stdx.workspace = true diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attr.rs b/src/tools/rust-analyzer/crates/hir-def/src/attr.rs index fae071118..c6454eb9e 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/attr.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/attr.rs @@ -5,7 +5,7 @@ pub mod builtin; #[cfg(test)] mod tests; -use std::{hash::Hash, ops}; +use std::{hash::Hash, ops, slice::Iter as SliceIter}; use base_db::CrateId; use cfg::{CfgExpr, CfgOptions}; @@ -14,12 +14,11 @@ use hir_expand::{ attrs::{collect_attrs, Attr, AttrId, RawAttrs}, HirFileId, InFile, }; -use itertools::Itertools; use la_arena::{ArenaMap, Idx, RawIdx}; use mbe::DelimiterKind; use syntax::{ - ast::{self, HasAttrs, IsString}, - AstPtr, AstToken, SmolStr, TextRange, TextSize, + ast::{self, HasAttrs}, + AstPtr, SmolStr, }; use triomphe::Arc; @@ -33,26 +32,6 @@ use crate::{ LocalFieldId, Lookup, MacroId, VariantId, }; -/// Holds documentation -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Documentation(String); - -impl Documentation { - pub fn new(s: String) -> Self { - Documentation(s) - } - - pub fn as_str(&self) -> &str { - &self.0 - } -} - -impl From<Documentation> for String { - fn from(Documentation(string): Documentation) -> Self { - string - } -} - #[derive(Default, Debug, Clone, PartialEq, Eq)] pub struct Attrs(RawAttrs); @@ -221,33 +200,6 @@ impl Attrs { self.by_key("lang").string_value().and_then(|it| LangItem::from_str(it)) } - pub fn docs(&self) -> Option<Documentation> { - let docs = self.by_key("doc").attrs().filter_map(|attr| attr.string_value()); - let indent = doc_indent(self); - let mut buf = String::new(); - for doc in docs { - // str::lines doesn't yield anything for the empty string - if !doc.is_empty() { - buf.extend(Itertools::intersperse( - doc.lines().map(|line| { - line.char_indices() - .nth(indent) - .map_or(line, |(offset, _)| &line[offset..]) - .trim_end() - }), - "\n", - )); - } - buf.push('\n'); - } - buf.pop(); - if buf.is_empty() { - None - } else { - Some(Documentation(buf)) - } - } - pub fn has_doc_hidden(&self) -> bool { self.by_key("doc").tt_values().any(|tt| { tt.delimiter.kind == DelimiterKind::Parenthesis && @@ -299,7 +251,6 @@ impl Attrs { } } -use std::slice::Iter as SliceIter; #[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] pub enum DocAtom { /// eg. `#[doc(hidden)]` @@ -313,7 +264,6 @@ pub enum DocAtom { // Adapted from `CfgExpr` parsing code #[derive(Debug, Clone, PartialEq, Eq, Hash)] -// #[cfg_attr(test, derive(derive_arbitrary::Arbitrary))] pub enum DocExpr { Invalid, /// eg. `#[doc(hidden)]`, `#[doc(alias = "x")]` @@ -431,12 +381,10 @@ impl AttrsWithOwner { .item_tree(db) .raw_attrs(AttrOwner::ModItem(definition_tree_id.value.into())) .clone(), - ModuleOrigin::BlockExpr { block } => RawAttrs::from_attrs_owner( - db.upcast(), - InFile::new(block.file_id, block.to_node(db.upcast())) - .as_ref() - .map(|it| it as &dyn ast::HasAttrs), - ), + ModuleOrigin::BlockExpr { id, .. } => { + let tree = db.block_item_tree_query(id); + tree.raw_attrs(AttrOwner::TopLevel).clone() + } } } AttrDefId::FieldId(it) => { @@ -576,62 +524,6 @@ impl AttrsWithOwner { AttrSourceMap::new(owner.as_ref().map(|node| node as &dyn HasAttrs)) } - - pub fn docs_with_rangemap( - &self, - db: &dyn DefDatabase, - ) -> Option<(Documentation, DocsRangeMap)> { - let docs = - self.by_key("doc").attrs().filter_map(|attr| attr.string_value().map(|s| (s, attr.id))); - let indent = doc_indent(self); - let mut buf = String::new(); - let mut mapping = Vec::new(); - for (doc, idx) in docs { - if !doc.is_empty() { - let mut base_offset = 0; - for raw_line in doc.split('\n') { - let line = raw_line.trim_end(); - let line_len = line.len(); - let (offset, line) = match line.char_indices().nth(indent) { - Some((offset, _)) => (offset, &line[offset..]), - None => (0, line), - }; - let buf_offset = buf.len(); - buf.push_str(line); - mapping.push(( - TextRange::new(buf_offset.try_into().ok()?, buf.len().try_into().ok()?), - idx, - TextRange::at( - (base_offset + offset).try_into().ok()?, - line_len.try_into().ok()?, - ), - )); - buf.push('\n'); - base_offset += raw_line.len() + 1; - } - } else { - buf.push('\n'); - } - } - buf.pop(); - if buf.is_empty() { - None - } else { - Some((Documentation(buf), DocsRangeMap { mapping, source_map: self.source_map(db) })) - } - } -} - -fn doc_indent(attrs: &Attrs) -> usize { - attrs - .by_key("doc") - .attrs() - .filter_map(|attr| attr.string_value()) - .flat_map(|s| s.lines()) - .filter(|line| !line.chars().all(|c| c.is_whitespace())) - .map(|line| line.chars().take_while(|c| c.is_whitespace()).count()) - .min() - .unwrap_or(0) } #[derive(Debug)] @@ -675,7 +567,7 @@ impl AttrSourceMap { self.source_of_id(attr.id) } - fn source_of_id(&self, id: AttrId) -> InFile<&Either<ast::Attr, ast::Comment>> { + pub fn source_of_id(&self, id: AttrId) -> InFile<&Either<ast::Attr, ast::Comment>> { let ast_idx = id.ast_index(); let file_id = match self.mod_def_site_file_id { Some((file_id, def_site_cut)) if def_site_cut <= ast_idx => file_id, @@ -689,69 +581,6 @@ impl AttrSourceMap { } } -/// A struct to map text ranges from [`Documentation`] back to TextRanges in the syntax tree. -#[derive(Debug)] -pub struct DocsRangeMap { - source_map: AttrSourceMap, - // (docstring-line-range, attr_index, attr-string-range) - // a mapping from the text range of a line of the [`Documentation`] to the attribute index and - // the original (untrimmed) syntax doc line - mapping: Vec<(TextRange, AttrId, TextRange)>, -} - -impl DocsRangeMap { - /// Maps a [`TextRange`] relative to the documentation string back to its AST range - pub fn map(&self, range: TextRange) -> Option<InFile<TextRange>> { - let found = self.mapping.binary_search_by(|(probe, ..)| probe.ordering(range)).ok()?; - let (line_docs_range, idx, original_line_src_range) = self.mapping[found]; - if !line_docs_range.contains_range(range) { - return None; - } - - let relative_range = range - line_docs_range.start(); - - let InFile { file_id, value: source } = self.source_map.source_of_id(idx); - match source { - Either::Left(attr) => { - let string = get_doc_string_in_attr(attr)?; - let text_range = string.open_quote_text_range()?; - let range = TextRange::at( - text_range.end() + original_line_src_range.start() + relative_range.start(), - string.syntax().text_range().len().min(range.len()), - ); - Some(InFile { file_id, value: range }) - } - Either::Right(comment) => { - let text_range = comment.syntax().text_range(); - let range = TextRange::at( - text_range.start() - + TextSize::try_from(comment.prefix().len()).ok()? - + original_line_src_range.start() - + relative_range.start(), - text_range.len().min(range.len()), - ); - Some(InFile { file_id, value: range }) - } - } - } -} - -fn get_doc_string_in_attr(it: &ast::Attr) -> Option<ast::String> { - match it.expr() { - // #[doc = lit] - Some(ast::Expr::Literal(lit)) => match lit.kind() { - ast::LiteralKind::String(it) => Some(it), - _ => None, - }, - // #[cfg_attr(..., doc = "", ...)] - None => { - // FIXME: See highlight injection for what to do here - None - } - _ => None, - } -} - #[derive(Debug, Clone, Copy)] pub struct AttrQuery<'attr> { attrs: &'attr Attrs, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attr/builtin.rs b/src/tools/rust-analyzer/crates/hir-def/src/attr/builtin.rs index cead64a33..152f05b2c 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/attr/builtin.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/attr/builtin.rs @@ -8,7 +8,8 @@ //! name resolution, and `BUILTIN_ATTRIBUTES` is almost entirely unchanged from the original, to //! ease updating. -use once_cell::sync::OnceCell; +use std::sync::OnceLock; + use rustc_hash::FxHashMap; /// Ignored attribute namespaces used by tools. @@ -29,7 +30,7 @@ pub struct AttributeTemplate { } pub fn find_builtin_attr_idx(name: &str) -> Option<usize> { - static BUILTIN_LOOKUP_TABLE: OnceCell<FxHashMap<&'static str, usize>> = OnceCell::new(); + static BUILTIN_LOOKUP_TABLE: OnceLock<FxHashMap<&'static str, usize>> = OnceLock::new(); BUILTIN_LOOKUP_TABLE .get_or_init(|| { INERT_ATTRIBUTES.iter().map(|attr| attr.name).enumerate().map(|(a, b)| (b, a)).collect() @@ -239,7 +240,7 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[ template!(List: "address, kcfi, memory, thread"), DuplicatesOk, experimental!(no_sanitize) ), - gated!(no_coverage, Normal, template!(Word), WarnFollowing, experimental!(no_coverage)), + gated!(coverage, Normal, template!(Word, List: "on|off"), WarnFollowing, experimental!(coverage)), ungated!( doc, Normal, template!(List: "hidden|inline|...", NameValueStr: "string"), DuplicatesOk diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body.rs b/src/tools/rust-analyzer/crates/hir-def/src/body.rs index f8d492d0e..c0baf6011 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/body.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/body.rs @@ -65,6 +65,8 @@ pub type LabelSource = InFile<LabelPtr>; pub type FieldPtr = AstPtr<ast::RecordExprField>; pub type FieldSource = InFile<FieldPtr>; +pub type PatFieldPtr = AstPtr<ast::RecordPatField>; +pub type PatFieldSource = InFile<PatFieldPtr>; /// An item body together with the mapping from syntax nodes to HIR expression /// IDs. This is needed to go from e.g. a position in a file to the HIR @@ -90,8 +92,8 @@ pub struct BodySourceMap { /// We don't create explicit nodes for record fields (`S { record_field: 92 }`). /// Instead, we use id of expression (`92`) to identify the field. - field_map: FxHashMap<FieldSource, ExprId>, field_map_back: FxHashMap<ExprId, FieldSource>, + pat_field_map_back: FxHashMap<PatId, PatFieldSource>, expansions: FxHashMap<InFile<AstPtr<ast::MacroCall>>, HirFileId>, @@ -164,9 +166,10 @@ impl Body { }; let module = def.module(db); let expander = Expander::new(db, file_id, module); - let (mut body, source_map) = + let (mut body, mut source_map) = Body::new(db, def, expander, params, body, module.krate, is_async_fn); body.shrink_to_fit(); + source_map.shrink_to_fit(); (Arc::new(body), Arc::new(source_map)) } @@ -375,9 +378,8 @@ impl BodySourceMap { self.field_map_back[&expr].clone() } - pub fn node_field(&self, node: InFile<&ast::RecordExprField>) -> Option<ExprId> { - let src = node.map(AstPtr::new); - self.field_map.get(&src).cloned() + pub fn pat_field_syntax(&self, pat: PatId) -> PatFieldSource { + self.pat_field_map_back[&pat].clone() } pub fn macro_expansion_expr(&self, node: InFile<&ast::MacroExpr>) -> Option<ExprId> { @@ -389,4 +391,29 @@ impl BodySourceMap { pub fn diagnostics(&self) -> &[BodyDiagnostic] { &self.diagnostics } + + fn shrink_to_fit(&mut self) { + let Self { + expr_map, + expr_map_back, + pat_map, + pat_map_back, + label_map, + label_map_back, + field_map_back, + pat_field_map_back, + expansions, + diagnostics, + } = self; + expr_map.shrink_to_fit(); + expr_map_back.shrink_to_fit(); + pat_map.shrink_to_fit(); + pat_map_back.shrink_to_fit(); + label_map.shrink_to_fit(); + label_map_back.shrink_to_fit(); + field_map_back.shrink_to_fit(); + pat_field_map_back.shrink_to_fit(); + expansions.shrink_to_fit(); + diagnostics.shrink_to_fit(); + } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs index 3853a6ab3..cc02df80a 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs @@ -25,13 +25,20 @@ use triomphe::Arc; use crate::{ body::{Body, BodyDiagnostic, BodySourceMap, ExprPtr, LabelPtr, PatPtr}, + builtin_type::BuiltinUint, data::adt::StructKind, db::DefDatabase, expander::Expander, hir::{ - dummy_expr_id, Array, Binding, BindingAnnotation, BindingId, BindingProblems, CaptureBy, - ClosureKind, Expr, ExprId, Label, LabelId, Literal, LiteralOrConst, MatchArm, Movability, - Pat, PatId, RecordFieldPat, RecordLitField, Statement, + dummy_expr_id, + format_args::{ + self, FormatAlignment, FormatArgs, FormatArgsPiece, FormatArgument, FormatArgumentKind, + FormatArgumentsCollector, FormatCount, FormatDebugHex, FormatOptions, + FormatPlaceholder, FormatSign, FormatTrait, + }, + Array, Binding, BindingAnnotation, BindingId, BindingProblems, CaptureBy, ClosureKind, + Expr, ExprId, InlineAsm, Label, LabelId, Literal, LiteralOrConst, MatchArm, Movability, + OffsetOf, Pat, PatId, RecordFieldPat, RecordLitField, Statement, }, item_scope::BuiltinShadowMode, lang_item::LangItem, @@ -42,6 +49,8 @@ use crate::{ AdtId, BlockId, BlockLoc, ConstBlockLoc, DefWithBodyId, ModuleDefId, UnresolvedMacro, }; +type FxIndexSet<K> = indexmap::IndexSet<K, std::hash::BuildHasherDefault<rustc_hash::FxHasher>>; + pub(super) fn lower( db: &dyn DefDatabase, owner: DefWithBodyId, @@ -437,7 +446,6 @@ impl ExprCollector<'_> { None => self.missing_expr(), }; let src = self.expander.to_source(AstPtr::new(&field)); - self.source_map.field_map.insert(src.clone(), expr); self.source_map.field_map_back.insert(expr, src); Some(RecordLitField { name, expr }) }) @@ -505,6 +513,9 @@ impl ExprCollector<'_> { let mut args = Vec::new(); let mut arg_types = Vec::new(); if let Some(pl) = e.param_list() { + let num_params = pl.params().count(); + args.reserve_exact(num_params); + arg_types.reserve_exact(num_params); for param in pl.params() { let pat = this.collect_pat_top(param.pat()); let type_ref = @@ -576,11 +587,6 @@ impl ExprCollector<'_> { syntax_ptr, ) } - ast::Expr::BoxExpr(e) => { - let expr = self.collect_expr_opt(e.expr()); - self.alloc_expr(Expr::Box { expr }, syntax_ptr) - } - ast::Expr::ArrayExpr(e) => { let kind = e.kind(); @@ -650,6 +656,16 @@ impl ExprCollector<'_> { } } ast::Expr::UnderscoreExpr(_) => self.alloc_expr(Expr::Underscore, syntax_ptr), + ast::Expr::AsmExpr(e) => { + let e = self.collect_expr_opt(e.expr()); + self.alloc_expr(Expr::InlineAsm(InlineAsm { e }), syntax_ptr) + } + ast::Expr::OffsetOfExpr(e) => { + let container = Interned::new(TypeRef::from_ast_opt(&self.ctx(), e.ty())); + let fields = e.fields().map(|it| it.as_name()).collect(); + self.alloc_expr(Expr::OffsetOf(OffsetOf { container, fields }), syntax_ptr) + } + ast::Expr::FormatArgsExpr(f) => self.collect_format_args(f, syntax_ptr), }) } @@ -660,6 +676,7 @@ impl ExprCollector<'_> { let result_expr_id = self.alloc_expr(Expr::Missing, syntax_ptr); let prev_binding_owner = self.current_binding_owner.take(); self.current_binding_owner = Some(result_expr_id); + (result_expr_id, prev_binding_owner) } @@ -741,7 +758,27 @@ impl ExprCollector<'_> { fn collect_while_loop(&mut self, syntax_ptr: AstPtr<ast::Expr>, e: ast::WhileExpr) -> ExprId { let label = e.label().map(|label| self.collect_label(label)); let body = self.collect_labelled_block_opt(label, e.loop_body()); - let condition = self.collect_expr_opt(e.condition()); + + // Labels can also be used in the condition expression, like this: + // ``` + // fn main() { + // let mut optional = Some(0); + // 'my_label: while let Some(a) = match optional { + // None => break 'my_label, + // Some(val) => Some(val), + // } { + // println!("{}", a); + // optional = None; + // } + // } + // ``` + let condition = match label { + Some(label) => { + self.with_labeled_rib(label, |this| this.collect_expr_opt(e.condition())) + } + None => self.collect_expr_opt(e.condition()), + }; + let break_expr = self.alloc_expr(Expr::Break { expr: None, label: None }, syntax_ptr.clone()); let if_expr = self.alloc_expr( @@ -1100,7 +1137,9 @@ impl ExprCollector<'_> { ast::Stmt::ExprStmt(es) => matches!(es.expr(), Some(ast::Expr::MacroExpr(_))), _ => false, }); - statement_has_item || matches!(block.tail_expr(), Some(ast::Expr::MacroExpr(_))) + statement_has_item + || matches!(block.tail_expr(), Some(ast::Expr::MacroExpr(_))) + || (block.may_carry_attributes() && block.attrs().next().is_some()) }; let block_id = if block_has_items { @@ -1290,23 +1329,21 @@ impl ExprCollector<'_> { ast::Pat::RecordPat(p) => { let path = p.path().and_then(|path| self.expander.parse_path(self.db, path)).map(Box::new); - let args = p - .record_pat_field_list() - .expect("every struct should have a field list") + let record_pat_field_list = + &p.record_pat_field_list().expect("every struct should have a field list"); + let args = record_pat_field_list .fields() .filter_map(|f| { let ast_pat = f.pat()?; let pat = self.collect_pat(ast_pat, binding_list); let name = f.field_name()?.as_name(); + let src = self.expander.to_source(AstPtr::new(&f)); + self.source_map.pat_field_map_back.insert(pat, src); Some(RecordFieldPat { name, pat }) }) .collect(); - let ellipsis = p - .record_pat_field_list() - .expect("every struct should have a field list") - .rest_pat() - .is_some(); + let ellipsis = record_pat_field_list.rest_pat().is_some(); Pat::Record { path, args, ellipsis } } @@ -1526,6 +1563,401 @@ impl ExprCollector<'_> { } } // endregion: labels + + // region: format + fn expand_macros_to_string(&mut self, expr: ast::Expr) -> Option<(ast::String, bool)> { + let m = match expr { + ast::Expr::MacroExpr(m) => m, + ast::Expr::Literal(l) => { + return match l.kind() { + ast::LiteralKind::String(s) => Some((s, true)), + _ => None, + } + } + _ => return None, + }; + let e = m.macro_call()?; + let macro_ptr = AstPtr::new(&e); + let (exp, _) = self.collect_macro_call(e, macro_ptr, true, |this, expansion| { + expansion.and_then(|it| this.expand_macros_to_string(it)) + })?; + Some((exp, false)) + } + + fn collect_format_args( + &mut self, + f: ast::FormatArgsExpr, + syntax_ptr: AstPtr<ast::Expr>, + ) -> ExprId { + let mut args = FormatArgumentsCollector::new(); + f.args().for_each(|arg| { + args.add(FormatArgument { + kind: match arg.name() { + Some(name) => FormatArgumentKind::Named(name.as_name()), + None => FormatArgumentKind::Normal, + }, + expr: self.collect_expr_opt(arg.expr()), + }); + }); + let template = f.template(); + let fmt_snippet = template.as_ref().map(ToString::to_string); + let fmt = match template.and_then(|it| self.expand_macros_to_string(it)) { + Some((s, is_direct_literal)) => { + format_args::parse(&s, fmt_snippet, args, is_direct_literal, |name| { + self.alloc_expr_desugared(Expr::Path(Path::from(name))) + }) + } + None => FormatArgs { template: Default::default(), arguments: args.finish() }, + }; + + // Create a list of all _unique_ (argument, format trait) combinations. + // E.g. "{0} {0:x} {0} {1}" -> [(0, Display), (0, LowerHex), (1, Display)] + let mut argmap = FxIndexSet::default(); + for piece in fmt.template.iter() { + let FormatArgsPiece::Placeholder(placeholder) = piece else { continue }; + if let Ok(index) = placeholder.argument.index { + argmap.insert((index, ArgumentType::Format(placeholder.format_trait))); + } + } + + let lit_pieces = + fmt.template + .iter() + .enumerate() + .filter_map(|(i, piece)| { + match piece { + FormatArgsPiece::Literal(s) => Some( + self.alloc_expr_desugared(Expr::Literal(Literal::String(s.clone()))), + ), + &FormatArgsPiece::Placeholder(_) => { + // Inject empty string before placeholders when not already preceded by a literal piece. + if i == 0 + || matches!(fmt.template[i - 1], FormatArgsPiece::Placeholder(_)) + { + Some(self.alloc_expr_desugared(Expr::Literal(Literal::String( + "".into(), + )))) + } else { + None + } + } + } + }) + .collect(); + let lit_pieces = self.alloc_expr_desugared(Expr::Array(Array::ElementList { + elements: lit_pieces, + is_assignee_expr: false, + })); + let lit_pieces = self.alloc_expr_desugared(Expr::Ref { + expr: lit_pieces, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }); + let format_options = { + // Generate: + // &[format_spec_0, format_spec_1, format_spec_2] + let elements = fmt + .template + .iter() + .filter_map(|piece| { + let FormatArgsPiece::Placeholder(placeholder) = piece else { return None }; + Some(self.make_format_spec(placeholder, &mut argmap)) + }) + .collect(); + let array = self.alloc_expr_desugared(Expr::Array(Array::ElementList { + elements, + is_assignee_expr: false, + })); + self.alloc_expr_desugared(Expr::Ref { + expr: array, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }) + }; + let arguments = &*fmt.arguments.arguments; + + let args = if arguments.is_empty() { + let expr = self.alloc_expr_desugared(Expr::Array(Array::ElementList { + elements: Box::default(), + is_assignee_expr: false, + })); + self.alloc_expr_desugared(Expr::Ref { + expr, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }) + } else { + // Generate: + // &match (&arg0, &arg1, &…) { + // args => [ + // <core::fmt::Argument>::new_display(args.0), + // <core::fmt::Argument>::new_lower_hex(args.1), + // <core::fmt::Argument>::new_debug(args.0), + // … + // ] + // } + let args = argmap + .iter() + .map(|&(arg_index, ty)| { + let arg = self.alloc_expr_desugared(Expr::Ref { + expr: arguments[arg_index].expr, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }); + self.make_argument(arg, ty) + }) + .collect(); + let array = self.alloc_expr_desugared(Expr::Array(Array::ElementList { + elements: args, + is_assignee_expr: false, + })); + self.alloc_expr_desugared(Expr::Ref { + expr: array, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }) + }; + + // Generate: + // <core::fmt::Arguments>::new_v1_formatted( + // lit_pieces, + // args, + // format_options, + // unsafe { ::core::fmt::UnsafeArg::new() } + // ) + + let Some(new_v1_formatted) = + LangItem::FormatArguments.ty_rel_path(self.db, self.krate, name![new_v1_formatted]) + else { + return self.missing_expr(); + }; + let Some(unsafe_arg_new) = + LangItem::FormatUnsafeArg.ty_rel_path(self.db, self.krate, name![new]) + else { + return self.missing_expr(); + }; + let new_v1_formatted = self.alloc_expr_desugared(Expr::Path(new_v1_formatted)); + + let unsafe_arg_new = self.alloc_expr_desugared(Expr::Path(unsafe_arg_new)); + let unsafe_arg_new = self.alloc_expr_desugared(Expr::Call { + callee: unsafe_arg_new, + args: Box::default(), + is_assignee_expr: false, + }); + let unsafe_arg_new = self.alloc_expr_desugared(Expr::Unsafe { + id: None, + statements: Box::default(), + tail: Some(unsafe_arg_new), + }); + + self.alloc_expr( + Expr::Call { + callee: new_v1_formatted, + args: Box::new([lit_pieces, args, format_options, unsafe_arg_new]), + is_assignee_expr: false, + }, + syntax_ptr, + ) + } + + /// Generate a hir expression for a format_args placeholder specification. + /// + /// Generates + /// + /// ```text + /// <core::fmt::rt::Placeholder::new( + /// …usize, // position + /// '…', // fill + /// <core::fmt::rt::Alignment>::…, // alignment + /// …u32, // flags + /// <core::fmt::rt::Count::…>, // width + /// <core::fmt::rt::Count::…>, // precision + /// ) + /// ``` + fn make_format_spec( + &mut self, + placeholder: &FormatPlaceholder, + argmap: &mut FxIndexSet<(usize, ArgumentType)>, + ) -> ExprId { + let position = match placeholder.argument.index { + Ok(arg_index) => { + let (i, _) = + argmap.insert_full((arg_index, ArgumentType::Format(placeholder.format_trait))); + self.alloc_expr_desugared(Expr::Literal(Literal::Uint( + i as u128, + Some(BuiltinUint::Usize), + ))) + } + Err(_) => self.missing_expr(), + }; + let &FormatOptions { + ref width, + ref precision, + alignment, + fill, + sign, + alternate, + zero_pad, + debug_hex, + } = &placeholder.format_options; + let fill = self.alloc_expr_desugared(Expr::Literal(Literal::Char(fill.unwrap_or(' ')))); + + let align = { + let align = LangItem::FormatAlignment.ty_rel_path( + self.db, + self.krate, + match alignment { + Some(FormatAlignment::Left) => name![Left], + Some(FormatAlignment::Right) => name![Right], + Some(FormatAlignment::Center) => name![Center], + None => name![Unknown], + }, + ); + match align { + Some(path) => self.alloc_expr_desugared(Expr::Path(path)), + None => self.missing_expr(), + } + }; + // This needs to match `Flag` in library/core/src/fmt/rt.rs. + let flags: u32 = ((sign == Some(FormatSign::Plus)) as u32) + | ((sign == Some(FormatSign::Minus)) as u32) << 1 + | (alternate as u32) << 2 + | (zero_pad as u32) << 3 + | ((debug_hex == Some(FormatDebugHex::Lower)) as u32) << 4 + | ((debug_hex == Some(FormatDebugHex::Upper)) as u32) << 5; + let flags = self.alloc_expr_desugared(Expr::Literal(Literal::Uint( + flags as u128, + Some(BuiltinUint::U32), + ))); + let precision = self.make_count(&precision, argmap); + let width = self.make_count(&width, argmap); + + let format_placeholder_new = { + let format_placeholder_new = + LangItem::FormatPlaceholder.ty_rel_path(self.db, self.krate, name![new]); + match format_placeholder_new { + Some(path) => self.alloc_expr_desugared(Expr::Path(path)), + None => self.missing_expr(), + } + }; + + self.alloc_expr_desugared(Expr::Call { + callee: format_placeholder_new, + args: Box::new([position, fill, align, flags, precision, width]), + is_assignee_expr: false, + }) + } + + /// Generate a hir expression for a format_args Count. + /// + /// Generates: + /// + /// ```text + /// <core::fmt::rt::Count>::Is(…) + /// ``` + /// + /// or + /// + /// ```text + /// <core::fmt::rt::Count>::Param(…) + /// ``` + /// + /// or + /// + /// ```text + /// <core::fmt::rt::Count>::Implied + /// ``` + fn make_count( + &mut self, + count: &Option<FormatCount>, + argmap: &mut FxIndexSet<(usize, ArgumentType)>, + ) -> ExprId { + match count { + Some(FormatCount::Literal(n)) => { + match LangItem::FormatCount.ty_rel_path(self.db, self.krate, name![Is]) { + Some(count_is) => { + let count_is = self.alloc_expr_desugared(Expr::Path(count_is)); + let args = self.alloc_expr_desugared(Expr::Literal(Literal::Uint( + *n as u128, + Some(BuiltinUint::Usize), + ))); + self.alloc_expr_desugared(Expr::Call { + callee: count_is, + args: Box::new([args]), + is_assignee_expr: false, + }) + } + None => self.missing_expr(), + } + } + Some(FormatCount::Argument(arg)) => { + if let Ok(arg_index) = arg.index { + let (i, _) = argmap.insert_full((arg_index, ArgumentType::Usize)); + + match LangItem::FormatCount.ty_rel_path(self.db, self.krate, name![Param]) { + Some(count_param) => { + let count_param = self.alloc_expr_desugared(Expr::Path(count_param)); + let args = self.alloc_expr_desugared(Expr::Literal(Literal::Uint( + i as u128, + Some(BuiltinUint::Usize), + ))); + self.alloc_expr_desugared(Expr::Call { + callee: count_param, + args: Box::new([args]), + is_assignee_expr: false, + }) + } + None => self.missing_expr(), + } + } else { + self.missing_expr() + } + } + None => match LangItem::FormatCount.ty_rel_path(self.db, self.krate, name![Implied]) { + Some(count_param) => self.alloc_expr_desugared(Expr::Path(count_param)), + None => self.missing_expr(), + }, + } + } + + /// Generate a hir expression representing an argument to a format_args invocation. + /// + /// Generates: + /// + /// ```text + /// <core::fmt::Argument>::new_…(arg) + /// ``` + fn make_argument(&mut self, arg: ExprId, ty: ArgumentType) -> ExprId { + use ArgumentType::*; + use FormatTrait::*; + match LangItem::FormatArgument.ty_rel_path( + self.db, + self.krate, + match ty { + Format(Display) => name![new_display], + Format(Debug) => name![new_debug], + Format(LowerExp) => name![new_lower_exp], + Format(UpperExp) => name![new_upper_exp], + Format(Octal) => name![new_octal], + Format(Pointer) => name![new_pointer], + Format(Binary) => name![new_binary], + Format(LowerHex) => name![new_lower_hex], + Format(UpperHex) => name![new_upper_hex], + Usize => name![from_usize], + }, + ) { + Some(new_fn) => { + let new_fn = self.alloc_expr_desugared(Expr::Path(new_fn)); + self.alloc_expr_desugared(Expr::Call { + callee: new_fn, + args: Box::new([arg]), + is_assignee_expr: false, + }) + } + None => self.missing_expr(), + } + } + // endregion: format } fn pat_literal_to_hir(lit: &ast::LiteralPat) -> Option<(Literal, ast::Literal)> { @@ -1601,3 +2033,9 @@ fn comma_follows_token(t: Option<syntax::SyntaxToken>) -> bool { (|| syntax::algo::skip_trivia_token(t?.next_token()?, syntax::Direction::Next))() .map_or(false, |it| it.kind() == syntax::T![,]) } + +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +enum ArgumentType { + Format(FormatTrait), + Usize, +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body/pretty.rs b/src/tools/rust-analyzer/crates/hir-def/src/body/pretty.rs index 5d71abe37..fad4d7a4d 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/body/pretty.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/body/pretty.rs @@ -2,7 +2,7 @@ use std::fmt::{self, Write}; -use hir_expand::db::ExpandDatabase; +use itertools::Itertools; use syntax::ast::HasName; use crate::{ @@ -51,8 +51,7 @@ pub(super) fn print_body_hir(db: &dyn DefDatabase, body: &Body, owner: DefWithBo } }; - let mut p = - Printer { db: db.upcast(), body, buf: header, indent_level: 0, needs_indent: false }; + let mut p = Printer { db, body, buf: header, indent_level: 0, needs_indent: false }; if let DefWithBodyId::FunctionId(it) = owner { p.buf.push('('); body.params.iter().zip(&db.function_data(it).params).for_each(|(¶m, ty)| { @@ -76,8 +75,7 @@ pub(super) fn print_expr_hir( _owner: DefWithBodyId, expr: ExprId, ) -> String { - let mut p = - Printer { db: db.upcast(), body, buf: String::new(), indent_level: 0, needs_indent: false }; + let mut p = Printer { db, body, buf: String::new(), indent_level: 0, needs_indent: false }; p.print_expr(expr); p.buf } @@ -98,7 +96,7 @@ macro_rules! wln { } struct Printer<'a> { - db: &'a dyn ExpandDatabase, + db: &'a dyn DefDatabase, body: &'a Body, buf: String, indent_level: usize, @@ -142,9 +140,14 @@ impl Printer<'_> { } fn newline(&mut self) { - match self.buf.chars().rev().find(|ch| *ch != ' ') { - Some('\n') | None => {} - _ => writeln!(self).unwrap(), + match self.buf.chars().rev().find_position(|ch| *ch != ' ') { + Some((_, '\n')) | None => {} + Some((idx, _)) => { + if idx != 0 { + self.buf.drain(self.buf.len() - idx..); + } + writeln!(self).unwrap() + } } } @@ -154,6 +157,19 @@ impl Printer<'_> { match expr { Expr::Missing => w!(self, "�"), Expr::Underscore => w!(self, "_"), + Expr::InlineAsm(_) => w!(self, "builtin#asm(_)"), + Expr::OffsetOf(offset_of) => { + w!(self, "builtin#offset_of("); + self.print_type_ref(&offset_of.container); + w!( + self, + ", {})", + offset_of + .fields + .iter() + .format_with(".", |field, f| f(&field.display(self.db.upcast()))) + ); + } Expr::Path(path) => self.print_path(path), Expr::If { condition, then_branch, else_branch } => { w!(self, "if "); @@ -173,7 +189,7 @@ impl Printer<'_> { } Expr::Loop { body, label } => { if let Some(lbl) = label { - w!(self, "{}: ", self.body[*lbl].name.display(self.db)); + w!(self, "{}: ", self.body[*lbl].name.display(self.db.upcast())); } w!(self, "loop "); self.print_expr(*body); @@ -193,7 +209,7 @@ impl Printer<'_> { } Expr::MethodCall { receiver, method_name, args, generic_args } => { self.print_expr(*receiver); - w!(self, ".{}", method_name.display(self.db)); + w!(self, ".{}", method_name.display(self.db.upcast())); if let Some(args) = generic_args { w!(self, "::<"); print_generic_args(self.db, args, self).unwrap(); @@ -231,13 +247,13 @@ impl Printer<'_> { Expr::Continue { label } => { w!(self, "continue"); if let Some(lbl) = label { - w!(self, " {}", self.body[*lbl].name.display(self.db)); + w!(self, " {}", self.body[*lbl].name.display(self.db.upcast())); } } Expr::Break { expr, label } => { w!(self, "break"); if let Some(lbl) = label { - w!(self, " {}", self.body[*lbl].name.display(self.db)); + w!(self, " {}", self.body[*lbl].name.display(self.db.upcast())); } if let Some(expr) = expr { self.whitespace(); @@ -276,7 +292,7 @@ impl Printer<'_> { w!(self, "{{"); self.indented(|p| { for field in &**fields { - w!(p, "{}: ", field.name.display(self.db)); + w!(p, "{}: ", field.name.display(self.db.upcast())); p.print_expr(field.expr); wln!(p, ","); } @@ -293,7 +309,7 @@ impl Printer<'_> { } Expr::Field { expr, name } => { self.print_expr(*expr); - w!(self, ".{}", name.display(self.db)); + w!(self, ".{}", name.display(self.db.upcast())); } Expr::Await { expr } => { self.print_expr(*expr); @@ -431,7 +447,8 @@ impl Printer<'_> { } Expr::Literal(lit) => self.print_literal(lit), Expr::Block { id: _, statements, tail, label } => { - let label = label.map(|lbl| format!("{}: ", self.body[lbl].name.display(self.db))); + let label = + label.map(|lbl| format!("{}: ", self.body[lbl].name.display(self.db.upcast()))); self.print_block(label.as_deref(), statements, tail); } Expr::Unsafe { id: _, statements, tail } => { @@ -507,7 +524,7 @@ impl Printer<'_> { w!(self, " {{"); self.indented(|p| { for arg in args.iter() { - w!(p, "{}: ", arg.name.display(self.db)); + w!(p, "{}: ", arg.name.display(self.db.upcast())); p.print_pat(arg.pat); wln!(p, ","); } @@ -666,6 +683,6 @@ impl Printer<'_> { BindingAnnotation::Ref => "ref ", BindingAnnotation::RefMut => "ref mut ", }; - w!(self, "{}{}", mode, name.display(self.db)); + w!(self, "{}{}", mode, name.display(self.db.upcast())); } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body/tests.rs b/src/tools/rust-analyzer/crates/hir-def/src/body/tests.rs index d55820116..1658757d2 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/body/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/body/tests.rs @@ -1,13 +1,13 @@ mod block; use base_db::{fixture::WithFixture, SourceDatabase}; -use expect_test::Expect; +use expect_test::{expect, Expect}; use crate::{test_db::TestDB, ModuleDefId}; use super::*; -fn lower(ra_fixture: &str) -> Arc<Body> { +fn lower(ra_fixture: &str) -> (TestDB, Arc<Body>, DefWithBodyId) { let db = TestDB::with_files(ra_fixture); let krate = db.crate_graph().iter().next().unwrap(); @@ -21,8 +21,10 @@ fn lower(ra_fixture: &str) -> Arc<Body> { } } } + let fn_def = fn_def.unwrap().into(); - db.body(fn_def.unwrap().into()) + let body = db.body(fn_def); + (db, body, fn_def) } fn def_map_at(ra_fixture: &str) -> String { @@ -138,3 +140,84 @@ mod m { "#, ); } + +#[test] +fn desugar_builtin_format_args() { + // Regression test for a path resolution bug introduced with inner item handling. + let (db, body, def) = lower( + r#" +//- minicore: fmt +fn main() { + let are = "are"; + let count = 10; + builtin#format_args("hello {count:02} {} friends, we {are:?} {0}{last}", "fancy", last = "!"); +} +"#, + ); + + expect![[r#" + fn main() { + let are = "are"; + let count = 10; + builtin#lang(Arguments::new_v1_formatted)( + &[ + "\"hello ", " ", " friends, we ", " ", "", "\"", + ], + &[ + builtin#lang(Argument::new_display)( + &count, + ), builtin#lang(Argument::new_display)( + &"fancy", + ), builtin#lang(Argument::new_debug)( + &are, + ), builtin#lang(Argument::new_display)( + &"!", + ), + ], + &[ + builtin#lang(Placeholder::new)( + 0usize, + ' ', + builtin#lang(Alignment::Unknown), + 8u32, + builtin#lang(Count::Implied), + builtin#lang(Count::Is)( + 2usize, + ), + ), builtin#lang(Placeholder::new)( + 1usize, + ' ', + builtin#lang(Alignment::Unknown), + 0u32, + builtin#lang(Count::Implied), + builtin#lang(Count::Implied), + ), builtin#lang(Placeholder::new)( + 2usize, + ' ', + builtin#lang(Alignment::Unknown), + 0u32, + builtin#lang(Count::Implied), + builtin#lang(Count::Implied), + ), builtin#lang(Placeholder::new)( + 1usize, + ' ', + builtin#lang(Alignment::Unknown), + 0u32, + builtin#lang(Count::Implied), + builtin#lang(Count::Implied), + ), builtin#lang(Placeholder::new)( + 3usize, + ' ', + builtin#lang(Alignment::Unknown), + 0u32, + builtin#lang(Count::Implied), + builtin#lang(Count::Implied), + ), + ], + unsafe { + builtin#lang(UnsafeArg::new)() + }, + ); + }"#]] + .assert_eq(&body.pretty_print(&db, def)) +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body/tests/block.rs b/src/tools/rust-analyzer/crates/hir-def/src/body/tests/block.rs index 4e015a7fb..44eeed9e3 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/body/tests/block.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/body/tests/block.rs @@ -38,9 +38,9 @@ fn outer() { "#, expect![[r#" block scope - CrateStruct: t - PlainStruct: t v - SelfStruct: t + CrateStruct: ti + PlainStruct: ti vi + SelfStruct: ti Struct: v SuperStruct: _ @@ -66,7 +66,7 @@ fn outer() { "#, expect![[r#" block scope - imported: t v + imported: ti vi name: v crate @@ -92,9 +92,9 @@ fn outer() { "#, expect![[r#" block scope - inner1: t + inner1: ti inner2: v - outer: v + outer: vi block scope inner: v @@ -121,7 +121,7 @@ struct Struct {} "#, expect![[r#" block scope - Struct: t + Struct: ti crate Struct: t @@ -153,7 +153,7 @@ fn outer() { "#, expect![[r#" block scope - ResolveMe: t + ResolveMe: ti block scope m2: t @@ -214,7 +214,7 @@ fn f() { "#, expect![[r#" block scope - ResolveMe: t + ResolveMe: ti block scope h: v @@ -292,7 +292,7 @@ pub mod cov_mark { nested: v crate - cov_mark: t + cov_mark: ti f: v "#]], ); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/data.rs b/src/tools/rust-analyzer/crates/hir-def/src/data.rs index 91db68058..68defa385 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/data.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/data.rs @@ -487,7 +487,7 @@ impl ExternCrateDeclData { db.crate_def_map(loc.container.krate()) .extern_prelude() .find(|&(prelude_name, ..)| *prelude_name == name) - .map(|(_, root)| root.krate()) + .map(|(_, (root, _))| root.krate()) }; Arc::new(Self { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/data/adt.rs b/src/tools/rust-analyzer/crates/hir-def/src/data/adt.rs index c8df3f3f9..224f7328f 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/data/adt.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/data/adt.rs @@ -447,6 +447,7 @@ impl VariantData { } } + // FIXME: Linear lookup pub fn field(&self, name: &Name) -> Option<LocalFieldId> { self.fields().iter().find_map(|(id, data)| if &data.name == name { Some(id) } else { None }) } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/db.rs b/src/tools/rust-analyzer/crates/hir-def/src/db.rs index e34a6768f..31c1a7130 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/db.rs @@ -82,6 +82,9 @@ pub trait DefDatabase: InternDatabase + ExpandDatabase + Upcast<dyn ExpandDataba #[salsa::invoke(ItemTree::file_item_tree_query)] fn file_item_tree(&self, file_id: HirFileId) -> Arc<ItemTree>; + #[salsa::invoke(ItemTree::block_item_tree_query)] + fn block_item_tree_query(&self, block_id: BlockId) -> Arc<ItemTree>; + #[salsa::invoke(crate_def_map_wait)] #[salsa::transparent] fn crate_def_map(&self, krate: CrateId) -> Arc<DefMap>; diff --git a/src/tools/rust-analyzer/crates/hir-def/src/find_path.rs b/src/tools/rust-analyzer/crates/hir-def/src/find_path.rs index df2af4c89..b9c5ff727 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/find_path.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/find_path.rs @@ -11,7 +11,7 @@ use crate::{ nameres::DefMap, path::{ModPath, PathKind}, visibility::Visibility, - ModuleDefId, ModuleId, + CrateRootModuleId, ModuleDefId, ModuleId, }; /// Find a path that can be used to refer to a certain item. This can depend on @@ -37,6 +37,20 @@ pub fn find_path_prefixed( find_path_inner(db, item, from, Some(prefix_kind), prefer_no_std) } +#[derive(Copy, Clone, Debug)] +enum Stability { + Unstable, + Stable, +} +use Stability::*; + +fn zip_stability(a: Stability, b: Stability) -> Stability { + match (a, b) { + (Stable, Stable) => Stable, + _ => Unstable, + } +} + const MAX_PATH_LEN: usize = 15; #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -81,7 +95,7 @@ fn find_path_inner( } let def_map = from.def_map(db); - let crate_root = def_map.crate_root().into(); + let crate_root = def_map.crate_root(); // - if the item is a module, jump straight to module search if let ItemInNs::Types(ModuleDefId::ModuleId(module_id)) = item { let mut visited_modules = FxHashSet::default(); @@ -95,7 +109,8 @@ fn find_path_inner( MAX_PATH_LEN, prefixed, prefer_no_std || db.crate_supports_no_std(crate_root.krate), - ); + ) + .map(|(item, _)| item); } // - if the item is already in scope, return the name under which it is @@ -143,19 +158,20 @@ fn find_path_inner( prefer_no_std || db.crate_supports_no_std(crate_root.krate), scope_name, ) + .map(|(item, _)| item) } fn find_path_for_module( db: &dyn DefDatabase, def_map: &DefMap, visited_modules: &mut FxHashSet<ModuleId>, - crate_root: ModuleId, + crate_root: CrateRootModuleId, from: ModuleId, module_id: ModuleId, max_len: usize, prefixed: Option<PrefixKind>, prefer_no_std: bool, -) -> Option<ModPath> { +) -> Option<(ModPath, Stability)> { if max_len == 0 { return None; } @@ -165,25 +181,25 @@ fn find_path_for_module( let scope_name = find_in_scope(db, def_map, from, ItemInNs::Types(module_id.into())); if prefixed.is_none() { if let Some(scope_name) = scope_name { - return Some(ModPath::from_segments(PathKind::Plain, Some(scope_name))); + return Some((ModPath::from_segments(PathKind::Plain, Some(scope_name)), Stable)); } } // - if the item is the crate root, return `crate` if module_id == crate_root { - return Some(ModPath::from_segments(PathKind::Crate, None)); + return Some((ModPath::from_segments(PathKind::Crate, None), Stable)); } // - if relative paths are fine, check if we are searching for a parent if prefixed.filter(PrefixKind::is_absolute).is_none() { if let modpath @ Some(_) = find_self_super(def_map, module_id, from) { - return modpath; + return modpath.zip(Some(Stable)); } } // - if the item is the crate root of a dependency crate, return the name from the extern prelude let root_def_map = crate_root.def_map(db); - for (name, def_id) in root_def_map.extern_prelude() { + for (name, (def_id, _extern_crate)) in root_def_map.extern_prelude() { if module_id == def_id { let name = scope_name.unwrap_or_else(|| name.clone()); @@ -192,7 +208,7 @@ fn find_path_for_module( def_map[local_id] .scope .type_(&name) - .filter(|&(id, _)| id != ModuleDefId::ModuleId(def_id)) + .filter(|&(id, _)| id != ModuleDefId::ModuleId(def_id.into())) }) .is_some(); let kind = if name_already_occupied_in_type_ns { @@ -201,14 +217,14 @@ fn find_path_for_module( } else { PathKind::Plain }; - return Some(ModPath::from_segments(kind, Some(name))); + return Some((ModPath::from_segments(kind, Some(name)), Stable)); } } if let value @ Some(_) = find_in_prelude(db, &root_def_map, &def_map, ItemInNs::Types(module_id.into()), from) { - return value; + return value.zip(Some(Stable)); } calculate_best_path( db, @@ -224,6 +240,7 @@ fn find_path_for_module( ) } +// FIXME: Do we still need this now that we record import origins, and hence aliases? fn find_in_scope( db: &dyn DefDatabase, def_map: &DefMap, @@ -244,7 +261,7 @@ fn find_in_prelude( item: ItemInNs, from: ModuleId, ) -> Option<ModPath> { - let prelude_module = root_def_map.prelude()?; + let (prelude_module, _) = root_def_map.prelude()?; // Preludes in block DefMaps are ignored, only the crate DefMap is searched let prelude_def_map = prelude_module.def_map(db); let prelude_scope = &prelude_def_map[prelude_module.local_id].scope; @@ -293,18 +310,26 @@ fn calculate_best_path( db: &dyn DefDatabase, def_map: &DefMap, visited_modules: &mut FxHashSet<ModuleId>, - crate_root: ModuleId, + crate_root: CrateRootModuleId, max_len: usize, item: ItemInNs, from: ModuleId, mut prefixed: Option<PrefixKind>, prefer_no_std: bool, scope_name: Option<Name>, -) -> Option<ModPath> { +) -> Option<(ModPath, Stability)> { if max_len <= 1 { return None; } let mut best_path = None; + let update_best_path = + |best_path: &mut Option<_>, new_path: (ModPath, Stability)| match best_path { + Some((old_path, old_stability)) => { + *old_path = new_path.0; + *old_stability = zip_stability(*old_stability, new_path.1); + } + None => *best_path = Some(new_path), + }; // Recursive case: // - otherwise, look for modules containing (reexporting) it and import it from one of those if item.krate(db) == Some(from.krate) { @@ -327,14 +352,14 @@ fn calculate_best_path( prefixed, prefer_no_std, ) { - path.push_segment(name); + path.0.push_segment(name); - let new_path = match best_path { + let new_path = match best_path.take() { Some(best_path) => select_best_path(best_path, path, prefer_no_std), None => path, }; - best_path_len = new_path.len(); - best_path = Some(new_path); + best_path_len = new_path.0.len(); + update_best_path(&mut best_path, new_path); } } } else { @@ -346,9 +371,14 @@ fn calculate_best_path( let extern_paths = crate_graph[from.krate].dependencies.iter().filter_map(|dep| { let import_map = db.import_map(dep.crate_id); import_map.import_info_for(item).and_then(|info| { + if info.is_doc_hidden { + // the item or import is `#[doc(hidden)]`, so skip it as it is in an external crate + return None; + } + // Determine best path for containing module and append last segment from `info`. // FIXME: we should guide this to look up the path locally, or from the same crate again? - let mut path = find_path_for_module( + let (mut path, path_stability) = find_path_for_module( db, def_map, visited_modules, @@ -361,16 +391,19 @@ fn calculate_best_path( )?; cov_mark::hit!(partially_imported); path.push_segment(info.name.clone()); - Some(path) + Some(( + path, + zip_stability(path_stability, if info.is_unstable { Unstable } else { Stable }), + )) }) }); for path in extern_paths { - let new_path = match best_path { + let new_path = match best_path.take() { Some(best_path) => select_best_path(best_path, path, prefer_no_std), None => path, }; - best_path = Some(new_path); + update_best_path(&mut best_path, new_path); } } if let Some(module) = item.module(db) { @@ -381,15 +414,24 @@ fn calculate_best_path( } match prefixed.map(PrefixKind::prefix) { Some(prefix) => best_path.or_else(|| { - scope_name.map(|scope_name| ModPath::from_segments(prefix, Some(scope_name))) + scope_name.map(|scope_name| (ModPath::from_segments(prefix, Some(scope_name)), Stable)) }), None => best_path, } } -fn select_best_path(old_path: ModPath, new_path: ModPath, prefer_no_std: bool) -> ModPath { +fn select_best_path( + old_path: (ModPath, Stability), + new_path: (ModPath, Stability), + prefer_no_std: bool, +) -> (ModPath, Stability) { + match (old_path.1, new_path.1) { + (Stable, Unstable) => return old_path, + (Unstable, Stable) => return new_path, + _ => {} + } const STD_CRATES: [Name; 3] = [known::std, known::core, known::alloc]; - match (old_path.segments().first(), new_path.segments().first()) { + match (old_path.0.segments().first(), new_path.0.segments().first()) { (Some(old), Some(new)) if STD_CRATES.contains(old) && STD_CRATES.contains(new) => { let rank = match prefer_no_std { false => |name: &Name| match name { @@ -410,7 +452,7 @@ fn select_best_path(old_path: ModPath, new_path: ModPath, prefer_no_std: bool) - match nrank.cmp(&orank) { Ordering::Less => old_path, Ordering::Equal => { - if new_path.len() < old_path.len() { + if new_path.0.len() < old_path.0.len() { new_path } else { old_path @@ -420,7 +462,7 @@ fn select_best_path(old_path: ModPath, new_path: ModPath, prefer_no_std: bool) - } } _ => { - if new_path.len() < old_path.len() { + if new_path.0.len() < old_path.0.len() { new_path } else { old_path @@ -1293,4 +1335,90 @@ pub mod prelude { "None", ); } + + #[test] + fn different_crate_renamed_through_dep() { + check_found_path( + r#" +//- /main.rs crate:main deps:intermediate +$0 +//- /intermediate.rs crate:intermediate deps:std +pub extern crate std as std_renamed; +//- /std.rs crate:std +pub struct S; + "#, + "intermediate::std_renamed::S", + "intermediate::std_renamed::S", + "intermediate::std_renamed::S", + "intermediate::std_renamed::S", + ); + } + + #[test] + fn different_crate_doc_hidden() { + check_found_path( + r#" +//- /main.rs crate:main deps:intermediate +$0 +//- /intermediate.rs crate:intermediate deps:std +#[doc(hidden)] +pub extern crate std; +pub extern crate std as longer; +//- /std.rs crate:std +pub struct S; + "#, + "intermediate::longer::S", + "intermediate::longer::S", + "intermediate::longer::S", + "intermediate::longer::S", + ); + } + + #[test] + fn respect_doc_hidden() { + check_found_path( + r#" +//- /main.rs crate:main deps:std,lazy_static +$0 +//- /lazy_static.rs crate:lazy_static deps:core +#[doc(hidden)] +pub use core::ops::Deref as __Deref; +//- /std.rs crate:std deps:core +pub use core::ops; +//- /core.rs crate:core +pub mod ops { + pub trait Deref {} +} + "#, + "std::ops::Deref", + "std::ops::Deref", + "std::ops::Deref", + "std::ops::Deref", + ); + } + + #[test] + fn respect_unstable_modules() { + check_found_path( + r#" +//- /main.rs crate:main deps:std,core +#![no_std] +extern crate std; +$0 +//- /longer.rs crate:std deps:core +pub mod error { + pub use core::error::Error; +} +//- /core.rs crate:core +pub mod error { + #![unstable(feature = "error_in_core", issue = "103765")] + pub trait Error {} +} +"#, + "std::error::Error", + "std::error::Error", + "std::error::Error", + "std::error::Error", + ); + } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/generics.rs b/src/tools/rust-analyzer/crates/hir-def/src/generics.rs index d7d44e413..1e2535a8a 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/generics.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/generics.rs @@ -21,10 +21,11 @@ use crate::{ db::DefDatabase, dyn_map::{keys, DynMap}, expander::Expander, + item_tree::{AttrOwner, ItemTree}, lower::LowerCtx, nameres::{DefMap, MacroSubNs}, src::{HasChildSource, HasSource}, - type_ref::{LifetimeRef, TypeBound, TypeRef}, + type_ref::{ConstRef, LifetimeRef, TypeBound, TypeRef}, AdtId, ConstParamId, GenericDefId, HasModule, LifetimeParamId, LocalLifetimeParamId, LocalTypeOrConstParamId, Lookup, TypeOrConstParamId, TypeParamId, }; @@ -48,7 +49,7 @@ pub struct LifetimeParamData { pub struct ConstParamData { pub name: Name, pub ty: Interned<TypeRef>, - pub has_default: bool, + pub default: Option<ConstRef>, } #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] @@ -75,7 +76,7 @@ impl TypeOrConstParamData { pub fn has_default(&self) -> bool { match self { TypeOrConstParamData::TypeParamData(it) => it.default.is_some(), - TypeOrConstParamData::ConstParamData(it) => it.has_default, + TypeOrConstParamData::ConstParamData(it) => it.default.is_some(), } } @@ -154,12 +155,58 @@ impl GenericParams { def: GenericDefId, ) -> Interned<GenericParams> { let _p = profile::span("generic_params_query"); + + let krate = def.module(db).krate; + let cfg_options = db.crate_graph(); + let cfg_options = &cfg_options[krate].cfg_options; + + // Returns the generic parameters that are enabled under the current `#[cfg]` options + let enabled_params = |params: &Interned<GenericParams>, item_tree: &ItemTree| { + let enabled = |param| item_tree.attrs(db, krate, param).is_cfg_enabled(cfg_options); + + // In the common case, no parameters will by disabled by `#[cfg]` attributes. + // Therefore, make a first pass to check if all parameters are enabled and, if so, + // clone the `Interned<GenericParams>` instead of recreating an identical copy. + let all_type_or_consts_enabled = + params.type_or_consts.iter().all(|(idx, _)| enabled(idx.into())); + let all_lifetimes_enabled = params.lifetimes.iter().all(|(idx, _)| enabled(idx.into())); + + if all_type_or_consts_enabled && all_lifetimes_enabled { + params.clone() + } else { + Interned::new(GenericParams { + type_or_consts: all_type_or_consts_enabled + .then(|| params.type_or_consts.clone()) + .unwrap_or_else(|| { + params + .type_or_consts + .iter() + .filter_map(|(idx, param)| { + enabled(idx.into()).then(|| param.clone()) + }) + .collect() + }), + lifetimes: all_lifetimes_enabled + .then(|| params.lifetimes.clone()) + .unwrap_or_else(|| { + params + .lifetimes + .iter() + .filter_map(|(idx, param)| { + enabled(idx.into()).then(|| param.clone()) + }) + .collect() + }), + where_predicates: params.where_predicates.clone(), + }) + } + }; macro_rules! id_to_generics { ($id:ident) => {{ let id = $id.lookup(db).id; let tree = id.item_tree(db); let item = &tree[id.value]; - item.generic_params.clone() + enabled_params(&item.generic_params, &tree) }}; } @@ -169,7 +216,8 @@ impl GenericParams { let tree = loc.id.item_tree(db); let item = &tree[loc.id.value]; - let mut generic_params = GenericParams::clone(&item.explicit_generic_params); + let enabled_params = enabled_params(&item.explicit_generic_params, &tree); + let mut generic_params = GenericParams::clone(&enabled_params); let module = loc.container.module(db); let func_data = db.function_data(id); @@ -198,9 +246,14 @@ impl GenericParams { } } - pub(crate) fn fill(&mut self, lower_ctx: &LowerCtx<'_>, node: &dyn HasGenericParams) { + pub(crate) fn fill( + &mut self, + lower_ctx: &LowerCtx<'_>, + node: &dyn HasGenericParams, + add_param_attrs: impl FnMut(AttrOwner, ast::GenericParam), + ) { if let Some(params) = node.generic_param_list() { - self.fill_params(lower_ctx, params) + self.fill_params(lower_ctx, params, add_param_attrs) } if let Some(where_clause) = node.where_clause() { self.fill_where_predicates(lower_ctx, where_clause); @@ -218,7 +271,12 @@ impl GenericParams { } } - fn fill_params(&mut self, lower_ctx: &LowerCtx<'_>, params: ast::GenericParamList) { + fn fill_params( + &mut self, + lower_ctx: &LowerCtx<'_>, + params: ast::GenericParamList, + mut add_param_attrs: impl FnMut(AttrOwner, ast::GenericParam), + ) { for type_or_const_param in params.type_or_const_params() { match type_or_const_param { ast::TypeOrConstParam::Type(type_param) => { @@ -232,13 +290,14 @@ impl GenericParams { default, provenance: TypeParamProvenance::TypeParamList, }; - self.type_or_consts.alloc(param.into()); + let idx = self.type_or_consts.alloc(param.into()); let type_ref = TypeRef::Path(name.into()); self.fill_bounds( lower_ctx, type_param.type_bound_list(), Either::Left(type_ref), ); + add_param_attrs(idx.into(), ast::GenericParam::TypeParam(type_param)); } ast::TypeOrConstParam::Const(const_param) => { let name = const_param.name().map_or_else(Name::missing, |it| it.as_name()); @@ -248,9 +307,10 @@ impl GenericParams { let param = ConstParamData { name, ty: Interned::new(ty), - has_default: const_param.default_val().is_some(), + default: ConstRef::from_const_param(lower_ctx, &const_param), }; - self.type_or_consts.alloc(param.into()); + let idx = self.type_or_consts.alloc(param.into()); + add_param_attrs(idx.into(), ast::GenericParam::ConstParam(const_param)); } } } @@ -258,13 +318,14 @@ impl GenericParams { let name = lifetime_param.lifetime().map_or_else(Name::missing, |lt| Name::new_lifetime(<)); let param = LifetimeParamData { name: name.clone() }; - self.lifetimes.alloc(param); + let idx = self.lifetimes.alloc(param); let lifetime_ref = LifetimeRef::new_name(name); self.fill_bounds( lower_ctx, lifetime_param.type_bound_list(), Either::Right(lifetime_ref), ); + add_param_attrs(idx.into(), ast::GenericParam::LifetimeParam(lifetime_param)); } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/hir.rs b/src/tools/rust-analyzer/crates/hir-def/src/hir.rs index 6591c92ac..591ee77c7 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/hir.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/hir.rs @@ -13,6 +13,7 @@ //! See also a neighboring `body` module. pub mod type_ref; +pub mod format_args; use std::fmt; @@ -117,7 +118,6 @@ impl From<ast::LiteralKind> for Literal { fn from(ast_lit_kind: ast::LiteralKind) -> Self { use ast::LiteralKind; match ast_lit_kind { - // FIXME: these should have actual values filled in, but unsure on perf impact LiteralKind::IntNumber(lit) => { if let builtin @ Some(_) = lit.suffix().and_then(BuiltinFloat::from_suffix) { Literal::Float( @@ -281,6 +281,19 @@ pub enum Expr { Array(Array), Literal(Literal), Underscore, + OffsetOf(OffsetOf), + InlineAsm(InlineAsm), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct OffsetOf { + pub container: Interned<TypeRef>, + pub fields: Box<[Name]>, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct InlineAsm { + pub e: ExprId, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -341,7 +354,8 @@ impl Expr { pub fn walk_child_exprs(&self, mut f: impl FnMut(ExprId)) { match self { Expr::Missing => {} - Expr::Path(_) => {} + Expr::Path(_) | Expr::OffsetOf(_) => {} + Expr::InlineAsm(it) => f(it.e), Expr::If { condition, then_branch, else_branch } => { f(*condition); f(*then_branch); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/hir/format_args.rs b/src/tools/rust-analyzer/crates/hir-def/src/hir/format_args.rs new file mode 100644 index 000000000..75025a984 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/hir/format_args.rs @@ -0,0 +1,502 @@ +//! Parses `format_args` input. +use std::mem; + +use hir_expand::name::Name; +use rustc_parse_format as parse; +use syntax::{ + ast::{self, IsString}, + AstToken, SmolStr, TextRange, +}; + +use crate::hir::ExprId; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FormatArgs { + pub template: Box<[FormatArgsPiece]>, + pub arguments: FormatArguments, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FormatArguments { + pub arguments: Box<[FormatArgument]>, + pub num_unnamed_args: usize, + pub num_explicit_args: usize, + pub names: Box<[(Name, usize)]>, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum FormatArgsPiece { + Literal(Box<str>), + Placeholder(FormatPlaceholder), +} + +#[derive(Copy, Debug, Clone, PartialEq, Eq)] +pub struct FormatPlaceholder { + /// Index into [`FormatArgs::arguments`]. + pub argument: FormatArgPosition, + /// The span inside the format string for the full `{…}` placeholder. + pub span: Option<TextRange>, + /// `{}`, `{:?}`, or `{:x}`, etc. + pub format_trait: FormatTrait, + /// `{}` or `{:.5}` or `{:-^20}`, etc. + pub format_options: FormatOptions, +} + +#[derive(Copy, Debug, Clone, PartialEq, Eq)] +pub struct FormatArgPosition { + /// Which argument this position refers to (Ok), + /// or would've referred to if it existed (Err). + pub index: Result<usize, usize>, + /// What kind of position this is. See [`FormatArgPositionKind`]. + pub kind: FormatArgPositionKind, + /// The span of the name or number. + pub span: Option<TextRange>, +} + +#[derive(Copy, Debug, Clone, PartialEq, Eq)] +pub enum FormatArgPositionKind { + /// `{}` or `{:.*}` + Implicit, + /// `{1}` or `{:1$}` or `{:.1$}` + Number, + /// `{a}` or `{:a$}` or `{:.a$}` + Named, +} + +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub enum FormatTrait { + /// `{}` + Display, + /// `{:?}` + Debug, + /// `{:e}` + LowerExp, + /// `{:E}` + UpperExp, + /// `{:o}` + Octal, + /// `{:p}` + Pointer, + /// `{:b}` + Binary, + /// `{:x}` + LowerHex, + /// `{:X}` + UpperHex, +} + +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] +pub struct FormatOptions { + /// The width. E.g. `{:5}` or `{:width$}`. + pub width: Option<FormatCount>, + /// The precision. E.g. `{:.5}` or `{:.precision$}`. + pub precision: Option<FormatCount>, + /// The alignment. E.g. `{:>}` or `{:<}` or `{:^}`. + pub alignment: Option<FormatAlignment>, + /// The fill character. E.g. the `.` in `{:.>10}`. + pub fill: Option<char>, + /// The `+` or `-` flag. + pub sign: Option<FormatSign>, + /// The `#` flag. + pub alternate: bool, + /// The `0` flag. E.g. the `0` in `{:02x}`. + pub zero_pad: bool, + /// The `x` or `X` flag (for `Debug` only). E.g. the `x` in `{:x?}`. + pub debug_hex: Option<FormatDebugHex>, +} +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum FormatSign { + /// The `+` flag. + Plus, + /// The `-` flag. + Minus, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum FormatDebugHex { + /// The `x` flag in `{:x?}`. + Lower, + /// The `X` flag in `{:X?}`. + Upper, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum FormatAlignment { + /// `{:<}` + Left, + /// `{:>}` + Right, + /// `{:^}` + Center, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum FormatCount { + /// `{:5}` or `{:.5}` + Literal(usize), + /// `{:.*}`, `{:.5$}`, or `{:a$}`, etc. + Argument(FormatArgPosition), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FormatArgument { + pub kind: FormatArgumentKind, + pub expr: ExprId, +} + +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum FormatArgumentKind { + /// `format_args(…, arg)` + Normal, + /// `format_args(…, arg = 1)` + Named(Name), + /// `format_args("… {arg} …")` + Captured(Name), +} + +// Only used in parse_args and report_invalid_references, +// to indicate how a referred argument was used. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum PositionUsedAs { + Placeholder(Option<TextRange>), + Precision, + Width, +} +use PositionUsedAs::*; + +pub(crate) fn parse( + s: &ast::String, + fmt_snippet: Option<String>, + mut args: FormatArgumentsCollector, + is_direct_literal: bool, + mut synth: impl FnMut(Name) -> ExprId, +) -> FormatArgs { + let text = s.text(); + let str_style = match s.quote_offsets() { + Some(offsets) => { + let raw = u32::from(offsets.quotes.0.len()) - 1; + (raw != 0).then_some(raw as usize) + } + None => None, + }; + let mut parser = + parse::Parser::new(text, str_style, fmt_snippet, false, parse::ParseMode::Format); + + let mut pieces = Vec::new(); + while let Some(piece) = parser.next() { + if !parser.errors.is_empty() { + break; + } else { + pieces.push(piece); + } + } + let is_source_literal = parser.is_source_literal; + if !parser.errors.is_empty() { + // FIXME: Diagnose + return FormatArgs { template: Default::default(), arguments: args.finish() }; + } + + let to_span = |inner_span: parse::InnerSpan| { + is_source_literal.then(|| { + TextRange::new(inner_span.start.try_into().unwrap(), inner_span.end.try_into().unwrap()) + }) + }; + + let mut used = vec![false; args.explicit_args().len()]; + let mut invalid_refs = Vec::new(); + let mut numeric_refences_to_named_arg = Vec::new(); + + enum ArgRef<'a> { + Index(usize), + Name(&'a str, Option<TextRange>), + } + let mut lookup_arg = |arg: ArgRef<'_>, + span: Option<TextRange>, + used_as: PositionUsedAs, + kind: FormatArgPositionKind| + -> FormatArgPosition { + let index = match arg { + ArgRef::Index(index) => { + if let Some(arg) = args.by_index(index) { + used[index] = true; + if arg.kind.ident().is_some() { + // This was a named argument, but it was used as a positional argument. + numeric_refences_to_named_arg.push((index, span, used_as)); + } + Ok(index) + } else { + // Doesn't exist as an explicit argument. + invalid_refs.push((index, span, used_as, kind)); + Err(index) + } + } + ArgRef::Name(name, _span) => { + let name = Name::new_text_dont_use(SmolStr::new(name)); + if let Some((index, _)) = args.by_name(&name) { + // Name found in `args`, so we resolve it to its index. + if index < args.explicit_args().len() { + // Mark it as used, if it was an explicit argument. + used[index] = true; + } + Ok(index) + } else { + // Name not found in `args`, so we add it as an implicitly captured argument. + if !is_direct_literal { + // For the moment capturing variables from format strings expanded from macros is + // disabled (see RFC #2795) + // FIXME: Diagnose + } + Ok(args.add(FormatArgument { + kind: FormatArgumentKind::Captured(name.clone()), + // FIXME: This is problematic, we might want to synthesize a dummy + // expression proper and/or desugar these. + expr: synth(name), + })) + } + } + }; + FormatArgPosition { index, kind, span } + }; + + let mut template = Vec::new(); + let mut unfinished_literal = String::new(); + let mut placeholder_index = 0; + + for piece in pieces { + match piece { + parse::Piece::String(s) => { + unfinished_literal.push_str(s); + } + parse::Piece::NextArgument(arg) => { + let parse::Argument { position, position_span, format } = *arg; + if !unfinished_literal.is_empty() { + template.push(FormatArgsPiece::Literal( + mem::take(&mut unfinished_literal).into_boxed_str(), + )); + } + + let span = parser.arg_places.get(placeholder_index).and_then(|&s| to_span(s)); + placeholder_index += 1; + + let position_span = to_span(position_span); + let argument = match position { + parse::ArgumentImplicitlyIs(i) => lookup_arg( + ArgRef::Index(i), + position_span, + Placeholder(span), + FormatArgPositionKind::Implicit, + ), + parse::ArgumentIs(i) => lookup_arg( + ArgRef::Index(i), + position_span, + Placeholder(span), + FormatArgPositionKind::Number, + ), + parse::ArgumentNamed(name) => lookup_arg( + ArgRef::Name(name, position_span), + position_span, + Placeholder(span), + FormatArgPositionKind::Named, + ), + }; + + let alignment = match format.align { + parse::AlignUnknown => None, + parse::AlignLeft => Some(FormatAlignment::Left), + parse::AlignRight => Some(FormatAlignment::Right), + parse::AlignCenter => Some(FormatAlignment::Center), + }; + + let format_trait = match format.ty { + "" => FormatTrait::Display, + "?" => FormatTrait::Debug, + "e" => FormatTrait::LowerExp, + "E" => FormatTrait::UpperExp, + "o" => FormatTrait::Octal, + "p" => FormatTrait::Pointer, + "b" => FormatTrait::Binary, + "x" => FormatTrait::LowerHex, + "X" => FormatTrait::UpperHex, + _ => { + // FIXME: Diagnose + FormatTrait::Display + } + }; + + let precision_span = format.precision_span.and_then(to_span); + let precision = match format.precision { + parse::CountIs(n) => Some(FormatCount::Literal(n)), + parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg( + ArgRef::Name(name, to_span(name_span)), + precision_span, + Precision, + FormatArgPositionKind::Named, + ))), + parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg( + ArgRef::Index(i), + precision_span, + Precision, + FormatArgPositionKind::Number, + ))), + parse::CountIsStar(i) => Some(FormatCount::Argument(lookup_arg( + ArgRef::Index(i), + precision_span, + Precision, + FormatArgPositionKind::Implicit, + ))), + parse::CountImplied => None, + }; + + let width_span = format.width_span.and_then(to_span); + let width = match format.width { + parse::CountIs(n) => Some(FormatCount::Literal(n)), + parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg( + ArgRef::Name(name, to_span(name_span)), + width_span, + Width, + FormatArgPositionKind::Named, + ))), + parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg( + ArgRef::Index(i), + width_span, + Width, + FormatArgPositionKind::Number, + ))), + parse::CountIsStar(_) => unreachable!(), + parse::CountImplied => None, + }; + + template.push(FormatArgsPiece::Placeholder(FormatPlaceholder { + argument, + span, + format_trait, + format_options: FormatOptions { + fill: format.fill, + alignment, + sign: format.sign.map(|s| match s { + parse::Sign::Plus => FormatSign::Plus, + parse::Sign::Minus => FormatSign::Minus, + }), + alternate: format.alternate, + zero_pad: format.zero_pad, + debug_hex: format.debug_hex.map(|s| match s { + parse::DebugHex::Lower => FormatDebugHex::Lower, + parse::DebugHex::Upper => FormatDebugHex::Upper, + }), + precision, + width, + }, + })); + } + } + } + + if !unfinished_literal.is_empty() { + template.push(FormatArgsPiece::Literal(unfinished_literal.into_boxed_str())); + } + + if !invalid_refs.is_empty() { + // FIXME: Diagnose + } + + let unused = used + .iter() + .enumerate() + .filter(|&(_, used)| !used) + .map(|(i, _)| { + let named = matches!(args.explicit_args()[i].kind, FormatArgumentKind::Named(_)); + (args.explicit_args()[i].expr, named) + }) + .collect::<Vec<_>>(); + + if !unused.is_empty() { + // FIXME: Diagnose + } + + FormatArgs { template: template.into_boxed_slice(), arguments: args.finish() } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FormatArgumentsCollector { + arguments: Vec<FormatArgument>, + num_unnamed_args: usize, + num_explicit_args: usize, + names: Vec<(Name, usize)>, +} + +impl FormatArgumentsCollector { + pub(crate) fn finish(self) -> FormatArguments { + FormatArguments { + arguments: self.arguments.into_boxed_slice(), + num_unnamed_args: self.num_unnamed_args, + num_explicit_args: self.num_explicit_args, + names: self.names.into_boxed_slice(), + } + } + + pub fn new() -> Self { + Self { arguments: vec![], names: vec![], num_unnamed_args: 0, num_explicit_args: 0 } + } + + pub fn add(&mut self, arg: FormatArgument) -> usize { + let index = self.arguments.len(); + if let Some(name) = arg.kind.ident() { + self.names.push((name.clone(), index)); + } else if self.names.is_empty() { + // Only count the unnamed args before the first named arg. + // (Any later ones are errors.) + self.num_unnamed_args += 1; + } + if !matches!(arg.kind, FormatArgumentKind::Captured(..)) { + // This is an explicit argument. + // Make sure that all arguments so far are explicit. + assert_eq!( + self.num_explicit_args, + self.arguments.len(), + "captured arguments must be added last" + ); + self.num_explicit_args += 1; + } + self.arguments.push(arg); + index + } + + pub fn by_name(&self, name: &Name) -> Option<(usize, &FormatArgument)> { + let &(_, i) = self.names.iter().find(|(n, _)| n == name)?; + Some((i, &self.arguments[i])) + } + + pub fn by_index(&self, i: usize) -> Option<&FormatArgument> { + (i < self.num_explicit_args).then(|| &self.arguments[i]) + } + + pub fn unnamed_args(&self) -> &[FormatArgument] { + &self.arguments[..self.num_unnamed_args] + } + + pub fn named_args(&self) -> &[FormatArgument] { + &self.arguments[self.num_unnamed_args..self.num_explicit_args] + } + + pub fn explicit_args(&self) -> &[FormatArgument] { + &self.arguments[..self.num_explicit_args] + } + + pub fn all_args(&self) -> &[FormatArgument] { + &self.arguments[..] + } + + pub fn all_args_mut(&mut self) -> &mut Vec<FormatArgument> { + &mut self.arguments + } +} + +impl FormatArgumentKind { + pub fn ident(&self) -> Option<&Name> { + match self { + Self::Normal => None, + Self::Named(id) => Some(id), + Self::Captured(id) => Some(id), + } + } +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/hir/type_ref.rs b/src/tools/rust-analyzer/crates/hir-def/src/hir/type_ref.rs index 57f023ef3..75adf21ab 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/hir/type_ref.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/hir/type_ref.rs @@ -393,6 +393,17 @@ impl ConstRef { Self::Scalar(LiteralConstRef::Unknown) } + pub(crate) fn from_const_param( + lower_ctx: &LowerCtx<'_>, + param: &ast::ConstParam, + ) -> Option<Self> { + let default = param.default_val(); + match default { + Some(_) => Some(Self::from_const_arg(lower_ctx, default)), + None => None, + } + } + pub fn display<'a>(&'a self, db: &'a dyn ExpandDatabase) -> impl fmt::Display + 'a { struct Display<'a>(&'a dyn ExpandDatabase, &'a ConstRef); impl fmt::Display for Display<'_> { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/import_map.rs b/src/tools/rust-analyzer/crates/hir-def/src/import_map.rs index 4b2e5041a..44b7f1b4f 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/import_map.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/import_map.rs @@ -11,6 +11,7 @@ use itertools::Itertools; use rustc_hash::{FxHashMap, FxHashSet, FxHasher}; use triomphe::Arc; +use crate::item_scope::ImportOrExternCrate; use crate::{ db::DefDatabase, item_scope::ItemInNs, nameres::DefMap, visibility::Visibility, AssocItemId, ModuleDefId, ModuleId, TraitId, @@ -29,6 +30,10 @@ pub struct ImportInfo { pub container: ModuleId, /// Whether the import is a trait associated item or not. pub is_trait_assoc_item: bool, + /// Whether this item is annotated with `#[doc(hidden)]`. + pub is_doc_hidden: bool, + /// Whether this item is annotated with `#[unstable(..)]`. + pub is_unstable: bool, } /// A map from publicly exported items to its name. @@ -109,23 +114,71 @@ fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> FxIndexMap<ItemIn }); for (name, per_ns) in visible_items { - for item in per_ns.iter_items() { + for (item, import) in per_ns.iter_items() { + let attr_id = if let Some(import) = import { + match import { + ImportOrExternCrate::ExternCrate(id) => Some(id.into()), + ImportOrExternCrate::Import(id) => Some(id.import.into()), + } + } else { + match item { + ItemInNs::Types(id) | ItemInNs::Values(id) => id.try_into().ok(), + ItemInNs::Macros(id) => Some(id.into()), + } + }; + let status @ (is_doc_hidden, is_unstable) = + attr_id.map_or((false, false), |attr_id| { + let attrs = db.attrs(attr_id); + (attrs.has_doc_hidden(), attrs.is_unstable()) + }); + let import_info = ImportInfo { name: name.clone(), container: module, is_trait_assoc_item: false, + is_doc_hidden, + is_unstable, }; match depth_map.entry(item) { - Entry::Vacant(entry) => { - entry.insert(depth); - } + Entry::Vacant(entry) => _ = entry.insert((depth, status)), Entry::Occupied(mut entry) => { - if depth < *entry.get() { - entry.insert(depth); - } else { + let &(occ_depth, (occ_is_doc_hidden, occ_is_unstable)) = entry.get(); + (depth, occ_depth); + let overwrite = match ( + is_doc_hidden, + occ_is_doc_hidden, + is_unstable, + occ_is_unstable, + ) { + // no change of hiddeness or unstableness + (true, true, true, true) + | (true, true, false, false) + | (false, false, true, true) + | (false, false, false, false) => depth < occ_depth, + + // either less hidden or less unstable, accept + (true, true, false, true) + | (false, true, true, true) + | (false, true, false, true) + | (false, true, false, false) + | (false, false, false, true) => true, + // more hidden or unstable, discard + (true, true, true, false) + | (true, false, true, true) + | (true, false, true, false) + | (true, false, false, false) + | (false, false, true, false) => false, + + // exchanges doc(hidden) for unstable (and vice-versa), + (true, false, false, true) | (false, true, true, false) => { + depth < occ_depth + } + }; + if !overwrite { continue; } + entry.insert((depth, status)); } } @@ -150,7 +203,7 @@ fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> FxIndexMap<ItemIn } } } - + map.shrink_to_fit(); map } @@ -162,10 +215,10 @@ fn collect_trait_assoc_items( trait_import_info: &ImportInfo, ) { let _p = profile::span("collect_trait_assoc_items"); - for (assoc_item_name, item) in &db.trait_data(tr).items { + for &(ref assoc_item_name, item) in &db.trait_data(tr).items { let module_def_id = match item { - AssocItemId::FunctionId(f) => ModuleDefId::from(*f), - AssocItemId::ConstId(c) => ModuleDefId::from(*c), + AssocItemId::FunctionId(f) => ModuleDefId::from(f), + AssocItemId::ConstId(c) => ModuleDefId::from(c), // cannot use associated type aliases directly: need a `<Struct as Trait>::TypeAlias` // qualifier, ergo no need to store it for imports in import_map AssocItemId::TypeAliasId(_) => { @@ -179,10 +232,13 @@ fn collect_trait_assoc_items( ItemInNs::Values(module_def_id) }; + let attrs = &db.attrs(item.into()); let assoc_item_info = ImportInfo { container: trait_import_info.container, name: assoc_item_name.clone(), is_trait_assoc_item: true, + is_doc_hidden: attrs.has_doc_hidden(), + is_unstable: attrs.is_unstable(), }; map.insert(assoc_item, assoc_item_info); } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs index 873accafb..7c11fb9d1 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs @@ -6,6 +6,7 @@ use std::collections::hash_map::Entry; use base_db::CrateId; use hir_expand::{attrs::AttrId, db::ExpandDatabase, name::Name, AstId, MacroCallId}; use itertools::Itertools; +use la_arena::Idx; use once_cell::sync::Lazy; use profile::Count; use rustc_hash::{FxHashMap, FxHashSet}; @@ -15,16 +16,10 @@ use syntax::ast; use crate::{ db::DefDatabase, per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, ConstId, - ExternCrateId, HasModule, ImplId, LocalModuleId, MacroId, ModuleDefId, ModuleId, TraitId, - UseId, + ExternCrateId, HasModule, ImplId, LocalModuleId, Lookup, MacroId, ModuleDefId, ModuleId, + TraitId, UseId, }; -#[derive(Copy, Clone, Debug)] -pub(crate) enum ImportType { - Glob, - Named, -} - #[derive(Debug, Default)] pub struct PerNsGlobImports { types: FxHashSet<(LocalModuleId, Name)>, @@ -32,15 +27,50 @@ pub struct PerNsGlobImports { macros: FxHashSet<(LocalModuleId, Name)>, } +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum ImportOrExternCrate { + Import(ImportId), + ExternCrate(ExternCrateId), +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub(crate) enum ImportType { + Import(ImportId), + Glob(UseId), + ExternCrate(ExternCrateId), +} + +impl ImportOrExternCrate { + pub fn into_import(self) -> Option<ImportId> { + match self { + ImportOrExternCrate::Import(it) => Some(it), + _ => None, + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum ImportOrDef { + Import(ImportId), + ExternCrate(ExternCrateId), + Def(ModuleDefId), +} +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] +pub struct ImportId { + pub import: UseId, + pub idx: Idx<ast::UseTree>, +} + #[derive(Debug, Default, PartialEq, Eq)] pub struct ItemScope { _c: Count<Self>, /// Defs visible in this scope. This includes `declarations`, but also - /// imports. - types: FxHashMap<Name, (ModuleDefId, Visibility)>, - values: FxHashMap<Name, (ModuleDefId, Visibility)>, - macros: FxHashMap<Name, (MacroId, Visibility)>, + /// imports. The imports belong to this module and can be resolved by using them on + /// the `use_imports_*` fields. + types: FxHashMap<Name, (ModuleDefId, Visibility, Option<ImportOrExternCrate>)>, + values: FxHashMap<Name, (ModuleDefId, Visibility, Option<ImportId>)>, + macros: FxHashMap<Name, (MacroId, Visibility, Option<ImportId>)>, unresolved: FxHashSet<Name>, /// The defs declared in this scope. Each def has a single scope where it is @@ -50,7 +80,14 @@ pub struct ItemScope { impls: Vec<ImplId>, unnamed_consts: Vec<ConstId>, /// Traits imported via `use Trait as _;`. - unnamed_trait_imports: FxHashMap<TraitId, Visibility>, + unnamed_trait_imports: FxHashMap<TraitId, (Visibility, Option<ImportId>)>, + + // the resolutions of the imports of this scope + use_imports_types: FxHashMap<ImportOrExternCrate, ImportOrDef>, + use_imports_values: FxHashMap<ImportId, ImportOrDef>, + use_imports_macros: FxHashMap<ImportId, ImportOrDef>, + + use_decls: Vec<UseId>, extern_crate_decls: Vec<ExternCrateId>, /// Macros visible in current module in legacy textual scope /// @@ -82,7 +119,7 @@ struct DeriveMacroInvocation { pub(crate) static BUILTIN_SCOPE: Lazy<FxHashMap<Name, PerNs>> = Lazy::new(|| { BuiltinType::ALL .iter() - .map(|(name, ty)| (name.clone(), PerNs::types((*ty).into(), Visibility::Public))) + .map(|(name, ty)| (name.clone(), PerNs::types((*ty).into(), Visibility::Public, None))) .collect() }); @@ -105,11 +142,77 @@ impl ItemScope { .chain(self.values.keys()) .chain(self.macros.keys()) .chain(self.unresolved.iter()) - .sorted() .unique() + .sorted() .map(move |name| (name, self.get(name))) } + pub fn imports(&self) -> impl Iterator<Item = ImportId> + '_ { + self.use_imports_types + .keys() + .copied() + .filter_map(ImportOrExternCrate::into_import) + .chain(self.use_imports_values.keys().copied()) + .chain(self.use_imports_macros.keys().copied()) + .unique() + .sorted() + } + + pub fn fully_resolve_import(&self, db: &dyn DefDatabase, mut import: ImportId) -> PerNs { + let mut res = PerNs::none(); + + let mut def_map; + let mut scope = self; + while let Some(&m) = scope.use_imports_macros.get(&import) { + match m { + ImportOrDef::Import(i) => { + let module_id = i.import.lookup(db).container; + def_map = module_id.def_map(db); + scope = &def_map[module_id.local_id].scope; + import = i; + } + ImportOrDef::Def(ModuleDefId::MacroId(def)) => { + res.macros = Some((def, Visibility::Public, None)); + break; + } + _ => break, + } + } + let mut scope = self; + while let Some(&m) = scope.use_imports_types.get(&ImportOrExternCrate::Import(import)) { + match m { + ImportOrDef::Import(i) => { + let module_id = i.import.lookup(db).container; + def_map = module_id.def_map(db); + scope = &def_map[module_id.local_id].scope; + import = i; + } + ImportOrDef::Def(def) => { + res.types = Some((def, Visibility::Public, None)); + break; + } + _ => break, + } + } + let mut scope = self; + while let Some(&m) = scope.use_imports_values.get(&import) { + match m { + ImportOrDef::Import(i) => { + let module_id = i.import.lookup(db).container; + def_map = module_id.def_map(db); + scope = &def_map[module_id.local_id].scope; + import = i; + } + ImportOrDef::Def(def) => { + res.values = Some((def, Visibility::Public, None)); + break; + } + _ => break, + } + } + res + } + pub fn declarations(&self) -> impl Iterator<Item = ModuleDefId> + '_ { self.declarations.iter().copied() } @@ -121,8 +224,7 @@ impl ItemScope { } pub fn use_decls(&self) -> impl Iterator<Item = UseId> + ExactSizeIterator + '_ { - // FIXME: to be implemented - std::iter::empty() + self.use_decls.iter().copied() } pub fn impls(&self) -> impl Iterator<Item = ImplId> + ExactSizeIterator + '_ { @@ -132,13 +234,13 @@ impl ItemScope { pub fn values( &self, ) -> impl Iterator<Item = (ModuleDefId, Visibility)> + ExactSizeIterator + '_ { - self.values.values().copied() + self.values.values().copied().map(|(a, b, _)| (a, b)) } - pub fn types( + pub(crate) fn types( &self, ) -> impl Iterator<Item = (ModuleDefId, Visibility)> + ExactSizeIterator + '_ { - self.types.values().copied() + self.types.values().copied().map(|(def, vis, _)| (def, vis)) } pub fn unnamed_consts(&self) -> impl Iterator<Item = ConstId> + '_ { @@ -165,33 +267,55 @@ impl ItemScope { } pub(crate) fn type_(&self, name: &Name) -> Option<(ModuleDefId, Visibility)> { - self.types.get(name).copied() + self.types.get(name).copied().map(|(a, b, _)| (a, b)) } /// XXX: this is O(N) rather than O(1), try to not introduce new usages. pub(crate) fn name_of(&self, item: ItemInNs) -> Option<(&Name, Visibility)> { - let (def, mut iter) = match item { - ItemInNs::Macros(def) => { - return self.macros.iter().find_map(|(name, &(other_def, vis))| { - (other_def == def).then_some((name, vis)) - }); - } - ItemInNs::Types(def) => (def, self.types.iter()), - ItemInNs::Values(def) => (def, self.values.iter()), - }; - iter.find_map(|(name, &(other_def, vis))| (other_def == def).then_some((name, vis))) + match item { + ItemInNs::Macros(def) => self + .macros + .iter() + .find_map(|(name, &(other_def, vis, _))| (other_def == def).then_some((name, vis))), + ItemInNs::Types(def) => self + .types + .iter() + .find_map(|(name, &(other_def, vis, _))| (other_def == def).then_some((name, vis))), + + ItemInNs::Values(def) => self + .values + .iter() + .find_map(|(name, &(other_def, vis, _))| (other_def == def).then_some((name, vis))), + } } pub(crate) fn traits(&self) -> impl Iterator<Item = TraitId> + '_ { self.types .values() - .filter_map(|&(def, _)| match def { + .filter_map(|&(def, _, _)| match def { ModuleDefId::TraitId(t) => Some(t), _ => None, }) .chain(self.unnamed_trait_imports.keys().copied()) } + pub(crate) fn resolutions(&self) -> impl Iterator<Item = (Option<Name>, PerNs)> + '_ { + self.entries().map(|(name, res)| (Some(name.clone()), res)).chain( + self.unnamed_trait_imports.iter().map(|(tr, (vis, i))| { + ( + None, + PerNs::types( + ModuleDefId::TraitId(*tr), + *vis, + i.map(ImportOrExternCrate::Import), + ), + ) + }), + ) + } +} + +impl ItemScope { pub(crate) fn declare(&mut self, def: ModuleDefId) { self.declarations.push(def) } @@ -277,12 +401,14 @@ impl ItemScope { }) } + // FIXME: This is only used in collection, we should move the relevant parts of it out of ItemScope pub(crate) fn unnamed_trait_vis(&self, tr: TraitId) -> Option<Visibility> { - self.unnamed_trait_imports.get(&tr).copied() + self.unnamed_trait_imports.get(&tr).copied().map(|(a, _)| a) } pub(crate) fn push_unnamed_trait(&mut self, tr: TraitId, vis: Visibility) { - self.unnamed_trait_imports.insert(tr, vis); + // FIXME: import + self.unnamed_trait_imports.insert(tr, (vis, None)); } pub(crate) fn push_res_with_import( @@ -290,51 +416,187 @@ impl ItemScope { glob_imports: &mut PerNsGlobImports, lookup: (LocalModuleId, Name), def: PerNs, - def_import_type: ImportType, + import: Option<ImportType>, ) -> bool { let mut changed = false; - macro_rules! check_changed { - ( - $changed:ident, - ( $this:ident / $def:ident ) . $field:ident, - $glob_imports:ident [ $lookup:ident ], - $def_import_type:ident - ) => {{ - if let Some(fld) = $def.$field { - let existing = $this.$field.entry($lookup.1.clone()); - match existing { - Entry::Vacant(entry) => { - match $def_import_type { - ImportType::Glob => { - $glob_imports.$field.insert($lookup.clone()); + // FIXME: Document and simplify this + + if let Some(mut fld) = def.types { + let existing = self.types.entry(lookup.1.clone()); + match existing { + Entry::Vacant(entry) => { + match import { + Some(ImportType::Glob(_)) => { + glob_imports.types.insert(lookup.clone()); + } + _ => _ = glob_imports.types.remove(&lookup), + } + let import = match import { + Some(ImportType::ExternCrate(extern_crate)) => { + Some(ImportOrExternCrate::ExternCrate(extern_crate)) + } + Some(ImportType::Import(import)) => { + Some(ImportOrExternCrate::Import(import)) + } + None | Some(ImportType::Glob(_)) => None, + }; + let prev = std::mem::replace(&mut fld.2, import); + if let Some(import) = import { + self.use_imports_types.insert( + import, + match prev { + Some(ImportOrExternCrate::Import(import)) => { + ImportOrDef::Import(import) } - ImportType::Named => { - $glob_imports.$field.remove(&$lookup); + Some(ImportOrExternCrate::ExternCrate(import)) => { + ImportOrDef::ExternCrate(import) } + None => ImportOrDef::Def(fld.0), + }, + ); + } + entry.insert(fld); + changed = true; + } + Entry::Occupied(mut entry) if !matches!(import, Some(ImportType::Glob(..))) => { + if glob_imports.types.remove(&lookup) { + let import = match import { + Some(ImportType::ExternCrate(extern_crate)) => { + Some(ImportOrExternCrate::ExternCrate(extern_crate)) + } + Some(ImportType::Import(import)) => { + Some(ImportOrExternCrate::Import(import)) } + None | Some(ImportType::Glob(_)) => None, + }; + let prev = std::mem::replace(&mut fld.2, import); + if let Some(import) = import { + self.use_imports_types.insert( + import, + match prev { + Some(ImportOrExternCrate::Import(import)) => { + ImportOrDef::Import(import) + } + Some(ImportOrExternCrate::ExternCrate(import)) => { + ImportOrDef::ExternCrate(import) + } + None => ImportOrDef::Def(fld.0), + }, + ); + } + cov_mark::hit!(import_shadowed); + entry.insert(fld); + changed = true; + } + } + _ => {} + } + } - entry.insert(fld); - $changed = true; + if let Some(mut fld) = def.values { + let existing = self.values.entry(lookup.1.clone()); + match existing { + Entry::Vacant(entry) => { + match import { + Some(ImportType::Glob(_)) => { + glob_imports.values.insert(lookup.clone()); } - Entry::Occupied(mut entry) - if matches!($def_import_type, ImportType::Named) => - { - if $glob_imports.$field.remove(&$lookup) { - cov_mark::hit!(import_shadowed); - entry.insert(fld); - $changed = true; - } + _ => _ = glob_imports.values.remove(&lookup), + } + let import = match import { + Some(ImportType::Import(import)) => Some(import), + _ => None, + }; + let prev = std::mem::replace(&mut fld.2, import); + if let Some(import) = import { + self.use_imports_values.insert( + import, + match prev { + Some(import) => ImportOrDef::Import(import), + None => ImportOrDef::Def(fld.0), + }, + ); + } + entry.insert(fld); + changed = true; + } + Entry::Occupied(mut entry) if !matches!(import, Some(ImportType::Glob(..))) => { + if glob_imports.values.remove(&lookup) { + cov_mark::hit!(import_shadowed); + let import = match import { + Some(ImportType::Import(import)) => Some(import), + _ => None, + }; + let prev = std::mem::replace(&mut fld.2, import); + if let Some(import) = import { + self.use_imports_values.insert( + import, + match prev { + Some(import) => ImportOrDef::Import(import), + None => ImportOrDef::Def(fld.0), + }, + ); } - _ => {} + entry.insert(fld); + changed = true; } } - }}; + _ => {} + } } - check_changed!(changed, (self / def).types, glob_imports[lookup], def_import_type); - check_changed!(changed, (self / def).values, glob_imports[lookup], def_import_type); - check_changed!(changed, (self / def).macros, glob_imports[lookup], def_import_type); + if let Some(mut fld) = def.macros { + let existing = self.macros.entry(lookup.1.clone()); + match existing { + Entry::Vacant(entry) => { + match import { + Some(ImportType::Glob(_)) => { + glob_imports.macros.insert(lookup.clone()); + } + _ => _ = glob_imports.macros.remove(&lookup), + } + let import = match import { + Some(ImportType::Import(import)) => Some(import), + _ => None, + }; + let prev = std::mem::replace(&mut fld.2, import); + if let Some(import) = import { + self.use_imports_macros.insert( + import, + match prev { + Some(import) => ImportOrDef::Import(import), + None => ImportOrDef::Def(fld.0.into()), + }, + ); + } + entry.insert(fld); + changed = true; + } + Entry::Occupied(mut entry) if !matches!(import, Some(ImportType::Glob(..))) => { + if glob_imports.macros.remove(&lookup) { + cov_mark::hit!(import_shadowed); + let import = match import { + Some(ImportType::Import(import)) => Some(import), + _ => None, + }; + let prev = std::mem::replace(&mut fld.2, import); + if let Some(import) = import { + self.use_imports_macros.insert( + import, + match prev { + Some(import) => ImportOrDef::Import(import), + None => ImportOrDef::Def(fld.0.into()), + }, + ); + } + entry.insert(fld); + changed = true; + } + } + _ => {} + } + } if def.is_none() && self.unresolved.insert(lookup.1) { changed = true; @@ -343,27 +605,18 @@ impl ItemScope { changed } - pub(crate) fn resolutions(&self) -> impl Iterator<Item = (Option<Name>, PerNs)> + '_ { - self.entries().map(|(name, res)| (Some(name.clone()), res)).chain( - self.unnamed_trait_imports - .iter() - .map(|(tr, vis)| (None, PerNs::types(ModuleDefId::TraitId(*tr), *vis))), - ) - } - /// Marks everything that is not a procedural macro as private to `this_module`. pub(crate) fn censor_non_proc_macros(&mut self, this_module: ModuleId) { self.types .values_mut() - .chain(self.values.values_mut()) + .map(|(def, vis, _)| (def, vis)) + .chain(self.values.values_mut().map(|(def, vis, _)| (def, vis))) .map(|(_, v)| v) - .chain(self.unnamed_trait_imports.values_mut()) + .chain(self.unnamed_trait_imports.values_mut().map(|(vis, _)| vis)) .for_each(|vis| *vis = Visibility::Module(this_module)); - for (mac, vis) in self.macros.values_mut() { - if let MacroId::ProcMacroId(_) = mac { - // FIXME: Technically this is insufficient since reexports of proc macros are also - // forbidden. Practically nobody does that. + for (mac, vis, import) in self.macros.values_mut() { + if matches!(mac, MacroId::ProcMacroId(_) if import.is_none()) { continue; } @@ -382,14 +635,25 @@ impl ItemScope { name.map_or("_".to_string(), |name| name.display(db).to_string()) ); - if def.types.is_some() { + if let Some((.., i)) = def.types { buf.push_str(" t"); + match i { + Some(ImportOrExternCrate::Import(_)) => buf.push('i'), + Some(ImportOrExternCrate::ExternCrate(_)) => buf.push('e'), + None => (), + } } - if def.values.is_some() { + if let Some((.., i)) = def.values { buf.push_str(" v"); + if i.is_some() { + buf.push('i'); + } } - if def.macros.is_some() { + if let Some((.., i)) = def.macros { buf.push_str(" m"); + if i.is_some() { + buf.push('i'); + } } if def.is_none() { buf.push_str(" _"); @@ -415,10 +679,17 @@ impl ItemScope { attr_macros, derive_macros, extern_crate_decls, + use_decls, + use_imports_values, + use_imports_types, + use_imports_macros, } = self; types.shrink_to_fit(); values.shrink_to_fit(); macros.shrink_to_fit(); + use_imports_types.shrink_to_fit(); + use_imports_values.shrink_to_fit(); + use_imports_macros.shrink_to_fit(); unresolved.shrink_to_fit(); declarations.shrink_to_fit(); impls.shrink_to_fit(); @@ -428,32 +699,44 @@ impl ItemScope { attr_macros.shrink_to_fit(); derive_macros.shrink_to_fit(); extern_crate_decls.shrink_to_fit(); + use_decls.shrink_to_fit(); } } impl PerNs { - pub(crate) fn from_def(def: ModuleDefId, v: Visibility, has_constructor: bool) -> PerNs { + pub(crate) fn from_def( + def: ModuleDefId, + v: Visibility, + has_constructor: bool, + import: Option<ImportOrExternCrate>, + ) -> PerNs { match def { - ModuleDefId::ModuleId(_) => PerNs::types(def, v), - ModuleDefId::FunctionId(_) => PerNs::values(def, v), + ModuleDefId::ModuleId(_) => PerNs::types(def, v, import), + ModuleDefId::FunctionId(_) => { + PerNs::values(def, v, import.and_then(ImportOrExternCrate::into_import)) + } ModuleDefId::AdtId(adt) => match adt { - AdtId::UnionId(_) => PerNs::types(def, v), - AdtId::EnumId(_) => PerNs::types(def, v), + AdtId::UnionId(_) => PerNs::types(def, v, import), + AdtId::EnumId(_) => PerNs::types(def, v, import), AdtId::StructId(_) => { if has_constructor { - PerNs::both(def, def, v) + PerNs::both(def, def, v, import) } else { - PerNs::types(def, v) + PerNs::types(def, v, import) } } }, - ModuleDefId::EnumVariantId(_) => PerNs::both(def, def, v), - ModuleDefId::ConstId(_) | ModuleDefId::StaticId(_) => PerNs::values(def, v), - ModuleDefId::TraitId(_) => PerNs::types(def, v), - ModuleDefId::TraitAliasId(_) => PerNs::types(def, v), - ModuleDefId::TypeAliasId(_) => PerNs::types(def, v), - ModuleDefId::BuiltinType(_) => PerNs::types(def, v), - ModuleDefId::MacroId(mac) => PerNs::macros(mac, v), + ModuleDefId::EnumVariantId(_) => PerNs::both(def, def, v, import), + ModuleDefId::ConstId(_) | ModuleDefId::StaticId(_) => { + PerNs::values(def, v, import.and_then(ImportOrExternCrate::into_import)) + } + ModuleDefId::TraitId(_) => PerNs::types(def, v, import), + ModuleDefId::TraitAliasId(_) => PerNs::types(def, v, import), + ModuleDefId::TypeAliasId(_) => PerNs::types(def, v, import), + ModuleDefId::BuiltinType(_) => PerNs::types(def, v, import), + ModuleDefId::MacroId(mac) => { + PerNs::macros(mac, v, import.and_then(ImportOrExternCrate::into_import)) + } } } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs index c9b0f75f1..4c812b62a 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs @@ -64,11 +64,11 @@ use triomphe::Arc; use crate::{ attr::Attrs, db::DefDatabase, - generics::GenericParams, + generics::{GenericParams, LifetimeParamData, TypeOrConstParamData}, path::{path, AssociatedTypeBinding, GenericArgs, ImportAlias, ModPath, Path, PathKind}, type_ref::{Mutability, TraitRef, TypeBound, TypeRef}, visibility::RawVisibility, - BlockId, + BlockId, Lookup, }; #[derive(Copy, Clone, Eq, PartialEq)] @@ -143,6 +143,16 @@ impl ItemTree { Arc::new(item_tree) } + pub(crate) fn block_item_tree_query(db: &dyn DefDatabase, block: BlockId) -> Arc<ItemTree> { + let loc = block.lookup(db); + let block = loc.ast_id.to_node(db.upcast()); + + let ctx = lower::Ctx::new(db, loc.ast_id.file_id); + let mut item_tree = ctx.lower_block(&block); + item_tree.shrink_to_fit(); + Arc::new(item_tree) + } + /// Returns an iterator over all items located at the top level of the `HirFileId` this /// `ItemTree` was created from. pub fn top_level_items(&self) -> &[ModItem] { @@ -167,7 +177,7 @@ impl ItemTree { } pub fn pretty_print(&self, db: &dyn DefDatabase) -> String { - pretty::print_item_tree(db.upcast(), self) + pretty::print_item_tree(db, self) } fn data(&self) -> &ItemTreeData { @@ -178,13 +188,6 @@ impl ItemTree { self.data.get_or_insert_with(Box::default) } - fn block_item_tree(db: &dyn DefDatabase, block: BlockId) -> Arc<ItemTree> { - let loc = db.lookup_intern_block(block); - let block = loc.ast_id.to_node(db.upcast()); - let ctx = lower::Ctx::new(db, loc.ast_id.file_id); - Arc::new(ctx.lower_block(&block)) - } - fn shrink_to_fit(&mut self) { if let Some(data) = &mut self.data { let ItemTreeData { @@ -296,10 +299,12 @@ pub enum AttrOwner { Variant(Idx<Variant>), Field(Idx<Field>), Param(Idx<Param>), + TypeOrConstParamData(Idx<TypeOrConstParamData>), + LifetimeParamData(Idx<LifetimeParamData>), } macro_rules! from_attrs { - ( $( $var:ident($t:ty) ),+ ) => { + ( $( $var:ident($t:ty) ),+ $(,)? ) => { $( impl From<$t> for AttrOwner { fn from(t: $t) -> AttrOwner { @@ -310,7 +315,14 @@ macro_rules! from_attrs { }; } -from_attrs!(ModItem(ModItem), Variant(Idx<Variant>), Field(Idx<Field>), Param(Idx<Param>)); +from_attrs!( + ModItem(ModItem), + Variant(Idx<Variant>), + Field(Idx<Field>), + Param(Idx<Param>), + TypeOrConstParamData(Idx<TypeOrConstParamData>), + LifetimeParamData(Idx<LifetimeParamData>), +); /// Trait implemented by all item nodes in the item tree. pub trait ItemTreeNode: Clone { @@ -373,7 +385,7 @@ impl TreeId { pub(crate) fn item_tree(&self, db: &dyn DefDatabase) -> Arc<ItemTree> { match self.block { - Some(block) => ItemTree::block_item_tree(db, block), + Some(block) => db.block_item_tree_query(block), None => db.file_item_tree(self.file), } } @@ -761,6 +773,19 @@ impl Use { lower::lower_use_tree(db, &hygiene, ast_use_tree).expect("failed to lower use tree"); source_map[index].clone() } + /// Maps a `UseTree` contained in this import back to its AST node. + pub fn use_tree_source_map( + &self, + db: &dyn DefDatabase, + file_id: HirFileId, + ) -> Arena<ast::UseTree> { + // Re-lower the AST item and get the source map. + // Note: The AST unwraps are fine, since if they fail we should have never obtained `index`. + let ast = InFile::new(file_id, self.ast_id).to_node(db.upcast()); + let ast_use_tree = ast.use_tree().expect("missing `use_tree`"); + let hygiene = Hygiene::new(db.upcast(), file_id); + lower::lower_use_tree(db, &hygiene, ast_use_tree).expect("failed to lower use tree").1 + } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -785,7 +810,7 @@ impl UseTree { fn expand_impl( &self, prefix: Option<ModPath>, - cb: &mut dyn FnMut(Idx<ast::UseTree>, ModPath, ImportKind, Option<ImportAlias>), + cb: &mut impl FnMut(Idx<ast::UseTree>, ModPath, ImportKind, Option<ImportAlias>), ) { fn concat_mod_paths( prefix: Option<ModPath>, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs index 7b898e62d..e4702c113 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs @@ -77,6 +77,9 @@ impl<'a> Ctx<'a> { } pub(super) fn lower_block(mut self, block: &ast::BlockExpr) -> ItemTree { + self.tree + .attrs + .insert(AttrOwner::TopLevel, RawAttrs::new(self.db.upcast(), block, self.hygiene())); self.tree.top_level = block .statements() .filter_map(|stmt| match stmt { @@ -602,7 +605,21 @@ impl<'a> Ctx<'a> { generics.fill_bounds(&self.body_ctx, bounds, Either::Left(self_param)); } - generics.fill(&self.body_ctx, node); + let add_param_attrs = |item, param| { + let attrs = RawAttrs::new(self.db.upcast(), ¶m, self.body_ctx.hygiene()); + // This is identical to the body of `Ctx::add_attrs()` but we can't call that here + // because it requires `&mut self` and the call to `generics.fill()` below also + // references `self`. + match self.tree.attrs.entry(item) { + Entry::Occupied(mut entry) => { + *entry.get_mut() = entry.get().merge(attrs); + } + Entry::Vacant(entry) => { + entry.insert(attrs); + } + } + }; + generics.fill(&self.body_ctx, node, add_param_attrs); generics.shrink_to_fit(); Interned::new(generics) @@ -763,7 +780,7 @@ impl UseTreeLowering<'_> { } } -pub(super) fn lower_use_tree( +pub(crate) fn lower_use_tree( db: &dyn DefDatabase, hygiene: &Hygiene, tree: ast::UseTree, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/pretty.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/pretty.rs index da30830fe..417bd37c8 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/pretty.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/pretty.rs @@ -2,8 +2,6 @@ use std::fmt::{self, Write}; -use hir_expand::db::ExpandDatabase; - use crate::{ generics::{TypeOrConstParamData, WherePredicate, WherePredicateTypeTarget}, pretty::{print_path, print_type_bounds, print_type_ref}, @@ -12,11 +10,11 @@ use crate::{ use super::*; -pub(super) fn print_item_tree(db: &dyn ExpandDatabase, tree: &ItemTree) -> String { +pub(super) fn print_item_tree(db: &dyn DefDatabase, tree: &ItemTree) -> String { let mut p = Printer { db, tree, buf: String::new(), indent_level: 0, needs_indent: true }; if let Some(attrs) = tree.attrs.get(&AttrOwner::TopLevel) { - p.print_attrs(attrs, true); + p.print_attrs(attrs, true, "\n"); } p.blank(); @@ -45,7 +43,7 @@ macro_rules! wln { } struct Printer<'a> { - db: &'a dyn ExpandDatabase, + db: &'a dyn DefDatabase, tree: &'a ItemTree, buf: String, indent_level: usize, @@ -84,28 +82,29 @@ impl Printer<'_> { } } - fn print_attrs(&mut self, attrs: &RawAttrs, inner: bool) { + fn print_attrs(&mut self, attrs: &RawAttrs, inner: bool, separated_by: &str) { let inner = if inner { "!" } else { "" }; for attr in &**attrs { - wln!( + w!( self, - "#{}[{}{}]", + "#{}[{}{}]{}", inner, - attr.path.display(self.db), + attr.path.display(self.db.upcast()), attr.input.as_ref().map(|it| it.to_string()).unwrap_or_default(), + separated_by, ); } } - fn print_attrs_of(&mut self, of: impl Into<AttrOwner>) { + fn print_attrs_of(&mut self, of: impl Into<AttrOwner>, separated_by: &str) { if let Some(attrs) = self.tree.attrs.get(&of.into()) { - self.print_attrs(attrs, false); + self.print_attrs(attrs, false, separated_by); } } fn print_visibility(&mut self, vis: RawVisibilityId) { match &self.tree[vis] { - RawVisibility::Module(path) => w!(self, "pub({}) ", path.display(self.db)), + RawVisibility::Module(path) => w!(self, "pub({}) ", path.display(self.db.upcast())), RawVisibility::Public => w!(self, "pub "), }; } @@ -118,9 +117,9 @@ impl Printer<'_> { self.indented(|this| { for field in fields.clone() { let Field { visibility, name, type_ref, ast_id: _ } = &this.tree[field]; - this.print_attrs_of(field); + this.print_attrs_of(field, "\n"); this.print_visibility(*visibility); - w!(this, "{}: ", name.display(self.db)); + w!(this, "{}: ", name.display(self.db.upcast())); this.print_type_ref(type_ref); wln!(this, ","); } @@ -132,9 +131,9 @@ impl Printer<'_> { self.indented(|this| { for field in fields.clone() { let Field { visibility, name, type_ref, ast_id: _ } = &this.tree[field]; - this.print_attrs_of(field); + this.print_attrs_of(field, "\n"); this.print_visibility(*visibility); - w!(this, "{}: ", name.display(self.db)); + w!(this, "{}: ", name.display(self.db.upcast())); this.print_type_ref(type_ref); wln!(this, ","); } @@ -167,20 +166,20 @@ impl Printer<'_> { fn print_use_tree(&mut self, use_tree: &UseTree) { match &use_tree.kind { UseTreeKind::Single { path, alias } => { - w!(self, "{}", path.display(self.db)); + w!(self, "{}", path.display(self.db.upcast())); if let Some(alias) = alias { w!(self, " as {}", alias); } } UseTreeKind::Glob { path } => { if let Some(path) = path { - w!(self, "{}::", path.display(self.db)); + w!(self, "{}::", path.display(self.db.upcast())); } w!(self, "*"); } UseTreeKind::Prefixed { prefix, list } => { if let Some(prefix) = prefix { - w!(self, "{}::", prefix.display(self.db)); + w!(self, "{}::", prefix.display(self.db.upcast())); } w!(self, "{{"); for (i, tree) in list.iter().enumerate() { @@ -195,7 +194,7 @@ impl Printer<'_> { } fn print_mod_item(&mut self, item: ModItem) { - self.print_attrs_of(item); + self.print_attrs_of(item, "\n"); match item { ModItem::Use(it) => { @@ -208,7 +207,7 @@ impl Printer<'_> { ModItem::ExternCrate(it) => { let ExternCrate { name, alias, visibility, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - w!(self, "extern crate {}", name.display(self.db)); + w!(self, "extern crate {}", name.display(self.db.upcast())); if let Some(alias) = alias { w!(self, " as {}", alias); } @@ -255,13 +254,13 @@ impl Printer<'_> { if let Some(abi) = abi { w!(self, "extern \"{}\" ", abi); } - w!(self, "fn {}", name.display(self.db)); + w!(self, "fn {}", name.display(self.db.upcast())); self.print_generic_params(explicit_generic_params); w!(self, "("); if !params.is_empty() { self.indented(|this| { for param in params.clone() { - this.print_attrs_of(param); + this.print_attrs_of(param, "\n"); match &this.tree[param] { Param::Normal(ty) => { if flags.contains(FnFlags::HAS_SELF_PARAM) { @@ -289,7 +288,7 @@ impl Printer<'_> { ModItem::Struct(it) => { let Struct { visibility, name, fields, generic_params, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - w!(self, "struct {}", name.display(self.db)); + w!(self, "struct {}", name.display(self.db.upcast())); self.print_generic_params(generic_params); self.print_fields_and_where_clause(fields, generic_params); if matches!(fields, Fields::Record(_)) { @@ -301,7 +300,7 @@ impl Printer<'_> { ModItem::Union(it) => { let Union { name, visibility, fields, generic_params, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - w!(self, "union {}", name.display(self.db)); + w!(self, "union {}", name.display(self.db.upcast())); self.print_generic_params(generic_params); self.print_fields_and_where_clause(fields, generic_params); if matches!(fields, Fields::Record(_)) { @@ -313,14 +312,14 @@ impl Printer<'_> { ModItem::Enum(it) => { let Enum { name, visibility, variants, generic_params, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - w!(self, "enum {}", name.display(self.db)); + w!(self, "enum {}", name.display(self.db.upcast())); self.print_generic_params(generic_params); self.print_where_clause_and_opening_brace(generic_params); self.indented(|this| { for variant in variants.clone() { let Variant { name, fields, ast_id: _ } = &this.tree[variant]; - this.print_attrs_of(variant); - w!(this, "{}", name.display(self.db)); + this.print_attrs_of(variant, "\n"); + w!(this, "{}", name.display(self.db.upcast())); this.print_fields(fields); wln!(this, ","); } @@ -332,7 +331,7 @@ impl Printer<'_> { self.print_visibility(*visibility); w!(self, "const "); match name { - Some(name) => w!(self, "{}", name.display(self.db)), + Some(name) => w!(self, "{}", name.display(self.db.upcast())), None => w!(self, "_"), } w!(self, ": "); @@ -346,7 +345,7 @@ impl Printer<'_> { if *mutable { w!(self, "mut "); } - w!(self, "{}: ", name.display(self.db)); + w!(self, "{}: ", name.display(self.db.upcast())); self.print_type_ref(type_ref); w!(self, " = _;"); wln!(self); @@ -368,7 +367,7 @@ impl Printer<'_> { if *is_auto { w!(self, "auto "); } - w!(self, "trait {}", name.display(self.db)); + w!(self, "trait {}", name.display(self.db.upcast())); self.print_generic_params(generic_params); self.print_where_clause_and_opening_brace(generic_params); self.indented(|this| { @@ -381,7 +380,7 @@ impl Printer<'_> { ModItem::TraitAlias(it) => { let TraitAlias { name, visibility, generic_params, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - w!(self, "trait {}", name.display(self.db)); + w!(self, "trait {}", name.display(self.db.upcast())); self.print_generic_params(generic_params); w!(self, " = "); self.print_where_clause(generic_params); @@ -414,7 +413,7 @@ impl Printer<'_> { let TypeAlias { name, visibility, bounds, type_ref, generic_params, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - w!(self, "type {}", name.display(self.db)); + w!(self, "type {}", name.display(self.db.upcast())); self.print_generic_params(generic_params); if !bounds.is_empty() { w!(self, ": "); @@ -431,7 +430,7 @@ impl Printer<'_> { ModItem::Mod(it) => { let Mod { name, visibility, kind, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - w!(self, "mod {}", name.display(self.db)); + w!(self, "mod {}", name.display(self.db.upcast())); match kind { ModKind::Inline { items } => { w!(self, " {{"); @@ -449,16 +448,16 @@ impl Printer<'_> { } ModItem::MacroCall(it) => { let MacroCall { path, ast_id: _, expand_to: _ } = &self.tree[it]; - wln!(self, "{}!(...);", path.display(self.db)); + wln!(self, "{}!(...);", path.display(self.db.upcast())); } ModItem::MacroRules(it) => { let MacroRules { name, ast_id: _ } = &self.tree[it]; - wln!(self, "macro_rules! {} {{ ... }}", name.display(self.db)); + wln!(self, "macro_rules! {} {{ ... }}", name.display(self.db.upcast())); } ModItem::MacroDef(it) => { let MacroDef { name, visibility, ast_id: _ } = &self.tree[it]; self.print_visibility(*visibility); - wln!(self, "macro {} {{ ... }}", name.display(self.db)); + wln!(self, "macro {} {{ ... }}", name.display(self.db.upcast())); } } @@ -484,25 +483,27 @@ impl Printer<'_> { w!(self, "<"); let mut first = true; - for (_, lt) in params.lifetimes.iter() { + for (idx, lt) in params.lifetimes.iter() { if !first { w!(self, ", "); } first = false; - w!(self, "{}", lt.name.display(self.db)); + self.print_attrs_of(idx, " "); + w!(self, "{}", lt.name.display(self.db.upcast())); } for (idx, x) in params.type_or_consts.iter() { if !first { w!(self, ", "); } first = false; + self.print_attrs_of(idx, " "); match x { TypeOrConstParamData::TypeParamData(ty) => match &ty.name { - Some(name) => w!(self, "{}", name.display(self.db)), + Some(name) => w!(self, "{}", name.display(self.db.upcast())), None => w!(self, "_anon_{}", idx.into_raw()), }, TypeOrConstParamData::ConstParamData(konst) => { - w!(self, "const {}: ", konst.name.display(self.db)); + w!(self, "const {}: ", konst.name.display(self.db.upcast())); self.print_type_ref(&konst.ty); } } @@ -537,8 +538,8 @@ impl Printer<'_> { wln!( this, "{}: {},", - target.name.display(self.db), - bound.name.display(self.db) + target.name.display(self.db.upcast()), + bound.name.display(self.db.upcast()) ); continue; } @@ -548,7 +549,7 @@ impl Printer<'_> { if i != 0 { w!(this, ", "); } - w!(this, "{}", lt.display(self.db)); + w!(this, "{}", lt.display(self.db.upcast())); } w!(this, "> "); (target, bound) @@ -559,7 +560,7 @@ impl Printer<'_> { WherePredicateTypeTarget::TypeRef(ty) => this.print_type_ref(ty), WherePredicateTypeTarget::TypeOrConstParam(id) => { match ¶ms.type_or_consts[*id].name() { - Some(name) => w!(this, "{}", name.display(self.db)), + Some(name) => w!(this, "{}", name.display(self.db.upcast())), None => w!(this, "_anon_{}", id.into_raw()), } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/tests.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/tests.rs index 5ded4b6b2..4180f8172 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/tests.rs @@ -358,3 +358,15 @@ trait Tr<'a, T: 'a>: Super where Self: for<'a> Tr<'a, T> {} "#]], ) } + +#[test] +fn generics_with_attributes() { + check( + r#" +struct S<#[cfg(never)] T>; + "#, + expect![[r#" + pub(self) struct S<#[cfg(never)] T>; + "#]], + ) +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs b/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs index 627479bb7..1ae6bd4c9 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs @@ -2,6 +2,7 @@ //! //! This attribute to tell the compiler about semi built-in std library //! features, such as Fn family of traits. +use hir_expand::name::Name; use rustc_hash::FxHashMap; use syntax::SmolStr; use triomphe::Arc; @@ -238,7 +239,17 @@ impl LangItem { pub fn path(&self, db: &dyn DefDatabase, start_crate: CrateId) -> Option<Path> { let t = db.lang_item(start_crate, *self)?; - Some(Path::LangItem(t)) + Some(Path::LangItem(t, None)) + } + + pub fn ty_rel_path( + &self, + db: &dyn DefDatabase, + start_crate: CrateId, + seg: Name, + ) -> Option<Path> { + let t = db.lang_item(start_crate, *self)?; + Some(Path::LangItem(t, Some(seg))) } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/lib.rs b/src/tools/rust-analyzer/crates/hir-def/src/lib.rs index 1901db8a0..3f87fe62b 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/lib.rs @@ -109,6 +109,17 @@ impl CrateRootModuleId { } } +impl PartialEq<ModuleId> for CrateRootModuleId { + fn eq(&self, other: &ModuleId) -> bool { + other.block.is_none() && other.local_id == DefMap::ROOT && self.krate == other.krate + } +} +impl PartialEq<CrateRootModuleId> for ModuleId { + fn eq(&self, other: &CrateRootModuleId) -> bool { + other == self + } +} + impl From<CrateRootModuleId> for ModuleId { fn from(CrateRootModuleId { krate }: CrateRootModuleId) -> Self { ModuleId { krate, block: None, local_id: DefMap::ROOT } @@ -854,14 +865,36 @@ impl_from!( ConstId, FunctionId, TraitId, + TraitAliasId, TypeAliasId, MacroId(Macro2Id, MacroRulesId, ProcMacroId), ImplId, GenericParamId, - ExternCrateId + ExternCrateId, + UseId for AttrDefId ); +impl TryFrom<ModuleDefId> for AttrDefId { + type Error = (); + + fn try_from(value: ModuleDefId) -> Result<Self, Self::Error> { + match value { + ModuleDefId::ModuleId(it) => Ok(it.into()), + ModuleDefId::FunctionId(it) => Ok(it.into()), + ModuleDefId::AdtId(it) => Ok(it.into()), + ModuleDefId::EnumVariantId(it) => Ok(it.into()), + ModuleDefId::ConstId(it) => Ok(it.into()), + ModuleDefId::StaticId(it) => Ok(it.into()), + ModuleDefId::TraitId(it) => Ok(it.into()), + ModuleDefId::TypeAliasId(it) => Ok(it.into()), + ModuleDefId::TraitAliasId(id) => Ok(id.into()), + ModuleDefId::MacroId(id) => Ok(id.into()), + ModuleDefId::BuiltinType(_) => Err(()), + } + } +} + impl From<ItemContainerId> for AttrDefId { fn from(acid: ItemContainerId) -> Self { match acid { @@ -872,6 +905,15 @@ impl From<ItemContainerId> for AttrDefId { } } } +impl From<AssocItemId> for AttrDefId { + fn from(assoc: AssocItemId) -> Self { + match assoc { + AssocItemId::FunctionId(it) => AttrDefId::FunctionId(it), + AssocItemId::ConstId(it) => AttrDefId::ConstId(it), + AssocItemId::TypeAliasId(it) => AttrDefId::TypeAliasId(it), + } + } +} #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum VariantId { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/lower.rs index e523c2291..52781d988 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/lower.rs @@ -1,10 +1,11 @@ //! Context for lowering paths. +use std::cell::OnceCell; + use hir_expand::{ ast_id_map::{AstIdMap, AstIdNode}, hygiene::Hygiene, AstId, HirFileId, InFile, }; -use once_cell::unsync::OnceCell; use syntax::ast; use triomphe::Arc; diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs index 1250cbb74..4aedb22c6 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs @@ -23,6 +23,45 @@ fn main() { 0 as u32; } } #[test] +fn test_asm_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! asm {() => {}} + +fn main() { + let i: u64 = 3; + let o: u64; + unsafe { + asm!( + "mov {0}, {1}", + "add {0}, 5", + out(reg) o, + in(reg) i, + ); + } +} +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! asm {() => {}} + +fn main() { + let i: u64 = 3; + let o: u64; + unsafe { + builtin #asm ( { + $crate::format_args!("mov {0}, {1}"); + $crate::format_args!("add {0}, 5"); + } + ); + } +} +"##]], + ); +} + +#[test] fn test_line_expand() { check( r#" @@ -201,7 +240,7 @@ macro_rules! format_args { } fn main() { - ::core::fmt::Arguments::new_v1(&["", " ", ], &[::core::fmt::ArgumentV1::new(&(arg1(a, b, c)), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(arg2), ::core::fmt::Debug::fmt), ]); + builtin #format_args ("{} {:?}", arg1(a, b, c), arg2); } "##]], ); @@ -219,10 +258,10 @@ macro_rules! format_args { fn main() { format_args!(x = 2); - format_args!(x =); - format_args!(x =, x = 2); - format_args!("{}", x =); - format_args!(=, "{}", x =); + format_args!/*+errors*/(x =); + format_args!/*+errors*/(x =, x = 2); + format_args!/*+errors*/("{}", x =); + format_args!/*+errors*/(=, "{}", x =); format_args!(x = 2, "{}", 5); } "#, @@ -234,12 +273,19 @@ macro_rules! format_args { } fn main() { - /* error: no rule matches input tokens */; - /* error: expected expression */; - /* error: expected expression, expected COMMA */; - /* error: expected expression */::core::fmt::Arguments::new_v1(&["", ], &[::core::fmt::ArgumentV1::new(&(), ::core::fmt::Display::fmt), ]); - /* error: expected expression, expected expression */; - ::core::fmt::Arguments::new_v1(&["", ], &[::core::fmt::ArgumentV1::new(&(5), ::core::fmt::Display::fmt), ]); + builtin #format_args (x = 2); + /* parse error: expected expression */ +builtin #format_args (x = ); + /* parse error: expected expression */ +/* parse error: expected R_PAREN */ +/* parse error: expected expression, item or let statement */ +builtin #format_args (x = , x = 2); + /* parse error: expected expression */ +builtin #format_args ("{}", x = ); + /* parse error: expected expression */ +/* parse error: expected expression */ +builtin #format_args ( = , "{}", x = ); + builtin #format_args (x = 2, "{}", 5); } "##]], ); @@ -267,7 +313,7 @@ macro_rules! format_args { } fn main() { - ::core::fmt::Arguments::new_v1(&["", " ", ], &[::core::fmt::ArgumentV1::new(&(a::<A, B>()), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(b), ::core::fmt::Debug::fmt), ]); + builtin #format_args ("{} {:?}", a::<A, B>(), b); } "##]], ); @@ -300,7 +346,7 @@ macro_rules! format_args { } fn main() { - ::core::fmt::Arguments::new_v1(&[r#""#, r#",mismatch,""#, r#"",""#, r#"""#, ], &[::core::fmt::ArgumentV1::new(&(location_csv_pat(db, &analysis, vfs, &sm, pat_id)), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(mismatch.expected.display(db)), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(mismatch.actual.display(db)), ::core::fmt::Display::fmt), ]); + builtin #format_args (r#"{},mismatch,"{}","{}""#, location_csv_pat(db, &analysis, vfs, &sm, pat_id), mismatch.expected.display(db), mismatch.actual.display(db)); } "##]], ); @@ -334,7 +380,7 @@ macro_rules! format_args { } fn main() { - ::core::fmt::Arguments::new_v1(&["xxx", "y", "zzz", ], &[::core::fmt::ArgumentV1::new(&(2), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(b), ::core::fmt::Debug::fmt), ]); + builtin #format_args (concat!("xxx{}y", "{:?}zzz"), 2, b); } "##]], ); @@ -364,8 +410,8 @@ macro_rules! format_args { fn main() { let _ = - /* error: expected field name or number *//* parse error: expected field name or number */ -::core::fmt::Arguments::new_v1(&["", " ", ], &[::core::fmt::ArgumentV1::new(&(a.), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(), ::core::fmt::Debug::fmt), ]); + /* parse error: expected field name or number */ +builtin #format_args ("{} {:?}", a.); } "##]], ); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs index 2170cadcf..d09062132 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs @@ -117,7 +117,7 @@ fn main(foo: ()) { macro_rules! format_args {} fn main(foo: ()) { - /* error: unresolved macro identity */::core::fmt::Arguments::new_v1(&["", " ", " ", ], &[::core::fmt::ArgumentV1::new(&(::core::fmt::Arguments::new_v1(&["", ], &[::core::fmt::ArgumentV1::new(&(0), ::core::fmt::Display::fmt), ])), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(foo), ::core::fmt::Display::fmt), ::core::fmt::ArgumentV1::new(&(identity!(10)), ::core::fmt::Display::fmt), ]) + builtin #format_args ("{} {} {}", format_args!("{}", 0), foo, identity!(10), "bar") } "##]], ); @@ -150,8 +150,8 @@ macro_rules! identity { } fn main(foo: ()) { - // format_args/*+tokenids*/!("{} {} {}"#1,#3 format_args!("{}", 0#10),#12 foo#13,#14 identity!(10#18),#21 "bar"#22) -::core#4294967295::fmt#4294967295::Arguments#4294967295::new_v1#4294967295(�[#4294967295""#4294967295,#4294967295 " "#4294967295,#4294967295 " "#4294967295,#4294967295 ]#4294967295,#4294967295 �[::core#4294967295::fmt#4294967295::ArgumentV1#4294967295::new#4294967295(�(::core#4294967295::fmt#4294967295::Arguments#4294967295::new_v1#4294967295(�[#4294967295""#4294967295,#4294967295 ]#4294967295,#4294967295 �[::core#4294967295::fmt#4294967295::ArgumentV1#4294967295::new#4294967295(�(#42949672950#10)#4294967295,#4294967295 ::core#4294967295::fmt#4294967295::Display#4294967295::fmt#4294967295)#4294967295,#4294967295 ]#4294967295)#4294967295)#4294967295,#4294967295 ::core#4294967295::fmt#4294967295::Display#4294967295::fmt#4294967295)#4294967295,#4294967295 ::core#4294967295::fmt#4294967295::ArgumentV1#4294967295::new#4294967295(�(#4294967295foo#13)#4294967295,#4294967295 ::core#4294967295::fmt#4294967295::Display#4294967295::fmt#4294967295)#4294967295,#4294967295 ::core#4294967295::fmt#4294967295::ArgumentV1#4294967295::new#4294967295(�(#429496729510#18)#4294967295,#4294967295 ::core#4294967295::fmt#4294967295::Display#4294967295::fmt#4294967295)#4294967295,#4294967295 ]#4294967295)#4294967295 + // format_args/*+tokenids*/!("{} {} {}"#1,#2 format_args#3!#4("{}"#6,#7 0#8),#9 foo#10,#11 identity#12!#13(10#15),#16 "bar"#17) +builtin#4294967295 ##4294967295format_args#4294967295 (#0"{} {} {}"#1,#2 format_args#3!#4(#5"{}"#6,#7 0#8)#5,#9 foo#10,#11 identity#12!#13(#1410#15)#14,#16 "bar"#17)#0 } "##]], 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 d8e4a4dcc..b416f45ff 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 @@ -909,3 +909,64 @@ macro_rules! with_std { "##]], ) } + +#[test] +fn eager_regression_15403() { + check( + r#" +#[rustc_builtin_macro] +#[macro_export] +macro_rules! format_args {} + +fn main() { + format_args /* +errors */ !("{}", line.1.); +} + +"#, + expect![[r##" +#[rustc_builtin_macro] +#[macro_export] +macro_rules! format_args {} + +fn main() { + /* parse error: expected field name or number */ +builtin #format_args ("{}", line.1.); +} + +"##]], + ); +} + +#[test] +fn eager_regression_154032() { + check( + r#" +#[rustc_builtin_macro] +#[macro_export] +macro_rules! format_args {} + +fn main() { + format_args /* +errors */ !("{}", &[0 2]); +} + +"#, + expect![[r##" +#[rustc_builtin_macro] +#[macro_export] +macro_rules! format_args {} + +fn main() { + /* parse error: expected COMMA */ +/* parse error: expected R_BRACK */ +/* parse error: expected COMMA */ +/* parse error: expected COMMA */ +/* parse error: expected expression */ +/* parse error: expected R_PAREN */ +/* parse error: expected expression, item or let statement */ +/* parse error: expected expression, item or let statement */ +builtin #format_args ("{}", &[0 2]); +} + +"##]], + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs index 7a87e61c6..8adced4e0 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs @@ -131,7 +131,7 @@ pub fn identity_when_valid(_attr: TokenStream, item: TokenStream) -> TokenStream .as_call_id_with_errors(&db, krate, |path| { resolver .resolve_path_as_macro(&db, &path, Some(MacroSubNs::Bang)) - .map(|it| macro_id_to_def_id(&db, it)) + .map(|(it, _)| macro_id_to_def_id(&db, it)) }) .unwrap(); let macro_call_id = res.value.unwrap(); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs index 86818ce26..9a9fa0e02 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs @@ -60,7 +60,7 @@ mod tests; use std::{cmp::Ord, ops::Deref}; use base_db::{CrateId, Edition, FileId, ProcMacroKind}; -use hir_expand::{name::Name, HirFileId, InFile, MacroCallId, MacroDefId}; +use hir_expand::{ast_id_map::FileAstId, name::Name, HirFileId, InFile, MacroCallId, MacroDefId}; use itertools::Itertools; use la_arena::Arena; use profile::Count; @@ -77,8 +77,8 @@ use crate::{ path::ModPath, per_ns::PerNs, visibility::Visibility, - AstId, BlockId, BlockLoc, CrateRootModuleId, FunctionId, LocalModuleId, Lookup, MacroExpander, - MacroId, ModuleId, ProcMacroId, + AstId, BlockId, BlockLoc, CrateRootModuleId, ExternCrateId, FunctionId, LocalModuleId, Lookup, + MacroExpander, MacroId, ModuleId, ProcMacroId, UseId, }; /// Contains the results of (early) name resolution. @@ -93,7 +93,7 @@ use crate::{ #[derive(Debug, PartialEq, Eq)] pub struct DefMap { _c: Count<Self>, - /// When this is a block def map, this will hold the block id of the the block and module that + /// When this is a block def map, this will hold the block id of the block and module that /// contains this block. block: Option<BlockInfo>, /// The modules and their data declared in this crate. @@ -105,10 +105,11 @@ pub struct DefMap { /// The prelude is empty for non-block DefMaps (unless `#[prelude_import]` was used, /// but that attribute is nightly and when used in a block, it affects resolution globally /// so we aren't handling this correctly anyways). - prelude: Option<ModuleId>, + prelude: Option<(ModuleId, Option<UseId>)>, /// `macro_use` prelude that contains macros from `#[macro_use]`'d external crates. Note that /// this contains all kinds of macro, not just `macro_rules!` macro. - macro_use_prelude: FxHashMap<Name, MacroId>, + /// ExternCrateId being None implies it being imported from the general prelude import. + macro_use_prelude: FxHashMap<Name, (MacroId, Option<ExternCrateId>)>, /// Tracks which custom derives are in scope for an item, to allow resolution of derive helper /// attributes. @@ -125,7 +126,7 @@ pub struct DefMap { #[derive(Clone, Debug, PartialEq, Eq)] struct DefMapCrateData { /// The extern prelude which contains all root modules of external crates that are in scope. - extern_prelude: FxHashMap<Name, CrateRootModuleId>, + extern_prelude: FxHashMap<Name, (CrateRootModuleId, Option<ExternCrateId>)>, /// Side table for resolving derive helpers. exported_derives: FxHashMap<MacroDefId, Box<[Name]>>, @@ -217,16 +218,17 @@ pub enum ModuleOrigin { /// Note that non-inline modules, by definition, live inside non-macro file. File { is_mod_rs: bool, - declaration: AstId<ast::Module>, + declaration: FileAstId<ast::Module>, declaration_tree_id: ItemTreeId<Mod>, definition: FileId, }, Inline { definition_tree_id: ItemTreeId<Mod>, - definition: AstId<ast::Module>, + definition: FileAstId<ast::Module>, }, /// Pseudo-module introduced by a block scope (contains only inner items). BlockExpr { + id: BlockId, block: AstId<ast::BlockExpr>, }, } @@ -234,8 +236,12 @@ pub enum ModuleOrigin { impl ModuleOrigin { pub fn declaration(&self) -> Option<AstId<ast::Module>> { match self { - ModuleOrigin::File { declaration: module, .. } - | ModuleOrigin::Inline { definition: module, .. } => Some(*module), + &ModuleOrigin::File { declaration, declaration_tree_id, .. } => { + Some(AstId::new(declaration_tree_id.file_id(), declaration)) + } + &ModuleOrigin::Inline { definition, definition_tree_id } => { + Some(AstId::new(definition_tree_id.file_id(), definition)) + } ModuleOrigin::CrateRoot { .. } | ModuleOrigin::BlockExpr { .. } => None, } } @@ -260,16 +266,17 @@ impl ModuleOrigin { /// That is, a file or a `mod foo {}` with items. fn definition_source(&self, db: &dyn DefDatabase) -> InFile<ModuleSource> { match self { - ModuleOrigin::File { definition, .. } | ModuleOrigin::CrateRoot { definition } => { - let file_id = *definition; - let sf = db.parse(file_id).tree(); - InFile::new(file_id.into(), ModuleSource::SourceFile(sf)) + &ModuleOrigin::File { definition, .. } | &ModuleOrigin::CrateRoot { definition } => { + let sf = db.parse(definition).tree(); + InFile::new(definition.into(), ModuleSource::SourceFile(sf)) } - ModuleOrigin::Inline { definition, .. } => InFile::new( - definition.file_id, - ModuleSource::Module(definition.to_node(db.upcast())), + &ModuleOrigin::Inline { definition, definition_tree_id } => InFile::new( + definition_tree_id.file_id(), + ModuleSource::Module( + AstId::new(definition_tree_id.file_id(), definition).to_node(db.upcast()), + ), ), - ModuleOrigin::BlockExpr { block } => { + ModuleOrigin::BlockExpr { block, .. } => { InFile::new(block.file_id, ModuleSource::BlockExpr(block.to_node(db.upcast()))) } } @@ -314,9 +321,7 @@ impl DefMap { } pub(crate) fn block_def_map_query(db: &dyn DefDatabase, block_id: BlockId) -> Arc<DefMap> { - let block: BlockLoc = db.lookup_intern_block(block_id); - - let tree_id = TreeId::new(block.ast_id.file_id, Some(block_id)); + let block: BlockLoc = block_id.lookup(db); let parent_map = block.module.def_map(db); let krate = block.module.krate; @@ -325,8 +330,10 @@ impl DefMap { // modules declared by blocks with items. At the moment, we don't use // this visibility for anything outside IDE, so that's probably OK. let visibility = Visibility::Module(ModuleId { krate, local_id, block: None }); - let module_data = - ModuleData::new(ModuleOrigin::BlockExpr { block: block.ast_id }, visibility); + let module_data = ModuleData::new( + ModuleOrigin::BlockExpr { block: block.ast_id, id: block_id }, + visibility, + ); let mut def_map = DefMap::empty(krate, parent_map.data.edition, module_data); def_map.data = parent_map.data.clone(); @@ -338,7 +345,8 @@ impl DefMap { }, }); - let def_map = collector::collect_defs(db, def_map, tree_id); + let def_map = + collector::collect_defs(db, def_map, TreeId::new(block.ast_id.file_id, Some(block_id))); Arc::new(def_map) } @@ -427,15 +435,19 @@ impl DefMap { self.block.map(|block| block.block) } - pub(crate) fn prelude(&self) -> Option<ModuleId> { + pub(crate) fn prelude(&self) -> Option<(ModuleId, Option<UseId>)> { self.prelude } - pub(crate) fn extern_prelude(&self) -> impl Iterator<Item = (&Name, ModuleId)> + '_ { - self.data.extern_prelude.iter().map(|(name, &def)| (name, def.into())) + pub(crate) fn extern_prelude( + &self, + ) -> impl Iterator<Item = (&Name, (CrateRootModuleId, Option<ExternCrateId>))> + '_ { + self.data.extern_prelude.iter().map(|(name, &def)| (name, def)) } - pub(crate) fn macro_use_prelude(&self) -> impl Iterator<Item = (&Name, MacroId)> + '_ { + pub(crate) fn macro_use_prelude( + &self, + ) -> impl Iterator<Item = (&Name, (MacroId, Option<ExternCrateId>))> + '_ { self.macro_use_prelude.iter().map(|(name, &def)| (name, def)) } @@ -638,8 +650,8 @@ impl ModuleData { ModuleOrigin::File { definition, .. } | ModuleOrigin::CrateRoot { definition } => { definition.into() } - ModuleOrigin::Inline { definition, .. } => definition.file_id, - ModuleOrigin::BlockExpr { block } => block.file_id, + ModuleOrigin::Inline { definition_tree_id, .. } => definition_tree_id.file_id(), + ModuleOrigin::BlockExpr { block, .. } => block.file_id, } } 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 eef54fc49..2d4586146 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 @@ -33,7 +33,7 @@ use crate::{ attr_macro_as_call_id, db::DefDatabase, derive_macro_as_call_id, - item_scope::{ImportType, PerNsGlobImports}, + item_scope::{ImportId, ImportOrExternCrate, ImportType, PerNsGlobImports}, item_tree::{ self, ExternCrate, Fields, FileItemTreeId, ImportKind, ItemTree, ItemTreeId, ItemTreeNode, MacroCall, MacroDef, MacroRules, Mod, ModItem, ModKind, TreeId, @@ -52,10 +52,10 @@ use crate::{ tt, visibility::{RawVisibility, Visibility}, AdtId, AstId, AstIdWithPath, ConstLoc, CrateRootModuleId, EnumLoc, EnumVariantId, - ExternBlockLoc, ExternCrateLoc, FunctionId, FunctionLoc, ImplLoc, Intern, ItemContainerId, - LocalModuleId, Macro2Id, Macro2Loc, MacroExpander, MacroId, MacroRulesId, MacroRulesLoc, - ModuleDefId, ModuleId, ProcMacroId, ProcMacroLoc, StaticLoc, StructLoc, TraitAliasLoc, - TraitLoc, TypeAliasLoc, UnionLoc, UnresolvedMacro, UseLoc, + ExternBlockLoc, ExternCrateId, ExternCrateLoc, FunctionId, FunctionLoc, ImplLoc, Intern, + ItemContainerId, LocalModuleId, Lookup, Macro2Id, Macro2Loc, MacroExpander, MacroId, + MacroRulesId, MacroRulesLoc, ModuleDefId, ModuleId, ProcMacroId, ProcMacroLoc, StaticLoc, + StructLoc, TraitAliasLoc, TraitLoc, TypeAliasLoc, UnionLoc, UnresolvedMacro, UseId, UseLoc, }; static GLOB_RECURSION_LIMIT: Limit = Limit::new(100); @@ -146,8 +146,8 @@ impl PartialResolvedImport { #[derive(Clone, Debug, Eq, PartialEq)] enum ImportSource { - Use { id: ItemTreeId<item_tree::Use>, use_tree: Idx<ast::UseTree> }, - ExternCrate(ItemTreeId<item_tree::ExternCrate>), + Use { use_tree: Idx<ast::UseTree>, id: UseId, is_prelude: bool, kind: ImportKind }, + ExternCrate { id: ExternCrateId }, } #[derive(Debug, Eq, PartialEq)] @@ -155,54 +155,41 @@ struct Import { path: ModPath, alias: Option<ImportAlias>, visibility: RawVisibility, - kind: ImportKind, source: ImportSource, - is_prelude: bool, - is_macro_use: bool, } impl Import { fn from_use( - db: &dyn DefDatabase, - krate: CrateId, tree: &ItemTree, - id: ItemTreeId<item_tree::Use>, + item_tree_id: ItemTreeId<item_tree::Use>, + id: UseId, + is_prelude: bool, mut cb: impl FnMut(Self), ) { - let it = &tree[id.value]; - let attrs = &tree.attrs(db, krate, ModItem::from(id.value).into()); + let it = &tree[item_tree_id.value]; let visibility = &tree[it.visibility]; - let is_prelude = attrs.by_key("prelude_import").exists(); it.use_tree.expand(|idx, path, kind, alias| { cb(Self { path, alias, visibility: visibility.clone(), - kind, - is_prelude, - is_macro_use: false, - source: ImportSource::Use { id, use_tree: idx }, + source: ImportSource::Use { use_tree: idx, id, is_prelude, kind }, }); }); } fn from_extern_crate( - db: &dyn DefDatabase, - krate: CrateId, tree: &ItemTree, - id: ItemTreeId<item_tree::ExternCrate>, + item_tree_id: ItemTreeId<item_tree::ExternCrate>, + id: ExternCrateId, ) -> Self { - let it = &tree[id.value]; - let attrs = &tree.attrs(db, krate, ModItem::from(id.value).into()); + let it = &tree[item_tree_id.value]; let visibility = &tree[it.visibility]; Self { path: ModPath::from_segments(PathKind::Plain, iter::once(it.name.clone())), alias: it.alias.clone(), visibility: visibility.clone(), - kind: ImportKind::Plain, - is_prelude: false, - is_macro_use: attrs.by_key("macro_use").exists(), - source: ImportSource::ExternCrate(id), + source: ImportSource::ExternCrate { id }, } } } @@ -235,7 +222,7 @@ struct DefCollector<'a> { db: &'a dyn DefDatabase, def_map: DefMap, deps: FxHashMap<Name, Dependency>, - glob_imports: FxHashMap<LocalModuleId, Vec<(LocalModuleId, Visibility)>>, + glob_imports: FxHashMap<LocalModuleId, Vec<(LocalModuleId, Visibility, UseId)>>, unresolved_imports: Vec<ImportDirective>, indeterminate_imports: Vec<ImportDirective>, unresolved_macros: Vec<MacroDirective>, @@ -280,7 +267,7 @@ impl DefCollector<'_> { if dep.is_prelude() { crate_data .extern_prelude - .insert(name.clone(), CrateRootModuleId { krate: dep.crate_id }); + .insert(name.clone(), (CrateRootModuleId { krate: dep.crate_id }, None)); } } @@ -556,8 +543,12 @@ impl DefCollector<'_> { self.def_map.resolve_path(self.db, DefMap::ROOT, &path, BuiltinShadowMode::Other, None); match per_ns.types { - Some((ModuleDefId::ModuleId(m), _)) => { - self.def_map.prelude = Some(m); + Some((ModuleDefId::ModuleId(m), _, import)) => { + // FIXME: This should specifically look for a glob import somehow and record that here + self.def_map.prelude = Some(( + m, + import.and_then(ImportOrExternCrate::into_import).map(|it| it.import), + )); } types => { tracing::debug!( @@ -657,9 +648,9 @@ impl DefCollector<'_> { self.def_map.modules[module_id].scope.declare(macro_.into()); self.update( module_id, - &[(Some(name), PerNs::macros(macro_.into(), Visibility::Public))], + &[(Some(name), PerNs::macros(macro_.into(), Visibility::Public, None))], Visibility::Public, - ImportType::Named, + None, ); } } @@ -693,9 +684,9 @@ impl DefCollector<'_> { self.def_map.modules[module_id].scope.declare(macro_.into()); self.update( module_id, - &[(Some(name), PerNs::macros(macro_.into(), Visibility::Public))], + &[(Some(name), PerNs::macros(macro_.into(), Visibility::Public, None))], vis, - ImportType::Named, + None, ); } @@ -708,9 +699,9 @@ impl DefCollector<'_> { self.def_map.modules[module_id].scope.declare(macro_.into()); self.update( module_id, - &[(Some(name), PerNs::macros(macro_.into(), Visibility::Public))], + &[(Some(name), PerNs::macros(macro_.into(), Visibility::Public, None))], Visibility::Public, - ImportType::Named, + None, ); } @@ -720,21 +711,29 @@ impl DefCollector<'_> { /// Exported macros are just all macros in the root module scope. /// Note that it contains not only all `#[macro_export]` macros, but also all aliases /// created by `use` in the root module, ignoring the visibility of `use`. - fn import_macros_from_extern_crate(&mut self, krate: CrateId, names: Option<Vec<Name>>) { + fn import_macros_from_extern_crate( + &mut self, + krate: CrateId, + names: Option<Vec<Name>>, + extern_crate: Option<ExternCrateId>, + ) { let def_map = self.db.crate_def_map(krate); // `#[macro_use]` brings macros into macro_use prelude. Yes, even non-`macro_rules!` // macros. let root_scope = &def_map[DefMap::ROOT].scope; - if let Some(names) = names { - for name in names { - // FIXME: Report diagnostic on 404. - if let Some(def) = root_scope.get(&name).take_macros() { - self.def_map.macro_use_prelude.insert(name, def); + match names { + Some(names) => { + for name in names { + // FIXME: Report diagnostic on 404. + if let Some(def) = root_scope.get(&name).take_macros() { + self.def_map.macro_use_prelude.insert(name, (def, extern_crate)); + } } } - } else { - for (name, def) in root_scope.macros() { - self.def_map.macro_use_prelude.insert(name.clone(), def); + None => { + for (name, def) in root_scope.macros() { + self.def_map.macro_use_prelude.insert(name.clone(), (def, extern_crate)); + } } } } @@ -771,48 +770,53 @@ impl DefCollector<'_> { let _p = profile::span("resolve_import") .detail(|| format!("{}", import.path.display(self.db.upcast()))); tracing::debug!("resolving import: {:?} ({:?})", import, self.def_map.data.edition); - if matches!(import.source, ImportSource::ExternCrate { .. }) { - let name = import - .path - .as_ident() - .expect("extern crate should have been desugared to one-element path"); - - let res = self.resolve_extern_crate(name); - - match res { - Some(res) => { - PartialResolvedImport::Resolved(PerNs::types(res.into(), Visibility::Public)) + match import.source { + ImportSource::ExternCrate { .. } => { + let name = import + .path + .as_ident() + .expect("extern crate should have been desugared to one-element path"); + + let res = self.resolve_extern_crate(name); + + match res { + Some(res) => PartialResolvedImport::Resolved(PerNs::types( + res.into(), + Visibility::Public, + None, + )), + None => PartialResolvedImport::Unresolved, } - None => PartialResolvedImport::Unresolved, } - } else { - let res = self.def_map.resolve_path_fp_with_macro( - self.db, - ResolveMode::Import, - module_id, - &import.path, - BuiltinShadowMode::Module, - None, // An import may resolve to any kind of macro. - ); + ImportSource::Use { .. } => { + let res = self.def_map.resolve_path_fp_with_macro( + self.db, + ResolveMode::Import, + module_id, + &import.path, + BuiltinShadowMode::Module, + None, // An import may resolve to any kind of macro. + ); - let def = res.resolved_def; - if res.reached_fixedpoint == ReachedFixedPoint::No || def.is_none() { - return PartialResolvedImport::Unresolved; - } + let def = res.resolved_def; + if res.reached_fixedpoint == ReachedFixedPoint::No || def.is_none() { + return PartialResolvedImport::Unresolved; + } - if let Some(krate) = res.krate { - if krate != self.def_map.krate { - return PartialResolvedImport::Resolved( - def.filter_visibility(|v| matches!(v, Visibility::Public)), - ); + if let Some(krate) = res.krate { + if krate != self.def_map.krate { + return PartialResolvedImport::Resolved( + def.filter_visibility(|v| matches!(v, Visibility::Public)), + ); + } } - } - // Check whether all namespaces are resolved. - if def.is_full() { - PartialResolvedImport::Resolved(def) - } else { - PartialResolvedImport::Indeterminate(def) + // Check whether all namespaces are resolved. + if def.is_full() { + PartialResolvedImport::Resolved(def) + } else { + PartialResolvedImport::Indeterminate(def) + } } } } @@ -837,8 +841,9 @@ impl DefCollector<'_> { .resolve_visibility(self.db, module_id, &directive.import.visibility, false) .unwrap_or(Visibility::Public); - match import.kind { - ImportKind::Plain | ImportKind::TypeOnly => { + match import.source { + ImportSource::ExternCrate { .. } + | ImportSource::Use { kind: ImportKind::Plain | ImportKind::TypeOnly, .. } => { let name = match &import.alias { Some(ImportAlias::Alias(name)) => Some(name), Some(ImportAlias::Underscore) => None, @@ -851,40 +856,44 @@ impl DefCollector<'_> { }, }; - if import.kind == ImportKind::TypeOnly { - def.values = None; - def.macros = None; - } - - tracing::debug!("resolved import {:?} ({:?}) to {:?}", name, import, def); - - // extern crates in the crate root are special-cased to insert entries into the extern prelude: rust-lang/rust#54658 - if matches!(import.source, ImportSource::ExternCrate { .. }) - && self.def_map.block.is_none() - && module_id == DefMap::ROOT - { - if let (Some(ModuleDefId::ModuleId(def)), Some(name)) = (def.take_types(), name) - { - if let Ok(def) = def.try_into() { - Arc::get_mut(&mut self.def_map.data) - .unwrap() - .extern_prelude - .insert(name.clone(), def); + let imp = match import.source { + // extern crates in the crate root are special-cased to insert entries into the extern prelude: rust-lang/rust#54658 + ImportSource::ExternCrate { id, .. } => { + if self.def_map.block.is_none() && module_id == DefMap::ROOT { + if let (Some(ModuleDefId::ModuleId(def)), Some(name)) = + (def.take_types(), name) + { + if let Ok(def) = def.try_into() { + Arc::get_mut(&mut self.def_map.data) + .unwrap() + .extern_prelude + .insert(name.clone(), (def, Some(id))); + } + } } + ImportType::ExternCrate(id) } - } + ImportSource::Use { kind, id, use_tree, .. } => { + if kind == ImportKind::TypeOnly { + def.values = None; + def.macros = None; + } + ImportType::Import(ImportId { import: id, idx: use_tree }) + } + }; + tracing::debug!("resolved import {:?} ({:?}) to {:?}", name, import, def); - self.update(module_id, &[(name.cloned(), def)], vis, ImportType::Named); + self.update(module_id, &[(name.cloned(), def)], vis, Some(imp)); } - ImportKind::Glob => { + ImportSource::Use { kind: ImportKind::Glob, id, .. } => { tracing::debug!("glob import: {:?}", import); match def.take_types() { Some(ModuleDefId::ModuleId(m)) => { - if import.is_prelude { + if let ImportSource::Use { id, is_prelude: true, .. } = import.source { // Note: This dodgily overrides the injected prelude. The rustc // implementation seems to work the same though. cov_mark::hit!(std_prelude); - self.def_map.prelude = Some(m); + self.def_map.prelude = Some((m, Some(id))); } else if m.krate != self.def_map.krate { cov_mark::hit!(glob_across_crates); // glob import from other crate => we can just import everything once @@ -901,7 +910,7 @@ impl DefCollector<'_> { .filter(|(_, res)| !res.is_none()) .collect::<Vec<_>>(); - self.update(module_id, &items, vis, ImportType::Glob); + self.update(module_id, &items, vis, Some(ImportType::Glob(id))); } else { // glob import from same crate => we do an initial // import, and then need to propagate any further @@ -933,11 +942,11 @@ impl DefCollector<'_> { .filter(|(_, res)| !res.is_none()) .collect::<Vec<_>>(); - self.update(module_id, &items, vis, ImportType::Glob); + self.update(module_id, &items, vis, Some(ImportType::Glob(id))); // record the glob import in case we add further items let glob = self.glob_imports.entry(m.local_id).or_default(); - if !glob.iter().any(|(mid, _)| *mid == module_id) { - glob.push((module_id, vis)); + if !glob.iter().any(|(mid, _, _)| *mid == module_id) { + glob.push((module_id, vis, id)); } } } @@ -959,11 +968,11 @@ impl DefCollector<'_> { .map(|(local_id, variant_data)| { let name = variant_data.name.clone(); let variant = EnumVariantId { parent: e, local_id }; - let res = PerNs::both(variant.into(), variant.into(), vis); + let res = PerNs::both(variant.into(), variant.into(), vis, None); (Some(name), res) }) .collect::<Vec<_>>(); - self.update(module_id, &resolutions, vis, ImportType::Glob); + self.update(module_id, &resolutions, vis, Some(ImportType::Glob(id))); } Some(d) => { tracing::debug!("glob import {:?} from non-module/enum {:?}", import, d); @@ -983,10 +992,10 @@ impl DefCollector<'_> { resolutions: &[(Option<Name>, PerNs)], // Visibility this import will have vis: Visibility, - import_type: ImportType, + import: Option<ImportType>, ) { self.db.unwind_if_cancelled(); - self.update_recursive(module_id, resolutions, vis, import_type, 0) + self.update_recursive(module_id, resolutions, vis, import, 0) } fn update_recursive( @@ -997,7 +1006,7 @@ impl DefCollector<'_> { // All resolutions are imported with this visibility; the visibilities in // the `PerNs` values are ignored and overwritten vis: Visibility, - import_type: ImportType, + import: Option<ImportType>, depth: usize, ) { if GLOB_RECURSION_LIMIT.check(depth).is_err() { @@ -1014,7 +1023,7 @@ impl DefCollector<'_> { &mut self.from_glob_import, (module_id, name.clone()), res.with_visibility(vis), - import_type, + import, ); } None => { @@ -1059,7 +1068,7 @@ impl DefCollector<'_> { .get(&module_id) .into_iter() .flatten() - .filter(|(glob_importing_module, _)| { + .filter(|(glob_importing_module, _, _)| { // we know all resolutions have the same visibility (`vis`), so we // just need to check that once vis.is_visible_from_def_map(self.db, &self.def_map, *glob_importing_module) @@ -1067,12 +1076,12 @@ impl DefCollector<'_> { .cloned() .collect::<Vec<_>>(); - for (glob_importing_module, glob_import_vis) in glob_imports { + for (glob_importing_module, glob_import_vis, use_) in glob_imports { self.update_recursive( glob_importing_module, resolutions, glob_import_vis, - ImportType::Glob, + Some(ImportType::Glob(use_)), depth + 1, ); } @@ -1460,31 +1469,34 @@ impl DefCollector<'_> { // heuristic, but it works in practice. let mut diagnosed_extern_crates = FxHashSet::default(); for directive in &self.unresolved_imports { - if let ImportSource::ExternCrate(krate) = directive.import.source { - let item_tree = krate.item_tree(self.db); - let extern_crate = &item_tree[krate.value]; + if let ImportSource::ExternCrate { id } = directive.import.source { + let item_tree_id = id.lookup(self.db).id; + let item_tree = item_tree_id.item_tree(self.db); + let extern_crate = &item_tree[item_tree_id.value]; diagnosed_extern_crates.insert(extern_crate.name.clone()); self.def_map.diagnostics.push(DefDiagnostic::unresolved_extern_crate( directive.module_id, - InFile::new(krate.file_id(), extern_crate.ast_id), + InFile::new(item_tree_id.file_id(), extern_crate.ast_id), )); } } for directive in &self.unresolved_imports { - if let ImportSource::Use { id: import, use_tree } = directive.import.source { + if let ImportSource::Use { use_tree, id, is_prelude: _, kind: _ } = + directive.import.source + { if matches!( (directive.import.path.segments().first(), &directive.import.path.kind), (Some(krate), PathKind::Plain | PathKind::Abs) if diagnosed_extern_crates.contains(krate) ) { continue; } - + let item_tree_id = id.lookup(self.db).id; self.def_map.diagnostics.push(DefDiagnostic::unresolved_import( directive.module_id, - import, + item_tree_id, use_tree, )); } @@ -1519,72 +1531,66 @@ impl ModCollector<'_, '_> { self.def_collector.mod_dirs.insert(self.module_id, self.mod_dir.clone()); // Prelude module is always considered to be `#[macro_use]`. - if let Some(prelude_module) = self.def_collector.def_map.prelude { + if let Some((prelude_module, _use)) = self.def_collector.def_map.prelude { if prelude_module.krate != krate && is_crate_root { cov_mark::hit!(prelude_is_macro_use); - self.def_collector.import_macros_from_extern_crate(prelude_module.krate, None); - } - } - - // This should be processed eagerly instead of deferred to resolving. - // `#[macro_use] extern crate` is hoisted to imports macros before collecting - // any other items. - // - // If we're not at the crate root, `macro_use`d extern crates are an error so let's just - // ignore them. - if is_crate_root { - for &item in items { - if let ModItem::ExternCrate(id) = item { - self.process_macro_use_extern_crate(id); - } + self.def_collector.import_macros_from_extern_crate( + prelude_module.krate, + None, + None, + ); } } + let db = self.def_collector.db; + let module_id = self.module_id; + let update_def = + |def_collector: &mut DefCollector<'_>, id, name: &Name, vis, has_constructor| { + def_collector.def_map.modules[module_id].scope.declare(id); + def_collector.update( + module_id, + &[(Some(name.clone()), PerNs::from_def(id, vis, has_constructor, None))], + vis, + None, + ) + }; + let resolve_vis = |def_map: &DefMap, visibility| { + def_map + .resolve_visibility(db, module_id, visibility, false) + .unwrap_or(Visibility::Public) + }; - for &item in items { - let attrs = self.item_tree.attrs(self.def_collector.db, krate, item.into()); + let mut process_mod_item = |item: ModItem| { + let attrs = self.item_tree.attrs(db, krate, item.into()); if let Some(cfg) = attrs.cfg() { if !self.is_cfg_enabled(&cfg) { self.emit_unconfigured_diagnostic(item, &cfg); - continue; + return; } } if let Err(()) = self.resolve_attributes(&attrs, item, container) { // Do not process the item. It has at least one non-builtin attribute, so the // fixed-point algorithm is required to resolve the rest of them. - continue; + return; } - let db = self.def_collector.db; - let module = self.def_collector.def_map.module_id(self.module_id); + let module = self.def_collector.def_map.module_id(module_id); let def_map = &mut self.def_collector.def_map; - let update_def = - |def_collector: &mut DefCollector<'_>, id, name: &Name, vis, has_constructor| { - def_collector.def_map.modules[self.module_id].scope.declare(id); - def_collector.update( - self.module_id, - &[(Some(name.clone()), PerNs::from_def(id, vis, has_constructor))], - vis, - ImportType::Named, - ) - }; - let resolve_vis = |def_map: &DefMap, visibility| { - def_map - .resolve_visibility(db, self.module_id, visibility, false) - .unwrap_or(Visibility::Public) - }; match item { ModItem::Mod(m) => self.collect_module(m, &attrs), - ModItem::Use(import_id) => { - let _import_id = - UseLoc { container: module, id: ItemTreeId::new(self.tree_id, import_id) } - .intern(db); + ModItem::Use(item_tree_id) => { + let id = UseLoc { + container: module, + id: ItemTreeId::new(self.tree_id, item_tree_id), + } + .intern(db); + let is_prelude = attrs.by_key("prelude_import").exists(); Import::from_use( - db, - krate, self.item_tree, - ItemTreeId::new(self.tree_id, import_id), + ItemTreeId::new(self.tree_id, item_tree_id), + id, + is_prelude, |import| { self.def_collector.unresolved_imports.push(ImportDirective { module_id: self.module_id, @@ -1594,22 +1600,29 @@ impl ModCollector<'_, '_> { }, ) } - ModItem::ExternCrate(import_id) => { - let extern_crate_id = ExternCrateLoc { + ModItem::ExternCrate(item_tree_id) => { + let id = ExternCrateLoc { container: module, - id: ItemTreeId::new(self.tree_id, import_id), + id: ItemTreeId::new(self.tree_id, item_tree_id), } .intern(db); + if is_crate_root { + self.process_macro_use_extern_crate( + item_tree_id, + id, + attrs.by_key("macro_use").attrs(), + ); + } + self.def_collector.def_map.modules[self.module_id] .scope - .define_extern_crate_decl(extern_crate_id); + .define_extern_crate_decl(id); self.def_collector.unresolved_imports.push(ImportDirective { module_id: self.module_id, import: Import::from_extern_crate( - db, - krate, self.item_tree, - ItemTreeId::new(self.tree_id, import_id), + ItemTreeId::new(self.tree_id, item_tree_id), + id, ), status: PartialResolvedImport::Unresolved, }) @@ -1768,21 +1781,34 @@ impl ModCollector<'_, '_> { ); } } + }; + + // extern crates should be processed eagerly instead of deferred to resolving. + // `#[macro_use] extern crate` is hoisted to imports macros before collecting + // any other items. + if is_crate_root { + items + .iter() + .filter(|it| matches!(it, ModItem::ExternCrate(..))) + .copied() + .for_each(&mut process_mod_item); + items + .iter() + .filter(|it| !matches!(it, ModItem::ExternCrate(..))) + .copied() + .for_each(process_mod_item); + } else { + items.iter().copied().for_each(process_mod_item); } } - fn process_macro_use_extern_crate(&mut self, extern_crate: FileItemTreeId<ExternCrate>) { + fn process_macro_use_extern_crate<'a>( + &mut self, + extern_crate: FileItemTreeId<ExternCrate>, + extern_crate_id: ExternCrateId, + macro_use_attrs: impl Iterator<Item = &'a Attr>, + ) { let db = self.def_collector.db; - let attrs = self.item_tree.attrs( - db, - self.def_collector.def_map.krate, - ModItem::from(extern_crate).into(), - ); - if let Some(cfg) = attrs.cfg() { - if !self.is_cfg_enabled(&cfg) { - return; - } - } let target_crate = match self.def_collector.resolve_extern_crate(&self.item_tree[extern_crate].name) { @@ -1798,11 +1824,15 @@ impl ModCollector<'_, '_> { let mut single_imports = Vec::new(); let hygiene = Hygiene::new_unhygienic(); - for attr in attrs.by_key("macro_use").attrs() { + for attr in macro_use_attrs { let Some(paths) = attr.parse_path_comma_token_tree(db.upcast(), &hygiene) else { // `#[macro_use]` (without any paths) found, forget collected names and just import // all visible macros. - self.def_collector.import_macros_from_extern_crate(target_crate, None); + self.def_collector.import_macros_from_extern_crate( + target_crate, + None, + Some(extern_crate_id), + ); return; }; for path in paths { @@ -1812,7 +1842,11 @@ impl ModCollector<'_, '_> { } } - self.def_collector.import_macros_from_extern_crate(target_crate, Some(single_imports)); + self.def_collector.import_macros_from_extern_crate( + target_crate, + Some(single_imports), + Some(extern_crate_id), + ); } fn collect_module(&mut self, module_id: FileItemTreeId<Mod>, attrs: &Attrs) { @@ -1824,7 +1858,7 @@ impl ModCollector<'_, '_> { ModKind::Inline { items } => { let module_id = self.push_child_module( module.name.clone(), - AstId::new(self.file_id(), module.ast_id), + module.ast_id, None, &self.item_tree[module.visibility], module_id, @@ -1862,7 +1896,7 @@ impl ModCollector<'_, '_> { if is_enabled { let module_id = self.push_child_module( module.name.clone(), - ast_id, + ast_id.value, Some((file_id, is_mod_rs)), &self.item_tree[module.visibility], module_id, @@ -1889,7 +1923,7 @@ impl ModCollector<'_, '_> { Err(candidates) => { self.push_child_module( module.name.clone(), - ast_id, + ast_id.value, None, &self.item_tree[module.visibility], module_id, @@ -1906,7 +1940,7 @@ impl ModCollector<'_, '_> { fn push_child_module( &mut self, name: Name, - declaration: AstId<ast::Module>, + declaration: FileAstId<ast::Module>, definition: Option<(FileId, bool)>, visibility: &crate::visibility::RawVisibility, mod_tree_id: FileItemTreeId<Mod>, @@ -1948,9 +1982,9 @@ impl ModCollector<'_, '_> { def_map.modules[self.module_id].scope.declare(def); self.def_collector.update( self.module_id, - &[(Some(name), PerNs::from_def(def, vis, false))], + &[(Some(name), PerNs::from_def(def, vis, false, None))], vis, - ImportType::Named, + None, ); res } @@ -2198,7 +2232,7 @@ impl ModCollector<'_, '_> { map[module].scope.get_legacy_macro(name)?.last().copied() }) .or_else(|| def_map[self.module_id].scope.get(name).take_macros()) - .or_else(|| def_map.macro_use_prelude.get(name).copied()) + .or_else(|| Some(def_map.macro_use_prelude.get(name).copied()?.0)) .filter(|&id| { sub_namespace_match( Some(MacroSubNs::from_id(db, id)), diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/path_resolution.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/path_resolution.rs index de22ea101..460a908b6 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/path_resolution.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/path_resolution.rs @@ -15,8 +15,9 @@ use hir_expand::name::Name; use triomphe::Arc; use crate::{ + data::adt::VariantData, db::DefDatabase, - item_scope::BUILTIN_SCOPE, + item_scope::{ImportOrExternCrate, BUILTIN_SCOPE}, nameres::{sub_namespace_match, BlockInfo, BuiltinShadowMode, DefMap, MacroSubNs}, path::{ModPath, PathKind}, per_ns::PerNs, @@ -65,7 +66,7 @@ impl PerNs { db: &dyn DefDatabase, expected: Option<MacroSubNs>, ) -> Self { - self.macros = self.macros.filter(|&(id, _)| { + self.macros = self.macros.filter(|&(id, _, _)| { let this = MacroSubNs::from_id(db, id); sub_namespace_match(Some(this), expected) }); @@ -196,15 +197,15 @@ impl DefMap { PathKind::DollarCrate(krate) => { if krate == self.krate { cov_mark::hit!(macro_dollar_crate_self); - PerNs::types(self.crate_root().into(), Visibility::Public) + PerNs::types(self.crate_root().into(), Visibility::Public, None) } else { let def_map = db.crate_def_map(krate); let module = def_map.module_id(Self::ROOT); cov_mark::hit!(macro_dollar_crate_other); - PerNs::types(module.into(), Visibility::Public) + PerNs::types(module.into(), Visibility::Public, None) } } - PathKind::Crate => PerNs::types(self.crate_root().into(), Visibility::Public), + PathKind::Crate => PerNs::types(self.crate_root().into(), Visibility::Public, None), // plain import or absolute path in 2015: crate-relative with // fallback to extern prelude (with the simplification in // rust-lang/rust#57745) @@ -291,7 +292,7 @@ impl DefMap { ); } - PerNs::types(module.into(), Visibility::Public) + PerNs::types(module.into(), Visibility::Public, None) } PathKind::Abs => { // 2018-style absolute path -- only extern prelude @@ -299,9 +300,13 @@ impl DefMap { Some((_, segment)) => segment, None => return ResolvePathResult::empty(ReachedFixedPoint::Yes), }; - if let Some(&def) = self.data.extern_prelude.get(segment) { + if let Some(&(def, extern_crate)) = self.data.extern_prelude.get(segment) { tracing::debug!("absolute path {:?} resolved to crate {:?}", path, def); - PerNs::types(def.into(), Visibility::Public) + PerNs::types( + def.into(), + Visibility::Public, + extern_crate.map(ImportOrExternCrate::ExternCrate), + ) } else { return ResolvePathResult::empty(ReachedFixedPoint::No); // extern crate declarations can add to the extern prelude } @@ -309,7 +314,7 @@ impl DefMap { }; for (i, segment) in segments { - let (curr, vis) = match curr_per_ns.take_types_vis() { + let (curr, vis, imp) = match curr_per_ns.take_types_full() { Some(r) => r, None => { // we still have path segments left, but the path so far @@ -364,18 +369,20 @@ impl DefMap { Some(local_id) => { let variant = EnumVariantId { parent: e, local_id }; match &*enum_data.variants[local_id].variant_data { - crate::data::adt::VariantData::Record(_) => { - PerNs::types(variant.into(), Visibility::Public) - } - crate::data::adt::VariantData::Tuple(_) - | crate::data::adt::VariantData::Unit => { - PerNs::both(variant.into(), variant.into(), Visibility::Public) + VariantData::Record(_) => { + PerNs::types(variant.into(), Visibility::Public, None) } + VariantData::Tuple(_) | VariantData::Unit => PerNs::both( + variant.into(), + variant.into(), + Visibility::Public, + None, + ), } } None => { return ResolvePathResult::with( - PerNs::types(e.into(), vis), + PerNs::types(e.into(), vis, imp), ReachedFixedPoint::Yes, Some(i), Some(self.krate), @@ -393,7 +400,7 @@ impl DefMap { ); return ResolvePathResult::with( - PerNs::types(s, vis), + PerNs::types(s, vis, imp), ReachedFixedPoint::Yes, Some(i), Some(self.krate), @@ -430,7 +437,7 @@ impl DefMap { .filter(|&id| { sub_namespace_match(Some(MacroSubNs::from_id(db, id)), expected_macro_subns) }) - .map_or_else(PerNs::none, |m| PerNs::macros(m, Visibility::Public)); + .map_or_else(PerNs::none, |m| PerNs::macros(m, Visibility::Public, None)); let from_scope = self[module].scope.get(name).filter_macro(db, expected_macro_subns); let from_builtin = match self.block { Some(_) => { @@ -449,18 +456,27 @@ impl DefMap { let extern_prelude = || { if self.block.is_some() { - // Don't resolve extern prelude in block `DefMap`s. + // Don't resolve extern prelude in block `DefMap`s, defer it to the crate def map so + // that blocks can properly shadow them return PerNs::none(); } - self.data - .extern_prelude - .get(name) - .map_or(PerNs::none(), |&it| PerNs::types(it.into(), Visibility::Public)) + self.data.extern_prelude.get(name).map_or(PerNs::none(), |&(it, extern_crate)| { + PerNs::types( + it.into(), + Visibility::Public, + extern_crate.map(ImportOrExternCrate::ExternCrate), + ) + }) }; let macro_use_prelude = || { - self.macro_use_prelude - .get(name) - .map_or(PerNs::none(), |&it| PerNs::macros(it.into(), Visibility::Public)) + self.macro_use_prelude.get(name).map_or(PerNs::none(), |&(it, _extern_crate)| { + PerNs::macros( + it.into(), + Visibility::Public, + // FIXME? + None, // extern_crate.map(ImportOrExternCrate::ExternCrate), + ) + }) }; let prelude = || self.resolve_in_prelude(db, name); @@ -488,18 +504,23 @@ impl DefMap { // Don't resolve extern prelude in block `DefMap`s. return PerNs::none(); } - self.data - .extern_prelude - .get(name) - .copied() - .map_or(PerNs::none(), |it| PerNs::types(it.into(), Visibility::Public)) + self.data.extern_prelude.get(name).copied().map_or( + PerNs::none(), + |(it, extern_crate)| { + PerNs::types( + it.into(), + Visibility::Public, + extern_crate.map(ImportOrExternCrate::ExternCrate), + ) + }, + ) }; from_crate_root.or_else(from_extern_prelude) } fn resolve_in_prelude(&self, db: &dyn DefDatabase, name: &Name) -> PerNs { - if let Some(prelude) = self.prelude { + if let Some((prelude, _use)) = self.prelude { let keep; let def_map = if prelude.krate == self.krate { self diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests.rs index dd7c3c363..e7cc44b04 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests.rs @@ -168,7 +168,7 @@ pub struct Baz; "#, expect![[r#" crate - Foo: t v + Foo: ti vi foo: t crate::foo @@ -194,8 +194,8 @@ pub enum Quux {}; "#, expect![[r#" crate - Baz: t v - Quux: t + Baz: ti vi + Quux: ti foo: t crate::foo @@ -225,11 +225,11 @@ pub struct Baz; "#, expect![[r#" crate - Baz: t v + Baz: ti vi foo: t crate::foo - Baz: t v + Baz: ti vi bar: t crate::foo::bar @@ -274,7 +274,7 @@ use self::E::V; expect![[r#" crate E: t - V: t v + V: ti vi "#]], ); } @@ -307,7 +307,7 @@ pub struct FromLib; crate::foo Bar: _ - FromLib: t v + FromLib: ti vi "#]], ); } @@ -328,7 +328,7 @@ pub struct Baz; "#, expect![[r#" crate - Baz: t + Baz: ti foo: t crate::foo @@ -352,7 +352,7 @@ pub struct Baz; "#, expect![[r#" crate - Baz: t v + Baz: ti vi "#]], ); } @@ -375,13 +375,13 @@ pub struct Arc; expect![[r#" crate alloc: t - alloc_crate: t + alloc_crate: te sync: t crate::alloc crate::sync - Arc: t v + Arc: ti vi "#]], ); } @@ -404,13 +404,13 @@ pub struct Arc; expect![[r#" crate alloc: t - alloc_crate: t + alloc_crate: te sync: t crate::alloc crate::sync - Arc: t v + Arc: ti vi "#]], ); } @@ -426,7 +426,7 @@ extern crate self as bla; "#, expect![[r#" crate - bla: t + bla: te "#]], ); } @@ -447,7 +447,7 @@ pub struct Baz; "#, expect![[r#" crate - Baz: t v + Baz: ti vi "#]], ); } @@ -465,7 +465,7 @@ pub struct Bar; "#, expect![[r#" crate - Bar: t v + Bar: ti vi foo: v "#]], ); @@ -492,9 +492,9 @@ fn no_std_prelude() { } "#, expect![[r#" - crate - Rust: t v - "#]], + crate + Rust: ti vi + "#]], ); } @@ -516,9 +516,9 @@ fn edition_specific_preludes() { } "#, expect![[r#" - crate - Rust2018: t v - "#]], + crate + Rust2018: ti vi + "#]], ); check( r#" @@ -533,9 +533,9 @@ fn edition_specific_preludes() { } "#, expect![[r#" - crate - Rust2021: t v - "#]], + crate + Rust2021: ti vi + "#]], ); } @@ -563,8 +563,8 @@ pub mod prelude { "#, expect![[r#" crate - Bar: t v - Foo: t v + Bar: ti vi + Foo: ti vi "#]], ); } @@ -590,7 +590,7 @@ pub mod prelude { "#, expect![[r#" crate - Bar: t v + Bar: ti vi Baz: _ Foo: _ "#]], @@ -619,8 +619,8 @@ pub mod prelude { expect![[r#" crate Bar: _ - Baz: t v - Foo: t v + Baz: ti vi + Foo: ti vi "#]], ); } @@ -643,7 +643,7 @@ mod b { "#, expect![[r#" crate - T: t v + T: ti vi a: t b: t @@ -816,8 +816,8 @@ fn bar() {} expect![[r#" crate bar: v - baz: v - foo: t + baz: vi + foo: ti "#]], ); } @@ -836,7 +836,7 @@ use self::m::S::{self}; "#, expect![[r#" crate - S: t + S: ti m: t crate::m @@ -860,8 +860,8 @@ pub const settings: () = (); "#, expect![[r#" crate - Settings: t v - settings: v + Settings: ti vi + settings: vi "#]], ) } @@ -890,8 +890,8 @@ pub struct Struct; "#, expect![[r#" crate - Struct: t v - dep: t + Struct: ti vi + dep: te "#]], ); } @@ -917,13 +917,13 @@ use some_module::unknown_func; crate other_module: t some_module: t - unknown_func: v + unknown_func: vi crate::other_module some_submodule: t crate::other_module::some_submodule - unknown_func: v + unknown_func: vi crate::some_module unknown_func: v diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/globs.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/globs.rs index 88a3c7639..1ca74b5da 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/globs.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/globs.rs @@ -24,7 +24,7 @@ pub struct Baz; foo: t crate::foo - Baz: t v + Baz: ti vi Foo: t v bar: t @@ -237,9 +237,9 @@ pub mod baz { pub struct Bar; } "#, expect![[r#" crate - Bar: t v + Bar: ti vi bar: t - baz: t + baz: ti foo: t crate::bar @@ -276,9 +276,9 @@ pub mod baz { pub struct Bar; } "#, expect![[r#" crate - Bar: t v + Bar: ti vi bar: t - baz: t + baz: ti foo: t crate::bar @@ -323,7 +323,7 @@ mod d { X: t v crate::b - foo: t + foo: ti crate::c foo: t @@ -332,8 +332,8 @@ mod d { Y: t v crate::d - Y: t v - foo: t + Y: ti vi + foo: ti "#]], ); } @@ -355,7 +355,7 @@ use event::Event; "#, expect![[r#" crate - Event: t + Event: ti event: t crate::event diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs index 40d3a1654..4a86f88e5 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs @@ -212,7 +212,7 @@ pub type Ty = (); } for (_, res) in module_data.scope.resolutions() { - match res.values.or(res.types).unwrap().0 { + match res.values.map(|(a, _, _)| a).or(res.types.map(|(a, _, _)| a)).unwrap() { ModuleDefId::FunctionId(f) => _ = db.function_data(f), ModuleDefId::AdtId(adt) => match adt { AdtId::StructId(it) => _ = db.struct_data(it), diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/macros.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/macros.rs index f4cca8d68..e64fa0b46 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/macros.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/macros.rs @@ -203,8 +203,8 @@ macro_rules! bar { expect![[r#" crate Foo: t - bar: m - foo: m + bar: mi + foo: mi "#]], ); } @@ -251,7 +251,7 @@ mod priv_mod { Bar: t v Foo: t v bar: t - foo: t + foo: te crate::bar Baz: t v @@ -318,9 +318,9 @@ macro_rules! baz3 { () => { struct OkBaz3; } } OkBaz1: t v OkBaz2: t v OkBaz3: t v - all: t - empty: t - multiple: t + all: te + empty: te + multiple: te "#]], ); } @@ -551,8 +551,8 @@ fn baz() {} "#, expect![[r#" crate - bar: t m - baz: t v m + bar: ti mi + baz: ti v mi foo: t m "#]], ); @@ -583,7 +583,7 @@ mod m { crate Alias: t v Direct: t v - foo: t + foo: te "#]], ); } @@ -628,9 +628,9 @@ mod m { m: t crate::m - alias1: m - alias2: m - alias3: m + alias1: mi + alias2: mi + alias3: mi not_found: _ "#]], ); @@ -682,11 +682,11 @@ pub struct Baz; "#, expect![[r#" crate - Bar: t v - Baz: t v + Bar: ti vi + Baz: ti vi Foo: t v - FooSelf: t v - foo: t + FooSelf: ti vi + foo: te m: t crate::m @@ -725,7 +725,7 @@ pub struct bar; "#, expect![[r#" crate - bar: t v + bar: ti vi "#]], ); } @@ -1340,7 +1340,7 @@ pub mod prelude { crate Ok: t v bar: m - dep: t + dep: te foo: m ok: v "#]], @@ -1370,13 +1370,13 @@ macro_rules! mk_foo { } "#, expect![[r#" - crate - a: t - lib: t + crate + a: t + lib: te - crate::a - Ok: t v - "#]], + crate::a + Ok: t v + "#]], ); } @@ -1427,8 +1427,8 @@ pub mod prelude { expect![[r#" crate Ok: t v - bar: m - foo: m + bar: mi + foo: mi ok: v "#]], ); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/mod_resolution.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/mod_resolution.rs index 81bc0ff91..1327d9aa6 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/mod_resolution.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/mod_resolution.rs @@ -80,18 +80,18 @@ pub trait Iterator; prelude: t crate::iter - Iterator: t + Iterator: ti traits: t crate::iter::traits - Iterator: t + Iterator: ti iterator: t crate::iter::traits::iterator Iterator: t crate::prelude - Iterator: t + Iterator: ti "#]], ); } @@ -109,7 +109,7 @@ pub struct Bar; "#, expect![[r#" crate - Bar: t v + Bar: ti vi foo: t crate::foo @@ -139,7 +139,7 @@ pub struct Baz; "#, expect![[r#" crate - Bar: t v + Bar: ti vi r#async: t crate::r#async @@ -176,8 +176,8 @@ pub struct Bar; "#, expect![[r#" crate - Bar: t v - Foo: t v + Bar: ti vi + Foo: ti vi r#async: t crate::r#async @@ -207,7 +207,7 @@ pub struct Bar; "#, expect![[r#" crate - Bar: t v + Bar: ti vi foo: t crate::foo @@ -236,7 +236,7 @@ pub struct Baz; foo: t crate::foo - Baz: t v + Baz: ti vi bar: t crate::foo::bar @@ -265,7 +265,7 @@ pub struct Baz; foo: t crate::foo - Baz: t v + Baz: ti vi bar: t crate::foo::bar @@ -292,7 +292,7 @@ use super::Baz; foo: t crate::foo - Baz: t v + Baz: ti vi "#]], ); } @@ -626,7 +626,7 @@ pub struct Baz; "#, expect![[r#" crate - Baz: t v + Baz: ti vi foo: t crate::foo @@ -660,7 +660,7 @@ pub struct Baz; foo: t crate::foo - Baz: t v + Baz: ti vi bar: t crate::foo::bar @@ -694,7 +694,7 @@ pub struct Baz; foo: t crate::foo - Baz: t v + Baz: ti vi bar: t crate::foo::bar @@ -728,7 +728,7 @@ pub struct Baz; foo: t crate::foo - Baz: t v + Baz: ti vi bar: t crate::foo::bar @@ -868,7 +868,7 @@ pub mod hash { pub trait Hash {} } "#, expect![[r#" crate - Hash: t + Hash: ti core: t crate::core diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/primitives.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/primitives.rs index 215e8952d..271eb1c79 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/primitives.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/primitives.rs @@ -14,10 +14,10 @@ pub use i32 as int; expect![[r#" crate foo: t - int: t + int: ti crate::foo - int: t + int: ti "#]], ); } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/path.rs b/src/tools/rust-analyzer/crates/hir-def/src/path.rs index 06530cc7e..3894172a5 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/path.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/path.rs @@ -47,7 +47,7 @@ pub enum Path { }, /// A link to a lang item. It is used in desugaring of things like `it?`. We can show these /// links via a normal path since they might be private and not accessible in the usage place. - LangItem(LangItemTarget), + LangItem(LangItemTarget, Option<Name>), } /// Generic arguments to a path segment (e.g. the `i32` in `Option<i32>`). This @@ -122,33 +122,40 @@ impl Path { pub fn kind(&self) -> &PathKind { match self { Path::Normal { mod_path, .. } => &mod_path.kind, - Path::LangItem(_) => &PathKind::Abs, + Path::LangItem(..) => &PathKind::Abs, } } pub fn type_anchor(&self) -> Option<&TypeRef> { match self { Path::Normal { type_anchor, .. } => type_anchor.as_deref(), - Path::LangItem(_) => None, + Path::LangItem(..) => None, } } pub fn segments(&self) -> PathSegments<'_> { - let Path::Normal { mod_path, generic_args, .. } = self else { - return PathSegments { segments: &[], generic_args: None }; - }; - let s = - PathSegments { segments: mod_path.segments(), generic_args: generic_args.as_deref() }; - if let Some(generic_args) = s.generic_args { - assert_eq!(s.segments.len(), generic_args.len()); + match self { + Path::Normal { mod_path, generic_args, .. } => { + let s = PathSegments { + segments: mod_path.segments(), + generic_args: generic_args.as_deref(), + }; + if let Some(generic_args) = s.generic_args { + assert_eq!(s.segments.len(), generic_args.len()); + } + s + } + Path::LangItem(_, seg) => PathSegments { + segments: seg.as_ref().map_or(&[], |seg| std::slice::from_ref(seg)), + generic_args: None, + }, } - s } pub fn mod_path(&self) -> Option<&ModPath> { match self { Path::Normal { mod_path, .. } => Some(&mod_path), - Path::LangItem(_) => None, + Path::LangItem(..) => None, } } 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 2bc1f8e92..14890364d 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,13 +3,24 @@ //! //! `PerNs` (per namespace) captures this. -use crate::{item_scope::ItemInNs, visibility::Visibility, MacroId, ModuleDefId}; +use crate::{ + item_scope::{ImportId, ImportOrExternCrate, ItemInNs}, + visibility::Visibility, + MacroId, ModuleDefId, +}; + +#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] +pub enum Namespace { + Types, + Values, + Macros, +} #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct PerNs { - pub types: Option<(ModuleDefId, Visibility)>, - pub values: Option<(ModuleDefId, Visibility)>, - pub macros: Option<(MacroId, Visibility)>, + pub types: Option<(ModuleDefId, Visibility, Option<ImportOrExternCrate>)>, + pub values: Option<(ModuleDefId, Visibility, Option<ImportId>)>, + pub macros: Option<(MacroId, Visibility, Option<ImportId>)>, } impl Default for PerNs { @@ -23,20 +34,29 @@ impl PerNs { PerNs { types: None, values: None, macros: None } } - pub fn values(t: ModuleDefId, v: Visibility) -> PerNs { - PerNs { types: None, values: Some((t, v)), macros: None } + pub fn values(t: ModuleDefId, v: Visibility, i: Option<ImportId>) -> PerNs { + PerNs { types: None, values: Some((t, v, i)), macros: None } } - pub fn types(t: ModuleDefId, v: Visibility) -> PerNs { - PerNs { types: Some((t, v)), values: None, macros: None } + pub fn types(t: ModuleDefId, v: Visibility, i: Option<ImportOrExternCrate>) -> PerNs { + PerNs { types: Some((t, v, i)), values: None, macros: None } } - pub fn both(types: ModuleDefId, values: ModuleDefId, v: Visibility) -> PerNs { - PerNs { types: Some((types, v)), values: Some((values, v)), macros: None } + pub fn both( + types: ModuleDefId, + values: ModuleDefId, + v: Visibility, + i: Option<ImportOrExternCrate>, + ) -> PerNs { + PerNs { + types: Some((types, v, i)), + values: Some((values, v, i.and_then(ImportOrExternCrate::into_import))), + macros: None, + } } - pub fn macros(macro_: MacroId, v: Visibility) -> PerNs { - PerNs { types: None, values: None, macros: Some((macro_, v)) } + pub fn macros(macro_: MacroId, v: Visibility, i: Option<ImportId>) -> PerNs { + PerNs { types: None, values: None, macros: Some((macro_, v, i)) } } pub fn is_none(&self) -> bool { @@ -51,7 +71,7 @@ impl PerNs { self.types.map(|it| it.0) } - pub fn take_types_vis(self) -> Option<(ModuleDefId, Visibility)> { + pub fn take_types_full(self) -> Option<(ModuleDefId, Visibility, Option<ImportOrExternCrate>)> { self.types } @@ -59,24 +79,32 @@ impl PerNs { self.values.map(|it| it.0) } + pub fn take_values_import(self) -> Option<(ModuleDefId, Option<ImportId>)> { + self.values.map(|it| (it.0, it.2)) + } + pub fn take_macros(self) -> Option<MacroId> { self.macros.map(|it| it.0) } + pub fn take_macros_import(self) -> Option<(MacroId, Option<ImportId>)> { + self.macros.map(|it| (it.0, it.2)) + } + pub fn filter_visibility(self, mut f: impl FnMut(Visibility) -> bool) -> PerNs { let _p = profile::span("PerNs::filter_visibility"); PerNs { - types: self.types.filter(|(_, v)| f(*v)), - values: self.values.filter(|(_, v)| f(*v)), - macros: self.macros.filter(|(_, v)| f(*v)), + types: self.types.filter(|&(_, v, _)| f(v)), + values: self.values.filter(|&(_, v, _)| f(v)), + macros: self.macros.filter(|&(_, v, _)| f(v)), } } pub fn with_visibility(self, vis: Visibility) -> PerNs { PerNs { - types: self.types.map(|(it, _)| (it, vis)), - values: self.values.map(|(it, _)| (it, vis)), - macros: self.macros.map(|(it, _)| (it, vis)), + types: self.types.map(|(it, _, c)| (it, vis, c)), + values: self.values.map(|(it, _, c)| (it, vis, c)), + macros: self.macros.map(|(it, _, import)| (it, vis, import)), } } @@ -96,12 +124,20 @@ impl PerNs { } } - pub fn iter_items(self) -> impl Iterator<Item = ItemInNs> { + pub fn iter_items(self) -> impl Iterator<Item = (ItemInNs, Option<ImportOrExternCrate>)> { let _p = profile::span("PerNs::iter_items"); self.types - .map(|it| ItemInNs::Types(it.0)) + .map(|it| (ItemInNs::Types(it.0), it.2)) .into_iter() - .chain(self.values.map(|it| ItemInNs::Values(it.0)).into_iter()) - .chain(self.macros.map(|it| ItemInNs::Macros(it.0)).into_iter()) + .chain( + self.values + .map(|it| (ItemInNs::Values(it.0), it.2.map(ImportOrExternCrate::Import))) + .into_iter(), + ) + .chain( + self.macros + .map(|it| (ItemInNs::Macros(it.0), it.2.map(ImportOrExternCrate::Import))) + .into_iter(), + ) } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/pretty.rs b/src/tools/rust-analyzer/crates/hir-def/src/pretty.rs index 11d58a6ba..f4f5541e3 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/pretty.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/pretty.rs @@ -2,18 +2,54 @@ use std::fmt::{self, Write}; -use hir_expand::{db::ExpandDatabase, mod_path::PathKind}; +use hir_expand::mod_path::PathKind; use intern::Interned; use itertools::Itertools; use crate::{ + db::DefDatabase, + lang_item::LangItemTarget, path::{GenericArg, GenericArgs, Path}, type_ref::{Mutability, TraitBoundModifier, TypeBound, TypeRef}, }; -pub(crate) fn print_path(db: &dyn ExpandDatabase, path: &Path, buf: &mut dyn Write) -> fmt::Result { - if let Path::LangItem(it) = path { - return write!(buf, "$lang_item::{it:?}"); +pub(crate) fn print_path(db: &dyn DefDatabase, path: &Path, buf: &mut dyn Write) -> fmt::Result { + if let Path::LangItem(it, s) = path { + write!(buf, "builtin#lang(")?; + match *it { + LangItemTarget::ImplDef(it) => write!(buf, "{it:?}")?, + LangItemTarget::EnumId(it) => { + write!(buf, "{}", db.enum_data(it).name.display(db.upcast()))? + } + LangItemTarget::Function(it) => { + write!(buf, "{}", db.function_data(it).name.display(db.upcast()))? + } + LangItemTarget::Static(it) => { + write!(buf, "{}", db.static_data(it).name.display(db.upcast()))? + } + LangItemTarget::Struct(it) => { + write!(buf, "{}", db.struct_data(it).name.display(db.upcast()))? + } + LangItemTarget::Union(it) => { + write!(buf, "{}", db.union_data(it).name.display(db.upcast()))? + } + LangItemTarget::TypeAlias(it) => { + write!(buf, "{}", db.type_alias_data(it).name.display(db.upcast()))? + } + LangItemTarget::Trait(it) => { + write!(buf, "{}", db.trait_data(it).name.display(db.upcast()))? + } + LangItemTarget::EnumVariant(it) => write!( + buf, + "{}", + db.enum_data(it.parent).variants[it.local_id].name.display(db.upcast()) + )?, + } + + if let Some(s) = s { + write!(buf, "::{}", s.display(db.upcast()))?; + } + return write!(buf, ")"); } match path.type_anchor() { Some(anchor) => { @@ -44,7 +80,7 @@ pub(crate) fn print_path(db: &dyn ExpandDatabase, path: &Path, buf: &mut dyn Wri write!(buf, "::")?; } - write!(buf, "{}", segment.name.display(db))?; + write!(buf, "{}", segment.name.display(db.upcast()))?; if let Some(generics) = segment.args_and_bindings { write!(buf, "::<")?; print_generic_args(db, generics, buf)?; @@ -57,7 +93,7 @@ pub(crate) fn print_path(db: &dyn ExpandDatabase, path: &Path, buf: &mut dyn Wri } pub(crate) fn print_generic_args( - db: &dyn ExpandDatabase, + db: &dyn DefDatabase, generics: &GenericArgs, buf: &mut dyn Write, ) -> fmt::Result { @@ -83,7 +119,7 @@ pub(crate) fn print_generic_args( write!(buf, ", ")?; } first = false; - write!(buf, "{}", binding.name.display(db))?; + write!(buf, "{}", binding.name.display(db.upcast()))?; if !binding.bounds.is_empty() { write!(buf, ": ")?; print_type_bounds(db, &binding.bounds, buf)?; @@ -97,19 +133,19 @@ pub(crate) fn print_generic_args( } pub(crate) fn print_generic_arg( - db: &dyn ExpandDatabase, + db: &dyn DefDatabase, arg: &GenericArg, buf: &mut dyn Write, ) -> fmt::Result { match arg { GenericArg::Type(ty) => print_type_ref(db, ty, buf), - GenericArg::Const(c) => write!(buf, "{}", c.display(db)), - GenericArg::Lifetime(lt) => write!(buf, "{}", lt.name.display(db)), + GenericArg::Const(c) => write!(buf, "{}", c.display(db.upcast())), + GenericArg::Lifetime(lt) => write!(buf, "{}", lt.name.display(db.upcast())), } } pub(crate) fn print_type_ref( - db: &dyn ExpandDatabase, + db: &dyn DefDatabase, type_ref: &TypeRef, buf: &mut dyn Write, ) -> fmt::Result { @@ -143,7 +179,7 @@ pub(crate) fn print_type_ref( }; write!(buf, "&")?; if let Some(lt) = lt { - write!(buf, "{} ", lt.name.display(db))?; + write!(buf, "{} ", lt.name.display(db.upcast()))?; } write!(buf, "{mtbl}")?; print_type_ref(db, pointee, buf)?; @@ -151,7 +187,7 @@ pub(crate) fn print_type_ref( TypeRef::Array(elem, len) => { write!(buf, "[")?; print_type_ref(db, elem, buf)?; - write!(buf, "; {}]", len.display(db))?; + write!(buf, "; {}]", len.display(db.upcast()))?; } TypeRef::Slice(elem) => { write!(buf, "[")?; @@ -198,7 +234,7 @@ pub(crate) fn print_type_ref( } pub(crate) fn print_type_bounds( - db: &dyn ExpandDatabase, + db: &dyn DefDatabase, bounds: &[Interned<TypeBound>], buf: &mut dyn Write, ) -> fmt::Result { @@ -216,10 +252,14 @@ pub(crate) fn print_type_bounds( print_path(db, path, buf)?; } TypeBound::ForLifetime(lifetimes, path) => { - write!(buf, "for<{}> ", lifetimes.iter().map(|it| it.display(db)).format(", "))?; + write!( + buf, + "for<{}> ", + lifetimes.iter().map(|it| it.display(db.upcast())).format(", ") + )?; print_path(db, path, buf)?; } - TypeBound::Lifetime(lt) => write!(buf, "{}", lt.name.display(db))?, + TypeBound::Lifetime(lt) => write!(buf, "{}", lt.name.display(db.upcast()))?, TypeBound::Error => write!(buf, "{{unknown}}")?, } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs b/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs index b112c1070..50da9ed06 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs @@ -12,20 +12,21 @@ use triomphe::Arc; use crate::{ body::scope::{ExprScopes, ScopeId}, builtin_type::BuiltinType, + data::ExternCrateDeclData, db::DefDatabase, generics::{GenericParams, TypeOrConstParamData}, hir::{BindingId, ExprId, LabelId}, - item_scope::{BuiltinShadowMode, BUILTIN_SCOPE}, + item_scope::{BuiltinShadowMode, ImportId, ImportOrExternCrate, BUILTIN_SCOPE}, lang_item::LangItemTarget, nameres::{DefMap, MacroSubNs}, path::{ModPath, Path, PathKind}, per_ns::PerNs, visibility::{RawVisibility, Visibility}, - AdtId, AssocItemId, ConstId, ConstParamId, CrateRootModuleId, DefWithBodyId, EnumId, - EnumVariantId, ExternBlockId, ExternCrateId, FunctionId, GenericDefId, GenericParamId, - HasModule, ImplId, ItemContainerId, LifetimeParamId, LocalModuleId, Lookup, Macro2Id, MacroId, - MacroRulesId, ModuleDefId, ModuleId, ProcMacroId, StaticId, StructId, TraitAliasId, TraitId, - TypeAliasId, TypeOrConstParamId, TypeOwnerId, TypeParamId, UseId, VariantId, + AdtId, ConstId, ConstParamId, CrateRootModuleId, DefWithBodyId, EnumId, EnumVariantId, + ExternBlockId, ExternCrateId, FunctionId, GenericDefId, GenericParamId, HasModule, ImplId, + ItemContainerId, LifetimeParamId, LocalModuleId, Lookup, Macro2Id, MacroId, MacroRulesId, + ModuleDefId, ModuleId, ProcMacroId, StaticId, StructId, TraitAliasId, TraitId, TypeAliasId, + TypeOrConstParamId, TypeOwnerId, TypeParamId, UseId, VariantId, }; #[derive(Debug, Clone)] @@ -100,8 +101,8 @@ pub enum TypeNs { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ResolveValueResult { - ValueNs(ValueNs), - Partial(TypeNs, usize), + ValueNs(ValueNs, Option<ImportId>), + Partial(TypeNs, usize, Option<ImportOrExternCrate>), } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] @@ -148,56 +149,26 @@ impl Resolver { self.resolve_module_path(db, path, BuiltinShadowMode::Module) } - // FIXME: This shouldn't exist - pub fn resolve_module_path_in_trait_assoc_items( - &self, - db: &dyn DefDatabase, - path: &ModPath, - ) -> Option<PerNs> { - let (item_map, module) = self.item_scope(); - let (module_res, idx) = - item_map.resolve_path(db, module, path, BuiltinShadowMode::Module, None); - match module_res.take_types()? { - ModuleDefId::TraitId(it) => { - let idx = idx?; - let unresolved = &path.segments()[idx..]; - let assoc = match unresolved { - [it] => it, - _ => return None, - }; - let &(_, assoc) = db.trait_data(it).items.iter().find(|(n, _)| n == assoc)?; - Some(match assoc { - AssocItemId::FunctionId(it) => PerNs::values(it.into(), Visibility::Public), - AssocItemId::ConstId(it) => PerNs::values(it.into(), Visibility::Public), - AssocItemId::TypeAliasId(it) => PerNs::types(it.into(), Visibility::Public), - }) - } - _ => None, - } - } - pub fn resolve_path_in_type_ns( &self, db: &dyn DefDatabase, path: &Path, - ) -> Option<(TypeNs, Option<usize>)> { + ) -> Option<(TypeNs, Option<usize>, Option<ImportOrExternCrate>)> { let path = match path { Path::Normal { mod_path, .. } => mod_path, - Path::LangItem(l) => { - return Some(( - match *l { - LangItemTarget::Union(it) => TypeNs::AdtId(it.into()), - LangItemTarget::TypeAlias(it) => TypeNs::TypeAliasId(it), - LangItemTarget::Struct(it) => TypeNs::AdtId(it.into()), - LangItemTarget::EnumVariant(it) => TypeNs::EnumVariantId(it), - LangItemTarget::EnumId(it) => TypeNs::AdtId(it.into()), - LangItemTarget::Trait(it) => TypeNs::TraitId(it), - LangItemTarget::Function(_) - | LangItemTarget::ImplDef(_) - | LangItemTarget::Static(_) => return None, - }, - None, - )) + Path::LangItem(l, seg) => { + let type_ns = match *l { + LangItemTarget::Union(it) => TypeNs::AdtId(it.into()), + LangItemTarget::TypeAlias(it) => TypeNs::TypeAliasId(it), + LangItemTarget::Struct(it) => TypeNs::AdtId(it.into()), + LangItemTarget::EnumVariant(it) => TypeNs::EnumVariantId(it), + LangItemTarget::EnumId(it) => TypeNs::AdtId(it.into()), + LangItemTarget::Trait(it) => TypeNs::TraitId(it), + LangItemTarget::Function(_) + | LangItemTarget::ImplDef(_) + | LangItemTarget::Static(_) => return None, + }; + return Some((type_ns, seg.as_ref().map(|_| 1), None)); } }; let first_name = path.segments().first()?; @@ -213,17 +184,17 @@ impl Resolver { Scope::ExprScope(_) => continue, Scope::GenericParams { params, def } => { if let Some(id) = params.find_type_by_name(first_name, *def) { - return Some((TypeNs::GenericParam(id), remaining_idx())); + return Some((TypeNs::GenericParam(id), remaining_idx(), None)); } } &Scope::ImplDefScope(impl_) => { if first_name == &name![Self] { - return Some((TypeNs::SelfType(impl_), remaining_idx())); + return Some((TypeNs::SelfType(impl_), remaining_idx(), None)); } } &Scope::AdtScope(adt) => { if first_name == &name![Self] { - return Some((TypeNs::AdtSelfType(adt), remaining_idx())); + return Some((TypeNs::AdtSelfType(adt), remaining_idx(), None)); } } Scope::BlockScope(m) => { @@ -236,12 +207,24 @@ impl Resolver { self.module_scope.resolve_path_in_type_ns(db, path) } + pub fn resolve_path_in_type_ns_fully_with_imports( + &self, + db: &dyn DefDatabase, + path: &Path, + ) -> Option<(TypeNs, Option<ImportOrExternCrate>)> { + let (res, unresolved, imp) = self.resolve_path_in_type_ns(db, path)?; + if unresolved.is_some() { + return None; + } + Some((res, imp)) + } + pub fn resolve_path_in_type_ns_fully( &self, db: &dyn DefDatabase, path: &Path, ) -> Option<TypeNs> { - let (res, unresolved) = self.resolve_path_in_type_ns(db, path)?; + let (res, unresolved, _) = self.resolve_path_in_type_ns(db, path)?; if unresolved.is_some() { return None; } @@ -263,7 +246,6 @@ impl Resolver { RawVisibility::Public => Some(Visibility::Public), } } - pub fn resolve_path_in_value_ns( &self, db: &dyn DefDatabase, @@ -271,18 +253,35 @@ impl Resolver { ) -> Option<ResolveValueResult> { let path = match path { Path::Normal { mod_path, .. } => mod_path, - Path::LangItem(l) => { - return Some(ResolveValueResult::ValueNs(match *l { - LangItemTarget::Function(it) => ValueNs::FunctionId(it), - LangItemTarget::Static(it) => ValueNs::StaticId(it), - LangItemTarget::Struct(it) => ValueNs::StructId(it), - LangItemTarget::EnumVariant(it) => ValueNs::EnumVariantId(it), - LangItemTarget::Union(_) + Path::LangItem(l, None) => { + return Some(ResolveValueResult::ValueNs( + match *l { + LangItemTarget::Function(it) => ValueNs::FunctionId(it), + LangItemTarget::Static(it) => ValueNs::StaticId(it), + LangItemTarget::Struct(it) => ValueNs::StructId(it), + LangItemTarget::EnumVariant(it) => ValueNs::EnumVariantId(it), + LangItemTarget::Union(_) + | LangItemTarget::ImplDef(_) + | LangItemTarget::TypeAlias(_) + | LangItemTarget::Trait(_) + | LangItemTarget::EnumId(_) => return None, + }, + None, + )) + } + Path::LangItem(l, Some(_)) => { + let type_ns = match *l { + LangItemTarget::Union(it) => TypeNs::AdtId(it.into()), + LangItemTarget::TypeAlias(it) => TypeNs::TypeAliasId(it), + LangItemTarget::Struct(it) => TypeNs::AdtId(it.into()), + LangItemTarget::EnumVariant(it) => TypeNs::EnumVariantId(it), + LangItemTarget::EnumId(it) => TypeNs::AdtId(it.into()), + LangItemTarget::Trait(it) => TypeNs::TraitId(it), + LangItemTarget::Function(_) | LangItemTarget::ImplDef(_) - | LangItemTarget::TypeAlias(_) - | LangItemTarget::Trait(_) - | LangItemTarget::EnumId(_) => return None, - })) + | LangItemTarget::Static(_) => return None, + }; + return Some(ResolveValueResult::Partial(type_ns, 1, None)); } }; let n_segments = path.segments().len(); @@ -304,20 +303,24 @@ impl Resolver { .find(|entry| entry.name() == first_name); if let Some(e) = entry { - return Some(ResolveValueResult::ValueNs(ValueNs::LocalBinding( - e.binding(), - ))); + return Some(ResolveValueResult::ValueNs( + ValueNs::LocalBinding(e.binding()), + None, + )); } } Scope::GenericParams { params, def } => { if let Some(id) = params.find_const_by_name(first_name, *def) { let val = ValueNs::GenericParam(id); - return Some(ResolveValueResult::ValueNs(val)); + return Some(ResolveValueResult::ValueNs(val, None)); } } &Scope::ImplDefScope(impl_) => { if first_name == &name![Self] { - return Some(ResolveValueResult::ValueNs(ValueNs::ImplSelf(impl_))); + return Some(ResolveValueResult::ValueNs( + ValueNs::ImplSelf(impl_), + None, + )); } } // bare `Self` doesn't work in the value namespace in a struct/enum definition @@ -336,18 +339,22 @@ impl Resolver { Scope::GenericParams { params, def } => { if let Some(id) = params.find_type_by_name(first_name, *def) { let ty = TypeNs::GenericParam(id); - return Some(ResolveValueResult::Partial(ty, 1)); + return Some(ResolveValueResult::Partial(ty, 1, None)); } } &Scope::ImplDefScope(impl_) => { if first_name == &name![Self] { - return Some(ResolveValueResult::Partial(TypeNs::SelfType(impl_), 1)); + return Some(ResolveValueResult::Partial( + TypeNs::SelfType(impl_), + 1, + None, + )); } } Scope::AdtScope(adt) => { if first_name == &name![Self] { let ty = TypeNs::AdtSelfType(*adt); - return Some(ResolveValueResult::Partial(ty, 1)); + return Some(ResolveValueResult::Partial(ty, 1, None)); } } Scope::BlockScope(m) => { @@ -368,7 +375,7 @@ impl Resolver { // `use core::u16;`. if path.kind == PathKind::Plain && n_segments > 1 { if let Some(builtin) = BuiltinType::by_name(first_name) { - return Some(ResolveValueResult::Partial(TypeNs::BuiltinType(builtin), 1)); + return Some(ResolveValueResult::Partial(TypeNs::BuiltinType(builtin), 1, None)); } } @@ -381,7 +388,7 @@ impl Resolver { path: &Path, ) -> Option<ValueNs> { match self.resolve_path_in_value_ns(db, path)? { - ResolveValueResult::ValueNs(it) => Some(it), + ResolveValueResult::ValueNs(it, _) => Some(it), ResolveValueResult::Partial(..) => None, } } @@ -391,12 +398,12 @@ impl Resolver { db: &dyn DefDatabase, path: &ModPath, expected_macro_kind: Option<MacroSubNs>, - ) -> Option<MacroId> { + ) -> Option<(MacroId, Option<ImportId>)> { let (item_map, module) = self.item_scope(); item_map .resolve_path(db, module, path, BuiltinShadowMode::Other, expected_macro_kind) .0 - .take_macros() + .take_macros_import() } /// Returns a set of names available in the current scope. @@ -456,21 +463,22 @@ impl Resolver { def_map[module_id].scope.entries().for_each(|(name, def)| { res.add_per_ns(name, def); }); + def_map[module_id].scope.legacy_macros().for_each(|(name, macs)| { macs.iter().for_each(|&mac| { res.add(name, ScopeDef::ModuleDef(ModuleDefId::MacroId(mac))); }) }); - def_map.macro_use_prelude().for_each(|(name, def)| { + def_map.macro_use_prelude().for_each(|(name, (def, _extern_crate))| { res.add(name, ScopeDef::ModuleDef(def.into())); }); - def_map.extern_prelude().for_each(|(name, def)| { - res.add(name, ScopeDef::ModuleDef(ModuleDefId::ModuleId(def))); + def_map.extern_prelude().for_each(|(name, (def, _extern_crate))| { + res.add(name, ScopeDef::ModuleDef(ModuleDefId::ModuleId(def.into()))); }); BUILTIN_SCOPE.iter().for_each(|(name, &def)| { res.add_per_ns(name, def); }); - if let Some(prelude) = def_map.prelude() { + if let Some((prelude, _use)) = def_map.prelude() { let prelude_def_map = prelude.def_map(db); for (name, def) in prelude_def_map[prelude.local_id].scope.entries() { res.add_per_ns(name, def) @@ -479,6 +487,23 @@ impl Resolver { res.map } + pub fn extern_crate_decls_in_scope<'a>( + &'a self, + db: &'a dyn DefDatabase, + ) -> impl Iterator<Item = Name> + 'a { + self.module_scope.def_map[self.module_scope.module_id] + .scope + .extern_crate_decls() + .map(|id| ExternCrateDeclData::extern_crate_decl_data_query(db, id).name.clone()) + } + + pub fn extern_crates_in_scope<'a>(&'a self) -> impl Iterator<Item = (Name, ModuleId)> + 'a { + self.module_scope + .def_map + .extern_prelude() + .map(|(name, module_id)| (name.clone(), module_id.0.into())) + } + pub fn traits_in_scope(&self, db: &dyn DefDatabase) -> FxHashSet<TraitId> { // FIXME(trait_alias): Trait alias brings aliased traits in scope! Note that supertraits of // aliased traits are NOT brought in scope (unless also aliased). @@ -501,7 +526,7 @@ impl Resolver { } // Fill in the prelude traits - if let Some(prelude) = self.module_scope.def_map.prelude() { + if let Some((prelude, _use)) = self.module_scope.def_map.prelude() { let prelude_def_map = prelude.def_map(db); traits.extend(prelude_def_map[prelude.local_id].scope.traits()); } @@ -804,11 +829,12 @@ impl ModuleItemMap { self.def_map.resolve_path_locally(db, self.module_id, path, BuiltinShadowMode::Other); match idx { None => { - let value = to_value_ns(module_def)?; - Some(ResolveValueResult::ValueNs(value)) + let (value, import) = to_value_ns(module_def)?; + Some(ResolveValueResult::ValueNs(value, import)) } Some(idx) => { - let ty = match module_def.take_types()? { + let (def, _, import) = module_def.take_types_full()?; + let ty = match def { ModuleDefId::AdtId(it) => TypeNs::AdtId(it), ModuleDefId::TraitId(it) => TypeNs::TraitId(it), ModuleDefId::TraitAliasId(it) => TypeNs::TraitAliasId(it), @@ -822,7 +848,7 @@ impl ModuleItemMap { | ModuleDefId::MacroId(_) | ModuleDefId::StaticId(_) => return None, }; - Some(ResolveValueResult::Partial(ty, idx)) + Some(ResolveValueResult::Partial(ty, idx, import)) } } } @@ -831,16 +857,17 @@ impl ModuleItemMap { &self, db: &dyn DefDatabase, path: &ModPath, - ) -> Option<(TypeNs, Option<usize>)> { + ) -> Option<(TypeNs, Option<usize>, Option<ImportOrExternCrate>)> { let (module_def, idx) = self.def_map.resolve_path_locally(db, self.module_id, path, BuiltinShadowMode::Other); - let res = to_type_ns(module_def)?; - Some((res, idx)) + let (res, import) = to_type_ns(module_def)?; + Some((res, idx, import)) } } -fn to_value_ns(per_ns: PerNs) -> Option<ValueNs> { - let res = match per_ns.take_values()? { +fn to_value_ns(per_ns: PerNs) -> Option<(ValueNs, Option<ImportId>)> { + let (def, import) = per_ns.take_values_import()?; + let res = match def { ModuleDefId::FunctionId(it) => ValueNs::FunctionId(it), ModuleDefId::AdtId(AdtId::StructId(it)) => ValueNs::StructId(it), ModuleDefId::EnumVariantId(it) => ValueNs::EnumVariantId(it), @@ -855,11 +882,12 @@ fn to_value_ns(per_ns: PerNs) -> Option<ValueNs> { | ModuleDefId::MacroId(_) | ModuleDefId::ModuleId(_) => return None, }; - Some(res) + Some((res, import)) } -fn to_type_ns(per_ns: PerNs) -> Option<TypeNs> { - let res = match per_ns.take_types()? { +fn to_type_ns(per_ns: PerNs) -> Option<(TypeNs, Option<ImportOrExternCrate>)> { + let (def, _, import) = per_ns.take_types_full()?; + let res = match def { ModuleDefId::AdtId(it) => TypeNs::AdtId(it), ModuleDefId::EnumVariantId(it) => TypeNs::EnumVariantId(it), @@ -875,7 +903,7 @@ fn to_type_ns(per_ns: PerNs) -> Option<TypeNs> { | ModuleDefId::StaticId(_) | ModuleDefId::ModuleId(_) => return None, }; - Some(res) + Some((res, import)) } type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<rustc_hash::FxHasher>>; @@ -892,13 +920,13 @@ impl ScopeNames { } } fn add_per_ns(&mut self, name: &Name, def: PerNs) { - if let &Some((ty, _)) = &def.types { + if let &Some((ty, _, _)) = &def.types { self.add(name, ScopeDef::ModuleDef(ty)) } - if let &Some((def, _)) = &def.values { + if let &Some((def, _, _)) = &def.values { self.add(name, ScopeDef::ModuleDef(def)) } - if let &Some((mac, _)) = &def.macros { + if let &Some((mac, _, _)) = &def.macros { self.add(name, ScopeDef::ModuleDef(ModuleDefId::MacroId(mac))) } if def.is_none() { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/src.rs b/src/tools/rust-analyzer/crates/hir-def/src/src.rs index 6047f770d..3770103cd 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/src.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/src.rs @@ -5,8 +5,8 @@ use la_arena::ArenaMap; use syntax::ast; use crate::{ - db::DefDatabase, item_tree::ItemTreeNode, AssocItemLoc, ItemLoc, Macro2Loc, MacroRulesLoc, - ProcMacroLoc, + db::DefDatabase, item_tree::ItemTreeNode, AssocItemLoc, ItemLoc, Lookup, Macro2Loc, + MacroRulesLoc, ProcMacroLoc, UseId, }; pub trait HasSource { @@ -83,3 +83,18 @@ pub trait HasChildSource<ChildId> { type Value; fn child_source(&self, db: &dyn DefDatabase) -> InFile<ArenaMap<ChildId, Self::Value>>; } + +impl HasChildSource<la_arena::Idx<ast::UseTree>> for UseId { + type Value = ast::UseTree; + fn child_source( + &self, + db: &dyn DefDatabase, + ) -> InFile<ArenaMap<la_arena::Idx<ast::UseTree>, Self::Value>> { + let loc = &self.lookup(db); + let use_ = &loc.id.item_tree(db)[loc.id.value]; + InFile::new( + loc.id.file_id(), + use_.use_tree_source_map(db, loc.id.file_id()).into_iter().collect(), + ) + } +} diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/attrs.rs b/src/tools/rust-analyzer/crates/hir-expand/src/attrs.rs index 4c918e55b..0ec2422b3 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/attrs.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/attrs.rs @@ -342,14 +342,7 @@ fn inner_attributes( ast::Impl(it) => it.assoc_item_list()?.syntax().clone(), ast::Module(it) => it.item_list()?.syntax().clone(), ast::BlockExpr(it) => { - use syntax::SyntaxKind::{BLOCK_EXPR , EXPR_STMT}; - // Block expressions accept outer and inner attributes, but only when they are the outer - // expression of an expression statement or the final expression of another block expression. - let may_carry_attributes = matches!( - it.syntax().parent().map(|it| it.kind()), - Some(BLOCK_EXPR | EXPR_STMT) - ); - if !may_carry_attributes { + if !it.may_carry_attributes() { return None } syntax.clone() diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_fn_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_fn_macro.rs index 95c6baf42..30b19b6e5 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_fn_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_fn_macro.rs @@ -1,13 +1,9 @@ //! Builtin macro -use std::mem; - -use ::tt::Ident; use base_db::{AnchoredPath, Edition, FileId}; use cfg::CfgExpr; use either::Either; use mbe::{parse_exprs_with_sep, parse_to_token_tree, TokenMap}; -use rustc_hash::FxHashMap; use syntax::{ ast::{self, AstToken}, SmolStr, @@ -97,11 +93,11 @@ register_builtin! { (unreachable, Unreachable) => unreachable_expand, (log_syntax, LogSyntax) => log_syntax_expand, (trace_macros, TraceMacros) => trace_macros_expand, - - EAGER: (format_args, FormatArgs) => format_args_expand, (const_format_args, ConstFormatArgs) => format_args_expand, (format_args_nl, FormatArgsNl) => format_args_nl_expand, + + EAGER: (compile_error, CompileError) => compile_error_expand, (concat, Concat) => concat_expand, (concat_idents, ConcatIdents) => concat_idents_expand, @@ -247,151 +243,15 @@ fn format_args_expand_general( _db: &dyn ExpandDatabase, _id: MacroCallId, tt: &tt::Subtree, - end_string: &str, + // FIXME: Make use of this so that mir interpretation works properly + _end_string: &str, ) -> ExpandResult<tt::Subtree> { - let args = parse_exprs_with_sep(tt, ','); - - let expand_error = - ExpandResult::new(tt::Subtree::empty(), mbe::ExpandError::NoMatchingRule.into()); - - let mut key_args = FxHashMap::default(); - let mut args = args.into_iter().filter_map(|mut arg| { - // Remove `key =`. - if matches!(arg.token_trees.get(1), Some(tt::TokenTree::Leaf(tt::Leaf::Punct(p))) if p.char == '=') - { - // but not with `==` - if !matches!(arg.token_trees.get(2), Some(tt::TokenTree::Leaf(tt::Leaf::Punct(p))) if p.char == '=') - { - let key = arg.token_trees.drain(..2).next().unwrap(); - key_args.insert(key.to_string(), arg); - return None; - } - } - Some(arg) - }).collect::<Vec<_>>().into_iter(); - // ^^^^^^^ we need this collect, to enforce the side effect of the filter_map closure (building the `key_args`) - let Some(format_subtree) = args.next() else { - return expand_error; - }; - let format_string = (|| { - let token_tree = format_subtree.token_trees.get(0)?; - match token_tree { - tt::TokenTree::Leaf(l) => match l { - tt::Leaf::Literal(l) => { - if let Some(mut text) = l.text.strip_prefix('r') { - let mut raw_sharps = String::new(); - while let Some(t) = text.strip_prefix('#') { - text = t; - raw_sharps.push('#'); - } - text = - text.strip_suffix(&raw_sharps)?.strip_prefix('"')?.strip_suffix('"')?; - Some((text, l.span, Some(raw_sharps))) - } else { - let text = l.text.strip_prefix('"')?.strip_suffix('"')?; - let span = l.span; - Some((text, span, None)) - } - } - _ => None, - }, - tt::TokenTree::Subtree(_) => None, - } - })(); - let Some((format_string, _format_string_span, raw_sharps)) = format_string else { - return expand_error; - }; - let mut format_iter = format_string.chars().peekable(); - let mut parts = vec![]; - let mut last_part = String::new(); - let mut arg_tts = vec![]; - let mut err = None; - while let Some(c) = format_iter.next() { - // Parsing the format string. See https://doc.rust-lang.org/std/fmt/index.html#syntax for the grammar and more info - match c { - '{' => { - if format_iter.peek() == Some(&'{') { - format_iter.next(); - last_part.push('{'); - continue; - } - let mut argument = String::new(); - while ![Some(&'}'), Some(&':')].contains(&format_iter.peek()) { - argument.push(match format_iter.next() { - Some(c) => c, - None => return expand_error, - }); - } - let format_spec = match format_iter.next().unwrap() { - '}' => "".to_owned(), - ':' => { - let mut s = String::new(); - while let Some(c) = format_iter.next() { - if c == '}' { - break; - } - s.push(c); - } - s - } - _ => unreachable!(), - }; - parts.push(mem::take(&mut last_part)); - let arg_tree = if argument.is_empty() { - match args.next() { - Some(it) => it, - None => { - err = Some(mbe::ExpandError::NoMatchingRule.into()); - tt::Subtree::empty() - } - } - } else if let Some(tree) = key_args.get(&argument) { - tree.clone() - } else { - // FIXME: we should pick the related substring of the `_format_string_span` as the span. You - // can use `.char_indices()` instead of `.char()` for `format_iter` to find the substring interval. - let ident = Ident::new(argument, tt::TokenId::unspecified()); - quote!(#ident) - }; - let formatter = match &*format_spec { - "?" => quote!(::core::fmt::Debug::fmt), - "" => quote!(::core::fmt::Display::fmt), - _ => { - // FIXME: implement the rest and return expand error here - quote!(::core::fmt::Display::fmt) - } - }; - arg_tts.push(quote! { ::core::fmt::ArgumentV1::new(&(#arg_tree), #formatter), }); - } - '}' => { - if format_iter.peek() == Some(&'}') { - format_iter.next(); - last_part.push('}'); - } else { - return expand_error; - } - } - _ => last_part.push(c), - } - } - last_part += end_string; - if !last_part.is_empty() { - parts.push(last_part); - } - let part_tts = parts.into_iter().map(|it| { - let text = if let Some(raw) = &raw_sharps { - format!("r{raw}\"{}\"{raw}", it).into() - } else { - format!("\"{}\"", it).into() - }; - let l = tt::Literal { span: tt::TokenId::unspecified(), text }; - quote!(#l ,) + let pound = quote! {@PUNCT '#'}; + let mut tt = tt.clone(); + tt.delimiter.kind = tt::DelimiterKind::Parenthesis; + return ExpandResult::ok(quote! { + builtin #pound format_args #tt }); - let arg_tts = arg_tts.into_iter().flat_map(|arg| arg.token_trees); - let expanded = quote! { - ::core::fmt::Arguments::new_v1(&[##part_tts], &[##arg_tts]) - }; - ExpandResult { value: expanded, err } } fn asm_expand( @@ -415,10 +275,12 @@ fn asm_expand( } } - let expanded = quote! {{ - ##literals - loop {} - }}; + let pound = quote! {@PUNCT '#'}; + let expanded = quote! { + builtin #pound asm ( + {##literals} + ) + }; ExpandResult::ok(expanded) } @@ -692,7 +554,7 @@ pub(crate) fn include_arg_to_tt( arg_id: MacroCallId, ) -> Result<(triomphe::Arc<(::tt::Subtree<::tt::TokenId>, TokenMap)>, FileId), ExpandError> { let loc = db.lookup_intern_macro_call(arg_id); - let Some(EagerCallInfo { arg,arg_id, .. }) = loc.eager.as_deref() else { + let Some(EagerCallInfo { arg, arg_id, .. }) = loc.eager.as_deref() else { panic!("include_arg_to_tt called on non include macro call: {:?}", &loc.eager); }; let path = parse_string(&arg.0)?; diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs b/src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs index ade4a5928..ca65db113 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs @@ -242,7 +242,7 @@ impl HygieneFrame { krate, call_site: None, def_site: None, - } + }; }; let def_site = info.attr_input_or_mac_def_start.map(|it| db.hygiene_frame(it.file_id)); 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 1f1e20f49..4be55126b 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs @@ -37,7 +37,7 @@ use either::Either; use syntax::{ algo::{self, skip_trivia_token}, ast::{self, AstNode, HasDocComments}, - AstPtr, Direction, SyntaxNode, SyntaxNodePtr, SyntaxToken, + AstPtr, Direction, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextSize, }; use crate::{ @@ -544,7 +544,7 @@ impl MacroCallKind { }; let range = match kind { - MacroCallKind::FnLike { ast_id, .. } => ast_id.to_node(db).syntax().text_range(), + MacroCallKind::FnLike { ast_id, .. } => ast_id.to_ptr(db).text_range(), MacroCallKind::Derive { ast_id, derive_attr_index, .. } => { // FIXME: should be the range of the macro name, not the whole derive // FIXME: handle `cfg_attr` @@ -642,6 +642,8 @@ impl ExpansionInfo { db: &dyn db::ExpandDatabase, item: Option<ast::Item>, token: InFile<&SyntaxToken>, + // FIXME: use this for range mapping, so that we can resolve inline format args + _relative_token_offset: Option<TextSize>, ) -> Option<impl Iterator<Item = InFile<SyntaxToken>> + '_> { assert_eq!(token.file_id, self.arg.file_id); let token_id_in_attr_input = if let Some(item) = item { @@ -840,9 +842,6 @@ impl<N: AstIdNode> AstId<N> { pub type ErasedAstId = InFile<ErasedFileAstId>; impl ErasedAstId { - pub fn to_node(&self, db: &dyn db::ExpandDatabase) -> SyntaxNode { - self.to_ptr(db).to_node(&db.parse_or_expand(self.file_id)) - } pub fn to_ptr(&self, db: &dyn db::ExpandDatabase) -> SyntaxNodePtr { db.ast_id_map(self.file_id).get_raw(self.value) } @@ -1054,16 +1053,6 @@ impl InFile<SyntaxToken> { } } } - - pub fn ancestors_with_macros( - self, - db: &dyn db::ExpandDatabase, - ) -> impl Iterator<Item = InFile<SyntaxNode>> + '_ { - self.value.parent().into_iter().flat_map({ - let file_id = self.file_id; - move |parent| InFile::new(file_id, &parent).ancestors_with_macros(db) - }) - } } #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/name.rs b/src/tools/rust-analyzer/crates/hir-expand/src/name.rs index 7c179c0cf..a876f48bd 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/name.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/name.rs @@ -54,6 +54,12 @@ impl Name { Name(Repr::Text(text)) } + // FIXME: See above, unfortunately some places really need this right now + #[doc(hidden)] + pub const fn new_text_dont_use(text: SmolStr) -> Name { + Name(Repr::Text(text)) + } + pub fn new_tuple_field(idx: usize) -> Name { Name(Repr::TupleField(idx)) } @@ -302,6 +308,16 @@ pub mod known { rust_2018, rust_2021, v1, + new_display, + new_debug, + new_lower_exp, + new_upper_exp, + new_octal, + new_pointer, + new_binary, + new_lower_hex, + new_upper_hex, + from_usize, // Components of known path (type name) Iterator, IntoIterator, @@ -327,6 +343,13 @@ pub mod known { Not, None, Index, + Left, + Right, + Center, + Unknown, + Is, + Param, + Implied, // Components of known path (function name) filter_map, next, @@ -335,6 +358,8 @@ pub mod known { is_empty, as_str, new, + new_v1_formatted, + none, // Builtin macros asm, assert, diff --git a/src/tools/rust-analyzer/crates/hir-ty/Cargo.toml b/src/tools/rust-analyzer/crates/hir-ty/Cargo.toml index abc19d63a..b95ae05cc 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/Cargo.toml +++ b/src/tools/rust-analyzer/crates/hir-ty/Cargo.toml @@ -32,7 +32,8 @@ once_cell = "1.17.0" triomphe.workspace = true nohash-hasher.workspace = true typed-arena = "2.0.1" -rustc_index = { version = "0.0.20221221", package = "hkalbasi-rustc-ap-rustc_index", default-features = false } + +rustc_index.workspace = true # local deps stdx.workspace = true diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/builder.rs b/src/tools/rust-analyzer/crates/hir-ty/src/builder.rs index eec57ba3f..967e028bf 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/builder.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/builder.rs @@ -17,7 +17,8 @@ use smallvec::SmallVec; use crate::{ consteval::unknown_const_as_generic, db::HirDatabase, infer::unify::InferenceTable, primitive, to_assoc_type_id, to_chalk_trait_id, utils::generics, Binders, BoundVar, CallableSig, - GenericArg, Interner, ProjectionTy, Substitution, TraitRef, Ty, TyDefId, TyExt, TyKind, + GenericArg, GenericArgData, Interner, ProjectionTy, Substitution, TraitRef, Ty, TyDefId, TyExt, + TyKind, }; #[derive(Debug, Clone, PartialEq, Eq)] @@ -79,9 +80,9 @@ impl<D> TyBuilder<D> { let expected_kind = &self.param_kinds[self.vec.len()]; let arg_kind = match arg.data(Interner) { - chalk_ir::GenericArgData::Ty(_) => ParamKind::Type, - chalk_ir::GenericArgData::Lifetime(_) => panic!("Got lifetime in TyBuilder::push"), - chalk_ir::GenericArgData::Const(c) => { + GenericArgData::Ty(_) => ParamKind::Type, + GenericArgData::Lifetime(_) => panic!("Got lifetime in TyBuilder::push"), + GenericArgData::Const(c) => { let c = c.data(Interner); ParamKind::Const(c.ty.clone()) } @@ -139,8 +140,8 @@ impl<D> TyBuilder<D> { fn assert_match_kind(&self, a: &chalk_ir::GenericArg<Interner>, e: &ParamKind) { match (a.data(Interner), e) { - (chalk_ir::GenericArgData::Ty(_), ParamKind::Type) - | (chalk_ir::GenericArgData::Const(_), ParamKind::Const(_)) => (), + (GenericArgData::Ty(_), ParamKind::Type) + | (GenericArgData::Const(_), ParamKind::Const(_)) => (), _ => panic!("Mismatched kinds: {a:?}, {:?}, {:?}", self.vec, self.param_kinds), } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs b/src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs index 1c0f7b08d..0348680e5 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs @@ -1,7 +1,7 @@ //! Constant evaluation details use base_db::CrateId; -use chalk_ir::{BoundVar, DebruijnIndex, GenericArgData}; +use chalk_ir::{cast::Cast, BoundVar, DebruijnIndex}; use hir_def::{ hir::Expr, path::Path, @@ -120,7 +120,7 @@ pub fn unknown_const(ty: Ty) -> Const { } pub fn unknown_const_as_generic(ty: Ty) -> GenericArg { - GenericArgData::Const(unknown_const(ty)).intern(Interner) + unknown_const(ty).cast(Interner) } /// Interns a constant scalar with the given type diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests.rs b/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests.rs index 666955fa1..7ad3659a4 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests.rs @@ -1203,6 +1203,27 @@ fn destructing_assignment() { "#, 5, ); + check_number( + r#" + const GOAL: u8 = { + let (mut a, mut b) = (2, 5); + (a, b) = (b, a); + a * 10 + b + }; + "#, + 52, + ); + check_number( + r#" + struct Point { x: i32, y: i32 } + const GOAL: i32 = { + let mut p = Point { x: 5, y: 6 }; + (p.x, _) = (p.y, p.x); + p.x * 10 + p.y + }; + "#, + 66, + ); } #[test] @@ -1433,6 +1454,30 @@ fn from_trait() { } #[test] +fn closure_clone() { + check_number( + r#" +//- minicore: clone, fn +struct S(u8); + +impl Clone for S(u8) { + fn clone(&self) -> S { + S(self.0 + 5) + } +} + +const GOAL: u8 = { + let s = S(3); + let cl = move || s; + let cl = cl.clone(); + cl().0 +} + "#, + 8, + ); +} + +#[test] fn builtin_derive_macro() { check_number( r#" @@ -2396,14 +2441,14 @@ fn const_loop() { fn const_transfer_memory() { check_number( r#" - //- minicore: slice, index, coerce_unsized + //- minicore: slice, index, coerce_unsized, option const A1: &i32 = &1; const A2: &i32 = &10; const A3: [&i32; 3] = [&1, &2, &100]; - const A4: (i32, &i32) = (1, &1000); - const GOAL: i32 = *A1 + *A2 + *A3[2] + *A4.1; + const A4: (i32, &i32, Option<&i32>) = (1, &1000, Some(&10000)); + const GOAL: i32 = *A1 + *A2 + *A3[2] + *A4.1 + *A4.2.unwrap_or(&5); "#, - 1111, + 11111, ); } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests/intrinsics.rs b/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests/intrinsics.rs index 2855f7890..44a4ac27a 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests/intrinsics.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests/intrinsics.rs @@ -499,24 +499,26 @@ fn offset() { r#" //- minicore: coerce_unsized, index, slice extern "rust-intrinsic" { - pub fn offset<T>(dst: *const T, offset: isize) -> *const T; + pub fn offset<Ptr, Delta>(dst: Ptr, offset: Delta) -> Ptr; + pub fn arith_offset<T>(dst: *const T, offset: isize) -> *const T; } - const GOAL: u8 = unsafe { - let ar: &[(u8, u8, u8)] = &[ + const GOAL: i32 = unsafe { + let ar: &[(i32, i32, i32)] = &[ (10, 11, 12), (20, 21, 22), (30, 31, 32), (40, 41, 42), (50, 51, 52), ]; - let ar: *const [(u8, u8, u8)] = ar; - let ar = ar as *const (u8, u8, u8); - let element = *offset(ar, 2); - element.1 + let ar: *const [(i32, i32, i32)] = ar; + let ar = ar as *const (i32, i32, i32); + let element3 = *offset(ar, 2usize); + let element4 = *arith_offset(ar, 3); + element3.1 * 100 + element4.0 }; "#, - 31, + 3140, ); } @@ -585,6 +587,24 @@ fn write_bytes() { } #[test] +fn write_via_move() { + check_number( + r#" + extern "rust-intrinsic" { + fn write_via_move<T>(ptr: *mut T, value: T); + } + + const GOAL: i32 = unsafe { + let mut x = 2; + write_via_move(&mut x, 100); + x + }; + "#, + 100, + ); +} + +#[test] fn copy() { check_number( r#" diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs index a94a962c1..36d69edf9 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs @@ -163,25 +163,56 @@ impl<'a> DeclValidator<'a> { || allows.contains(allow::NONSTANDARD_STYLE) }) }; + let db = self.db.upcast(); + let file_id_is_derive = || { + match id { + AttrDefId::ModuleId(m) => { + m.def_map(db)[m.local_id].origin.file_id().map(Into::into) + } + AttrDefId::FunctionId(f) => Some(f.lookup(db).id.file_id()), + AttrDefId::StaticId(sid) => Some(sid.lookup(db).id.file_id()), + AttrDefId::ConstId(cid) => Some(cid.lookup(db).id.file_id()), + AttrDefId::TraitId(tid) => Some(tid.lookup(db).id.file_id()), + AttrDefId::TraitAliasId(taid) => Some(taid.lookup(db).id.file_id()), + AttrDefId::ImplId(iid) => Some(iid.lookup(db).id.file_id()), + AttrDefId::ExternBlockId(id) => Some(id.lookup(db).id.file_id()), + AttrDefId::ExternCrateId(id) => Some(id.lookup(db).id.file_id()), + AttrDefId::UseId(id) => Some(id.lookup(db).id.file_id()), + // These warnings should not explore macro definitions at all + AttrDefId::MacroId(_) => None, + AttrDefId::AdtId(aid) => match aid { + AdtId::StructId(sid) => Some(sid.lookup(db).id.file_id()), + AdtId::EnumId(eid) => Some(eid.lookup(db).id.file_id()), + // Unions aren't yet supported + AdtId::UnionId(_) => None, + }, + AttrDefId::FieldId(_) => None, + AttrDefId::EnumVariantId(_) => None, + AttrDefId::TypeAliasId(_) => None, + AttrDefId::GenericParamId(_) => None, + } + .map_or(false, |file_id| { + file_id.is_custom_derive(db.upcast()) || file_id.is_builtin_derive(db.upcast()) + }) + }; - is_allowed(id) - // go upwards one step or give up - || match id { - AttrDefId::ModuleId(m) => m.containing_module(self.db.upcast()).map(|v| v.into()), - AttrDefId::FunctionId(f) => Some(f.lookup(self.db.upcast()).container.into()), - AttrDefId::StaticId(sid) => Some(sid.lookup(self.db.upcast()).container.into()), - AttrDefId::ConstId(cid) => Some(cid.lookup(self.db.upcast()).container.into()), - AttrDefId::TraitId(tid) => Some(tid.lookup(self.db.upcast()).container.into()), - AttrDefId::TraitAliasId(taid) => Some(taid.lookup(self.db.upcast()).container.into()), - AttrDefId::ImplId(iid) => Some(iid.lookup(self.db.upcast()).container.into()), - AttrDefId::ExternBlockId(id) => Some(id.lookup(self.db.upcast()).container.into()), - AttrDefId::ExternCrateId(id) => Some(id.lookup(self.db.upcast()).container.into()), - AttrDefId::UseId(id) => Some(id.lookup(self.db.upcast()).container.into()), + let parent = || { + match id { + AttrDefId::ModuleId(m) => m.containing_module(db).map(|v| v.into()), + AttrDefId::FunctionId(f) => Some(f.lookup(db).container.into()), + AttrDefId::StaticId(sid) => Some(sid.lookup(db).container.into()), + AttrDefId::ConstId(cid) => Some(cid.lookup(db).container.into()), + AttrDefId::TraitId(tid) => Some(tid.lookup(db).container.into()), + AttrDefId::TraitAliasId(taid) => Some(taid.lookup(db).container.into()), + AttrDefId::ImplId(iid) => Some(iid.lookup(db).container.into()), + AttrDefId::ExternBlockId(id) => Some(id.lookup(db).container.into()), + AttrDefId::ExternCrateId(id) => Some(id.lookup(db).container.into()), + AttrDefId::UseId(id) => Some(id.lookup(db).container.into()), // These warnings should not explore macro definitions at all AttrDefId::MacroId(_) => None, AttrDefId::AdtId(aid) => match aid { - AdtId::StructId(sid) => Some(sid.lookup(self.db.upcast()).container.into()), - AdtId::EnumId(eid) => Some(eid.lookup(self.db.upcast()).container.into()), + AdtId::StructId(sid) => Some(sid.lookup(db).container.into()), + AdtId::EnumId(eid) => Some(eid.lookup(db).container.into()), // Unions aren't yet supported AdtId::UnionId(_) => None, }, @@ -191,6 +222,12 @@ impl<'a> DeclValidator<'a> { AttrDefId::GenericParamId(_) => None, } .is_some_and(|mid| self.allowed(mid, allow_name, true)) + }; + is_allowed(id) + // FIXME: this is a hack to avoid false positives in derive macros currently + || file_id_is_derive() + // go upwards one step or give up + || parent() } fn validate_func(&mut self, func: FunctionId) { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/unsafe_check.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/unsafe_check.rs index 9f9a56ffa..cbca0e801 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/unsafe_check.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/unsafe_check.rs @@ -75,7 +75,7 @@ fn walk_unsafe( Expr::Path(path) => { let resolver = resolver_for_expr(db.upcast(), def, current); let value_or_partial = resolver.resolve_path_in_value_ns(db.upcast(), path); - if let Some(ResolveValueResult::ValueNs(ValueNs::StaticId(id))) = value_or_partial { + if let Some(ResolveValueResult::ValueNs(ValueNs::StaticId(id), _)) = value_or_partial { if db.static_data(id).mutable { unsafe_expr_cb(UnsafeExpr { expr: current, inside_unsafe_block }); } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/display.rs b/src/tools/rust-analyzer/crates/hir-ty/src/display.rs index 1b4ee4613..f6d6b00d7 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/display.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/display.rs @@ -1809,6 +1809,25 @@ impl HirDisplay for Path { } } + // Convert trait's `Self` bound back to the surface syntax. Note there is no associated + // trait, so there can only be one path segment that `has_self_type`. The `Self` type + // itself can contain further qualified path through, which will be handled by recursive + // `hir_fmt`s. + // + // `trait_mod::Trait<Self = type_mod::Type, Args>::Assoc` + // => + // `<type_mod::Type as trait_mod::Trait<Args>>::Assoc` + let trait_self_ty = self.segments().iter().find_map(|seg| { + let generic_args = seg.args_and_bindings?; + generic_args.has_self_type.then(|| &generic_args.args[0]) + }); + if let Some(ty) = trait_self_ty { + write!(f, "<")?; + ty.hir_fmt(f)?; + write!(f, " as ")?; + // Now format the path of the trait... + } + for (seg_idx, segment) in self.segments().iter().enumerate() { if !matches!(self.kind(), PathKind::Plain) || seg_idx > 0 { write!(f, "::")?; @@ -1840,15 +1859,12 @@ impl HirDisplay for Path { return Ok(()); } - write!(f, "<")?; let mut first = true; - for arg in generic_args.args.iter() { + // Skip the `Self` bound if exists. It's handled outside the loop. + for arg in &generic_args.args[generic_args.has_self_type as usize..] { if first { first = false; - if generic_args.has_self_type { - // FIXME: Convert to `<Ty as Trait>` form. - write!(f, "Self = ")?; - } + write!(f, "<")?; } else { write!(f, ", ")?; } @@ -1857,6 +1873,7 @@ impl HirDisplay for Path { for binding in generic_args.bindings.iter() { if first { first = false; + write!(f, "<")?; } else { write!(f, ", ")?; } @@ -1872,9 +1889,20 @@ impl HirDisplay for Path { } } } - write!(f, ">")?; + + // There may be no generic arguments to print, in case of a trait having only a + // single `Self` bound which is converted to `<Ty as Trait>::Assoc`. + if !first { + write!(f, ">")?; + } + + // Current position: `<Ty as Trait<Args>|` + if generic_args.has_self_type { + write!(f, ">")?; + } } } + Ok(()) } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs index b4915dbf0..78d3c667a 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs @@ -194,7 +194,8 @@ pub(crate) type InferResult<T> = Result<InferOk<T>, TypeError>; #[derive(Debug, PartialEq, Eq, Clone)] pub enum InferenceDiagnostic { NoSuchField { - expr: ExprId, + field: ExprOrPatId, + private: bool, }, PrivateField { expr: ExprId, @@ -228,6 +229,11 @@ pub enum InferenceDiagnostic { expected: usize, found: usize, }, + MismatchedTupleStructPatArgCount { + pat: ExprOrPatId, + expected: usize, + found: usize, + }, ExpectedFunction { call_expr: ExprId, found: Ty, @@ -1017,7 +1023,7 @@ impl<'a> InferenceContext<'a> { let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver, self.owner.into()); let (resolution, unresolved) = if value_ns { match self.resolver.resolve_path_in_value_ns(self.db.upcast(), path) { - Some(ResolveValueResult::ValueNs(value)) => match value { + Some(ResolveValueResult::ValueNs(value, _)) => match value { ValueNs::EnumVariantId(var) => { let substs = ctx.substs_from_path(path, var.into(), true); let ty = self.db.ty(var.parent.into()); @@ -1033,12 +1039,14 @@ impl<'a> InferenceContext<'a> { ValueNs::ImplSelf(impl_id) => (TypeNs::SelfType(impl_id), None), _ => return (self.err_ty(), None), }, - Some(ResolveValueResult::Partial(typens, unresolved)) => (typens, Some(unresolved)), + Some(ResolveValueResult::Partial(typens, unresolved, _)) => { + (typens, Some(unresolved)) + } None => return (self.err_ty(), None), } } else { match self.resolver.resolve_path_in_type_ns(self.db.upcast(), path) { - Some(it) => it, + Some((it, idx, _)) => (it, idx), None => return (self.err_ty(), None), } }; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs index 9e1c74b16..a116d4447 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs @@ -39,8 +39,14 @@ impl CastCheck { } fn check_ref_to_ptr_cast(expr_ty: Ty, cast_ty: Ty, table: &mut InferenceTable<'_>) -> bool { - let Some((expr_inner_ty, _, _)) = expr_ty.as_reference() else { return false; }; - let Some((cast_inner_ty, _)) = cast_ty.as_raw_ptr() else { return false; }; - let TyKind::Array(expr_elt_ty, _) = expr_inner_ty.kind(Interner) else { return false; }; + let Some((expr_inner_ty, _, _)) = expr_ty.as_reference() else { + return false; + }; + let Some((cast_inner_ty, _)) = cast_ty.as_raw_ptr() else { + return false; + }; + let TyKind::Array(expr_elt_ty, _) = expr_inner_ty.kind(Interner) else { + return false; + }; table.coerce(expr_elt_ty, cast_inner_ty).is_ok() } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs index 1781f6c58..13d6b5643 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs @@ -322,7 +322,7 @@ impl InferenceContext<'_> { Expr::Path(p) => { let resolver = resolver_for_expr(self.db.upcast(), self.owner, tgt_expr); if let Some(r) = resolver.resolve_path_in_value_ns(self.db.upcast(), p) { - if let ResolveValueResult::ValueNs(v) = r { + if let ResolveValueResult::ValueNs(v, _) = r { if let ValueNs::LocalBinding(b) = v { return Some(HirPlace { local: b, projections: vec![] }); } @@ -452,6 +452,8 @@ impl InferenceContext<'_> { fn walk_expr_without_adjust(&mut self, tgt_expr: ExprId) { match &self.body[tgt_expr] { + Expr::OffsetOf(_) => (), + Expr::InlineAsm(e) => self.walk_expr_without_adjust(e.e), Expr::If { condition, then_branch, else_branch } => { self.consume_expr(*condition); self.consume_expr(*then_branch); @@ -467,13 +469,13 @@ impl InferenceContext<'_> { Statement::Let { pat, type_ref: _, initializer, else_branch } => { if let Some(else_branch) = else_branch { self.consume_expr(*else_branch); - if let Some(initializer) = initializer { - self.consume_expr(*initializer); - } - return; } if let Some(initializer) = initializer { - self.walk_expr(*initializer); + if else_branch.is_some() { + self.consume_expr(*initializer); + } else { + self.walk_expr(*initializer); + } if let Some(place) = self.place_of_expr(*initializer) { self.consume_with_pat(place, *pat); } @@ -620,6 +622,7 @@ impl InferenceContext<'_> { | Expr::Tuple { exprs, is_assignee_expr: _ } => { self.consume_exprs(exprs.iter().copied()) } + Expr::Missing | Expr::Continue { .. } | Expr::Path(_) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs index 8cbdae625..0c3c725a7 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs @@ -5,9 +5,7 @@ use std::{ mem, }; -use chalk_ir::{ - cast::Cast, fold::Shift, DebruijnIndex, GenericArgData, Mutability, TyVariableKind, -}; +use chalk_ir::{cast::Cast, fold::Shift, DebruijnIndex, Mutability, TyVariableKind}; use hir_def::{ generics::TypeOrConstParamData, hir::{ @@ -516,9 +514,6 @@ impl InferenceContext<'_> { } Expr::RecordLit { path, fields, spread, .. } => { let (ty, def_id) = self.resolve_variant(path.as_deref(), false); - if let Some(variant) = def_id { - self.write_variant_resolution(tgt_expr.into(), variant); - } if let Some(t) = expected.only_has_type(&mut self.table) { self.unify(&ty, &t); @@ -528,26 +523,56 @@ impl InferenceContext<'_> { .as_adt() .map(|(_, s)| s.clone()) .unwrap_or_else(|| Substitution::empty(Interner)); - let field_types = def_id.map(|it| self.db.field_types(it)).unwrap_or_default(); - let variant_data = def_id.map(|it| it.variant_data(self.db.upcast())); - for field in fields.iter() { - let field_def = - variant_data.as_ref().and_then(|it| match it.field(&field.name) { - Some(local_id) => Some(FieldId { parent: def_id.unwrap(), local_id }), - None => { - self.push_diagnostic(InferenceDiagnostic::NoSuchField { - expr: field.expr, - }); - None - } - }); - let field_ty = field_def.map_or(self.err_ty(), |it| { - field_types[it.local_id].clone().substitute(Interner, &substs) - }); - // Field type might have some unknown types - // FIXME: we may want to emit a single type variable for all instance of type fields? - let field_ty = self.insert_type_vars(field_ty); - self.infer_expr_coerce(field.expr, &Expectation::has_type(field_ty)); + if let Some(variant) = def_id { + self.write_variant_resolution(tgt_expr.into(), variant); + } + match def_id { + _ if fields.is_empty() => {} + Some(def) => { + let field_types = self.db.field_types(def); + let variant_data = def.variant_data(self.db.upcast()); + let visibilities = self.db.field_visibilities(def); + for field in fields.iter() { + let field_def = { + match variant_data.field(&field.name) { + Some(local_id) => { + if !visibilities[local_id].is_visible_from( + self.db.upcast(), + self.resolver.module(), + ) { + self.push_diagnostic( + InferenceDiagnostic::NoSuchField { + field: field.expr.into(), + private: true, + }, + ); + } + Some(local_id) + } + None => { + self.push_diagnostic(InferenceDiagnostic::NoSuchField { + field: field.expr.into(), + private: false, + }); + None + } + } + }; + let field_ty = field_def.map_or(self.err_ty(), |it| { + field_types[it].clone().substitute(Interner, &substs) + }); + + // Field type might have some unknown types + // FIXME: we may want to emit a single type variable for all instance of type fields? + let field_ty = self.insert_type_vars(field_ty); + self.infer_expr_coerce(field.expr, &Expectation::has_type(field_ty)); + } + } + None => { + for field in fields.iter() { + self.infer_expr_coerce(field.expr, &Expectation::None); + } + } } if let Some(expr) = spread { self.infer_expr(*expr, &Expectation::has_type(ty.clone())); @@ -750,7 +775,7 @@ impl InferenceContext<'_> { self.resolve_associated_type_with_params( self_ty, self.resolve_ops_index_output(), - &[GenericArgData::Ty(index_ty).intern(Interner)], + &[index_ty.cast(Interner)], ) } else { self.err_ty() @@ -845,6 +870,11 @@ impl InferenceContext<'_> { }); expected } + Expr::OffsetOf(_) => TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(Interner), + Expr::InlineAsm(it) => { + self.infer_expr_no_expect(it.e); + self.result.standard_types.unit.clone() + } }; // use a new type variable if we got unknown here let ty = self.insert_type_vars_shallow(ty); @@ -1124,7 +1154,7 @@ impl InferenceContext<'_> { Expr::Underscore => rhs_ty.clone(), _ => { // `lhs` is a place expression, a unit struct, or an enum variant. - let lhs_ty = self.infer_expr(lhs, &Expectation::none()); + let lhs_ty = self.infer_expr_inner(lhs, &Expectation::none()); // This is the only branch where this function may coerce any type. // We are returning early to avoid the unifiability check below. @@ -1721,16 +1751,13 @@ impl InferenceContext<'_> { for (id, data) in def_generics.iter().skip(substs.len()) { match data { TypeOrConstParamData::TypeParamData(_) => { - substs.push(GenericArgData::Ty(self.table.new_type_var()).intern(Interner)) - } - TypeOrConstParamData::ConstParamData(_) => { - substs.push( - GenericArgData::Const(self.table.new_const_var( - self.db.const_param_ty(ConstParamId::from_unchecked(id)), - )) - .intern(Interner), - ) + substs.push(self.table.new_type_var().cast(Interner)) } + TypeOrConstParamData::ConstParamData(_) => substs.push( + self.table + .new_const_var(self.db.const_param_ty(ConstParamId::from_unchecked(id))) + .cast(Interner), + ), } } assert_eq!(substs.len(), total_len); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs index 396ca0044..b8a1af96f 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs @@ -35,6 +35,8 @@ impl InferenceContext<'_> { fn infer_mut_expr_without_adjust(&mut self, tgt_expr: ExprId, mutability: Mutability) { match &self.body[tgt_expr] { Expr::Missing => (), + Expr::InlineAsm(e) => self.infer_mut_expr_without_adjust(e.e, Mutability::Not), + Expr::OffsetOf(_) => (), &Expr::If { condition, then_branch, else_branch } => { self.infer_mut_expr(condition, Mutability::Not); self.infer_mut_expr(then_branch, Mutability::Not); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/pat.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/pat.rs index 5da0ab76b..4e28ec060 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/pat.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/pat.rs @@ -15,7 +15,8 @@ use crate::{ infer::{BindingMode, Expectation, InferenceContext, TypeMismatch}, lower::lower_to_chalk_mutability, primitive::UintTy, - static_lifetime, Interner, Scalar, Substitution, Ty, TyBuilder, TyExt, TyKind, + static_lifetime, InferenceDiagnostic, Interner, Scalar, Substitution, Ty, TyBuilder, TyExt, + TyKind, }; /// Used to generalize patterns and assignee expressions. @@ -74,29 +75,68 @@ impl InferenceContext<'_> { if let Some(variant) = def { self.write_variant_resolution(id.into(), variant); } + if let Some(var) = &var_data { + let cmp = if ellipsis.is_some() { usize::gt } else { usize::ne }; + + if cmp(&subs.len(), &var.fields().len()) { + self.push_diagnostic(InferenceDiagnostic::MismatchedTupleStructPatArgCount { + pat: id.into(), + expected: var.fields().len(), + found: subs.len(), + }); + } + } + self.unify(&ty, expected); let substs = ty.as_adt().map(|(_, s)| s.clone()).unwrap_or_else(|| Substitution::empty(Interner)); - let field_tys = def.map(|it| self.db.field_types(it)).unwrap_or_default(); - let (pre, post) = match ellipsis { - Some(idx) => subs.split_at(idx), - None => (subs, &[][..]), - }; - let post_idx_offset = field_tys.iter().count().saturating_sub(post.len()); - - let pre_iter = pre.iter().enumerate(); - let post_iter = (post_idx_offset..).zip(post.iter()); - for (i, &subpat) in pre_iter.chain(post_iter) { - let expected_ty = var_data - .as_ref() - .and_then(|d| d.field(&Name::new_tuple_field(i))) - .map_or(self.err_ty(), |field| { - field_tys[field].clone().substitute(Interner, &substs) - }); - let expected_ty = self.normalize_associated_types_in(expected_ty); - T::infer(self, subpat, &expected_ty, default_bm); + match def { + _ if subs.len() == 0 => {} + Some(def) => { + let field_types = self.db.field_types(def); + let variant_data = def.variant_data(self.db.upcast()); + let visibilities = self.db.field_visibilities(def); + + let (pre, post) = match ellipsis { + Some(idx) => subs.split_at(idx), + None => (subs, &[][..]), + }; + let post_idx_offset = field_types.iter().count().saturating_sub(post.len()); + + let pre_iter = pre.iter().enumerate(); + let post_iter = (post_idx_offset..).zip(post.iter()); + + for (i, &subpat) in pre_iter.chain(post_iter) { + let field_def = { + match variant_data.field(&Name::new_tuple_field(i)) { + Some(local_id) => { + if !visibilities[local_id] + .is_visible_from(self.db.upcast(), self.resolver.module()) + { + // FIXME(DIAGNOSE): private tuple field + } + Some(local_id) + } + None => None, + } + }; + + let expected_ty = field_def.map_or(self.err_ty(), |f| { + field_types[f].clone().substitute(Interner, &substs) + }); + let expected_ty = self.normalize_associated_types_in(expected_ty); + + T::infer(self, subpat, &expected_ty, default_bm); + } + } + None => { + let err_ty = self.err_ty(); + for &inner in subs { + T::infer(self, inner, &err_ty, default_bm); + } + } } ty @@ -109,7 +149,7 @@ impl InferenceContext<'_> { expected: &Ty, default_bm: T::BindingMode, id: T, - subs: impl Iterator<Item = (Name, T)>, + subs: impl Iterator<Item = (Name, T)> + ExactSizeIterator, ) -> Ty { let (ty, def) = self.resolve_variant(path, false); if let Some(variant) = def { @@ -121,17 +161,51 @@ impl InferenceContext<'_> { let substs = ty.as_adt().map(|(_, s)| s.clone()).unwrap_or_else(|| Substitution::empty(Interner)); - let field_tys = def.map(|it| self.db.field_types(it)).unwrap_or_default(); - let var_data = def.map(|it| it.variant_data(self.db.upcast())); + match def { + _ if subs.len() == 0 => {} + Some(def) => { + let field_types = self.db.field_types(def); + let variant_data = def.variant_data(self.db.upcast()); + let visibilities = self.db.field_visibilities(def); + + for (name, inner) in subs { + let field_def = { + match variant_data.field(&name) { + Some(local_id) => { + if !visibilities[local_id] + .is_visible_from(self.db.upcast(), self.resolver.module()) + { + self.push_diagnostic(InferenceDiagnostic::NoSuchField { + field: inner.into(), + private: true, + }); + } + Some(local_id) + } + None => { + self.push_diagnostic(InferenceDiagnostic::NoSuchField { + field: inner.into(), + private: false, + }); + None + } + } + }; - for (name, inner) in subs { - let expected_ty = var_data - .as_ref() - .and_then(|it| it.field(&name)) - .map_or(self.err_ty(), |f| field_tys[f].clone().substitute(Interner, &substs)); - let expected_ty = self.normalize_associated_types_in(expected_ty); + let expected_ty = field_def.map_or(self.err_ty(), |f| { + field_types[f].clone().substitute(Interner, &substs) + }); + let expected_ty = self.normalize_associated_types_in(expected_ty); - T::infer(self, inner, &expected_ty, default_bm); + T::infer(self, inner, &expected_ty, default_bm); + } + } + None => { + let err_ty = self.err_ty(); + for (_, inner) in subs { + T::infer(self, inner, &err_ty, default_bm); + } + } } ty diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs index 79d9e21e7..c6bbf2f61 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs @@ -61,8 +61,8 @@ impl InferenceContext<'_> { self.resolver.resolve_path_in_value_ns(self.db.upcast(), path)?; match value_or_partial { - ResolveValueResult::ValueNs(it) => (it, None), - ResolveValueResult::Partial(def, remaining_index) => self + ResolveValueResult::ValueNs(it, _) => (it, None), + ResolveValueResult::Partial(def, remaining_index, _) => self .resolve_assoc_item(def, path, remaining_index, id) .map(|(it, substs)| (it, Some(substs)))?, } @@ -178,13 +178,30 @@ impl InferenceContext<'_> { remaining_index: usize, id: ExprOrPatId, ) -> Option<(ValueNs, Substitution)> { - assert!(remaining_index < path.segments().len()); // there may be more intermediate segments between the resolved one and // the end. Only the last segment needs to be resolved to a value; from // the segments before that, we need to get either a type or a trait ref. - let resolved_segment = path.segments().get(remaining_index - 1).unwrap(); - let remaining_segments = path.segments().skip(remaining_index); + let _d; + let (resolved_segment, remaining_segments) = match path { + Path::Normal { .. } => { + assert!(remaining_index < path.segments().len()); + ( + path.segments().get(remaining_index - 1).unwrap(), + path.segments().skip(remaining_index), + ) + } + Path::LangItem(..) => ( + PathSegment { + name: { + _d = hir_expand::name::known::Unknown; + &_d + }, + args_and_bindings: None, + }, + path.segments(), + ), + }; let is_before_last = remaining_segments.len() == 1; match (def, is_before_last) { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs index 0fb71135b..0a68a9f3b 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs @@ -10,7 +10,6 @@ use chalk_solve::infer::ParameterEnaVariableExt; use either::Either; use ena::unify::UnifyKey; use hir_expand::name; -use stdx::never; use triomphe::Arc; use super::{InferOk, InferResult, InferenceContext, TypeError}; @@ -92,15 +91,10 @@ pub(crate) fn unify( let vars = Substitution::from_iter( Interner, tys.binders.iter(Interner).map(|it| match &it.kind { - chalk_ir::VariableKind::Ty(_) => { - GenericArgData::Ty(table.new_type_var()).intern(Interner) - } - chalk_ir::VariableKind::Lifetime => { - GenericArgData::Ty(table.new_type_var()).intern(Interner) - } // FIXME: maybe wrong? - chalk_ir::VariableKind::Const(ty) => { - GenericArgData::Const(table.new_const_var(ty.clone())).intern(Interner) - } + chalk_ir::VariableKind::Ty(_) => table.new_type_var().cast(Interner), + // FIXME: maybe wrong? + chalk_ir::VariableKind::Lifetime => table.new_type_var().cast(Interner), + chalk_ir::VariableKind::Const(ty) => table.new_const_var(ty.clone()).cast(Interner), }), ); let ty1_with_vars = vars.apply(tys.value.0.clone(), Interner); @@ -111,10 +105,10 @@ pub(crate) fn unify( // default any type vars that weren't unified back to their original bound vars // (kind of hacky) let find_var = |iv| { - vars.iter(Interner).position(|v| match v.interned() { - chalk_ir::GenericArgData::Ty(ty) => ty.inference_var(Interner), - chalk_ir::GenericArgData::Lifetime(lt) => lt.inference_var(Interner), - chalk_ir::GenericArgData::Const(c) => c.inference_var(Interner), + vars.iter(Interner).position(|v| match v.data(Interner) { + GenericArgData::Ty(ty) => ty.inference_var(Interner), + GenericArgData::Lifetime(lt) => lt.inference_var(Interner), + GenericArgData::Const(c) => c.inference_var(Interner), } == Some(iv)) }; let fallback = |iv, kind, default, binder| match kind { @@ -149,6 +143,9 @@ pub(crate) struct InferenceTable<'a> { var_unification_table: ChalkInferenceTable, type_variable_table: Vec<TypeVariableFlags>, pending_obligations: Vec<Canonicalized<InEnvironment<Goal>>>, + /// Double buffer used in [`Self::resolve_obligations_as_possible`] to cut down on + /// temporary allocations. + resolve_obligations_buffer: Vec<Canonicalized<InEnvironment<Goal>>>, } pub(crate) struct InferenceTableSnapshot { @@ -165,6 +162,7 @@ impl<'a> InferenceTable<'a> { var_unification_table: ChalkInferenceTable::new(), type_variable_table: Vec::new(), pending_obligations: Vec::new(), + resolve_obligations_buffer: Vec::new(), } } @@ -516,10 +514,10 @@ impl<'a> InferenceTable<'a> { pub(crate) fn resolve_obligations_as_possible(&mut self) { let _span = profile::span("resolve_obligations_as_possible"); let mut changed = true; - let mut obligations = Vec::new(); - while changed { - changed = false; + let mut obligations = mem::take(&mut self.resolve_obligations_buffer); + while mem::take(&mut changed) { mem::swap(&mut self.pending_obligations, &mut obligations); + for canonicalized in obligations.drain(..) { if !self.check_changed(&canonicalized) { self.pending_obligations.push(canonicalized); @@ -534,6 +532,8 @@ impl<'a> InferenceTable<'a> { self.register_obligation_in_env(uncanonical); } } + self.resolve_obligations_buffer = obligations; + self.resolve_obligations_buffer.clear(); } pub(crate) fn fudge_inference<T: TypeFoldable<Interner>>( @@ -611,9 +611,9 @@ impl<'a> InferenceTable<'a> { fn check_changed(&mut self, canonicalized: &Canonicalized<InEnvironment<Goal>>) -> bool { canonicalized.free_vars.iter().any(|var| { let iv = match var.data(Interner) { - chalk_ir::GenericArgData::Ty(ty) => ty.inference_var(Interner), - chalk_ir::GenericArgData::Lifetime(lt) => lt.inference_var(Interner), - chalk_ir::GenericArgData::Const(c) => c.inference_var(Interner), + GenericArgData::Ty(ty) => ty.inference_var(Interner), + GenericArgData::Lifetime(lt) => lt.inference_var(Interner), + GenericArgData::Const(c) => c.inference_var(Interner), } .expect("free var is not inference var"); if self.var_unification_table.probe_var(iv).is_some() { @@ -690,14 +690,10 @@ impl<'a> InferenceTable<'a> { .fill(|it| { let arg = match it { ParamKind::Type => self.new_type_var(), - ParamKind::Const(ty) => { - never!("Tuple with const parameter"); - return GenericArgData::Const(self.new_const_var(ty.clone())) - .intern(Interner); - } + ParamKind::Const(_) => unreachable!("Tuple with const parameter"), }; arg_tys.push(arg.clone()); - GenericArgData::Ty(arg).intern(Interner) + arg.cast(Interner) }) .build(); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs index b15339d44..1a6106c02 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs @@ -24,7 +24,7 @@ pub use self::{ macro_rules! user_error { ($it: expr) => { - return Err(LayoutError::UserError(format!($it))) + return Err(LayoutError::UserError(format!($it).into())) }; } @@ -50,7 +50,7 @@ pub type Variants = hir_def::layout::Variants<RustcEnumVariantIdx>; #[derive(Debug, PartialEq, Eq, Clone)] pub enum LayoutError { - UserError(String), + UserError(Box<str>), SizeOverflow, TargetLayoutNotAvailable, HasPlaceholder, @@ -109,7 +109,8 @@ fn layout_of_simd_ty( // * the homogeneous field type and the number of fields. let (e_ty, e_len, is_array) = if let TyKind::Array(e_ty, _) = f0_ty.kind(Interner) { // Extract the number of elements from the layout of the array field: - let FieldsShape::Array { count, .. } = db.layout_of_ty(f0_ty.clone(), env.clone())?.fields else { + let FieldsShape::Array { count, .. } = db.layout_of_ty(f0_ty.clone(), env.clone())?.fields + else { user_error!("Array with non array layout"); }; @@ -233,9 +234,9 @@ pub fn layout_of_ty_query( cx.univariant(dl, &fields, &ReprOptions::default(), kind).ok_or(LayoutError::Unknown)? } TyKind::Array(element, count) => { - let count = try_const_usize(db, &count).ok_or(LayoutError::UserError( - "unevaluated or mistyped const generic parameter".to_string(), - ))? as u64; + let count = try_const_usize(db, &count).ok_or(LayoutError::UserError(Box::from( + "unevaluated or mistyped const generic parameter", + )))? as u64; let element = db.layout_of_ty(element.clone(), trait_env.clone())?; let size = element.size.checked_mul(count, dl).ok_or(LayoutError::SizeOverflow)?; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs index 1c92e80f3..85ef649b8 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs @@ -163,7 +163,7 @@ fn repr_discr( return Err(LayoutError::UserError( "Integer::repr_discr: `#[repr]` hint too small for \ discriminant range of enum " - .to_string(), + .into(), )); } return Ok((discr, ity.is_signed())); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs index 333ad473a..ffdbb9de9 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs @@ -212,14 +212,14 @@ fn recursive() { } check_fail( r#"struct Goal(Goal);"#, - LayoutError::UserError("infinite sized recursive type".to_string()), + LayoutError::UserError("infinite sized recursive type".into()), ); check_fail( r#" struct Foo<T>(Foo<T>); struct Goal(Foo<i32>); "#, - LayoutError::UserError("infinite sized recursive type".to_string()), + LayoutError::UserError("infinite sized recursive type".into()), ); } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests/closure.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests/closure.rs index 576e7f3fc..bbe855a14 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests/closure.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests/closure.rs @@ -255,3 +255,17 @@ fn ellipsis_pattern() { } } } + +#[test] +fn regression_15623() { + size_and_align_expr! { + let a = 2; + let b = 3; + let c = 5; + move || { + let 0 = a else { return b; }; + let y = c; + y + } + } +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs index b3ca2a222..405bb001b 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs @@ -52,12 +52,14 @@ use hir_expand::name; use la_arena::{Arena, Idx}; use mir::{MirEvalError, VTableMap}; use rustc_hash::FxHashSet; +use syntax::ast::{make, ConstArg}; use traits::FnTrait; use triomphe::Arc; use utils::Generics; use crate::{ - consteval::unknown_const, db::HirDatabase, infer::unify::InferenceTable, utils::generics, + consteval::unknown_const, db::HirDatabase, display::HirDisplay, infer::unify::InferenceTable, + utils::generics, }; pub use autoderef::autoderef; @@ -719,3 +721,16 @@ where value.visit_with(&mut collector, DebruijnIndex::INNERMOST); collector.placeholders.into_iter().collect() } + +pub fn known_const_to_ast(konst: &Const, db: &dyn HirDatabase) -> Option<ConstArg> { + if let ConstValue::Concrete(c) = &konst.interned().value { + match c.interned { + ConstScalar::UnevaluatedConst(GeneralConstId::InTypeConstId(cid), _) => { + return Some(cid.source(db.upcast())); + } + ConstScalar::Unknown => return None, + _ => (), + } + } + Some(make::expr_const_value(konst.display(db).to_string().as_str())) +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs index 2837f400b..9a61f1535 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs @@ -58,10 +58,9 @@ use crate::{ InTypeConstIdMetadata, }, AliasEq, AliasTy, Binders, BoundVar, CallableSig, Const, ConstScalar, DebruijnIndex, DynTy, - FnPointer, FnSig, FnSubst, GenericArgData, ImplTraitId, Interner, ParamKind, PolyFnSig, - ProjectionTy, QuantifiedWhereClause, QuantifiedWhereClauses, ReturnTypeImplTrait, - ReturnTypeImplTraits, Substitution, TraitEnvironment, TraitRef, TraitRefExt, Ty, TyBuilder, - TyKind, WhereClause, + FnPointer, FnSig, FnSubst, ImplTraitId, Interner, ParamKind, PolyFnSig, ProjectionTy, + QuantifiedWhereClause, QuantifiedWhereClauses, ReturnTypeImplTrait, ReturnTypeImplTraits, + Substitution, TraitEnvironment, TraitRef, TraitRefExt, Ty, TyBuilder, TyKind, WhereClause, }; #[derive(Debug)] @@ -213,6 +212,19 @@ impl<'a> TyLoweringContext<'a> { self.lower_ty_ext(type_ref).0 } + pub fn lower_const(&self, const_ref: &ConstRef, const_type: Ty) -> Const { + const_or_path_to_chalk( + self.db, + self.resolver, + self.owner, + const_type, + const_ref, + self.type_param_mode, + || self.generics(), + self.in_binders, + ) + } + fn generics(&self) -> Generics { generics( self.db.upcast(), @@ -242,17 +254,7 @@ impl<'a> TyLoweringContext<'a> { } TypeRef::Array(inner, len) => { let inner_ty = self.lower_ty(inner); - let const_len = const_or_path_to_chalk( - self.db, - self.resolver, - self.owner, - TyBuilder::usize(), - len, - self.type_param_mode, - || self.generics(), - self.in_binders, - ); - + let const_len = self.lower_const(len, TyBuilder::usize()); TyKind::Array(inner_ty, const_len).intern(Interner) } TypeRef::Slice(inner) => { @@ -391,11 +393,9 @@ impl<'a> TyLoweringContext<'a> { let ty = { let macro_call = macro_call.to_node(self.db.upcast()); let resolver = |path| { - self.resolver.resolve_path_as_macro( - self.db.upcast(), - &path, - Some(MacroSubNs::Bang), - ) + self.resolver + .resolve_path_as_macro(self.db.upcast(), &path, Some(MacroSubNs::Bang)) + .map(|(it, _)| it) }; match expander.enter_expand::<ast::Type>(self.db.upcast(), macro_call, resolver) { @@ -447,7 +447,7 @@ impl<'a> TyLoweringContext<'a> { return None; } let resolution = match self.resolver.resolve_path_in_type_ns(self.db.upcast(), path) { - Some((it, None)) => it, + Some((it, None, _)) => it, _ => return None, }; match resolution { @@ -627,7 +627,7 @@ impl<'a> TyLoweringContext<'a> { return self.lower_ty_relative_path(ty, res, path.segments()); } - let (resolution, remaining_index) = + let (resolution, remaining_index, _) = match self.resolver.resolve_path_in_type_ns(self.db.upcast(), path) { Some(it) => it, None => return (TyKind::Error.intern(Interner), None), @@ -847,18 +847,7 @@ impl<'a> TyLoweringContext<'a> { arg, &mut (), |_, type_ref| self.lower_ty(type_ref), - |_, c, ty| { - const_or_path_to_chalk( - self.db, - self.resolver, - self.owner, - ty, - c, - self.type_param_mode, - || self.generics(), - self.in_binders, - ) - }, + |_, const_ref, ty| self.lower_const(const_ref, ty), ) { had_explicit_args = true; substs.push(x); @@ -1604,24 +1593,35 @@ pub(crate) fn generic_defaults_query( .iter() .enumerate() .map(|(idx, (id, p))| { - let p = match p { - TypeOrConstParamData::TypeParamData(p) => p, - TypeOrConstParamData::ConstParamData(_) => { - // FIXME: implement const generic defaults - let val = unknown_const_as_generic( - db.const_param_ty(ConstParamId::from_unchecked(id)), + match p { + TypeOrConstParamData::TypeParamData(p) => { + let mut ty = p + .default + .as_ref() + .map_or(TyKind::Error.intern(Interner), |t| ctx.lower_ty(t)); + // Each default can only refer to previous parameters. + // Type variable default referring to parameter coming + // after it is forbidden (FIXME: report diagnostic) + ty = fallback_bound_vars(ty, idx, parent_start_idx); + crate::make_binders(db, &generic_params, ty.cast(Interner)) + } + TypeOrConstParamData::ConstParamData(p) => { + let mut val = p.default.as_ref().map_or_else( + || { + unknown_const_as_generic( + db.const_param_ty(ConstParamId::from_unchecked(id)), + ) + }, + |c| { + let c = ctx.lower_const(c, ctx.lower_ty(&p.ty)); + c.cast(Interner) + }, ); - return make_binders(db, &generic_params, val); + // Each default can only refer to previous parameters, see above. + val = fallback_bound_vars(val, idx, parent_start_idx); + make_binders(db, &generic_params, val) } - }; - let mut ty = - p.default.as_ref().map_or(TyKind::Error.intern(Interner), |t| ctx.lower_ty(t)); - - // Each default can only refer to previous parameters. - // Type variable default referring to parameter coming - // after it is forbidden (FIXME: report diagnostic) - ty = fallback_bound_vars(ty, idx, parent_start_idx); - crate::make_binders(db, &generic_params, ty.cast(Interner)) + } }) // FIXME: use `Arc::from_iter` when it becomes available .collect::<Vec<_>>(), @@ -1643,9 +1643,7 @@ pub(crate) fn generic_defaults_recover( .iter_id() .map(|id| { let val = match id { - Either::Left(_) => { - GenericArgData::Ty(TyKind::Error.intern(Interner)).intern(Interner) - } + Either::Left(_) => TyKind::Error.intern(Interner).cast(Interner), Either::Right(id) => unknown_const_as_generic(db.const_param_ty(id)), }; crate::make_binders(db, &generic_params, val) @@ -1991,16 +1989,9 @@ pub(crate) fn generic_arg_to_chalk<'a, T>( } }; Some(match (arg, kind) { - (GenericArg::Type(type_ref), ParamKind::Type) => { - let ty = for_type(this, type_ref); - GenericArgData::Ty(ty).intern(Interner) - } - (GenericArg::Const(c), ParamKind::Const(c_ty)) => { - GenericArgData::Const(for_const(this, c, c_ty)).intern(Interner) - } - (GenericArg::Const(_), ParamKind::Type) => { - GenericArgData::Ty(TyKind::Error.intern(Interner)).intern(Interner) - } + (GenericArg::Type(type_ref), ParamKind::Type) => for_type(this, type_ref).cast(Interner), + (GenericArg::Const(c), ParamKind::Const(c_ty)) => for_const(this, c, c_ty).cast(Interner), + (GenericArg::Const(_), ParamKind::Type) => TyKind::Error.intern(Interner).cast(Interner), (GenericArg::Type(t), ParamKind::Const(c_ty)) => { // We want to recover simple idents, which parser detects them // as types. Maybe here is not the best place to do it, but @@ -2010,9 +2001,7 @@ pub(crate) fn generic_arg_to_chalk<'a, T>( if p.kind == PathKind::Plain { if let [n] = p.segments() { let c = ConstRef::Path(n.clone()); - return Some( - GenericArgData::Const(for_const(this, &c, c_ty)).intern(Interner), - ); + return Some(for_const(this, &c, c_ty).cast(Interner)); } } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs index 4723c25ed..e953058cc 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs @@ -1,6 +1,6 @@ //! MIR definitions and implementation -use std::{fmt::Display, iter}; +use std::{collections::hash_map::Entry, fmt::Display, iter}; use crate::{ consteval::usize_const, @@ -37,6 +37,7 @@ pub use monomorphization::{ monomorphize_mir_body_bad, monomorphized_mir_body_for_closure_query, monomorphized_mir_body_query, monomorphized_mir_body_recover, }; +use rustc_hash::FxHashMap; use smallvec::{smallvec, SmallVec}; use stdx::{impl_from, never}; use triomphe::Arc; @@ -165,8 +166,8 @@ impl<V, T> ProjectionElem<V, T> { TyKind::Adt(_, subst) => { db.field_types(f.parent)[f.local_id].clone().substitute(Interner, subst) } - _ => { - never!("Only adt has field"); + ty => { + never!("Only adt has field, found {:?}", ty); return TyKind::Error.intern(Interner); } }, @@ -223,35 +224,93 @@ impl<V, T> ProjectionElem<V, T> { type PlaceElem = ProjectionElem<LocalId, Ty>; +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ProjectionId(u32); + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ProjectionStore { + id_to_proj: FxHashMap<ProjectionId, Box<[PlaceElem]>>, + proj_to_id: FxHashMap<Box<[PlaceElem]>, ProjectionId>, +} + +impl Default for ProjectionStore { + fn default() -> Self { + let mut this = Self { id_to_proj: Default::default(), proj_to_id: Default::default() }; + // Ensure that [] will get the id 0 which is used in `ProjectionId::Empty` + this.intern(Box::new([])); + this + } +} + +impl ProjectionStore { + fn shrink_to_fit(&mut self) { + self.id_to_proj.shrink_to_fit(); + self.proj_to_id.shrink_to_fit(); + } + + fn intern_if_exist(&self, projection: &[PlaceElem]) -> Option<ProjectionId> { + self.proj_to_id.get(projection).copied() + } + + fn intern(&mut self, projection: Box<[PlaceElem]>) -> ProjectionId { + let new_id = ProjectionId(self.proj_to_id.len() as u32); + match self.proj_to_id.entry(projection) { + Entry::Occupied(id) => *id.get(), + Entry::Vacant(e) => { + let key_clone = e.key().clone(); + e.insert(new_id); + self.id_to_proj.insert(new_id, key_clone); + new_id + } + } + } +} + +impl ProjectionId { + const EMPTY: ProjectionId = ProjectionId(0); + + fn lookup(self, store: &ProjectionStore) -> &[PlaceElem] { + store.id_to_proj.get(&self).unwrap() + } + + fn project(self, projection: PlaceElem, store: &mut ProjectionStore) -> ProjectionId { + let mut current = self.lookup(store).to_vec(); + current.push(projection); + store.intern(current.into()) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Place { pub local: LocalId, - pub projection: Box<[PlaceElem]>, + pub projection: ProjectionId, } impl Place { - fn is_parent(&self, child: &Place) -> bool { - self.local == child.local && child.projection.starts_with(&self.projection) + fn is_parent(&self, child: &Place, store: &ProjectionStore) -> bool { + self.local == child.local + && child.projection.lookup(store).starts_with(&self.projection.lookup(store)) } /// The place itself is not included - fn iterate_over_parents(&self) -> impl Iterator<Item = Place> + '_ { - (0..self.projection.len()) - .map(|x| &self.projection[0..x]) - .map(|x| Place { local: self.local, projection: x.to_vec().into() }) + fn iterate_over_parents<'a>( + &'a self, + store: &'a ProjectionStore, + ) -> impl Iterator<Item = Place> + 'a { + let projection = self.projection.lookup(store); + (0..projection.len()).map(|x| &projection[0..x]).filter_map(move |x| { + Some(Place { local: self.local, projection: store.intern_if_exist(x)? }) + }) } - fn project(&self, projection: PlaceElem) -> Place { - Place { - local: self.local, - projection: self.projection.iter().cloned().chain([projection]).collect(), - } + fn project(&self, projection: PlaceElem, store: &mut ProjectionStore) -> Place { + Place { local: self.local, projection: self.projection.project(projection, store) } } } impl From<LocalId> for Place { fn from(local: LocalId) -> Self { - Self { local, projection: vec![].into() } + Self { local, projection: ProjectionId::EMPTY } } } @@ -368,7 +427,7 @@ pub enum TerminatorKind { /// /// Only permitted in cleanup blocks. `Resume` is not permitted with `-C unwind=abort` after /// deaggregation runs. - Resume, + UnwindResume, /// Indicates that the landing pad is finished and that the process should abort. /// @@ -997,6 +1056,7 @@ pub struct BasicBlock { #[derive(Debug, Clone, PartialEq, Eq)] pub struct MirBody { + pub projection_store: ProjectionStore, pub basic_blocks: Arena<BasicBlock>, pub locals: Arena<Local>, pub start_block: BasicBlockId, @@ -1009,11 +1069,15 @@ pub struct MirBody { } impl MirBody { - fn walk_places(&mut self, mut f: impl FnMut(&mut Place)) { - fn for_operand(op: &mut Operand, f: &mut impl FnMut(&mut Place)) { + fn walk_places(&mut self, mut f: impl FnMut(&mut Place, &mut ProjectionStore)) { + fn for_operand( + op: &mut Operand, + f: &mut impl FnMut(&mut Place, &mut ProjectionStore), + store: &mut ProjectionStore, + ) { match op { Operand::Copy(p) | Operand::Move(p) => { - f(p); + f(p, store); } Operand::Constant(_) | Operand::Static(_) => (), } @@ -1022,30 +1086,30 @@ impl MirBody { for statement in &mut block.statements { match &mut statement.kind { StatementKind::Assign(p, r) => { - f(p); + f(p, &mut self.projection_store); match r { Rvalue::ShallowInitBoxWithAlloc(_) => (), Rvalue::ShallowInitBox(o, _) | Rvalue::UnaryOp(_, o) | Rvalue::Cast(_, o, _) | Rvalue::Repeat(o, _) - | Rvalue::Use(o) => for_operand(o, &mut f), + | Rvalue::Use(o) => for_operand(o, &mut f, &mut self.projection_store), Rvalue::CopyForDeref(p) | Rvalue::Discriminant(p) | Rvalue::Len(p) - | Rvalue::Ref(_, p) => f(p), + | Rvalue::Ref(_, p) => f(p, &mut self.projection_store), Rvalue::CheckedBinaryOp(_, o1, o2) => { - for_operand(o1, &mut f); - for_operand(o2, &mut f); + for_operand(o1, &mut f, &mut self.projection_store); + for_operand(o2, &mut f, &mut self.projection_store); } Rvalue::Aggregate(_, ops) => { for op in ops.iter_mut() { - for_operand(op, &mut f); + for_operand(op, &mut f, &mut self.projection_store); } } } } - StatementKind::Deinit(p) => f(p), + StatementKind::Deinit(p) => f(p, &mut self.projection_store), StatementKind::StorageLive(_) | StatementKind::StorageDead(_) | StatementKind::Nop => (), @@ -1053,33 +1117,36 @@ impl MirBody { } match &mut block.terminator { Some(x) => match &mut x.kind { - TerminatorKind::SwitchInt { discr, .. } => for_operand(discr, &mut f), + TerminatorKind::SwitchInt { discr, .. } => { + for_operand(discr, &mut f, &mut self.projection_store) + } TerminatorKind::FalseEdge { .. } | TerminatorKind::FalseUnwind { .. } | TerminatorKind::Goto { .. } - | TerminatorKind::Resume + | TerminatorKind::UnwindResume | TerminatorKind::GeneratorDrop | TerminatorKind::Abort | TerminatorKind::Return | TerminatorKind::Unreachable => (), TerminatorKind::Drop { place, .. } => { - f(place); + f(place, &mut self.projection_store); } TerminatorKind::DropAndReplace { place, value, .. } => { - f(place); - for_operand(value, &mut f); + f(place, &mut self.projection_store); + for_operand(value, &mut f, &mut self.projection_store); } TerminatorKind::Call { func, args, destination, .. } => { - for_operand(func, &mut f); - args.iter_mut().for_each(|x| for_operand(x, &mut f)); - f(destination); + for_operand(func, &mut f, &mut self.projection_store); + args.iter_mut() + .for_each(|x| for_operand(x, &mut f, &mut self.projection_store)); + f(destination, &mut self.projection_store); } TerminatorKind::Assert { cond, .. } => { - for_operand(cond, &mut f); + for_operand(cond, &mut f, &mut self.projection_store); } TerminatorKind::Yield { value, resume_arg, .. } => { - for_operand(value, &mut f); - f(resume_arg); + for_operand(value, &mut f, &mut self.projection_store); + f(resume_arg, &mut self.projection_store); } }, None => (), @@ -1096,7 +1163,9 @@ impl MirBody { binding_locals, param_locals, closures, + projection_store, } = self; + projection_store.shrink_to_fit(); basic_blocks.shrink_to_fit(); locals.shrink_to_fit(); binding_locals.shrink_to_fit(); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs index ad98e8fa1..41fb12965 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs @@ -42,30 +42,27 @@ pub struct BorrowckResult { fn all_mir_bodies( db: &dyn HirDatabase, def: DefWithBodyId, -) -> Box<dyn Iterator<Item = Result<Arc<MirBody>, MirLowerError>> + '_> { + mut cb: impl FnMut(Arc<MirBody>), +) -> Result<(), MirLowerError> { fn for_closure( db: &dyn HirDatabase, c: ClosureId, - ) -> Box<dyn Iterator<Item = Result<Arc<MirBody>, MirLowerError>> + '_> { + cb: &mut impl FnMut(Arc<MirBody>), + ) -> Result<(), MirLowerError> { match db.mir_body_for_closure(c) { Ok(body) => { - let closures = body.closures.clone(); - Box::new( - iter::once(Ok(body)) - .chain(closures.into_iter().flat_map(|it| for_closure(db, it))), - ) + cb(body.clone()); + body.closures.iter().map(|&it| for_closure(db, it, cb)).collect() } - Err(e) => Box::new(iter::once(Err(e))), + Err(e) => Err(e), } } match db.mir_body(def) { Ok(body) => { - let closures = body.closures.clone(); - Box::new( - iter::once(Ok(body)).chain(closures.into_iter().flat_map(|it| for_closure(db, it))), - ) + cb(body.clone()); + body.closures.iter().map(|&it| for_closure(db, it, &mut cb)).collect() } - Err(e) => Box::new(iter::once(Err(e))), + Err(e) => Err(e), } } @@ -74,17 +71,15 @@ pub fn borrowck_query( def: DefWithBodyId, ) -> Result<Arc<[BorrowckResult]>, MirLowerError> { let _p = profile::span("borrowck_query"); - let r = all_mir_bodies(db, def) - .map(|body| { - let body = body?; - Ok(BorrowckResult { - mutability_of_locals: mutability_of_locals(db, &body), - moved_out_of_ref: moved_out_of_ref(db, &body), - mir_body: body, - }) - }) - .collect::<Result<Vec<_>, MirLowerError>>()?; - Ok(r.into()) + let mut res = vec![]; + all_mir_bodies(db, def, |body| { + res.push(BorrowckResult { + mutability_of_locals: mutability_of_locals(db, &body), + moved_out_of_ref: moved_out_of_ref(db, &body), + mir_body: body, + }); + })?; + Ok(res.into()) } fn moved_out_of_ref(db: &dyn HirDatabase, body: &MirBody) -> Vec<MovedOutOfRef> { @@ -93,7 +88,7 @@ fn moved_out_of_ref(db: &dyn HirDatabase, body: &MirBody) -> Vec<MovedOutOfRef> Operand::Copy(p) | Operand::Move(p) => { let mut ty: Ty = body.locals[p.local].ty.clone(); let mut is_dereference_of_ref = false; - for proj in &*p.projection { + for proj in p.projection.lookup(&body.projection_store) { if *proj == ProjectionElem::Deref && ty.as_reference().is_some() { is_dereference_of_ref = true; } @@ -125,6 +120,7 @@ fn moved_out_of_ref(db: &dyn HirDatabase, body: &MirBody) -> Vec<MovedOutOfRef> Operand::Constant(_) | Operand::Static(_) => (), }; for (_, block) in body.basic_blocks.iter() { + db.unwind_if_cancelled(); for statement in &block.statements { match &statement.kind { StatementKind::Assign(_, r) => match r { @@ -160,7 +156,7 @@ fn moved_out_of_ref(db: &dyn HirDatabase, body: &MirBody) -> Vec<MovedOutOfRef> TerminatorKind::FalseEdge { .. } | TerminatorKind::FalseUnwind { .. } | TerminatorKind::Goto { .. } - | TerminatorKind::Resume + | TerminatorKind::UnwindResume | TerminatorKind::GeneratorDrop | TerminatorKind::Abort | TerminatorKind::Return @@ -183,6 +179,7 @@ fn moved_out_of_ref(db: &dyn HirDatabase, body: &MirBody) -> Vec<MovedOutOfRef> None => (), } } + result.shrink_to_fit(); result } @@ -199,7 +196,7 @@ enum ProjectionCase { fn place_case(db: &dyn HirDatabase, body: &MirBody, lvalue: &Place) -> ProjectionCase { let mut is_part_of = false; let mut ty = body.locals[lvalue.local].ty.clone(); - for proj in lvalue.projection.iter() { + for proj in lvalue.projection.lookup(&body.projection_store).iter() { match proj { ProjectionElem::Deref if ty.as_adt().is_none() => return ProjectionCase::Indirect, // It's indirect in case of reference and raw ProjectionElem::Deref // It's direct in case of `Box<T>` @@ -258,7 +255,7 @@ fn ever_initialized_map( for statement in &block.statements { match &statement.kind { StatementKind::Assign(p, _) => { - if p.projection.len() == 0 && p.local == l { + if p.projection.lookup(&body.projection_store).len() == 0 && p.local == l { is_ever_initialized = true; } } @@ -277,21 +274,37 @@ fn ever_initialized_map( ); return; }; - let targets = match &terminator.kind { - TerminatorKind::Goto { target } => vec![*target], - TerminatorKind::SwitchInt { targets, .. } => targets.all_targets().to_vec(), - TerminatorKind::Resume + let mut process = |target, is_ever_initialized| { + if !result[target].contains_idx(l) || !result[target][l] && is_ever_initialized { + result[target].insert(l, is_ever_initialized); + dfs(db, body, target, l, result); + } + }; + match &terminator.kind { + TerminatorKind::Goto { target } => process(*target, is_ever_initialized), + TerminatorKind::SwitchInt { targets, .. } => { + targets.all_targets().iter().for_each(|&it| process(it, is_ever_initialized)); + } + TerminatorKind::UnwindResume | TerminatorKind::Abort | TerminatorKind::Return - | TerminatorKind::Unreachable => vec![], + | TerminatorKind::Unreachable => (), TerminatorKind::Call { target, cleanup, destination, .. } => { - if destination.projection.len() == 0 && destination.local == l { + if destination.projection.lookup(&body.projection_store).len() == 0 + && destination.local == l + { is_ever_initialized = true; } - target.into_iter().chain(cleanup.into_iter()).copied().collect() + target + .into_iter() + .chain(cleanup.into_iter()) + .for_each(|&it| process(it, is_ever_initialized)); } TerminatorKind::Drop { target, unwind, place: _ } => { - Some(target).into_iter().chain(unwind.into_iter()).copied().collect() + iter::once(target) + .into_iter() + .chain(unwind.into_iter()) + .for_each(|&it| process(it, is_ever_initialized)); } TerminatorKind::DropAndReplace { .. } | TerminatorKind::Assert { .. } @@ -300,13 +313,7 @@ fn ever_initialized_map( | TerminatorKind::FalseEdge { .. } | TerminatorKind::FalseUnwind { .. } => { never!("We don't emit these MIR terminators yet"); - vec![] - } - }; - for target in targets { - if !result[target].contains_idx(l) || !result[target][l] && is_ever_initialized { - result[target].insert(l, is_ever_initialized); - dfs(db, body, target, l, result); + () } } } @@ -315,6 +322,7 @@ fn ever_initialized_map( dfs(db, body, body.start_block, l, &mut result); } for l in body.locals.iter().map(|it| it.0) { + db.unwind_if_cancelled(); if !result[body.start_block].contains_idx(l) { result[body.start_block].insert(l, false); dfs(db, body, body.start_block, l, &mut result); @@ -371,7 +379,7 @@ fn mutability_of_locals( }; match &terminator.kind { TerminatorKind::Goto { .. } - | TerminatorKind::Resume + | TerminatorKind::UnwindResume | TerminatorKind::Abort | TerminatorKind::Return | TerminatorKind::Unreachable @@ -384,7 +392,7 @@ fn mutability_of_locals( | TerminatorKind::Assert { .. } | TerminatorKind::Yield { .. } => (), TerminatorKind::Call { destination, .. } => { - if destination.projection.len() == 0 { + if destination.projection.lookup(&body.projection_store).len() == 0 { if ever_init_map.get(destination.local).copied().unwrap_or_default() { push_mut_span(destination.local, MirSpan::Unknown); } else { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs index 9e30eed56..4364e0d32 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs @@ -10,7 +10,7 @@ use std::{ }; use base_db::{CrateId, FileId}; -use chalk_ir::Mutability; +use chalk_ir::{cast::Cast, Mutability}; use either::Either; use hir_def::{ builtin_type::BuiltinType, @@ -40,14 +40,14 @@ use crate::{ name, static_lifetime, traits::FnTrait, utils::{detect_variant_from_bytes, ClosureSubst}, - CallableDefId, ClosureId, Const, ConstScalar, FnDefId, GenericArgData, Interner, MemoryMap, - Substitution, TraitEnvironment, Ty, TyBuilder, TyExt, TyKind, + CallableDefId, ClosureId, Const, ConstScalar, FnDefId, Interner, MemoryMap, Substitution, + TraitEnvironment, Ty, TyBuilder, TyExt, TyKind, }; use super::{ return_slot, AggregateKind, BasicBlockId, BinOp, CastKind, LocalId, MirBody, MirLowerError, - MirSpan, Operand, Place, PlaceElem, ProjectionElem, Rvalue, StatementKind, TerminatorKind, - UnOp, + MirSpan, Operand, Place, PlaceElem, ProjectionElem, ProjectionStore, Rvalue, StatementKind, + TerminatorKind, UnOp, }; mod shim; @@ -215,9 +215,7 @@ impl Interval { } fn write_from_interval(&self, memory: &mut Evaluator<'_>, interval: Interval) -> Result<()> { - // FIXME: this could be more efficient - let bytes = &interval.get(memory)?.to_vec(); - memory.write_memory(self.addr, bytes) + memory.copy_from_interval(self.addr, interval) } fn slice(self, range: Range<usize>) -> Interval { @@ -341,7 +339,7 @@ pub enum MirEvalError { InvalidVTableId(usize), CoerceUnsizedError(Ty), LangItemNotFound(LangItem), - BrokenLayout(Layout), + BrokenLayout(Box<Layout>), } impl MirEvalError { @@ -410,7 +408,7 @@ impl MirEvalError { err.pretty_print(f, db, span_formatter)?; } MirEvalError::ConstEvalError(name, err) => { - MirLowerError::ConstEvalError(name.clone(), err.clone()).pretty_print( + MirLowerError::ConstEvalError((**name).into(), err.clone()).pretty_print( f, db, span_formatter, @@ -485,17 +483,18 @@ struct DropFlags { } impl DropFlags { - fn add_place(&mut self, p: Place) { - if p.iterate_over_parents().any(|it| self.need_drop.contains(&it)) { + fn add_place(&mut self, p: Place, store: &ProjectionStore) { + if p.iterate_over_parents(store).any(|it| self.need_drop.contains(&it)) { return; } - self.need_drop.retain(|it| !p.is_parent(it)); + self.need_drop.retain(|it| !p.is_parent(it, store)); self.need_drop.insert(p); } - fn remove_place(&mut self, p: &Place) -> bool { + fn remove_place(&mut self, p: &Place, store: &ProjectionStore) -> bool { // FIXME: replace parents with parts - if let Some(parent) = p.iterate_over_parents().find(|it| self.need_drop.contains(&it)) { + if let Some(parent) = p.iterate_over_parents(store).find(|it| self.need_drop.contains(&it)) + { self.need_drop.remove(&parent); return true; } @@ -656,7 +655,7 @@ impl Evaluator<'_> { let mut addr = locals.ptr[p.local].addr; let mut ty: Ty = locals.body.locals[p.local].ty.clone(); let mut metadata: Option<IntervalOrOwned> = None; // locals are always sized - for proj in &*p.projection { + for proj in p.projection.lookup(&locals.body.projection_store) { let prev_ty = ty.clone(); ty = self.projected_ty(ty, proj.clone()); match proj { @@ -837,7 +836,9 @@ impl Evaluator<'_> { let addr = self.place_addr(l, &locals)?; let result = self.eval_rvalue(r, &mut locals)?.to_vec(&self)?; self.write_memory(addr, &result)?; - locals.drop_flags.add_place(l.clone()); + locals + .drop_flags + .add_place(l.clone(), &locals.body.projection_store); } StatementKind::Deinit(_) => not_supported!("de-init statement"), StatementKind::StorageLive(_) @@ -889,7 +890,9 @@ impl Evaluator<'_> { )?, it => not_supported!("unknown function type {it:?}"), }; - locals.drop_flags.add_place(destination.clone()); + locals + .drop_flags + .add_place(destination.clone(), &locals.body.projection_store); if let Some(stack_frame) = stack_frame { self.code_stack.push(my_stack_frame); current_block_idx = stack_frame.locals.body.start_block; @@ -970,7 +973,7 @@ impl Evaluator<'_> { ) -> Result<()> { let mut remain_args = body.param_locals.len(); for ((l, interval), value) in locals.ptr.iter().skip(1).zip(args) { - locals.drop_flags.add_place(l.into()); + locals.drop_flags.add_place(l.into(), &locals.body.projection_store); match value { IntervalOrOwned::Owned(value) => interval.write_from_bytes(self, &value)?, IntervalOrOwned::Borrowed(value) => interval.write_from_interval(self, value)?, @@ -1629,7 +1632,7 @@ impl Evaluator<'_> { if let Some((offset, size, value)) = tag { match result.get_mut(offset..offset + size) { Some(it) => it.copy_from_slice(&value.to_le_bytes()[0..size]), - None => return Err(MirEvalError::BrokenLayout(variant_layout.clone())), + None => return Err(MirEvalError::BrokenLayout(Box::new(variant_layout.clone()))), } } for (i, op) in values.enumerate() { @@ -1637,7 +1640,7 @@ impl Evaluator<'_> { let op = op.get(&self)?; match result.get_mut(offset..offset + op.len()) { Some(it) => it.copy_from_slice(op), - None => return Err(MirEvalError::BrokenLayout(variant_layout.clone())), + None => return Err(MirEvalError::BrokenLayout(Box::new(variant_layout.clone()))), } } Ok(result) @@ -1646,7 +1649,7 @@ impl Evaluator<'_> { fn eval_operand(&mut self, it: &Operand, locals: &mut Locals) -> Result<Interval> { Ok(match it { Operand::Copy(p) | Operand::Move(p) => { - locals.drop_flags.remove_place(p); + locals.drop_flags.remove_place(p, &locals.body.projection_store); self.eval_place(p, locals)? } Operand::Static(st) => { @@ -1760,6 +1763,48 @@ impl Evaluator<'_> { Ok(()) } + fn copy_from_interval(&mut self, addr: Address, r: Interval) -> Result<()> { + if r.size == 0 { + return Ok(()); + } + + let oob = || MirEvalError::UndefinedBehavior("out of bounds memory write".to_string()); + + match (addr, r.addr) { + (Stack(dst), Stack(src)) => { + if self.stack.len() < src + r.size || self.stack.len() < dst + r.size { + return Err(oob()); + } + self.stack.copy_within(src..src + r.size, dst) + } + (Heap(dst), Heap(src)) => { + if self.stack.len() < src + r.size || self.stack.len() < dst + r.size { + return Err(oob()); + } + self.heap.copy_within(src..src + r.size, dst) + } + (Stack(dst), Heap(src)) => { + self.stack + .get_mut(dst..dst + r.size) + .ok_or_else(oob)? + .copy_from_slice(self.heap.get(src..src + r.size).ok_or_else(oob)?); + } + (Heap(dst), Stack(src)) => { + self.heap + .get_mut(dst..dst + r.size) + .ok_or_else(oob)? + .copy_from_slice(self.stack.get(src..src + r.size).ok_or_else(oob)?); + } + _ => { + return Err(MirEvalError::UndefinedBehavior(format!( + "invalid memory write at address {addr:?}" + ))) + } + } + + Ok(()) + } + fn size_align_of(&self, ty: &Ty, locals: &Locals) -> Result<Option<(usize, usize)>> { if let Some(layout) = self.layout_cache.borrow().get(ty) { return Ok(layout @@ -2007,7 +2052,28 @@ impl Evaluator<'_> { } } AdtId::UnionId(_) => (), - AdtId::EnumId(_) => (), + AdtId::EnumId(e) => { + if let Some((variant, layout)) = detect_variant_from_bytes( + &layout, + self.db, + self.trait_env.clone(), + self.read_memory(addr, layout.size.bytes_usize())?, + e, + ) { + let ev = EnumVariantId { parent: e, local_id: variant }; + for (i, (_, ty)) in self.db.field_types(ev.into()).iter().enumerate() { + let offset = layout.fields.offset(i).bytes_usize(); + let ty = ty.clone().substitute(Interner, subst); + self.patch_addresses( + patch_map, + old_vtable, + addr.offset(offset), + &ty, + locals, + )?; + } + } + } }, TyKind::Tuple(_, subst) => { for (id, ty) in subst.iter(Interner).enumerate() { @@ -2248,7 +2314,7 @@ impl Evaluator<'_> { interval: args_for_target[0].interval.slice(0..self.ptr_size()), ty: ty.clone(), }; - let ty = GenericArgData::Ty(ty.clone()).intern(Interner); + let ty = ty.clone().cast(Interner); let generics_for_target = Substitution::from_iter( Interner, generic_args.iter(Interner).enumerate().map(|(i, it)| { @@ -2447,7 +2513,7 @@ impl Evaluator<'_> { fn drop_place(&mut self, place: &Place, locals: &mut Locals, span: MirSpan) -> Result<()> { let (addr, ty, metadata) = self.place_addr_and_ty_and_metadata(place, locals)?; - if !locals.drop_flags.remove_place(place) { + if !locals.drop_flags.remove_place(place, &locals.body.projection_store) { return Ok(()); } let metadata = match metadata { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim.rs index b2e29fd34..803ef631f 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim.rs @@ -4,7 +4,10 @@ use std::cmp; use chalk_ir::TyKind; -use hir_def::resolver::HasResolver; +use hir_def::{ + builtin_type::{BuiltinInt, BuiltinUint}, + resolver::HasResolver, +}; use hir_expand::mod_path::ModPath; use super::*; @@ -136,7 +139,10 @@ impl Evaluator<'_> { not_supported!("wrong generic arg kind for clone"); }; // Clone has special impls for tuples and function pointers - if matches!(self_ty.kind(Interner), TyKind::Function(_) | TyKind::Tuple(..)) { + if matches!( + self_ty.kind(Interner), + TyKind::Function(_) | TyKind::Tuple(..) | TyKind::Closure(..) + ) { self.exec_clone(def, args, self_ty.clone(), locals, destination, span)?; return Ok(true); } @@ -167,32 +173,26 @@ impl Evaluator<'_> { return destination .write_from_interval(self, Interval { addr, size: destination.size }); } + TyKind::Closure(id, subst) => { + let [arg] = args else { + not_supported!("wrong arg count for clone"); + }; + let addr = Address::from_bytes(arg.get(self)?)?; + let (closure_owner, _) = self.db.lookup_intern_closure((*id).into()); + let infer = self.db.infer(closure_owner); + let (captures, _) = infer.closure_info(id); + let layout = self.layout(&self_ty)?; + let ty_iter = captures.iter().map(|c| c.ty(subst)); + self.exec_clone_for_fields(ty_iter, layout, addr, def, locals, destination, span)?; + } TyKind::Tuple(_, subst) => { let [arg] = args else { not_supported!("wrong arg count for clone"); }; let addr = Address::from_bytes(arg.get(self)?)?; let layout = self.layout(&self_ty)?; - for (i, ty) in subst.iter(Interner).enumerate() { - let ty = ty.assert_ty_ref(Interner); - let size = self.layout(ty)?.size.bytes_usize(); - let tmp = self.heap_allocate(self.ptr_size(), self.ptr_size())?; - let arg = IntervalAndTy { - interval: Interval { addr: tmp, size: self.ptr_size() }, - ty: TyKind::Ref(Mutability::Not, static_lifetime(), ty.clone()) - .intern(Interner), - }; - let offset = layout.fields.offset(i).bytes_usize(); - self.write_memory(tmp, &addr.offset(offset).to_bytes())?; - self.exec_clone( - def, - &[arg], - ty.clone(), - locals, - destination.slice(offset..offset + size), - span, - )?; - } + let ty_iter = subst.iter(Interner).map(|ga| ga.assert_ty_ref(Interner).clone()); + self.exec_clone_for_fields(ty_iter, layout, addr, def, locals, destination, span)?; } _ => { self.exec_fn_with_args( @@ -209,6 +209,37 @@ impl Evaluator<'_> { Ok(()) } + fn exec_clone_for_fields( + &mut self, + ty_iter: impl Iterator<Item = Ty>, + layout: Arc<Layout>, + addr: Address, + def: FunctionId, + locals: &Locals, + destination: Interval, + span: MirSpan, + ) -> Result<()> { + for (i, ty) in ty_iter.enumerate() { + let size = self.layout(&ty)?.size.bytes_usize(); + let tmp = self.heap_allocate(self.ptr_size(), self.ptr_size())?; + let arg = IntervalAndTy { + interval: Interval { addr: tmp, size: self.ptr_size() }, + ty: TyKind::Ref(Mutability::Not, static_lifetime(), ty.clone()).intern(Interner), + }; + let offset = layout.fields.offset(i).bytes_usize(); + self.write_memory(tmp, &addr.offset(offset).to_bytes())?; + self.exec_clone( + def, + &[arg], + ty, + locals, + destination.slice(offset..offset + size), + span, + )?; + } + Ok(()) + } + fn exec_alloc_fn( &mut self, alloc_fn: &str, @@ -272,21 +303,36 @@ impl Evaluator<'_> { BeginPanic => Err(MirEvalError::Panic("<unknown-panic-payload>".to_string())), PanicFmt => { let message = (|| { - let resolver = self.db.crate_def_map(self.crate_id).crate_root().resolver(self.db.upcast()); + let resolver = self + .db + .crate_def_map(self.crate_id) + .crate_root() + .resolver(self.db.upcast()); let Some(format_fn) = resolver.resolve_path_in_value_ns_fully( self.db.upcast(), - &hir_def::path::Path::from_known_path_with_no_generic(ModPath::from_segments( - hir_expand::mod_path::PathKind::Abs, - [name![std], name![fmt], name![format]].into_iter(), - )), + &hir_def::path::Path::from_known_path_with_no_generic( + ModPath::from_segments( + hir_expand::mod_path::PathKind::Abs, + [name![std], name![fmt], name![format]].into_iter(), + ), + ), ) else { not_supported!("std::fmt::format not found"); }; - let hir_def::resolver::ValueNs::FunctionId(format_fn) = format_fn else { not_supported!("std::fmt::format is not a function") }; - let message_string = self.interpret_mir(self.db.mir_body(format_fn.into()).map_err(|e| MirEvalError::MirLowerError(format_fn, e))?, args.map(|x| IntervalOrOwned::Owned(x.clone())))?; - let addr = Address::from_bytes(&message_string[self.ptr_size()..2 * self.ptr_size()])?; + let hir_def::resolver::ValueNs::FunctionId(format_fn) = format_fn else { + not_supported!("std::fmt::format is not a function") + }; + let message_string = self.interpret_mir( + self.db + .mir_body(format_fn.into()) + .map_err(|e| MirEvalError::MirLowerError(format_fn, e))?, + args.map(|x| IntervalOrOwned::Owned(x.clone())), + )?; + let addr = + Address::from_bytes(&message_string[self.ptr_size()..2 * self.ptr_size()])?; let size = from_bytes!(usize, message_string[2 * self.ptr_size()..]); - Ok(std::string::String::from_utf8_lossy(self.read_memory(addr, size)?).into_owned()) + Ok(std::string::String::from_utf8_lossy(self.read_memory(addr, size)?) + .into_owned()) })() .unwrap_or_else(|e| format!("Failed to render panic format args: {e:?}")); Err(MirEvalError::Panic(message)) @@ -455,9 +501,7 @@ impl Evaluator<'_> { } "syscall" => { let Some((id, rest)) = args.split_first() else { - return Err(MirEvalError::TypeError( - "syscall arg1 is not provided", - )); + return Err(MirEvalError::TypeError("syscall arg1 is not provided")); }; let id = from_bytes!(i64, id.get(self)?); self.exec_syscall(id, rest, destination, locals, span) @@ -473,6 +517,38 @@ impl Evaluator<'_> { self.write_memory_using_ref(destination.addr, destination.size)?.fill(0); Ok(()) } + "getenv" => { + let [name] = args else { + return Err(MirEvalError::TypeError("libc::write args are not provided")); + }; + let mut name_buf = vec![]; + let name = { + let mut index = Address::from_bytes(name.get(self)?)?; + loop { + let byte = self.read_memory(index, 1)?[0]; + index = index.offset(1); + if byte == 0 { + break; + } + name_buf.push(byte); + } + String::from_utf8_lossy(&name_buf) + }; + let value = self.db.crate_graph()[self.crate_id].env.get(&name); + match value { + None => { + // Write null as fail + self.write_memory_using_ref(destination.addr, destination.size)?.fill(0); + } + Some(mut value) => { + value.push('\0'); + let addr = self.heap_allocate(value.len(), 1)?; + self.write_memory(addr, value.as_bytes())?; + self.write_memory(destination.addr, &addr.to_bytes())?; + } + } + Ok(()) + } _ => not_supported!("unknown external function {as_str}"), } } @@ -650,7 +726,8 @@ impl Evaluator<'_> { } match name { "size_of" => { - let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) + let Some(ty) = + generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) else { return Err(MirEvalError::TypeError("size_of generic arg is not provided")); }; @@ -658,14 +735,17 @@ impl Evaluator<'_> { destination.write_from_bytes(self, &size.to_le_bytes()[0..destination.size]) } "min_align_of" | "pref_align_of" => { - let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) else { + let Some(ty) = + generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) + else { return Err(MirEvalError::TypeError("align_of generic arg is not provided")); }; let align = self.layout(ty)?.align.abi.bytes(); destination.write_from_bytes(self, &align.to_le_bytes()[0..destination.size]) } "size_of_val" => { - let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) + let Some(ty) = + generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) else { return Err(MirEvalError::TypeError("size_of_val generic arg is not provided")); }; @@ -681,8 +761,12 @@ impl Evaluator<'_> { } } "min_align_of_val" => { - let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) else { - return Err(MirEvalError::TypeError("min_align_of_val generic arg is not provided")); + let Some(ty) = + generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) + else { + return Err(MirEvalError::TypeError( + "min_align_of_val generic arg is not provided", + )); }; let [arg] = args else { return Err(MirEvalError::TypeError("min_align_of_val args are not provided")); @@ -696,7 +780,8 @@ impl Evaluator<'_> { } } "type_name" => { - let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) + let Some(ty) = + generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) else { return Err(MirEvalError::TypeError("type_name generic arg is not provided")); }; @@ -719,7 +804,8 @@ impl Evaluator<'_> { .write_from_bytes(self, &len.to_le_bytes()) } "needs_drop" => { - let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) + let Some(ty) = + generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) else { return Err(MirEvalError::TypeError("size_of generic arg is not provided")); }; @@ -771,9 +857,12 @@ impl Evaluator<'_> { let lhs = i128::from_le_bytes(pad16(lhs.get(self)?, false)); let rhs = i128::from_le_bytes(pad16(rhs.get(self)?, false)); let ans = lhs.wrapping_sub(rhs); - let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) + let Some(ty) = + generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) else { - return Err(MirEvalError::TypeError("ptr_offset_from generic arg is not provided")); + return Err(MirEvalError::TypeError( + "ptr_offset_from generic arg is not provided", + )); }; let size = self.size_of_sized(ty, locals, "ptr_offset_from arg")? as i128; let ans = ans / size; @@ -880,7 +969,8 @@ impl Evaluator<'_> { "copy_nonoverlapping args are not provided", )); }; - let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) + let Some(ty) = + generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) else { return Err(MirEvalError::TypeError( "copy_nonoverlapping generic arg is not provided", @@ -899,9 +989,45 @@ impl Evaluator<'_> { let [ptr, offset] = args else { return Err(MirEvalError::TypeError("offset args are not provided")); }; - let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) - else { - return Err(MirEvalError::TypeError("offset generic arg is not provided")); + let ty = if name == "offset" { + let Some(ty0) = + generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) + else { + return Err(MirEvalError::TypeError("offset generic arg is not provided")); + }; + let Some(ty1) = + generic_args.as_slice(Interner).get(1).and_then(|it| it.ty(Interner)) + else { + return Err(MirEvalError::TypeError("offset generic arg is not provided")); + }; + if !matches!( + ty1.as_builtin(), + Some( + BuiltinType::Int(BuiltinInt::Isize) + | BuiltinType::Uint(BuiltinUint::Usize) + ) + ) { + return Err(MirEvalError::TypeError( + "offset generic arg is not usize or isize", + )); + } + match ty0.as_raw_ptr() { + Some((ty, _)) => ty, + None => { + return Err(MirEvalError::TypeError( + "offset generic arg is not a raw pointer", + )); + } + } + } else { + let Some(ty) = + generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) + else { + return Err(MirEvalError::TypeError( + "arith_offset generic arg is not provided", + )); + }; + ty }; let ptr = u128::from_le_bytes(pad16(ptr.get(self)?, false)); let offset = u128::from_le_bytes(pad16(offset.get(self)?, false)); @@ -1019,7 +1145,8 @@ impl Evaluator<'_> { let [arg] = args else { return Err(MirEvalError::TypeError("discriminant_value arg is not provided")); }; - let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) + let Some(ty) = + generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) else { return Err(MirEvalError::TypeError( "discriminant_value generic arg is not provided", @@ -1073,17 +1200,32 @@ impl Evaluator<'_> { let addr = Address::from_bytes(arg.interval.get(self)?)?; destination.write_from_interval(self, Interval { addr, size: destination.size }) } + "write_via_move" => { + let [ptr, val] = args else { + return Err(MirEvalError::TypeError("write_via_move args are not provided")); + }; + let dst = Address::from_bytes(ptr.get(self)?)?; + let Some(ty) = + generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) + else { + return Err(MirEvalError::TypeError( + "write_via_copy generic arg is not provided", + )); + }; + let size = self.size_of_sized(ty, locals, "write_via_move ptr type")?; + Interval { addr: dst, size }.write_from_interval(self, val.interval)?; + Ok(()) + } "write_bytes" => { let [dst, val, count] = args else { return Err(MirEvalError::TypeError("write_bytes args are not provided")); }; let count = from_bytes!(usize, count.get(self)?); let val = from_bytes!(u8, val.get(self)?); - let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) + let Some(ty) = + generic_args.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) else { - return Err(MirEvalError::TypeError( - "write_bytes generic arg is not provided", - )); + return Err(MirEvalError::TypeError("write_bytes generic arg is not provided")); }; let dst = Address::from_bytes(dst.get(self)?)?; let size = self.size_of_sized(ty, locals, "copy_nonoverlapping ptr type")?; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim/simd.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim/simd.rs index ec7463104..519006624 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim/simd.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim/simd.rs @@ -45,7 +45,9 @@ impl Evaluator<'_> { }; match try_const_usize(self.db, len) { Some(len) => { - let Some(ty) = subst.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) else { + let Some(ty) = + subst.as_slice(Interner).get(0).and_then(|it| it.ty(Interner)) + else { return Err(MirEvalError::TypeError("simd type with no ty param")); }; Ok((len as usize, ty.clone())) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/tests.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/tests.rs index 46165cf3d..ff30dc6da 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/tests.rs @@ -730,6 +730,48 @@ fn main() { } #[test] +fn posix_getenv() { + check_pass( + r#" +//- /main.rs env:foo=bar + +type c_char = u8; + +extern "C" { + pub fn getenv(s: *const c_char) -> *mut c_char; +} + +fn should_not_reach() { + _ // FIXME: replace this function with panic when that works +} + +fn main() { + let result = getenv(b"foo\0" as *const _); + if *result != b'b' { + should_not_reach(); + } + let result = (result as usize + 1) as *const c_char; + if *result != b'a' { + should_not_reach(); + } + let result = (result as usize + 1) as *const c_char; + if *result != b'r' { + should_not_reach(); + } + let result = (result as usize + 1) as *const c_char; + if *result != 0 { + should_not_reach(); + } + let result = getenv(b"not found\0" as *const _); + if result as usize != 0 { + should_not_reach(); + } +} +"#, + ); +} + +#[test] fn posix_tls() { check_pass( r#" diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs index 718df8331..dd2dba717 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs @@ -15,7 +15,7 @@ use hir_def::{ path::Path, resolver::{resolver_for_expr, HasResolver, ResolveValueResult, ValueNs}, AdtId, DefWithBodyId, EnumVariantId, GeneralConstId, HasModule, ItemContainerId, LocalFieldId, - TraitId, TypeOrConstParamId, + Lookup, TraitId, TypeOrConstParamId, }; use hir_expand::name::Name; use la_arena::ArenaMap; @@ -71,7 +71,7 @@ struct MirLowerCtx<'a> { #[derive(Debug, Clone, PartialEq, Eq)] pub enum MirLowerError { - ConstEvalError(String, Box<ConstEvalError>), + ConstEvalError(Box<str>, Box<ConstEvalError>), LayoutError(LayoutError), IncompleteExpr, IncompletePattern, @@ -84,7 +84,7 @@ pub enum MirLowerError { UnsizedTemporary(Ty), MissingFunctionDefinition(DefWithBodyId, ExprId), TypeMismatch(TypeMismatch), - /// This should be never happen. Type mismatch should catch everything. + /// This should never happen. Type mismatch should catch everything. TypeError(&'static str), NotSupported(String), ContinueWithoutLoop, @@ -244,6 +244,7 @@ impl<'ctx> MirLowerCtx<'ctx> { let locals = Arena::new(); let binding_locals: ArenaMap<BindingId, LocalId> = ArenaMap::new(); let mir = MirBody { + projection_store: ProjectionStore::default(), basic_blocks, locals, start_block, @@ -370,9 +371,15 @@ impl<'ctx> MirLowerCtx<'ctx> { mut current: BasicBlockId, ) -> Result<Option<BasicBlockId>> { match &self.body.exprs[expr_id] { + Expr::OffsetOf(_) => { + not_supported!("builtin#offset_of") + } + Expr::InlineAsm(_) => { + not_supported!("builtin#asm") + } Expr::Missing => { if let DefWithBodyId::FunctionId(f) = self.owner { - let assoc = self.db.lookup_intern_function(f); + let assoc = f.lookup(self.db.upcast()); if let ItemContainerId::TraitId(t) = assoc.container { let name = &self.db.function_data(f).name; return Err(MirLowerError::TraitFunctionDefinition(t, name.clone())); @@ -803,36 +810,34 @@ impl<'ctx> MirLowerCtx<'ctx> { current = c; operands[u32::from(field_id.into_raw()) as usize] = Some(op); } - self.push_assignment( - current, - place, - Rvalue::Aggregate( - AggregateKind::Adt(variant_id, subst), - match spread_place { - Some(sp) => operands - .into_iter() - .enumerate() - .map(|(i, it)| match it { - Some(it) => it, - None => { - let p = - sp.project(ProjectionElem::Field(FieldId { - parent: variant_id, - local_id: LocalFieldId::from_raw( - RawIdx::from(i as u32), - ), - })); - Operand::Copy(p) - } - }) - .collect(), - None => operands.into_iter().collect::<Option<_>>().ok_or( - MirLowerError::TypeError("missing field in record literal"), - )?, - }, - ), - expr_id.into(), + let rvalue = Rvalue::Aggregate( + AggregateKind::Adt(variant_id, subst), + match spread_place { + Some(sp) => operands + .into_iter() + .enumerate() + .map(|(i, it)| match it { + Some(it) => it, + None => { + let p = sp.project( + ProjectionElem::Field(FieldId { + parent: variant_id, + local_id: LocalFieldId::from_raw(RawIdx::from( + i as u32, + )), + }), + &mut self.result.projection_store, + ); + Operand::Copy(p) + } + }) + .collect(), + None => operands.into_iter().collect::<Option<_>>().ok_or( + MirLowerError::TypeError("missing field in record literal"), + )?, + }, ); + self.push_assignment(current, place, rvalue, expr_id.into()); Ok(Some(current)) } VariantId::UnionId(union_id) => { @@ -841,10 +846,10 @@ impl<'ctx> MirLowerCtx<'ctx> { }; let local_id = variant_data.field(name).ok_or(MirLowerError::UnresolvedField)?; - let place = place.project(PlaceElem::Field(FieldId { - parent: union_id.into(), - local_id, - })); + let place = place.project( + PlaceElem::Field(FieldId { parent: union_id.into(), local_id }), + &mut self.result.projection_store, + ); self.lower_expr_to_place(*expr, place, current) } } @@ -898,7 +903,7 @@ impl<'ctx> MirLowerCtx<'ctx> { else { return Ok(None); }; - let p = place.project(ProjectionElem::Deref); + let p = place.project(ProjectionElem::Deref, &mut self.result.projection_store); self.push_assignment(current, p, operand.into(), expr_id.into()); Ok(Some(current)) } @@ -1120,27 +1125,31 @@ impl<'ctx> MirLowerCtx<'ctx> { for capture in captures.iter() { let p = Place { local: self.binding_local(capture.place.local)?, - projection: capture - .place - .projections - .clone() - .into_iter() - .map(|it| match it { - ProjectionElem::Deref => ProjectionElem::Deref, - ProjectionElem::Field(it) => ProjectionElem::Field(it), - ProjectionElem::TupleOrClosureField(it) => { - ProjectionElem::TupleOrClosureField(it) - } - ProjectionElem::ConstantIndex { offset, from_end } => { - ProjectionElem::ConstantIndex { offset, from_end } - } - ProjectionElem::Subslice { from, to } => { - ProjectionElem::Subslice { from, to } - } - ProjectionElem::OpaqueCast(it) => ProjectionElem::OpaqueCast(it), - ProjectionElem::Index(it) => match it {}, - }) - .collect(), + projection: self.result.projection_store.intern( + capture + .place + .projections + .clone() + .into_iter() + .map(|it| match it { + ProjectionElem::Deref => ProjectionElem::Deref, + ProjectionElem::Field(it) => ProjectionElem::Field(it), + ProjectionElem::TupleOrClosureField(it) => { + ProjectionElem::TupleOrClosureField(it) + } + ProjectionElem::ConstantIndex { offset, from_end } => { + ProjectionElem::ConstantIndex { offset, from_end } + } + ProjectionElem::Subslice { from, to } => { + ProjectionElem::Subslice { from, to } + } + ProjectionElem::OpaqueCast(it) => { + ProjectionElem::OpaqueCast(it) + } + ProjectionElem::Index(it) => match it {}, + }) + .collect(), + ), }; match &capture.kind { CaptureKind::ByRef(bk) => { @@ -1201,7 +1210,8 @@ impl<'ctx> MirLowerCtx<'ctx> { let Some(values) = elements .iter() .map(|it| { - let Some((o, c)) = self.lower_expr_to_some_operand(*it, current)? else { + let Some((o, c)) = self.lower_expr_to_some_operand(*it, current)? + else { return Ok(None); }; current = c; @@ -1244,6 +1254,40 @@ impl<'ctx> MirLowerCtx<'ctx> { } } + fn lower_destructing_assignment( + &mut self, + mut current: BasicBlockId, + lhs: ExprId, + rhs: Place, + span: MirSpan, + ) -> Result<Option<BasicBlockId>> { + match &self.body.exprs[lhs] { + Expr::Tuple { exprs, is_assignee_expr: _ } => { + for (i, expr) in exprs.iter().enumerate() { + let rhs = rhs.project( + ProjectionElem::TupleOrClosureField(i), + &mut self.result.projection_store, + ); + let Some(c) = self.lower_destructing_assignment(current, *expr, rhs, span)? + else { + return Ok(None); + }; + current = c; + } + Ok(Some(current)) + } + Expr::Underscore => Ok(Some(current)), + _ => { + let Some((lhs_place, current)) = self.lower_expr_as_place(current, lhs, false)? + else { + return Ok(None); + }; + self.push_assignment(current, lhs_place, Operand::Copy(rhs).into(), span); + Ok(Some(current)) + } + } + } + fn lower_assignment( &mut self, current: BasicBlockId, @@ -1251,17 +1295,22 @@ impl<'ctx> MirLowerCtx<'ctx> { rhs: ExprId, span: MirSpan, ) -> Result<Option<BasicBlockId>> { - let Some((rhs_op, current)) = - self.lower_expr_to_some_operand(rhs, current)? - else { + let Some((rhs_op, current)) = self.lower_expr_to_some_operand(rhs, current)? else { return Ok(None); }; if matches!(&self.body.exprs[lhs], Expr::Underscore) { return Ok(Some(current)); } - let Some((lhs_place, current)) = - self.lower_expr_as_place(current, lhs, false)? - else { + if matches!( + &self.body.exprs[lhs], + Expr::Tuple { .. } | Expr::RecordLit { .. } | Expr::Call { .. } + ) { + let temp = self.temp(self.expr_ty_after_adjustments(rhs), current, rhs.into())?; + let temp = Place::from(temp); + self.push_assignment(current, temp.clone(), rhs_op.into(), span); + return self.lower_destructing_assignment(current, lhs, temp, span); + } + let Some((lhs_place, current)) = self.lower_expr_as_place(current, lhs, false)? else { return Ok(None); }; self.push_assignment(current, lhs_place, rhs_op.into(), span); @@ -1276,17 +1325,21 @@ impl<'ctx> MirLowerCtx<'ctx> { placeholder_subst } - fn push_field_projection(&self, place: &mut Place, expr_id: ExprId) -> Result<()> { + fn push_field_projection(&mut self, place: &mut Place, expr_id: ExprId) -> Result<()> { if let Expr::Field { expr, name } = &self.body[expr_id] { if let TyKind::Tuple(..) = self.expr_ty_after_adjustments(*expr).kind(Interner) { let index = name .as_tuple_index() .ok_or(MirLowerError::TypeError("named field on tuple"))?; - *place = place.project(ProjectionElem::TupleOrClosureField(index)) + *place = place.project( + ProjectionElem::TupleOrClosureField(index), + &mut self.result.projection_store, + ) } else { let field = self.infer.field_resolution(expr_id).ok_or(MirLowerError::UnresolvedField)?; - *place = place.project(ProjectionElem::Field(field)); + *place = + place.project(ProjectionElem::Field(field), &mut self.result.projection_store); } } else { not_supported!("") @@ -1308,14 +1361,14 @@ impl<'ctx> MirLowerCtx<'ctx> { .resolve_path_in_value_ns(self.db.upcast(), c) .ok_or_else(unresolved_name)?; match pr { - ResolveValueResult::ValueNs(v) => { + ResolveValueResult::ValueNs(v, _) => { if let ValueNs::ConstId(c) = v { self.lower_const_to_operand(Substitution::empty(Interner), c.into(), ty) } else { not_supported!("bad path in range pattern"); } } - ResolveValueResult::Partial(_, _) => { + ResolveValueResult::Partial(_, _, _) => { not_supported!("associated constants in range pattern") } } @@ -1403,7 +1456,7 @@ impl<'ctx> MirLowerCtx<'ctx> { let name = const_id.name(self.db.upcast()); self.db .const_eval(const_id.into(), subst, None) - .map_err(|e| MirLowerError::ConstEvalError(name, Box::new(e)))? + .map_err(|e| MirLowerError::ConstEvalError(name.into(), Box::new(e)))? }; Ok(Operand::Constant(c)) } @@ -1800,7 +1853,7 @@ impl<'ctx> MirLowerCtx<'ctx> { data.name.display(self.db.upcast()), data.variants[variant.local_id].name.display(self.db.upcast()) ); - Err(MirLowerError::ConstEvalError(name, Box::new(e))) + Err(MirLowerError::ConstEvalError(name.into(), Box::new(e))) } } } @@ -1948,13 +2001,14 @@ pub fn mir_body_for_closure_query( FnTrait::FnOnce => vec![], FnTrait::FnMut | FnTrait::Fn => vec![ProjectionElem::Deref], }; - ctx.result.walk_places(|p| { + ctx.result.walk_places(|p, store| { if let Some(it) = upvar_map.get(&p.local) { let r = it.iter().find(|it| { - if p.projection.len() < it.0.place.projections.len() { + if p.projection.lookup(&store).len() < it.0.place.projections.len() { return false; } - for (it, y) in p.projection.iter().zip(it.0.place.projections.iter()) { + for (it, y) in p.projection.lookup(&store).iter().zip(it.0.place.projections.iter()) + { match (it, y) { (ProjectionElem::Deref, ProjectionElem::Deref) => (), (ProjectionElem::Field(it), ProjectionElem::Field(y)) if it == y => (), @@ -1972,13 +2026,18 @@ pub fn mir_body_for_closure_query( p.local = closure_local; let mut next_projs = closure_projection.clone(); next_projs.push(PlaceElem::TupleOrClosureField(it.1)); - let prev_projs = mem::take(&mut p.projection); + let prev_projs = p.projection; if it.0.kind != CaptureKind::ByValue { next_projs.push(ProjectionElem::Deref); } - next_projs - .extend(prev_projs.iter().cloned().skip(it.0.place.projections.len())); - p.projection = next_projs.into(); + next_projs.extend( + prev_projs + .lookup(&store) + .iter() + .cloned() + .skip(it.0.place.projections.len()), + ); + p.projection = store.intern(next_projs.into()); } None => err = Some(p.clone()), } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/as_place.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/as_place.rs index 213f151ab..8c078eb4a 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/as_place.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/as_place.rs @@ -70,7 +70,7 @@ impl MirLowerCtx<'_> { else { return Ok(None); }; - it.0 = it.0.project(ProjectionElem::Deref); + it.0 = it.0.project(ProjectionElem::Deref, &mut self.result.projection_store); Ok(Some(it)) } Adjust::Deref(Some(od)) => { @@ -152,7 +152,10 @@ impl MirLowerCtx<'_> { Operand::Static(s).into(), expr_id.into(), ); - Ok(Some((temp.project(ProjectionElem::Deref), current))) + Ok(Some(( + temp.project(ProjectionElem::Deref, &mut self.result.projection_store), + current, + ))) } _ => try_rvalue(self), } @@ -203,7 +206,7 @@ impl MirLowerCtx<'_> { else { return Ok(None); }; - r = r.project(ProjectionElem::Deref); + r = r.project(ProjectionElem::Deref, &mut self.result.projection_store); Ok(Some((r, current))) } _ => try_rvalue(self), @@ -267,7 +270,8 @@ impl MirLowerCtx<'_> { else { return Ok(None); }; - p_base = p_base.project(ProjectionElem::Index(l_index)); + p_base = p_base + .project(ProjectionElem::Index(l_index), &mut self.result.projection_store); Ok(Some((p_base, current))) } _ => try_rvalue(self), @@ -308,7 +312,7 @@ impl MirLowerCtx<'_> { else { return Ok(None); }; - result = result.project(ProjectionElem::Deref); + result = result.project(ProjectionElem::Deref, &mut self.result.projection_store); Ok(Some((result, current))) } @@ -363,7 +367,7 @@ impl MirLowerCtx<'_> { else { return Ok(None); }; - result = result.project(ProjectionElem::Deref); + result = result.project(ProjectionElem::Deref, &mut self.result.projection_store); Ok(Some((result, current))) } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/pattern_matching.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/pattern_matching.rs index 3354cbd76..270f75ad9 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/pattern_matching.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/pattern_matching.rs @@ -81,13 +81,16 @@ impl MirLowerCtx<'_> { mode: MatchingMode, ) -> Result<(BasicBlockId, Option<BasicBlockId>)> { let cnt = self.infer.pat_adjustments.get(&pattern).map(|x| x.len()).unwrap_or_default(); - cond_place.projection = cond_place - .projection - .iter() - .cloned() - .chain((0..cnt).map(|_| ProjectionElem::Deref)) - .collect::<Vec<_>>() - .into(); + cond_place.projection = self.result.projection_store.intern( + cond_place + .projection + .lookup(&self.result.projection_store) + .iter() + .cloned() + .chain((0..cnt).map(|_| ProjectionElem::Deref)) + .collect::<Vec<_>>() + .into(), + ); Ok(match &self.body.pats[pattern] { Pat::Missing => return Err(MirLowerError::IncompletePattern), Pat::Wild => (current, current_else), @@ -262,20 +265,23 @@ impl MirLowerCtx<'_> { } } for (i, &pat) in prefix.iter().enumerate() { - let next_place = (&mut cond_place).project(ProjectionElem::ConstantIndex { - offset: i as u64, - from_end: false, - }); + let next_place = (&mut cond_place).project( + ProjectionElem::ConstantIndex { offset: i as u64, from_end: false }, + &mut self.result.projection_store, + ); (current, current_else) = self.pattern_match_inner(current, current_else, next_place, pat, mode)?; } if let Some(slice) = slice { if mode == MatchingMode::Bind { if let Pat::Bind { id, subpat: _ } = self.body[*slice] { - let next_place = (&mut cond_place).project(ProjectionElem::Subslice { - from: prefix.len() as u64, - to: suffix.len() as u64, - }); + let next_place = (&mut cond_place).project( + ProjectionElem::Subslice { + from: prefix.len() as u64, + to: suffix.len() as u64, + }, + &mut self.result.projection_store, + ); (current, current_else) = self.pattern_match_binding( id, next_place, @@ -287,10 +293,10 @@ impl MirLowerCtx<'_> { } } for (i, &pat) in suffix.iter().enumerate() { - let next_place = (&mut cond_place).project(ProjectionElem::ConstantIndex { - offset: i as u64, - from_end: true, - }); + let next_place = (&mut cond_place).project( + ProjectionElem::ConstantIndex { offset: i as u64, from_end: true }, + &mut self.result.projection_store, + ); (current, current_else) = self.pattern_match_inner(current, current_else, next_place, pat, mode)?; } @@ -323,7 +329,7 @@ impl MirLowerCtx<'_> { break 'b (c, x.1); } } - if let ResolveValueResult::ValueNs(v) = pr { + if let ResolveValueResult::ValueNs(v, _) = pr { if let ValueNs::ConstId(c) = v { break 'b (c, Substitution::empty(Interner)); } @@ -412,13 +418,11 @@ impl MirLowerCtx<'_> { mode, )? } - Pat::Ref { pat, mutability: _ } => self.pattern_match_inner( - current, - current_else, - cond_place.project(ProjectionElem::Deref), - *pat, - mode, - )?, + Pat::Ref { pat, mutability: _ } => { + let cond_place = + cond_place.project(ProjectionElem::Deref, &mut self.result.projection_store); + self.pattern_match_inner(current, current_else, cond_place, *pat, mode)? + } Pat::Box { .. } => not_supported!("box pattern"), Pat::ConstBlock(_) => not_supported!("const block pattern"), }) @@ -594,7 +598,7 @@ impl MirLowerCtx<'_> { mode: MatchingMode, ) -> Result<(BasicBlockId, Option<BasicBlockId>)> { for (proj, arg) in args { - let cond_place = cond_place.project(proj); + let cond_place = cond_place.project(proj, &mut self.result.projection_store); (current, current_else) = self.pattern_match_inner(current, current_else, cond_place, arg, mode)?; } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/monomorphization.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/monomorphization.rs index c565228d9..df16d0d82 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/monomorphization.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/monomorphization.rs @@ -265,7 +265,7 @@ impl Filler<'_> { self.fill_operand(discr)?; } TerminatorKind::Goto { .. } - | TerminatorKind::Resume + | TerminatorKind::UnwindResume | TerminatorKind::Abort | TerminatorKind::Return | TerminatorKind::Unreachable diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/pretty.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/pretty.rs index 781ffaeca..0108859ff 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/pretty.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/pretty.rs @@ -329,7 +329,7 @@ impl<'a> MirPrettyCtx<'a> { } } } - f(self, p.local, &p.projection); + f(self, p.local, &p.projection.lookup(&self.body.projection_store)); } fn operand(&mut self, r: &Operand) { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs index 2ad7946c8..8140c4107 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs @@ -3,55 +3,6 @@ use expect_test::expect; use super::{check, check_infer, check_no_mismatches, check_types}; #[test] -fn infer_box() { - check_types( - r#" -//- /main.rs crate:main deps:std -fn test() { - let x = box 1; - let t = (x, box x, box &1, box [1]); - t; -} //^ (Box<i32>, Box<Box<i32>>, Box<&i32>, Box<[i32; 1]>) - -//- /std.rs crate:std -#[prelude_import] use prelude::*; -mod prelude {} - -mod boxed { - #[lang = "owned_box"] - pub struct Box<T: ?Sized> { - inner: *mut T, - } -} -"#, - ); -} - -#[test] -fn infer_box_with_allocator() { - check_types( - r#" -//- /main.rs crate:main deps:std -fn test() { - let x = box 1; - let t = (x, box x, box &1, box [1]); - t; -} //^ (Box<i32, {unknown}>, Box<Box<i32, {unknown}>, {unknown}>, Box<&i32, {unknown}>, Box<[i32; 1], {unknown}>) - -//- /std.rs crate:std -#[prelude_import] use prelude::*; -mod boxed { - #[lang = "owned_box"] - pub struct Box<T: ?Sized, A: Allocator> { - inner: *mut T, - allocator: A, - } -} -"#, - ); -} - -#[test] fn infer_adt_self() { check_types( r#" @@ -2763,8 +2714,8 @@ impl<T> [T] { } fn test() { - let vec = <[_]>::into_vec(box [1i32]); - let v: Vec<Box<dyn B>> = <[_]> :: into_vec(box [box Astruct]); + let vec = <[_]>::into_vec(#[rustc_box] Box::new([1i32])); + let v: Vec<Box<dyn B>> = <[_]> :: into_vec(#[rustc_box] Box::new([#[rustc_box] Box::new(Astruct)])); } trait B{} @@ -2774,20 +2725,20 @@ impl B for Astruct {} expect![[r#" 604..608 'self': Box<[T], A> 637..669 '{ ... }': Vec<T, A> - 683..796 '{ ...t]); }': () + 683..853 '{ ...])); }': () 693..696 'vec': Vec<i32, Global> 699..714 '<[_]>::into_vec': fn into_vec<i32, Global>(Box<[i32], Global>) -> Vec<i32, Global> - 699..726 '<[_]>:...1i32])': Vec<i32, Global> - 715..725 'box [1i32]': Box<[i32; 1], Global> - 719..725 '[1i32]': [i32; 1] - 720..724 '1i32': i32 - 736..737 'v': Vec<Box<dyn B, Global>, Global> - 757..774 '<[_]> ...to_vec': fn into_vec<Box<dyn B, Global>, Global>(Box<[Box<dyn B, Global>], Global>) -> Vec<Box<dyn B, Global>, Global> - 757..793 '<[_]> ...ruct])': Vec<Box<dyn B, Global>, Global> - 775..792 'box [b...truct]': Box<[Box<dyn B, Global>; 1], Global> - 779..792 '[box Astruct]': [Box<dyn B, Global>; 1] - 780..791 'box Astruct': Box<Astruct, Global> - 784..791 'Astruct': Astruct + 699..745 '<[_]>:...i32]))': Vec<i32, Global> + 715..744 '#[rust...1i32])': Box<[i32; 1], Global> + 737..743 '[1i32]': [i32; 1] + 738..742 '1i32': i32 + 755..756 'v': Vec<Box<dyn B, Global>, Global> + 776..793 '<[_]> ...to_vec': fn into_vec<Box<dyn B, Global>, Global>(Box<[Box<dyn B, Global>], Global>) -> Vec<Box<dyn B, Global>, Global> + 776..850 '<[_]> ...ct)]))': Vec<Box<dyn B, Global>, Global> + 794..849 '#[rust...uct)])': Box<[Box<dyn B, Global>; 1], Global> + 816..848 '[#[rus...ruct)]': [Box<dyn B, Global>; 1] + 817..847 '#[rust...truct)': Box<Astruct, Global> + 839..846 'Astruct': Astruct "#]], ) } @@ -3649,3 +3600,30 @@ fn main() { "#, ); } + +#[test] +fn offset_of() { + check_types( + r#" +fn main() { + builtin#offset_of((,), 0); + // ^^^^^^^^^^^^^^^^^^^^^^^^^ usize +} +"#, + ); +} + +#[test] +fn builtin_format_args() { + check( + r#" +//- minicore: fmt +fn main() { + let are = "are"; + let count = 10; + builtin#format_args("hello {count:02} {} friends, we {are:?} {0}{last}", "fancy", last = "!"); + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type: Arguments<'_> +} +"#, + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs index 542df8b34..d36b885ec 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs @@ -162,16 +162,16 @@ unsafe impl Allocator for Global {} #[lang = "owned_box"] #[fundamental] -pub struct Box<T: ?Sized, A: Allocator = Global>; +pub struct Box<T: ?Sized, A: Allocator = Global>(T); impl<T: ?Sized + Unsize<U>, U: ?Sized, A: Allocator> CoerceUnsized<Box<U, A>> for Box<T, A> {} fn send() -> Box<dyn Future<Output = ()> + Send + 'static>{ - box async move {} + Box(async move {}) } fn not_send() -> Box<dyn Future<Output = ()> + 'static> { - box async move {} + Box(async move {}) } "#, ); @@ -3057,7 +3057,7 @@ impl<T: ?Sized> core::ops::Deref for Box<T> { fn foo() { let s = None; - let f: Box<dyn FnOnce(&Option<i32>)> = box (|ps| {}); + let f: Box<dyn FnOnce(&Option<i32>)> = Box { inner: &mut (|ps| {}) }; f(&s); }"#, expect![[r#" @@ -3068,19 +3068,19 @@ fn foo() { 186..197 '*self.inner': T 187..191 'self': &Box<T> 187..197 'self.inner': *mut T - 218..308 '{ ...&s); }': () + 218..324 '{ ...&s); }': () 228..229 's': Option<i32> 232..236 'None': Option<i32> 246..247 'f': Box<dyn FnOnce(&Option<i32>)> - 281..294 'box (|ps| {})': Box<impl Fn(&Option<i32>)> - 286..293 '|ps| {}': impl Fn(&Option<i32>) - 287..289 'ps': &Option<i32> - 291..293 '{}': () - 300..301 'f': Box<dyn FnOnce(&Option<i32>)> - 300..305 'f(&s)': () - 302..304 '&s': &Option<i32> - 303..304 's': Option<i32> - 281..294: expected Box<dyn FnOnce(&Option<i32>)>, got Box<impl Fn(&Option<i32>)> + 281..310 'Box { ... {}) }': Box<dyn FnOnce(&Option<i32>)> + 294..308 '&mut (|ps| {})': &mut impl Fn(&Option<i32>) + 300..307 '|ps| {}': impl Fn(&Option<i32>) + 301..303 'ps': &Option<i32> + 305..307 '{}': () + 316..317 'f': Box<dyn FnOnce(&Option<i32>)> + 316..321 'f(&s)': () + 318..320 '&s': &Option<i32> + 319..320 's': Option<i32> "#]], ); } diff --git a/src/tools/rust-analyzer/crates/hir/src/attrs.rs b/src/tools/rust-analyzer/crates/hir/src/attrs.rs index 0f2fb2c81..796490abd 100644 --- a/src/tools/rust-analyzer/crates/hir/src/attrs.rs +++ b/src/tools/rust-analyzer/crates/hir/src/attrs.rs @@ -1,38 +1,27 @@ //! Attributes & documentation for hir types. use hir_def::{ - attr::{AttrsWithOwner, Documentation}, + attr::AttrsWithOwner, item_scope::ItemInNs, - path::ModPath, - resolver::HasResolver, - AttrDefId, GenericParamId, ModuleDefId, + path::{ModPath, Path}, + per_ns::Namespace, + resolver::{HasResolver, Resolver, TypeNs}, + AssocItemId, AttrDefId, ModuleDefId, }; -use hir_expand::hygiene::Hygiene; +use hir_expand::{hygiene::Hygiene, name::Name}; use hir_ty::db::HirDatabase; use syntax::{ast, AstNode}; use crate::{ - Adt, AssocItem, Const, ConstParam, Enum, ExternCrateDecl, Field, Function, GenericParam, Impl, - LifetimeParam, Macro, Module, ModuleDef, Static, Struct, Trait, TraitAlias, TypeAlias, - TypeParam, Union, Variant, + Adt, AsAssocItem, AssocItem, BuiltinType, Const, ConstParam, DocLinkDef, Enum, ExternCrateDecl, + Field, Function, GenericParam, Impl, LifetimeParam, Macro, Module, ModuleDef, Static, Struct, + Trait, TraitAlias, TypeAlias, TypeParam, Union, Variant, VariantDef, }; pub trait HasAttrs { fn attrs(self, db: &dyn HirDatabase) -> AttrsWithOwner; - fn docs(self, db: &dyn HirDatabase) -> Option<Documentation>; - fn resolve_doc_path( - self, - db: &dyn HirDatabase, - link: &str, - ns: Option<Namespace>, - ) -> Option<ModuleDef>; -} - -#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] -pub enum Namespace { - Types, - Values, - Macros, + #[doc(hidden)] + fn attr_id(self) -> AttrDefId; } macro_rules! impl_has_attrs { @@ -42,13 +31,8 @@ macro_rules! impl_has_attrs { let def = AttrDefId::$def_id(self.into()); db.attrs_with_owner(def) } - fn docs(self, db: &dyn HirDatabase) -> Option<Documentation> { - let def = AttrDefId::$def_id(self.into()); - db.attrs(def).docs() - } - fn resolve_doc_path(self, db: &dyn HirDatabase, link: &str, ns: Option<Namespace>) -> Option<ModuleDef> { - let def = AttrDefId::$def_id(self.into()); - resolve_doc_path(db, def, link, ns).map(ModuleDef::from) + fn attr_id(self) -> AttrDefId { + AttrDefId::$def_id(self.into()) } } )*}; @@ -68,6 +52,7 @@ impl_has_attrs![ (Module, ModuleId), (GenericParam, GenericParamId), (Impl, ImplId), + (ExternCrateDecl, ExternCrateId), ]; macro_rules! impl_has_attrs_enum { @@ -76,11 +61,8 @@ macro_rules! impl_has_attrs_enum { fn attrs(self, db: &dyn HirDatabase) -> AttrsWithOwner { $enum::$variant(self).attrs(db) } - fn docs(self, db: &dyn HirDatabase) -> Option<Documentation> { - $enum::$variant(self).docs(db) - } - fn resolve_doc_path(self, db: &dyn HirDatabase, link: &str, ns: Option<Namespace>) -> Option<ModuleDef> { - $enum::$variant(self).resolve_doc_path(db, link, ns) + fn attr_id(self) -> AttrDefId { + $enum::$variant(self).attr_id() } } )*}; @@ -97,70 +79,35 @@ impl HasAttrs for AssocItem { AssocItem::TypeAlias(it) => it.attrs(db), } } - - fn docs(self, db: &dyn HirDatabase) -> Option<Documentation> { - match self { - AssocItem::Function(it) => it.docs(db), - AssocItem::Const(it) => it.docs(db), - AssocItem::TypeAlias(it) => it.docs(db), - } - } - - fn resolve_doc_path( - self, - db: &dyn HirDatabase, - link: &str, - ns: Option<Namespace>, - ) -> Option<ModuleDef> { + fn attr_id(self) -> AttrDefId { match self { - AssocItem::Function(it) => it.resolve_doc_path(db, link, ns), - AssocItem::Const(it) => it.resolve_doc_path(db, link, ns), - AssocItem::TypeAlias(it) => it.resolve_doc_path(db, link, ns), + AssocItem::Function(it) => it.attr_id(), + AssocItem::Const(it) => it.attr_id(), + AssocItem::TypeAlias(it) => it.attr_id(), } } } -impl HasAttrs for ExternCrateDecl { - fn attrs(self, db: &dyn HirDatabase) -> AttrsWithOwner { - let def = AttrDefId::ExternCrateId(self.into()); - db.attrs_with_owner(def) - } - fn docs(self, db: &dyn HirDatabase) -> Option<Documentation> { - let crate_docs = self.resolved_crate(db)?.root_module().attrs(db).docs().map(String::from); - let def = AttrDefId::ExternCrateId(self.into()); - let decl_docs = db.attrs(def).docs().map(String::from); - match (decl_docs, crate_docs) { - (None, None) => None, - (Some(decl_docs), None) => Some(decl_docs), - (None, Some(crate_docs)) => Some(crate_docs), - (Some(mut decl_docs), Some(crate_docs)) => { - decl_docs.push('\n'); - decl_docs.push('\n'); - decl_docs += &crate_docs; - Some(decl_docs) - } - } - .map(Documentation::new) - } - fn resolve_doc_path( - self, - db: &dyn HirDatabase, - link: &str, - ns: Option<Namespace>, - ) -> Option<ModuleDef> { - let def = AttrDefId::ExternCrateId(self.into()); - resolve_doc_path(db, def, link, ns).map(ModuleDef::from) - } +/// Resolves the item `link` points to in the scope of `def`. +pub fn resolve_doc_path_on( + db: &dyn HirDatabase, + def: impl HasAttrs, + link: &str, + ns: Option<Namespace>, +) -> Option<DocLinkDef> { + // AttrDefId::FieldId(it) => it.parent.resolver(db.upcast()), + // AttrDefId::EnumVariantId(it) => it.parent.resolver(db.upcast()), + + resolve_doc_path_on_(db, link, def.attr_id(), ns) } -/// Resolves the item `link` points to in the scope of `def`. -fn resolve_doc_path( +fn resolve_doc_path_on_( db: &dyn HirDatabase, - def: AttrDefId, link: &str, + attr_id: AttrDefId, ns: Option<Namespace>, -) -> Option<ModuleDefId> { - let resolver = match def { +) -> Option<DocLinkDef> { + let resolver = match attr_id { AttrDefId::ModuleId(it) => it.resolver(db.upcast()), AttrDefId::FieldId(it) => it.parent.resolver(db.upcast()), AttrDefId::AdtId(it) => it.resolver(db.upcast()), @@ -176,16 +123,110 @@ fn resolve_doc_path( AttrDefId::UseId(it) => it.resolver(db.upcast()), AttrDefId::MacroId(it) => it.resolver(db.upcast()), AttrDefId::ExternCrateId(it) => it.resolver(db.upcast()), - AttrDefId::GenericParamId(it) => match it { - GenericParamId::TypeParamId(it) => it.parent(), - GenericParamId::ConstParamId(it) => it.parent(), - GenericParamId::LifetimeParamId(it) => it.parent, + AttrDefId::GenericParamId(_) => return None, + }; + + let mut modpath = modpath_from_str(db, link)?; + + let resolved = resolver.resolve_module_path_in_items(db.upcast(), &modpath); + if resolved.is_none() { + let last_name = modpath.pop_segment()?; + resolve_assoc_or_field(db, resolver, modpath, last_name, ns) + } else { + let def = match ns { + Some(Namespace::Types) => resolved.take_types(), + Some(Namespace::Values) => resolved.take_values(), + Some(Namespace::Macros) => resolved.take_macros().map(ModuleDefId::MacroId), + None => resolved.iter_items().next().map(|(it, _)| match it { + ItemInNs::Types(it) => it, + ItemInNs::Values(it) => it, + ItemInNs::Macros(it) => ModuleDefId::MacroId(it), + }), + }; + Some(DocLinkDef::ModuleDef(def?.into())) + } +} + +fn resolve_assoc_or_field( + db: &dyn HirDatabase, + resolver: Resolver, + path: ModPath, + name: Name, + ns: Option<Namespace>, +) -> Option<DocLinkDef> { + let path = Path::from_known_path_with_no_generic(path); + // FIXME: This does not handle `Self` on trait definitions, which we should resolve to the + // trait itself. + let base_def = resolver.resolve_path_in_type_ns_fully(db.upcast(), &path)?; + + let ty = match base_def { + TypeNs::SelfType(id) => Impl::from(id).self_ty(db), + TypeNs::GenericParam(_) => { + // Even if this generic parameter has some trait bounds, rustdoc doesn't + // resolve `name` to trait items. + return None; + } + TypeNs::AdtId(id) | TypeNs::AdtSelfType(id) => Adt::from(id).ty(db), + TypeNs::EnumVariantId(id) => { + // Enum variants don't have path candidates. + let variant = Variant::from(id); + return resolve_field(db, variant.into(), name, ns); + } + TypeNs::TypeAliasId(id) => { + let alias = TypeAlias::from(id); + if alias.as_assoc_item(db).is_some() { + // We don't normalize associated type aliases, so we have nothing to + // resolve `name` to. + return None; + } + alias.ty(db) + } + TypeNs::BuiltinType(id) => BuiltinType::from(id).ty(db), + TypeNs::TraitId(id) => { + // Doc paths in this context may only resolve to an item of this trait + // (i.e. no items of its supertraits), so we need to handle them here + // independently of others. + return db.trait_data(id).items.iter().find(|it| it.0 == name).map(|(_, assoc_id)| { + let def = match *assoc_id { + AssocItemId::FunctionId(it) => ModuleDef::Function(it.into()), + AssocItemId::ConstId(it) => ModuleDef::Const(it.into()), + AssocItemId::TypeAliasId(it) => ModuleDef::TypeAlias(it.into()), + }; + DocLinkDef::ModuleDef(def) + }); + } + TypeNs::TraitAliasId(_) => { + // XXX: Do these get resolved? + return None; } - .resolver(db.upcast()), }; - let modpath = { - // FIXME: this is not how we should get a mod path here + // FIXME: Resolve associated items here, e.g. `Option::map`. Note that associated items take + // precedence over fields. + + let variant_def = match ty.as_adt()? { + Adt::Struct(it) => it.into(), + Adt::Union(it) => it.into(), + Adt::Enum(_) => return None, + }; + resolve_field(db, variant_def, name, ns) +} + +fn resolve_field( + db: &dyn HirDatabase, + def: VariantDef, + name: Name, + ns: Option<Namespace>, +) -> Option<DocLinkDef> { + if let Some(Namespace::Types | Namespace::Macros) = ns { + return None; + } + def.fields(db).into_iter().find(|f| f.name(db) == name).map(DocLinkDef::Field) +} + +fn modpath_from_str(db: &dyn HirDatabase, link: &str) -> Option<ModPath> { + // FIXME: this is not how we should get a mod path here. + let try_get_modpath = |link: &str| { let ast_path = ast::SourceFile::parse(&format!("type T = {link};")) .syntax_node() .descendants() @@ -193,23 +234,20 @@ fn resolve_doc_path( if ast_path.syntax().text() != link { return None; } - ModPath::from_src(db.upcast(), ast_path, &Hygiene::new_unhygienic())? + ModPath::from_src(db.upcast(), ast_path, &Hygiene::new_unhygienic()) }; - let resolved = resolver.resolve_module_path_in_items(db.upcast(), &modpath); - let resolved = if resolved.is_none() { - resolver.resolve_module_path_in_trait_assoc_items(db.upcast(), &modpath)? - } else { - resolved - }; - match ns { - Some(Namespace::Types) => resolved.take_types(), - Some(Namespace::Values) => resolved.take_values(), - Some(Namespace::Macros) => resolved.take_macros().map(ModuleDefId::MacroId), - None => resolved.iter_items().next().map(|it| match it { - ItemInNs::Types(it) => it, - ItemInNs::Values(it) => it, - ItemInNs::Macros(it) => ModuleDefId::MacroId(it), - }), + let full = try_get_modpath(link); + if full.is_some() { + return full; } + + // Tuple field names cannot be a part of `ModPath` usually, but rustdoc can + // resolve doc paths like `TupleStruct::0`. + // FIXME: Find a better way to handle these. + let (base, maybe_tuple_field) = link.rsplit_once("::")?; + let tuple_field = Name::new_tuple_field(maybe_tuple_field.parse().ok()?); + let mut modpath = try_get_modpath(base)?; + modpath.push_segment(tuple_field); + Some(modpath) } diff --git a/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs b/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs index 80c3bcdca..479138b67 100644 --- a/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs @@ -43,6 +43,7 @@ diagnostics![ MacroExpansionParseError, MalformedDerive, MismatchedArgCount, + MismatchedTupleStructPatArgCount, MissingFields, MissingMatchArms, MissingUnsafe, @@ -172,7 +173,8 @@ pub struct MalformedDerive { #[derive(Debug)] pub struct NoSuchField { - pub field: InFile<AstPtr<ast::RecordExprField>>, + pub field: InFile<Either<AstPtr<ast::RecordExprField>, AstPtr<ast::RecordPatField>>>, + pub private: bool, } #[derive(Debug)] @@ -183,6 +185,13 @@ pub struct PrivateAssocItem { } #[derive(Debug)] +pub struct MismatchedTupleStructPatArgCount { + pub expr_or_pat: InFile<Either<AstPtr<ast::Expr>, AstPtr<ast::Pat>>>, + pub expected: usize, + pub found: usize, +} + +#[derive(Debug)] pub struct ExpectedFunction { pub call: InFile<AstPtr<ast::Expr>>, pub found: Type, diff --git a/src/tools/rust-analyzer/crates/hir/src/display.rs b/src/tools/rust-analyzer/crates/hir/src/display.rs index 9dfb98e45..ac171026d 100644 --- a/src/tools/rust-analyzer/crates/hir/src/display.rs +++ b/src/tools/rust-analyzer/crates/hir/src/display.rs @@ -8,7 +8,6 @@ use hir_def::{ type_ref::{TypeBound, TypeRef}, AdtId, GenericDefId, }; -use hir_expand::name; use hir_ty::{ display::{ write_bounds_like_dyn_trait_with_prefix, write_visibility, HirDisplay, HirDisplayError, @@ -19,8 +18,9 @@ use hir_ty::{ use crate::{ Adt, AsAssocItem, AssocItemContainer, Const, ConstParam, Enum, ExternCrateDecl, Field, - Function, GenericParam, HasCrate, HasVisibility, LifetimeParam, Macro, Module, Static, Struct, - Trait, TraitAlias, TyBuilder, Type, TypeAlias, TypeOrConstParam, TypeParam, Union, Variant, + Function, GenericParam, HasCrate, HasVisibility, LifetimeParam, Macro, Module, SelfParam, + Static, Struct, Trait, TraitAlias, TyBuilder, Type, TypeAlias, TypeOrConstParam, TypeParam, + Union, Variant, }; impl HirDisplay for Function { @@ -57,37 +57,21 @@ impl HirDisplay for Function { f.write_char('(')?; - let write_self_param = |ty: &TypeRef, f: &mut HirFormatter<'_>| match ty { - TypeRef::Path(p) if p.is_self_type() => f.write_str("self"), - TypeRef::Reference(inner, lifetime, mut_) if matches!(&**inner, TypeRef::Path(p) if p.is_self_type()) => - { - f.write_char('&')?; - if let Some(lifetime) = lifetime { - write!(f, "{} ", lifetime.name.display(f.db.upcast()))?; - } - if let hir_def::type_ref::Mutability::Mut = mut_ { - f.write_str("mut ")?; - } - f.write_str("self") - } - _ => { - f.write_str("self: ")?; - ty.hir_fmt(f) - } - }; - let mut first = true; + let mut skip_self = 0; + if let Some(self_param) = self.self_param(db) { + self_param.hir_fmt(f)?; + first = false; + skip_self = 1; + } + // FIXME: Use resolved `param.ty` once we no longer discard lifetimes - for (type_ref, param) in data.params.iter().zip(self.assoc_fn_params(db)) { + for (type_ref, param) in data.params.iter().zip(self.assoc_fn_params(db)).skip(skip_self) { let local = param.as_local(db).map(|it| it.name(db)); if !first { f.write_str(", ")?; } else { first = false; - if local == Some(name!(self)) { - write_self_param(type_ref, f)?; - continue; - } } match local { Some(name) => write!(f, "{}: ", name.display(f.db.upcast()))?, @@ -137,6 +121,31 @@ impl HirDisplay for Function { } } +impl HirDisplay for SelfParam { + fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { + let data = f.db.function_data(self.func); + let param = data.params.first().unwrap(); + match &**param { + TypeRef::Path(p) if p.is_self_type() => f.write_str("self"), + TypeRef::Reference(inner, lifetime, mut_) if matches!(&**inner, TypeRef::Path(p) if p.is_self_type()) => + { + f.write_char('&')?; + if let Some(lifetime) = lifetime { + write!(f, "{} ", lifetime.name.display(f.db.upcast()))?; + } + if let hir_def::type_ref::Mutability::Mut = mut_ { + f.write_str("mut ")?; + } + f.write_str("self") + } + ty => { + f.write_str("self: ")?; + ty.hir_fmt(f) + } + } + } +} + impl HirDisplay for Adt { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { match self { @@ -357,6 +366,11 @@ fn write_generic_params( delim(f)?; write!(f, "const {}: ", name.display(f.db.upcast()))?; c.ty.hir_fmt(f)?; + + if let Some(default) = &c.default { + f.write_str(" = ")?; + write!(f, "{}", default.display(f.db.upcast()))?; + } } } } diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index bf041b61f..b215ed38f 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -63,12 +63,13 @@ use hir_ty::{ all_super_traits, autoderef, consteval::{try_const_usize, unknown_const_as_generic, ConstEvalError, ConstExt}, diagnostics::BodyValidationDiagnostic, + known_const_to_ast, layout::{Layout as TyLayout, RustcEnumVariantIdx, TagEncoding}, method_resolution::{self, TyFingerprint}, mir::{self, interpret_mir}, primitive::UintTy, traits::FnTrait, - AliasTy, CallableDefId, CallableSig, Canonical, CanonicalVarKinds, Cast, ClosureId, + AliasTy, CallableDefId, CallableSig, Canonical, CanonicalVarKinds, Cast, ClosureId, GenericArg, GenericArgData, Interner, ParamKind, QuantifiedWhereClause, Scalar, Substitution, TraitEnvironment, TraitRefExt, Ty, TyBuilder, TyDefId, TyExt, TyKind, ValueTyDefId, WhereClause, @@ -87,13 +88,14 @@ use triomphe::Arc; use crate::db::{DefDatabase, HirDatabase}; pub use crate::{ - attrs::{HasAttrs, Namespace}, + attrs::{resolve_doc_path_on, HasAttrs}, diagnostics::{ AnyDiagnostic, BreakOutsideOfLoop, CaseType, ExpectedFunction, InactiveCode, IncoherentImpl, IncorrectCase, InvalidDeriveTarget, MacroDefError, MacroError, - MacroExpansionParseError, MalformedDerive, MismatchedArgCount, MissingFields, - MissingMatchArms, MissingUnsafe, MovedOutOfRef, NeedMut, NoSuchField, PrivateAssocItem, - PrivateField, ReplaceFilterMapNextWithFindMap, TypeMismatch, TypedHole, UndeclaredLabel, + MacroExpansionParseError, MalformedDerive, MismatchedArgCount, + MismatchedTupleStructPatArgCount, MissingFields, MissingMatchArms, MissingUnsafe, + MovedOutOfRef, NeedMut, NoSuchField, PrivateAssocItem, PrivateField, + ReplaceFilterMapNextWithFindMap, TypeMismatch, TypedHole, UndeclaredLabel, UnimplementedBuiltinMacro, UnreachableLabel, UnresolvedExternCrate, UnresolvedField, UnresolvedImport, UnresolvedMacroCall, UnresolvedMethodCall, UnresolvedModule, UnresolvedProcMacro, UnusedMut, @@ -114,13 +116,14 @@ pub use crate::{ pub use { cfg::{CfgAtom, CfgExpr, CfgOptions}, hir_def::{ - attr::{builtin::AttributeTemplate, Attrs, AttrsWithOwner, Documentation}, + attr::{builtin::AttributeTemplate, AttrSourceMap, Attrs, AttrsWithOwner}, data::adt::StructKind, find_path::PrefixKind, import_map, lang_item::LangItem, nameres::{DefMap, ModuleSource}, path::{ModPath, PathKind}, + per_ns::Namespace, type_ref::{Mutability, TypeRef}, visibility::Visibility, // FIXME: This is here since some queries take it as input that are used @@ -128,7 +131,7 @@ pub use { {AdtId, ModuleDefId}, }, hir_expand::{ - attrs::Attr, + attrs::{Attr, AttrId}, name::{known, Name}, ExpandResult, HirFileId, InFile, MacroFile, Origin, }, @@ -561,8 +564,8 @@ impl Module { emit_def_diagnostic(db, acc, diag); } - for decl in self.declarations(db) { - match decl { + for def in self.declarations(db) { + match def { ModuleDef::Module(m) => { // Only add diagnostics from inline modules if def_map[m.id.local_id].origin.is_inline() { @@ -573,7 +576,7 @@ impl Module { for diag in db.trait_data_with_diagnostics(t.id).1.iter() { emit_def_diagnostic(db, acc, diag); } - acc.extend(decl.diagnostics(db)) + acc.extend(def.diagnostics(db)) } ModuleDef::Adt(adt) => { match adt { @@ -597,10 +600,10 @@ impl Module { } } } - acc.extend(decl.diagnostics(db)) + acc.extend(def.diagnostics(db)) } ModuleDef::Macro(m) => emit_macro_def_diagnostics(db, acc, m), - _ => acc.extend(decl.diagnostics(db)), + _ => acc.extend(def.diagnostics(db)), } } self.legacy_macros(db).into_iter().for_each(|m| emit_macro_def_diagnostics(db, acc, m)); @@ -719,20 +722,18 @@ fn emit_def_diagnostic_( ) { match diag { DefDiagnosticKind::UnresolvedModule { ast: declaration, candidates } => { - let decl = declaration.to_node(db.upcast()); + let decl = declaration.to_ptr(db.upcast()); acc.push( UnresolvedModule { - decl: InFile::new(declaration.file_id, AstPtr::new(&decl)), + decl: InFile::new(declaration.file_id, decl), candidates: candidates.clone(), } .into(), ) } DefDiagnosticKind::UnresolvedExternCrate { ast } => { - let item = ast.to_node(db.upcast()); - acc.push( - UnresolvedExternCrate { decl: InFile::new(ast.file_id, AstPtr::new(&item)) }.into(), - ); + let item = ast.to_ptr(db.upcast()); + acc.push(UnresolvedExternCrate { decl: InFile::new(ast.file_id, item) }.into()); } DefDiagnosticKind::UnresolvedImport { id, index } => { @@ -747,14 +748,10 @@ fn emit_def_diagnostic_( } DefDiagnosticKind::UnconfiguredCode { ast, cfg, opts } => { - let item = ast.to_node(db.upcast()); + let item = ast.to_ptr(db.upcast()); acc.push( - InactiveCode { - node: ast.with_value(SyntaxNodePtr::new(&item).into()), - cfg: cfg.clone(), - opts: opts.clone(), - } - .into(), + InactiveCode { node: ast.with_value(item), cfg: cfg.clone(), opts: opts.clone() } + .into(), ); } DefDiagnosticKind::UnresolvedProcMacro { ast, krate } => { @@ -1273,7 +1270,7 @@ impl Adt { .fill(|x| { let r = it.next().unwrap_or_else(|| TyKind::Error.intern(Interner)); match x { - ParamKind::Type => GenericArgData::Ty(r).intern(Interner), + ParamKind::Type => r.cast(Interner), ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()), } }) @@ -1450,6 +1447,7 @@ impl DefWithBody { } pub fn diagnostics(self, db: &dyn HirDatabase, acc: &mut Vec<AnyDiagnostic>) { + db.unwind_if_cancelled(); let krate = self.module(db).id.krate(); let (body, source_map) = db.body_with_source_map(self.into()); @@ -1505,11 +1503,19 @@ impl DefWithBody { let infer = db.infer(self.into()); let source_map = Lazy::new(|| db.body_with_source_map(self.into()).1); let expr_syntax = |expr| source_map.expr_syntax(expr).expect("unexpected synthetic"); + let pat_syntax = |pat| source_map.pat_syntax(pat).expect("unexpected synthetic"); for d in &infer.diagnostics { match d { - &hir_ty::InferenceDiagnostic::NoSuchField { expr } => { - let field = source_map.field_syntax(expr); - acc.push(NoSuchField { field }.into()) + &hir_ty::InferenceDiagnostic::NoSuchField { field: expr, private } => { + let expr_or_pat = match expr { + ExprOrPatId::ExprId(expr) => { + source_map.field_syntax(expr).map(Either::Left) + } + ExprOrPatId::PatId(pat) => { + source_map.pat_field_syntax(pat).map(Either::Right) + } + }; + acc.push(NoSuchField { field: expr_or_pat, private }.into()) } &hir_ty::InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => { acc.push( @@ -1525,10 +1531,7 @@ impl DefWithBody { &hir_ty::InferenceDiagnostic::PrivateAssocItem { id, item } => { let expr_or_pat = match id { ExprOrPatId::ExprId(expr) => expr_syntax(expr).map(Either::Left), - ExprOrPatId::PatId(pat) => source_map - .pat_syntax(pat) - .expect("unexpected synthetic") - .map(Either::Right), + ExprOrPatId::PatId(pat) => pat_syntax(pat).map(Either::Right), }; let item = item.into(); acc.push(PrivateAssocItem { expr_or_pat, item }.into()) @@ -1600,6 +1603,23 @@ impl DefWithBody { .into(), ) } + &hir_ty::InferenceDiagnostic::MismatchedTupleStructPatArgCount { + pat, + expected, + found, + } => { + let expr_or_pat = match pat { + ExprOrPatId::ExprId(expr) => expr_syntax(expr).map(Either::Left), + ExprOrPatId::PatId(pat) => source_map + .pat_syntax(pat) + .expect("unexpected synthetic") + .map(|it| it.unwrap_left()) + .map(Either::Right), + }; + acc.push( + MismatchedTupleStructPatArgCount { expr_or_pat, expected, found }.into(), + ) + } } } for (pat_or_expr, mismatch) in infer.type_mismatches() { @@ -2096,14 +2116,6 @@ impl SelfParam { .unwrap_or(Access::Owned) } - pub fn display(self, db: &dyn HirDatabase) -> &'static str { - match self.access(db) { - Access::Shared => "&self", - Access::Exclusive => "&mut self", - Access::Owned => "self", - } - } - pub fn source(&self, db: &dyn HirDatabase) -> Option<InFile<ast::SelfParam>> { let InFile { file_id, value } = Function::from(self.func).source(db)?; value @@ -3142,12 +3154,8 @@ impl TypeParam { } pub fn default(self, db: &dyn HirDatabase) -> Option<Type> { - let params = db.generic_defaults(self.id.parent()); - let local_idx = hir_ty::param_idx(db, self.id.into())?; + let ty = generic_arg_from_param(db, self.id.into())?; let resolver = self.id.parent().resolver(db.upcast()); - let ty = params.get(local_idx)?.clone(); - let subst = TyBuilder::placeholder_subst(db, self.id.parent()); - let ty = ty.substitute(Interner, &subst); match ty.data(Interner) { GenericArgData::Ty(it) => { Some(Type::new_with_resolver_inner(db, &resolver, it.clone())) @@ -3209,6 +3217,19 @@ impl ConstParam { pub fn ty(self, db: &dyn HirDatabase) -> Type { Type::new(db, self.id.parent(), db.const_param_ty(self.id)) } + + pub fn default(self, db: &dyn HirDatabase) -> Option<ast::ConstArg> { + let arg = generic_arg_from_param(db, self.id.into())?; + known_const_to_ast(arg.constant(Interner)?, db) + } +} + +fn generic_arg_from_param(db: &dyn HirDatabase, id: TypeOrConstParamId) -> Option<GenericArg> { + let params = db.generic_defaults(id.parent); + let local_idx = hir_ty::param_idx(db, id)?; + let ty = params.get(local_idx)?.clone(); + let subst = TyBuilder::placeholder_subst(db, id.parent); + Some(ty.substitute(Interner, &subst)) } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -3716,7 +3737,7 @@ impl Type { .fill(|x| { let r = it.next().unwrap(); match x { - ParamKind::Type => GenericArgData::Ty(r).intern(Interner), + ParamKind::Type => r.cast(Interner), ParamKind::Const(ty) => { // FIXME: this code is not covered in tests. unknown_const_as_generic(ty.clone()) @@ -3749,9 +3770,7 @@ impl Type { .fill(|it| { // FIXME: this code is not covered in tests. match it { - ParamKind::Type => { - GenericArgData::Ty(args.next().unwrap().ty.clone()).intern(Interner) - } + ParamKind::Type => args.next().unwrap().ty.clone().cast(Interner), ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()), } }) @@ -4414,14 +4433,13 @@ impl Callable { Other => CallableKind::Other, } } - pub fn receiver_param(&self, db: &dyn HirDatabase) -> Option<(ast::SelfParam, Type)> { + pub fn receiver_param(&self, db: &dyn HirDatabase) -> Option<(SelfParam, Type)> { let func = match self.callee { Callee::Def(CallableDefId::FunctionId(it)) if self.is_bound_method => it, _ => return None, }; - let src = func.lookup(db.upcast()).source(db.upcast()); - let param_list = src.value.param_list()?; - Some((param_list.self_param()?, self.ty.derived(self.sig.params()[0].clone()))) + let func = Function { id: func }; + Some((func.self_param(db)?, self.ty.derived(self.sig.params()[0].clone()))) } pub fn n_params(&self) -> usize { self.sig.params().len() - if self.is_bound_method { 1 } else { 0 } @@ -4844,3 +4862,10 @@ pub enum ItemContainer { ExternBlock(), Crate(CrateId), } + +/// Subset of `ide_db::Definition` that doc links can resolve to. +pub enum DocLinkDef { + ModuleDef(ModuleDef), + Field(Field), + SelfType(Trait), +} diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs index e99d2984c..a42e0978b 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs @@ -127,148 +127,24 @@ impl<DB> fmt::Debug for Semantics<'_, DB> { } } +impl<'db, DB> ops::Deref for Semantics<'db, DB> { + type Target = SemanticsImpl<'db>; + + fn deref(&self) -> &Self::Target { + &self.imp + } +} + impl<'db, DB: HirDatabase> Semantics<'db, DB> { pub fn new(db: &DB) -> Semantics<'_, DB> { let impl_ = SemanticsImpl::new(db); Semantics { db, imp: impl_ } } - pub fn parse(&self, file_id: FileId) -> ast::SourceFile { - self.imp.parse(file_id) - } - - pub fn parse_or_expand(&self, file_id: HirFileId) -> SyntaxNode { - self.imp.parse_or_expand(file_id) - } - - pub fn expand(&self, macro_call: &ast::MacroCall) -> Option<SyntaxNode> { - self.imp.expand(macro_call) - } - - /// If `item` has an attribute macro attached to it, expands it. - pub fn expand_attr_macro(&self, item: &ast::Item) -> Option<SyntaxNode> { - self.imp.expand_attr_macro(item) - } - - pub fn expand_derive_as_pseudo_attr_macro(&self, attr: &ast::Attr) -> Option<SyntaxNode> { - self.imp.expand_derive_as_pseudo_attr_macro(attr) - } - - pub fn resolve_derive_macro(&self, derive: &ast::Attr) -> Option<Vec<Option<Macro>>> { - self.imp.resolve_derive_macro(derive) - } - - pub fn expand_derive_macro(&self, derive: &ast::Attr) -> Option<Vec<SyntaxNode>> { - self.imp.expand_derive_macro(derive) - } - - pub fn is_attr_macro_call(&self, item: &ast::Item) -> bool { - self.imp.is_attr_macro_call(item) - } - - pub fn is_derive_annotated(&self, item: &ast::Adt) -> bool { - self.imp.is_derive_annotated(item) - } - - pub fn speculative_expand( - &self, - actual_macro_call: &ast::MacroCall, - speculative_args: &ast::TokenTree, - token_to_map: SyntaxToken, - ) -> Option<(SyntaxNode, SyntaxToken)> { - self.imp.speculative_expand(actual_macro_call, speculative_args, token_to_map) - } - - pub fn speculative_expand_attr_macro( - &self, - actual_macro_call: &ast::Item, - speculative_args: &ast::Item, - token_to_map: SyntaxToken, - ) -> Option<(SyntaxNode, SyntaxToken)> { - self.imp.speculative_expand_attr(actual_macro_call, speculative_args, token_to_map) - } - - pub fn speculative_expand_derive_as_pseudo_attr_macro( - &self, - actual_macro_call: &ast::Attr, - speculative_args: &ast::Attr, - token_to_map: SyntaxToken, - ) -> Option<(SyntaxNode, SyntaxToken)> { - self.imp.speculative_expand_derive_as_pseudo_attr_macro( - actual_macro_call, - speculative_args, - token_to_map, - ) - } - - /// Descend the token into macrocalls to its first mapped counterpart. - pub fn descend_into_macros_single(&self, token: SyntaxToken) -> SyntaxToken { - self.imp.descend_into_macros_single(token) - } - - /// Descend the token into macrocalls to all its mapped counterparts. - pub fn descend_into_macros(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> { - self.imp.descend_into_macros(token) - } - - /// Descend the token into macrocalls to all its mapped counterparts that have the same text as the input token. - /// - /// Returns the original non descended token if none of the mapped counterparts have the same text. - pub fn descend_into_macros_with_same_text( - &self, - token: SyntaxToken, - ) -> SmallVec<[SyntaxToken; 1]> { - self.imp.descend_into_macros_with_same_text(token) - } - - pub fn descend_into_macros_with_kind_preference(&self, token: SyntaxToken) -> SyntaxToken { - self.imp.descend_into_macros_with_kind_preference(token) - } - - /// Maps a node down by mapping its first and last token down. - pub fn descend_node_into_attributes<N: AstNode>(&self, node: N) -> SmallVec<[N; 1]> { - self.imp.descend_node_into_attributes(node) - } - - /// Search for a definition's source and cache its syntax tree - pub fn source<Def: HasSource>(&self, def: Def) -> Option<InFile<Def::Ast>> - where - Def::Ast: AstNode, - { - self.imp.source(def) - } - pub fn hir_file_for(&self, syntax_node: &SyntaxNode) -> HirFileId { self.imp.find_file(syntax_node).file_id } - /// Attempts to map the node out of macro expanded files returning the original file range. - /// If upmapping is not possible, this will fall back to the range of the macro call of the - /// macro file the node resides in. - pub fn original_range(&self, node: &SyntaxNode) -> FileRange { - self.imp.original_range(node) - } - - /// Attempts to map the node out of macro expanded files returning the original file range. - pub fn original_range_opt(&self, node: &SyntaxNode) -> Option<FileRange> { - self.imp.original_range_opt(node) - } - - /// Attempts to map the node out of macro expanded files. - /// This only work for attribute expansions, as other ones do not have nodes as input. - pub fn original_ast_node<N: AstNode>(&self, node: N) -> Option<N> { - self.imp.original_ast_node(node) - } - /// Attempts to map the node out of macro expanded files. - /// This only work for attribute expansions, as other ones do not have nodes as input. - pub fn original_syntax_node(&self, node: &SyntaxNode) -> Option<SyntaxNode> { - self.imp.original_syntax_node(node) - } - - pub fn diagnostics_display_range(&self, diagnostics: InFile<SyntaxNodePtr>) -> FileRange { - self.imp.diagnostics_display_range(diagnostics) - } - pub fn token_ancestors_with_macros( &self, token: SyntaxToken, @@ -276,19 +152,6 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { token.parent().into_iter().flat_map(move |it| self.ancestors_with_macros(it)) } - /// Iterates the ancestors of the given node, climbing up macro expansions while doing so. - pub fn ancestors_with_macros(&self, node: SyntaxNode) -> impl Iterator<Item = SyntaxNode> + '_ { - self.imp.ancestors_with_macros(node) - } - - pub fn ancestors_at_offset_with_macros( - &self, - node: &SyntaxNode, - offset: TextSize, - ) -> impl Iterator<Item = SyntaxNode> + '_ { - self.imp.ancestors_at_offset_with_macros(node, offset) - } - /// Find an AstNode by offset inside SyntaxNode, if it is inside *Macrofile*, /// search up until it is of the target AstNode type pub fn find_node_at_offset_with_macros<N: AstNode>( @@ -319,53 +182,6 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { self.imp.descend_node_at_offset(node, offset).filter_map(|mut it| it.find_map(N::cast)) } - pub fn resolve_lifetime_param(&self, lifetime: &ast::Lifetime) -> Option<LifetimeParam> { - self.imp.resolve_lifetime_param(lifetime) - } - - pub fn resolve_label(&self, lifetime: &ast::Lifetime) -> Option<Label> { - self.imp.resolve_label(lifetime) - } - - pub fn resolve_type(&self, ty: &ast::Type) -> Option<Type> { - self.imp.resolve_type(ty) - } - - pub fn resolve_trait(&self, trait_: &ast::Path) -> Option<Trait> { - self.imp.resolve_trait(trait_) - } - - pub fn expr_adjustments(&self, expr: &ast::Expr) -> Option<Vec<Adjustment>> { - self.imp.expr_adjustments(expr) - } - - pub fn type_of_expr(&self, expr: &ast::Expr) -> Option<TypeInfo> { - self.imp.type_of_expr(expr) - } - - pub fn type_of_pat(&self, pat: &ast::Pat) -> Option<TypeInfo> { - self.imp.type_of_pat(pat) - } - - /// It also includes the changes that binding mode makes in the type. For example in - /// `let ref x @ Some(_) = None` the result of `type_of_pat` is `Option<T>` but the result - /// of this function is `&mut Option<T>` - pub fn type_of_binding_in_pat(&self, pat: &ast::IdentPat) -> Option<Type> { - self.imp.type_of_binding_in_pat(pat) - } - - pub fn type_of_self(&self, param: &ast::SelfParam) -> Option<Type> { - self.imp.type_of_self(param) - } - - pub fn pattern_adjustments(&self, pat: &ast::Pat) -> SmallVec<[Type; 1]> { - self.imp.pattern_adjustments(pat) - } - - pub fn binding_mode_of_pat(&self, pat: &ast::IdentPat) -> Option<BindingMode> { - self.imp.binding_mode_of_pat(pat) - } - pub fn resolve_method_call(&self, call: &ast::MethodCallExpr) -> Option<Function> { self.imp.resolve_method_call(call).map(Function::from) } @@ -400,61 +216,10 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { self.imp.resolve_try_expr(try_expr).map(Function::from) } - pub fn resolve_method_call_as_callable(&self, call: &ast::MethodCallExpr) -> Option<Callable> { - self.imp.resolve_method_call_as_callable(call) - } - - pub fn resolve_field(&self, field: &ast::FieldExpr) -> Option<Field> { - self.imp.resolve_field(field) - } - - pub fn resolve_record_field( - &self, - field: &ast::RecordExprField, - ) -> Option<(Field, Option<Local>, Type)> { - self.imp.resolve_record_field(field) - } - - pub fn resolve_record_pat_field(&self, field: &ast::RecordPatField) -> Option<(Field, Type)> { - self.imp.resolve_record_pat_field(field) - } - - pub fn resolve_macro_call(&self, macro_call: &ast::MacroCall) -> Option<Macro> { - self.imp.resolve_macro_call(macro_call) - } - - pub fn is_unsafe_macro_call(&self, macro_call: &ast::MacroCall) -> bool { - self.imp.is_unsafe_macro_call(macro_call) - } - - pub fn resolve_attr_macro_call(&self, item: &ast::Item) -> Option<Macro> { - self.imp.resolve_attr_macro_call(item) - } - - pub fn resolve_path(&self, path: &ast::Path) -> Option<PathResolution> { - self.imp.resolve_path(path) - } - pub fn resolve_variant(&self, record_lit: ast::RecordExpr) -> Option<VariantDef> { self.imp.resolve_variant(record_lit).map(VariantDef::from) } - pub fn resolve_bind_pat_to_const(&self, pat: &ast::IdentPat) -> Option<ModuleDef> { - self.imp.resolve_bind_pat_to_const(pat) - } - - pub fn record_literal_missing_fields(&self, literal: &ast::RecordExpr) -> Vec<(Field, Type)> { - self.imp.record_literal_missing_fields(literal) - } - - pub fn record_pattern_missing_fields(&self, pattern: &ast::RecordPat) -> Vec<(Field, Type)> { - self.imp.record_pattern_missing_fields(pattern) - } - - pub fn to_def<T: ToDef>(&self, src: &T) -> Option<T::Def> { - self.imp.to_def(src) - } - pub fn to_module_def(&self, file: FileId) -> Option<Module> { self.imp.to_module_def(file).next() } @@ -462,39 +227,6 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { pub fn to_module_defs(&self, file: FileId) -> impl Iterator<Item = Module> { self.imp.to_module_def(file) } - - pub fn scope(&self, node: &SyntaxNode) -> Option<SemanticsScope<'db>> { - self.imp.scope(node) - } - - pub fn scope_at_offset( - &self, - node: &SyntaxNode, - offset: TextSize, - ) -> Option<SemanticsScope<'db>> { - self.imp.scope_at_offset(node, offset) - } - - pub fn assert_contains_node(&self, node: &SyntaxNode) { - self.imp.assert_contains_node(node) - } - - pub fn is_unsafe_method_call(&self, method_call_expr: &ast::MethodCallExpr) -> bool { - self.imp.is_unsafe_method_call(method_call_expr) - } - - pub fn is_unsafe_ref_expr(&self, ref_expr: &ast::RefExpr) -> bool { - self.imp.is_unsafe_ref_expr(ref_expr) - } - - pub fn is_unsafe_ident_pat(&self, ident_pat: &ast::IdentPat) -> bool { - self.imp.is_unsafe_ident_pat(ident_pat) - } - - /// Returns `true` if the `node` is inside an `unsafe` context. - pub fn is_inside_unsafe(&self, expr: &ast::Expr) -> bool { - self.imp.is_inside_unsafe(expr) - } } impl<'db> SemanticsImpl<'db> { @@ -508,32 +240,33 @@ impl<'db> SemanticsImpl<'db> { } } - fn parse(&self, file_id: FileId) -> ast::SourceFile { + pub fn parse(&self, file_id: FileId) -> ast::SourceFile { let tree = self.db.parse(file_id).tree(); self.cache(tree.syntax().clone(), file_id.into()); tree } - fn parse_or_expand(&self, file_id: HirFileId) -> SyntaxNode { + pub fn parse_or_expand(&self, file_id: HirFileId) -> SyntaxNode { let node = self.db.parse_or_expand(file_id); self.cache(node.clone(), file_id); node } - fn expand(&self, macro_call: &ast::MacroCall) -> Option<SyntaxNode> { + pub fn expand(&self, macro_call: &ast::MacroCall) -> Option<SyntaxNode> { let sa = self.analyze_no_infer(macro_call.syntax())?; let file_id = sa.expand(self.db, InFile::new(sa.file_id, macro_call))?; let node = self.parse_or_expand(file_id); Some(node) } - fn expand_attr_macro(&self, item: &ast::Item) -> Option<SyntaxNode> { + /// If `item` has an attribute macro attached to it, expands it. + pub fn expand_attr_macro(&self, item: &ast::Item) -> Option<SyntaxNode> { let src = self.wrap_node_infile(item.clone()); let macro_call_id = self.with_ctx(|ctx| ctx.item_to_macro_call(src))?; Some(self.parse_or_expand(macro_call_id.as_file())) } - fn expand_derive_as_pseudo_attr_macro(&self, attr: &ast::Attr) -> Option<SyntaxNode> { + pub fn expand_derive_as_pseudo_attr_macro(&self, attr: &ast::Attr) -> Option<SyntaxNode> { let adt = attr.syntax().parent().and_then(ast::Adt::cast)?; let src = self.wrap_node_infile(attr.clone()); let call_id = self.with_ctx(|ctx| { @@ -542,7 +275,7 @@ impl<'db> SemanticsImpl<'db> { Some(self.parse_or_expand(call_id.as_file())) } - fn resolve_derive_macro(&self, attr: &ast::Attr) -> Option<Vec<Option<Macro>>> { + pub fn resolve_derive_macro(&self, attr: &ast::Attr) -> Option<Vec<Option<Macro>>> { let calls = self.derive_macro_calls(attr)?; self.with_ctx(|ctx| { Some( @@ -556,7 +289,7 @@ impl<'db> SemanticsImpl<'db> { }) } - fn expand_derive_macro(&self, attr: &ast::Attr) -> Option<Vec<SyntaxNode>> { + pub fn expand_derive_macro(&self, attr: &ast::Attr) -> Option<Vec<SyntaxNode>> { let res: Vec<_> = self .derive_macro_calls(attr)? .into_iter() @@ -581,19 +314,21 @@ impl<'db> SemanticsImpl<'db> { }) } - fn is_derive_annotated(&self, adt: &ast::Adt) -> bool { + pub fn is_derive_annotated(&self, adt: &ast::Adt) -> bool { let file_id = self.find_file(adt.syntax()).file_id; let adt = InFile::new(file_id, adt); self.with_ctx(|ctx| ctx.has_derives(adt)) } - fn is_attr_macro_call(&self, item: &ast::Item) -> bool { + pub fn is_attr_macro_call(&self, item: &ast::Item) -> bool { let file_id = self.find_file(item.syntax()).file_id; let src = InFile::new(file_id, item.clone()); self.with_ctx(|ctx| ctx.item_to_macro_call(src).is_some()) } - fn speculative_expand( + /// Expand the macro call with a different token tree, mapping the `token_to_map` down into the + /// expansion. `token_to_map` should be a token from the `speculative args` node. + pub fn speculative_expand( &self, actual_macro_call: &ast::MacroCall, speculative_args: &ast::TokenTree, @@ -606,7 +341,7 @@ impl<'db> SemanticsImpl<'db> { let macro_call_id = macro_call.as_call_id(self.db.upcast(), krate, |path| { resolver .resolve_path_as_macro(self.db.upcast(), &path, Some(MacroSubNs::Bang)) - .map(|it| macro_id_to_def_id(self.db.upcast(), it)) + .map(|(it, _)| macro_id_to_def_id(self.db.upcast(), it)) })?; hir_expand::db::expand_speculative( self.db.upcast(), @@ -616,7 +351,9 @@ impl<'db> SemanticsImpl<'db> { ) } - fn speculative_expand_attr( + /// Expand the macro call with a different item as the input, mapping the `token_to_map` down into the + /// expansion. `token_to_map` should be a token from the `speculative args` node. + pub fn speculative_expand_attr_macro( &self, actual_macro_call: &ast::Item, speculative_args: &ast::Item, @@ -632,7 +369,7 @@ impl<'db> SemanticsImpl<'db> { ) } - fn speculative_expand_derive_as_pseudo_attr_macro( + pub fn speculative_expand_derive_as_pseudo_attr_macro( &self, actual_macro_call: &ast::Attr, speculative_args: &ast::Attr, @@ -651,8 +388,9 @@ impl<'db> SemanticsImpl<'db> { ) } - // This might not be the correct way to do this, but it works for now - fn descend_node_into_attributes<N: AstNode>(&self, node: N) -> SmallVec<[N; 1]> { + /// Maps a node down by mapping its first and last token down. + pub fn descend_node_into_attributes<N: AstNode>(&self, node: N) -> SmallVec<[N; 1]> { + // This might not be the correct way to do this, but it works for now let mut res = smallvec![]; let tokens = (|| { let first = skip_trivia_token(node.syntax().first_token()?, Direction::Next)?; @@ -665,7 +403,7 @@ impl<'db> SemanticsImpl<'db> { }; if first == last { - self.descend_into_macros_impl(first, &mut |InFile { value, .. }| { + self.descend_into_macros_impl(first, 0.into(), &mut |InFile { value, .. }| { if let Some(node) = value.parent_ancestors().find_map(N::cast) { res.push(node) } @@ -674,7 +412,7 @@ impl<'db> SemanticsImpl<'db> { } else { // Descend first and last token, then zip them to look for the node they belong to let mut scratch: SmallVec<[_; 1]> = smallvec![]; - self.descend_into_macros_impl(first, &mut |token| { + self.descend_into_macros_impl(first, 0.into(), &mut |token| { scratch.push(token); false }); @@ -682,6 +420,7 @@ impl<'db> SemanticsImpl<'db> { let mut scratch = scratch.into_iter(); self.descend_into_macros_impl( last, + 0.into(), &mut |InFile { value: last, file_id: last_fid }| { if let Some(InFile { value: first, file_id: first_fid }) = scratch.next() { if first_fid == last_fid { @@ -705,19 +444,33 @@ impl<'db> SemanticsImpl<'db> { res } - fn descend_into_macros(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> { + /// Descend the token into its macro call if it is part of one, returning the tokens in the + /// expansion that it is associated with. If `offset` points into the token's range, it will + /// be considered for the mapping in case of inline format args. + pub fn descend_into_macros( + &self, + token: SyntaxToken, + offset: TextSize, + ) -> SmallVec<[SyntaxToken; 1]> { let mut res = smallvec![]; - self.descend_into_macros_impl(token, &mut |InFile { value, .. }| { + self.descend_into_macros_impl(token, offset, &mut |InFile { value, .. }| { res.push(value); false }); res } - fn descend_into_macros_with_same_text(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> { + /// Descend the token into macrocalls to all its mapped counterparts that have the same text as the input token. + /// + /// Returns the original non descended token if none of the mapped counterparts have the same text. + pub fn descend_into_macros_with_same_text( + &self, + token: SyntaxToken, + offset: TextSize, + ) -> SmallVec<[SyntaxToken; 1]> { let text = token.text(); let mut res = smallvec![]; - self.descend_into_macros_impl(token.clone(), &mut |InFile { value, .. }| { + self.descend_into_macros_impl(token.clone(), offset, &mut |InFile { value, .. }| { if value.text() == text { res.push(value); } @@ -729,7 +482,11 @@ impl<'db> SemanticsImpl<'db> { res } - fn descend_into_macros_with_kind_preference(&self, token: SyntaxToken) -> SyntaxToken { + pub fn descend_into_macros_with_kind_preference( + &self, + token: SyntaxToken, + offset: TextSize, + ) -> SyntaxToken { let fetch_kind = |token: &SyntaxToken| match token.parent() { Some(node) => match node.kind() { kind @ (SyntaxKind::NAME | SyntaxKind::NAME_REF) => { @@ -741,7 +498,7 @@ impl<'db> SemanticsImpl<'db> { }; let preferred_kind = fetch_kind(&token); let mut res = None; - self.descend_into_macros_impl(token.clone(), &mut |InFile { value, .. }| { + self.descend_into_macros_impl(token.clone(), offset, &mut |InFile { value, .. }| { if fetch_kind(&value) == preferred_kind { res = Some(value); true @@ -755,9 +512,12 @@ impl<'db> SemanticsImpl<'db> { res.unwrap_or(token) } - fn descend_into_macros_single(&self, token: SyntaxToken) -> SyntaxToken { + /// Descend the token into its macro call if it is part of one, returning the token in the + /// expansion that it is associated with. If `offset` points into the token's range, it will + /// be considered for the mapping in case of inline format args. + pub fn descend_into_macros_single(&self, token: SyntaxToken, offset: TextSize) -> SyntaxToken { let mut res = token.clone(); - self.descend_into_macros_impl(token, &mut |InFile { value, .. }| { + self.descend_into_macros_impl(token, offset, &mut |InFile { value, .. }| { res = value; true }); @@ -767,9 +527,13 @@ impl<'db> SemanticsImpl<'db> { fn descend_into_macros_impl( &self, token: SyntaxToken, + // FIXME: We might want this to be Option<TextSize> to be able to opt out of subrange + // mapping, specifically for node downmapping + offset: TextSize, f: &mut dyn FnMut(InFile<SyntaxToken>) -> bool, ) { let _p = profile::span("descend_into_macros"); + let relative_token_offset = token.text_range().start().checked_sub(offset); let parent = match token.parent() { Some(it) => it, None => return, @@ -796,7 +560,12 @@ impl<'db> SemanticsImpl<'db> { self.cache(value, file_id); } - let mapped_tokens = expansion_info.map_token_down(self.db.upcast(), item, token)?; + let mapped_tokens = expansion_info.map_token_down( + self.db.upcast(), + item, + token, + relative_token_offset, + )?; let len = stack.len(); // requeue the tokens we got from mapping our current token down @@ -943,7 +712,7 @@ impl<'db> SemanticsImpl<'db> { offset: TextSize, ) -> impl Iterator<Item = impl Iterator<Item = SyntaxNode> + '_> + '_ { node.token_at_offset(offset) - .map(move |token| self.descend_into_macros(token)) + .map(move |token| self.descend_into_macros(token, offset)) .map(|descendants| { descendants.into_iter().map(move |it| self.token_ancestors_with_macros(it)) }) @@ -956,17 +725,23 @@ impl<'db> SemanticsImpl<'db> { }) } - fn original_range(&self, node: &SyntaxNode) -> FileRange { + /// Attempts to map the node out of macro expanded files returning the original file range. + /// If upmapping is not possible, this will fall back to the range of the macro call of the + /// macro file the node resides in. + pub fn original_range(&self, node: &SyntaxNode) -> FileRange { let node = self.find_file(node); node.original_file_range(self.db.upcast()) } - fn original_range_opt(&self, node: &SyntaxNode) -> Option<FileRange> { + /// Attempts to map the node out of macro expanded files returning the original file range. + pub fn original_range_opt(&self, node: &SyntaxNode) -> Option<FileRange> { let node = self.find_file(node); node.original_file_range_opt(self.db.upcast()) } - fn original_ast_node<N: AstNode>(&self, node: N) -> Option<N> { + /// Attempts to map the node out of macro expanded files. + /// This only work for attribute expansions, as other ones do not have nodes as input. + pub fn original_ast_node<N: AstNode>(&self, node: N) -> Option<N> { self.wrap_node_infile(node).original_ast_node(self.db.upcast()).map( |InFile { file_id, value }| { self.cache(find_root(value.syntax()), file_id); @@ -975,7 +750,9 @@ impl<'db> SemanticsImpl<'db> { ) } - fn original_syntax_node(&self, node: &SyntaxNode) -> Option<SyntaxNode> { + /// Attempts to map the node out of macro expanded files. + /// This only work for attribute expansions, as other ones do not have nodes as input. + pub fn original_syntax_node(&self, node: &SyntaxNode) -> Option<SyntaxNode> { let InFile { file_id, .. } = self.find_file(node); InFile::new(file_id, node).original_syntax_node(self.db.upcast()).map( |InFile { file_id, value }| { @@ -985,7 +762,7 @@ impl<'db> SemanticsImpl<'db> { ) } - fn diagnostics_display_range(&self, src: InFile<SyntaxNodePtr>) -> FileRange { + pub fn diagnostics_display_range(&self, src: InFile<SyntaxNodePtr>) -> FileRange { let root = self.parse_or_expand(src.file_id); let node = src.map(|it| it.to_node(&root)); node.as_ref().original_file_range(self.db.upcast()) @@ -998,7 +775,8 @@ impl<'db> SemanticsImpl<'db> { token.parent().into_iter().flat_map(move |parent| self.ancestors_with_macros(parent)) } - fn ancestors_with_macros( + /// Iterates the ancestors of the given node, climbing up macro expansions while doing so. + pub fn ancestors_with_macros( &self, node: SyntaxNode, ) -> impl Iterator<Item = SyntaxNode> + Clone + '_ { @@ -1016,7 +794,7 @@ impl<'db> SemanticsImpl<'db> { .map(|it| it.value) } - fn ancestors_at_offset_with_macros( + pub fn ancestors_at_offset_with_macros( &self, node: &SyntaxNode, offset: TextSize, @@ -1026,7 +804,7 @@ impl<'db> SemanticsImpl<'db> { .kmerge_by(|node1, node2| node1.text_range().len() < node2.text_range().len()) } - fn resolve_lifetime_param(&self, lifetime: &ast::Lifetime) -> Option<LifetimeParam> { + pub fn resolve_lifetime_param(&self, lifetime: &ast::Lifetime) -> Option<LifetimeParam> { let text = lifetime.text(); let lifetime_param = lifetime.syntax().ancestors().find_map(|syn| { let gpl = ast::AnyHasGenericParams::cast(syn)?.generic_param_list()?; @@ -1037,7 +815,7 @@ impl<'db> SemanticsImpl<'db> { ToDef::to_def(self, src) } - fn resolve_label(&self, lifetime: &ast::Lifetime) -> Option<Label> { + pub fn resolve_label(&self, lifetime: &ast::Lifetime) -> Option<Label> { let text = lifetime.text(); let label = lifetime.syntax().ancestors().find_map(|syn| { let label = match_ast! { @@ -1059,7 +837,7 @@ impl<'db> SemanticsImpl<'db> { ToDef::to_def(self, src) } - fn resolve_type(&self, ty: &ast::Type) -> Option<Type> { + pub fn resolve_type(&self, ty: &ast::Type) -> Option<Type> { let analyze = self.analyze(ty.syntax())?; let ctx = LowerCtx::with_file_id(self.db.upcast(), analyze.file_id); let ty = hir_ty::TyLoweringContext::new( @@ -1071,7 +849,7 @@ impl<'db> SemanticsImpl<'db> { Some(Type::new_with_resolver(self.db, &analyze.resolver, ty)) } - fn resolve_trait(&self, path: &ast::Path) -> Option<Trait> { + pub fn resolve_trait(&self, path: &ast::Path) -> Option<Trait> { let analyze = self.analyze(path.syntax())?; let hygiene = hir_expand::hygiene::Hygiene::new(self.db.upcast(), analyze.file_id); let ctx = LowerCtx::with_hygiene(self.db.upcast(), &hygiene); @@ -1082,7 +860,7 @@ impl<'db> SemanticsImpl<'db> { } } - fn expr_adjustments(&self, expr: &ast::Expr) -> Option<Vec<Adjustment>> { + pub fn expr_adjustments(&self, expr: &ast::Expr) -> Option<Vec<Adjustment>> { let mutability = |m| match m { hir_ty::Mutability::Not => Mutability::Shared, hir_ty::Mutability::Mut => Mutability::Mut, @@ -1126,33 +904,36 @@ impl<'db> SemanticsImpl<'db> { }) } - fn type_of_expr(&self, expr: &ast::Expr) -> Option<TypeInfo> { + pub fn type_of_expr(&self, expr: &ast::Expr) -> Option<TypeInfo> { self.analyze(expr.syntax())? .type_of_expr(self.db, expr) .map(|(ty, coerced)| TypeInfo { original: ty, adjusted: coerced }) } - fn type_of_pat(&self, pat: &ast::Pat) -> Option<TypeInfo> { + pub fn type_of_pat(&self, pat: &ast::Pat) -> Option<TypeInfo> { self.analyze(pat.syntax())? .type_of_pat(self.db, pat) .map(|(ty, coerced)| TypeInfo { original: ty, adjusted: coerced }) } - fn type_of_binding_in_pat(&self, pat: &ast::IdentPat) -> Option<Type> { + /// It also includes the changes that binding mode makes in the type. For example in + /// `let ref x @ Some(_) = None` the result of `type_of_pat` is `Option<T>` but the result + /// of this function is `&mut Option<T>` + pub fn type_of_binding_in_pat(&self, pat: &ast::IdentPat) -> Option<Type> { self.analyze(pat.syntax())?.type_of_binding_in_pat(self.db, pat) } - fn type_of_self(&self, param: &ast::SelfParam) -> Option<Type> { + pub fn type_of_self(&self, param: &ast::SelfParam) -> Option<Type> { self.analyze(param.syntax())?.type_of_self(self.db, param) } - fn pattern_adjustments(&self, pat: &ast::Pat) -> SmallVec<[Type; 1]> { + pub fn pattern_adjustments(&self, pat: &ast::Pat) -> SmallVec<[Type; 1]> { self.analyze(pat.syntax()) .and_then(|it| it.pattern_adjustments(self.db, pat)) .unwrap_or_default() } - fn binding_mode_of_pat(&self, pat: &ast::IdentPat) -> Option<BindingMode> { + pub fn binding_mode_of_pat(&self, pat: &ast::IdentPat) -> Option<BindingMode> { self.analyze(pat.syntax())?.binding_mode_of_pat(self.db, pat) } @@ -1187,32 +968,32 @@ impl<'db> SemanticsImpl<'db> { self.analyze(try_expr.syntax())?.resolve_try_expr(self.db, try_expr) } - fn resolve_method_call_as_callable(&self, call: &ast::MethodCallExpr) -> Option<Callable> { + pub fn resolve_method_call_as_callable(&self, call: &ast::MethodCallExpr) -> Option<Callable> { self.analyze(call.syntax())?.resolve_method_call_as_callable(self.db, call) } - fn resolve_field(&self, field: &ast::FieldExpr) -> Option<Field> { + pub fn resolve_field(&self, field: &ast::FieldExpr) -> Option<Field> { self.analyze(field.syntax())?.resolve_field(self.db, field) } - fn resolve_record_field( + pub fn resolve_record_field( &self, field: &ast::RecordExprField, ) -> Option<(Field, Option<Local>, Type)> { self.analyze(field.syntax())?.resolve_record_field(self.db, field) } - fn resolve_record_pat_field(&self, field: &ast::RecordPatField) -> Option<(Field, Type)> { + pub fn resolve_record_pat_field(&self, field: &ast::RecordPatField) -> Option<(Field, Type)> { self.analyze(field.syntax())?.resolve_record_pat_field(self.db, field) } - fn resolve_macro_call(&self, macro_call: &ast::MacroCall) -> Option<Macro> { + pub fn resolve_macro_call(&self, macro_call: &ast::MacroCall) -> Option<Macro> { let sa = self.analyze(macro_call.syntax())?; let macro_call = self.find_file(macro_call.syntax()).with_value(macro_call); sa.resolve_macro_call(self.db, macro_call) } - fn is_unsafe_macro_call(&self, macro_call: &ast::MacroCall) -> bool { + pub fn is_unsafe_macro_call(&self, macro_call: &ast::MacroCall) -> bool { let sa = match self.analyze(macro_call.syntax()) { Some(it) => it, None => return false, @@ -1221,7 +1002,7 @@ impl<'db> SemanticsImpl<'db> { sa.is_unsafe_macro_call(self.db, macro_call) } - fn resolve_attr_macro_call(&self, item: &ast::Item) -> Option<Macro> { + pub fn resolve_attr_macro_call(&self, item: &ast::Item) -> Option<Macro> { let item_in_file = self.wrap_node_infile(item.clone()); let id = self.with_ctx(|ctx| { let macro_call_id = ctx.item_to_macro_call(item_in_file)?; @@ -1230,7 +1011,7 @@ impl<'db> SemanticsImpl<'db> { Some(Macro { id }) } - fn resolve_path(&self, path: &ast::Path) -> Option<PathResolution> { + pub fn resolve_path(&self, path: &ast::Path) -> Option<PathResolution> { self.analyze(path.syntax())?.resolve_path(self.db, path) } @@ -1238,17 +1019,17 @@ impl<'db> SemanticsImpl<'db> { self.analyze(record_lit.syntax())?.resolve_variant(self.db, record_lit) } - fn resolve_bind_pat_to_const(&self, pat: &ast::IdentPat) -> Option<ModuleDef> { + pub fn resolve_bind_pat_to_const(&self, pat: &ast::IdentPat) -> Option<ModuleDef> { self.analyze(pat.syntax())?.resolve_bind_pat_to_const(self.db, pat) } - fn record_literal_missing_fields(&self, literal: &ast::RecordExpr) -> Vec<(Field, Type)> { + pub fn record_literal_missing_fields(&self, literal: &ast::RecordExpr) -> Vec<(Field, Type)> { self.analyze(literal.syntax()) .and_then(|it| it.record_literal_missing_fields(self.db, literal)) .unwrap_or_default() } - fn record_pattern_missing_fields(&self, pattern: &ast::RecordPat) -> Vec<(Field, Type)> { + pub fn record_pattern_missing_fields(&self, pattern: &ast::RecordPat) -> Vec<(Field, Type)> { self.analyze(pattern.syntax()) .and_then(|it| it.record_pattern_missing_fields(self.db, pattern)) .unwrap_or_default() @@ -1260,7 +1041,7 @@ impl<'db> SemanticsImpl<'db> { f(&mut ctx) } - fn to_def<T: ToDef>(&self, src: &T) -> Option<T::Def> { + pub fn to_def<T: ToDef>(&self, src: &T) -> Option<T::Def> { let src = self.find_file(src.syntax()).with_value(src).cloned(); T::to_def(self, src) } @@ -1269,7 +1050,7 @@ impl<'db> SemanticsImpl<'db> { self.with_ctx(|ctx| ctx.file_to_def(file)).into_iter().map(Module::from) } - fn scope(&self, node: &SyntaxNode) -> Option<SemanticsScope<'db>> { + pub fn scope(&self, node: &SyntaxNode) -> Option<SemanticsScope<'db>> { self.analyze_no_infer(node).map(|SourceAnalyzer { file_id, resolver, .. }| SemanticsScope { db: self.db, file_id, @@ -1277,7 +1058,11 @@ impl<'db> SemanticsImpl<'db> { }) } - fn scope_at_offset(&self, node: &SyntaxNode, offset: TextSize) -> Option<SemanticsScope<'db>> { + pub fn scope_at_offset( + &self, + node: &SyntaxNode, + offset: TextSize, + ) -> Option<SemanticsScope<'db>> { self.analyze_with_offset_no_infer(node, offset).map( |SourceAnalyzer { file_id, resolver, .. }| SemanticsScope { db: self.db, @@ -1287,7 +1072,8 @@ impl<'db> SemanticsImpl<'db> { ) } - fn source<Def: HasSource>(&self, def: Def) -> Option<InFile<Def::Ast>> + /// Search for a definition's source and cache its syntax tree + pub fn source<Def: HasSource>(&self, def: Def) -> Option<InFile<Def::Ast>> where Def::Ast: AstNode, { @@ -1352,7 +1138,7 @@ impl<'db> SemanticsImpl<'db> { assert!(prev == None || prev == Some(file_id)) } - fn assert_contains_node(&self, node: &SyntaxNode) { + pub fn assert_contains_node(&self, node: &SyntaxNode) { self.find_file(node); } @@ -1388,7 +1174,7 @@ impl<'db> SemanticsImpl<'db> { InFile::new(file_id, node) } - fn is_unsafe_method_call(&self, method_call_expr: &ast::MethodCallExpr) -> bool { + pub fn is_unsafe_method_call(&self, method_call_expr: &ast::MethodCallExpr) -> bool { method_call_expr .receiver() .and_then(|expr| { @@ -1411,7 +1197,7 @@ impl<'db> SemanticsImpl<'db> { .unwrap_or(false) } - fn is_unsafe_ref_expr(&self, ref_expr: &ast::RefExpr) -> bool { + pub fn is_unsafe_ref_expr(&self, ref_expr: &ast::RefExpr) -> bool { ref_expr .expr() .and_then(|expr| { @@ -1430,7 +1216,7 @@ impl<'db> SemanticsImpl<'db> { // more than it should with the current implementation. } - fn is_unsafe_ident_pat(&self, ident_pat: &ast::IdentPat) -> bool { + pub fn is_unsafe_ident_pat(&self, ident_pat: &ast::IdentPat) -> bool { if ident_pat.ref_token().is_none() { return false; } @@ -1473,7 +1259,8 @@ impl<'db> SemanticsImpl<'db> { .unwrap_or(false) } - fn is_inside_unsafe(&self, expr: &ast::Expr) -> bool { + /// Returns `true` if the `node` is inside an `unsafe` context. + pub fn is_inside_unsafe(&self, expr: &ast::Expr) -> bool { let Some(enclosing_item) = expr.syntax().ancestors().find_map(Either::<ast::Item, ast::Variant>::cast) else { @@ -1683,6 +1470,14 @@ impl SemanticsScope<'_> { |name, id| cb(name, id.into()), ) } + + pub fn extern_crates(&self) -> impl Iterator<Item = (Name, Module)> + '_ { + self.resolver.extern_crates_in_scope().map(|(name, id)| (name, Module { id })) + } + + pub fn extern_crate_decls(&self) -> impl Iterator<Item = Name> + '_ { + self.resolver.extern_crate_decls_in_scope(self.db.upcast()) + } } #[derive(Debug)] diff --git a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs index 3499daf11..f29fb1edf 100644 --- a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs +++ b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs @@ -487,7 +487,7 @@ impl SourceAnalyzer { let path = macro_call.value.path().and_then(|ast| Path::from_src(ast, &ctx))?; self.resolver .resolve_path_as_macro(db.upcast(), path.mod_path()?, Some(MacroSubNs::Bang)) - .map(|it| it.into()) + .map(|(it, _)| it.into()) } pub(crate) fn resolve_bind_pat_to_const( @@ -760,7 +760,7 @@ impl SourceAnalyzer { let macro_call_id = macro_call.as_call_id(db.upcast(), krate, |path| { self.resolver .resolve_path_as_macro(db.upcast(), &path, Some(MacroSubNs::Bang)) - .map(|it| macro_id_to_def_id(db.upcast(), it)) + .map(|(it, _)| macro_id_to_def_id(db.upcast(), it)) })?; Some(macro_call_id.as_file()).filter(|it| it.expansion_level(db.upcast()) < 64) } @@ -966,6 +966,7 @@ pub(crate) fn resolve_hir_path_as_attr_macro( ) -> Option<Macro> { resolver .resolve_path_as_macro(db.upcast(), path.mod_path()?, Some(MacroSubNs::Attr)) + .map(|(it, _)| it) .map(Into::into) } @@ -983,7 +984,7 @@ fn resolve_hir_path_( res.map(|ty_ns| (ty_ns, path.segments().first())) } None => { - let (ty, remaining_idx) = resolver.resolve_path_in_type_ns(db.upcast(), path)?; + let (ty, remaining_idx, _) = resolver.resolve_path_in_type_ns(db.upcast(), path)?; match remaining_idx { Some(remaining_idx) => { if remaining_idx + 1 == path.segments().len() { @@ -1067,7 +1068,7 @@ fn resolve_hir_path_( let macros = || { resolver .resolve_path_as_macro(db.upcast(), path.mod_path()?, None) - .map(|def| PathResolution::Def(ModuleDef::Macro(def.into()))) + .map(|(def, _)| PathResolution::Def(ModuleDef::Macro(def.into()))) }; if prefer_value_ns { values().or_else(types) } else { types().or_else(values) } diff --git a/src/tools/rust-analyzer/crates/hir/src/symbols.rs b/src/tools/rust-analyzer/crates/hir/src/symbols.rs index 43d957412..ca7874c36 100644 --- a/src/tools/rust-analyzer/crates/hir/src/symbols.rs +++ b/src/tools/rust-analyzer/crates/hir/src/symbols.rs @@ -2,8 +2,10 @@ use base_db::FileRange; use hir_def::{ - src::HasSource, AdtId, AssocItemId, DefWithBodyId, HasModule, ImplId, Lookup, MacroId, - ModuleDefId, ModuleId, TraitId, + item_scope::ItemInNs, + src::{HasChildSource, HasSource}, + AdtId, AssocItemId, DefWithBodyId, HasModule, ImplId, Lookup, MacroId, ModuleDefId, ModuleId, + TraitId, }; use hir_expand::{HirFileId, InFile}; use hir_ty::db::HirDatabase; @@ -167,6 +169,40 @@ impl<'a> SymbolCollector<'a> { self.collect_from_impl(impl_id); } + // Record renamed imports. + // In case it imports multiple items under different namespaces we just pick one arbitrarily + // for now. + for id in scope.imports() { + let loc = id.import.lookup(self.db.upcast()); + loc.id.item_tree(self.db.upcast()); + let source = id.import.child_source(self.db.upcast()); + let Some(use_tree_src) = source.value.get(id.idx) else { continue }; + let Some(rename) = use_tree_src.rename() else { continue }; + let Some(name) = rename.name() else { continue }; + + let res = scope.fully_resolve_import(self.db.upcast(), id); + res.iter_items().for_each(|(item, _)| { + let def = match item { + ItemInNs::Types(def) | ItemInNs::Values(def) => def, + ItemInNs::Macros(def) => ModuleDefId::from(def), + } + .into(); + let dec_loc = DeclarationLocation { + hir_file_id: source.file_id, + ptr: SyntaxNodePtr::new(use_tree_src.syntax()), + name_ptr: SyntaxNodePtr::new(name.syntax()), + }; + + self.symbols.push(FileSymbol { + name: name.text().into(), + def, + container_name: self.current_container_name.clone(), + loc: dec_loc, + is_alias: false, + }); + }); + } + for const_id in scope.unnamed_consts() { self.collect_from_body(const_id); } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs index 6aca716bb..c0e5429a2 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs @@ -422,7 +422,7 @@ impl<'x, 'y, T, V, U: Default> Trait<'x, 'y, T, V, U> for () { check_assist( add_missing_default_members, r#" -struct Bar<const: N: bool> { +struct Bar<const N: usize> { bar: [i32, N] } @@ -439,7 +439,7 @@ impl<const X: usize, Y, Z> Foo<X, Z> for S<Y> { $0 }"#, r#" -struct Bar<const: N: bool> { +struct Bar<const N: usize> { bar: [i32, N] } @@ -484,6 +484,107 @@ impl<X> Foo<42, {20 + 22}, X> for () { } #[test] + fn test_const_substitution_with_defaults() { + check_assist( + add_missing_default_members, + r#" +trait Foo<T, const N: usize = 42, const M: bool = false, const P: char = 'a'> { + fn get_n(&self) -> usize { N } + fn get_m(&self) -> bool { M } + fn get_p(&self) -> char { P } + fn get_array(&self, arg: &T) -> [bool; N] { [M; N] } +} + +impl<X> Foo<X> for () { + $0 +}"#, + r#" +trait Foo<T, const N: usize = 42, const M: bool = false, const P: char = 'a'> { + fn get_n(&self) -> usize { N } + fn get_m(&self) -> bool { M } + fn get_p(&self) -> char { P } + fn get_array(&self, arg: &T) -> [bool; N] { [M; N] } +} + +impl<X> Foo<X> for () { + $0fn get_n(&self) -> usize { 42 } + + fn get_m(&self) -> bool { false } + + fn get_p(&self) -> char { 'a' } + + fn get_array(&self, arg: &X) -> [bool; 42] { [false; 42] } +}"#, + ); + } + + #[test] + fn test_const_substitution_with_defaults_2() { + check_assist( + add_missing_impl_members, + r#" +mod m { + pub const LEN: usize = 42; + pub trait Foo<const M: usize = LEN, const N: usize = M, T = [bool; N]> { + fn get_t(&self) -> T; + } +} + +impl m::Foo for () { + $0 +}"#, + r#" +mod m { + pub const LEN: usize = 42; + pub trait Foo<const M: usize = LEN, const N: usize = M, T = [bool; N]> { + fn get_t(&self) -> T; + } +} + +impl m::Foo for () { + fn get_t(&self) -> [bool; m::LEN] { + ${0:todo!()} + } +}"#, + ) + } + + #[test] + fn test_const_substitution_with_defaults_3() { + check_assist( + add_missing_default_members, + r#" +mod m { + pub const VAL: usize = 0; + + pub trait Foo<const N: usize = {40 + 2}, const M: usize = {VAL + 1}> { + fn get_n(&self) -> usize { N } + fn get_m(&self) -> usize { M } + } +} + +impl m::Foo for () { + $0 +}"#, + r#" +mod m { + pub const VAL: usize = 0; + + pub trait Foo<const N: usize = {40 + 2}, const M: usize = {VAL + 1}> { + fn get_n(&self) -> usize { N } + fn get_m(&self) -> usize { M } + } +} + +impl m::Foo for () { + $0fn get_n(&self) -> usize { {40 + 2} } + + fn get_m(&self) -> usize { {m::VAL + 1} } +}"#, + ) + } + + #[test] fn test_cursor_after_empty_impl_def() { check_assist( add_missing_impl_members, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs index 57cfa17cc..66bc2f6da 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs @@ -1,6 +1,10 @@ use std::collections::VecDeque; -use syntax::ast::{self, AstNode}; +use syntax::{ + ast::{self, AstNode, Expr::BinExpr}, + ted::{self, Position}, + SyntaxKind, +}; use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists}; @@ -23,121 +27,117 @@ use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKin // } // ``` pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { - let expr = ctx.find_node_at_offset::<ast::BinExpr>()?; - let op = expr.op_kind()?; - let op_range = expr.op_token()?.text_range(); + let mut bin_expr = ctx.find_node_at_offset::<ast::BinExpr>()?; + let op = bin_expr.op_kind()?; + let op_range = bin_expr.op_token()?.text_range(); - let opposite_op = match op { - ast::BinaryOp::LogicOp(ast::LogicOp::And) => "||", - ast::BinaryOp::LogicOp(ast::LogicOp::Or) => "&&", - _ => return None, - }; - - let cursor_in_range = op_range.contains_range(ctx.selection_trimmed()); - if !cursor_in_range { + // Is the cursor on the expression's logical operator? + if !op_range.contains_range(ctx.selection_trimmed()) { return None; } - let mut expr = expr; - // Walk up the tree while we have the same binary operator - while let Some(parent_expr) = expr.syntax().parent().and_then(ast::BinExpr::cast) { - match expr.op_kind() { + while let Some(parent_expr) = bin_expr.syntax().parent().and_then(ast::BinExpr::cast) { + match parent_expr.op_kind() { Some(parent_op) if parent_op == op => { - expr = parent_expr; + bin_expr = parent_expr; } _ => break, } } - let mut expr_stack = vec![expr.clone()]; - let mut terms = Vec::new(); - let mut op_ranges = Vec::new(); - - // Find all the children with the same binary operator - while let Some(expr) = expr_stack.pop() { - let mut traverse_bin_expr_arm = |expr| { - if let ast::Expr::BinExpr(bin_expr) = expr { - if let Some(expr_op) = bin_expr.op_kind() { - if expr_op == op { - expr_stack.push(bin_expr); - } else { - terms.push(ast::Expr::BinExpr(bin_expr)); - } + let op = bin_expr.op_kind()?; + let inv_token = match op { + ast::BinaryOp::LogicOp(ast::LogicOp::And) => SyntaxKind::PIPE2, + ast::BinaryOp::LogicOp(ast::LogicOp::Or) => SyntaxKind::AMP2, + _ => return None, + }; + + let demorganed = bin_expr.clone_subtree().clone_for_update(); + + ted::replace(demorganed.op_token()?, ast::make::token(inv_token)); + let mut exprs = VecDeque::from(vec![ + (bin_expr.lhs()?, demorganed.lhs()?), + (bin_expr.rhs()?, demorganed.rhs()?), + ]); + + while let Some((expr, dm)) = exprs.pop_front() { + if let BinExpr(bin_expr) = &expr { + if let BinExpr(cbin_expr) = &dm { + if op == bin_expr.op_kind()? { + ted::replace(cbin_expr.op_token()?, ast::make::token(inv_token)); + exprs.push_back((bin_expr.lhs()?, cbin_expr.lhs()?)); + exprs.push_back((bin_expr.rhs()?, cbin_expr.rhs()?)); } else { - terms.push(ast::Expr::BinExpr(bin_expr)); + let mut inv = invert_boolean_expression(expr); + if inv.needs_parens_in(dm.syntax().parent()?) { + inv = ast::make::expr_paren(inv).clone_for_update(); + } + ted::replace(dm.syntax(), inv.syntax()); } } else { - terms.push(expr); + return None; } - }; - - op_ranges.extend(expr.op_token().map(|t| t.text_range())); - traverse_bin_expr_arm(expr.lhs()?); - traverse_bin_expr_arm(expr.rhs()?); + } else { + let mut inv = invert_boolean_expression(dm.clone_subtree()).clone_for_update(); + if inv.needs_parens_in(dm.syntax().parent()?) { + inv = ast::make::expr_paren(inv).clone_for_update(); + } + ted::replace(dm.syntax(), inv.syntax()); + } } + let dm_lhs = demorganed.lhs()?; + acc.add( AssistId("apply_demorgan", AssistKind::RefactorRewrite), "Apply De Morgan's law", op_range, |edit| { - terms.sort_by_key(|t| t.syntax().text_range().start()); - let mut terms = VecDeque::from(terms); - - let paren_expr = expr.syntax().parent().and_then(ast::ParenExpr::cast); - + let paren_expr = bin_expr.syntax().parent().and_then(ast::ParenExpr::cast); let neg_expr = paren_expr .clone() .and_then(|paren_expr| paren_expr.syntax().parent()) .and_then(ast::PrefixExpr::cast) .and_then(|prefix_expr| { - if prefix_expr.op_kind().unwrap() == ast::UnaryOp::Not { + if prefix_expr.op_kind()? == ast::UnaryOp::Not { Some(prefix_expr) } else { None } }); - for op_range in op_ranges { - edit.replace(op_range, opposite_op); - } - if let Some(paren_expr) = paren_expr { - for term in terms { - let range = term.syntax().text_range(); - let not_term = invert_boolean_expression(term); - - edit.replace(range, not_term.syntax().text()); - } - if let Some(neg_expr) = neg_expr { cov_mark::hit!(demorgan_double_negation); - edit.replace(neg_expr.op_token().unwrap().text_range(), ""); + edit.replace_ast(ast::Expr::PrefixExpr(neg_expr), demorganed.into()); } else { cov_mark::hit!(demorgan_double_parens); - edit.replace(paren_expr.l_paren_token().unwrap().text_range(), "!("); + ted::insert_all_raw( + Position::before(dm_lhs.syntax()), + vec![ + syntax::NodeOrToken::Token(ast::make::token(SyntaxKind::BANG)), + syntax::NodeOrToken::Token(ast::make::token(SyntaxKind::L_PAREN)), + ], + ); + + ted::append_child_raw( + demorganed.syntax(), + syntax::NodeOrToken::Token(ast::make::token(SyntaxKind::R_PAREN)), + ); + + edit.replace_ast(ast::Expr::ParenExpr(paren_expr), demorganed.into()); } } else { - if let Some(lhs) = terms.pop_front() { - let lhs_range = lhs.syntax().text_range(); - let not_lhs = invert_boolean_expression(lhs); - - edit.replace(lhs_range, format!("!({not_lhs}")); - } - - if let Some(rhs) = terms.pop_back() { - let rhs_range = rhs.syntax().text_range(); - let not_rhs = invert_boolean_expression(rhs); - - edit.replace(rhs_range, format!("{not_rhs})")); - } - - for term in terms { - let term_range = term.syntax().text_range(); - let not_term = invert_boolean_expression(term); - edit.replace(term_range, not_term.to_string()); - } + ted::insert_all_raw( + Position::before(dm_lhs.syntax()), + vec![ + syntax::NodeOrToken::Token(ast::make::token(SyntaxKind::BANG)), + syntax::NodeOrToken::Token(ast::make::token(SyntaxKind::L_PAREN)), + ], + ); + ted::append_child_raw(demorganed.syntax(), ast::make::token(SyntaxKind::R_PAREN)); + edit.replace_ast(bin_expr, demorganed); } }, ) @@ -145,9 +145,8 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti #[cfg(test)] mod tests { - use crate::tests::{check_assist, check_assist_not_applicable}; - use super::*; + use crate::tests::{check_assist, check_assist_not_applicable}; #[test] fn demorgan_handles_leq() { @@ -213,7 +212,7 @@ fn f() { !(S <= S || S < S) } #[test] fn demorgan_doesnt_double_negation() { cov_mark::check!(demorgan_double_negation); - check_assist(apply_demorgan, "fn f() { !(x ||$0 x) }", "fn f() { (!x && !x) }") + check_assist(apply_demorgan, "fn f() { !(x ||$0 x) }", "fn f() { !x && !x }") } #[test] @@ -222,13 +221,38 @@ fn f() { !(S <= S || S < S) } check_assist(apply_demorgan, "fn f() { (x ||$0 x) }", "fn f() { !(!x && !x) }") } - // https://github.com/rust-lang/rust-analyzer/issues/10963 + // FIXME : This needs to go. + // // https://github.com/rust-lang/rust-analyzer/issues/10963 + // #[test] + // fn demorgan_doesnt_hang() { + // check_assist( + // apply_demorgan, + // "fn f() { 1 || 3 &&$0 4 || 5 }", + // "fn f() { !(!1 || !3 || !4) || 5 }", + // ) + // } + + #[test] + fn demorgan_keep_pars_for_op_precedence() { + check_assist( + apply_demorgan, + "fn main() { + let _ = !(!a ||$0 !(b || c)); +} +", + "fn main() { + let _ = a && (b || c); +} +", + ); + } + #[test] - fn demorgan_doesnt_hang() { + fn demorgan_removes_pars_in_eq_precedence() { check_assist( apply_demorgan, - "fn f() { 1 || 3 &&$0 4 || 5 }", - "fn f() { !(!1 || !3 || !4) || 5 }", + "fn() { let x = a && !(!b |$0| !c); }", + "fn() { let x = a && b && c; }", ) } } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/bind_unused_param.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/bind_unused_param.rs new file mode 100644 index 000000000..45c1f0cca --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/bind_unused_param.rs @@ -0,0 +1,159 @@ +use crate::assist_context::{AssistContext, Assists}; +use ide_db::{ + assists::{AssistId, AssistKind}, + defs::Definition, + LineIndexDatabase, +}; +use syntax::{ + ast::{self, edit_in_place::Indent}, + AstNode, +}; + +// Assist: bind_unused_param +// +// Binds unused function parameter to an underscore. +// +// ``` +// fn some_function(x: i32$0) {} +// ``` +// -> +// ``` +// fn some_function(x: i32) { +// let _ = x; +// } +// ``` +pub(crate) fn bind_unused_param(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let param: ast::Param = ctx.find_node_at_offset()?; + + let Some(ast::Pat::IdentPat(ident_pat)) = param.pat() else { return None }; + + let param_def = { + let local = ctx.sema.to_def(&ident_pat)?; + Definition::Local(local) + }; + if param_def.usages(&ctx.sema).at_least_one() { + cov_mark::hit!(keep_used); + return None; + } + + let func = param.syntax().ancestors().find_map(ast::Fn::cast)?; + let stmt_list = func.body()?.stmt_list()?; + let l_curly_range = stmt_list.l_curly_token()?.text_range(); + let r_curly_range = stmt_list.r_curly_token()?.text_range(); + + acc.add( + AssistId("bind_unused_param", AssistKind::QuickFix), + &format!("Bind as `let _ = {};`", &ident_pat), + param.syntax().text_range(), + |builder| { + let line_index = ctx.db().line_index(ctx.file_id()); + + let indent = func.indent_level(); + let text_indent = indent + 1; + let mut text = format!("\n{text_indent}let _ = {ident_pat};"); + + let left_line = line_index.line_col(l_curly_range.end()).line; + let right_line = line_index.line_col(r_curly_range.start()).line; + + if left_line == right_line { + cov_mark::hit!(single_line); + text.push_str(&format!("\n{indent}")); + } + + builder.insert(l_curly_range.end(), text); + }, + ) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn bind_unused_empty_block() { + cov_mark::check!(single_line); + check_assist( + bind_unused_param, + r#" +fn foo($0y: i32) {} +"#, + r#" +fn foo(y: i32) { + let _ = y; +} +"#, + ); + } + + #[test] + fn bind_unused_empty_block_with_newline() { + check_assist( + bind_unused_param, + r#" +fn foo($0y: i32) { +} +"#, + r#" +fn foo(y: i32) { + let _ = y; +} +"#, + ); + } + + #[test] + fn bind_unused_generic() { + check_assist( + bind_unused_param, + r#" +fn foo<T>($0y: T) +where T : Default { +} +"#, + r#" +fn foo<T>(y: T) +where T : Default { + let _ = y; +} +"#, + ); + } + + #[test] + fn trait_impl() { + check_assist( + bind_unused_param, + r#" +trait Trait { + fn foo(x: i32); +} +impl Trait for () { + fn foo($0x: i32) {} +} +"#, + r#" +trait Trait { + fn foo(x: i32); +} +impl Trait for () { + fn foo(x: i32) { + let _ = x; + } +} +"#, + ); + } + + #[test] + fn keep_used() { + cov_mark::check!(keep_used); + check_assist_not_applicable( + bind_unused_param, + r#" +fn foo(x: i32, $0y: i32) { y; } +"#, + ); + } +} diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs index 1af52c592..d231708c5 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs @@ -103,7 +103,6 @@ pub(crate) fn convert_if_to_bool_then(acc: &mut Assists, ctx: &AssistContext<'_> cond, ast::Expr::BinExpr(_) | ast::Expr::BlockExpr(_) - | ast::Expr::BoxExpr(_) | ast::Expr::BreakExpr(_) | ast::Expr::CastExpr(_) | ast::Expr::ClosureExpr(_) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs index fe1cb6fce..76f021ed9 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs @@ -161,9 +161,9 @@ fn process_struct_name_reference( let path_segment = name_ref.syntax().parent().and_then(ast::PathSegment::cast)?; // A `PathSegment` always belongs to a `Path`, so there's at least one `Path` at this point. let full_path = - path_segment.syntax().parent()?.ancestors().map_while(ast::Path::cast).last().unwrap(); + path_segment.syntax().parent()?.ancestors().map_while(ast::Path::cast).last()?; - if full_path.segment().unwrap().name_ref()? != *name_ref { + if full_path.segment()?.name_ref()? != *name_ref { // `name_ref` isn't the last segment of the path, so `full_path` doesn't point to the // struct we want to edit. return None; diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs index dcb96ab8a..7d0e42476 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs @@ -58,7 +58,7 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext<' return None; } - let bound_ident = pat.fields().next().unwrap(); + let bound_ident = pat.fields().next()?; if !ast::IdentPat::can_cast(bound_ident.syntax().kind()) { return None; } @@ -108,6 +108,15 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext<' then_block.syntax().last_child_or_token().filter(|t| t.kind() == T!['}'])?; + let then_block_items = then_block.dedent(IndentLevel(1)).clone_for_update(); + + let end_of_then = then_block_items.syntax().last_child_or_token()?; + let end_of_then = if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) { + end_of_then.prev_sibling_or_token()? + } else { + end_of_then + }; + let target = if_expr.syntax().text_range(); acc.add( AssistId("convert_to_guarded_return", AssistKind::RefactorRewrite), @@ -141,16 +150,6 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext<' } }; - let then_block_items = then_block.dedent(IndentLevel(1)).clone_for_update(); - - let end_of_then = then_block_items.syntax().last_child_or_token().unwrap(); - let end_of_then = - if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) { - end_of_then.prev_sibling_or_token().unwrap() - } else { - end_of_then - }; - let then_statements = replacement .children_with_tokens() .chain( diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs index 4f3b6e0c2..31a1ff496 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs @@ -15,26 +15,13 @@ use syntax::{ast, AstNode, AstToken, NodeOrToken, SyntaxKind::COMMA, TextRange}; // Move an expression out of a format string. // // ``` -// macro_rules! format_args { -// ($lit:literal $(tt:tt)*) => { 0 }, -// } -// macro_rules! print { -// ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*))); -// } -// +// # //- minicore: fmt // fn main() { // print!("{var} {x + 1}$0"); // } // ``` // -> // ``` -// macro_rules! format_args { -// ($lit:literal $(tt:tt)*) => { 0 }, -// } -// macro_rules! print { -// ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*))); -// } -// // fn main() { // print!("{var} {}"$0, x + 1); // } @@ -48,7 +35,7 @@ pub(crate) fn extract_expressions_from_format_string( let tt = fmt_string.syntax().parent().and_then(ast::TokenTree::cast)?; let expanded_t = ast::String::cast( - ctx.sema.descend_into_macros_with_kind_preference(fmt_string.syntax().clone()), + ctx.sema.descend_into_macros_with_kind_preference(fmt_string.syntax().clone(), 0.into()), )?; if !is_format_string(&expanded_t) { return None; @@ -158,37 +145,21 @@ mod tests { use super::*; use crate::tests::check_assist; - const MACRO_DECL: &'static str = r#" -macro_rules! format_args { - ($lit:literal $(tt:tt)*) => { 0 }, -} -macro_rules! print { - ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*))); -} -"#; - - fn add_macro_decl(s: &'static str) -> String { - MACRO_DECL.to_string() + s - } - #[test] fn multiple_middle_arg() { check_assist( extract_expressions_from_format_string, - &add_macro_decl( - r#" + r#" +//- minicore: fmt fn main() { print!("{} {x + 1:b} {}$0", y + 2, 2); } "#, - ), - &add_macro_decl( - r#" + r#" fn main() { print!("{} {:b} {}"$0, y + 2, x + 1, 2); } "#, - ), ); } @@ -196,20 +167,17 @@ fn main() { fn single_arg() { check_assist( extract_expressions_from_format_string, - &add_macro_decl( - r#" + r#" +//- minicore: fmt fn main() { print!("{obj.value:b}$0",); } "#, - ), - &add_macro_decl( - r#" + r#" fn main() { print!("{:b}"$0, obj.value); } "#, - ), ); } @@ -217,20 +185,17 @@ fn main() { fn multiple_middle_placeholders_arg() { check_assist( extract_expressions_from_format_string, - &add_macro_decl( - r#" + r#" +//- minicore: fmt fn main() { print!("{} {x + 1:b} {} {}$0", y + 2, 2); } "#, - ), - &add_macro_decl( - r#" + r#" fn main() { print!("{} {:b} {} {}"$0, y + 2, x + 1, 2, $1); } "#, - ), ); } @@ -238,20 +203,17 @@ fn main() { fn multiple_trailing_args() { check_assist( extract_expressions_from_format_string, - &add_macro_decl( - r#" + r#" +//- minicore: fmt fn main() { print!("{:b} {x + 1:b} {Struct(1, 2)}$0", 1); } "#, - ), - &add_macro_decl( - r#" + r#" fn main() { print!("{:b} {:b} {}"$0, 1, x + 1, Struct(1, 2)); } "#, - ), ); } @@ -259,20 +221,17 @@ fn main() { fn improper_commas() { check_assist( extract_expressions_from_format_string, - &add_macro_decl( - r#" + r#" +//- minicore: fmt fn main() { print!("{} {x + 1:b} {Struct(1, 2)}$0", 1,); } "#, - ), - &add_macro_decl( - r#" + r#" fn main() { print!("{} {:b} {}"$0, 1, x + 1, Struct(1, 2)); } "#, - ), ); } @@ -280,20 +239,17 @@ fn main() { fn nested_tt() { check_assist( extract_expressions_from_format_string, - &add_macro_decl( - r#" + r#" +//- minicore: fmt fn main() { print!("My name is {} {x$0 + x}", stringify!(Paperino)) } "#, - ), - &add_macro_decl( - r#" + r#" fn main() { print!("My name is {} {}"$0, stringify!(Paperino), x + x) } "#, - ), ); } @@ -301,22 +257,19 @@ fn main() { fn extract_only_expressions() { check_assist( extract_expressions_from_format_string, - &add_macro_decl( - r#" + r#" +//- minicore: fmt fn main() { let var = 1 + 1; print!("foobar {var} {var:?} {x$0 + x}") } "#, - ), - &add_macro_decl( - r#" + r#" fn main() { let var = 1 + 1; print!("foobar {var} {var:?} {}"$0, x + x) } "#, - ), ); } } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs index b8b781ea4..de591cfde 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs @@ -531,7 +531,7 @@ impl FunctionBody { fn extracted_from_trait_impl(&self) -> bool { match self.node().ancestors().find_map(ast::Impl::cast) { - Some(c) => return c.trait_().is_some(), + Some(c) => c.trait_().is_some(), None => false, } } @@ -750,7 +750,7 @@ impl FunctionBody { .descendants_with_tokens() .filter_map(SyntaxElement::into_token) .filter(|it| matches!(it.kind(), SyntaxKind::IDENT | T![self])) - .flat_map(|t| sema.descend_into_macros(t)) + .flat_map(|t| sema.descend_into_macros(t, 0.into())) .for_each(|t| add_name_if_local(t.parent().and_then(ast::NameRef::cast))); } } @@ -810,7 +810,7 @@ impl FunctionBody { (true, konst.body(), Some(sema.to_def(&konst)?.ty(sema.db))) }, ast::ConstParam(cp) => { - (true, cp.default_val(), Some(sema.to_def(&cp)?.ty(sema.db))) + (true, cp.default_val()?.expr(), Some(sema.to_def(&cp)?.ty(sema.db))) }, ast::ConstBlockPat(cbp) => { let expr = cbp.block_expr().map(ast::Expr::BlockExpr); @@ -1048,23 +1048,17 @@ impl GenericParent { fn generic_parents(parent: &SyntaxNode) -> Vec<GenericParent> { let mut list = Vec::new(); if let Some(parent_item) = parent.ancestors().find_map(ast::Item::cast) { - match parent_item { - ast::Item::Fn(ref fn_) => { - if let Some(parent_parent) = parent_item - .syntax() - .parent() - .and_then(|it| it.parent()) - .and_then(ast::Item::cast) - { - match parent_parent { - ast::Item::Impl(impl_) => list.push(GenericParent::Impl(impl_)), - ast::Item::Trait(trait_) => list.push(GenericParent::Trait(trait_)), - _ => (), - } + if let ast::Item::Fn(ref fn_) = parent_item { + if let Some(parent_parent) = + parent_item.syntax().parent().and_then(|it| it.parent()).and_then(ast::Item::cast) + { + match parent_parent { + ast::Item::Impl(impl_) => list.push(GenericParent::Impl(impl_)), + ast::Item::Trait(trait_) => list.push(GenericParent::Trait(trait_)), + _ => (), } - list.push(GenericParent::Fn(fn_.clone())); } - _ => (), + list.push(GenericParent::Fn(fn_.clone())); } } list @@ -1385,31 +1379,30 @@ enum FlowHandler { impl FlowHandler { fn from_ret_ty(fun: &Function, ret_ty: &FunType) -> FlowHandler { - match &fun.control_flow.kind { - None => FlowHandler::None, - Some(flow_kind) => { - let action = flow_kind.clone(); - if let FunType::Unit = ret_ty { - match flow_kind { - FlowKind::Return(None) - | FlowKind::Break(_, None) - | FlowKind::Continue(_) => FlowHandler::If { action }, - FlowKind::Return(_) | FlowKind::Break(_, _) => { - FlowHandler::IfOption { action } - } - FlowKind::Try { kind } => FlowHandler::Try { kind: kind.clone() }, - } - } else { - match flow_kind { - FlowKind::Return(None) - | FlowKind::Break(_, None) - | FlowKind::Continue(_) => FlowHandler::MatchOption { none: action }, - FlowKind::Return(_) | FlowKind::Break(_, _) => { - FlowHandler::MatchResult { err: action } - } - FlowKind::Try { kind } => FlowHandler::Try { kind: kind.clone() }, - } + if fun.contains_tail_expr { + return FlowHandler::None; + } + let Some(action) = fun.control_flow.kind.clone() else { + return FlowHandler::None; + }; + + if let FunType::Unit = ret_ty { + match action { + FlowKind::Return(None) | FlowKind::Break(_, None) | FlowKind::Continue(_) => { + FlowHandler::If { action } + } + FlowKind::Return(_) | FlowKind::Break(_, _) => FlowHandler::IfOption { action }, + FlowKind::Try { kind } => FlowHandler::Try { kind }, + } + } else { + match action { + FlowKind::Return(None) | FlowKind::Break(_, None) | FlowKind::Continue(_) => { + FlowHandler::MatchOption { none: action } + } + FlowKind::Return(_) | FlowKind::Break(_, _) => { + FlowHandler::MatchResult { err: action } } + FlowKind::Try { kind } => FlowHandler::Try { kind }, } } } @@ -1654,11 +1647,7 @@ impl Function { fn make_ret_ty(&self, ctx: &AssistContext<'_>, module: hir::Module) -> Option<ast::RetType> { let fun_ty = self.return_type(ctx); - let handler = if self.contains_tail_expr { - FlowHandler::None - } else { - FlowHandler::from_ret_ty(self, &fun_ty) - }; + let handler = FlowHandler::from_ret_ty(self, &fun_ty); let ret_ty = match &handler { FlowHandler::None => { if matches!(fun_ty, FunType::Unit) { @@ -1728,16 +1717,12 @@ fn make_body( fun: &Function, ) -> ast::BlockExpr { let ret_ty = fun.return_type(ctx); - let handler = if fun.contains_tail_expr { - FlowHandler::None - } else { - FlowHandler::from_ret_ty(fun, &ret_ty) - }; + let handler = FlowHandler::from_ret_ty(fun, &ret_ty); let block = match &fun.body { FunctionBody::Expr(expr) => { let expr = rewrite_body_segment(ctx, &fun.params, &handler, expr.syntax()); - let expr = ast::Expr::cast(expr).unwrap(); + let expr = ast::Expr::cast(expr).expect("Body segment should be an expr"); match expr { ast::Expr::BlockExpr(block) => { // If the extracted expression is itself a block, there is no need to wrap it inside another block. @@ -1877,9 +1862,8 @@ fn with_tail_expr(block: ast::BlockExpr, tail_expr: ast::Expr) -> ast::BlockExpr if let Some(stmt_list) = block.stmt_list() { stmt_list.syntax().children_with_tokens().for_each(|node_or_token| { - match &node_or_token { - syntax::NodeOrToken::Token(_) => elements.push(node_or_token), - _ => (), + if let syntax::NodeOrToken::Token(_) = &node_or_token { + elements.push(node_or_token) }; }); } @@ -1943,12 +1927,18 @@ fn fix_param_usages(ctx: &AssistContext<'_>, params: &[Param], syntax: &SyntaxNo Some(ast::Expr::RefExpr(node)) if param.kind() == ParamKind::MutRef && node.mut_token().is_some() => { - ted::replace(node.syntax(), node.expr().unwrap().syntax()); + ted::replace( + node.syntax(), + node.expr().expect("RefExpr::expr() cannot be None").syntax(), + ); } Some(ast::Expr::RefExpr(node)) if param.kind() == ParamKind::SharedRef && node.mut_token().is_none() => { - ted::replace(node.syntax(), node.expr().unwrap().syntax()); + ted::replace( + node.syntax(), + node.expr().expect("RefExpr::expr() cannot be None").syntax(), + ); } Some(_) | None => { let p = &make::expr_prefix(T![*], usage.clone()).clone_for_update(); @@ -4471,7 +4461,7 @@ async fn foo() -> Result<(), ()> { "#, r#" async fn foo() -> Result<(), ()> { - fun_name().await? + fun_name().await } async fn $0fun_name() -> Result<(), ()> { @@ -4690,7 +4680,7 @@ fn $0fun_name() { check_assist( extract_function, r#" -//- minicore: result +//- minicore: result, try fn foo() -> Result<(), i64> { $0Result::<i32, i64>::Ok(0)?; Ok(())$0 @@ -4698,7 +4688,7 @@ fn foo() -> Result<(), i64> { "#, r#" fn foo() -> Result<(), i64> { - fun_name()? + fun_name() } fn $0fun_name() -> Result<(), i64> { @@ -5754,6 +5744,34 @@ fn $0fun_name<T, V>(t: T, v: V) -> i32 where T: Into<i32> + Copy, V: Into<i32> { } #[test] + fn tail_expr_no_extra_control_flow() { + check_assist( + extract_function, + r#" +//- minicore: result +fn fallible() -> Result<(), ()> { + $0if true { + return Err(()); + } + Ok(())$0 +} +"#, + r#" +fn fallible() -> Result<(), ()> { + fun_name() +} + +fn $0fun_name() -> Result<(), ()> { + if true { + return Err(()); + } + Ok(()) +} +"#, + ); + } + + #[test] fn non_tail_expr_of_tail_expr_loop() { check_assist( extract_function, @@ -5800,12 +5818,6 @@ fn $0fun_name() -> ControlFlow<()> { extract_function, r#" //- minicore: option, try -impl<T> core::ops::Try for Option<T> { - type Output = T; - type Residual = Option<!>; -} -impl<T> core::ops::FromResidual for Option<T> {} - fn f() -> Option<()> { if true { let a = $0if true { @@ -5820,12 +5832,6 @@ fn f() -> Option<()> { } "#, r#" -impl<T> core::ops::Try for Option<T> { - type Output = T; - type Residual = Option<!>; -} -impl<T> core::ops::FromResidual for Option<T> {} - fn f() -> Option<()> { if true { let a = fun_name()?;; @@ -5852,12 +5858,6 @@ fn $0fun_name() -> Option<()> { extract_function, r#" //- minicore: option, try -impl<T> core::ops::Try for Option<T> { - type Output = T; - type Residual = Option<!>; -} -impl<T> core::ops::FromResidual for Option<T> {} - fn f() -> Option<()> { if true { $0{ @@ -5874,15 +5874,9 @@ fn f() -> Option<()> { } "#, r#" -impl<T> core::ops::Try for Option<T> { - type Output = T; - type Residual = Option<!>; -} -impl<T> core::ops::FromResidual for Option<T> {} - fn f() -> Option<()> { if true { - fun_name()? + fun_name() } else { None } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs index 31fc69562..bbac0a26e 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs @@ -95,6 +95,9 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<' let Some(impl_def) = find_struct_impl(ctx, &adt, std::slice::from_ref(&name)) else { continue; }; + + let field = make::ext::field_from_idents(["self", &field_name])?; + acc.add_group( &GroupLabel("Generate delegate methods…".to_owned()), AssistId("generate_delegate_methods", AssistKind::Generate), @@ -115,11 +118,7 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<' Some(list) => convert_param_list_to_arg_list(list), None => make::arg_list([]), }; - let tail_expr = make::expr_method_call( - make::ext::field_from_idents(["self", &field_name]).unwrap(), // This unwrap is ok because we have at least 1 arg in the list - make::name_ref(&name), - arg_list, - ); + let tail_expr = make::expr_method_call(field, make::name_ref(&name), arg_list); let ret_type = method_source.ret_type(); let is_async = method_source.async_token().is_some(); let is_const = method_source.const_token().is_some(); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_derive.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_derive.rs index 747f70f9f..53ba144ba 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_derive.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_derive.rs @@ -27,13 +27,19 @@ pub(crate) fn generate_derive(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opt let cap = ctx.config.snippet_cap?; let nominal = ctx.find_node_at_offset::<ast::Adt>()?; let target = nominal.syntax().text_range(); + let derive_attr = nominal + .attrs() + .filter_map(|x| x.as_simple_call()) + .filter(|(name, _arg)| name == "derive") + .map(|(_name, arg)| arg) + .next(); + + let delimiter = match &derive_attr { + None => None, + Some(tt) => Some(tt.right_delimiter_token()?), + }; + acc.add(AssistId("generate_derive", AssistKind::Generate), "Add `#[derive]`", target, |edit| { - let derive_attr = nominal - .attrs() - .filter_map(|x| x.as_simple_call()) - .filter(|(name, _arg)| name == "derive") - .map(|(_name, arg)| arg) - .next(); match derive_attr { None => { let derive = make::attr_outer(make::meta_token_tree( @@ -45,16 +51,23 @@ pub(crate) fn generate_derive(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opt let nominal = edit.make_mut(nominal); nominal.add_attr(derive.clone()); + let delimiter = derive + .meta() + .expect("make::attr_outer was expected to have Meta") + .token_tree() + .expect("failed to get token tree out of Meta") + .r_paren_token() + .expect("make::attr_outer was expected to have a R_PAREN"); + + edit.add_tabstop_before_token(cap, delimiter); + } + Some(_) => { + // Just move the cursor. edit.add_tabstop_before_token( cap, - derive.meta().unwrap().token_tree().unwrap().r_paren_token().unwrap(), + delimiter.expect("Right delim token could not be found."), ); } - Some(tt) => { - // Just move the cursor. - let tt = edit.make_mut(tt); - edit.add_tabstop_before_token(cap, tt.right_delimiter_token().unwrap()); - } }; }) } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/into_to_qualified_from.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/into_to_qualified_from.rs new file mode 100644 index 000000000..663df266b --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/into_to_qualified_from.rs @@ -0,0 +1,205 @@ +use hir::{AsAssocItem, HirDisplay}; +use ide_db::{ + assists::{AssistId, AssistKind}, + famous_defs::FamousDefs, +}; +use syntax::{ast, AstNode}; + +use crate::assist_context::{AssistContext, Assists}; + +// Assist: into_to_qualified_from +// +// Convert an `into` method call to a fully qualified `from` call. +// +// ``` +// //- minicore: from +// struct B; +// impl From<i32> for B { +// fn from(a: i32) -> Self { +// B +// } +// } +// +// fn main() -> () { +// let a = 3; +// let b: B = a.in$0to(); +// } +// ``` +// -> +// ``` +// struct B; +// impl From<i32> for B { +// fn from(a: i32) -> Self { +// B +// } +// } +// +// fn main() -> () { +// let a = 3; +// let b: B = B::from(a); +// } +// ``` +pub(crate) fn into_to_qualified_from(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let method_call: ast::MethodCallExpr = ctx.find_node_at_offset()?; + let nameref = method_call.name_ref()?; + let receiver = method_call.receiver()?; + let db = ctx.db(); + let sema = &ctx.sema; + let fnc = sema.resolve_method_call(&method_call)?; + let scope = sema.scope(method_call.syntax())?; + // Check if the method call refers to Into trait. + if fnc.as_assoc_item(db)?.containing_trait_impl(db)? + == FamousDefs(sema, scope.krate()).core_convert_Into()? + { + let type_call = sema.type_of_expr(&method_call.clone().into())?; + let type_call_disp = + type_call.adjusted().display_source_code(db, scope.module().into(), true).ok()?; + + acc.add( + AssistId("into_to_qualified_from", AssistKind::Generate), + "Convert `into` to fully qualified `from`", + nameref.syntax().text_range(), + |edit| { + edit.replace( + method_call.syntax().text_range(), + format!("{}::from({})", type_call_disp, receiver), + ); + }, + ); + } + + Some(()) +} + +#[cfg(test)] +mod tests { + use crate::tests::check_assist; + + use super::into_to_qualified_from; + + #[test] + fn two_types_in_same_mod() { + check_assist( + into_to_qualified_from, + r#" +//- minicore: from +struct A; +struct B; +impl From<A> for B { + fn from(a: A) -> Self { + B + } +} + +fn main() -> () { + let a: A = A; + let b: B = a.in$0to(); +}"#, + r#" +struct A; +struct B; +impl From<A> for B { + fn from(a: A) -> Self { + B + } +} + +fn main() -> () { + let a: A = A; + let b: B = B::from(a); +}"#, + ) + } + + #[test] + fn fromed_in_child_mod_imported() { + check_assist( + into_to_qualified_from, + r#" +//- minicore: from +use C::B; + +struct A; + +mod C { + use crate::A; + + pub(super) struct B; + impl From<A> for B { + fn from(a: A) -> Self { + B + } + } +} + +fn main() -> () { + let a: A = A; + let b: B = a.in$0to(); +}"#, + r#" +use C::B; + +struct A; + +mod C { + use crate::A; + + pub(super) struct B; + impl From<A> for B { + fn from(a: A) -> Self { + B + } + } +} + +fn main() -> () { + let a: A = A; + let b: B = B::from(a); +}"#, + ) + } + + #[test] + fn fromed_in_child_mod_not_imported() { + check_assist( + into_to_qualified_from, + r#" +//- minicore: from +struct A; + +mod C { + use crate::A; + + pub(super) struct B; + impl From<A> for B { + fn from(a: A) -> Self { + B + } + } +} + +fn main() -> () { + let a: A = A; + let b: C::B = a.in$0to(); +}"#, + r#" +struct A; + +mod C { + use crate::A; + + pub(super) struct B; + impl From<A> for B { + fn from(a: A) -> Self { + B + } + } +} + +fn main() -> () { + let a: A = A; + let b: C::B = C::B::from(a); +}"#, + ) + } +} diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/promote_local_to_const.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/promote_local_to_const.rs index 5cc110cf1..6ed9bd85f 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/promote_local_to_const.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/promote_local_to_const.rs @@ -76,12 +76,19 @@ pub(crate) fn promote_local_to_const(acc: &mut Assists, ctx: &AssistContext<'_>) let name = to_upper_snake_case(&name.to_string()); let usages = Definition::Local(local).usages(&ctx.sema).all(); if let Some(usages) = usages.references.get(&ctx.file_id()) { - let name = make::name_ref(&name); + let name_ref = make::name_ref(&name); for usage in usages { let Some(usage) = usage.name.as_name_ref().cloned() else { continue }; - let usage = edit.make_mut(usage); - ted::replace(usage.syntax(), name.clone_for_update().syntax()); + if let Some(record_field) = ast::RecordExprField::for_name_ref(&usage) { + let record_field = edit.make_mut(record_field); + let name_expr = + make::expr_path(make::path_from_text(&name)).clone_for_update(); + record_field.replace_expr(name_expr); + } else { + let usage = edit.make_mut(usage); + ted::replace(usage.syntax(), name_ref.clone_for_update().syntax()); + } } } @@ -120,8 +127,7 @@ fn is_body_const(sema: &Semantics<'_, RootDatabase>, expr: &ast::Expr) -> bool { is_const &= sema.resolve_method_call(&call).map(|it| it.is_const(sema.db)).unwrap_or(true) } - ast::Expr::BoxExpr(_) - | ast::Expr::ForExpr(_) + ast::Expr::ForExpr(_) | ast::Expr::ReturnExpr(_) | ast::Expr::TryExpr(_) | ast::Expr::YieldExpr(_) @@ -180,6 +186,33 @@ fn foo() { } #[test] + fn usage_in_field_shorthand() { + check_assist( + promote_local_to_const, + r" +struct Foo { + bar: usize, +} + +fn main() { + let $0bar = 0; + let foo = Foo { bar }; +} +", + r" +struct Foo { + bar: usize, +} + +fn main() { + const $0BAR: usize = 0; + let foo = Foo { bar: BAR }; +} +", + ) + } + + #[test] fn not_applicable_non_const_meth_call() { cov_mark::check!(promote_local_non_const); check_assist_not_applicable( diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs index a403d5bc6..cffa3f55c 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs @@ -39,14 +39,11 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<( let replacements = macro_calls.into_iter().filter_map(compute_dbg_replacement).collect::<Vec<_>>(); - if replacements.is_empty() { - return None; - } acc.add( AssistId("remove_dbg", AssistKind::Refactor), "Remove dbg!()", - replacements.iter().map(|&(range, _)| range).reduce(|acc, range| acc.cover(range)).unwrap(), + replacements.iter().map(|&(range, _)| range).reduce(|acc, range| acc.cover(range))?, |builder| { for (range, expr) in replacements { if let Some(expr) = expr { @@ -116,10 +113,7 @@ fn compute_dbg_replacement(macro_expr: ast::MacroExpr) -> Option<(TextRange, Opt Some(parent) => match (expr, parent) { (ast::Expr::CastExpr(_), ast::Expr::CastExpr(_)) => false, ( - ast::Expr::BoxExpr(_) - | ast::Expr::PrefixExpr(_) - | ast::Expr::RefExpr(_) - | ast::Expr::MacroExpr(_), + ast::Expr::PrefixExpr(_) | ast::Expr::RefExpr(_) | ast::Expr::MacroExpr(_), ast::Expr::AwaitExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::CastExpr(_) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_unused_imports.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_unused_imports.rs index dd4839351..5fcab8c02 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_unused_imports.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_unused_imports.rs @@ -67,7 +67,7 @@ pub(crate) fn remove_unused_imports(acc: &mut Assists, ctx: &AssistContext<'_>) // This case maps to the situation where the * token is braced. // In this case, the parent use tree's path is the one we should use to resolve the glob. match u.syntax().ancestors().skip(1).find_map(ast::UseTree::cast) { - Some(parent_u) if parent_u.path().is_some() => parent_u.path().unwrap(), + Some(parent_u) if parent_u.path().is_some() => parent_u.path()?, _ => return None, } } else { diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_return_type_in_result.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_return_type_in_result.rs index 24c338745..61e9bcdcc 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_return_type_in_result.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/wrap_return_type_in_result.rs @@ -48,6 +48,11 @@ pub(crate) fn wrap_return_type_in_result(acc: &mut Assists, ctx: &AssistContext< return None; } + let new_result_ty = + make::ext::ty_result(type_ref.clone(), make::ty_placeholder()).clone_for_update(); + let generic_args = new_result_ty.syntax().descendants().find_map(ast::GenericArgList::cast)?; + let last_genarg = generic_args.generic_args().last()?; + acc.add( AssistId("wrap_return_type_in_result", AssistKind::RefactorRewrite), "Wrap return type in Result", @@ -75,19 +80,12 @@ pub(crate) fn wrap_return_type_in_result(acc: &mut Assists, ctx: &AssistContext< ted::replace(ret_expr_arg.syntax(), ok_wrapped.syntax()); } - let new_result_ty = - make::ext::ty_result(type_ref.clone(), make::ty_placeholder()).clone_for_update(); let old_result_ty = edit.make_mut(type_ref.clone()); ted::replace(old_result_ty.syntax(), new_result_ty.syntax()); if let Some(cap) = ctx.config.snippet_cap { - let generic_args = new_result_ty - .syntax() - .descendants() - .find_map(ast::GenericArgList::cast) - .unwrap(); - edit.add_placeholder_snippet(cap, generic_args.generic_args().last().unwrap()); + edit.add_placeholder_snippet(cap, last_genarg); } }, ) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs index 2ebb5ef9b..6f973ab53 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs @@ -114,6 +114,7 @@ mod handlers { mod add_turbo_fish; mod apply_demorgan; mod auto_import; + mod bind_unused_param; mod change_visibility; mod convert_bool_then; mod convert_comment_block; @@ -211,6 +212,7 @@ mod handlers { mod unwrap_result_return_type; mod unqualify_method_call; mod wrap_return_type_in_result; + mod into_to_qualified_from; pub(crate) fn all() -> &'static [Handler] { &[ @@ -224,6 +226,7 @@ mod handlers { add_turbo_fish::add_turbo_fish, apply_demorgan::apply_demorgan, auto_import::auto_import, + bind_unused_param::bind_unused_param, change_visibility::change_visibility, convert_bool_then::convert_bool_then_to_if, convert_bool_then::convert_if_to_bool_then, @@ -274,6 +277,7 @@ mod handlers { inline_local_variable::inline_local_variable, inline_type_alias::inline_type_alias, inline_type_alias::inline_type_alias_uses, + into_to_qualified_from::into_to_qualified_from, introduce_named_generic::introduce_named_generic, introduce_named_lifetime::introduce_named_lifetime, invert_if::invert_if, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs index 6eadc3dbc..dfaa53449 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs @@ -266,6 +266,21 @@ pub mod std { pub mod collections { pub struct HashMap { } } } } #[test] +fn doctest_bind_unused_param() { + check_doc_test( + "bind_unused_param", + r#####" +fn some_function(x: i32$0) {} +"#####, + r#####" +fn some_function(x: i32) { + let _ = x; +} +"#####, + ) +} + +#[test] fn doctest_change_visibility() { check_doc_test( "change_visibility", @@ -694,25 +709,12 @@ fn doctest_extract_expressions_from_format_string() { check_doc_test( "extract_expressions_from_format_string", r#####" -macro_rules! format_args { - ($lit:literal $(tt:tt)*) => { 0 }, -} -macro_rules! print { - ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*))); -} - +//- minicore: fmt fn main() { print!("{var} {x + 1}$0"); } "#####, r#####" -macro_rules! format_args { - ($lit:literal $(tt:tt)*) => { 0 }, -} -macro_rules! print { - ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*))); -} - fn main() { print!("{var} {}"$0, x + 1); } @@ -1755,6 +1757,40 @@ fn foo() { } #[test] +fn doctest_into_to_qualified_from() { + check_doc_test( + "into_to_qualified_from", + r#####" +//- minicore: from +struct B; +impl From<i32> for B { + fn from(a: i32) -> Self { + B + } +} + +fn main() -> () { + let a = 3; + let b: B = a.in$0to(); +} +"#####, + r#####" +struct B; +impl From<i32> for B { + fn from(a: i32) -> Self { + B + } +} + +fn main() -> () { + let a = 3; + let b: B = B::from(a); +} +"#####, + ) +} + +#[test] fn doctest_introduce_named_generic() { check_doc_test( "introduce_named_generic", diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils/suggest_name.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils/suggest_name.rs index f74ebfae0..16704d598 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils/suggest_name.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils/suggest_name.rs @@ -103,7 +103,6 @@ pub(crate) fn for_variable(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) match expr { ast::Expr::RefExpr(inner) => next_expr = inner.expr(), - ast::Expr::BoxExpr(inner) => next_expr = inner.expr(), ast::Expr::AwaitExpr(inner) => next_expr = inner.expr(), // ast::Expr::BlockExpr(block) => expr = block.tail_expr(), ast::Expr::CastExpr(inner) => next_expr = inner.expr(), diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs index 480cb77b4..f60ac1501 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs @@ -20,6 +20,7 @@ pub(crate) mod r#type; pub(crate) mod use_; pub(crate) mod vis; pub(crate) mod env_vars; +pub(crate) mod extern_crate; use std::iter; @@ -703,7 +704,9 @@ pub(super) fn complete_name_ref( TypeLocation::TypeAscription(ascription) => { r#type::complete_ascribed_type(acc, ctx, path_ctx, ascription); } - TypeLocation::GenericArgList(_) + TypeLocation::GenericArg { .. } + | TypeLocation::AssocConstEq + | TypeLocation::AssocTypeEq | TypeLocation::TypeBound | TypeLocation::ImplTarget | TypeLocation::ImplTrait @@ -737,6 +740,7 @@ pub(super) fn complete_name_ref( } } } + NameRefKind::ExternCrate => extern_crate::complete_extern_crate(acc, ctx), NameRefKind::DotAccess(dot_access) => { flyimport::import_on_the_fly_dot(acc, ctx, dot_access); dot::complete_dot(acc, ctx, dot_access); diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/cfg.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/cfg.rs index 62bdb6ee6..87a286778 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/cfg.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/cfg.rs @@ -1,10 +1,8 @@ //! Completion for cfg -use std::iter; - use ide_db::SymbolKind; use itertools::Itertools; -use syntax::SyntaxKind; +use syntax::{algo, ast::Ident, AstToken, Direction, NodeOrToken, SyntaxKind}; use crate::{completions::Completions, context::CompletionContext, CompletionItem}; @@ -15,31 +13,44 @@ pub(crate) fn complete_cfg(acc: &mut Completions, ctx: &CompletionContext<'_>) { acc.add(completion.build(ctx.db)); }; - let previous = iter::successors(ctx.original_token.prev_token(), |t| { - (matches!(t.kind(), SyntaxKind::EQ) || t.kind().is_trivia()) - .then(|| t.prev_token()) - .flatten() - }) - .find(|t| matches!(t.kind(), SyntaxKind::IDENT)); - - match previous.as_ref().map(|p| p.text()) { - Some("target_arch") => KNOWN_ARCH.iter().copied().for_each(add_completion), - Some("target_env") => KNOWN_ENV.iter().copied().for_each(add_completion), - Some("target_os") => KNOWN_OS.iter().copied().for_each(add_completion), - Some("target_vendor") => KNOWN_VENDOR.iter().copied().for_each(add_completion), - Some("target_endian") => ["little", "big"].into_iter().for_each(add_completion), - Some(name) => ctx.krate.potential_cfg(ctx.db).get_cfg_values(name).cloned().for_each(|s| { - let insert_text = format!(r#""{s}""#); - let mut item = CompletionItem::new(SymbolKind::BuiltinAttr, ctx.source_range(), s); - item.insert_text(insert_text); + // FIXME: Move this into context/analysis.rs + let previous = ctx + .original_token + .prev_token() + .and_then(|it| { + if matches!(it.kind(), SyntaxKind::EQ) { + Some(it.into()) + } else { + algo::non_trivia_sibling(it.into(), Direction::Prev) + } + }) + .filter(|t| matches!(t.kind(), SyntaxKind::EQ)) + .and_then(|it| algo::non_trivia_sibling(it.prev_sibling_or_token()?, Direction::Prev)) + .map(|it| match it { + NodeOrToken::Node(_) => None, + NodeOrToken::Token(t) => Ident::cast(t), + }); + match previous { + Some(None) => (), + Some(Some(p)) => match p.text() { + "target_arch" => KNOWN_ARCH.iter().copied().for_each(add_completion), + "target_env" => KNOWN_ENV.iter().copied().for_each(add_completion), + "target_os" => KNOWN_OS.iter().copied().for_each(add_completion), + "target_vendor" => KNOWN_VENDOR.iter().copied().for_each(add_completion), + "target_endian" => ["little", "big"].into_iter().for_each(add_completion), + name => ctx.krate.potential_cfg(ctx.db).get_cfg_values(name).cloned().for_each(|s| { + let insert_text = format!(r#""{s}""#); + let mut item = CompletionItem::new(SymbolKind::BuiltinAttr, ctx.source_range(), s); + item.insert_text(insert_text); - acc.add(item.build(ctx.db)); - }), + acc.add(item.build(ctx.db)); + }), + }, None => ctx.krate.potential_cfg(ctx.db).get_cfg_keys().cloned().unique().for_each(|s| { let item = CompletionItem::new(SymbolKind::BuiltinAttr, ctx.source_range(), s); acc.add(item.build(ctx.db)); }), - }; + } } const KNOWN_ARCH: [&str; 20] = [ diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/derive.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/derive.rs index 9447bc7db..90dac1902 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/derive.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/derive.rs @@ -1,6 +1,6 @@ //! Completion for derives -use hir::{HasAttrs, ScopeDef}; -use ide_db::SymbolKind; +use hir::ScopeDef; +use ide_db::{documentation::HasDocs, SymbolKind}; use itertools::Itertools; use syntax::SmolStr; diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/lint.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/lint.rs index 6bc6f34ed..f9dec5380 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/lint.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/lint.rs @@ -1,5 +1,5 @@ //! Completion for lints -use ide_db::{generated::lints::Lint, SymbolKind}; +use ide_db::{documentation::Documentation, generated::lints::Lint, SymbolKind}; use syntax::ast; use crate::{context::CompletionContext, item::CompletionItem, Completions}; @@ -55,7 +55,7 @@ pub(super) fn complete_lint( _ => name.to_owned(), }; let mut item = CompletionItem::new(SymbolKind::Attribute, ctx.source_range(), label); - item.documentation(hir::Documentation::new(description.to_owned())); + item.documentation(Documentation::new(description.to_owned())); item.add_to(acc, ctx.db) } } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/extern_crate.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/extern_crate.rs new file mode 100644 index 000000000..f9cde4466 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/extern_crate.rs @@ -0,0 +1,71 @@ +//! Completion for extern crates + +use hir::Name; +use ide_db::{documentation::HasDocs, SymbolKind}; + +use crate::{context::CompletionContext, CompletionItem, CompletionItemKind}; + +use super::Completions; + +pub(crate) fn complete_extern_crate(acc: &mut Completions, ctx: &CompletionContext<'_>) { + let imported_extern_crates: Vec<Name> = ctx.scope.extern_crate_decls().collect(); + + for (name, module) in ctx.scope.extern_crates() { + if imported_extern_crates.contains(&name) { + continue; + } + + let mut item = CompletionItem::new( + CompletionItemKind::SymbolKind(SymbolKind::Module), + ctx.source_range(), + name.to_smol_str(), + ); + item.set_documentation(module.docs(ctx.db)); + + item.add_to(acc, ctx.db); + } +} + +#[cfg(test)] +mod test { + use crate::tests::completion_list_no_kw; + + #[test] + fn can_complete_extern_crate() { + let case = r#" +//- /lib.rs crate:other_crate_a +// nothing here +//- /other_crate_b.rs crate:other_crate_b +pub mod good_mod{} +//- /lib.rs crate:crate_c +// nothing here +//- /lib.rs crate:lib deps:other_crate_a,other_crate_b,crate_c extern-prelude:other_crate_a +extern crate oth$0 +mod other_mod {} +"#; + + let completion_list = completion_list_no_kw(case); + + assert_eq!("md other_crate_a\n".to_string(), completion_list); + } + + #[test] + fn will_not_complete_existing_import() { + let case = r#" +//- /lib.rs crate:other_crate_a +// nothing here +//- /lib.rs crate:crate_c +// nothing here +//- /lib.rs crate:other_crate_b +// +//- /lib.rs crate:lib deps:other_crate_a,other_crate_b,crate_c extern-prelude:other_crate_a,other_crate_b +extern crate other_crate_b; +extern crate oth$0 +mod other_mod {} +"#; + + let completion_list = completion_list_no_kw(case); + + assert_eq!("md other_crate_a\n".to_string(), completion_list); + } +} diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/format_string.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/format_string.rs index 8e904fd60..cecbe7539 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/format_string.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/format_string.rs @@ -51,9 +51,7 @@ mod tests { fn works_when_wrapped() { check( r#" -macro_rules! format_args { - ($lit:literal $(tt:tt)*) => { 0 }, -} +//- minicore: fmt macro_rules! print { ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*))); } @@ -70,9 +68,7 @@ fn main() { fn no_completion_without_brace() { check( r#" -macro_rules! format_args { - ($lit:literal $(tt:tt)*) => { 0 }, -} +//- minicore: fmt fn main() { let foobar = 1; format_args!("f$0"); @@ -87,18 +83,13 @@ fn main() { check_edit( "foobar", r#" -macro_rules! format_args { - ($lit:literal $(tt:tt)*) => { 0 }, -} +//- minicore: fmt fn main() { let foobar = 1; format_args!("{f$0"); } "#, r#" -macro_rules! format_args { - ($lit:literal $(tt:tt)*) => { 0 }, -} fn main() { let foobar = 1; format_args!("{foobar"); @@ -108,18 +99,13 @@ fn main() { check_edit( "foobar", r#" -macro_rules! format_args { - ($lit:literal $(tt:tt)*) => { 0 }, -} +//- minicore: fmt fn main() { let foobar = 1; format_args!("{$0"); } "#, r#" -macro_rules! format_args { - ($lit:literal $(tt:tt)*) => { 0 }, -} fn main() { let foobar = 1; format_args!("{foobar"); diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs index 269e40e6e..42dfbfc7d 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs @@ -33,8 +33,8 @@ use hir::{self, HasAttrs}; use ide_db::{ - path_transform::PathTransform, syntax_helpers::insert_whitespace_into_node, - traits::get_missing_assoc_items, SymbolKind, + documentation::HasDocs, path_transform::PathTransform, + syntax_helpers::insert_whitespace_into_node, traits::get_missing_assoc_items, SymbolKind, }; use syntax::{ ast::{self, edit_in_place::AttrsOwnerEdit, HasTypeBounds}, 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 2ffe12337..fc21bba45 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 @@ -2,8 +2,12 @@ mod format_like; -use hir::{Documentation, HasAttrs}; -use ide_db::{imports::insert_use::ImportScope, ty_filter::TryEnum, SnippetCap}; +use ide_db::{ + documentation::{Documentation, HasDocs}, + imports::insert_use::ImportScope, + ty_filter::TryEnum, + SnippetCap, +}; use syntax::{ ast::{self, make, AstNode, AstToken}, SyntaxKind::{BLOCK_EXPR, EXPR_STMT, FOR_EXPR, IF_EXPR, LOOP_EXPR, STMT_LIST, WHILE_EXPR}, diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/snippet.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/snippet.rs index e9831a5b2..3ff68b978 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/snippet.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/snippet.rs @@ -1,7 +1,6 @@ //! This file provides snippet completions, like `pd` => `eprintln!(...)`. -use hir::Documentation; -use ide_db::{imports::insert_use::ImportScope, SnippetCap}; +use ide_db::{documentation::Documentation, imports::insert_use::ImportScope, SnippetCap}; use crate::{ context::{ExprCtx, ItemListKind, PathCompletionCtx, Qualified}, diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/type.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/type.rs index e47054756..a30fd13b1 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/type.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/type.rs @@ -1,7 +1,7 @@ //! Completion of names from the current scope in type position. use hir::{HirDisplay, ScopeDef}; -use syntax::{ast, AstNode, SyntaxKind}; +use syntax::{ast, AstNode}; use crate::{ context::{PathCompletionCtx, Qualified, TypeAscriptionTarget, TypeLocation}, @@ -20,16 +20,15 @@ pub(crate) fn complete_type_path( let scope_def_applicable = |def| { use hir::{GenericParam::*, ModuleDef::*}; match def { - ScopeDef::GenericParam(LifetimeParam(_)) | ScopeDef::Label(_) => false, + ScopeDef::GenericParam(LifetimeParam(_)) => location.complete_lifetimes(), + ScopeDef::Label(_) => false, // no values in type places ScopeDef::ModuleDef(Function(_) | Variant(_) | Static(_)) | ScopeDef::Local(_) => false, // unless its a constant in a generic arg list position ScopeDef::ModuleDef(Const(_)) | ScopeDef::GenericParam(ConstParam(_)) => { - matches!(location, TypeLocation::GenericArgList(_)) - } - ScopeDef::ImplSelfType(_) => { - !matches!(location, TypeLocation::ImplTarget | TypeLocation::ImplTrait) + location.complete_consts() } + ScopeDef::ImplSelfType(_) => location.complete_self_type(), // Don't suggest attribute macros and derives. ScopeDef::ModuleDef(Macro(mac)) => mac.is_fn_like(ctx.db), // Type things are fine @@ -38,12 +37,12 @@ pub(crate) fn complete_type_path( ) | ScopeDef::AdtSelfType(_) | ScopeDef::Unknown - | ScopeDef::GenericParam(TypeParam(_)) => true, + | ScopeDef::GenericParam(TypeParam(_)) => location.complete_types(), } }; let add_assoc_item = |acc: &mut Completions, item| match item { - hir::AssocItem::Const(ct) if matches!(location, TypeLocation::GenericArgList(_)) => { + hir::AssocItem::Const(ct) if matches!(location, TypeLocation::GenericArg { .. }) => { acc.add_const(ctx, ct) } hir::AssocItem::Function(_) | hir::AssocItem::Const(_) => (), @@ -157,56 +156,30 @@ pub(crate) fn complete_type_path( }); return; } - TypeLocation::GenericArgList(Some(arg_list)) => { - let in_assoc_type_arg = ctx - .original_token - .parent_ancestors() - .any(|node| node.kind() == SyntaxKind::ASSOC_TYPE_ARG); - - if !in_assoc_type_arg { - if let Some(path_seg) = - arg_list.syntax().parent().and_then(ast::PathSegment::cast) - { - if path_seg - .syntax() - .ancestors() - .find_map(ast::TypeBound::cast) - .is_some() - { - if let Some(hir::PathResolution::Def(hir::ModuleDef::Trait( - trait_, - ))) = ctx.sema.resolve_path(&path_seg.parent_path()) - { - let arg_idx = arg_list - .generic_args() - .filter(|arg| { - arg.syntax().text_range().end() - < ctx.original_token.text_range().start() - }) - .count(); - - let n_required_params = - trait_.type_or_const_param_count(ctx.sema.db, true); - if arg_idx >= n_required_params { - trait_ - .items_with_supertraits(ctx.sema.db) - .into_iter() - .for_each(|it| { - if let hir::AssocItem::TypeAlias(alias) = it { - cov_mark::hit!( - complete_assoc_type_in_generics_list - ); - acc.add_type_alias_with_eq(ctx, alias); - } - }); - - let n_params = - trait_.type_or_const_param_count(ctx.sema.db, false); - if arg_idx >= n_params { - return; // only show assoc types - } - } + TypeLocation::GenericArg { + args: Some(arg_list), of_trait: Some(trait_), .. + } => { + if arg_list.syntax().ancestors().find_map(ast::TypeBound::cast).is_some() { + let arg_idx = arg_list + .generic_args() + .filter(|arg| { + arg.syntax().text_range().end() + < ctx.original_token.text_range().start() + }) + .count(); + + let n_required_params = trait_.type_or_const_param_count(ctx.sema.db, true); + if arg_idx >= n_required_params { + trait_.items_with_supertraits(ctx.sema.db).into_iter().for_each(|it| { + if let hir::AssocItem::TypeAlias(alias) = it { + cov_mark::hit!(complete_assoc_type_in_generics_list); + acc.add_type_alias_with_eq(ctx, alias); } + }); + + let n_params = trait_.type_or_const_param_count(ctx.sema.db, false); + if arg_idx >= n_params { + return; // only show assoc types } } } 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 3cb65b272..0da7ba6d0 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs @@ -155,13 +155,63 @@ pub(crate) struct ExprCtx { pub(crate) enum TypeLocation { TupleField, TypeAscription(TypeAscriptionTarget), - GenericArgList(Option<ast::GenericArgList>), + /// Generic argument position e.g. `Foo<$0>` + GenericArg { + /// The generic argument list containing the generic arg + args: Option<ast::GenericArgList>, + /// `Some(trait_)` if `trait_` is being instantiated with `args` + of_trait: Option<hir::Trait>, + /// The generic parameter being filled in by the generic arg + corresponding_param: Option<ast::GenericParam>, + }, + /// Associated type equality constraint e.g. `Foo<Bar = $0>` + AssocTypeEq, + /// Associated constant equality constraint e.g. `Foo<X = $0>` + AssocConstEq, TypeBound, ImplTarget, ImplTrait, Other, } +impl TypeLocation { + pub(crate) fn complete_lifetimes(&self) -> bool { + matches!( + self, + TypeLocation::GenericArg { + corresponding_param: Some(ast::GenericParam::LifetimeParam(_)), + .. + } + ) + } + + pub(crate) fn complete_consts(&self) -> bool { + match self { + TypeLocation::GenericArg { + corresponding_param: Some(ast::GenericParam::ConstParam(_)), + .. + } => true, + TypeLocation::AssocConstEq => true, + _ => false, + } + } + + pub(crate) fn complete_types(&self) -> bool { + match self { + TypeLocation::GenericArg { corresponding_param: Some(param), .. } => { + matches!(param, ast::GenericParam::TypeParam(_)) + } + TypeLocation::AssocConstEq => false, + TypeLocation::AssocTypeEq => true, + _ => true, + } + } + + pub(crate) fn complete_self_type(&self) -> bool { + self.complete_types() && !matches!(self, TypeLocation::ImplTarget | TypeLocation::ImplTrait) + } +} + #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum TypeAscriptionTarget { Let(Option<ast::Pat>), @@ -301,6 +351,7 @@ pub(super) enum NameRefKind { expr: ast::RecordExpr, }, Pattern(PatternContext), + ExternCrate, } /// The identifier we are currently completing. diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs index 3ea506590..1e6b2f319 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs @@ -1,11 +1,11 @@ //! Module responsible for analyzing the code surrounding the cursor for completion. use std::iter; -use hir::{Semantics, Type, TypeInfo, Variant}; +use hir::{HasSource, Semantics, Type, TypeInfo, Variant}; use ide_db::{active_parameter::ActiveParameter, RootDatabase}; use syntax::{ algo::{find_node_at_offset, non_trivia_sibling}, - ast::{self, AttrKind, HasArgList, HasLoopBody, HasName, NameOrNameRef}, + ast::{self, AttrKind, HasArgList, HasGenericParams, HasLoopBody, HasName, NameOrNameRef}, match_ast, AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize, T, }; @@ -624,6 +624,10 @@ fn classify_name_ref( }); return Some(make_res(kind)); }, + ast::ExternCrate(_) => { + let kind = NameRefKind::ExternCrate; + return Some(make_res(kind)); + }, ast::MethodCallExpr(method) => { let receiver = find_opt_node_in_file(original_file, method.receiver()); let kind = NameRefKind::DotAccess(DotAccess { @@ -719,6 +723,136 @@ fn classify_name_ref( None }; + let generic_arg_location = |arg: ast::GenericArg| { + let mut override_location = None; + let location = find_opt_node_in_file_compensated( + sema, + original_file, + arg.syntax().parent().and_then(ast::GenericArgList::cast), + ) + .map(|args| { + let mut in_trait = None; + let param = (|| { + let parent = args.syntax().parent()?; + let params = match_ast! { + match parent { + ast::PathSegment(segment) => { + match sema.resolve_path(&segment.parent_path().top_path())? { + hir::PathResolution::Def(def) => match def { + hir::ModuleDef::Function(func) => { + func.source(sema.db)?.value.generic_param_list() + } + hir::ModuleDef::Adt(adt) => { + adt.source(sema.db)?.value.generic_param_list() + } + hir::ModuleDef::Variant(variant) => { + variant.parent_enum(sema.db).source(sema.db)?.value.generic_param_list() + } + hir::ModuleDef::Trait(trait_) => { + if let ast::GenericArg::AssocTypeArg(arg) = &arg { + let arg_name = arg.name_ref()?; + let arg_name = arg_name.text(); + for item in trait_.items_with_supertraits(sema.db) { + match item { + hir::AssocItem::TypeAlias(assoc_ty) => { + if assoc_ty.name(sema.db).as_str()? == arg_name { + override_location = Some(TypeLocation::AssocTypeEq); + return None; + } + }, + hir::AssocItem::Const(const_) => { + if const_.name(sema.db)?.as_str()? == arg_name { + override_location = Some(TypeLocation::AssocConstEq); + return None; + } + }, + _ => (), + } + } + return None; + } else { + in_trait = Some(trait_); + trait_.source(sema.db)?.value.generic_param_list() + } + } + hir::ModuleDef::TraitAlias(trait_) => { + trait_.source(sema.db)?.value.generic_param_list() + } + hir::ModuleDef::TypeAlias(ty_) => { + ty_.source(sema.db)?.value.generic_param_list() + } + _ => None, + }, + _ => None, + } + }, + ast::MethodCallExpr(call) => { + let func = sema.resolve_method_call(&call)?; + func.source(sema.db)?.value.generic_param_list() + }, + ast::AssocTypeArg(arg) => { + let trait_ = ast::PathSegment::cast(arg.syntax().parent()?.parent()?)?; + match sema.resolve_path(&trait_.parent_path().top_path())? { + hir::PathResolution::Def(def) => match def { + hir::ModuleDef::Trait(trait_) => { + let arg_name = arg.name_ref()?; + let arg_name = arg_name.text(); + let trait_items = trait_.items_with_supertraits(sema.db); + let assoc_ty = trait_items.iter().find_map(|item| match item { + hir::AssocItem::TypeAlias(assoc_ty) => { + (assoc_ty.name(sema.db).as_str()? == arg_name) + .then_some(assoc_ty) + }, + _ => None, + })?; + assoc_ty.source(sema.db)?.value.generic_param_list() + } + _ => None, + }, + _ => None, + } + }, + _ => None, + } + }?; + // Determine the index of the argument in the `GenericArgList` and match it with + // the corresponding parameter in the `GenericParamList`. Since lifetime parameters + // are often omitted, ignore them for the purposes of matching the argument with + // its parameter unless a lifetime argument is provided explicitly. That is, for + // `struct S<'a, 'b, T>`, match `S::<$0>` to `T` and `S::<'a, $0, _>` to `'b`. + // FIXME: This operates on the syntax tree and will produce incorrect results when + // generic parameters are disabled by `#[cfg]` directives. It should operate on the + // HIR, but the functionality necessary to do so is not exposed at the moment. + let mut explicit_lifetime_arg = false; + let arg_idx = arg + .syntax() + .siblings(Direction::Prev) + // Skip the node itself + .skip(1) + .map(|arg| if ast::LifetimeArg::can_cast(arg.kind()) { explicit_lifetime_arg = true }) + .count(); + let param_idx = if explicit_lifetime_arg { + arg_idx + } else { + // Lifetimes parameters always precede type and generic parameters, + // so offset the argument index by the total number of lifetime params + arg_idx + params.lifetime_params().count() + }; + params.generic_params().nth(param_idx) + })(); + (args, in_trait, param) + }); + let (arg_list, of_trait, corresponding_param) = match location { + Some((arg_list, of_trait, param)) => (Some(arg_list), of_trait, param), + _ => (None, None, None), + }; + override_location.unwrap_or(TypeLocation::GenericArg { + args: arg_list, + of_trait, + corresponding_param, + }) + }; + let type_location = |node: &SyntaxNode| { let parent = node.parent()?; let res = match_ast! { @@ -774,9 +908,12 @@ fn classify_name_ref( ast::TypeBound(_) => TypeLocation::TypeBound, // is this case needed? ast::TypeBoundList(_) => TypeLocation::TypeBound, - ast::GenericArg(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(sema, original_file, it.syntax().parent().and_then(ast::GenericArgList::cast))), + ast::GenericArg(it) => generic_arg_location(it), // is this case needed? - ast::GenericArgList(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(sema, original_file, Some(it))), + ast::GenericArgList(it) => { + let args = find_opt_node_in_file_compensated(sema, original_file, Some(it)); + TypeLocation::GenericArg { args, of_trait: None, corresponding_param: None } + }, ast::TupleField(_) => TypeLocation::TupleField, _ => return None, } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/item.rs b/src/tools/rust-analyzer/crates/ide-completion/src/item.rs index 0309952c2..c45cc8d7b 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/item.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/item.rs @@ -2,8 +2,11 @@ use std::fmt; -use hir::{Documentation, Mutability}; -use ide_db::{imports::import_assets::LocatedImport, RootDatabase, SnippetCap, SymbolKind}; +use hir::Mutability; +use ide_db::{ + documentation::Documentation, imports::import_assets::LocatedImport, RootDatabase, SnippetCap, + SymbolKind, +}; use itertools::Itertools; use smallvec::SmallVec; use stdx::{impl_from, never}; diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render.rs index 1953eb479..dfe8fe7e2 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/render.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/render.rs @@ -12,7 +12,10 @@ pub(crate) mod literal; use hir::{AsAssocItem, HasAttrs, HirDisplay, ScopeDef}; use ide_db::{ - helpers::item_name, imports::import_assets::LocatedImport, RootDatabase, SnippetCap, SymbolKind, + documentation::{Documentation, HasDocs}, + helpers::item_name, + imports::import_assets::LocatedImport, + RootDatabase, SnippetCap, SymbolKind, }; use syntax::{AstNode, SmolStr, SyntaxKind, TextRange}; @@ -114,7 +117,7 @@ impl<'a> RenderContext<'a> { } // FIXME: remove this - fn docs(&self, def: impl HasAttrs) -> Option<hir::Documentation> { + fn docs(&self, def: impl HasDocs) -> Option<Documentation> { def.docs(self.db()) } } @@ -409,7 +412,7 @@ fn res_to_kind(resolution: ScopeDef) -> CompletionItemKind { } } -fn scope_def_docs(db: &RootDatabase, resolution: ScopeDef) -> Option<hir::Documentation> { +fn scope_def_docs(db: &RootDatabase, resolution: ScopeDef) -> Option<Documentation> { use hir::ModuleDef::*; match resolution { ScopeDef::ModuleDef(Module(it)) => it.docs(db), diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/literal.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/literal.rs index 728d236df..b218502f7 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/render/literal.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/literal.rs @@ -1,7 +1,10 @@ //! Renderer for `enum` variants. -use hir::{db::HirDatabase, Documentation, HasAttrs, StructKind}; -use ide_db::SymbolKind; +use hir::{db::HirDatabase, StructKind}; +use ide_db::{ + documentation::{Documentation, HasDocs}, + SymbolKind, +}; use crate::{ context::{CompletionContext, PathCompletionCtx, PathKind}, diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/macro_.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/macro_.rs index ce7af1d34..68d175c2b 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/render/macro_.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/macro_.rs @@ -1,7 +1,7 @@ //! Renderer for macro invocations. -use hir::{Documentation, HirDisplay}; -use ide_db::SymbolKind; +use hir::HirDisplay; +use ide_db::{documentation::Documentation, SymbolKind}; use syntax::SmolStr; use crate::{ diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/pattern.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/pattern.rs index d06abc5e9..6f998119b 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/render/pattern.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/pattern.rs @@ -1,7 +1,7 @@ //! Renderer for patterns. -use hir::{db::HirDatabase, HasAttrs, Name, StructKind}; -use ide_db::SnippetCap; +use hir::{db::HirDatabase, Name, StructKind}; +use ide_db::{documentation::HasDocs, SnippetCap}; use itertools::Itertools; use syntax::SmolStr; @@ -103,7 +103,7 @@ fn build_completion( label: SmolStr, lookup: SmolStr, pat: String, - def: impl HasAttrs + Copy, + def: impl HasDocs + Copy, adt_ty: hir::Type, // Missing in context of match statement completions is_variant_missing: bool, 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 1aaf39587..d8c134c53 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 @@ -67,11 +67,6 @@ struct Foo; } #[test] -fn inside_nested_attr() { - check(r#"#[cfg($0)]"#, expect![[]]) -} - -#[test] fn with_existing_attr() { check( r#"#[no_mangle] #[$0] mcall!();"#, @@ -636,6 +631,32 @@ mod cfg { use super::*; #[test] + fn inside_cfg() { + check( + r#" +//- /main.rs cfg:test,dbg=false,opt_level=2 +#[cfg($0)] +"#, + expect![[r#" + ba dbg + ba opt_level + ba test + "#]], + ); + check( + r#" +//- /main.rs cfg:test,dbg=false,opt_level=2 +#[cfg(b$0)] +"#, + expect![[r#" + ba dbg + ba opt_level + ba test + "#]], + ); + } + + #[test] fn cfg_target_endian() { check( r#"#[cfg(target_endian = $0"#, @@ -644,6 +665,13 @@ mod cfg { ba little "#]], ); + check( + r#"#[cfg(target_endian = b$0"#, + expect![[r#" + ba big + ba little + "#]], + ); } } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs index 8c038c0fb..4cdfd546f 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs @@ -1286,3 +1286,57 @@ macro_rules! println { expect![""], ) } + +#[test] +fn no_completions_for_external_doc_hidden_in_path() { + check( + r#" +//- /main.rs crate:main deps:dep +fn main() { + Span$0 +} +//- /lib.rs crate:dep +#[doc(hidden)] +pub mod bridge { + pub mod server { + pub trait Span + } +} +pub mod bridge2 { + #[doc(hidden)] + pub mod server2 { + pub trait Span + } +} +"#, + expect![""], + ); + // unless re-exported + check( + r#" +//- /main.rs crate:main deps:dep +fn main() { + Span$0 +} +//- /lib.rs crate:dep +#[doc(hidden)] +pub mod bridge { + pub mod server { + pub trait Span + } +} +pub use bridge::server::Span; +pub mod bridge2 { + #[doc(hidden)] + pub mod server2 { + pub trait Span2 + } +} +pub use bridge2::server2::Span2; +"#, + expect![[r#" + tt Span (use dep::Span) + tt Span2 (use dep::Span2) + "#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/type_pos.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/type_pos.rs index 8cb1ff4a1..d518dd764 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/type_pos.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/type_pos.rs @@ -384,10 +384,8 @@ trait Trait2<T>: Trait1 { fn foo<'lt, T: Trait2<$0>, const CONST_PARAM: usize>(_: T) {} "#, expect![[r#" - ct CONST - cp CONST_PARAM en Enum - ma makro!(…) macro_rules! makro + ma makro!(…) macro_rules! makro md module st Record st Tuple @@ -404,14 +402,13 @@ fn foo<'lt, T: Trait2<$0>, const CONST_PARAM: usize>(_: T) {} ); check( r#" -trait Trait2 { +trait Trait2<T> { type Foo; } fn foo<'lt, T: Trait2<self::$0>, const CONST_PARAM: usize>(_: T) {} "#, expect![[r#" - ct CONST en Enum ma makro!(…) macro_rules! makro md module @@ -437,7 +434,6 @@ trait Tr<T> { impl Tr<$0 "#, expect![[r#" - ct CONST en Enum ma makro!(…) macro_rules! makro md module @@ -485,7 +481,6 @@ trait MyTrait<T, U> { fn f(t: impl MyTrait<u$0 "#, expect![[r#" - ct CONST en Enum ma makro!(…) macro_rules! makro md module @@ -511,7 +506,6 @@ trait MyTrait<T, U> { fn f(t: impl MyTrait<u8, u$0 "#, expect![[r#" - ct CONST en Enum ma makro!(…) macro_rules! makro md module @@ -555,7 +549,6 @@ trait MyTrait<T, U = u8> { fn f(t: impl MyTrait<u$0 "#, expect![[r#" - ct CONST en Enum ma makro!(…) macro_rules! makro md module @@ -581,7 +574,6 @@ trait MyTrait<T, U = u8> { fn f(t: impl MyTrait<u8, u$0 "#, expect![[r#" - ct CONST en Enum ma makro!(…) macro_rules! makro md module @@ -627,7 +619,6 @@ trait MyTrait { fn f(t: impl MyTrait<Item1 = $0 "#, expect![[r#" - ct CONST en Enum ma makro!(…) macro_rules! makro md module @@ -653,7 +644,6 @@ trait MyTrait { fn f(t: impl MyTrait<Item1 = u8, Item2 = $0 "#, expect![[r#" - ct CONST en Enum ma makro!(…) macro_rules! makro md module @@ -668,6 +658,22 @@ fn f(t: impl MyTrait<Item1 = u8, Item2 = $0 kw self:: "#]], ); + + check( + r#" +trait MyTrait { + const C: usize; +}; + +fn f(t: impl MyTrait<C = $0 +"#, + expect![[r#" + ct CONST + ma makro!(…) macro_rules! makro + kw crate:: + kw self:: + "#]], + ); } #[test] @@ -719,3 +725,267 @@ pub struct S; "#]], ) } + +#[test] +fn completes_const_and_type_generics_separately() { + // Function generic params + check( + r#" + struct Foo; + const X: usize = 0; + fn foo<T, const N: usize>() {} + fn main() { + foo::<F$0, _>(); + } + "#, + expect![[r#" + en Enum + ma makro!(…) macro_rules! makro + md module + st Foo + st Record + st Tuple + st Unit + tt Trait + un Union + bt u32 + kw crate:: + kw self:: + "#]], + ); + // FIXME: This should probably also suggest completions for types, at least those that have + // associated constants usable in this position. For example, a user could be typing + // `foo::<_, { usize::MAX }>()`, but we currently don't suggest `usize` in constant position. + check( + r#" + struct Foo; + const X: usize = 0; + fn foo<T, const N: usize>() {} + fn main() { + foo::<_, $0>(); + } + "#, + expect![[r#" + ct CONST + ct X + ma makro!(…) macro_rules! makro + kw crate:: + kw self:: + "#]], + ); + + // Method generic params + check( + r#" + const X: usize = 0; + struct Foo; + impl Foo { fn bar<const N: usize, T>(self) {} } + fn main() { + Foo.bar::<_, $0>(); + } + "#, + expect![[r#" + en Enum + ma makro!(…) macro_rules! makro + md module + st Foo + st Record + st Tuple + st Unit + tt Trait + un Union + bt u32 + kw crate:: + kw self:: + "#]], + ); + check( + r#" + const X: usize = 0; + struct Foo; + impl Foo { fn bar<const N: usize, T>(self) {} } + fn main() { + Foo.bar::<X$0, _>(); + } + "#, + expect![[r#" + ct CONST + ct X + ma makro!(…) macro_rules! makro + kw crate:: + kw self:: + "#]], + ); + + // Associated type generic params + check( + r#" + const X: usize = 0; + struct Foo; + trait Bar { + type Baz<T, const X: usize>; + } + fn foo(_: impl Bar<Baz<F$0, 0> = ()>) {} + "#, + expect![[r#" + en Enum + ma makro!(…) macro_rules! makro + md module + st Foo + st Record + st Tuple + st Unit + tt Bar + tt Trait + un Union + bt u32 + kw crate:: + kw self:: + "#]], + ); + check( + r#" + const X: usize = 0; + struct Foo; + trait Bar { + type Baz<T, const X: usize>; + } + fn foo<T: Bar<Baz<(), $0> = ()>>() {} + "#, + expect![[r#" + ct CONST + ct X + ma makro!(…) macro_rules! makro + kw crate:: + kw self:: + "#]], + ); + + // Type generic params + check( + r#" + const X: usize = 0; + struct Foo<T, const N: usize>(T); + fn main() { + let _: Foo::<_, $0> = Foo(()); + } + "#, + expect![[r#" + ct CONST + ct X + ma makro!(…) macro_rules! makro + kw crate:: + kw self:: + "#]], + ); + + // Type alias generic params + check( + r#" + const X: usize = 0; + struct Foo<T, const N: usize>(T); + type Bar<const X: usize, U> = Foo<U, X>; + fn main() { + let _: Bar::<X$0, _> = Bar(()); + } + "#, + expect![[r#" + ct CONST + ct X + ma makro!(…) macro_rules! makro + kw crate:: + kw self:: + "#]], + ); + + // Enum variant params + check( + r#" + const X: usize = 0; + enum Foo<T, const N: usize> { A(T), B } + fn main() { + Foo::B::<(), $0>; + } + "#, + expect![[r#" + ct CONST + ct X + ma makro!(…) macro_rules! makro + kw crate:: + kw self:: + "#]], + ); + + // Trait params + check( + r#" + const X: usize = 0; + trait Foo<T, const N: usize> {} + impl Foo<(), $0> for () {} + "#, + expect![[r#" + ct CONST + ct X + ma makro!(…) macro_rules! makro + kw crate:: + kw self:: + "#]], + ); + + // Trait alias params + check( + r#" + #![feature(trait_alias)] + const X: usize = 0; + trait Foo<T, const N: usize> {} + trait Bar<const M: usize, U> = Foo<U, M>; + fn foo<T: Bar<X$0, ()>>() {} + "#, + expect![[r#" + ct CONST + ct X + ma makro!(…) macro_rules! makro + kw crate:: + kw self:: + "#]], + ); + + // Omitted lifetime params + check( + r#" +struct S<'a, 'b, const C: usize, T>(core::marker::PhantomData<&'a &'b T>); +fn foo<'a>() { S::<F$0, _>; } + "#, + expect![[r#" + ct CONST + ma makro!(…) macro_rules! makro + kw crate:: + kw self:: + "#]], + ); + // Explicit lifetime params + check( + r#" +struct S<'a, 'b, const C: usize, T>(core::marker::PhantomData<&'a &'b T>); +fn foo<'a>() { S::<'static, 'static, F$0, _>; } + "#, + expect![[r#" + ct CONST + ma makro!(…) macro_rules! makro + kw crate:: + kw self:: + "#]], + ); + check( + r#" +struct S<'a, 'b, const C: usize, T>(core::marker::PhantomData<&'a &'b T>); +fn foo<'a>() { S::<'static, F$0, _, _>; } + "#, + expect![[r#" + lt 'a + ma makro!(…) macro_rules! makro + kw crate:: + kw self:: + "#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/ide-db/src/defs.rs b/src/tools/rust-analyzer/crates/ide-db/src/defs.rs index 5e4562d9c..4ce80532e 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/defs.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/defs.rs @@ -7,7 +7,7 @@ use arrayvec::ArrayVec; use hir::{ - Adt, AsAssocItem, AssocItem, BuiltinAttr, BuiltinType, Const, Crate, DeriveHelper, + Adt, AsAssocItem, AssocItem, BuiltinAttr, BuiltinType, Const, Crate, DeriveHelper, DocLinkDef, ExternCrateDecl, Field, Function, GenericParam, HasVisibility, Impl, Label, Local, Macro, Module, ModuleDef, Name, PathResolution, Semantics, Static, ToolModule, Trait, TraitAlias, TypeAlias, Variant, Visibility, @@ -649,3 +649,13 @@ impl From<ModuleDef> for Definition { } } } + +impl From<DocLinkDef> for Definition { + fn from(def: DocLinkDef) -> Self { + match def { + DocLinkDef::ModuleDef(it) => it.into(), + DocLinkDef::Field(it) => it.into(), + DocLinkDef::SelfType(it) => it.into(), + } + } +} diff --git a/src/tools/rust-analyzer/crates/ide-db/src/documentation.rs b/src/tools/rust-analyzer/crates/ide-db/src/documentation.rs new file mode 100644 index 000000000..26f3cd28a --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-db/src/documentation.rs @@ -0,0 +1,281 @@ +//! Documentation attribute related utilties. +use either::Either; +use hir::{ + db::{DefDatabase, HirDatabase}, + resolve_doc_path_on, AttrId, AttrSourceMap, AttrsWithOwner, HasAttrs, InFile, +}; +use itertools::Itertools; +use syntax::{ + ast::{self, IsString}, + AstToken, +}; +use text_edit::{TextRange, TextSize}; + +/// Holds documentation +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Documentation(String); + +impl Documentation { + pub fn new(s: String) -> Self { + Documentation(s) + } + + pub fn as_str(&self) -> &str { + &self.0 + } +} + +impl From<Documentation> for String { + fn from(Documentation(string): Documentation) -> Self { + string + } +} + +pub trait HasDocs: HasAttrs { + fn docs(self, db: &dyn HirDatabase) -> Option<Documentation>; + fn resolve_doc_path( + self, + db: &dyn HirDatabase, + link: &str, + ns: Option<hir::Namespace>, + ) -> Option<hir::DocLinkDef>; +} +/// A struct to map text ranges from [`Documentation`] back to TextRanges in the syntax tree. +#[derive(Debug)] +pub struct DocsRangeMap { + source_map: AttrSourceMap, + // (docstring-line-range, attr_index, attr-string-range) + // a mapping from the text range of a line of the [`Documentation`] to the attribute index and + // the original (untrimmed) syntax doc line + mapping: Vec<(TextRange, AttrId, TextRange)>, +} + +impl DocsRangeMap { + /// Maps a [`TextRange`] relative to the documentation string back to its AST range + pub fn map(&self, range: TextRange) -> Option<InFile<TextRange>> { + let found = self.mapping.binary_search_by(|(probe, ..)| probe.ordering(range)).ok()?; + let (line_docs_range, idx, original_line_src_range) = self.mapping[found]; + if !line_docs_range.contains_range(range) { + return None; + } + + let relative_range = range - line_docs_range.start(); + + let InFile { file_id, value: source } = self.source_map.source_of_id(idx); + match source { + Either::Left(attr) => { + let string = get_doc_string_in_attr(attr)?; + let text_range = string.open_quote_text_range()?; + let range = TextRange::at( + text_range.end() + original_line_src_range.start() + relative_range.start(), + string.syntax().text_range().len().min(range.len()), + ); + Some(InFile { file_id, value: range }) + } + Either::Right(comment) => { + let text_range = comment.syntax().text_range(); + let range = TextRange::at( + text_range.start() + + TextSize::try_from(comment.prefix().len()).ok()? + + original_line_src_range.start() + + relative_range.start(), + text_range.len().min(range.len()), + ); + Some(InFile { file_id, value: range }) + } + } + } +} + +pub fn docs_with_rangemap( + db: &dyn DefDatabase, + attrs: &AttrsWithOwner, +) -> Option<(Documentation, DocsRangeMap)> { + let docs = + attrs.by_key("doc").attrs().filter_map(|attr| attr.string_value().map(|s| (s, attr.id))); + let indent = doc_indent(attrs); + let mut buf = String::new(); + let mut mapping = Vec::new(); + for (doc, idx) in docs { + if !doc.is_empty() { + let mut base_offset = 0; + for raw_line in doc.split('\n') { + let line = raw_line.trim_end(); + let line_len = line.len(); + let (offset, line) = match line.char_indices().nth(indent) { + Some((offset, _)) => (offset, &line[offset..]), + None => (0, line), + }; + let buf_offset = buf.len(); + buf.push_str(line); + mapping.push(( + TextRange::new(buf_offset.try_into().ok()?, buf.len().try_into().ok()?), + idx, + TextRange::at( + (base_offset + offset).try_into().ok()?, + line_len.try_into().ok()?, + ), + )); + buf.push('\n'); + base_offset += raw_line.len() + 1; + } + } else { + buf.push('\n'); + } + } + buf.pop(); + if buf.is_empty() { + None + } else { + Some((Documentation(buf), DocsRangeMap { mapping, source_map: attrs.source_map(db) })) + } +} + +pub fn docs_from_attrs(attrs: &hir::Attrs) -> Option<String> { + let docs = attrs.by_key("doc").attrs().filter_map(|attr| attr.string_value()); + let indent = doc_indent(attrs); + let mut buf = String::new(); + for doc in docs { + // str::lines doesn't yield anything for the empty string + if !doc.is_empty() { + buf.extend(Itertools::intersperse( + doc.lines().map(|line| { + line.char_indices() + .nth(indent) + .map_or(line, |(offset, _)| &line[offset..]) + .trim_end() + }), + "\n", + )); + } + buf.push('\n'); + } + buf.pop(); + if buf.is_empty() { + None + } else { + Some(buf) + } +} + +macro_rules! impl_has_docs { + ($($def:ident,)*) => {$( + impl HasDocs for hir::$def { + fn docs(self, db: &dyn HirDatabase) -> Option<Documentation> { + docs_from_attrs(&self.attrs(db)).map(Documentation) + } + fn resolve_doc_path( + self, + db: &dyn HirDatabase, + link: &str, + ns: Option<hir::Namespace> + ) -> Option<hir::DocLinkDef> { + resolve_doc_path_on(db, self, link, ns) + } + } + )*}; +} + +impl_has_docs![ + Variant, Field, Static, Const, Trait, TraitAlias, TypeAlias, Macro, Function, Adt, Module, + Impl, +]; + +macro_rules! impl_has_docs_enum { + ($($variant:ident),* for $enum:ident) => {$( + impl HasDocs for hir::$variant { + fn docs(self, db: &dyn HirDatabase) -> Option<Documentation> { + hir::$enum::$variant(self).docs(db) + } + fn resolve_doc_path( + self, + db: &dyn HirDatabase, + link: &str, + ns: Option<hir::Namespace> + ) -> Option<hir::DocLinkDef> { + hir::$enum::$variant(self).resolve_doc_path(db, link, ns) + } + } + )*}; +} + +impl_has_docs_enum![Struct, Union, Enum for Adt]; + +impl HasDocs for hir::AssocItem { + fn docs(self, db: &dyn HirDatabase) -> Option<Documentation> { + match self { + hir::AssocItem::Function(it) => it.docs(db), + hir::AssocItem::Const(it) => it.docs(db), + hir::AssocItem::TypeAlias(it) => it.docs(db), + } + } + + fn resolve_doc_path( + self, + db: &dyn HirDatabase, + link: &str, + ns: Option<hir::Namespace>, + ) -> Option<hir::DocLinkDef> { + match self { + hir::AssocItem::Function(it) => it.resolve_doc_path(db, link, ns), + hir::AssocItem::Const(it) => it.resolve_doc_path(db, link, ns), + hir::AssocItem::TypeAlias(it) => it.resolve_doc_path(db, link, ns), + } + } +} + +impl HasDocs for hir::ExternCrateDecl { + fn docs(self, db: &dyn HirDatabase) -> Option<Documentation> { + let crate_docs = + docs_from_attrs(&self.resolved_crate(db)?.root_module().attrs(db)).map(String::from); + let decl_docs = docs_from_attrs(&self.attrs(db)).map(String::from); + match (decl_docs, crate_docs) { + (None, None) => None, + (Some(decl_docs), None) => Some(decl_docs), + (None, Some(crate_docs)) => Some(crate_docs), + (Some(mut decl_docs), Some(crate_docs)) => { + decl_docs.push('\n'); + decl_docs.push('\n'); + decl_docs += &crate_docs; + Some(decl_docs) + } + } + .map(Documentation::new) + } + fn resolve_doc_path( + self, + db: &dyn HirDatabase, + link: &str, + ns: Option<hir::Namespace>, + ) -> Option<hir::DocLinkDef> { + resolve_doc_path_on(db, self, link, ns) + } +} + +fn get_doc_string_in_attr(it: &ast::Attr) -> Option<ast::String> { + match it.expr() { + // #[doc = lit] + Some(ast::Expr::Literal(lit)) => match lit.kind() { + ast::LiteralKind::String(it) => Some(it), + _ => None, + }, + // #[cfg_attr(..., doc = "", ...)] + None => { + // FIXME: See highlight injection for what to do here + None + } + _ => None, + } +} + +fn doc_indent(attrs: &hir::Attrs) -> usize { + attrs + .by_key("doc") + .attrs() + .filter_map(|attr| attr.string_value()) + .flat_map(|s| s.lines()) + .filter(|line| !line.chars().all(|c| c.is_whitespace())) + .map(|line| line.chars().take_while(|c| c.is_whitespace()).count()) + .min() + .unwrap_or(0) +} diff --git a/src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs b/src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs index 49b37024a..57563a174 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs @@ -3505,8 +3505,8 @@ This serves two purposes: "##, }, Lint { - label: "no_coverage", - description: r##"# `no_coverage` + label: "coverage", + description: r##"# `coverage` The tracking issue for this feature is: [#84605] @@ -3514,7 +3514,7 @@ The tracking issue for this feature is: [#84605] --- -The `no_coverage` attribute can be used to selectively disable coverage +The `coverage` attribute can be used to selectively disable coverage instrumentation in an annotated function. This might be useful to: - Avoid instrumentation overhead in a performance critical function @@ -3524,14 +3524,14 @@ instrumentation in an annotated function. This might be useful to: ## Example ```rust -#![feature(no_coverage)] +#![feature(coverage)] // `foo()` will get coverage instrumentation (by default) fn foo() { // ... } -#[no_coverage] +#[coverage(off)] fn bar() { // ... } diff --git a/src/tools/rust-analyzer/crates/ide-db/src/helpers.rs b/src/tools/rust-analyzer/crates/ide-db/src/helpers.rs index 1eb8f0002..330af442f 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/helpers.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/helpers.rs @@ -117,7 +117,7 @@ pub fn get_definition( sema: &Semantics<'_, RootDatabase>, token: SyntaxToken, ) -> Option<Definition> { - for token in sema.descend_into_macros(token) { + for token in sema.descend_into_macros(token, 0.into()) { let def = IdentClass::classify_token(sema, &token).map(IdentClass::definitions_no_ops); if let Some(&[x]) = def.as_deref() { return Some(x); diff --git a/src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs b/src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs index e52dc3567..e475c5cd6 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs @@ -6,7 +6,7 @@ use hir::{ use itertools::Itertools; use rustc_hash::FxHashSet; use syntax::{ - ast::{self, HasName}, + ast::{self, make, HasName}, utils::path_to_string_stripping_turbo_fish, AstNode, SyntaxNode, }; @@ -607,7 +607,7 @@ impl ImportCandidate { fn for_name(sema: &Semantics<'_, RootDatabase>, name: &ast::Name) -> Option<Self> { if sema .scope(name.syntax())? - .speculative_resolve(&ast::make::ext::ident_path(&name.text())) + .speculative_resolve(&make::ext::ident_path(&name.text())) .is_some() { return None; diff --git a/src/tools/rust-analyzer/crates/ide-db/src/lib.rs b/src/tools/rust-analyzer/crates/ide-db/src/lib.rs index f27ed485d..226def4d5 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/lib.rs @@ -22,6 +22,7 @@ pub mod symbol_index; pub mod traits; pub mod ty_filter; pub mod use_trivial_constructor; +pub mod documentation; pub mod imports { pub mod import_assets; @@ -94,18 +95,21 @@ impl fmt::Debug for RootDatabase { } impl Upcast<dyn ExpandDatabase> for RootDatabase { + #[inline] fn upcast(&self) -> &(dyn ExpandDatabase + 'static) { &*self } } impl Upcast<dyn DefDatabase> for RootDatabase { + #[inline] fn upcast(&self) -> &(dyn DefDatabase + 'static) { &*self } } impl Upcast<dyn HirDatabase> for RootDatabase { + #[inline] fn upcast(&self) -> &(dyn HirDatabase + 'static) { &*self } diff --git a/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs b/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs index 1d0cb426a..fb75b5b45 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs @@ -5,7 +5,7 @@ use either::Either; use hir::{AsAssocItem, HirDisplay, SemanticsScope}; use rustc_hash::FxHashMap; use syntax::{ - ast::{self, AstNode}, + ast::{self, make, AstNode}, ted, SyntaxNode, }; @@ -21,6 +21,7 @@ enum TypeOrConst { } type LifetimeName = String; +type DefaultedParam = Either<hir::TypeParam, hir::ConstParam>; /// `PathTransform` substitutes path in SyntaxNodes in bulk. /// @@ -115,7 +116,7 @@ impl<'a> PathTransform<'a> { }; let mut type_substs: FxHashMap<hir::TypeParam, ast::Type> = Default::default(); let mut const_substs: FxHashMap<hir::ConstParam, SyntaxNode> = Default::default(); - let mut default_types: Vec<hir::TypeParam> = Default::default(); + let mut defaulted_params: Vec<DefaultedParam> = Default::default(); self.generic_def .into_iter() .flat_map(|it| it.type_params(db)) @@ -138,8 +139,8 @@ impl<'a> PathTransform<'a> { if let Some(default) = &default.display_source_code(db, source_module.into(), false).ok() { - type_substs.insert(k, ast::make::ty(default).clone_for_update()); - default_types.push(k); + type_substs.insert(k, make::ty(default).clone_for_update()); + defaulted_params.push(Either::Left(k)); } } } @@ -155,11 +156,19 @@ impl<'a> PathTransform<'a> { // is a standalone statement or a part of another expresson) // and sometimes require slight modifications; see // https://doc.rust-lang.org/reference/statements.html#expression-statements + // (default values in curly brackets can cause the same problem) const_substs.insert(k, expr.syntax().clone()); } } - (Either::Left(_), None) => (), // FIXME: get default const value - _ => (), // ignore mismatching params + (Either::Left(k), None) => { + if let Some(default) = k.default(db) { + if let Some(default) = default.expr() { + const_substs.insert(k, default.syntax().clone_for_update()); + defaulted_params.push(Either::Right(k)); + } + } + } + _ => (), // ignore mismatching params }); let lifetime_substs: FxHashMap<_, _> = self .generic_def @@ -175,7 +184,7 @@ impl<'a> PathTransform<'a> { target_module, source_scope: self.source_scope, }; - ctx.transform_default_type_substs(default_types); + ctx.transform_default_values(defaulted_params); ctx } } @@ -212,13 +221,19 @@ impl Ctx<'_> { }); } - fn transform_default_type_substs(&self, default_types: Vec<hir::TypeParam>) { - for k in default_types { - let v = self.type_substs.get(&k).unwrap(); + fn transform_default_values(&self, defaulted_params: Vec<DefaultedParam>) { + // By now the default values are simply copied from where they are declared + // and should be transformed. As any value is allowed to refer to previous + // generic (both type and const) parameters, they should be all iterated left-to-right. + for param in defaulted_params { + let value = match param { + Either::Left(k) => self.type_substs.get(&k).unwrap().syntax(), + Either::Right(k) => self.const_substs.get(&k).unwrap(), + }; // `transform_path` may update a node's parent and that would break the // tree traversal. Thus all paths in the tree are collected into a vec // so that such operation is safe. - let paths = postorder(&v.syntax()).filter_map(ast::Path::cast).collect::<Vec<_>>(); + let paths = postorder(value).filter_map(ast::Path::cast).collect::<Vec<_>>(); for path in paths { self.transform_path(path); } @@ -263,15 +278,14 @@ impl Ctx<'_> { hir::ModuleDef::Trait(trait_ref), false, )?; - match ast::make::ty_path(mod_path_to_ast(&found_path)) { + match make::ty_path(mod_path_to_ast(&found_path)) { ast::Type::PathType(path_ty) => Some(path_ty), _ => None, } }); - let segment = ast::make::path_segment_ty(subst.clone(), trait_ref); - let qualified = - ast::make::path_from_segments(std::iter::once(segment), false); + let segment = make::path_segment_ty(subst.clone(), trait_ref); + let qualified = make::path_from_segments(std::iter::once(segment), false); ted::replace(path.syntax(), qualified.clone_for_update().syntax()); } else if let Some(path_ty) = ast::PathType::cast(parent) { ted::replace( diff --git a/src/tools/rust-analyzer/crates/ide-db/src/rename.rs b/src/tools/rust-analyzer/crates/ide-db/src/rename.rs index aa0bb7cce..353a9749a 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/rename.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/rename.rs @@ -71,12 +71,29 @@ impl Definition { sema: &Semantics<'_, RootDatabase>, new_name: &str, ) -> Result<SourceChange> { + // self.krate() returns None if + // self is a built-in attr, built-in type or tool module. + // it is not allowed for these defs to be renamed. + // cases where self.krate() is None is handled below. + if let Some(krate) = self.krate(sema.db) { + if !krate.origin(sema.db).is_local() { + bail!("Cannot rename a non-local definition.") + } + } + match *self { Definition::Module(module) => rename_mod(sema, module, new_name), + Definition::ToolModule(_) => { + bail!("Cannot rename a tool module") + } Definition::BuiltinType(_) => { bail!("Cannot rename builtin type") } + Definition::BuiltinAttr(_) => { + bail!("Cannot rename a builtin attr.") + } Definition::SelfType(_) => bail!("Cannot rename `Self`"), + Definition::Macro(mac) => rename_reference(sema, Definition::Macro(mac), new_name), def => rename_reference(sema, def, new_name), } } diff --git a/src/tools/rust-analyzer/crates/ide-db/src/rust_doc.rs b/src/tools/rust-analyzer/crates/ide-db/src/rust_doc.rs index e27e23867..ab2a25028 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/rust_doc.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/rust_doc.rs @@ -1,5 +1,7 @@ //! Rustdoc specific doc comment handling +use crate::documentation::Documentation; + // stripped down version of https://github.com/rust-lang/rust/blob/392ba2ba1a7d6c542d2459fb8133bebf62a4a423/src/librustdoc/html/markdown.rs#L810-L933 pub fn is_rust_fence(s: &str) -> bool { let mut seen_rust_tags = false; @@ -32,3 +34,170 @@ pub fn is_rust_fence(s: &str) -> bool { !seen_other_tags || seen_rust_tags } + +const RUSTDOC_FENCES: [&str; 2] = ["```", "~~~"]; + +pub fn format_docs(src: &Documentation) -> String { + format_docs_(src.as_str()) +} + +fn format_docs_(src: &str) -> String { + let mut processed_lines = Vec::new(); + let mut in_code_block = false; + let mut is_rust = false; + + for mut line in src.lines() { + if in_code_block && is_rust && code_line_ignored_by_rustdoc(line) { + continue; + } + + if let Some(header) = RUSTDOC_FENCES.into_iter().find_map(|fence| line.strip_prefix(fence)) + { + in_code_block ^= true; + + if in_code_block { + is_rust = is_rust_fence(header); + + if is_rust { + line = "```rust"; + } + } + } + + if in_code_block { + let trimmed = line.trim_start(); + if is_rust && trimmed.starts_with("##") { + line = &trimmed[1..]; + } + } + + processed_lines.push(line); + } + processed_lines.join("\n") +} + +fn code_line_ignored_by_rustdoc(line: &str) -> bool { + let trimmed = line.trim(); + trimmed == "#" || trimmed.starts_with("# ") || trimmed.starts_with("#\t") +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_format_docs_adds_rust() { + let comment = "```\nfn some_rust() {}\n```"; + assert_eq!(format_docs_(comment), "```rust\nfn some_rust() {}\n```"); + } + + #[test] + fn test_format_docs_handles_plain_text() { + let comment = "```text\nthis is plain text\n```"; + assert_eq!(format_docs_(comment), "```text\nthis is plain text\n```"); + } + + #[test] + fn test_format_docs_handles_non_rust() { + let comment = "```sh\nsupposedly shell code\n```"; + assert_eq!(format_docs_(comment), "```sh\nsupposedly shell code\n```"); + } + + #[test] + fn test_format_docs_handles_rust_alias() { + let comment = "```ignore\nlet z = 55;\n```"; + assert_eq!(format_docs_(comment), "```rust\nlet z = 55;\n```"); + } + + #[test] + fn test_format_docs_handles_complex_code_block_attrs() { + let comment = "```rust,no_run\nlet z = 55;\n```"; + assert_eq!(format_docs_(comment), "```rust\nlet z = 55;\n```"); + } + + #[test] + fn test_format_docs_handles_error_codes() { + let comment = "```compile_fail,E0641\nlet b = 0 as *const _;\n```"; + assert_eq!(format_docs_(comment), "```rust\nlet b = 0 as *const _;\n```"); + } + + #[test] + fn test_format_docs_skips_comments_in_rust_block() { + let comment = + "```rust\n # skip1\n# skip2\n#stay1\nstay2\n#\n #\n # \n #\tskip3\n\t#\t\n```"; + assert_eq!(format_docs_(comment), "```rust\n#stay1\nstay2\n```"); + } + + #[test] + fn test_format_docs_does_not_skip_lines_if_plain_text() { + let comment = + "```text\n # stay1\n# stay2\n#stay3\nstay4\n#\n #\n # \n #\tstay5\n\t#\t\n```"; + assert_eq!( + format_docs_(comment), + "```text\n # stay1\n# stay2\n#stay3\nstay4\n#\n #\n # \n #\tstay5\n\t#\t\n```", + ); + } + + #[test] + fn test_format_docs_keeps_comments_outside_of_rust_block() { + let comment = " # stay1\n# stay2\n#stay3\nstay4\n#\n #\n # \n #\tstay5\n\t#\t"; + assert_eq!(format_docs_(comment), comment); + } + + #[test] + fn test_format_docs_preserves_newlines() { + let comment = "this\nis\nmultiline"; + assert_eq!(format_docs_(comment), comment); + } + + #[test] + fn test_code_blocks_in_comments_marked_as_rust() { + let comment = r#"```rust +fn main(){} +``` +Some comment. +``` +let a = 1; +```"#; + + assert_eq!( + format_docs_(comment), + "```rust\nfn main(){}\n```\nSome comment.\n```rust\nlet a = 1;\n```" + ); + } + + #[test] + fn test_code_blocks_in_comments_marked_as_text() { + let comment = r#"```text +filler +text +``` +Some comment. +``` +let a = 1; +```"#; + + assert_eq!( + format_docs_(comment), + "```text\nfiller\ntext\n```\nSome comment.\n```rust\nlet a = 1;\n```" + ); + } + + #[test] + fn test_format_docs_handles_escape_double_hashes() { + let comment = r#"```rust +let s = "foo +## bar # baz"; +```"#; + + assert_eq!(format_docs_(comment), "```rust\nlet s = \"foo\n# bar # baz\";\n```"); + } + + #[test] + fn test_format_docs_handles_double_hashes_non_rust() { + let comment = r#"```markdown +## A second-level heading +```"#; + assert_eq!(format_docs_(comment), "```markdown\n## A second-level heading\n```"); + } +} diff --git a/src/tools/rust-analyzer/crates/ide-db/src/search.rs b/src/tools/rust-analyzer/crates/ide-db/src/search.rs index d5abd0991..9c4f0ac8c 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/search.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/search.rs @@ -6,7 +6,7 @@ use std::mem; -use base_db::{FileId, FileRange, SourceDatabase, SourceDatabaseExt}; +use base_db::{salsa::Database, FileId, FileRange, SourceDatabase, SourceDatabaseExt}; use hir::{ AsAssocItem, DefWithBody, HasAttrs, HasSource, InFile, ModuleSource, Semantics, Visibility, }; @@ -221,7 +221,6 @@ impl Definition { } // def is crate root - // FIXME: We don't do searches for crates currently, as a crate does not actually have a single name if let &Definition::Module(module) = self { if module.is_crate_root() { return SearchScope::reverse_dependencies(db, module.krate()); @@ -393,7 +392,10 @@ impl<'a> FindUsages<'a> { let name = match self.def { // special case crate modules as these do not have a proper name Definition::Module(module) if module.is_crate_root() => { - // FIXME: This assumes the crate name is always equal to its display name when it really isn't + // FIXME: This assumes the crate name is always equal to its display name when it + // really isn't + // we should instead look at the dependency edge name and recursively search our way + // up the ancestors module .krate() .display_name(self.sema.db) @@ -456,18 +458,19 @@ impl<'a> FindUsages<'a> { it.text().trim_start_matches("r#") == name }) .into_iter() - .flat_map(|token| { + .flat_map(move |token| { // FIXME: There should be optimization potential here // Currently we try to descend everything we find which // means we call `Semantics::descend_into_macros` on // every textual hit. That function is notoriously // expensive even for things that do not get down mapped // into macros. - sema.descend_into_macros(token).into_iter().filter_map(|it| it.parent()) + sema.descend_into_macros(token, offset).into_iter().filter_map(|it| it.parent()) }) }; for (text, file_id, search_range) in scope_files(sema, &search_scope) { + self.sema.db.unwind_if_cancelled(); let tree = Lazy::new(move || sema.parse(file_id).syntax().clone()); // Search for occurrences of the items name @@ -504,6 +507,7 @@ impl<'a> FindUsages<'a> { let finder = &Finder::new("super"); for (text, file_id, search_range) in scope_files(sema, &scope) { + self.sema.db.unwind_if_cancelled(); let tree = Lazy::new(move || sema.parse(file_id).syntax().clone()); for offset in match_indices(&text, finder, search_range) { diff --git a/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs b/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs index b54c43b29..f699f999b 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs @@ -323,6 +323,8 @@ impl Query { hir::ModuleDef::Adt(..) | hir::ModuleDef::TypeAlias(..) | hir::ModuleDef::BuiltinType(..) + | hir::ModuleDef::TraitAlias(..) + | hir::ModuleDef::Trait(..) ) { continue; @@ -417,9 +419,16 @@ const CONST_WITH_INNER: () = { mod b_mod; + +use define_struct as really_define_struct; +use Macro as ItemLikeMacro; +use Macro as Trait; // overlay namespaces //- /b_mod.rs struct StructInModB; - "#, +use super::Macro as SuperItemLikeMacro; +use crate::b_mod::StructInModB as ThisStruct; +use crate::Trait as IsThisJustATrait; +"#, ); let symbols: Vec<_> = Crate::from(db.test_crate()) diff --git a/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/format_string.rs b/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/format_string.rs index acf0a67de..8302b015d 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/format_string.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/format_string.rs @@ -1,10 +1,10 @@ //! Tools to work with format string literals for the `format_args!` family of macros. -use crate::syntax_helpers::node_ext::macro_call_for_string_token; use syntax::{ ast::{self, IsString}, - TextRange, TextSize, + AstNode, AstToken, TextRange, TextSize, }; +// FIXME: This can probably be re-implemented via the HIR? pub fn is_format_string(string: &ast::String) -> bool { // Check if `string` is a format string argument of a macro invocation. // `string` is a string literal, mapped down into the innermost macro expansion. @@ -15,19 +15,9 @@ pub fn is_format_string(string: &ast::String) -> bool { // This setup lets us correctly highlight the components of `concat!("{}", "bla")` format // strings. It still fails for `concat!("{", "}")`, but that is rare. (|| { - let name = macro_call_for_string_token(string)?.path()?.segment()?.name_ref()?; - - if !matches!( - name.text().as_str(), - "format_args" | "format_args_nl" | "const_format_args" | "panic_2015" | "panic_2021" - ) { - return None; - } - - // NB: we match against `panic_2015`/`panic_2021` here because they have a special-cased arm for - // `"{}"`, which otherwise wouldn't get highlighted. - - Some(()) + let lit = string.syntax().parent().and_then(ast::Literal::cast)?; + let fa = lit.syntax().parent().and_then(ast::FormatArgsExpr::cast)?; + (fa.template()? == ast::Expr::Literal(lit)).then_some(|| ()) })() .is_some() } diff --git a/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/node_ext.rs b/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/node_ext.rs index 22ced69d8..e4e735cec 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/node_ext.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/node_ext.rs @@ -312,7 +312,6 @@ pub fn for_each_tail_expr(expr: &ast::Expr, cb: &mut dyn FnMut(&ast::Expr)) { ast::Expr::ArrayExpr(_) | ast::Expr::AwaitExpr(_) | ast::Expr::BinExpr(_) - | ast::Expr::BoxExpr(_) | ast::Expr::BreakExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::CastExpr(_) @@ -335,7 +334,10 @@ pub fn for_each_tail_expr(expr: &ast::Expr, cb: &mut dyn FnMut(&ast::Expr)) { | ast::Expr::LetExpr(_) | ast::Expr::UnderscoreExpr(_) | ast::Expr::YieldExpr(_) - | ast::Expr::YeetExpr(_) => cb(expr), + | ast::Expr::YeetExpr(_) + | ast::Expr::OffsetOfExpr(_) + | ast::Expr::FormatArgsExpr(_) + | ast::Expr::AsmExpr(_) => cb(expr), } } diff --git a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt index 1a00e2938..87ad5844c 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt +++ b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt @@ -119,6 +119,35 @@ is_alias: false, }, FileSymbol { + name: "ItemLikeMacro", + def: Macro( + Macro { + id: Macro2Id( + Macro2Id( + 0, + ), + ), + }, + ), + loc: DeclarationLocation { + hir_file_id: FileId( + FileId( + 0, + ), + ), + ptr: SyntaxNodePtr { + kind: USE_TREE, + range: 654..676, + }, + name_ptr: SyntaxNodePtr { + kind: NAME, + range: 663..676, + }, + }, + container_name: None, + is_alias: false, + }, + FileSymbol { name: "Macro", def: Macro( Macro { @@ -353,6 +382,35 @@ is_alias: false, }, FileSymbol { + name: "Trait", + def: Macro( + Macro { + id: Macro2Id( + Macro2Id( + 0, + ), + ), + }, + ), + loc: DeclarationLocation { + hir_file_id: FileId( + FileId( + 0, + ), + ), + ptr: SyntaxNodePtr { + kind: USE_TREE, + range: 682..696, + }, + name_ptr: SyntaxNodePtr { + kind: NAME, + range: 691..696, + }, + }, + container_name: None, + is_alias: false, + }, + FileSymbol { name: "Union", def: Adt( Union( @@ -552,6 +610,35 @@ is_alias: false, }, FileSymbol { + name: "really_define_struct", + def: Macro( + Macro { + id: MacroRulesId( + MacroRulesId( + 1, + ), + ), + }, + ), + loc: DeclarationLocation { + hir_file_id: FileId( + FileId( + 0, + ), + ), + ptr: SyntaxNodePtr { + kind: USE_TREE, + range: 611..648, + }, + name_ptr: SyntaxNodePtr { + kind: NAME, + range: 628..648, + }, + }, + container_name: None, + is_alias: false, + }, + FileSymbol { name: "trait_fn", def: Function( Function { @@ -632,6 +719,35 @@ }, [ FileSymbol { + name: "IsThisJustATrait", + def: Macro( + Macro { + id: Macro2Id( + Macro2Id( + 0, + ), + ), + }, + ), + loc: DeclarationLocation { + hir_file_id: FileId( + FileId( + 1, + ), + ), + ptr: SyntaxNodePtr { + kind: USE_TREE, + range: 111..143, + }, + name_ptr: SyntaxNodePtr { + kind: NAME, + range: 127..143, + }, + }, + container_name: None, + is_alias: false, + }, + FileSymbol { name: "StructInModB", def: Adt( Struct( @@ -660,6 +776,93 @@ container_name: None, is_alias: false, }, + FileSymbol { + name: "SuperItemLikeMacro", + def: Macro( + Macro { + id: Macro2Id( + Macro2Id( + 0, + ), + ), + }, + ), + loc: DeclarationLocation { + hir_file_id: FileId( + FileId( + 1, + ), + ), + ptr: SyntaxNodePtr { + kind: USE_TREE, + range: 25..59, + }, + name_ptr: SyntaxNodePtr { + kind: NAME, + range: 41..59, + }, + }, + container_name: None, + is_alias: false, + }, + FileSymbol { + name: "ThisStruct", + def: Adt( + Struct( + Struct { + id: StructId( + 3, + ), + }, + ), + ), + loc: DeclarationLocation { + hir_file_id: FileId( + FileId( + 1, + ), + ), + ptr: SyntaxNodePtr { + kind: USE_TREE, + range: 65..105, + }, + name_ptr: SyntaxNodePtr { + kind: NAME, + range: 95..105, + }, + }, + container_name: None, + is_alias: false, + }, + FileSymbol { + name: "ThisStruct", + def: Adt( + Struct( + Struct { + id: StructId( + 3, + ), + }, + ), + ), + loc: DeclarationLocation { + hir_file_id: FileId( + FileId( + 1, + ), + ), + ptr: SyntaxNodePtr { + kind: USE_TREE, + range: 65..105, + }, + name_ptr: SyntaxNodePtr { + kind: NAME, + range: 95..105, + }, + }, + container_name: None, + is_alias: false, + }, ], ), ] diff --git a/src/tools/rust-analyzer/crates/ide-db/src/use_trivial_constructor.rs b/src/tools/rust-analyzer/crates/ide-db/src/use_trivial_constructor.rs index f96ea29ae..a915391ad 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/use_trivial_constructor.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/use_trivial_constructor.rs @@ -1,31 +1,29 @@ //! Functionality for generating trivial constructors use hir::StructKind; -use syntax::ast; +use syntax::ast::{make, Expr, Path}; /// given a type return the trivial constructor (if one exists) pub fn use_trivial_constructor( db: &crate::RootDatabase, - path: ast::Path, + path: Path, ty: &hir::Type, -) -> Option<ast::Expr> { +) -> Option<Expr> { match ty.as_adt() { Some(hir::Adt::Enum(x)) => { if let &[variant] = &*x.variants(db) { if variant.kind(db) == hir::StructKind::Unit { - let path = ast::make::path_qualified( + let path = make::path_qualified( path, - syntax::ast::make::path_segment(ast::make::name_ref( - &variant.name(db).to_smol_str(), - )), + make::path_segment(make::name_ref(&variant.name(db).to_smol_str())), ); - return Some(syntax::ast::make::expr_path(path)); + return Some(make::expr_path(path)); } } } Some(hir::Adt::Struct(x)) if x.kind(db) == StructKind::Unit => { - return Some(syntax::ast::make::expr_path(path)); + return Some(make::expr_path(path)); } _ => {} } diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/macro_error.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/macro_error.rs index f54cdd63b..7ca0a0eab 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/macro_error.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/macro_error.rs @@ -157,6 +157,7 @@ struct S; fn macro_diag_builtin() { check_diagnostics( r#" +//- minicore: fmt #[rustc_builtin_macro] macro_rules! env {} @@ -166,9 +167,6 @@ macro_rules! include {} #[rustc_builtin_macro] macro_rules! compile_error {} -#[rustc_builtin_macro] -macro_rules! format_args { () => {} } - fn main() { // Test a handful of built-in (eager) macros: @@ -189,7 +187,7 @@ fn main() { // Lazy: format_args!(); - //^^^^^^^^^^^ error: no rule matches input tokens + //^^^^^^^^^^^ error: Syntax Error in Expansion: expected expression } "#, ); diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs index 6238c7e09..8265e0b1c 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs @@ -1,10 +1,37 @@ +use either::Either; +use hir::InFile; use syntax::{ ast::{self, HasArgList}, - AstNode, TextRange, + AstNode, SyntaxNodePtr, TextRange, }; use crate::{adjusted_display_range, Diagnostic, DiagnosticCode, DiagnosticsContext}; +// Diagnostic: mismatched-tuple-struct-pat-arg-count +// +// This diagnostic is triggered if a function is invoked with an incorrect amount of arguments. +pub(crate) fn mismatched_tuple_struct_pat_arg_count( + ctx: &DiagnosticsContext<'_>, + d: &hir::MismatchedTupleStructPatArgCount, +) -> Diagnostic { + let s = if d.found == 1 { "" } else { "s" }; + let s2 = if d.expected == 1 { "" } else { "s" }; + let message = format!( + "this pattern has {} field{s}, but the corresponding tuple struct has {} field{s2}", + d.found, d.expected + ); + Diagnostic::new( + DiagnosticCode::RustcHardError("E0023"), + message, + invalid_args_range( + ctx, + d.expr_or_pat.clone().map(|it| it.either(Into::into, Into::into)), + d.expected, + d.found, + ), + ) +} + // Diagnostic: mismatched-arg-count // // This diagnostic is triggered if a function is invoked with an incorrect amount of arguments. @@ -14,31 +41,63 @@ pub(crate) fn mismatched_arg_count( ) -> Diagnostic { let s = if d.expected == 1 { "" } else { "s" }; let message = format!("expected {} argument{s}, found {}", d.expected, d.found); - Diagnostic::new(DiagnosticCode::RustcHardError("E0107"), message, invalid_args_range(ctx, d)) + Diagnostic::new( + DiagnosticCode::RustcHardError("E0107"), + message, + invalid_args_range(ctx, d.call_expr.clone().map(Into::into), d.expected, d.found), + ) } -fn invalid_args_range(ctx: &DiagnosticsContext<'_>, d: &hir::MismatchedArgCount) -> TextRange { - adjusted_display_range::<ast::Expr>(ctx, d.call_expr.clone().map(|it| it.into()), &|expr| { - let arg_list = match expr { - ast::Expr::CallExpr(call) => call.arg_list()?, - ast::Expr::MethodCallExpr(call) => call.arg_list()?, +fn invalid_args_range( + ctx: &DiagnosticsContext<'_>, + source: InFile<SyntaxNodePtr>, + expected: usize, + found: usize, +) -> TextRange { + adjusted_display_range::<Either<ast::Expr, ast::TupleStructPat>>(ctx, source, &|expr| { + let (text_range, r_paren_token, expected_arg) = match expr { + Either::Left(ast::Expr::CallExpr(call)) => { + let arg_list = call.arg_list()?; + ( + arg_list.syntax().text_range(), + arg_list.r_paren_token(), + arg_list.args().nth(expected).map(|it| it.syntax().text_range()), + ) + } + Either::Left(ast::Expr::MethodCallExpr(call)) => { + let arg_list = call.arg_list()?; + ( + arg_list.syntax().text_range(), + arg_list.r_paren_token(), + arg_list.args().nth(expected).map(|it| it.syntax().text_range()), + ) + } + Either::Right(pat) => { + let r_paren = pat.r_paren_token()?; + let l_paren = pat.l_paren_token()?; + ( + l_paren.text_range().cover(r_paren.text_range()), + Some(r_paren), + pat.fields().nth(expected).map(|it| it.syntax().text_range()), + ) + } _ => return None, }; - if d.found < d.expected { - if d.found == 0 { - return Some(arg_list.syntax().text_range()); + if found < expected { + if found == 0 { + return Some(text_range); } - if let Some(r_paren) = arg_list.r_paren_token() { + if let Some(r_paren) = r_paren_token { return Some(r_paren.text_range()); } } - if d.expected < d.found { - if d.expected == 0 { - return Some(arg_list.syntax().text_range()); + if expected < found { + if expected == 0 { + return Some(text_range); } - let zip = arg_list.args().nth(d.expected).zip(arg_list.r_paren_token()); + let zip = expected_arg.zip(r_paren_token); if let Some((arg, r_paren)) = zip { - return Some(arg.syntax().text_range().cover(r_paren.text_range())); + return Some(arg.cover(r_paren.text_range())); } } @@ -331,4 +390,21 @@ fn g() { "#, ) } + + #[test] + fn tuple_struct_pat() { + check_diagnostics( + r#" +struct S(u32, u32); +fn f( + S(a, b, c): S, + // ^^ error: this pattern has 3 fields, but the corresponding tuple struct has 2 fields + S(): S, + // ^^ error: this pattern has 0 fields, but the corresponding tuple struct has 2 fields + S(e, f, .., g, d): S + // ^^^^^^^^^ error: this pattern has 4 fields, but the corresponding tuple struct has 2 fields +) {} +"#, + ) + } } diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_match_arms.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_match_arms.rs index 82a9a3bd5..06b03d3d1 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_match_arms.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_match_arms.rs @@ -319,6 +319,7 @@ fn main() { match Either::A { Either::A => (), Either::B() => (), + // ^^ error: this pattern has 0 fields, but the corresponding tuple struct has 1 field } } "#, @@ -334,9 +335,11 @@ enum A { B(isize, isize), C } fn main() { match A::B(1, 2) { A::B(_, _, _) => (), + // ^^ error: this pattern has 3 fields, but the corresponding tuple struct has 2 fields } match A::B(1, 2) { A::C(_) => (), + // ^^^ error: this pattern has 1 field, but the corresponding tuple struct has 0 fields } } "#, @@ -846,6 +849,7 @@ fn main() { struct Foo { } fn main(f: Foo) { match f { Foo { bar } => () } + // ^^^ error: no such field } "#, ); diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mutability_errors.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mutability_errors.rs index e0c3bedce..d056e5c85 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mutability_errors.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mutability_errors.rs @@ -76,7 +76,7 @@ pub(crate) fn unused_mut(ctx: &DiagnosticsContext<'_>, d: &hir::UnusedMut) -> Di "variable does not need to be mutable", ast, ) - .experimental() // Not supporting `#[allow(unused_mut)]` leads to false positive. + .experimental() // Not supporting `#[allow(unused_mut)]` in proc macros leads to false positive. .with_fixes(fixes) } @@ -1173,4 +1173,27 @@ fn f() { "#, ); } + + #[test] + fn regression_15623() { + check_diagnostics( + r#" +//- minicore: fn + +struct Foo; + +impl Foo { + fn needs_mut(&mut self) {} +} + +fn foo(mut foo: Foo) { + let mut call_me = || { + let 0 = 1 else { return }; + foo.needs_mut(); + }; + call_me(); +} +"#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs index a34a5824f..290c16c9d 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs @@ -1,3 +1,4 @@ +use either::Either; use hir::{db::ExpandDatabase, HasSource, HirDisplay, Semantics}; use ide_db::{base_db::FileId, source_change::SourceChange, RootDatabase}; use syntax::{ @@ -12,22 +13,39 @@ use crate::{fix, Assist, Diagnostic, DiagnosticCode, DiagnosticsContext}; // // This diagnostic is triggered if created structure does not have field provided in record. pub(crate) fn no_such_field(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Diagnostic { - Diagnostic::new_with_syntax_node_ptr( - ctx, - DiagnosticCode::RustcHardError("E0559"), - "no such field", - d.field.clone().map(|it| it.into()), - ) - .with_fixes(fixes(ctx, d)) + let node = d.field.clone().map(|it| it.either(Into::into, Into::into)); + if d.private { + // FIXME: quickfix to add required visibility + Diagnostic::new_with_syntax_node_ptr( + ctx, + DiagnosticCode::RustcHardError("E0451"), + "field is private", + node, + ) + } else { + Diagnostic::new_with_syntax_node_ptr( + ctx, + DiagnosticCode::RustcHardError("E0559"), + "no such field", + node, + ) + .with_fixes(fixes(ctx, d)) + } } fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Option<Vec<Assist>> { - let root = ctx.sema.db.parse_or_expand(d.field.file_id); - missing_record_expr_field_fixes( - &ctx.sema, - d.field.file_id.original_file(ctx.sema.db), - &d.field.value.to_node(&root), - ) + // FIXME: quickfix for pattern + match &d.field.value { + Either::Left(ptr) => { + let root = ctx.sema.db.parse_or_expand(d.field.file_id); + missing_record_expr_field_fixes( + &ctx.sema, + d.field.file_id.original_file(ctx.sema.db), + &ptr.to_node(&root), + ) + } + _ => None, + } } fn missing_record_expr_field_fixes( @@ -118,13 +136,34 @@ mod tests { r#" struct S { foo: i32, bar: () } impl S { - fn new() -> S { + fn new( + s@S { + //^ 💡 error: missing structure fields: + //| - bar + foo, + baz: baz2, + //^^^^^^^^^ error: no such field + qux + //^^^ error: no such field + }: S + ) -> S { + S { + //^ 💡 error: missing structure fields: + //| - bar + foo, + baz: baz2, + //^^^^^^^^^ error: no such field + qux + //^^^ error: no such field + } = s; S { //^ 💡 error: missing structure fields: //| - bar foo: 92, baz: 62, //^^^^^^^ 💡 error: no such field + qux + //^^^ error: no such field } } } @@ -295,4 +334,38 @@ fn main() { "#, ) } + + #[test] + fn test_struct_field_private() { + check_diagnostics( + r#" +mod m { + pub struct Struct { + field: u32, + field2: u32, + } +} +fn f(s@m::Struct { + field: f, + //^^^^^^^^ error: field is private + field2 + //^^^^^^ error: field is private +}: m::Struct) { + // assignee expression + m::Struct { + field: 0, + //^^^^^^^^ error: field is private + field2 + //^^^^^^ error: field is private + } = s; + m::Struct { + field: 0, + //^^^^^^^^ error: field is private + field2 + //^^^^^^ error: field is private + }; +} +"#, + ) + } } diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/undeclared_label.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/undeclared_label.rs index 7de9a9a32..495ea7487 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/undeclared_label.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/undeclared_label.rs @@ -35,6 +35,25 @@ fn foo() { } #[test] + fn while_let_loop_with_label_in_condition() { + check_diagnostics( + r#" +fn foo() { + let mut optional = Some(0); + + 'my_label: while let Some(a) = match optional { + None => break 'my_label, + Some(val) => Some(val), + } { + optional = None; + continue 'my_label; + } +} +"#, + ); + } + + #[test] fn for_loop() { check_diagnostics( r#" diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/useless_braces.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/useless_braces.rs index 0aa439f79..c4ac59ec2 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/useless_braces.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/useless_braces.rs @@ -1,3 +1,4 @@ +use hir::InFile; use ide_db::{base_db::FileId, source_change::SourceChange}; use itertools::Itertools; use syntax::{ast, AstNode, SyntaxNode}; @@ -39,6 +40,7 @@ pub(crate) fn useless_braces( "Unnecessary braces in use statement".to_string(), use_range, ) + .with_main_node(InFile::new(file_id.into(), node.clone())) .with_fixes(Some(vec![fix( "remove_braces", "Remove unnecessary braces", @@ -156,4 +158,23 @@ use a::{c, d::e}; "#, ); } + + #[test] + fn respect_lint_attributes_for_unused_braces() { + check_diagnostics( + r#" +mod b {} +#[allow(unused_braces)] +use {b}; +"#, + ); + check_diagnostics( + r#" +mod b {} +#[deny(unused_braces)] +use {b}; + //^^^ 💡 error: Unnecessary braces in use statement +"#, + ); + } } 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 b1b9b4b8e..ebe197a67 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs @@ -369,6 +369,7 @@ pub fn diagnostics( AnyDiagnostic::UnresolvedProcMacro(d) => handlers::unresolved_proc_macro::unresolved_proc_macro(&ctx, &d, config.proc_macros_enabled, config.proc_attr_macros_enabled), AnyDiagnostic::UnusedMut(d) => handlers::mutability_errors::unused_mut(&ctx, &d), AnyDiagnostic::BreakOutsideOfLoop(d) => handlers::break_outside_of_loop::break_outside_of_loop(&ctx, &d), + AnyDiagnostic::MismatchedTupleStructPatArgCount(d) => handlers::mismatched_arg_count::mismatched_tuple_struct_pat_arg_count(&ctx, &d), }; res.push(d) } @@ -432,7 +433,8 @@ fn handle_lint_attributes( diagnostics_of_range: &mut FxHashMap<InFile<SyntaxNode>, &mut Diagnostic>, ) { let file_id = sema.hir_file_for(root); - for ev in root.preorder() { + let mut preorder = root.preorder(); + while let Some(ev) = preorder.next() { match ev { syntax::WalkEvent::Enter(node) => { for attr in node.children().filter_map(ast::Attr::cast) { @@ -515,7 +517,7 @@ fn parse_lint_attribute( let Some((tag, args_tt)) = attr.as_simple_call() else { return; }; - let serevity = match tag.as_str() { + let severity = match tag.as_str() { "allow" => Severity::Allow, "warn" => Severity::Warning, "forbid" | "deny" => Severity::Error, @@ -523,12 +525,12 @@ fn parse_lint_attribute( }; for lint in parse_tt_as_comma_sep_paths(args_tt).into_iter().flatten() { if let Some(lint) = lint.as_single_name_ref() { - job(rustc_stack.entry(lint.to_string()).or_default(), serevity); + job(rustc_stack.entry(lint.to_string()).or_default(), severity); } if let Some(tool) = lint.qualifier().and_then(|x| x.as_single_name_ref()) { if let Some(name_ref) = &lint.segment().and_then(|x| x.name_ref()) { if tool.to_string() == "clippy" { - job(clippy_stack.entry(name_ref.to_string()).or_default(), serevity); + job(clippy_stack.entry(name_ref.to_string()).or_default(), severity); } } } diff --git a/src/tools/rust-analyzer/crates/ide-ssr/src/matching.rs b/src/tools/rust-analyzer/crates/ide-ssr/src/matching.rs index a8e883690..60fcbbbd3 100644 --- a/src/tools/rust-analyzer/crates/ide-ssr/src/matching.rs +++ b/src/tools/rust-analyzer/crates/ide-ssr/src/matching.rs @@ -560,8 +560,10 @@ impl<'db, 'sema> Matcher<'db, 'sema> { placeholder_value.autoref_kind = self .sema .resolve_method_call_as_callable(code) - .and_then(|callable| callable.receiver_param(self.sema.db)) - .map(|(self_param, _)| self_param.kind()) + .and_then(|callable| { + let (self_param, _) = callable.receiver_param(self.sema.db)?; + Some(self_param.source(self.sema.db)?.value.kind()) + }) .unwrap_or(ast::SelfParamKind::Owned); } } diff --git a/src/tools/rust-analyzer/crates/ide/src/annotations.rs b/src/tools/rust-analyzer/crates/ide/src/annotations.rs index f994c284c..fb79b5dc2 100644 --- a/src/tools/rust-analyzer/crates/ide/src/annotations.rs +++ b/src/tools/rust-analyzer/crates/ide/src/annotations.rs @@ -94,10 +94,9 @@ pub(crate) fn annotations( enum_ .variants(db) .into_iter() - .map(|variant| { + .filter_map(|variant| { variant.source(db).and_then(|node| name_range(db, node, file_id)) }) - .flatten() .for_each(|range| { let (annotation_range, target_position) = mk_ranges(range); annotations.push(Annotation { diff --git a/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs b/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs index dd1d0d75c..f834f2ce5 100644 --- a/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs +++ b/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs @@ -74,18 +74,20 @@ pub(crate) fn incoming_calls( Some(calls.into_items()) } -pub(crate) fn outgoing_calls(db: &RootDatabase, position: FilePosition) -> Option<Vec<CallItem>> { +pub(crate) fn outgoing_calls( + db: &RootDatabase, + FilePosition { file_id, offset }: FilePosition, +) -> Option<Vec<CallItem>> { let sema = Semantics::new(db); - let file_id = position.file_id; let file = sema.parse(file_id); let file = file.syntax(); - let token = pick_best_token(file.token_at_offset(position.offset), |kind| match kind { + let token = pick_best_token(file.token_at_offset(offset), |kind| match kind { IDENT => 1, _ => 0, })?; let mut calls = CallLocations::default(); - sema.descend_into_macros(token) + sema.descend_into_macros(token, offset) .into_iter() .filter_map(|it| it.parent_ancestors().nth(1).and_then(ast::Item::cast)) .filter_map(|item| match item { diff --git a/src/tools/rust-analyzer/crates/ide/src/doc_links.rs b/src/tools/rust-analyzer/crates/ide/src/doc_links.rs index d240127f3..37a177622 100644 --- a/src/tools/rust-analyzer/crates/ide/src/doc_links.rs +++ b/src/tools/rust-analyzer/crates/ide/src/doc_links.rs @@ -16,6 +16,7 @@ use hir::{db::HirDatabase, Adt, AsAssocItem, AssocItem, AssocItemContainer, HasA use ide_db::{ base_db::{CrateOrigin, LangCrateOrigin, ReleaseChannel, SourceDatabase}, defs::{Definition, NameClass, NameRefClass}, + documentation::{docs_with_rangemap, Documentation, HasDocs}, helpers::pick_best_token, RootDatabase, }; @@ -131,19 +132,19 @@ pub(crate) fn remove_links(markdown: &str) -> String { // |=== pub(crate) fn external_docs( db: &RootDatabase, - position: &FilePosition, + FilePosition { file_id, offset }: FilePosition, target_dir: Option<&OsStr>, sysroot: Option<&OsStr>, ) -> Option<DocumentationLinks> { let sema = &Semantics::new(db); - let file = sema.parse(position.file_id).syntax().clone(); - let token = pick_best_token(file.token_at_offset(position.offset), |kind| match kind { + let file = sema.parse(file_id).syntax().clone(); + let token = pick_best_token(file.token_at_offset(offset), |kind| match kind { IDENT | INT_NUMBER | T![self] => 3, T!['('] | T![')'] => 2, kind if kind.is_trivia() => 0, _ => 1, })?; - let token = sema.descend_into_macros_single(token); + let token = sema.descend_into_macros_single(token, offset); let node = token.parent()?; let definition = match_ast! { @@ -171,7 +172,7 @@ pub(crate) fn external_docs( /// Extracts all links from a given markdown text returning the definition text range, link-text /// and the namespace if known. pub(crate) fn extract_definitions_from_docs( - docs: &hir::Documentation, + docs: &Documentation, ) -> Vec<(TextRange, String, Option<hir::Namespace>)> { Parser::new_with_broken_link_callback( docs.as_str(), @@ -285,7 +286,7 @@ impl DocCommentToken { let original_start = doc_token.text_range().start(); let relative_comment_offset = offset - original_start - prefix_len; - sema.descend_into_macros(doc_token).into_iter().find_map(|t| { + sema.descend_into_macros(doc_token, offset).into_iter().find_map(|t| { let (node, descended_prefix_len) = match_ast! { match t { ast::Comment(comment) => (t.parent()?, TextSize::try_from(comment.prefix().len()).ok()?), @@ -297,7 +298,7 @@ impl DocCommentToken { let abs_in_expansion_offset = token_start + relative_comment_offset + descended_prefix_len; let (attributes, def) = doc_attributes(sema, &node)?; - let (docs, doc_mapping) = attributes.docs_with_rangemap(sema.db)?; + let (docs, doc_mapping) = docs_with_rangemap(sema.db, &attributes)?; let (in_expansion_range, link, ns) = extract_definitions_from_docs(&docs).into_iter().find_map(|(range, link, ns)| { let mapped = doc_mapping.map(range)?; diff --git a/src/tools/rust-analyzer/crates/ide/src/doc_links/tests.rs b/src/tools/rust-analyzer/crates/ide/src/doc_links/tests.rs index 05a64b33b..9ae70ae66 100644 --- a/src/tools/rust-analyzer/crates/ide/src/doc_links/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/doc_links/tests.rs @@ -1,10 +1,11 @@ use std::ffi::OsStr; use expect_test::{expect, Expect}; -use hir::{HasAttrs, Semantics}; +use hir::Semantics; use ide_db::{ base_db::{FilePosition, FileRange}, defs::Definition, + documentation::{Documentation, HasDocs}, RootDatabase, }; use itertools::Itertools; @@ -78,7 +79,7 @@ fn check_doc_links(ra_fixture: &str) { fn def_under_cursor( sema: &Semantics<'_, RootDatabase>, position: &FilePosition, -) -> (Definition, hir::Documentation) { +) -> (Definition, Documentation) { let (docs, def) = sema .parse(position.file_id) .syntax() @@ -96,7 +97,7 @@ fn def_under_cursor( fn node_to_def( sema: &Semantics<'_, RootDatabase>, node: &SyntaxNode, -) -> Option<Option<(Option<hir::Documentation>, Definition)>> { +) -> Option<Option<(Option<Documentation>, Definition)>> { Some(match_ast! { match node { ast::SourceFile(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Module(def))), @@ -518,6 +519,62 @@ fn function(); } #[test] +fn doc_links_field() { + check_doc_links( + r#" +/// [`S::f`] +/// [`S2::f`] +/// [`T::0`] +/// [`U::a`] +/// [`E::A::f`] +/// [`E::B::0`] +struct S$0 { + f: i32, + //^ S::f + //^ S2::f +} +type S2 = S; +struct T(i32); + //^^^ T::0 +union U { + a: i32, + //^ U::a +} +enum E { + A { f: i32 }, + //^ E::A::f + B(i32), + //^^^ E::B::0 +} +"#, + ); +} + +#[test] +fn doc_links_field_via_self() { + check_doc_links( + r#" +/// [`Self::f`] +struct S$0 { + f: i32, + //^ Self::f +} +"#, + ); +} + +#[test] +fn doc_links_tuple_field_via_self() { + check_doc_links( + r#" +/// [`Self::0`] +struct S$0(i32); + //^^^ Self::0 +"#, + ); +} + +#[test] fn rewrite_html_root_url() { check_rewrite( r#" diff --git a/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs b/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs index d6bbf2bf7..119a9c7c3 100644 --- a/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs +++ b/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs @@ -40,28 +40,33 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option< // struct Bar; // ``` - let derive = sema.descend_into_macros(tok.clone()).into_iter().find_map(|descended| { - let hir_file = sema.hir_file_for(&descended.parent()?); - if !hir_file.is_derive_attr_pseudo_expansion(db) { - return None; - } + let derive = + sema.descend_into_macros(tok.clone(), 0.into()).into_iter().find_map(|descended| { + let hir_file = sema.hir_file_for(&descended.parent()?); + if !hir_file.is_derive_attr_pseudo_expansion(db) { + return None; + } - let name = descended.parent_ancestors().filter_map(ast::Path::cast).last()?.to_string(); - // up map out of the #[derive] expansion - let token = hir::InFile::new(hir_file, descended).upmap(db)?.value; - let attr = token.parent_ancestors().find_map(ast::Attr::cast)?; - let expansions = sema.expand_derive_macro(&attr)?; - let idx = attr - .token_tree()? - .token_trees_and_tokens() - .filter_map(NodeOrToken::into_token) - .take_while(|it| it != &token) - .filter(|it| it.kind() == T![,]) - .count(); - let expansion = - format(db, SyntaxKind::MACRO_ITEMS, position.file_id, expansions.get(idx).cloned()?); - Some(ExpandedMacro { name, expansion }) - }); + let name = descended.parent_ancestors().filter_map(ast::Path::cast).last()?.to_string(); + // up map out of the #[derive] expansion + let token = hir::InFile::new(hir_file, descended).upmap(db)?.value; + let attr = token.parent_ancestors().find_map(ast::Attr::cast)?; + let expansions = sema.expand_derive_macro(&attr)?; + let idx = attr + .token_tree()? + .token_trees_and_tokens() + .filter_map(NodeOrToken::into_token) + .take_while(|it| it != &token) + .filter(|it| it.kind() == T![,]) + .count(); + let expansion = format( + db, + SyntaxKind::MACRO_ITEMS, + position.file_id, + expansions.get(idx).cloned()?, + ); + Some(ExpandedMacro { name, expansion }) + }); if derive.is_some() { return derive; diff --git a/src/tools/rust-analyzer/crates/ide/src/extend_selection.rs b/src/tools/rust-analyzer/crates/ide/src/extend_selection.rs index f90618222..3d89599c5 100644 --- a/src/tools/rust-analyzer/crates/ide/src/extend_selection.rs +++ b/src/tools/rust-analyzer/crates/ide/src/extend_selection.rs @@ -17,8 +17,6 @@ use crate::FileRange; // Extends or shrinks the current selection to the encompassing syntactic construct // (expression, statement, item, module, etc). It works with multiple cursors. // -// This is a standard LSP feature and not a protocol extension. -// // |=== // | Editor | Shortcut // @@ -142,8 +140,10 @@ fn extend_tokens_from_range( // compute original mapped token range let extended = { - let fst_expanded = sema.descend_into_macros_single(first_token.clone()); - let lst_expanded = sema.descend_into_macros_single(last_token.clone()); + let fst_expanded = + sema.descend_into_macros_single(first_token.clone(), original_range.start()); + let lst_expanded = + sema.descend_into_macros_single(last_token.clone(), original_range.end()); let mut lca = algo::least_common_ancestor(&fst_expanded.parent()?, &lst_expanded.parent()?)?; lca = shallowest_node(&lca); @@ -154,13 +154,16 @@ fn extend_tokens_from_range( }; // Compute parent node range - let validate = |token: &SyntaxToken| -> bool { - let expanded = sema.descend_into_macros_single(token.clone()); - let parent = match expanded.parent() { - Some(it) => it, - None => return false, - }; - algo::least_common_ancestor(&extended, &parent).as_ref() == Some(&extended) + let validate = |offset: TextSize| { + let extended = &extended; + move |token: &SyntaxToken| -> bool { + let expanded = sema.descend_into_macros_single(token.clone(), offset); + let parent = match expanded.parent() { + Some(it) => it, + None => return false, + }; + algo::least_common_ancestor(extended, &parent).as_ref() == Some(extended) + } }; // Find the first and last text range under expanded parent @@ -168,14 +171,14 @@ fn extend_tokens_from_range( let token = token.prev_token()?; skip_trivia_token(token, Direction::Prev) }) - .take_while(validate) + .take_while(validate(original_range.start())) .last()?; let last = successors(Some(last_token), |token| { let token = token.next_token()?; skip_trivia_token(token, Direction::Next) }) - .take_while(validate) + .take_while(validate(original_range.end())) .last()?; let range = first.text_range().cover(last.text_range()); diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs b/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs index c39c696cf..7e0fab426 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs @@ -20,16 +20,16 @@ use crate::{ // - fields in patterns will navigate to the field declaration of the struct, union or variant pub(crate) fn goto_declaration( db: &RootDatabase, - position: FilePosition, + position @ FilePosition { file_id, offset }: FilePosition, ) -> Option<RangeInfo<Vec<NavigationTarget>>> { let sema = Semantics::new(db); - let file = sema.parse(position.file_id).syntax().clone(); + let file = sema.parse(file_id).syntax().clone(); let original_token = file - .token_at_offset(position.offset) + .token_at_offset(offset) .find(|it| matches!(it.kind(), IDENT | T![self] | T![super] | T![crate] | T![Self]))?; let range = original_token.text_range(); let info: Vec<NavigationTarget> = sema - .descend_into_macros(original_token) + .descend_into_macros(original_token, offset) .iter() .filter_map(|token| { let parent = token.parent()?; 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 21471ab2a..e09b9f391 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs @@ -29,45 +29,39 @@ use syntax::{ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TextRange, T}; // image::https://user-images.githubusercontent.com/48062697/113065563-025fbe00-91b1-11eb-83e4-a5a703610b23.gif[] pub(crate) fn goto_definition( db: &RootDatabase, - position: FilePosition, + FilePosition { file_id, offset }: FilePosition, ) -> Option<RangeInfo<Vec<NavigationTarget>>> { let sema = &Semantics::new(db); - let file = sema.parse(position.file_id).syntax().clone(); - let original_token = - pick_best_token(file.token_at_offset(position.offset), |kind| match kind { - IDENT - | INT_NUMBER - | LIFETIME_IDENT - | T![self] - | T![super] - | T![crate] - | T![Self] - | COMMENT => 4, - // index and prefix ops - T!['['] | T![']'] | T![?] | T![*] | T![-] | T![!] => 3, - kind if kind.is_keyword() => 2, - T!['('] | T![')'] => 2, - kind if kind.is_trivia() => 0, - _ => 1, - })?; + let file = sema.parse(file_id).syntax().clone(); + let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind { + IDENT + | INT_NUMBER + | LIFETIME_IDENT + | T![self] + | T![super] + | T![crate] + | T![Self] + | COMMENT => 4, + // index and prefix ops + T!['['] | T![']'] | T![?] | T![*] | T![-] | T![!] => 3, + kind if kind.is_keyword() => 2, + T!['('] | T![')'] => 2, + kind if kind.is_trivia() => 0, + _ => 1, + })?; if let Some(doc_comment) = token_as_doc_comment(&original_token) { - return doc_comment.get_definition_with_descend_at( - sema, - position.offset, - |def, _, link_range| { - let nav = def.try_to_nav(db)?; - Some(RangeInfo::new(link_range, vec![nav])) - }, - ); + return doc_comment.get_definition_with_descend_at(sema, offset, |def, _, link_range| { + let nav = def.try_to_nav(db)?; + Some(RangeInfo::new(link_range, vec![nav])) + }); } let navs = sema - .descend_into_macros(original_token.clone()) + .descend_into_macros(original_token.clone(), offset) .into_iter() .filter_map(|token| { let parent = token.parent()?; if let Some(tt) = ast::TokenTree::cast(parent) { - if let Some(x) = try_lookup_include_path(sema, tt, token.clone(), position.file_id) - { + if let Some(x) = try_lookup_include_path(sema, tt, token.clone(), file_id) { return Some(vec![x]); } } diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs b/src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs index 37166bdbd..544c6b423 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs @@ -22,20 +22,19 @@ use crate::{FilePosition, NavigationTarget, RangeInfo, TryToNav}; // image::https://user-images.githubusercontent.com/48062697/113065566-02f85480-91b1-11eb-9288-aaad8abd8841.gif[] pub(crate) fn goto_implementation( db: &RootDatabase, - position: FilePosition, + FilePosition { file_id, offset }: FilePosition, ) -> Option<RangeInfo<Vec<NavigationTarget>>> { let sema = Semantics::new(db); - let source_file = sema.parse(position.file_id); + let source_file = sema.parse(file_id); let syntax = source_file.syntax().clone(); - let original_token = - pick_best_token(syntax.token_at_offset(position.offset), |kind| match kind { - IDENT | T![self] | INT_NUMBER => 1, - _ => 0, - })?; + let original_token = pick_best_token(syntax.token_at_offset(offset), |kind| match kind { + IDENT | T![self] | INT_NUMBER => 1, + _ => 0, + })?; let range = original_token.text_range(); let navs = - sema.descend_into_macros(original_token) + sema.descend_into_macros(original_token, offset) .into_iter() .filter_map(|token| token.parent().and_then(ast::NameLike::cast)) .filter_map(|node| match &node { diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs b/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs index 6048990f7..955923d76 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs @@ -16,13 +16,13 @@ use crate::{FilePosition, NavigationTarget, RangeInfo, TryToNav}; // image::https://user-images.githubusercontent.com/48062697/113020657-b560f500-917a-11eb-9007-0f809733a338.gif[] pub(crate) fn goto_type_definition( db: &RootDatabase, - position: FilePosition, + FilePosition { file_id, offset }: FilePosition, ) -> Option<RangeInfo<Vec<NavigationTarget>>> { let sema = hir::Semantics::new(db); - let file: ast::SourceFile = sema.parse(position.file_id); + let file: ast::SourceFile = sema.parse(file_id); let token: SyntaxToken = - pick_best_token(file.syntax().token_at_offset(position.offset), |kind| match kind { + pick_best_token(file.syntax().token_at_offset(offset), |kind| match kind { IDENT | INT_NUMBER | T![self] => 2, kind if kind.is_trivia() => 0, _ => 1, @@ -37,7 +37,7 @@ pub(crate) fn goto_type_definition( } }; let range = token.text_range(); - sema.descend_into_macros(token) + sema.descend_into_macros(token, offset) .into_iter() .filter_map(|token| { let ty = sema diff --git a/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs b/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs index 43e89a334..46a0464e9 100644 --- a/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs +++ b/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs @@ -15,6 +15,7 @@ use syntax::{ SyntaxKind::{self, IDENT, INT_NUMBER}, SyntaxNode, SyntaxToken, TextRange, T, }; +use text_edit::TextSize; use crate::{navigation_target::ToNav, references, NavigationTarget, TryToNav}; @@ -51,7 +52,7 @@ pub struct HighlightRelatedConfig { pub(crate) fn highlight_related( sema: &Semantics<'_, RootDatabase>, config: HighlightRelatedConfig, - FilePosition { offset, file_id }: FilePosition, + pos @ FilePosition { offset, file_id }: FilePosition, ) -> Option<Vec<HighlightedRange>> { let _p = profile::span("highlight_related"); let syntax = sema.parse(file_id).syntax().clone(); @@ -79,7 +80,7 @@ pub(crate) fn highlight_related( } T![|] if config.closure_captures => highlight_closure_captures(sema, token, file_id), T![move] if config.closure_captures => highlight_closure_captures(sema, token, file_id), - _ if config.references => highlight_references(sema, &syntax, token, file_id), + _ if config.references => highlight_references(sema, &syntax, token, pos), _ => None, } } @@ -129,9 +130,9 @@ fn highlight_references( sema: &Semantics<'_, RootDatabase>, node: &SyntaxNode, token: SyntaxToken, - file_id: FileId, + FilePosition { file_id, offset }: FilePosition, ) -> Option<Vec<HighlightedRange>> { - let defs = find_defs(sema, token.clone()); + let defs = find_defs(sema, token.clone(), offset); let usages = defs .iter() .filter_map(|&d| { @@ -455,8 +456,12 @@ fn cover_range(r0: Option<TextRange>, r1: Option<TextRange>) -> Option<TextRange } } -fn find_defs(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> FxHashSet<Definition> { - sema.descend_into_macros(token) +fn find_defs( + sema: &Semantics<'_, RootDatabase>, + token: SyntaxToken, + offset: TextSize, +) -> FxHashSet<Definition> { + sema.descend_into_macros(token, offset) .into_iter() .filter_map(|token| IdentClass::classify_token(sema, &token)) .map(IdentClass::definitions_no_ops) diff --git a/src/tools/rust-analyzer/crates/ide/src/hover.rs b/src/tools/rust-analyzer/crates/ide/src/hover.rs index 40659e6c2..21934b948 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover.rs @@ -162,9 +162,9 @@ fn hover_simple( // prefer descending the same token kind in attribute expansions, in normal macros text // equivalency is more important let descended = if in_attr { - [sema.descend_into_macros_with_kind_preference(original_token.clone())].into() + [sema.descend_into_macros_with_kind_preference(original_token.clone(), offset)].into() } else { - sema.descend_into_macros_with_same_text(original_token.clone()) + sema.descend_into_macros_with_same_text(original_token.clone(), offset) }; let descended = || descended.iter(); 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 a33a6ee18..f72ce37d1 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs @@ -3,12 +3,13 @@ use std::fmt::Display; use either::Either; use hir::{ - Adt, AsAssocItem, AttributeTemplate, CaptureKind, HasAttrs, HasSource, HirDisplay, Layout, - LayoutError, Semantics, TypeInfo, + Adt, AsAssocItem, AttributeTemplate, CaptureKind, HasSource, HirDisplay, Layout, LayoutError, + Semantics, TypeInfo, }; use ide_db::{ base_db::SourceDatabase, defs::Definition, + documentation::{Documentation, HasDocs}, famous_defs::FamousDefs, generated::lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES}, syntax_helpers::insert_whitespace_into_node, @@ -470,7 +471,7 @@ pub(super) fn definition( Definition::SelfType(impl_def) => { impl_def.self_ty(db).as_adt().map(|adt| label_and_docs(db, adt))? } - Definition::GenericParam(it) => label_and_docs(db, it), + Definition::GenericParam(it) => (it.display(db).to_string(), None), Definition::Label(it) => return Some(Markup::fenced_block(&it.name(db).display(db))), Definition::ExternCrateDecl(it) => label_and_docs(db, it), // FIXME: We should be able to show more info about these @@ -616,9 +617,9 @@ fn render_builtin_attr(db: &RootDatabase, attr: hir::BuiltinAttr) -> Option<Mark markup(Some(docs.replace('*', "\\*")), desc, None) } -fn label_and_docs<D>(db: &RootDatabase, def: D) -> (String, Option<hir::Documentation>) +fn label_and_docs<D>(db: &RootDatabase, def: D) -> (String, Option<Documentation>) where - D: HasAttrs + HirDisplay, + D: HasDocs + HirDisplay, { let label = def.display(db).to_string(); let docs = def.docs(db); @@ -631,9 +632,9 @@ fn label_and_layout_info_and_docs<D, E, E2>( config: &HoverConfig, layout_extractor: E, layout_offset_extractor: E2, -) -> (String, Option<hir::Documentation>) +) -> (String, Option<Documentation>) where - D: HasAttrs + HirDisplay, + D: HasDocs + HirDisplay, E: Fn(&D) -> Result<Layout, LayoutError>, E2: Fn(&Layout) -> Option<u64>, { @@ -657,9 +658,9 @@ fn label_value_and_layout_info_and_docs<D, E, E2, E3, V>( value_extractor: E, layout_extractor: E2, layout_tag_extractor: E3, -) -> (String, Option<hir::Documentation>) +) -> (String, Option<Documentation>) where - D: HasAttrs + HirDisplay, + D: HasDocs + HirDisplay, E: Fn(&D) -> Option<V>, E2: Fn(&D) -> Result<Layout, LayoutError>, E3: Fn(&Layout) -> Option<usize>, @@ -686,9 +687,9 @@ fn label_value_and_docs<D, E, V>( db: &RootDatabase, def: D, value_extractor: E, -) -> (String, Option<hir::Documentation>) +) -> (String, Option<Documentation>) where - D: HasAttrs + HirDisplay, + D: HasDocs + HirDisplay, E: Fn(&D) -> Option<V>, V: Display, { 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 ddc71dffa..81d6db564 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -1557,6 +1557,49 @@ fn test_hover_function_show_types() { } #[test] +fn test_hover_function_associated_type_params() { + check( + r#" +trait Foo { type Bar; } +impl Foo for i32 { type Bar = i64; } +fn foo(arg: <i32 as Foo>::Bar) {} +fn main() { foo$0; } +"#, + expect![[r#" + *foo* + + ```rust + test + ``` + + ```rust + fn foo(arg: <i32 as Foo>::Bar) + ``` + "#]], + ); + + check( + r#" +trait Foo<T> { type Bar<U>; } +impl Foo<i64> for i32 { type Bar<U> = i32; } +fn foo(arg: <<i32 as Foo<i64>>::Bar<i8> as Foo<i64>>::Bar<i8>) {} +fn main() { foo$0; } +"#, + expect![[r#" + *foo* + + ```rust + test + ``` + + ```rust + fn foo(arg: <<i32 as Foo<i64>>::Bar<i8> as Foo<i64>>::Bar<i8>) + ``` + "#]], + ); +} + +#[test] fn test_hover_function_pointer_show_identifiers() { check( r#"type foo$0 = fn(a: i32, b: i32) -> i32;"#, @@ -3292,7 +3335,50 @@ struct S$0T<const C: usize = 1, T = Foo>(T); ``` ```rust - struct ST<const C: usize, T = Foo> + struct ST<const C: usize = 1, T = Foo> + ``` + "#]], + ); +} + +#[test] +fn const_generic_default_value() { + check( + r#" +struct Foo; +struct S$0T<const C: usize = {40 + 2}, T = Foo>(T); +"#, + expect![[r#" + *ST* + + ```rust + test + ``` + + ```rust + struct ST<const C: usize = {const}, T = Foo> + ``` + "#]], + ); +} + +#[test] +fn const_generic_default_value_2() { + check( + r#" +struct Foo; +const VAL = 1; +struct S$0T<const C: usize = VAL, T = Foo>(T); +"#, + expect![[r#" + *ST* + + ```rust + test + ``` + + ```rust + struct ST<const C: usize = VAL, T = Foo> ``` "#]], ); @@ -6469,3 +6555,42 @@ fn test() { "#]], ); } + +#[test] +fn generic_params_disabled_by_cfg() { + check( + r#" +struct S<#[cfg(never)] T>; +fn test() { + let s$0: S = S; +} +"#, + expect![[r#" + *s* + + ```rust + let s: S // size = 0, align = 1 + ``` + "#]], + ); +} + +#[test] +fn format_args_arg() { + check( + r#" +//- minicore: fmt +fn test() { + let foo = 0; + format_args!("{}", foo$0); +} +"#, + expect![[r#" + *foo* + + ```rust + let foo: i32 // size = 4, align = 4 + ``` + "#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs index 292591674..a5d070fe7 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs @@ -52,6 +52,28 @@ pub struct InlayHintsConfig { pub closure_style: ClosureStyle, pub max_length: Option<usize>, pub closing_brace_hints_min_lines: Option<usize>, + pub fields_to_resolve: InlayFieldsToResolve, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct InlayFieldsToResolve { + pub resolve_text_edits: bool, + pub resolve_hint_tooltip: bool, + pub resolve_label_tooltip: bool, + pub resolve_label_location: bool, + pub resolve_label_command: bool, +} + +impl InlayFieldsToResolve { + pub const fn empty() -> Self { + Self { + resolve_text_edits: false, + resolve_hint_tooltip: false, + resolve_label_tooltip: false, + resolve_label_location: false, + resolve_label_command: false, + } + } } #[derive(Clone, Debug, PartialEq, Eq)] @@ -123,11 +145,13 @@ pub struct InlayHint { pub label: InlayHintLabel, /// Text edit to apply when "accepting" this inlay hint. pub text_edit: Option<TextEdit>, + pub needs_resolve: bool, } impl InlayHint { fn closing_paren_after(kind: InlayKind, range: TextRange) -> InlayHint { InlayHint { + needs_resolve: false, range, kind, label: InlayHintLabel::from(")"), @@ -139,6 +163,7 @@ impl InlayHint { } fn opening_paren_before(kind: InlayKind, range: TextRange) -> InlayHint { InlayHint { + needs_resolve: false, range, kind, label: InlayHintLabel::from("("), @@ -196,6 +221,10 @@ impl InlayHintLabel { }), } } + + pub fn needs_resolve(&self) -> bool { + self.parts.iter().any(|part| part.linked_location.is_some() || part.tooltip.is_some()) + } } impl From<String> for InlayHintLabel { @@ -529,6 +558,7 @@ fn closure_has_block_body(closure: &ast::ClosureExpr) -> bool { #[cfg(test)] mod tests { + use expect_test::Expect; use hir::ClosureStyle; use itertools::Itertools; @@ -538,7 +568,7 @@ mod tests { use crate::DiscriminantHints; use crate::{fixture, inlay_hints::InlayHintsConfig, LifetimeElisionHints}; - use super::ClosureReturnTypeHints; + use super::{ClosureReturnTypeHints, InlayFieldsToResolve}; pub(super) const DISABLED_CONFIG: InlayHintsConfig = InlayHintsConfig { discriminant_hints: DiscriminantHints::Never, @@ -559,6 +589,7 @@ mod tests { param_names_for_lifetime_elision_hints: false, max_length: None, closing_brace_hints_min_lines: None, + fields_to_resolve: InlayFieldsToResolve::empty(), }; pub(super) const TEST_CONFIG: InlayHintsConfig = InlayHintsConfig { type_hints: true, diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs index 6d6bd315e..631807d99 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs @@ -137,21 +137,23 @@ pub(super) fn hints( } _ => continue, }; + let label = InlayHintLabel::simple( + if postfix { format!(".{}", text.trim_end()) } else { text.to_owned() }, + Some(InlayTooltip::Markdown(format!( + "`{}` → `{}` ({coercion} coercion)", + source.display(sema.db), + target.display(sema.db), + ))), + None, + ); acc.push(InlayHint { + needs_resolve: label.needs_resolve(), range: expr.syntax().text_range(), pad_left: false, pad_right: false, position: if postfix { InlayHintPosition::After } else { InlayHintPosition::Before }, kind: InlayKind::Adjustment, - label: InlayHintLabel::simple( - if postfix { format!(".{}", text.trim_end()) } else { text.to_owned() }, - Some(InlayTooltip::Markdown(format!( - "`{}` → `{}` ({coercion} coercion)", - source.display(sema.db), - target.display(sema.db), - ))), - None, - ), + label, text_edit: None, }); } diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs index 07b9f9cc1..680035c72 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs @@ -99,6 +99,7 @@ pub(super) fn hints( None => pat.syntax().text_range(), }; acc.push(InlayHint { + needs_resolve: label.needs_resolve() || text_edit.is_some(), range: match type_ascriptable { Some(Some(t)) => text_range.cover(t.text_range()), _ => text_range, diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/binding_mode.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/binding_mode.rs index 343cf17e5..35504ffa7 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/binding_mode.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/binding_mode.rs @@ -50,9 +50,10 @@ pub(super) fn hints( _ => return, }; acc.push(InlayHint { + needs_resolve: false, range, kind: InlayKind::BindingMode, - label: r.to_string().into(), + label: r.into(), text_edit: None, position: InlayHintPosition::Before, pad_left: false, @@ -68,9 +69,10 @@ pub(super) fn hints( hir::BindingMode::Ref(Mutability::Shared) => "ref", }; acc.push(InlayHint { + needs_resolve: false, range: pat.syntax().text_range(), kind: InlayKind::BindingMode, - label: bm.to_string().into(), + label: bm.into(), text_edit: None, position: InlayHintPosition::Before, pad_left: false, diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs index b621a8dda..12e46c0f8 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs @@ -57,10 +57,12 @@ pub(super) fn hints( } } } + let label = label_of_ty(famous_defs, config, &ty)?; acc.push(InlayHint { + needs_resolve: label.needs_resolve(), range: expr.syntax().text_range(), kind: InlayKind::Chaining, - label: label_of_ty(famous_defs, config, &ty)?, + label, text_edit: None, position: InlayHintPosition::After, pad_left: true, @@ -128,6 +130,7 @@ fn main() { "", ], text_edit: None, + needs_resolve: true, }, InlayHint { range: 147..154, @@ -152,6 +155,7 @@ fn main() { "", ], text_edit: None, + needs_resolve: true, }, ] "#]], @@ -221,6 +225,7 @@ fn main() { "", ], text_edit: None, + needs_resolve: true, }, InlayHint { range: 143..179, @@ -245,6 +250,7 @@ fn main() { "", ], text_edit: None, + needs_resolve: true, }, ] "#]], @@ -298,6 +304,7 @@ fn main() { "", ], text_edit: None, + needs_resolve: true, }, InlayHint { range: 143..179, @@ -322,6 +329,7 @@ fn main() { "", ], text_edit: None, + needs_resolve: true, }, ] "#]], @@ -389,6 +397,7 @@ fn main() { "<i32, bool>>", ], text_edit: None, + needs_resolve: true, }, InlayHint { range: 246..265, @@ -426,6 +435,7 @@ fn main() { "<i32, bool>>", ], text_edit: None, + needs_resolve: true, }, ] "#]], @@ -474,7 +484,7 @@ fn main() { file_id: FileId( 1, ), - range: 9289..9297, + range: 10739..10747, }, ), tooltip: "", @@ -487,7 +497,7 @@ fn main() { file_id: FileId( 1, ), - range: 9321..9325, + range: 10771..10775, }, ), tooltip: "", @@ -495,6 +505,7 @@ fn main() { " = ()>", ], text_edit: None, + needs_resolve: true, }, InlayHint { range: 174..224, @@ -511,7 +522,7 @@ fn main() { file_id: FileId( 1, ), - range: 9289..9297, + range: 10739..10747, }, ), tooltip: "", @@ -524,7 +535,7 @@ fn main() { file_id: FileId( 1, ), - range: 9321..9325, + range: 10771..10775, }, ), tooltip: "", @@ -532,6 +543,7 @@ fn main() { " = ()>", ], text_edit: None, + needs_resolve: true, }, InlayHint { range: 174..206, @@ -548,7 +560,7 @@ fn main() { file_id: FileId( 1, ), - range: 9289..9297, + range: 10739..10747, }, ), tooltip: "", @@ -561,7 +573,7 @@ fn main() { file_id: FileId( 1, ), - range: 9321..9325, + range: 10771..10775, }, ), tooltip: "", @@ -569,6 +581,7 @@ fn main() { " = ()>", ], text_edit: None, + needs_resolve: true, }, InlayHint { range: 174..189, @@ -593,6 +606,7 @@ fn main() { "", ], text_edit: None, + needs_resolve: true, }, ] "#]], @@ -655,6 +669,7 @@ fn main() { ], }, ), + needs_resolve: true, }, InlayHint { range: 145..185, @@ -679,6 +694,7 @@ fn main() { "", ], text_edit: None, + needs_resolve: true, }, InlayHint { range: 145..168, @@ -703,6 +719,7 @@ fn main() { "", ], text_edit: None, + needs_resolve: true, }, InlayHint { range: 222..228, @@ -725,6 +742,7 @@ fn main() { }, ], text_edit: None, + needs_resolve: true, }, ] "#]], diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closing_brace.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closing_brace.rs index 2cefd5acd..2b68538c1 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closing_brace.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closing_brace.rs @@ -109,6 +109,7 @@ pub(super) fn hints( let linked_location = name_range.map(|range| FileRange { file_id, range }); acc.push(InlayHint { + needs_resolve: linked_location.is_some(), range: closing_token.text_range(), kind: InlayKind::ClosingBrace, label: InlayHintLabel::simple(label, None, linked_location), diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_captures.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_captures.rs index 9d5defcbb..d691303c1 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_captures.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_captures.rs @@ -31,9 +31,10 @@ pub(super) fn hints( let range = closure.syntax().first_token()?.prev_token()?.text_range(); let range = TextRange::new(range.end() - TextSize::from(1), range.end()); acc.push(InlayHint { + needs_resolve: false, range, kind: InlayKind::ClosureCapture, - label: InlayHintLabel::simple("move", None, None), + label: InlayHintLabel::from("move"), text_edit: None, position: InlayHintPosition::After, pad_left: false, @@ -43,6 +44,7 @@ pub(super) fn hints( } }; acc.push(InlayHint { + needs_resolve: false, range: move_kw_range, kind: InlayKind::ClosureCapture, label: InlayHintLabel::from("("), @@ -59,23 +61,25 @@ pub(super) fn hints( // force cache the source file, otherwise sema lookup will potentially panic _ = sema.parse_or_expand(source.file()); + let label = InlayHintLabel::simple( + format!( + "{}{}", + match capture.kind() { + hir::CaptureKind::SharedRef => "&", + hir::CaptureKind::UniqueSharedRef => "&unique ", + hir::CaptureKind::MutableRef => "&mut ", + hir::CaptureKind::Move => "", + }, + capture.display_place(sema.db) + ), + None, + source.name().and_then(|name| name.syntax().original_file_range_opt(sema.db)), + ); acc.push(InlayHint { + needs_resolve: label.needs_resolve(), range: move_kw_range, kind: InlayKind::ClosureCapture, - label: InlayHintLabel::simple( - format!( - "{}{}", - match capture.kind() { - hir::CaptureKind::SharedRef => "&", - hir::CaptureKind::UniqueSharedRef => "&unique ", - hir::CaptureKind::MutableRef => "&mut ", - hir::CaptureKind::Move => "", - }, - capture.display_place(sema.db) - ), - None, - source.name().and_then(|name| name.syntax().original_file_range_opt(sema.db)), - ), + label, text_edit: None, position: InlayHintPosition::After, pad_left: false, @@ -84,9 +88,10 @@ pub(super) fn hints( if idx != last { acc.push(InlayHint { + needs_resolve: false, range: move_kw_range, kind: InlayKind::ClosureCapture, - label: InlayHintLabel::simple(", ", None, None), + label: InlayHintLabel::from(", "), text_edit: None, position: InlayHintPosition::After, pad_left: false, @@ -95,6 +100,7 @@ pub(super) fn hints( } } acc.push(InlayHint { + needs_resolve: false, range: move_kw_range, kind: InlayKind::ClosureCapture, label: InlayHintLabel::from(")"), diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_ret.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_ret.rs index 3b41db0f1..204967cd7 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_ret.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_ret.rs @@ -64,6 +64,7 @@ pub(super) fn hints( }; acc.push(InlayHint { + needs_resolve: label.needs_resolve() || text_edit.is_some(), range: param_list.syntax().text_range(), kind: InlayKind::Type, label, diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs index c4d2ac75c..26dc6fa8b 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs @@ -79,6 +79,7 @@ fn variant_hints( None, ); acc.push(InlayHint { + needs_resolve: label.needs_resolve(), range: match eq_token { Some(t) => range.cover(t.text_range()), _ => range, diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/fn_lifetime_fn.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/fn_lifetime_fn.rs index 5fce11b78..7b05e32ad 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/fn_lifetime_fn.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/fn_lifetime_fn.rs @@ -22,6 +22,7 @@ pub(super) fn hints( } let mk_lt_hint = |t: SyntaxToken, label: String| InlayHint { + needs_resolve: false, range: t.text_range(), kind: InlayKind::Lifetime, label: label.into(), @@ -185,6 +186,7 @@ pub(super) fn hints( let angle_tok = gpl.l_angle_token()?; let is_empty = gpl.generic_params().next().is_none(); acc.push(InlayHint { + needs_resolve: false, range: angle_tok.text_range(), kind: InlayKind::Lifetime, label: format!( @@ -200,6 +202,7 @@ pub(super) fn hints( }); } (None, allocated_lifetimes) => acc.push(InlayHint { + needs_resolve: false, range: func.name()?.syntax().text_range(), kind: InlayKind::GenericParamList, label: format!("<{}>", allocated_lifetimes.iter().format(", "),).into(), diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_static.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_static.rs index fc297a8d8..f18e6421c 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_static.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_static.rs @@ -31,9 +31,10 @@ pub(super) fn hints( if ty.lifetime().is_none() { let t = ty.amp_token()?; acc.push(InlayHint { + needs_resolve: false, range: t.text_range(), kind: InlayKind::Lifetime, - label: "'static".to_owned().into(), + label: "'static".into(), text_edit: None, position: InlayHintPosition::After, pad_left: false, diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/param_name.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/param_name.rs index c4f43f411..b4260d825 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/param_name.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/param_name.rs @@ -57,6 +57,7 @@ pub(super) fn hints( let label = InlayHintLabel::simple(format!("{param_name}{colon}"), None, linked_location); InlayHint { + needs_resolve: label.needs_resolve(), range, kind: InlayKind::Parameter, label, diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs index bf77d55d5..aee03d218 100644 --- a/src/tools/rust-analyzer/crates/ide/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide/src/lib.rs @@ -91,9 +91,9 @@ pub use crate::{ MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, }, inlay_hints::{ - AdjustmentHints, AdjustmentHintsMode, ClosureReturnTypeHints, DiscriminantHints, InlayHint, - InlayHintLabel, InlayHintLabelPart, InlayHintPosition, InlayHintsConfig, InlayKind, - InlayTooltip, LifetimeElisionHints, + AdjustmentHints, AdjustmentHintsMode, ClosureReturnTypeHints, DiscriminantHints, + InlayFieldsToResolve, InlayHint, InlayHintLabel, InlayHintLabelPart, InlayHintPosition, + InlayHintsConfig, InlayKind, InlayTooltip, LifetimeElisionHints, }, join_lines::JoinLinesConfig, markup::Markup, @@ -111,7 +111,7 @@ pub use crate::{ HighlightConfig, HlRange, }, }; -pub use hir::{Documentation, Semantics}; +pub use hir::Semantics; pub use ide_assists::{ Assist, AssistConfig, AssistId, AssistKind, AssistResolveStrategy, SingleResolve, }; @@ -124,6 +124,7 @@ pub use ide_db::{ Cancelled, Change, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRoot, SourceRootId, }, + documentation::Documentation, label::Label, line_index::{LineCol, LineIndex}, search::{ReferenceCategory, SearchScope}, @@ -484,7 +485,7 @@ impl Analysis { sysroot: Option<&OsStr>, ) -> Cancellable<doc_links::DocumentationLinks> { self.with_db(|db| { - doc_links::external_docs(db, &position, target_dir, sysroot).unwrap_or_default() + doc_links::external_docs(db, position, target_dir, sysroot).unwrap_or_default() }) } diff --git a/src/tools/rust-analyzer/crates/ide/src/moniker.rs b/src/tools/rust-analyzer/crates/ide/src/moniker.rs index 17f3771b1..2ca2b5b1d 100644 --- a/src/tools/rust-analyzer/crates/ide/src/moniker.rs +++ b/src/tools/rust-analyzer/crates/ide/src/moniker.rs @@ -99,7 +99,7 @@ pub(crate) fn moniker( }); } let navs = sema - .descend_into_macros(original_token.clone()) + .descend_into_macros(original_token.clone(), offset) .into_iter() .filter_map(|token| { IdentClass::classify_token(sema, &token).map(IdentClass::definitions_no_ops).map(|it| { diff --git a/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs b/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs index d1479dd1e..32f211c6b 100644 --- a/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs +++ b/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs @@ -4,14 +4,15 @@ use std::fmt; use either::Either; use hir::{ - symbols::FileSymbol, AssocItem, Documentation, FieldSource, HasAttrs, HasContainer, HasSource, - HirDisplay, HirFileId, InFile, LocalSource, ModuleSource, + symbols::FileSymbol, AssocItem, FieldSource, HasContainer, HasSource, HirDisplay, HirFileId, + InFile, LocalSource, ModuleSource, }; use ide_db::{ base_db::{FileId, FileRange}, - SymbolKind, + defs::Definition, + documentation::{Documentation, HasDocs}, + RootDatabase, SymbolKind, }; -use ide_db::{defs::Definition, RootDatabase}; use stdx::never; use syntax::{ ast::{self, HasName}, @@ -175,8 +176,12 @@ impl TryToNav for FileSymbol { Some(NavigationTarget { file_id: full_range.file_id, - name: if self.is_alias { self.def.name(db)?.to_smol_str() } else { self.name.clone() }, - alias: if self.is_alias { Some(self.name.clone()) } else { None }, + name: self + .is_alias + .then(|| self.def.name(db)) + .flatten() + .map_or_else(|| self.name.clone(), |it| it.to_smol_str()), + alias: self.is_alias.then(|| self.name.clone()), kind: Some(hir::ModuleDefId::from(self.def).into()), full_range: full_range.range, focus_range, @@ -323,7 +328,7 @@ impl ToNavFromAst for hir::TraitAlias { impl<D> TryToNav for D where - D: HasSource + ToNavFromAst + Copy + HasAttrs + HirDisplay, + D: HasSource + ToNavFromAst + Copy + HasDocs + HirDisplay, D::Ast: ast::HasName, { fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> { diff --git a/src/tools/rust-analyzer/crates/ide/src/references.rs b/src/tools/rust-analyzer/crates/ide/src/references.rs index 813f9ed94..2d0295692 100644 --- a/src/tools/rust-analyzer/crates/ide/src/references.rs +++ b/src/tools/rust-analyzer/crates/ide/src/references.rs @@ -126,7 +126,7 @@ pub(crate) fn find_defs<'a>( ) }); token.map(|token| { - sema.descend_into_macros_with_same_text(token) + sema.descend_into_macros_with_same_text(token, offset) .into_iter() .filter_map(|it| ast::NameLike::cast(it.parent()?)) .filter_map(move |name_like| { diff --git a/src/tools/rust-analyzer/crates/ide/src/rename.rs b/src/tools/rust-analyzer/crates/ide/src/rename.rs index dae8e71e8..ac9df5ed6 100644 --- a/src/tools/rust-analyzer/crates/ide/src/rename.rs +++ b/src/tools/rust-analyzer/crates/ide/src/rename.rs @@ -2634,4 +2634,33 @@ use qux as frob; // ", // ); } + + #[test] + fn disallow_renaming_for_non_local_definition() { + check( + "Baz", + r#" +//- /lib.rs crate:lib new_source_root:library +pub struct S; +//- /main.rs crate:main deps:lib new_source_root:local +use lib::S$0; +"#, + "error: Cannot rename a non-local definition.", + ); + } + + #[test] + fn disallow_renaming_for_builtin_macros() { + check( + "Baz", + r#" +//- minicore: derive, hash +//- /main.rs crate:main +use core::hash::Hash; +#[derive(H$0ash)] +struct A; + "#, + "error: Cannot rename a non-local definition.", + ) + } } diff --git a/src/tools/rust-analyzer/crates/ide/src/runnables.rs b/src/tools/rust-analyzer/crates/ide/src/runnables.rs index 5f87a7855..2d528c642 100644 --- a/src/tools/rust-analyzer/crates/ide/src/runnables.rs +++ b/src/tools/rust-analyzer/crates/ide/src/runnables.rs @@ -7,6 +7,7 @@ use ide_assists::utils::test_related_attribute; use ide_db::{ base_db::{FilePosition, FileRange}, defs::Definition, + documentation::docs_from_attrs, helpers::visit_file_defs, search::SearchScope, FxHashMap, FxHashSet, RootDatabase, SymbolKind, @@ -496,7 +497,7 @@ const RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE: &[&str] = &["", "rust", "should_panic", "edition2015", "edition2018", "edition2021"]; fn has_runnable_doc_test(attrs: &hir::Attrs) -> bool { - attrs.docs().map_or(false, |doc| { + docs_from_attrs(attrs).map_or(false, |doc| { let mut in_code_block = false; for line in String::from(doc).lines() { diff --git a/src/tools/rust-analyzer/crates/ide/src/signature_help.rs b/src/tools/rust-analyzer/crates/ide/src/signature_help.rs index 7795be54e..e020b52e1 100644 --- a/src/tools/rust-analyzer/crates/ide/src/signature_help.rs +++ b/src/tools/rust-analyzer/crates/ide/src/signature_help.rs @@ -4,12 +4,11 @@ use std::collections::BTreeSet; use either::Either; -use hir::{ - AssocItem, GenericParam, HasAttrs, HirDisplay, ModuleDef, PathResolution, Semantics, Trait, -}; +use hir::{AssocItem, GenericParam, HirDisplay, ModuleDef, PathResolution, Semantics, Trait}; use ide_db::{ active_parameter::{callable_for_node, generic_def_for_node}, base_db::FilePosition, + documentation::{Documentation, HasDocs}, FxIndexMap, }; use stdx::format_to; @@ -28,7 +27,7 @@ use crate::RootDatabase; /// edited. #[derive(Debug)] pub struct SignatureHelp { - pub doc: Option<String>, + pub doc: Option<Documentation>, pub signature: String, pub active_parameter: Option<usize>, parameters: Vec<TextRange>, @@ -67,17 +66,20 @@ impl SignatureHelp { } /// Computes parameter information for the given position. -pub(crate) fn signature_help(db: &RootDatabase, position: FilePosition) -> Option<SignatureHelp> { +pub(crate) fn signature_help( + db: &RootDatabase, + FilePosition { file_id, offset }: FilePosition, +) -> Option<SignatureHelp> { let sema = Semantics::new(db); - let file = sema.parse(position.file_id); + let file = sema.parse(file_id); let file = file.syntax(); let token = file - .token_at_offset(position.offset) + .token_at_offset(offset) .left_biased() // if the cursor is sandwiched between two space tokens and the call is unclosed // this prevents us from leaving the CallExpression .and_then(|tok| algo::skip_trivia_token(tok, Direction::Prev))?; - let token = sema.descend_into_macros_single(token); + let token = sema.descend_into_macros_single(token, offset); for node in token.parent_ancestors() { match_ast! { @@ -176,7 +178,7 @@ fn signature_help_for_call( let mut fn_params = None; match callable.kind() { hir::CallableKind::Function(func) => { - res.doc = func.docs(db).map(|it| it.into()); + res.doc = func.docs(db); format_to!(res.signature, "fn {}", func.name(db).display(db)); fn_params = Some(match callable.receiver_param(db) { Some(_self) => func.params_without_self(db), @@ -184,11 +186,11 @@ fn signature_help_for_call( }); } hir::CallableKind::TupleStruct(strukt) => { - res.doc = strukt.docs(db).map(|it| it.into()); + res.doc = strukt.docs(db); format_to!(res.signature, "struct {}", strukt.name(db).display(db)); } hir::CallableKind::TupleEnumVariant(variant) => { - res.doc = variant.docs(db).map(|it| it.into()); + res.doc = variant.docs(db); format_to!( res.signature, "enum {}::{}", @@ -202,7 +204,7 @@ fn signature_help_for_call( res.signature.push('('); { if let Some((self_param, _)) = callable.receiver_param(db) { - format_to!(res.signature, "{}", self_param) + format_to!(res.signature, "{}", self_param.display(db)) } let mut buf = String::new(); for (idx, (pat, ty)) in callable.params(db).into_iter().enumerate() { @@ -262,38 +264,38 @@ fn signature_help_for_generics( let db = sema.db; match generics_def { hir::GenericDef::Function(it) => { - res.doc = it.docs(db).map(|it| it.into()); + res.doc = it.docs(db); format_to!(res.signature, "fn {}", it.name(db).display(db)); } hir::GenericDef::Adt(hir::Adt::Enum(it)) => { - res.doc = it.docs(db).map(|it| it.into()); + res.doc = it.docs(db); format_to!(res.signature, "enum {}", it.name(db).display(db)); } hir::GenericDef::Adt(hir::Adt::Struct(it)) => { - res.doc = it.docs(db).map(|it| it.into()); + res.doc = it.docs(db); format_to!(res.signature, "struct {}", it.name(db).display(db)); } hir::GenericDef::Adt(hir::Adt::Union(it)) => { - res.doc = it.docs(db).map(|it| it.into()); + res.doc = it.docs(db); format_to!(res.signature, "union {}", it.name(db).display(db)); } hir::GenericDef::Trait(it) => { - res.doc = it.docs(db).map(|it| it.into()); + res.doc = it.docs(db); format_to!(res.signature, "trait {}", it.name(db).display(db)); } hir::GenericDef::TraitAlias(it) => { - res.doc = it.docs(db).map(|it| it.into()); + res.doc = it.docs(db); format_to!(res.signature, "trait {}", it.name(db).display(db)); } hir::GenericDef::TypeAlias(it) => { - res.doc = it.docs(db).map(|it| it.into()); + res.doc = it.docs(db); format_to!(res.signature, "type {}", it.name(db).display(db)); } hir::GenericDef::Variant(it) => { // In paths, generics of an enum can be specified *after* one of its variants. // eg. `None::<u8>` // 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); let enum_ = it.parent_enum(db); format_to!(res.signature, "enum {}", enum_.name(db).display(db)); generics_def = enum_.into(); @@ -1315,6 +1317,25 @@ id! { } #[test] + fn fn_signature_for_method_call_defined_in_macro() { + check( + r#" +macro_rules! id { ($($tt:tt)*) => { $($tt)* } } +struct S; +id! { + impl S { + fn foo<'a>(&'a mut self) {} + } +} +fn test() { S.foo($0); } +"#, + expect![[r#" + fn foo(&'a mut self) + "#]], + ); + } + + #[test] fn call_info_for_lambdas() { check( r#" diff --git a/src/tools/rust-analyzer/crates/ide/src/static_index.rs b/src/tools/rust-analyzer/crates/ide/src/static_index.rs index d8696198d..aabd26da2 100644 --- a/src/tools/rust-analyzer/crates/ide/src/static_index.rs +++ b/src/tools/rust-analyzer/crates/ide/src/static_index.rs @@ -12,6 +12,7 @@ use ide_db::{ }; use syntax::{AstNode, SyntaxKind::*, TextRange, T}; +use crate::inlay_hints::InlayFieldsToResolve; use crate::{ hover::hover_for_definition, inlay_hints::AdjustmentHintsMode, @@ -125,6 +126,7 @@ impl StaticIndex<'_> { max_length: Some(25), closure_capture_hints: false, closing_brace_hints_min_lines: Some(25), + fields_to_resolve: InlayFieldsToResolve::empty(), }, file_id, None, diff --git a/src/tools/rust-analyzer/crates/ide/src/status.rs b/src/tools/rust-analyzer/crates/ide/src/status.rs index d2c77e2dc..c9ee460a1 100644 --- a/src/tools/rust-analyzer/crates/ide/src/status.rs +++ b/src/tools/rust-analyzer/crates/ide/src/status.rs @@ -66,6 +66,7 @@ pub(crate) fn status(db: &RootDatabase, file_id: Option<FileId>) -> String { None => format!("{}", krate.into_raw()), }; format_to!(buf, "Crate: {}\n", display_crate(krate)); + format_to!(buf, "Enabled cfgs: {:?}\n", crate_graph[krate].cfg_options); let deps = crate_graph[krate] .dependencies .iter() diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs index ae9723640..bb01c81d6 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs @@ -395,10 +395,10 @@ fn traverse( NodeOrToken::Token(token) if token.kind() != COMMENT => { let token = match attr_or_derive_item { Some(AttrOrDerive::Attr(_)) => { - sema.descend_into_macros_with_kind_preference(token) + sema.descend_into_macros_with_kind_preference(token, 0.into()) } Some(AttrOrDerive::Derive(_)) | None => { - sema.descend_into_macros_single(token) + sema.descend_into_macros_single(token, 0.into()) } }; match token.parent().and_then(ast::NameLike::cast) { diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/format.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/format.rs index 2ed57e201..2ef131594 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/format.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/format.rs @@ -17,6 +17,7 @@ pub(super) fn highlight_format_string( return; } + // FIXME: Replace this with the HIR info we have now. lex_format_specifiers(string, &mut |piece_range, kind| { if let Some(highlight) = highlight_format_specifier(kind) { stack.add(HlRange { diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs index 8e96bfa01..7d00282fc 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs @@ -617,6 +617,7 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight { CONST => SymbolKind::Const, STATIC => SymbolKind::Static, IDENT_PAT => SymbolKind::Local, + FORMAT_ARGS_ARG => SymbolKind::Local, _ => return default.into(), }; diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs index 2657a6414..71f4d0724 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs @@ -5,8 +5,8 @@ use std::mem; use either::Either; use hir::{InFile, Semantics}; use ide_db::{ - active_parameter::ActiveParameter, base_db::FileId, defs::Definition, rust_doc::is_rust_fence, - SymbolKind, + active_parameter::ActiveParameter, base_db::FileId, defs::Definition, + documentation::docs_with_rangemap, rust_doc::is_rust_fence, SymbolKind, }; use syntax::{ ast::{self, AstNode, IsString, QuoteOffsets}, @@ -118,7 +118,7 @@ pub(super) fn doc_comment( let src_file_id = src_file_id.into(); // Extract intra-doc links and emit highlights for them. - if let Some((docs, doc_mapping)) = attributes.docs_with_rangemap(sema.db) { + if let Some((docs, doc_mapping)) = docs_with_rangemap(sema.db, &attributes) { extract_definitions_from_docs(&docs) .into_iter() .filter_map(|(range, link, ns)| { diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html index 3ac8aa9cc..64e614cec 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html @@ -45,17 +45,11 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd </style> <pre><code><span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">println</span> <span class="brace">{</span> <span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="parenthesis">(</span><span class="brace">{</span> - <span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>io<span class="colon">:</span><span class="colon">:</span>_print<span class="parenthesis">(</span><span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>format_args_nl<span class="punctuation">!</span><span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="semicolon">;</span> + <span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>io<span class="colon">:</span><span class="colon">:</span>_print<span class="parenthesis">(</span>format_args_nl<span class="punctuation">!</span><span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="brace">}</span><span class="parenthesis">)</span> <span class="brace">}</span> <span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="attribute_bracket attribute">]</span> <span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">macro_export</span><span class="attribute_bracket attribute">]</span> -<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">format_args</span> <span class="brace">{</span><span class="brace">}</span> -<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="attribute_bracket attribute">]</span> -<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">macro_export</span><span class="attribute_bracket attribute">]</span> -<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">const_format_args</span> <span class="brace">{</span><span class="brace">}</span> -<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="attribute_bracket attribute">]</span> -<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">macro_export</span><span class="attribute_bracket attribute">]</span> <span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">format_args_nl</span> <span class="brace">{</span><span class="brace">}</span> <span class="keyword">mod</span> <span class="module declaration">panic</span> <span class="brace">{</span> @@ -75,7 +69,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd <span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panicking<span class="colon">:</span><span class="colon">:</span>panic_display<span class="parenthesis">(</span><span class="punctuation">&</span><span class="punctuation">$</span>arg<span class="parenthesis">)</span> <span class="parenthesis">)</span><span class="comma">,</span> <span class="parenthesis">(</span><span class="punctuation">$</span>fmt<span class="colon">:</span>expr<span class="comma">,</span> <span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">+</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="parenthesis">(</span> - <span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panicking<span class="colon">:</span><span class="colon">:</span>panic_fmt<span class="parenthesis">(</span><span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>const_format_args<span class="punctuation">!</span><span class="parenthesis">(</span><span class="punctuation">$</span>fmt<span class="comma">,</span> <span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="parenthesis">)</span><span class="punctuation">+</span><span class="parenthesis">)</span><span class="parenthesis">)</span> + <span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panicking<span class="colon">:</span><span class="colon">:</span>panic_fmt<span class="parenthesis">(</span>const_format_args<span class="punctuation">!</span><span class="parenthesis">(</span><span class="punctuation">$</span>fmt<span class="comma">,</span> <span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="parenthesis">)</span><span class="punctuation">+</span><span class="parenthesis">)</span><span class="parenthesis">)</span> <span class="parenthesis">)</span><span class="comma">,</span> <span class="brace">}</span> <span class="brace">}</span> @@ -92,7 +86,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd <span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">toho</span> <span class="brace">{</span> <span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="parenthesis">(</span><span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panic<span class="punctuation">!</span><span class="parenthesis">(</span><span class="string_literal">"not yet implemented"</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="semicolon">;</span> - <span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">+</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="parenthesis">(</span><span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panic<span class="punctuation">!</span><span class="parenthesis">(</span><span class="string_literal">"not yet implemented: {}"</span><span class="comma">,</span> <span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>format_args<span class="punctuation">!</span><span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="parenthesis">)</span><span class="punctuation">+</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="semicolon">;</span> + <span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">+</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">></span> <span class="parenthesis">(</span><span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panic<span class="punctuation">!</span><span class="parenthesis">(</span><span class="string_literal">"not yet implemented: {}"</span><span class="comma">,</span> format_args<span class="punctuation">!</span><span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="parenthesis">)</span><span class="punctuation">+</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="brace">}</span> <span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span> @@ -114,18 +108,18 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"Hello, </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">!"</span><span class="comma macro">,</span> <span class="string_literal macro">"world"</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="comment">// => "Hello, world!"</span> <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"The number is </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="numeric_literal macro">1</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="comment">// => "The number is 1"</span> <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">?</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="parenthesis macro">(</span><span class="numeric_literal macro">3</span><span class="comma macro">,</span> <span class="numeric_literal macro">4</span><span class="parenthesis macro">)</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="comment">// => "(3, 4)"</span> - <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="variable">value</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="none macro">value</span><span class="operator macro">=</span><span class="numeric_literal macro">4</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="comment">// => "4"</span> + <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="variable">value</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="variable declaration macro">value</span><span class="operator macro">=</span><span class="numeric_literal macro">4</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="comment">// => "4"</span> <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="numeric_literal macro">1</span><span class="comma macro">,</span> <span class="numeric_literal macro">2</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="comment">// => "1 2"</span> <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="numeric_literal">4</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="numeric_literal macro">42</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="comment">// => "0042" with leading zerosV</span> <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="numeric_literal">1</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="numeric_literal">0</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="numeric_literal macro">1</span><span class="comma macro">,</span> <span class="numeric_literal macro">2</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="comment">// => "2 1 1 2"</span> - <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="variable">argument</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="none macro">argument</span> <span class="operator macro">=</span> <span class="string_literal macro">"test"</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="comment">// => "test"</span> - <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="variable">name</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="numeric_literal macro">1</span><span class="comma macro">,</span> <span class="none macro">name</span> <span class="operator macro">=</span> <span class="numeric_literal macro">2</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="comment">// => "2 1"</span> - <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="variable">a</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="variable">c</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="variable">b</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="none macro">a</span><span class="operator macro">=</span><span class="string_literal macro">"a"</span><span class="comma macro">,</span> <span class="none macro">b</span><span class="operator macro">=</span><span class="char_literal macro">'b'</span><span class="comma macro">,</span> <span class="none macro">c</span><span class="operator macro">=</span><span class="numeric_literal macro">3</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="comment">// => "a 3 b"</span> + <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="variable">argument</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="variable declaration macro">argument</span> <span class="operator macro">=</span> <span class="string_literal macro">"test"</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="comment">// => "test"</span> + <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="variable">name</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="numeric_literal macro">1</span><span class="comma macro">,</span> <span class="variable declaration macro">name</span> <span class="operator macro">=</span> <span class="numeric_literal macro">2</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="comment">// => "2 1"</span> + <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="variable">a</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="variable">c</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="variable">b</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="variable declaration macro">a</span><span class="operator macro">=</span><span class="string_literal macro">"a"</span><span class="comma macro">,</span> <span class="variable declaration macro">b</span><span class="operator macro">=</span><span class="char_literal macro">'b'</span><span class="comma macro">,</span> <span class="variable declaration macro">c</span><span class="operator macro">=</span><span class="numeric_literal macro">3</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="comment">// => "a 3 b"</span> <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="escape_sequence">{{</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="escape_sequence">}}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="numeric_literal macro">2</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="comment">// => "{2}"</span> <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal macro">!"</span><span class="comma macro">,</span> <span class="string_literal macro">"x"</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">1</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal macro">!"</span><span class="comma macro">,</span> <span class="string_literal macro">"x"</span><span class="comma macro">,</span> <span class="numeric_literal macro">5</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"Hello </span><span class="format_specifier">{</span><span class="numeric_literal">1</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal macro">!"</span><span class="comma macro">,</span> <span class="numeric_literal macro">5</span><span class="comma macro">,</span> <span class="string_literal macro">"x"</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> - <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="variable">width</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal macro">!"</span><span class="comma macro">,</span> <span class="string_literal macro">"x"</span><span class="comma macro">,</span> <span class="none macro">width</span> <span class="operator macro">=</span> <span class="numeric_literal macro">5</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> + <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="variable">width</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal macro">!"</span><span class="comma macro">,</span> <span class="string_literal macro">"x"</span><span class="comma macro">,</span> <span class="variable declaration macro">width</span> <span class="operator macro">=</span> <span class="numeric_literal macro">5</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier"><</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal macro">!"</span><span class="comma macro">,</span> <span class="string_literal macro">"x"</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">-</span><span class="format_specifier"><</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal macro">!"</span><span class="comma macro">,</span> <span class="string_literal macro">"x"</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">^</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal macro">!"</span><span class="comma macro">,</span> <span class="string_literal macro">"x"</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> @@ -140,10 +134,10 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"Hello </span><span class="format_specifier">{</span><span class="numeric_literal">0</span><span class="format_specifier">}</span><span class="string_literal macro"> is </span><span class="format_specifier">{</span><span class="numeric_literal">2</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="numeric_literal">1</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="string_literal macro">"x"</span><span class="comma macro">,</span> <span class="numeric_literal macro">5</span><span class="comma macro">,</span> <span class="numeric_literal macro">0.01</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> is </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="format_specifier">*</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="string_literal macro">"x"</span><span class="comma macro">,</span> <span class="numeric_literal macro">5</span><span class="comma macro">,</span> <span class="numeric_literal macro">0.01</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> is </span><span class="format_specifier">{</span><span class="numeric_literal">2</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="format_specifier">*</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="string_literal macro">"x"</span><span class="comma macro">,</span> <span class="numeric_literal macro">5</span><span class="comma macro">,</span> <span class="numeric_literal macro">0.01</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> - <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> is </span><span class="format_specifier">{</span><span class="variable">number</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="variable">prec</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="string_literal macro">"x"</span><span class="comma macro">,</span> <span class="none macro">prec</span> <span class="operator macro">=</span> <span class="numeric_literal macro">5</span><span class="comma macro">,</span> <span class="none macro">number</span> <span class="operator macro">=</span> <span class="numeric_literal macro">0.01</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> - <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">, `</span><span class="format_specifier">{</span><span class="variable">name</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="format_specifier">*</span><span class="format_specifier">}</span><span class="string_literal macro">` has 3 fractional digits"</span><span class="comma macro">,</span> <span class="string_literal macro">"Hello"</span><span class="comma macro">,</span> <span class="numeric_literal macro">3</span><span class="comma macro">,</span> <span class="none macro">name</span><span class="operator macro">=</span><span class="numeric_literal macro">1234.56</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> - <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">, `</span><span class="format_specifier">{</span><span class="variable">name</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="format_specifier">*</span><span class="format_specifier">}</span><span class="string_literal macro">` has 3 characters"</span><span class="comma macro">,</span> <span class="string_literal macro">"Hello"</span><span class="comma macro">,</span> <span class="numeric_literal macro">3</span><span class="comma macro">,</span> <span class="none macro">name</span><span class="operator macro">=</span><span class="string_literal macro">"1234.56"</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> - <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">, `</span><span class="format_specifier">{</span><span class="variable">name</span><span class="format_specifier">:</span><span class="format_specifier">></span><span class="numeric_literal">8</span><span class="format_specifier">.</span><span class="format_specifier">*</span><span class="format_specifier">}</span><span class="string_literal macro">` has 3 right-aligned characters"</span><span class="comma macro">,</span> <span class="string_literal macro">"Hello"</span><span class="comma macro">,</span> <span class="numeric_literal macro">3</span><span class="comma macro">,</span> <span class="none macro">name</span><span class="operator macro">=</span><span class="string_literal macro">"1234.56"</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> + <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> is </span><span class="format_specifier">{</span><span class="variable">number</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="variable">prec</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="string_literal macro">"x"</span><span class="comma macro">,</span> <span class="variable declaration macro">prec</span> <span class="operator macro">=</span> <span class="numeric_literal macro">5</span><span class="comma macro">,</span> <span class="variable declaration macro">number</span> <span class="operator macro">=</span> <span class="numeric_literal macro">0.01</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> + <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">, `</span><span class="format_specifier">{</span><span class="variable">name</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="format_specifier">*</span><span class="format_specifier">}</span><span class="string_literal macro">` has 3 fractional digits"</span><span class="comma macro">,</span> <span class="string_literal macro">"Hello"</span><span class="comma macro">,</span> <span class="numeric_literal macro">3</span><span class="comma macro">,</span> <span class="variable declaration macro">name</span><span class="operator macro">=</span><span class="numeric_literal macro">1234.56</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> + <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">, `</span><span class="format_specifier">{</span><span class="variable">name</span><span class="format_specifier">:</span><span class="format_specifier">.</span><span class="format_specifier">*</span><span class="format_specifier">}</span><span class="string_literal macro">` has 3 characters"</span><span class="comma macro">,</span> <span class="string_literal macro">"Hello"</span><span class="comma macro">,</span> <span class="numeric_literal macro">3</span><span class="comma macro">,</span> <span class="variable declaration macro">name</span><span class="operator macro">=</span><span class="string_literal macro">"1234.56"</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> + <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">, `</span><span class="format_specifier">{</span><span class="variable">name</span><span class="format_specifier">:</span><span class="format_specifier">></span><span class="numeric_literal">8</span><span class="format_specifier">.</span><span class="format_specifier">*</span><span class="format_specifier">}</span><span class="string_literal macro">` has 3 right-aligned characters"</span><span class="comma macro">,</span> <span class="string_literal macro">"Hello"</span><span class="comma macro">,</span> <span class="numeric_literal macro">3</span><span class="comma macro">,</span> <span class="variable declaration macro">name</span><span class="operator macro">=</span><span class="string_literal macro">"1234.56"</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="keyword">let</span> <span class="punctuation">_</span> <span class="operator">=</span> <span class="string_literal">"{}"</span> <span class="keyword">let</span> <span class="punctuation">_</span> <span class="operator">=</span> <span class="string_literal">"{{}}"</span><span class="semicolon">;</span> @@ -167,16 +161,24 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd <span class="keyword">let</span> <span class="punctuation">_</span> <span class="operator">=</span> <span class="string_literal">c"</span><span class="escape_sequence">\u{FF}</span><span class="escape_sequence">\xFF</span><span class="string_literal">"</span><span class="semicolon">;</span> <span class="comment">// valid bytes, valid unicodes</span> <span class="keyword">let</span> <span class="variable declaration reference">backslash</span> <span class="operator">=</span> <span class="string_literal">r"\\"</span><span class="semicolon">;</span> - <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="escape_sequence">\x41</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="none macro">A</span> <span class="operator macro">=</span> <span class="numeric_literal macro">92</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> - <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="variable">ничоси</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="none macro">ничоси</span> <span class="operator macro">=</span> <span class="numeric_literal macro">92</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> + <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="escape_sequence">\x41</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="variable declaration macro">A</span> <span class="operator macro">=</span> <span class="numeric_literal macro">92</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> + <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="variable">ничоси</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="variable declaration macro">ничоси</span> <span class="operator macro">=</span> <span class="numeric_literal macro">92</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="variable">x</span><span class="format_specifier">?</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> "</span><span class="comma macro">,</span> <span class="unresolved_reference macro">thingy</span><span class="comma macro">,</span> <span class="unresolved_reference macro">n2</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> - <span class="macro">panic</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="numeric_literal macro">0</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> - <span class="macro">panic</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"more </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="numeric_literal macro">1</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> - <span class="macro">assert</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="bool_literal macro">true</span><span class="comma macro">,</span> <span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="numeric_literal macro">1</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> - <span class="macro">assert</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="bool_literal macro">true</span><span class="comma macro">,</span> <span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> asdasd"</span><span class="comma macro">,</span> <span class="numeric_literal macro">1</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> - <span class="macro">toho</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">fmt"</span><span class="comma macro">,</span> <span class="numeric_literal macro">0</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> - <span class="macro unsafe">asm</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"mov eax, </span><span class="format_specifier">{</span><span class="numeric_literal">0</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> - <span class="macro">format_args</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="none macro">concat</span><span class="punctuation macro">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="parenthesis macro">)</span><span class="comma macro">,</span> <span class="string_literal macro">"{}"</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> - <span class="macro">format_args</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="variable macro reference">backslash</span><span class="comma macro">,</span> <span class="none macro">format_args</span><span class="punctuation macro">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="numeric_literal macro">0</span><span class="parenthesis macro">)</span><span class="comma macro">,</span> <span class="unresolved_reference macro">foo</span><span class="comma macro">,</span> <span class="string_literal macro">"bar"</span><span class="comma macro">,</span> <span class="none macro">toho</span><span class="punctuation macro">!</span><span class="parenthesis macro">(</span><span class="parenthesis macro">)</span><span class="comma macro">,</span> <span class="variable macro reference">backslash</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> + <span class="macro">panic</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"{}"</span><span class="comma macro">,</span> <span class="numeric_literal macro">0</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> + <span class="macro">panic</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"more {}"</span><span class="comma macro">,</span> <span class="numeric_literal macro">1</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> + <span class="macro">assert</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="bool_literal macro">true</span><span class="comma macro">,</span> <span class="string_literal macro">"{}"</span><span class="comma macro">,</span> <span class="numeric_literal macro">1</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> + <span class="macro">assert</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="bool_literal macro">true</span><span class="comma macro">,</span> <span class="string_literal macro">"{} asdasd"</span><span class="comma macro">,</span> <span class="numeric_literal macro">1</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> + <span class="macro">toho</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"{}fmt"</span><span class="comma macro">,</span> <span class="numeric_literal macro">0</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> + <span class="keyword">let</span> <span class="variable declaration">i</span><span class="colon">:</span> <span class="builtin_type">u64</span> <span class="operator">=</span> <span class="numeric_literal">3</span><span class="semicolon">;</span> + <span class="keyword">let</span> <span class="variable declaration">o</span><span class="colon">:</span> <span class="builtin_type">u64</span><span class="semicolon">;</span> + <span class="macro unsafe">asm</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span> + <span class="string_literal macro">"mov {0}, {1}"</span><span class="comma macro">,</span> + <span class="string_literal macro">"add {0}, 5"</span><span class="comma macro">,</span> + <span class="none macro">out</span><span class="parenthesis macro">(</span><span class="none macro">reg</span><span class="parenthesis macro">)</span> <span class="none macro">o</span><span class="comma macro">,</span> + <span class="keyword control macro">in</span><span class="parenthesis macro">(</span><span class="none macro">reg</span><span class="parenthesis macro">)</span> <span class="none macro">i</span><span class="comma macro">,</span> + <span class="parenthesis macro">)</span><span class="semicolon">;</span> + + <span class="macro default_library library">format_args</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="macro macro">concat</span><span class="macro_bang macro">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"{}"</span><span class="parenthesis macro">)</span><span class="comma macro">,</span> <span class="string_literal macro">"{}"</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> + <span class="macro default_library library">format_args</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="variable macro reference">backslash</span><span class="comma macro">,</span> <span class="macro default_library library macro">format_args</span><span class="macro_bang macro">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="numeric_literal macro">0</span><span class="parenthesis macro">)</span><span class="comma macro">,</span> <span class="unresolved_reference macro">foo</span><span class="comma macro">,</span> <span class="string_literal macro">"bar"</span><span class="comma macro">,</span> <span class="macro macro">toho</span><span class="macro_bang macro">!</span><span class="parenthesis macro">(</span><span class="parenthesis macro">)</span><span class="comma macro">,</span> <span class="variable macro reference">backslash</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="brace">}</span></code></pre>
\ No newline at end of file diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs index 8749d355c..542d89925 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs @@ -401,19 +401,14 @@ fn test_string_highlighting() { // thus, we have to copy the macro definition from `std` check_highlighting( r#" +//- minicore: fmt macro_rules! println { ($($arg:tt)*) => ({ - $crate::io::_print($crate::format_args_nl!($($arg)*)); + $crate::io::_print(format_args_nl!($($arg)*)); }) } #[rustc_builtin_macro] #[macro_export] -macro_rules! format_args {} -#[rustc_builtin_macro] -#[macro_export] -macro_rules! const_format_args {} -#[rustc_builtin_macro] -#[macro_export] macro_rules! format_args_nl {} mod panic { @@ -433,7 +428,7 @@ mod panic { $crate::panicking::panic_display(&$arg) ), ($fmt:expr, $($arg:tt)+) => ( - $crate::panicking::panic_fmt($crate::const_format_args!($fmt, $($arg)+)) + $crate::panicking::panic_fmt(const_format_args!($fmt, $($arg)+)) ), } } @@ -450,7 +445,7 @@ macro_rules! concat {} macro_rules! toho { () => ($crate::panic!("not yet implemented")); - ($($arg:tt)+) => ($crate::panic!("not yet implemented: {}", $crate::format_args!($($arg)+))); + ($($arg:tt)+) => ($crate::panic!("not yet implemented: {}", format_args!($($arg)+))); } fn main() { @@ -534,7 +529,15 @@ fn main() { assert!(true, "{}", 1); assert!(true, "{} asdasd", 1); toho!("{}fmt", 0); - asm!("mov eax, {0}"); + let i: u64 = 3; + let o: u64; + asm!( + "mov {0}, {1}", + "add {0}, 5", + out(reg) o, + in(reg) i, + ); + format_args!(concat!("{}"), "{}"); format_args!("{} {} {} {} {} {}", backslash, format_args!("{}", 0), foo, "bar", toho!(), backslash); }"#, diff --git a/src/tools/rust-analyzer/crates/ide/src/typing.rs b/src/tools/rust-analyzer/crates/ide/src/typing.rs index c7e403f6b..b40509715 100644 --- a/src/tools/rust-analyzer/crates/ide/src/typing.rs +++ b/src/tools/rust-analyzer/crates/ide/src/typing.rs @@ -32,7 +32,7 @@ use crate::SourceChange; pub(crate) use on_enter::on_enter; // Don't forget to add new trigger characters to `server_capabilities` in `caps.rs`. -pub(crate) const TRIGGER_CHARS: &str = ".=<>{"; +pub(crate) const TRIGGER_CHARS: &str = ".=<>{("; struct ExtendedTextEdit { edit: TextEdit, @@ -86,45 +86,57 @@ fn on_char_typed_inner( if !stdx::always!(TRIGGER_CHARS.contains(char_typed)) { return None; } - return match char_typed { + let conv = |text_edit: Option<TextEdit>| { + Some(ExtendedTextEdit { edit: text_edit?, is_snippet: false }) + }; + match char_typed { '.' => conv(on_dot_typed(&file.tree(), offset)), '=' => conv(on_eq_typed(&file.tree(), offset)), '<' => on_left_angle_typed(&file.tree(), offset), '>' => conv(on_right_angle_typed(&file.tree(), offset)), - '{' => conv(on_opening_brace_typed(file, offset)), - _ => return None, - }; - - fn conv(text_edit: Option<TextEdit>) -> Option<ExtendedTextEdit> { - Some(ExtendedTextEdit { edit: text_edit?, is_snippet: false }) + '{' => conv(on_opening_bracket_typed(file, offset, '{')), + '(' => conv(on_opening_bracket_typed(file, offset, '(')), + _ => None, } } -/// Inserts a closing `}` when the user types an opening `{`, wrapping an existing expression in a -/// block, or a part of a `use` item. -fn on_opening_brace_typed(file: &Parse<SourceFile>, offset: TextSize) -> Option<TextEdit> { - if !stdx::always!(file.tree().syntax().text().char_at(offset) == Some('{')) { +/// Inserts a closing bracket when the user types an opening bracket, wrapping an existing expression in a +/// block, or a part of a `use` item (for `{`). +fn on_opening_bracket_typed( + file: &Parse<SourceFile>, + offset: TextSize, + opening_bracket: char, +) -> Option<TextEdit> { + let (closing_bracket, expected_ast_bracket) = match opening_bracket { + '{' => ('}', SyntaxKind::L_CURLY), + '(' => (')', SyntaxKind::L_PAREN), + _ => return None, + }; + + if !stdx::always!(file.tree().syntax().text().char_at(offset) == Some(opening_bracket)) { return None; } let brace_token = file.tree().syntax().token_at_offset(offset).right_biased()?; - if brace_token.kind() != SyntaxKind::L_CURLY { + if brace_token.kind() != expected_ast_bracket { return None; } - // Remove the `{` to get a better parse tree, and reparse. + // Remove the opening bracket to get a better parse tree, and reparse. let range = brace_token.text_range(); - if !stdx::always!(range.len() == TextSize::of('{')) { + if !stdx::always!(range.len() == TextSize::of(opening_bracket)) { return None; } let file = file.reparse(&Indel::delete(range)); - if let Some(edit) = brace_expr(&file.tree(), offset) { + if let Some(edit) = bracket_expr(&file.tree(), offset, opening_bracket, closing_bracket) { return Some(edit); } - if let Some(edit) = brace_use_path(&file.tree(), offset) { - return Some(edit); + if closing_bracket == '}' { + if let Some(edit) = brace_use_path(&file.tree(), offset) { + return Some(edit); + } } return None; @@ -143,7 +155,12 @@ fn on_opening_brace_typed(file: &Parse<SourceFile>, offset: TextSize) -> Option< )) } - fn brace_expr(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { + fn bracket_expr( + file: &SourceFile, + offset: TextSize, + opening_bracket: char, + closing_bracket: char, + ) -> Option<TextEdit> { let mut expr: ast::Expr = find_node_at_offset(file.syntax(), offset)?; if expr.syntax().text_range().start() != offset { return None; @@ -166,10 +183,10 @@ fn on_opening_brace_typed(file: &Parse<SourceFile>, offset: TextSize) -> Option< return None; } - // Insert `}` right after the expression. + // Insert the closing bracket right after the expression. Some(TextEdit::insert( - expr.syntax().text_range().end() + TextSize::of("{"), - "}".to_string(), + expr.syntax().text_range().end() + TextSize::of(opening_bracket), + closing_bracket.to_string(), )) } } @@ -938,6 +955,193 @@ use some::pa$0th::to::Item; } #[test] + fn adds_closing_parenthesis_for_expr() { + type_char( + '(', + r#" +fn f() { match () { _ => $0() } } + "#, + r#" +fn f() { match () { _ => (()) } } + "#, + ); + type_char( + '(', + r#" +fn f() { $0() } + "#, + r#" +fn f() { (()) } + "#, + ); + type_char( + '(', + r#" +fn f() { let x = $0(); } + "#, + r#" +fn f() { let x = (()); } + "#, + ); + type_char( + '(', + r#" +fn f() { let x = $0a.b(); } + "#, + r#" +fn f() { let x = (a.b()); } + "#, + ); + type_char( + '(', + r#" +const S: () = $0(); +fn f() {} + "#, + r#" +const S: () = (()); +fn f() {} + "#, + ); + type_char( + '(', + r#" +const S: () = $0a.b(); +fn f() {} + "#, + r#" +const S: () = (a.b()); +fn f() {} + "#, + ); + type_char( + '(', + r#" +fn f() { + match x { + 0 => $0(), + 1 => (), + } +} + "#, + r#" +fn f() { + match x { + 0 => (()), + 1 => (), + } +} + "#, + ); + type_char( + '(', + r#" + fn f() { + let z = Some($03); + } + "#, + r#" + fn f() { + let z = Some((3)); + } + "#, + ); + } + + #[test] + fn parenthesis_noop_in_string_literal() { + // Regression test for #9351 + type_char_noop( + '(', + r##" +fn check_with(ra_fixture: &str, expect: Expect) { + let base = r#" +enum E { T(), R$0, C } +use self::E::X; +const Z: E = E::C; +mod m {} +asdasdasdasdasdasda +sdasdasdasdasdasda +sdasdasdasdasd +"#; + let actual = completion_list(&format!("{}\n{}", base, ra_fixture)); + expect.assert_eq(&actual) +} + "##, + ); + } + + #[test] + fn parenthesis_noop_in_item_position_with_macro() { + type_char_noop('(', r#"$0println!();"#); + type_char_noop( + '(', + r#" +fn main() $0println!("hello"); +}"#, + ); + } + + #[test] + fn parenthesis_noop_in_use_tree() { + type_char_noop( + '(', + r#" +use some::$0Path; + "#, + ); + type_char_noop( + '(', + r#" +use some::{Path, $0Other}; + "#, + ); + type_char_noop( + '(', + r#" +use some::{$0Path, Other}; + "#, + ); + type_char_noop( + '(', + r#" +use some::path::$0to::Item; + "#, + ); + type_char_noop( + '(', + r#" +use some::$0path::to::Item; + "#, + ); + type_char_noop( + '(', + r#" +use $0some::path::to::Item; + "#, + ); + type_char_noop( + '(', + r#" +use some::path::$0to::{Item}; + "#, + ); + type_char_noop( + '(', + r#" +use $0Thing as _; + "#, + ); + + type_char_noop( + '(', + r#" +use some::pa$0th::to::Item; + "#, + ); + } + + #[test] fn adds_closing_angle_bracket_for_generic_args() { type_char( '<', diff --git a/src/tools/rust-analyzer/crates/intern/Cargo.toml b/src/tools/rust-analyzer/crates/intern/Cargo.toml index 4d56c7719..89b302c79 100644 --- a/src/tools/rust-analyzer/crates/intern/Cargo.toml +++ b/src/tools/rust-analyzer/crates/intern/Cargo.toml @@ -16,6 +16,5 @@ doctest = false # We need to freeze the version of the crate, as the raw-api feature is considered unstable dashmap = { version = "=5.4.0", features = ["raw-api"] } hashbrown.workspace = true -once_cell = "1.17.0" rustc-hash = "1.1.0" triomphe.workspace = true diff --git a/src/tools/rust-analyzer/crates/intern/src/lib.rs b/src/tools/rust-analyzer/crates/intern/src/lib.rs index dabbf3a38..2934d2667 100644 --- a/src/tools/rust-analyzer/crates/intern/src/lib.rs +++ b/src/tools/rust-analyzer/crates/intern/src/lib.rs @@ -6,11 +6,11 @@ use std::{ fmt::{self, Debug, Display}, hash::{BuildHasherDefault, Hash, Hasher}, ops::Deref, + sync::OnceLock, }; use dashmap::{DashMap, SharedValue}; use hashbrown::{hash_map::RawEntryMut, HashMap}; -use once_cell::sync::OnceCell; use rustc_hash::FxHasher; use triomphe::Arc; @@ -177,12 +177,12 @@ impl<T: Display + Internable + ?Sized> Display for Interned<T> { } pub struct InternStorage<T: ?Sized> { - map: OnceCell<InternMap<T>>, + map: OnceLock<InternMap<T>>, } impl<T: ?Sized> InternStorage<T> { pub const fn new() -> Self { - Self { map: OnceCell::new() } + Self { map: OnceLock::new() } } } diff --git a/src/tools/rust-analyzer/crates/mbe/src/syntax_bridge.rs b/src/tools/rust-analyzer/crates/mbe/src/syntax_bridge.rs index 62b2accf5..7b9bb61e6 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/syntax_bridge.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/syntax_bridge.rs @@ -961,6 +961,7 @@ impl TtTreeSink<'_> { if has_pseudo_dot { assert!(right.is_empty(), "{left}.{right}"); } else { + assert!(!right.is_empty(), "{left}.{right}"); self.inner.start_node(SyntaxKind::NAME_REF); self.inner.token(SyntaxKind::INT_NUMBER, right); self.inner.finish_node(); diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar.rs b/src/tools/rust-analyzer/crates/parser/src/grammar.rs index 333318f53..6a2a9adce 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar.rs @@ -184,7 +184,9 @@ pub(crate) mod entry { }; p.bump_any(); while !p.at(EOF) && !p.at(closing_paren_kind) { - expressions::expr(p); + if expressions::expr(p).is_none() { + break; + } if !p.at(EOF) && !p.at(closing_paren_kind) { p.expect(T![,]); } diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs index d8553d3f9..4197f248e 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs @@ -1,3 +1,5 @@ +use crate::grammar::types::type_; + use super::*; // test expr_literals @@ -73,6 +75,9 @@ pub(super) fn atom_expr( if let Some(m) = literal(p) { return Some((m, BlockLike::NotBlock)); } + if p.at_contextual_kw(T![builtin]) && p.nth_at(1, T![#]) { + return Some((builtin_expr(p)?, BlockLike::NotBlock)); + } if paths::is_path_start(p) { return Some(path_expr(p, r)); } @@ -93,7 +98,6 @@ pub(super) fn atom_expr( m.complete(p, UNDERSCORE_EXPR) } T![loop] => loop_expr(p, None), - T![box] => box_expr(p, None), T![while] => while_expr(p, None), T![try] => try_block_expr(p, None), T![match] => match_expr(p), @@ -212,6 +216,72 @@ fn tuple_expr(p: &mut Parser<'_>) -> CompletedMarker { m.complete(p, if saw_expr && !saw_comma { PAREN_EXPR } else { TUPLE_EXPR }) } +// test builtin_expr +// fn foo() { +// builtin#asm(0); +// builtin#format_args("", 0, 1, a = 2 + 3, a + b); +// builtin#offset_of(Foo, bar.baz.0); +// } +fn builtin_expr(p: &mut Parser<'_>) -> Option<CompletedMarker> { + let m = p.start(); + p.bump_remap(T![builtin]); + p.bump(T![#]); + if p.at_contextual_kw(T![offset_of]) { + p.bump_remap(T![offset_of]); + p.expect(T!['(']); + type_(p); + p.expect(T![,]); + while !p.at(EOF) && !p.at(T![')']) { + if p.at(IDENT) || p.at(INT_NUMBER) { + name_ref_or_index(p); + // } else if p.at(FLOAT_NUMBER) { + // FIXME: needs float hack + } else { + p.err_and_bump("expected field name or number"); + } + if !p.at(T![')']) { + p.expect(T![.]); + } + } + p.expect(T![')']); + Some(m.complete(p, OFFSET_OF_EXPR)) + } else if p.at_contextual_kw(T![format_args]) { + p.bump_remap(T![format_args]); + p.expect(T!['(']); + expr(p); + if p.eat(T![,]) { + while !p.at(EOF) && !p.at(T![')']) { + let m = p.start(); + if p.at(IDENT) && p.nth_at(1, T![=]) { + name(p); + p.bump(T![=]); + } + if expr(p).is_none() { + m.abandon(p); + break; + } + m.complete(p, FORMAT_ARGS_ARG); + + if !p.at(T![')']) { + p.expect(T![,]); + } + } + } + p.expect(T![')']); + Some(m.complete(p, FORMAT_ARGS_EXPR)) + } else if p.at_contextual_kw(T![asm]) { + p.bump_remap(T![asm]); + p.expect(T!['(']); + // FIXME: We just put expression here so highlighting kind of keeps working + expr(p); + p.expect(T![')']); + Some(m.complete(p, ASM_EXPR)) + } else { + m.abandon(p); + None + } +} + // test array_expr // fn foo() { // []; @@ -662,19 +732,3 @@ fn try_block_expr(p: &mut Parser<'_>, m: Option<Marker>) -> CompletedMarker { } m.complete(p, BLOCK_EXPR) } - -// test box_expr -// fn foo() { -// let x = box 1i32; -// let y = (box 1i32, box 2i32); -// let z = Foo(box 1i32, box 2i32); -// } -fn box_expr(p: &mut Parser<'_>, m: Option<Marker>) -> CompletedMarker { - assert!(p.at(T![box])); - let m = m.unwrap_or_else(|| p.start()); - p.bump(T![box]); - if p.at_ts(EXPR_FIRST) { - expr(p); - } - m.complete(p, BOX_EXPR) -} diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs index 8ed1c84c4..29d9b05d3 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs @@ -88,7 +88,7 @@ fn const_param(p: &mut Parser<'_>, m: Marker) { // test const_param_default_path // struct A<const N: i32 = i32::MAX>; - generic_args::const_arg_expr(p); + generic_args::const_arg(p); } m.complete(p, CONST_PARAM); diff --git a/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs b/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs index e4dce21f3..36c52953a 100644 --- a/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs +++ b/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs @@ -221,6 +221,7 @@ impl<'a> Converter<'a> { rustc_lexer::TokenKind::Caret => T![^], rustc_lexer::TokenKind::Percent => T![%], rustc_lexer::TokenKind::Unknown => ERROR, + rustc_lexer::TokenKind::UnknownPrefix if token_text == "builtin" => IDENT, rustc_lexer::TokenKind::UnknownPrefix => { err = "unknown literal prefix"; IDENT diff --git a/src/tools/rust-analyzer/crates/parser/src/shortcuts.rs b/src/tools/rust-analyzer/crates/parser/src/shortcuts.rs index 53cdad649..2c47e3d08 100644 --- a/src/tools/rust-analyzer/crates/parser/src/shortcuts.rs +++ b/src/tools/rust-analyzer/crates/parser/src/shortcuts.rs @@ -46,12 +46,16 @@ impl LexedStr<'_> { // Tag the token as joint if it is float with a fractional part // we use this jointness to inform the parser about what token split // event to emit when we encounter a float literal in a field access - if kind == SyntaxKind::FLOAT_NUMBER && !self.text(i).ends_with('.') { - res.was_joint(); + if kind == SyntaxKind::FLOAT_NUMBER { + if !self.text(i).ends_with('.') { + res.was_joint(); + } else { + was_joint = false; + } + } else { + was_joint = true; } } - - was_joint = true; } } res @@ -204,6 +208,7 @@ impl Builder<'_, '_> { assert!(right.is_empty(), "{left}.{right}"); self.state = State::Normal; } else { + assert!(!right.is_empty(), "{left}.{right}"); (self.sink)(StrStep::Enter { kind: SyntaxKind::NAME_REF }); (self.sink)(StrStep::Token { kind: SyntaxKind::INT_NUMBER, text: right }); (self.sink)(StrStep::Exit); diff --git a/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs b/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs index 48f407623..db5278f89 100644 --- a/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs +++ b/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs @@ -105,12 +105,16 @@ pub enum SyntaxKind { WHILE_KW, YIELD_KW, AUTO_KW, + BUILTIN_KW, DEFAULT_KW, EXISTENTIAL_KW, UNION_KW, RAW_KW, MACRO_RULES_KW, YEET_KW, + OFFSET_OF_KW, + ASM_KW, + FORMAT_ARGS_KW, INT_NUMBER, FLOAT_NUMBER, CHAR, @@ -203,7 +207,10 @@ pub enum SyntaxKind { RECORD_EXPR, RECORD_EXPR_FIELD_LIST, RECORD_EXPR_FIELD, - BOX_EXPR, + OFFSET_OF_EXPR, + ASM_EXPR, + FORMAT_ARGS_EXPR, + FORMAT_ARGS_ARG, CALL_EXPR, INDEX_EXPR, METHOD_CALL_EXPR, @@ -315,12 +322,16 @@ impl SyntaxKind { | WHILE_KW | YIELD_KW | AUTO_KW + | BUILTIN_KW | DEFAULT_KW | EXISTENTIAL_KW | UNION_KW | RAW_KW | MACRO_RULES_KW | YEET_KW + | OFFSET_OF_KW + | ASM_KW + | FORMAT_ARGS_KW ) } pub fn is_punct(self) -> bool { @@ -435,12 +446,16 @@ impl SyntaxKind { pub fn from_contextual_keyword(ident: &str) -> Option<SyntaxKind> { let kw = match ident { "auto" => AUTO_KW, + "builtin" => BUILTIN_KW, "default" => DEFAULT_KW, "existential" => EXISTENTIAL_KW, "union" => UNION_KW, "raw" => RAW_KW, "macro_rules" => MACRO_RULES_KW, "yeet" => YEET_KW, + "offset_of" => OFFSET_OF_KW, + "asm" => ASM_KW, + "format_args" => FORMAT_ARGS_KW, _ => return None, }; Some(kw) @@ -481,5 +496,5 @@ impl SyntaxKind { } } #[macro_export] -macro_rules ! T { [;] => { $ crate :: SyntaxKind :: SEMICOLON } ; [,] => { $ crate :: SyntaxKind :: COMMA } ; ['('] => { $ crate :: SyntaxKind :: L_PAREN } ; [')'] => { $ crate :: SyntaxKind :: R_PAREN } ; ['{'] => { $ crate :: SyntaxKind :: L_CURLY } ; ['}'] => { $ crate :: SyntaxKind :: R_CURLY } ; ['['] => { $ crate :: SyntaxKind :: L_BRACK } ; [']'] => { $ crate :: SyntaxKind :: R_BRACK } ; [<] => { $ crate :: SyntaxKind :: L_ANGLE } ; [>] => { $ crate :: SyntaxKind :: R_ANGLE } ; [@] => { $ crate :: SyntaxKind :: AT } ; [#] => { $ crate :: SyntaxKind :: POUND } ; [~] => { $ crate :: SyntaxKind :: TILDE } ; [?] => { $ crate :: SyntaxKind :: QUESTION } ; [$] => { $ crate :: SyntaxKind :: DOLLAR } ; [&] => { $ crate :: SyntaxKind :: AMP } ; [|] => { $ crate :: SyntaxKind :: PIPE } ; [+] => { $ crate :: SyntaxKind :: PLUS } ; [*] => { $ crate :: SyntaxKind :: STAR } ; [/] => { $ crate :: SyntaxKind :: SLASH } ; [^] => { $ crate :: SyntaxKind :: CARET } ; [%] => { $ crate :: SyntaxKind :: PERCENT } ; [_] => { $ crate :: SyntaxKind :: UNDERSCORE } ; [.] => { $ crate :: SyntaxKind :: DOT } ; [..] => { $ crate :: SyntaxKind :: DOT2 } ; [...] => { $ crate :: SyntaxKind :: DOT3 } ; [..=] => { $ crate :: SyntaxKind :: DOT2EQ } ; [:] => { $ crate :: SyntaxKind :: COLON } ; [::] => { $ crate :: SyntaxKind :: COLON2 } ; [=] => { $ crate :: SyntaxKind :: EQ } ; [==] => { $ crate :: SyntaxKind :: EQ2 } ; [=>] => { $ crate :: SyntaxKind :: FAT_ARROW } ; [!] => { $ crate :: SyntaxKind :: BANG } ; [!=] => { $ crate :: SyntaxKind :: NEQ } ; [-] => { $ crate :: SyntaxKind :: MINUS } ; [->] => { $ crate :: SyntaxKind :: THIN_ARROW } ; [<=] => { $ crate :: SyntaxKind :: LTEQ } ; [>=] => { $ crate :: SyntaxKind :: GTEQ } ; [+=] => { $ crate :: SyntaxKind :: PLUSEQ } ; [-=] => { $ crate :: SyntaxKind :: MINUSEQ } ; [|=] => { $ crate :: SyntaxKind :: PIPEEQ } ; [&=] => { $ crate :: SyntaxKind :: AMPEQ } ; [^=] => { $ crate :: SyntaxKind :: CARETEQ } ; [/=] => { $ crate :: SyntaxKind :: SLASHEQ } ; [*=] => { $ crate :: SyntaxKind :: STAREQ } ; [%=] => { $ crate :: SyntaxKind :: PERCENTEQ } ; [&&] => { $ crate :: SyntaxKind :: AMP2 } ; [||] => { $ crate :: SyntaxKind :: PIPE2 } ; [<<] => { $ crate :: SyntaxKind :: SHL } ; [>>] => { $ crate :: SyntaxKind :: SHR } ; [<<=] => { $ crate :: SyntaxKind :: SHLEQ } ; [>>=] => { $ crate :: SyntaxKind :: SHREQ } ; [as] => { $ crate :: SyntaxKind :: AS_KW } ; [async] => { $ crate :: SyntaxKind :: ASYNC_KW } ; [await] => { $ crate :: SyntaxKind :: AWAIT_KW } ; [box] => { $ crate :: SyntaxKind :: BOX_KW } ; [break] => { $ crate :: SyntaxKind :: BREAK_KW } ; [const] => { $ crate :: SyntaxKind :: CONST_KW } ; [continue] => { $ crate :: SyntaxKind :: CONTINUE_KW } ; [crate] => { $ crate :: SyntaxKind :: CRATE_KW } ; [do] => { $ crate :: SyntaxKind :: DO_KW } ; [dyn] => { $ crate :: SyntaxKind :: DYN_KW } ; [else] => { $ crate :: SyntaxKind :: ELSE_KW } ; [enum] => { $ crate :: SyntaxKind :: ENUM_KW } ; [extern] => { $ crate :: SyntaxKind :: EXTERN_KW } ; [false] => { $ crate :: SyntaxKind :: FALSE_KW } ; [fn] => { $ crate :: SyntaxKind :: FN_KW } ; [for] => { $ crate :: SyntaxKind :: FOR_KW } ; [if] => { $ crate :: SyntaxKind :: IF_KW } ; [impl] => { $ crate :: SyntaxKind :: IMPL_KW } ; [in] => { $ crate :: SyntaxKind :: IN_KW } ; [let] => { $ crate :: SyntaxKind :: LET_KW } ; [loop] => { $ crate :: SyntaxKind :: LOOP_KW } ; [macro] => { $ crate :: SyntaxKind :: MACRO_KW } ; [match] => { $ crate :: SyntaxKind :: MATCH_KW } ; [mod] => { $ crate :: SyntaxKind :: MOD_KW } ; [move] => { $ crate :: SyntaxKind :: MOVE_KW } ; [mut] => { $ crate :: SyntaxKind :: MUT_KW } ; [pub] => { $ crate :: SyntaxKind :: PUB_KW } ; [ref] => { $ crate :: SyntaxKind :: REF_KW } ; [return] => { $ crate :: SyntaxKind :: RETURN_KW } ; [self] => { $ crate :: SyntaxKind :: SELF_KW } ; [Self] => { $ crate :: SyntaxKind :: SELF_TYPE_KW } ; [static] => { $ crate :: SyntaxKind :: STATIC_KW } ; [struct] => { $ crate :: SyntaxKind :: STRUCT_KW } ; [super] => { $ crate :: SyntaxKind :: SUPER_KW } ; [trait] => { $ crate :: SyntaxKind :: TRAIT_KW } ; [true] => { $ crate :: SyntaxKind :: TRUE_KW } ; [try] => { $ crate :: SyntaxKind :: TRY_KW } ; [type] => { $ crate :: SyntaxKind :: TYPE_KW } ; [unsafe] => { $ crate :: SyntaxKind :: UNSAFE_KW } ; [use] => { $ crate :: SyntaxKind :: USE_KW } ; [where] => { $ crate :: SyntaxKind :: WHERE_KW } ; [while] => { $ crate :: SyntaxKind :: WHILE_KW } ; [yield] => { $ crate :: SyntaxKind :: YIELD_KW } ; [auto] => { $ crate :: SyntaxKind :: AUTO_KW } ; [default] => { $ crate :: SyntaxKind :: DEFAULT_KW } ; [existential] => { $ crate :: SyntaxKind :: EXISTENTIAL_KW } ; [union] => { $ crate :: SyntaxKind :: UNION_KW } ; [raw] => { $ crate :: SyntaxKind :: RAW_KW } ; [macro_rules] => { $ crate :: SyntaxKind :: MACRO_RULES_KW } ; [yeet] => { $ crate :: SyntaxKind :: YEET_KW } ; [lifetime_ident] => { $ crate :: SyntaxKind :: LIFETIME_IDENT } ; [ident] => { $ crate :: SyntaxKind :: IDENT } ; [shebang] => { $ crate :: SyntaxKind :: SHEBANG } ; } +macro_rules ! T { [;] => { $ crate :: SyntaxKind :: SEMICOLON } ; [,] => { $ crate :: SyntaxKind :: COMMA } ; ['('] => { $ crate :: SyntaxKind :: L_PAREN } ; [')'] => { $ crate :: SyntaxKind :: R_PAREN } ; ['{'] => { $ crate :: SyntaxKind :: L_CURLY } ; ['}'] => { $ crate :: SyntaxKind :: R_CURLY } ; ['['] => { $ crate :: SyntaxKind :: L_BRACK } ; [']'] => { $ crate :: SyntaxKind :: R_BRACK } ; [<] => { $ crate :: SyntaxKind :: L_ANGLE } ; [>] => { $ crate :: SyntaxKind :: R_ANGLE } ; [@] => { $ crate :: SyntaxKind :: AT } ; [#] => { $ crate :: SyntaxKind :: POUND } ; [~] => { $ crate :: SyntaxKind :: TILDE } ; [?] => { $ crate :: SyntaxKind :: QUESTION } ; [$] => { $ crate :: SyntaxKind :: DOLLAR } ; [&] => { $ crate :: SyntaxKind :: AMP } ; [|] => { $ crate :: SyntaxKind :: PIPE } ; [+] => { $ crate :: SyntaxKind :: PLUS } ; [*] => { $ crate :: SyntaxKind :: STAR } ; [/] => { $ crate :: SyntaxKind :: SLASH } ; [^] => { $ crate :: SyntaxKind :: CARET } ; [%] => { $ crate :: SyntaxKind :: PERCENT } ; [_] => { $ crate :: SyntaxKind :: UNDERSCORE } ; [.] => { $ crate :: SyntaxKind :: DOT } ; [..] => { $ crate :: SyntaxKind :: DOT2 } ; [...] => { $ crate :: SyntaxKind :: DOT3 } ; [..=] => { $ crate :: SyntaxKind :: DOT2EQ } ; [:] => { $ crate :: SyntaxKind :: COLON } ; [::] => { $ crate :: SyntaxKind :: COLON2 } ; [=] => { $ crate :: SyntaxKind :: EQ } ; [==] => { $ crate :: SyntaxKind :: EQ2 } ; [=>] => { $ crate :: SyntaxKind :: FAT_ARROW } ; [!] => { $ crate :: SyntaxKind :: BANG } ; [!=] => { $ crate :: SyntaxKind :: NEQ } ; [-] => { $ crate :: SyntaxKind :: MINUS } ; [->] => { $ crate :: SyntaxKind :: THIN_ARROW } ; [<=] => { $ crate :: SyntaxKind :: LTEQ } ; [>=] => { $ crate :: SyntaxKind :: GTEQ } ; [+=] => { $ crate :: SyntaxKind :: PLUSEQ } ; [-=] => { $ crate :: SyntaxKind :: MINUSEQ } ; [|=] => { $ crate :: SyntaxKind :: PIPEEQ } ; [&=] => { $ crate :: SyntaxKind :: AMPEQ } ; [^=] => { $ crate :: SyntaxKind :: CARETEQ } ; [/=] => { $ crate :: SyntaxKind :: SLASHEQ } ; [*=] => { $ crate :: SyntaxKind :: STAREQ } ; [%=] => { $ crate :: SyntaxKind :: PERCENTEQ } ; [&&] => { $ crate :: SyntaxKind :: AMP2 } ; [||] => { $ crate :: SyntaxKind :: PIPE2 } ; [<<] => { $ crate :: SyntaxKind :: SHL } ; [>>] => { $ crate :: SyntaxKind :: SHR } ; [<<=] => { $ crate :: SyntaxKind :: SHLEQ } ; [>>=] => { $ crate :: SyntaxKind :: SHREQ } ; [as] => { $ crate :: SyntaxKind :: AS_KW } ; [async] => { $ crate :: SyntaxKind :: ASYNC_KW } ; [await] => { $ crate :: SyntaxKind :: AWAIT_KW } ; [box] => { $ crate :: SyntaxKind :: BOX_KW } ; [break] => { $ crate :: SyntaxKind :: BREAK_KW } ; [const] => { $ crate :: SyntaxKind :: CONST_KW } ; [continue] => { $ crate :: SyntaxKind :: CONTINUE_KW } ; [crate] => { $ crate :: SyntaxKind :: CRATE_KW } ; [do] => { $ crate :: SyntaxKind :: DO_KW } ; [dyn] => { $ crate :: SyntaxKind :: DYN_KW } ; [else] => { $ crate :: SyntaxKind :: ELSE_KW } ; [enum] => { $ crate :: SyntaxKind :: ENUM_KW } ; [extern] => { $ crate :: SyntaxKind :: EXTERN_KW } ; [false] => { $ crate :: SyntaxKind :: FALSE_KW } ; [fn] => { $ crate :: SyntaxKind :: FN_KW } ; [for] => { $ crate :: SyntaxKind :: FOR_KW } ; [if] => { $ crate :: SyntaxKind :: IF_KW } ; [impl] => { $ crate :: SyntaxKind :: IMPL_KW } ; [in] => { $ crate :: SyntaxKind :: IN_KW } ; [let] => { $ crate :: SyntaxKind :: LET_KW } ; [loop] => { $ crate :: SyntaxKind :: LOOP_KW } ; [macro] => { $ crate :: SyntaxKind :: MACRO_KW } ; [match] => { $ crate :: SyntaxKind :: MATCH_KW } ; [mod] => { $ crate :: SyntaxKind :: MOD_KW } ; [move] => { $ crate :: SyntaxKind :: MOVE_KW } ; [mut] => { $ crate :: SyntaxKind :: MUT_KW } ; [pub] => { $ crate :: SyntaxKind :: PUB_KW } ; [ref] => { $ crate :: SyntaxKind :: REF_KW } ; [return] => { $ crate :: SyntaxKind :: RETURN_KW } ; [self] => { $ crate :: SyntaxKind :: SELF_KW } ; [Self] => { $ crate :: SyntaxKind :: SELF_TYPE_KW } ; [static] => { $ crate :: SyntaxKind :: STATIC_KW } ; [struct] => { $ crate :: SyntaxKind :: STRUCT_KW } ; [super] => { $ crate :: SyntaxKind :: SUPER_KW } ; [trait] => { $ crate :: SyntaxKind :: TRAIT_KW } ; [true] => { $ crate :: SyntaxKind :: TRUE_KW } ; [try] => { $ crate :: SyntaxKind :: TRY_KW } ; [type] => { $ crate :: SyntaxKind :: TYPE_KW } ; [unsafe] => { $ crate :: SyntaxKind :: UNSAFE_KW } ; [use] => { $ crate :: SyntaxKind :: USE_KW } ; [where] => { $ crate :: SyntaxKind :: WHERE_KW } ; [while] => { $ crate :: SyntaxKind :: WHILE_KW } ; [yield] => { $ crate :: SyntaxKind :: YIELD_KW } ; [auto] => { $ crate :: SyntaxKind :: AUTO_KW } ; [builtin] => { $ crate :: SyntaxKind :: BUILTIN_KW } ; [default] => { $ crate :: SyntaxKind :: DEFAULT_KW } ; [existential] => { $ crate :: SyntaxKind :: EXISTENTIAL_KW } ; [union] => { $ crate :: SyntaxKind :: UNION_KW } ; [raw] => { $ crate :: SyntaxKind :: RAW_KW } ; [macro_rules] => { $ crate :: SyntaxKind :: MACRO_RULES_KW } ; [yeet] => { $ crate :: SyntaxKind :: YEET_KW } ; [offset_of] => { $ crate :: SyntaxKind :: OFFSET_OF_KW } ; [asm] => { $ crate :: SyntaxKind :: ASM_KW } ; [format_args] => { $ crate :: SyntaxKind :: FORMAT_ARGS_KW } ; [lifetime_ident] => { $ crate :: SyntaxKind :: LIFETIME_IDENT } ; [ident] => { $ crate :: SyntaxKind :: IDENT } ; [shebang] => { $ crate :: SyntaxKind :: SHEBANG } ; } pub use T; diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0022_recover_from_missing_const_default.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0022_recover_from_missing_const_default.rast index 809ad1b8d..49f163b16 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0022_recover_from_missing_const_default.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0022_recover_from_missing_const_default.rast @@ -20,7 +20,8 @@ SOURCE_FILE IDENT "i32" WHITESPACE " " EQ "=" - WHITESPACE " " + WHITESPACE " " + CONST_ARG COMMA "," WHITESPACE " " CONST_PARAM @@ -37,8 +38,9 @@ SOURCE_FILE IDENT "i32" WHITESPACE " " EQ "=" + CONST_ARG R_ANGLE ">" SEMICOLON ";" WHITESPACE "\n" -error 23: expected a generic const argument +error 24: expected a generic const argument error 40: expected a generic const argument diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0132_box_expr.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0132_box_expr.rast deleted file mode 100644 index b21f37cd8..000000000 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0132_box_expr.rast +++ /dev/null @@ -1,90 +0,0 @@ -SOURCE_FILE - FN - FN_KW "fn" - WHITESPACE " " - NAME - IDENT "foo" - PARAM_LIST - L_PAREN "(" - R_PAREN ")" - WHITESPACE " " - BLOCK_EXPR - STMT_LIST - L_CURLY "{" - WHITESPACE "\n " - LET_STMT - LET_KW "let" - WHITESPACE " " - IDENT_PAT - NAME - IDENT "x" - WHITESPACE " " - EQ "=" - WHITESPACE " " - BOX_EXPR - BOX_KW "box" - WHITESPACE " " - LITERAL - INT_NUMBER "1i32" - SEMICOLON ";" - WHITESPACE "\n " - LET_STMT - LET_KW "let" - WHITESPACE " " - IDENT_PAT - NAME - IDENT "y" - WHITESPACE " " - EQ "=" - WHITESPACE " " - TUPLE_EXPR - L_PAREN "(" - BOX_EXPR - BOX_KW "box" - WHITESPACE " " - LITERAL - INT_NUMBER "1i32" - COMMA "," - WHITESPACE " " - BOX_EXPR - BOX_KW "box" - WHITESPACE " " - LITERAL - INT_NUMBER "2i32" - R_PAREN ")" - SEMICOLON ";" - WHITESPACE "\n " - LET_STMT - LET_KW "let" - WHITESPACE " " - IDENT_PAT - NAME - IDENT "z" - WHITESPACE " " - EQ "=" - WHITESPACE " " - CALL_EXPR - PATH_EXPR - PATH - PATH_SEGMENT - NAME_REF - IDENT "Foo" - ARG_LIST - L_PAREN "(" - BOX_EXPR - BOX_KW "box" - WHITESPACE " " - LITERAL - INT_NUMBER "1i32" - COMMA "," - WHITESPACE " " - BOX_EXPR - BOX_KW "box" - WHITESPACE " " - LITERAL - INT_NUMBER "2i32" - R_PAREN ")" - SEMICOLON ";" - WHITESPACE "\n" - R_CURLY "}" - WHITESPACE "\n" diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0132_box_expr.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0132_box_expr.rs deleted file mode 100644 index fc9923b71..000000000 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0132_box_expr.rs +++ /dev/null @@ -1,5 +0,0 @@ -fn foo() { - let x = box 1i32; - let y = (box 1i32, box 2i32); - let z = Foo(box 1i32, box 2i32); -} diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0188_const_param_default_path.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0188_const_param_default_path.rast index 11002bf98..3f5fb47d2 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0188_const_param_default_path.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0188_const_param_default_path.rast @@ -21,16 +21,17 @@ SOURCE_FILE WHITESPACE " " EQ "=" WHITESPACE " " - PATH_EXPR - PATH + CONST_ARG + PATH_EXPR PATH + PATH + PATH_SEGMENT + NAME_REF + IDENT "i32" + COLON2 "::" PATH_SEGMENT NAME_REF - IDENT "i32" - COLON2 "::" - PATH_SEGMENT - NAME_REF - IDENT "MAX" + IDENT "MAX" R_ANGLE ">" SEMICOLON ";" WHITESPACE "\n" diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0199_const_param_default_expression.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0199_const_param_default_expression.rast index 0607ff54f..d65011374 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0199_const_param_default_expression.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0199_const_param_default_expression.rast @@ -21,14 +21,15 @@ SOURCE_FILE WHITESPACE " " EQ "=" WHITESPACE " " - BLOCK_EXPR - STMT_LIST - L_CURLY "{" - WHITESPACE " " - LITERAL - INT_NUMBER "1" - WHITESPACE " " - R_CURLY "}" + CONST_ARG + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE " " + LITERAL + INT_NUMBER "1" + WHITESPACE " " + R_CURLY "}" R_ANGLE ">" SEMICOLON ";" WHITESPACE "\n" diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0200_const_param_default_literal.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0200_const_param_default_literal.rast index 8e5231365..6de10353b 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0200_const_param_default_literal.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0200_const_param_default_literal.rast @@ -21,10 +21,11 @@ SOURCE_FILE WHITESPACE " " EQ "=" WHITESPACE " " - PREFIX_EXPR - MINUS "-" - LITERAL - INT_NUMBER "1" + CONST_ARG + PREFIX_EXPR + MINUS "-" + LITERAL + INT_NUMBER "1" R_ANGLE ">" SEMICOLON ";" WHITESPACE "\n" diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0207_builtin_expr.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0207_builtin_expr.rast new file mode 100644 index 000000000..361900b6d --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0207_builtin_expr.rast @@ -0,0 +1,105 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "foo" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE "\n " + EXPR_STMT + ASM_EXPR + BUILTIN_KW "builtin" + POUND "#" + ASM_KW "asm" + L_PAREN "(" + LITERAL + INT_NUMBER "0" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n " + EXPR_STMT + FORMAT_ARGS_EXPR + BUILTIN_KW "builtin" + POUND "#" + FORMAT_ARGS_KW "format_args" + L_PAREN "(" + LITERAL + STRING "\"\"" + COMMA "," + WHITESPACE " " + FORMAT_ARGS_ARG + LITERAL + INT_NUMBER "0" + COMMA "," + WHITESPACE " " + FORMAT_ARGS_ARG + LITERAL + INT_NUMBER "1" + COMMA "," + WHITESPACE " " + FORMAT_ARGS_ARG + NAME + IDENT "a" + WHITESPACE " " + EQ "=" + WHITESPACE " " + BIN_EXPR + LITERAL + INT_NUMBER "2" + WHITESPACE " " + PLUS "+" + WHITESPACE " " + LITERAL + INT_NUMBER "3" + COMMA "," + WHITESPACE " " + FORMAT_ARGS_ARG + BIN_EXPR + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "a" + WHITESPACE " " + PLUS "+" + WHITESPACE " " + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "b" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n " + EXPR_STMT + OFFSET_OF_EXPR + BUILTIN_KW "builtin" + POUND "#" + OFFSET_OF_KW "offset_of" + L_PAREN "(" + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Foo" + COMMA "," + WHITESPACE " " + NAME_REF + IDENT "bar" + DOT "." + NAME_REF + IDENT "baz" + DOT "." + NAME_REF + INT_NUMBER "0" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" + R_CURLY "}" + WHITESPACE "\n" diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0207_builtin_expr.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0207_builtin_expr.rs new file mode 100644 index 000000000..14431b021 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0207_builtin_expr.rs @@ -0,0 +1,5 @@ +fn foo() { + builtin#asm(0); + builtin#format_args("", 0, 1, a = 2 + 3, a + b); + builtin#offset_of(Foo, bar.baz.0); +} diff --git a/src/tools/rust-analyzer/crates/proc-macro-test/build.rs b/src/tools/rust-analyzer/crates/proc-macro-test/build.rs index 19a5caa4c..782715786 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-test/build.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-test/build.rs @@ -71,6 +71,10 @@ fn main() { .arg("--target-dir") .arg(&target_dir); + if let Ok(target) = std::env::var("TARGET") { + cmd.args(["--target", &target]); + } + println!("Running {cmd:?}"); let output = cmd.output().unwrap(); diff --git a/src/tools/rust-analyzer/crates/profile/src/stop_watch.rs b/src/tools/rust-analyzer/crates/profile/src/stop_watch.rs index 71303d5a6..814a02574 100644 --- a/src/tools/rust-analyzer/crates/profile/src/stop_watch.rs +++ b/src/tools/rust-analyzer/crates/profile/src/stop_watch.rs @@ -10,13 +10,13 @@ pub struct StopWatch { time: Instant, #[cfg(target_os = "linux")] counter: Option<perf_event::Counter>, - memory: Option<MemoryUsage>, + memory: MemoryUsage, } pub struct StopWatchSpan { pub time: Duration, pub instructions: Option<u64>, - pub memory: Option<MemoryUsage>, + pub memory: MemoryUsage, } impl StopWatch { @@ -45,20 +45,16 @@ impl StopWatch { None } }; + let memory = MemoryUsage::now(); let time = Instant::now(); StopWatch { time, #[cfg(target_os = "linux")] counter, - memory: None, + memory, } } - pub fn memory(mut self, yes: bool) -> StopWatch { - if yes { - self.memory = Some(MemoryUsage::now()); - } - self - } + pub fn elapsed(&mut self) -> StopWatchSpan { let time = self.time.elapsed(); @@ -69,7 +65,7 @@ impl StopWatch { #[cfg(not(target_os = "linux"))] let instructions = None; - let memory = self.memory.map(|it| MemoryUsage::now() - it); + let memory = MemoryUsage::now() - self.memory; StopWatchSpan { time, instructions, memory } } } @@ -93,9 +89,7 @@ impl fmt::Display for StopWatchSpan { } write!(f, ", {instructions}{prefix}instr")?; } - if let Some(memory) = self.memory { - write!(f, ", {memory}")?; - } + write!(f, ", {}", self.memory)?; Ok(()) } } diff --git a/src/tools/rust-analyzer/crates/project-model/src/rustc_cfg.rs b/src/tools/rust-analyzer/crates/project-model/src/rustc_cfg.rs index 8392718b2..c5d55f7d2 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/rustc_cfg.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/rustc_cfg.rs @@ -2,14 +2,29 @@ use std::process::Command; +use anyhow::Context; use rustc_hash::FxHashMap; -use crate::{cfg_flag::CfgFlag, utf8_stdout, ManifestPath}; +use crate::{cfg_flag::CfgFlag, utf8_stdout, ManifestPath, Sysroot}; + +/// Determines how `rustc --print cfg` is discovered and invoked. +/// +/// There options are supported: +/// - [`RustcCfgConfig::Cargo`], which relies on `cargo rustc --print cfg` +/// and `RUSTC_BOOTSTRAP`. +/// - [`RustcCfgConfig::Explicit`], which uses an explicit path to the `rustc` +/// binary in the sysroot. +/// - [`RustcCfgConfig::Discover`], which uses [`toolchain::rustc`]. +pub(crate) enum RustcCfgConfig<'a> { + Cargo(&'a ManifestPath), + Explicit(&'a Sysroot), + Discover, +} pub(crate) fn get( - cargo_toml: Option<&ManifestPath>, target: Option<&str>, extra_env: &FxHashMap<String, String>, + config: RustcCfgConfig<'_>, ) -> Vec<CfgFlag> { let _p = profile::span("rustc_cfg::get"); let mut res = Vec::with_capacity(6 * 2 + 1); @@ -25,49 +40,67 @@ pub(crate) fn get( // Add miri cfg, which is useful for mir eval in stdlib res.push(CfgFlag::Atom("miri".into())); - match get_rust_cfgs(cargo_toml, target, extra_env) { + let rustc_cfgs = get_rust_cfgs(target, extra_env, config); + + let rustc_cfgs = match rustc_cfgs { + Ok(cfgs) => cfgs, + Err(e) => { + tracing::error!(?e, "failed to get rustc cfgs"); + return res; + } + }; + + let rustc_cfgs = + rustc_cfgs.lines().map(|it| it.parse::<CfgFlag>()).collect::<Result<Vec<_>, _>>(); + + match rustc_cfgs { Ok(rustc_cfgs) => { - tracing::debug!( - "rustc cfgs found: {:?}", - rustc_cfgs - .lines() - .map(|it| it.parse::<CfgFlag>().map(|it| it.to_string())) - .collect::<Vec<_>>() - ); - res.extend(rustc_cfgs.lines().filter_map(|it| it.parse().ok())); + tracing::debug!(?rustc_cfgs, "rustc cfgs found"); + res.extend(rustc_cfgs); + } + Err(e) => { + tracing::error!(?e, "failed to get rustc cfgs") } - Err(e) => tracing::error!("failed to get rustc cfgs: {e:?}"), } res } fn get_rust_cfgs( - cargo_toml: Option<&ManifestPath>, target: Option<&str>, extra_env: &FxHashMap<String, String>, + config: RustcCfgConfig<'_>, ) -> anyhow::Result<String> { - if let Some(cargo_toml) = cargo_toml { - let mut cargo_config = Command::new(toolchain::cargo()); - cargo_config.envs(extra_env); - cargo_config - .current_dir(cargo_toml.parent()) - .args(["rustc", "-Z", "unstable-options", "--print", "cfg"]) - .env("RUSTC_BOOTSTRAP", "1"); - if let Some(target) = target { - cargo_config.args(["--target", target]); + let mut cmd = match config { + RustcCfgConfig::Cargo(cargo_toml) => { + let mut cmd = Command::new(toolchain::cargo()); + cmd.envs(extra_env); + cmd.current_dir(cargo_toml.parent()) + .args(["rustc", "-Z", "unstable-options", "--print", "cfg"]) + .env("RUSTC_BOOTSTRAP", "1"); + if let Some(target) = target { + cmd.args(["--target", target]); + } + + return utf8_stdout(cmd).context("Unable to run `cargo rustc`"); } - match utf8_stdout(cargo_config) { - Ok(it) => return Ok(it), - Err(e) => tracing::debug!("{e:?}: falling back to querying rustc for cfgs"), + RustcCfgConfig::Explicit(sysroot) => { + let rustc: std::path::PathBuf = sysroot.discover_rustc()?.into(); + tracing::debug!(?rustc, "using explicit rustc from sysroot"); + Command::new(rustc) } - } - // using unstable cargo features failed, fall back to using plain rustc - let mut cmd = Command::new(toolchain::rustc()); + RustcCfgConfig::Discover => { + let rustc = toolchain::rustc(); + tracing::debug!(?rustc, "using rustc from env"); + Command::new(rustc) + } + }; + cmd.envs(extra_env); cmd.args(["--print", "cfg", "-O"]); if let Some(target) = target { cmd.args(["--target", target]); } - utf8_stdout(cmd) + + utf8_stdout(cmd).context("Unable to run `rustc`") } diff --git a/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs b/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs index da862c9e8..fe046dd14 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs @@ -115,10 +115,19 @@ impl Sysroot { Ok(Sysroot::load(sysroot_dir, src)) } - pub fn discover_rustc(&self) -> Option<ManifestPath> { + pub fn discover_rustc_src(&self) -> Option<ManifestPath> { get_rustc_src(&self.root) } + pub fn discover_rustc(&self) -> Result<AbsPathBuf, std::io::Error> { + let rustc = self.root.join("bin/rustc"); + tracing::debug!(?rustc, "checking for rustc binary at location"); + match fs::metadata(&rustc) { + Ok(_) => Ok(rustc), + Err(e) => Err(e), + } + } + pub fn with_sysroot_dir(sysroot_dir: AbsPathBuf) -> Result<Sysroot> { let sysroot_src_dir = discover_sysroot_src_dir(&sysroot_dir).ok_or_else(|| { format_err!("can't load standard library from sysroot path {sysroot_dir}") diff --git a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs index f51ea7eeb..e0209ca15 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs @@ -2,7 +2,7 @@ //! metadata` or `rust-project.json`) into representation stored in the salsa //! database -- `CrateGraph`. -use std::{collections::VecDeque, fmt, fs, process::Command, sync}; +use std::{collections::VecDeque, fmt, fs, iter, process::Command, str::FromStr, sync}; use anyhow::{format_err, Context}; use base_db::{ @@ -21,7 +21,7 @@ use crate::{ cargo_workspace::{DepKind, PackageData, RustLibSource}, cfg_flag::CfgFlag, project_json::Crate, - rustc_cfg, + rustc_cfg::{self, RustcCfgConfig}, sysroot::SysrootCrate, target_data_layout, utf8_stdout, CargoConfig, CargoWorkspace, InvocationStrategy, ManifestPath, Package, ProjectJson, ProjectManifest, Sysroot, TargetData, TargetKind, WorkspaceBuildScripts, @@ -167,7 +167,8 @@ impl ProjectWorkspace { cmd.envs(&config.extra_env); cmd.arg("--version").current_dir(current_dir); cmd - })?; + }) + .with_context(|| format!("Failed to query rust toolchain version at {current_dir}, is your toolchain setup correctly?"))?; anyhow::Ok( cargo_version .get(prefix.len()..) @@ -239,9 +240,9 @@ impl ProjectWorkspace { Some(RustLibSource::Path(path)) => ManifestPath::try_from(path.clone()) .map_err(|p| Some(format!("rustc source path is not absolute: {p}"))), Some(RustLibSource::Discover) => { - sysroot.as_ref().ok().and_then(Sysroot::discover_rustc).ok_or_else(|| { - Some(format!("Failed to discover rustc source for sysroot.")) - }) + sysroot.as_ref().ok().and_then(Sysroot::discover_rustc_src).ok_or_else( + || Some(format!("Failed to discover rustc source for sysroot.")), + ) } None => Err(None), }; @@ -278,8 +279,11 @@ impl ProjectWorkspace { } }); - let rustc_cfg = - rustc_cfg::get(Some(&cargo_toml), config.target.as_deref(), &config.extra_env); + let rustc_cfg = rustc_cfg::get( + config.target.as_deref(), + &config.extra_env, + RustcCfgConfig::Cargo(cargo_toml), + ); let cfg_overrides = config.cfg_overrides.clone(); let data_layout = target_data_layout::get( @@ -330,11 +334,18 @@ impl ProjectWorkspace { } (None, None) => Err(None), }; - if let Ok(sysroot) = &sysroot { - tracing::info!(src_root = %sysroot.src_root(), root = %sysroot.root(), "Using sysroot"); - } + let config = match &sysroot { + Ok(sysroot) => { + tracing::debug!(src_root = %sysroot.src_root(), root = %sysroot.root(), "Using sysroot"); + RustcCfgConfig::Explicit(sysroot) + } + Err(_) => { + tracing::debug!("discovering sysroot"); + RustcCfgConfig::Discover + } + }; - let rustc_cfg = rustc_cfg::get(None, target, extra_env); + let rustc_cfg = rustc_cfg::get(target, extra_env, config); ProjectWorkspace::Json { project: project_json, sysroot, rustc_cfg, toolchain } } @@ -356,10 +367,18 @@ impl ProjectWorkspace { } None => Err(None), }; - if let Ok(sysroot) = &sysroot { - tracing::info!(src_root = %sysroot.src_root(), root = %sysroot.root(), "Using sysroot"); - } - let rustc_cfg = rustc_cfg::get(None, None, &Default::default()); + let rustc_config = match &sysroot { + Ok(sysroot) => { + tracing::info!(src_root = %sysroot.src_root(), root = %sysroot.root(), "Using sysroot"); + RustcCfgConfig::Explicit(sysroot) + } + Err(_) => { + tracing::info!("discovering sysroot"); + RustcCfgConfig::Discover + } + }; + + let rustc_cfg = rustc_cfg::get(None, &FxHashMap::default(), rustc_config); Ok(ProjectWorkspace::DetachedFiles { files: detached_files, sysroot, rustc_cfg }) } @@ -729,6 +748,7 @@ fn project_json_to_crate_graph( ) }); + let r_a_cfg_flag = CfgFlag::Atom("rust_analyzer".to_owned()); let mut cfg_cache: FxHashMap<&str, Vec<CfgFlag>> = FxHashMap::default(); let crates: FxHashMap<CrateId, CrateId> = project .crates() @@ -753,9 +773,14 @@ fn project_json_to_crate_graph( let env = env.clone().into_iter().collect(); let target_cfgs = match target.as_deref() { - Some(target) => cfg_cache - .entry(target) - .or_insert_with(|| rustc_cfg::get(None, Some(target), extra_env)), + Some(target) => cfg_cache.entry(target).or_insert_with(|| { + let rustc_cfg = match sysroot { + Some(sysroot) => RustcCfgConfig::Explicit(sysroot), + None => RustcCfgConfig::Discover, + }; + + rustc_cfg::get(Some(target), extra_env, rustc_cfg) + }), None => &rustc_cfg, }; @@ -764,7 +789,12 @@ fn project_json_to_crate_graph( *edition, display_name.clone(), version.clone(), - target_cfgs.iter().chain(cfg.iter()).cloned().collect(), + target_cfgs + .iter() + .chain(cfg.iter()) + .chain(iter::once(&r_a_cfg_flag)) + .cloned() + .collect(), None, env, *is_proc_macro, @@ -819,7 +849,7 @@ fn cargo_to_crate_graph( sysroot: Option<&Sysroot>, rustc_cfg: Vec<CfgFlag>, override_cfg: &CfgOverrides, - // Don't compute cfg and use this if present + // Don't compute cfg and use this if present, only used for the sysroot experiment hack forced_cfg: Option<CfgOptions>, build_scripts: &WorkspaceBuildScripts, target_layout: TargetLayoutLoadResult, @@ -841,12 +871,7 @@ fn cargo_to_crate_graph( None => (SysrootPublicDeps::default(), None), }; - let cfg_options = { - let mut cfg_options = CfgOptions::default(); - cfg_options.extend(rustc_cfg); - cfg_options.insert_atom("debug_assertions".into()); - cfg_options - }; + let cfg_options = create_cfg_options(rustc_cfg); // Mapping of a package to its library target let mut pkg_to_lib_crate = FxHashMap::default(); @@ -865,6 +890,9 @@ fn cargo_to_crate_graph( if cargo[pkg].is_local { cfg_options.insert_atom("test".into()); } + if cargo[pkg].is_member { + cfg_options.insert_atom("rust_analyzer".into()); + } if !override_cfg.global.is_empty() { cfg_options.apply_diff(override_cfg.global.clone()); @@ -1028,8 +1056,8 @@ fn detached_files_to_crate_graph( None => (SysrootPublicDeps::default(), None), }; - let mut cfg_options = CfgOptions::default(); - cfg_options.extend(rustc_cfg); + let mut cfg_options = create_cfg_options(rustc_cfg); + cfg_options.insert_atom("rust_analyzer".into()); for detached_file in detached_files { let file_id = match load(detached_file) { @@ -1227,6 +1255,10 @@ fn add_target_crate_root( let mut env = Env::default(); inject_cargo_env(pkg, &mut env); + if let Ok(cname) = String::from_str(cargo_name) { + // CARGO_CRATE_NAME is the name of the Cargo target with - converted to _, such as the name of the library, binary, example, integration test, or benchmark. + env.set("CARGO_CRATE_NAME", cname.replace("-", "_")); + } if let Some(envs) = build_data.map(|it| &it.envs) { for (k, v) in envs { @@ -1290,8 +1322,7 @@ fn sysroot_to_crate_graph( channel: Option<ReleaseChannel>, ) -> (SysrootPublicDeps, Option<CrateId>) { let _p = profile::span("sysroot_to_crate_graph"); - let mut cfg_options = CfgOptions::default(); - cfg_options.extend(rustc_cfg.clone()); + let cfg_options = create_cfg_options(rustc_cfg.clone()); let sysroot_crates: FxHashMap<SysrootCrate, CrateId> = match &sysroot.hack_cargo_workspace { Some(cargo) => handle_hack_cargo_workspace( load, @@ -1470,3 +1501,10 @@ fn inject_cargo_env(package: &PackageData, env: &mut Env) { env.set("CARGO_PKG_LICENSE_FILE", String::new()); } + +fn create_cfg_options(rustc_cfg: Vec<CfgFlag>) -> CfgOptions { + let mut cfg_options = CfgOptions::default(); + cfg_options.extend(rustc_cfg); + cfg_options.insert_atom("debug_assertions".into()); + cfg_options +} diff --git a/src/tools/rust-analyzer/crates/project-model/test_data/output/cargo_hello_world_project_model.txt b/src/tools/rust-analyzer/crates/project-model/test_data/output/cargo_hello_world_project_model.txt index e595cd827..727d39a30 100644 --- a/src/tools/rust-analyzer/crates/project-model/test_data/output/cargo_hello_world_project_model.txt +++ b/src/tools/rust-analyzer/crates/project-model/test_data/output/cargo_hello_world_project_model.txt @@ -18,6 +18,7 @@ cfg_options: CfgOptions( [ "debug_assertions", + "rust_analyzer", "test", ], ), @@ -81,6 +82,7 @@ cfg_options: CfgOptions( [ "debug_assertions", + "rust_analyzer", "test", ], ), @@ -151,6 +153,7 @@ cfg_options: CfgOptions( [ "debug_assertions", + "rust_analyzer", "test", ], ), @@ -162,7 +165,7 @@ "CARGO_MANIFEST_DIR": "$ROOT$hello-world", "CARGO_PKG_VERSION": "0.1.0", "CARGO_PKG_AUTHORS": "", - "CARGO_CRATE_NAME": "hello_world", + "CARGO_CRATE_NAME": "an_example", "CARGO_PKG_LICENSE_FILE": "", "CARGO_PKG_HOMEPAGE": "", "CARGO_PKG_DESCRIPTION": "", @@ -221,6 +224,7 @@ cfg_options: CfgOptions( [ "debug_assertions", + "rust_analyzer", "test", ], ), @@ -232,7 +236,7 @@ "CARGO_MANIFEST_DIR": "$ROOT$hello-world", "CARGO_PKG_VERSION": "0.1.0", "CARGO_PKG_AUTHORS": "", - "CARGO_CRATE_NAME": "hello_world", + "CARGO_CRATE_NAME": "it", "CARGO_PKG_LICENSE_FILE": "", "CARGO_PKG_HOMEPAGE": "", "CARGO_PKG_DESCRIPTION": "", diff --git a/src/tools/rust-analyzer/crates/project-model/test_data/output/cargo_hello_world_project_model_with_selective_overrides.txt b/src/tools/rust-analyzer/crates/project-model/test_data/output/cargo_hello_world_project_model_with_selective_overrides.txt index e595cd827..727d39a30 100644 --- a/src/tools/rust-analyzer/crates/project-model/test_data/output/cargo_hello_world_project_model_with_selective_overrides.txt +++ b/src/tools/rust-analyzer/crates/project-model/test_data/output/cargo_hello_world_project_model_with_selective_overrides.txt @@ -18,6 +18,7 @@ cfg_options: CfgOptions( [ "debug_assertions", + "rust_analyzer", "test", ], ), @@ -81,6 +82,7 @@ cfg_options: CfgOptions( [ "debug_assertions", + "rust_analyzer", "test", ], ), @@ -151,6 +153,7 @@ cfg_options: CfgOptions( [ "debug_assertions", + "rust_analyzer", "test", ], ), @@ -162,7 +165,7 @@ "CARGO_MANIFEST_DIR": "$ROOT$hello-world", "CARGO_PKG_VERSION": "0.1.0", "CARGO_PKG_AUTHORS": "", - "CARGO_CRATE_NAME": "hello_world", + "CARGO_CRATE_NAME": "an_example", "CARGO_PKG_LICENSE_FILE": "", "CARGO_PKG_HOMEPAGE": "", "CARGO_PKG_DESCRIPTION": "", @@ -221,6 +224,7 @@ cfg_options: CfgOptions( [ "debug_assertions", + "rust_analyzer", "test", ], ), @@ -232,7 +236,7 @@ "CARGO_MANIFEST_DIR": "$ROOT$hello-world", "CARGO_PKG_VERSION": "0.1.0", "CARGO_PKG_AUTHORS": "", - "CARGO_CRATE_NAME": "hello_world", + "CARGO_CRATE_NAME": "it", "CARGO_PKG_LICENSE_FILE": "", "CARGO_PKG_HOMEPAGE": "", "CARGO_PKG_DESCRIPTION": "", diff --git a/src/tools/rust-analyzer/crates/project-model/test_data/output/cargo_hello_world_project_model_with_wildcard_overrides.txt b/src/tools/rust-analyzer/crates/project-model/test_data/output/cargo_hello_world_project_model_with_wildcard_overrides.txt index f10c55d04..89728babd 100644 --- a/src/tools/rust-analyzer/crates/project-model/test_data/output/cargo_hello_world_project_model_with_wildcard_overrides.txt +++ b/src/tools/rust-analyzer/crates/project-model/test_data/output/cargo_hello_world_project_model_with_wildcard_overrides.txt @@ -18,6 +18,7 @@ cfg_options: CfgOptions( [ "debug_assertions", + "rust_analyzer", ], ), potential_cfg_options: None, @@ -80,6 +81,7 @@ cfg_options: CfgOptions( [ "debug_assertions", + "rust_analyzer", ], ), potential_cfg_options: None, @@ -149,6 +151,7 @@ cfg_options: CfgOptions( [ "debug_assertions", + "rust_analyzer", ], ), potential_cfg_options: None, @@ -159,7 +162,7 @@ "CARGO_MANIFEST_DIR": "$ROOT$hello-world", "CARGO_PKG_VERSION": "0.1.0", "CARGO_PKG_AUTHORS": "", - "CARGO_CRATE_NAME": "hello_world", + "CARGO_CRATE_NAME": "an_example", "CARGO_PKG_LICENSE_FILE": "", "CARGO_PKG_HOMEPAGE": "", "CARGO_PKG_DESCRIPTION": "", @@ -218,6 +221,7 @@ cfg_options: CfgOptions( [ "debug_assertions", + "rust_analyzer", ], ), potential_cfg_options: None, @@ -228,7 +232,7 @@ "CARGO_MANIFEST_DIR": "$ROOT$hello-world", "CARGO_PKG_VERSION": "0.1.0", "CARGO_PKG_AUTHORS": "", - "CARGO_CRATE_NAME": "hello_world", + "CARGO_CRATE_NAME": "it", "CARGO_PKG_LICENSE_FILE": "", "CARGO_PKG_HOMEPAGE": "", "CARGO_PKG_DESCRIPTION": "", diff --git a/src/tools/rust-analyzer/crates/project-model/test_data/output/rust_project_hello_world_project_model.txt b/src/tools/rust-analyzer/crates/project-model/test_data/output/rust_project_hello_world_project_model.txt index fb3f5933b..b7bf6cb27 100644 --- a/src/tools/rust-analyzer/crates/project-model/test_data/output/rust_project_hello_world_project_model.txt +++ b/src/tools/rust-analyzer/crates/project-model/test_data/output/rust_project_hello_world_project_model.txt @@ -14,7 +14,9 @@ }, ), cfg_options: CfgOptions( - [], + [ + "debug_assertions", + ], ), potential_cfg_options: None, env: Env { @@ -53,7 +55,9 @@ }, ), cfg_options: CfgOptions( - [], + [ + "debug_assertions", + ], ), potential_cfg_options: None, env: Env { @@ -84,7 +88,9 @@ }, ), cfg_options: CfgOptions( - [], + [ + "debug_assertions", + ], ), potential_cfg_options: None, env: Env { @@ -115,7 +121,9 @@ }, ), cfg_options: CfgOptions( - [], + [ + "debug_assertions", + ], ), potential_cfg_options: None, env: Env { @@ -146,7 +154,9 @@ }, ), cfg_options: CfgOptions( - [], + [ + "debug_assertions", + ], ), potential_cfg_options: None, env: Env { @@ -192,7 +202,9 @@ }, ), cfg_options: CfgOptions( - [], + [ + "debug_assertions", + ], ), potential_cfg_options: None, env: Env { @@ -223,7 +235,9 @@ }, ), cfg_options: CfgOptions( - [], + [ + "debug_assertions", + ], ), potential_cfg_options: None, env: Env { @@ -311,7 +325,9 @@ }, ), cfg_options: CfgOptions( - [], + [ + "debug_assertions", + ], ), potential_cfg_options: None, env: Env { @@ -342,7 +358,9 @@ }, ), cfg_options: CfgOptions( - [], + [ + "debug_assertions", + ], ), potential_cfg_options: None, env: Env { @@ -373,7 +391,9 @@ }, ), cfg_options: CfgOptions( - [], + [ + "debug_assertions", + ], ), potential_cfg_options: None, env: Env { @@ -404,7 +424,9 @@ }, ), cfg_options: CfgOptions( - [], + [ + "rust_analyzer", + ], ), potential_cfg_options: None, env: Env { diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml b/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml index 5bfac7ee4..7410f0a3a 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml +++ b/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml @@ -24,7 +24,7 @@ crossbeam-channel = "0.5.5" dissimilar = "1.0.4" itertools = "0.10.5" scip = "0.1.1" -lsp-types = { version = "=0.94", features = ["proposed"] } +lsp-types = { version = "=0.94.0", features = ["proposed"] } parking_lot = "0.12.1" xflags = "0.3.0" oorandom = "11.1.3" @@ -50,7 +50,6 @@ always-assert = "0.1.2" # These 3 deps are not used by r-a directly, but we list them here to lock in their versions # in our transitive deps to prevent them from pulling in windows-sys 0.45.0 mio = "=0.8.5" -filetime = "=0.2.19" parking_lot_core = "=0.9.6" cfg.workspace = true diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/caps.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/caps.rs index ab06b9681..8c9261ab0 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/caps.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/caps.rs @@ -16,10 +16,12 @@ use lsp_types::{ }; use serde_json::json; -use crate::config::{Config, RustfmtConfig}; -use crate::line_index::PositionEncoding; -use crate::lsp_ext::negotiated_encoding; -use crate::semantic_tokens; +use crate::{ + config::{Config, RustfmtConfig}, + line_index::PositionEncoding, + lsp::semantic_tokens, + lsp_ext::negotiated_encoding, +}; pub fn server_capabilities(config: &Config) -> ServerCapabilities { ServerCapabilities { @@ -218,7 +220,7 @@ fn code_action_capabilities(client_caps: &ClientCapabilities) -> CodeActionProvi } fn more_trigger_character(config: &Config) -> Vec<String> { - let mut res = vec![".".to_string(), ">".to_string(), "{".to_string()]; + let mut res = vec![".".to_string(), ">".to_string(), "{".to_string(), "(".to_string()]; if config.snippet_cap() { res.push("<".to_string()); } 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 f446a7c05..dcb3ca658 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 @@ -15,7 +15,10 @@ use hir_def::{ hir::{ExprId, PatId}, }; use hir_ty::{Interner, Substitution, TyExt, TypeFlags}; -use ide::{Analysis, AnnotationConfig, DiagnosticsConfig, InlayHintsConfig, LineCol, RootDatabase}; +use ide::{ + Analysis, AnnotationConfig, DiagnosticsConfig, InlayFieldsToResolve, InlayHintsConfig, LineCol, + RootDatabase, +}; use ide_db::{ base_db::{ salsa::{self, debug::DebugQueryTable, ParallelDatabase}, @@ -235,9 +238,7 @@ impl flags::AnalysisStats { if let Some(instructions) = total_span.instructions { report_metric("total instructions", instructions, "#instr"); } - if let Some(memory) = total_span.memory { - report_metric("total memory", memory.allocated.megabytes() as u64, "MB"); - } + report_metric("total memory", total_span.memory.allocated.megabytes() as u64, "MB"); if env::var("RA_COUNT").is_ok() { eprintln!("{}", profile::countme::get_all()); @@ -257,7 +258,7 @@ impl flags::AnalysisStats { eprintln!("source files: {total_file_size}, macro files: {total_macro_file_size}"); } - if self.memory_usage && verbosity.is_verbose() { + if verbosity.is_verbose() { print_memory_usage(host, vfs); } @@ -319,9 +320,13 @@ impl flags::AnalysisStats { fn run_mir_lowering(&self, db: &RootDatabase, bodies: &[DefWithBody], verbosity: Verbosity) { let mut sw = self.stop_watch(); - let all = bodies.len() as u64; + let mut all = 0; let mut fail = 0; for &body in bodies { + if matches!(body, DefWithBody::Variant(_)) { + continue; + } + all += 1; let Err(e) = db.mir_body(body.into()) else { continue; }; @@ -784,6 +789,7 @@ impl flags::AnalysisStats { closure_style: hir::ClosureStyle::ImplFn, max_length: Some(25), closing_brace_hints_min_lines: Some(20), + fields_to_resolve: InlayFieldsToResolve::empty(), }, file_id, None, @@ -814,7 +820,7 @@ impl flags::AnalysisStats { } fn stop_watch(&self) -> StopWatch { - StopWatch::start().memory(self.memory_usage) + StopWatch::start() } } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/flags.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/flags.rs index 13b7f039b..419440b6d 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/flags.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/flags.rs @@ -62,8 +62,6 @@ xflags::xflags! { optional --randomize /// Run type inference in parallel. optional --parallel - /// Collect memory usage statistics. - optional --memory-usage /// Print the total length of all source and macro files (whitespace is not counted). optional --source-stats @@ -191,7 +189,6 @@ pub struct AnalysisStats { pub output: Option<OutputFormat>, pub randomize: bool, pub parallel: bool, - pub memory_usage: bool, pub source_stats: bool, pub only: Option<String>, pub with_deps: bool, diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/lsif.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/lsif.rs index 42d180114..d6a45ce06 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/lsif.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/lsif.rs @@ -21,7 +21,7 @@ use vfs::{AbsPathBuf, Vfs}; use crate::{ cli::flags, line_index::{LineEndings, LineIndex, PositionEncoding}, - to_proto, + lsp::to_proto, version::version, }; diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs index 44337f955..8c056fff0 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs @@ -51,7 +51,7 @@ impl flags::Scip { version: scip_types::ProtocolVersion::UnspecifiedProtocolVersion.into(), tool_info: Some(scip_types::ToolInfo { name: "rust-analyzer".to_owned(), - version: "0.1".to_owned(), + version: format!("{}", crate::version::version()), arguments: vec![], special_fields: Default::default(), }) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs index fa20c796e..ea3a21241 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -13,8 +13,9 @@ use cfg::{CfgAtom, CfgDiff}; use flycheck::FlycheckConfig; use ide::{ AssistConfig, CallableSnippets, CompletionConfig, DiagnosticsConfig, ExprFillDefaultMode, - HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayHintsConfig, - JoinLinesConfig, MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, Snippet, SnippetScope, + HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayFieldsToResolve, + InlayHintsConfig, JoinLinesConfig, MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, + Snippet, SnippetScope, }; use ide_db::{ imports::insert_use::{ImportGranularity, InsertUseConfig, PrefixKind}, @@ -150,18 +151,22 @@ config_data! { /// /// Set to `"all"` to pass `--all-features` to Cargo. check_features | checkOnSave_features: Option<CargoFeaturesDef> = "null", + /// List of `cargo check` (or other command specified in `check.command`) diagnostics to ignore. + /// + /// For example for `cargo check`: `dead_code`, `unused_imports`, `unused_variables`,... + check_ignore: FxHashSet<String> = "[]", /// Specifies the working directory for running checks. /// - "workspace": run checks for workspaces in the corresponding workspaces' root directories. // FIXME: Ideally we would support this in some way - /// This falls back to "root" if `#rust-analyzer.cargo.checkOnSave.invocationStrategy#` is set to `once`. + /// This falls back to "root" if `#rust-analyzer.cargo.check.invocationStrategy#` is set to `once`. /// - "root": run checks in the project's root directory. - /// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#` + /// This config only has an effect when `#rust-analyzer.cargo.check.overrideCommand#` /// is set. check_invocationLocation | checkOnSave_invocationLocation: InvocationLocation = "\"workspace\"", - /// Specifies the invocation strategy to use when running the checkOnSave command. + /// Specifies the invocation strategy to use when running the check command. /// If `per_workspace` is set, the command will be executed for each workspace. /// If `once` is set, the command will be executed once. - /// This config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#` + /// This config only has an effect when `#rust-analyzer.cargo.check.overrideCommand#` /// is set. check_invocationStrategy | checkOnSave_invocationStrategy: InvocationStrategy = "\"per_workspace\"", /// Whether to pass `--no-default-features` to Cargo. Defaults to @@ -1098,6 +1103,7 @@ impl Config { remap_prefix: self.data.diagnostics_remapPrefix.clone(), warnings_as_info: self.data.diagnostics_warningsAsInfo.clone(), warnings_as_hint: self.data.diagnostics_warningsAsHint.clone(), + check_ignore: self.data.check_ignore.clone(), } } @@ -1330,6 +1336,18 @@ impl Config { } pub fn inlay_hints(&self) -> InlayHintsConfig { + let client_capability_fields = self + .caps + .text_document + .as_ref() + .and_then(|text| text.inlay_hint.as_ref()) + .and_then(|inlay_hint_caps| inlay_hint_caps.resolve_support.as_ref()) + .map(|inlay_resolve| inlay_resolve.properties.iter()) + .into_iter() + .flatten() + .cloned() + .collect::<FxHashSet<_>>(); + InlayHintsConfig { render_colons: self.data.inlayHints_renderColons, type_hints: self.data.inlayHints_typeHints_enable, @@ -1390,6 +1408,13 @@ impl Config { } else { None }, + fields_to_resolve: InlayFieldsToResolve { + resolve_text_edits: client_capability_fields.contains("textEdits"), + resolve_hint_tooltip: client_capability_fields.contains("tooltip"), + resolve_label_tooltip: client_capability_fields.contains("label.tooltip"), + resolve_label_location: client_capability_fields.contains("label.location"), + resolve_label_command: client_capability_fields.contains("label.command"), + }, } } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics.rs index 33422fd05..71701ef16 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics.rs @@ -6,9 +6,10 @@ use std::mem; use ide::FileId; use ide_db::FxHashMap; use nohash_hasher::{IntMap, IntSet}; +use rustc_hash::FxHashSet; use triomphe::Arc; -use crate::lsp_ext; +use crate::{global_state::GlobalStateSnapshot, lsp, lsp_ext}; pub(crate) type CheckFixes = Arc<IntMap<usize, IntMap<FileId, Vec<Fix>>>>; @@ -17,6 +18,7 @@ pub struct DiagnosticsMapConfig { pub remap_prefix: FxHashMap<String, String>, pub warnings_as_info: Vec<String>, pub warnings_as_hint: Vec<String>, + pub check_ignore: FxHashSet<String>, } #[derive(Debug, Default, Clone)] @@ -120,3 +122,41 @@ fn are_diagnostics_equal(left: &lsp_types::Diagnostic, right: &lsp_types::Diagno && left.range == right.range && left.message == right.message } + +pub(crate) fn fetch_native_diagnostics( + snapshot: GlobalStateSnapshot, + subscriptions: Vec<FileId>, +) -> Vec<(FileId, Vec<lsp_types::Diagnostic>)> { + let _p = profile::span("fetch_native_diagnostics"); + let _ctx = stdx::panic_context::enter("fetch_native_diagnostics".to_owned()); + subscriptions + .into_iter() + .filter_map(|file_id| { + let line_index = snapshot.file_line_index(file_id).ok()?; + let diagnostics = snapshot + .analysis + .diagnostics( + &snapshot.config.diagnostics(), + ide::AssistResolveStrategy::None, + file_id, + ) + .ok()? + .into_iter() + .map(move |d| lsp_types::Diagnostic { + range: lsp::to_proto::range(&line_index, d.range), + severity: Some(lsp::to_proto::diagnostic_severity(d.severity)), + code: Some(lsp_types::NumberOrString::String(d.code.as_str().to_string())), + code_description: Some(lsp_types::CodeDescription { + href: lsp_types::Url::parse(&d.code.url()).unwrap(), + }), + source: Some("rust-analyzer".to_string()), + message: d.message, + related_information: None, + tags: d.unused.then(|| vec![lsp_types::DiagnosticTag::UNNECESSARY]), + data: None, + }) + .collect::<Vec<_>>(); + Some((file_id, diagnostics)) + }) + .collect() +} diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/to_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/to_proto.rs index e1d1130ff..731580557 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/to_proto.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/to_proto.rs @@ -8,8 +8,8 @@ use stdx::format_to; use vfs::{AbsPath, AbsPathBuf}; use crate::{ - global_state::GlobalStateSnapshot, line_index::PositionEncoding, lsp_ext, - to_proto::url_from_abs_path, + global_state::GlobalStateSnapshot, line_index::PositionEncoding, + lsp::to_proto::url_from_abs_path, lsp_ext, }; use super::{DiagnosticsMapConfig, Fix}; @@ -292,6 +292,13 @@ pub(crate) fn map_rust_diagnostic_to_lsp( let mut source = String::from("rustc"); let mut code = rd.code.as_ref().map(|c| c.code.clone()); + + if let Some(code_val) = &code { + if config.check_ignore.contains(code_val) { + return Vec::new(); + } + } + if let Some(code_val) = &code { // See if this is an RFC #2103 scoped lint (e.g. from Clippy) let scoped_code: Vec<&str> = code_val.split("::").collect(); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/dispatch.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/dispatch.rs index 5e5cd9a02..7da431188 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/dispatch.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/dispatch.rs @@ -8,9 +8,9 @@ use stdx::thread::ThreadIntent; use crate::{ global_state::{GlobalState, GlobalStateSnapshot}, + lsp::LspError, main_loop::Task, version::version, - LspError, }; /// A visitor for routing a raw JSON request to an appropriate handler function. diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs index ea8a69751..c09f57252 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs @@ -12,25 +12,27 @@ use ide_db::base_db::{CrateId, FileLoader, ProcMacroPaths, SourceDatabase}; use load_cargo::SourceRootConfig; use lsp_types::{SemanticTokens, Url}; use nohash_hasher::IntMap; -use parking_lot::{Mutex, RwLock}; +use parking_lot::{ + MappedRwLockReadGuard, Mutex, RwLock, RwLockReadGuard, RwLockUpgradableReadGuard, + RwLockWriteGuard, +}; use proc_macro_api::ProcMacroServer; use project_model::{CargoWorkspace, ProjectWorkspace, Target, WorkspaceBuildScripts}; use rustc_hash::{FxHashMap, FxHashSet}; use triomphe::Arc; -use vfs::AnchoredPathBuf; +use vfs::{AnchoredPathBuf, Vfs}; use crate::{ config::{Config, ConfigError}, diagnostics::{CheckFixes, DiagnosticCollection}, - from_proto, line_index::{LineEndings, LineIndex}, + lsp::{from_proto, to_proto::url_from_abs_path}, lsp_ext, main_loop::Task, mem_docs::MemDocs, op_queue::OpQueue, reload, task_pool::TaskPool, - to_proto::url_from_abs_path, }; // Enforces drop order @@ -40,7 +42,7 @@ pub(crate) struct Handle<H, C> { } pub(crate) type ReqHandler = fn(&mut GlobalState, lsp_server::Response); -pub(crate) type ReqQueue = lsp_server::ReqQueue<(String, Instant), ReqHandler>; +type ReqQueue = lsp_server::ReqQueue<(String, Instant), ReqHandler>; /// `GlobalState` is the primary mutable state of the language server /// @@ -49,6 +51,7 @@ pub(crate) type ReqQueue = lsp_server::ReqQueue<(String, Instant), ReqHandler>; /// incremental salsa database. /// /// Note that this struct has more than one impl in various modules! +#[doc(alias = "GlobalMess")] pub(crate) struct GlobalState { sender: Sender<lsp_server::Message>, req_queue: ReqQueue, @@ -66,6 +69,7 @@ pub(crate) struct GlobalState { // status pub(crate) shutdown_requested: bool, + pub(crate) send_hint_refresh_query: bool, pub(crate) last_reported_status: Option<lsp_ext::ServerStatusParams>, // proc macros @@ -177,6 +181,7 @@ impl GlobalState { mem_docs: MemDocs::default(), semantic_tokens_cache: Arc::new(Default::default()), shutdown_requested: false, + send_hint_refresh_query: false, last_reported_status: None, source_root_config: SourceRootConfig::default(), config_errors: Default::default(), @@ -216,12 +221,15 @@ impl GlobalState { let mut file_changes = FxHashMap::default(); let (change, changed_files, workspace_structure_change) = { let mut change = Change::new(); - let (vfs, line_endings_map) = &mut *self.vfs.write(); - let changed_files = vfs.take_changes(); + let mut guard = self.vfs.write(); + let changed_files = guard.0.take_changes(); if changed_files.is_empty() { return false; } + // downgrade to read lock to allow more readers while we are normalizing text + let guard = RwLockWriteGuard::downgrade_to_upgradable(guard); + let vfs: &Vfs = &guard.0; // We need to fix up the changed events a bit. If we have a create or modify for a file // id that is followed by a delete we actually skip observing the file text from the // earlier event, to avoid problems later on. @@ -272,6 +280,7 @@ impl GlobalState { let mut workspace_structure_change = None; // A file was added or deleted let mut has_structure_changes = false; + let mut bytes = vec![]; for file in &changed_files { let vfs_path = &vfs.file_path(file.file_id); if let Some(path) = vfs_path.as_path() { @@ -293,16 +302,28 @@ impl GlobalState { let text = if file.exists() { let bytes = vfs.file_contents(file.file_id).to_vec(); + String::from_utf8(bytes).ok().and_then(|text| { + // FIXME: Consider doing normalization in the `vfs` instead? That allows + // getting rid of some locking let (text, line_endings) = LineEndings::normalize(text); - line_endings_map.insert(file.file_id, line_endings); - Some(Arc::from(text)) + Some((Arc::from(text), line_endings)) }) } else { None }; - change.change_file(file.file_id, text); + // delay `line_endings_map` changes until we are done normalizing the text + // this allows delaying the re-acquisition of the write lock + bytes.push((file.file_id, text)); } + let (vfs, line_endings_map) = &mut *RwLockUpgradableReadGuard::upgrade(guard); + bytes.into_iter().for_each(|(file_id, text)| match text { + None => change.change_file(file_id, None), + Some((text, line_endings)) => { + line_endings_map.insert(file_id, line_endings); + change.change_file(file_id, Some(text)); + } + }); if has_structure_changes { let roots = self.source_root_config.partition(vfs); change.set_roots(roots); @@ -422,12 +443,16 @@ impl Drop for GlobalState { } impl GlobalStateSnapshot { + fn vfs_read(&self) -> MappedRwLockReadGuard<'_, vfs::Vfs> { + RwLockReadGuard::map(self.vfs.read(), |(it, _)| it) + } + pub(crate) fn url_to_file_id(&self, url: &Url) -> anyhow::Result<FileId> { - url_to_file_id(&self.vfs.read().0, url) + url_to_file_id(&self.vfs_read(), url) } pub(crate) fn file_id_to_url(&self, id: FileId) -> Url { - file_id_to_url(&self.vfs.read().0, id) + file_id_to_url(&self.vfs_read(), id) } pub(crate) fn file_line_index(&self, file_id: FileId) -> Cancellable<LineIndex> { @@ -443,7 +468,7 @@ impl GlobalStateSnapshot { } pub(crate) fn anchored_path(&self, path: &AnchoredPathBuf) -> Url { - let mut base = self.vfs.read().0.file_path(path.anchor); + let mut base = self.vfs_read().file_path(path.anchor); base.pop(); let path = base.join(&path.path).unwrap(); let path = path.as_path().unwrap(); @@ -451,7 +476,7 @@ impl GlobalStateSnapshot { } pub(crate) fn file_id_to_file_path(&self, file_id: FileId) -> vfs::VfsPath { - self.vfs.read().0.file_path(file_id) + self.vfs_read().file_path(file_id) } pub(crate) fn cargo_target_for_crate_root( @@ -459,7 +484,7 @@ impl GlobalStateSnapshot { crate_id: CrateId, ) -> Option<(&CargoWorkspace, Target)> { let file_id = self.analysis.crate_root(crate_id).ok()?; - let path = self.vfs.read().0.file_path(file_id); + let path = self.vfs_read().file_path(file_id); let path = path.as_path()?; self.workspaces.iter().find_map(|ws| match ws { ProjectWorkspace::Cargo { cargo, .. } => { @@ -471,7 +496,11 @@ impl GlobalStateSnapshot { } pub(crate) fn vfs_memory_usage(&self) -> usize { - self.vfs.read().0.memory_usage() + self.vfs_read().memory_usage() + } + + pub(crate) fn file_exists(&self, file_id: FileId) -> bool { + self.vfs.read().0.exists(file_id) } } 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 e830e5e9a..f9070d273 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 @@ -13,8 +13,12 @@ use triomphe::Arc; use vfs::{AbsPathBuf, ChangeKind, VfsPath}; use crate::{ - config::Config, from_proto, global_state::GlobalState, lsp_ext::RunFlycheckParams, - lsp_utils::apply_document_changes, mem_docs::DocumentData, reload, + config::Config, + global_state::GlobalState, + lsp::{from_proto, utils::apply_document_changes}, + lsp_ext::RunFlycheckParams, + mem_docs::DocumentData, + reload, }; pub(crate) fn handle_cancel(state: &mut GlobalState, params: CancelParams) -> anyhow::Result<()> { @@ -84,15 +88,16 @@ pub(crate) fn handle_did_change_text_document( } }; - let vfs = &mut state.vfs.write().0; - let file_id = vfs.file_id(&path).unwrap(); let text = apply_document_changes( state.config.position_encoding(), - || std::str::from_utf8(vfs.file_contents(file_id)).unwrap().into(), + || { + let vfs = &state.vfs.read().0; + let file_id = vfs.file_id(&path).unwrap(); + std::str::from_utf8(vfs.file_contents(file_id)).unwrap().into() + }, params.content_changes, ); - - vfs.set_file_contents(path, Some(text.into_bytes())); + state.vfs.write().0.set_file_contents(path, Some(text.into_bytes())); } Ok(()) } @@ -108,6 +113,10 @@ pub(crate) fn handle_did_close_text_document( tracing::error!("orphan DidCloseTextDocument: {}", path); } + if let Some(file_id) = state.vfs.read().0.file_id(&path) { + state.diagnostics.clear_native_for(file_id); + } + state.semantic_tokens_cache.lock().remove(¶ms.text_document.uri); if let Some(path) = path.as_path() { diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs index 5f1f731cf..b8a1a39be 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs @@ -11,8 +11,8 @@ use anyhow::Context; use ide::{ AnnotationConfig, AssistKind, AssistResolveStrategy, Cancellable, FilePosition, FileRange, - HoverAction, HoverGotoTypeData, Query, RangeInfo, ReferenceCategory, Runnable, RunnableKind, - SingleResolve, SourceChange, TextEdit, + HoverAction, HoverGotoTypeData, InlayFieldsToResolve, Query, RangeInfo, ReferenceCategory, + Runnable, RunnableKind, SingleResolve, SourceChange, TextEdit, }; use ide_db::SymbolKind; use lsp_server::ErrorCode; @@ -30,21 +30,23 @@ use serde_json::json; use stdx::{format_to, never}; use syntax::{algo, ast, AstNode, TextRange, TextSize}; use triomphe::Arc; -use vfs::{AbsPath, AbsPathBuf, VfsPath}; +use vfs::{AbsPath, AbsPathBuf, FileId, VfsPath}; use crate::{ cargo_target_spec::CargoTargetSpec, config::{Config, RustfmtConfig, WorkspaceSymbolConfig}, diff::diff, - from_proto, global_state::{GlobalState, GlobalStateSnapshot}, line_index::LineEndings, + lsp::{ + from_proto, to_proto, + utils::{all_edits_are_disjoint, invalid_params_error}, + LspError, + }, lsp_ext::{ self, CrateInfoResult, ExternalDocsPair, ExternalDocsResponse, FetchDependencyListParams, FetchDependencyListResult, PositionOrRange, ViewCrateGraphParams, WorkspaceSymbolParams, }, - lsp_utils::{all_edits_are_disjoint, invalid_params_error}, - to_proto, LspError, }; pub(crate) fn handle_workspace_reload(state: &mut GlobalState, _: ()) -> anyhow::Result<()> { @@ -354,7 +356,7 @@ pub(crate) fn handle_on_type_formatting( // This should be a single-file edit let (_, (text_edit, snippet_edit)) = edit.source_file_edits.into_iter().next().unwrap(); - stdx::never!(snippet_edit.is_none(), "on type formatting shouldn't use structured snippets"); + stdx::always!(snippet_edit.is_none(), "on type formatting shouldn't use structured snippets"); let change = to_proto::snippet_text_edit_vec(&line_index, edit.is_snippet, text_edit); Ok(Some(change)) @@ -972,7 +974,7 @@ pub(crate) fn handle_hover( PositionOrRange::Range(range) => range, }; - let file_range = from_proto::file_range(&snap, params.text_document, range)?; + let file_range = from_proto::file_range(&snap, ¶ms.text_document, range)?; let info = match snap.analysis.hover(&snap.config.hover(), file_range)? { None => return Ok(None), Some(info) => info, @@ -1128,7 +1130,7 @@ pub(crate) fn handle_code_action( let line_index = snap.file_line_index(from_proto::file_id(&snap, ¶ms.text_document.uri)?)?; - let frange = from_proto::file_range(&snap, params.text_document.clone(), params.range)?; + let frange = from_proto::file_range(&snap, ¶ms.text_document, params.range)?; let mut assists_config = snap.config.assist(); assists_config.allowed = params @@ -1381,7 +1383,7 @@ pub(crate) fn handle_ssr( let selections = params .selections .iter() - .map(|range| from_proto::file_range(&snap, params.position.text_document.clone(), *range)) + .map(|range| from_proto::file_range(&snap, ¶ms.position.text_document, *range)) .collect::<Result<Vec<_>, _>>()?; let position = from_proto::file_position(&snap, params.position)?; let source_change = snap.analysis.structural_search_replace( @@ -1401,7 +1403,7 @@ pub(crate) fn handle_inlay_hints( let document_uri = ¶ms.text_document.uri; let FileRange { file_id, range } = from_proto::file_range( &snap, - TextDocumentIdentifier::new(document_uri.to_owned()), + &TextDocumentIdentifier::new(document_uri.to_owned()), params.range, )?; let line_index = snap.file_line_index(file_id)?; @@ -1410,17 +1412,73 @@ pub(crate) fn handle_inlay_hints( snap.analysis .inlay_hints(&inlay_hints_config, file_id, Some(range))? .into_iter() - .map(|it| to_proto::inlay_hint(&snap, &line_index, it)) + .map(|it| { + to_proto::inlay_hint( + &snap, + &inlay_hints_config.fields_to_resolve, + &line_index, + file_id, + it, + ) + }) .collect::<Cancellable<Vec<_>>>()?, )) } pub(crate) fn handle_inlay_hints_resolve( - _snap: GlobalStateSnapshot, - hint: InlayHint, + snap: GlobalStateSnapshot, + mut original_hint: InlayHint, ) -> anyhow::Result<InlayHint> { let _p = profile::span("handle_inlay_hints_resolve"); - Ok(hint) + + let data = match original_hint.data.take() { + Some(it) => it, + None => return Ok(original_hint), + }; + + let resolve_data: lsp_ext::InlayHintResolveData = serde_json::from_value(data)?; + let file_id = FileId(resolve_data.file_id); + anyhow::ensure!(snap.file_exists(file_id), "Invalid LSP resolve data"); + + let line_index = snap.file_line_index(file_id)?; + let range = from_proto::text_range( + &line_index, + lsp_types::Range { start: original_hint.position, end: original_hint.position }, + )?; + let range_start = range.start(); + let range_end = range.end(); + let large_range = TextRange::new( + range_start.checked_sub(1.into()).unwrap_or(range_start), + range_end.checked_add(1.into()).unwrap_or(range_end), + ); + let mut forced_resolve_inlay_hints_config = snap.config.inlay_hints(); + forced_resolve_inlay_hints_config.fields_to_resolve = InlayFieldsToResolve::empty(); + let resolve_hints = snap.analysis.inlay_hints( + &forced_resolve_inlay_hints_config, + file_id, + Some(large_range), + )?; + + let mut resolved_hints = resolve_hints + .into_iter() + .filter_map(|it| { + to_proto::inlay_hint( + &snap, + &forced_resolve_inlay_hints_config.fields_to_resolve, + &line_index, + file_id, + it, + ) + .ok() + }) + .filter(|hint| hint.position == original_hint.position) + .filter(|hint| hint.kind == original_hint.kind); + if let Some(resolved_hint) = resolved_hints.next() { + if resolved_hints.next().is_none() { + return Ok(resolved_hint); + } + } + Ok(original_hint) } pub(crate) fn handle_call_hierarchy_prepare( @@ -1453,7 +1511,7 @@ pub(crate) fn handle_call_hierarchy_incoming( let item = params.item; let doc = TextDocumentIdentifier::new(item.uri); - let frange = from_proto::file_range(&snap, doc, item.selection_range)?; + let frange = from_proto::file_range(&snap, &doc, item.selection_range)?; let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; let call_items = match snap.analysis.incoming_calls(fpos)? { @@ -1488,7 +1546,7 @@ pub(crate) fn handle_call_hierarchy_outgoing( let item = params.item; let doc = TextDocumentIdentifier::new(item.uri); - let frange = from_proto::file_range(&snap, doc, item.selection_range)?; + let frange = from_proto::file_range(&snap, &doc, item.selection_range)?; let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; let call_items = match snap.analysis.outgoing_calls(fpos)? { @@ -1569,18 +1627,21 @@ pub(crate) fn handle_semantic_tokens_full_delta( snap.config.highlighting_non_standard_tokens(), ); - let mut cache = snap.semantic_tokens_cache.lock(); - let cached_tokens = cache.entry(params.text_document.uri).or_default(); + let cached_tokens = snap.semantic_tokens_cache.lock().remove(¶ms.text_document.uri); - if let Some(prev_id) = &cached_tokens.result_id { + if let Some(cached_tokens @ lsp_types::SemanticTokens { result_id: Some(prev_id), .. }) = + &cached_tokens + { if *prev_id == params.previous_result_id { let delta = to_proto::semantic_token_delta(cached_tokens, &semantic_tokens); - *cached_tokens = semantic_tokens; + snap.semantic_tokens_cache.lock().insert(params.text_document.uri, semantic_tokens); return Ok(Some(delta.into())); } } - *cached_tokens = semantic_tokens.clone(); + // Clone first to keep the lock short + let semantic_tokens_clone = semantic_tokens.clone(); + snap.semantic_tokens_cache.lock().insert(params.text_document.uri, semantic_tokens_clone); Ok(Some(semantic_tokens.into())) } @@ -1591,7 +1652,7 @@ pub(crate) fn handle_semantic_tokens_range( ) -> anyhow::Result<Option<SemanticTokensRangeResult>> { let _p = profile::span("handle_semantic_tokens_range"); - let frange = from_proto::file_range(&snap, params.text_document, params.range)?; + let frange = from_proto::file_range(&snap, ¶ms.text_document, params.range)?; let text = snap.analysis.file_text(frange.file_id)?; let line_index = snap.file_line_index(frange.file_id)?; @@ -1674,7 +1735,7 @@ pub(crate) fn handle_move_item( ) -> anyhow::Result<Vec<lsp_ext::SnippetTextEdit>> { let _p = profile::span("handle_move_item"); let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; - let range = from_proto::file_range(&snap, params.text_document, params.range)?; + let range = from_proto::file_range(&snap, ¶ms.text_document, params.range)?; let direction = match params.direction { lsp_ext::MoveItemDirection::Up => ide::Direction::Up, @@ -1879,12 +1940,15 @@ fn run_rustfmt( // Determine the edition of the crate the file belongs to (if there's multiple, we pick the // highest edition). - let editions = snap + let Ok(editions) = snap .analysis .relevant_crates_for(file_id)? .into_iter() .map(|crate_id| snap.analysis.crate_edition(crate_id)) - .collect::<Result<Vec<_>, _>>()?; + .collect::<Result<Vec<_>, _>>() + else { + return Ok(None); + }; let edition = editions.iter().copied().max(); let line_index = snap.file_line_index(file_id)?; @@ -1894,23 +1958,7 @@ fn run_rustfmt( let mut cmd = process::Command::new(toolchain::rustfmt()); cmd.envs(snap.config.extra_env()); cmd.args(extra_args); - // try to chdir to the file so we can respect `rustfmt.toml` - // FIXME: use `rustfmt --config-path` once - // https://github.com/rust-lang/rustfmt/issues/4660 gets fixed - match text_document.uri.to_file_path() { - Ok(mut path) => { - // pop off file name - if path.pop() && path.is_dir() { - cmd.current_dir(path); - } - } - Err(_) => { - tracing::error!( - "Unable to get file path for {}, rustfmt.toml might be ignored", - text_document.uri - ); - } - } + if let Some(edition) = edition { cmd.arg("--edition"); cmd.arg(edition.to_string()); @@ -1929,7 +1977,7 @@ fn run_rustfmt( .into()); } - let frange = from_proto::file_range(snap, text_document, range)?; + let frange = from_proto::file_range(snap, &text_document, range)?; let start_line = line_index.index.line_col(frange.range.start()).line; let end_line = line_index.index.line_col(frange.range.end()).line; @@ -1948,12 +1996,31 @@ fn run_rustfmt( } RustfmtConfig::CustomCommand { command, args } => { let mut cmd = process::Command::new(command); + cmd.envs(snap.config.extra_env()); cmd.args(args); cmd } }; + // try to chdir to the file so we can respect `rustfmt.toml` + // FIXME: use `rustfmt --config-path` once + // https://github.com/rust-lang/rustfmt/issues/4660 gets fixed + match text_document.uri.to_file_path() { + Ok(mut path) => { + // pop off file name + if path.pop() && path.is_dir() { + command.current_dir(path); + } + } + Err(_) => { + tracing::error!( + text_document = ?text_document.uri, + "Unable to get path, rustfmt.toml might be ignored" + ); + } + } + let mut rustfmt = command .stdin(Stdio::piped()) .stdout(Stdio::piped()) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs index 57e26c241..6c62577f6 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs @@ -23,18 +23,13 @@ mod cargo_target_spec; mod diagnostics; mod diff; mod dispatch; -mod from_proto; mod global_state; mod line_index; -mod lsp_utils; mod main_loop; -mod markdown; mod mem_docs; mod op_queue; mod reload; -mod semantic_tokens; mod task_pool; -mod to_proto; mod version; mod handlers { @@ -43,13 +38,12 @@ mod handlers { } pub mod config; -pub mod lsp_ext; +pub mod lsp; +use self::lsp::ext as lsp_ext; #[cfg(test)] mod integrated_benchmarks; -use std::fmt; - use serde::de::DeserializeOwned; pub use crate::{caps::server_capabilities, main_loop::main_loop, version::version}; @@ -61,23 +55,3 @@ pub fn from_json<T: DeserializeOwned>( serde_json::from_value(json.clone()) .map_err(|e| anyhow::format_err!("Failed to deserialize {what}: {e}; {json}")) } - -#[derive(Debug)] -struct LspError { - code: i32, - message: String, -} - -impl LspError { - fn new(code: i32, message: String) -> LspError { - LspError { code, message } - } -} - -impl fmt::Display for LspError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Language Server request failed with {}. ({})", self.code, self.message) - } -} - -impl std::error::Error for LspError {} diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp.rs new file mode 100644 index 000000000..ac7e1a95e --- /dev/null +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp.rs @@ -0,0 +1,29 @@ +//! Custom LSP definitions and protocol conversions. + +use core::fmt; + +pub(crate) mod utils; +pub(crate) mod semantic_tokens; +pub mod ext; +pub(crate) mod from_proto; +pub(crate) mod to_proto; + +#[derive(Debug)] +pub(crate) struct LspError { + pub(crate) code: i32, + pub(crate) message: String, +} + +impl LspError { + pub(crate) fn new(code: i32, message: String) -> LspError { + LspError { code, message } + } +} + +impl fmt::Display for LspError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Language Server request failed with {}. ({})", self.code, self.message) + } +} + +impl std::error::Error for LspError {} diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp_ext.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs index d0989b323..ad5689916 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp_ext.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs @@ -682,7 +682,9 @@ pub struct CompletionResolveData { } #[derive(Debug, Serialize, Deserialize)] -pub struct InlayHintResolveData {} +pub struct InlayHintResolveData { + pub file_id: u32, +} #[derive(Debug, Serialize, Deserialize)] pub struct CompletionImport { diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/from_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/from_proto.rs index c247e1bb2..69d6aba94 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/from_proto.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/from_proto.rs @@ -12,8 +12,8 @@ use crate::{ from_json, global_state::GlobalStateSnapshot, line_index::{LineIndex, PositionEncoding}, + lsp::utils::invalid_params_error, lsp_ext, - lsp_utils::invalid_params_error, }; pub(crate) fn abs_path(url: &lsp_types::Url) -> anyhow::Result<AbsPathBuf> { @@ -72,7 +72,7 @@ pub(crate) fn file_position( pub(crate) fn file_range( snap: &GlobalStateSnapshot, - text_document_identifier: lsp_types::TextDocumentIdentifier, + text_document_identifier: &lsp_types::TextDocumentIdentifier, range: lsp_types::Range, ) -> anyhow::Result<FileRange> { file_range_uri(snap, &text_document_identifier.uri, range) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/semantic_tokens.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/semantic_tokens.rs index 1fe02fc7e..1fe02fc7e 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/semantic_tokens.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/semantic_tokens.rs diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/to_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs index 7b32180e3..23074493a 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/to_proto.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs @@ -8,11 +8,12 @@ use std::{ use ide::{ Annotation, AnnotationKind, Assist, AssistKind, Cancellable, CompletionItem, CompletionItemKind, CompletionRelevance, Documentation, FileId, FileRange, FileSystemEdit, - Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel, InlayHint, - InlayHintLabel, InlayHintLabelPart, InlayKind, Markup, NavigationTarget, ReferenceCategory, - RenameError, Runnable, Severity, SignatureHelp, SnippetEdit, SourceChange, StructureNodeKind, - SymbolKind, TextEdit, TextRange, TextSize, + Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel, + InlayFieldsToResolve, InlayHint, InlayHintLabel, InlayHintLabelPart, InlayKind, Markup, + NavigationTarget, ReferenceCategory, RenameError, Runnable, Severity, SignatureHelp, + SnippetEdit, SourceChange, StructureNodeKind, SymbolKind, TextEdit, TextRange, TextSize, }; +use ide_db::rust_doc::format_docs; use itertools::Itertools; use serde_json::to_value; use vfs::AbsPath; @@ -22,9 +23,12 @@ use crate::{ config::{CallInfoConfig, Config}, global_state::GlobalStateSnapshot, line_index::{LineEndings, LineIndex, PositionEncoding}, + lsp::{ + semantic_tokens::{self, standard_fallback_type}, + utils::invalid_params_error, + LspError, + }, lsp_ext::{self, SnippetTextEdit}, - lsp_utils::invalid_params_error, - semantic_tokens::{self, standard_fallback_type}, }; pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::Position { @@ -102,7 +106,7 @@ pub(crate) fn diagnostic_severity(severity: Severity) -> lsp_types::DiagnosticSe } pub(crate) fn documentation(documentation: Documentation) -> lsp_types::Documentation { - let value = crate::markdown::format_docs(documentation.as_str()); + let value = format_docs(&documentation); let markup_content = lsp_types::MarkupContent { kind: lsp_types::MarkupKind::Markdown, value }; lsp_types::Documentation::MarkupContent(markup_content) } @@ -413,7 +417,7 @@ pub(crate) fn signature_help( let documentation = call_info.doc.filter(|_| config.docs).map(|doc| { lsp_types::Documentation::MarkupContent(lsp_types::MarkupContent { kind: lsp_types::MarkupKind::Markdown, - value: crate::markdown::format_docs(&doc), + value: format_docs(&doc), }) }); @@ -434,10 +438,25 @@ pub(crate) fn signature_help( pub(crate) fn inlay_hint( snap: &GlobalStateSnapshot, + fields_to_resolve: &InlayFieldsToResolve, line_index: &LineIndex, + file_id: FileId, inlay_hint: InlayHint, ) -> Cancellable<lsp_types::InlayHint> { - let (label, tooltip) = inlay_hint_label(snap, inlay_hint.label)?; + let needs_resolve = inlay_hint.needs_resolve; + let (label, tooltip, mut something_to_resolve) = + inlay_hint_label(snap, fields_to_resolve, needs_resolve, inlay_hint.label)?; + let text_edits = if needs_resolve && fields_to_resolve.resolve_text_edits { + something_to_resolve |= inlay_hint.text_edit.is_some(); + None + } else { + inlay_hint.text_edit.map(|it| text_edit_vec(line_index, it)) + }; + let data = if needs_resolve && something_to_resolve { + Some(to_value(lsp_ext::InlayHintResolveData { file_id: file_id.0 }).unwrap()) + } else { + None + }; Ok(lsp_types::InlayHint { position: match inlay_hint.position { @@ -451,8 +470,8 @@ pub(crate) fn inlay_hint( InlayKind::Type | InlayKind::Chaining => Some(lsp_types::InlayHintKind::TYPE), _ => None, }, - text_edits: inlay_hint.text_edit.map(|it| text_edit_vec(line_index, it)), - data: None, + text_edits, + data, tooltip, label, }) @@ -460,13 +479,18 @@ pub(crate) fn inlay_hint( fn inlay_hint_label( snap: &GlobalStateSnapshot, + fields_to_resolve: &InlayFieldsToResolve, + needs_resolve: bool, mut label: InlayHintLabel, -) -> Cancellable<(lsp_types::InlayHintLabel, Option<lsp_types::InlayHintTooltip>)> { - let res = match &*label.parts { +) -> Cancellable<(lsp_types::InlayHintLabel, Option<lsp_types::InlayHintTooltip>, bool)> { + let mut something_to_resolve = false; + let (label, tooltip) = match &*label.parts { [InlayHintLabelPart { linked_location: None, .. }] => { let InlayHintLabelPart { text, tooltip, .. } = label.parts.pop().unwrap(); - ( - lsp_types::InlayHintLabel::String(text), + let hint_tooltip = if needs_resolve && fields_to_resolve.resolve_hint_tooltip { + something_to_resolve |= tooltip.is_some(); + None + } else { match tooltip { Some(ide::InlayTooltip::String(s)) => { Some(lsp_types::InlayHintTooltip::String(s)) @@ -478,41 +502,52 @@ fn inlay_hint_label( })) } None => None, - }, - ) + } + }; + (lsp_types::InlayHintLabel::String(text), hint_tooltip) } _ => { let parts = label .parts .into_iter() .map(|part| { - part.linked_location.map(|range| location(snap, range)).transpose().map( - |location| lsp_types::InlayHintLabelPart { - value: part.text, - tooltip: match part.tooltip { - Some(ide::InlayTooltip::String(s)) => { - Some(lsp_types::InlayHintLabelPartTooltip::String(s)) - } - Some(ide::InlayTooltip::Markdown(s)) => { - Some(lsp_types::InlayHintLabelPartTooltip::MarkupContent( - lsp_types::MarkupContent { - kind: lsp_types::MarkupKind::Markdown, - value: s, - }, - )) - } - None => None, - }, - location, - command: None, - }, - ) + let tooltip = if needs_resolve && fields_to_resolve.resolve_label_tooltip { + something_to_resolve |= part.tooltip.is_some(); + None + } else { + match part.tooltip { + Some(ide::InlayTooltip::String(s)) => { + Some(lsp_types::InlayHintLabelPartTooltip::String(s)) + } + Some(ide::InlayTooltip::Markdown(s)) => { + Some(lsp_types::InlayHintLabelPartTooltip::MarkupContent( + lsp_types::MarkupContent { + kind: lsp_types::MarkupKind::Markdown, + value: s, + }, + )) + } + None => None, + } + }; + let location = if needs_resolve && fields_to_resolve.resolve_label_location { + something_to_resolve |= part.linked_location.is_some(); + None + } else { + part.linked_location.map(|range| location(snap, range)).transpose()? + }; + Ok(lsp_types::InlayHintLabelPart { + value: part.text, + tooltip, + location, + command: None, + }) }) .collect::<Cancellable<_>>()?; (lsp_types::InlayHintLabel::LabelParts(parts), None) } }; - Ok(res) + Ok((label, tooltip, something_to_resolve)) } static TOKEN_RESULT_COUNTER: AtomicU32 = AtomicU32::new(1); @@ -1323,17 +1358,18 @@ pub(crate) fn code_lens( }) } } - AnnotationKind::HasImpls { pos: file_range, data } => { + AnnotationKind::HasImpls { pos, data } => { if !client_commands_config.show_reference { return Ok(()); } - let line_index = snap.file_line_index(file_range.file_id)?; + let line_index = snap.file_line_index(pos.file_id)?; let annotation_range = range(&line_index, annotation.range); - let url = url(snap, file_range.file_id); + let url = url(snap, pos.file_id); + let pos = position(&line_index, pos.offset); let id = lsp_types::TextDocumentIdentifier { uri: url.clone() }; - let doc_pos = lsp_types::TextDocumentPositionParams::new(id, annotation_range.start); + let doc_pos = lsp_types::TextDocumentPositionParams::new(id, pos); let goto_params = lsp_types::request::GotoImplementationParams { text_document_position_params: doc_pos, @@ -1356,7 +1392,7 @@ pub(crate) fn code_lens( command::show_references( implementation_title(locations.len()), &url, - annotation_range.start, + pos, locations, ) }); @@ -1376,28 +1412,24 @@ pub(crate) fn code_lens( })(), }) } - AnnotationKind::HasReferences { pos: file_range, data } => { + AnnotationKind::HasReferences { pos, data } => { if !client_commands_config.show_reference { return Ok(()); } - let line_index = snap.file_line_index(file_range.file_id)?; + let line_index = snap.file_line_index(pos.file_id)?; let annotation_range = range(&line_index, annotation.range); - let url = url(snap, file_range.file_id); + let url = url(snap, pos.file_id); + let pos = position(&line_index, pos.offset); let id = lsp_types::TextDocumentIdentifier { uri: url.clone() }; - let doc_pos = lsp_types::TextDocumentPositionParams::new(id, annotation_range.start); + let doc_pos = lsp_types::TextDocumentPositionParams::new(id, pos); let command = data.map(|ranges| { let locations: Vec<lsp_types::Location> = ranges.into_iter().filter_map(|range| location(snap, range).ok()).collect(); - command::show_references( - reference_title(locations.len()), - &url, - annotation_range.start, - locations, - ) + command::show_references(reference_title(locations.len()), &url, pos, locations) }); acc.push(lsp_types::CodeLens { @@ -1425,8 +1457,8 @@ pub(crate) mod command { use crate::{ global_state::GlobalStateSnapshot, + lsp::to_proto::{location, location_link}, lsp_ext, - to_proto::{location, location_link}, }; pub(crate) fn show_references( @@ -1528,11 +1560,11 @@ pub(crate) fn markup_content( ide::HoverDocFormat::Markdown => lsp_types::MarkupKind::Markdown, ide::HoverDocFormat::PlainText => lsp_types::MarkupKind::PlainText, }; - let value = crate::markdown::format_docs(markup.as_str()); + let value = format_docs(&Documentation::new(markup.into())); lsp_types::MarkupContent { kind, value } } -pub(crate) fn rename_error(err: RenameError) -> crate::LspError { +pub(crate) fn rename_error(err: RenameError) -> LspError { // This is wrong, but we don't have a better alternative I suppose? // https://github.com/microsoft/language-server-protocol/issues/1341 invalid_params_error(err.to_string()) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp_utils.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/utils.rs index 74e79e8e6..b388b3175 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp_utils.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/utils.rs @@ -6,10 +6,10 @@ use lsp_types::request::Request; use triomphe::Arc; use crate::{ - from_proto, global_state::GlobalState, line_index::{LineEndings, LineIndex, PositionEncoding}, - lsp_ext, LspError, + lsp::{from_proto, LspError}, + lsp_ext, }; pub(crate) fn invalid_params_error(message: String) -> LspError { diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs index 74036710f..cdf41c955 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs @@ -17,11 +17,14 @@ use vfs::FileId; use crate::{ config::Config, + diagnostics::fetch_native_diagnostics, dispatch::{NotificationDispatcher, RequestDispatcher}, - from_proto, global_state::{file_id_to_url, url_to_file_id, GlobalState}, + lsp::{ + from_proto, + utils::{notification_is, Progress}, + }, lsp_ext, - lsp_utils::{notification_is, Progress}, reload::{BuildDataProgress, ProcMacroProgress, ProjectWorkspaceProgress}, }; @@ -317,8 +320,11 @@ impl GlobalState { } // Refresh inlay hints if the client supports it. - if self.config.inlay_hints_refresh() { + if (self.send_hint_refresh_query || self.proc_macro_changed) + && self.config.inlay_hints_refresh() + { self.send_request::<lsp_types::request::InlayHintRefreshRequest>((), |_, _| ()); + self.send_hint_refresh_query = false; } } @@ -420,6 +426,32 @@ impl GlobalState { }); } + fn update_diagnostics(&mut self) { + let db = self.analysis_host.raw_database(); + let subscriptions = self + .mem_docs + .iter() + .map(|path| self.vfs.read().0.file_id(path).unwrap()) + .filter(|&file_id| { + let source_root = db.file_source_root(file_id); + // Only publish diagnostics for files in the workspace, not from crates.io deps + // or the sysroot. + // While theoretically these should never have errors, we have quite a few false + // positives particularly in the stdlib, and those diagnostics would stay around + // forever if we emitted them here. + !db.source_root(source_root).is_library + }) + .collect::<Vec<_>>(); + tracing::trace!("updating notifications for {:?}", subscriptions); + + // Diagnostics are triggered by the user typing + // so we run them on a latency sensitive thread. + self.task_pool.handle.spawn(ThreadIntent::LatencySensitive, { + let snapshot = self.snapshot(); + move || Task::Diagnostics(fetch_native_diagnostics(snapshot, subscriptions)) + }); + } + fn update_status_or_notify(&mut self) { let status = self.current_status(); if self.last_reported_status.as_ref() != Some(&status) { @@ -509,6 +541,7 @@ impl GlobalState { } self.switch_workspaces("fetched build data".to_string()); + self.send_hint_refresh_query = true; (Some(Progress::End), None) } @@ -525,7 +558,7 @@ impl GlobalState { ProcMacroProgress::End(proc_macro_load_result) => { self.fetch_proc_macros_queue.op_completed(true); self.set_proc_macros(proc_macro_load_result); - + self.send_hint_refresh_query = true; (Some(Progress::End), None) } }; @@ -670,6 +703,7 @@ impl GlobalState { } use crate::handlers::request as handlers; + use lsp_types::request as lsp_request; dispatcher // Request handlers that must run on the main thread @@ -682,30 +716,30 @@ impl GlobalState { // are run on the main thread to reduce latency: .on_sync::<lsp_ext::JoinLines>(handlers::handle_join_lines) .on_sync::<lsp_ext::OnEnter>(handlers::handle_on_enter) - .on_sync::<lsp_types::request::SelectionRangeRequest>(handlers::handle_selection_range) + .on_sync::<lsp_request::SelectionRangeRequest>(handlers::handle_selection_range) .on_sync::<lsp_ext::MatchingBrace>(handlers::handle_matching_brace) .on_sync::<lsp_ext::OnTypeFormatting>(handlers::handle_on_type_formatting) // Formatting should be done immediately as the editor might wait on it, but we can't // put it on the main thread as we do not want the main thread to block on rustfmt. // So we have an extra thread just for formatting requests to make sure it gets handled // as fast as possible. - .on_fmt_thread::<lsp_types::request::Formatting>(handlers::handle_formatting) - .on_fmt_thread::<lsp_types::request::RangeFormatting>(handlers::handle_range_formatting) + .on_fmt_thread::<lsp_request::Formatting>(handlers::handle_formatting) + .on_fmt_thread::<lsp_request::RangeFormatting>(handlers::handle_range_formatting) // We can’t run latency-sensitive request handlers which do semantic // analysis on the main thread because that would block other // requests. Instead, we run these request handlers on higher priority // threads in the threadpool. - .on_latency_sensitive::<lsp_types::request::Completion>(handlers::handle_completion) - .on_latency_sensitive::<lsp_types::request::ResolveCompletionItem>( + .on_latency_sensitive::<lsp_request::Completion>(handlers::handle_completion) + .on_latency_sensitive::<lsp_request::ResolveCompletionItem>( handlers::handle_completion_resolve, ) - .on_latency_sensitive::<lsp_types::request::SemanticTokensFullRequest>( + .on_latency_sensitive::<lsp_request::SemanticTokensFullRequest>( handlers::handle_semantic_tokens_full, ) - .on_latency_sensitive::<lsp_types::request::SemanticTokensFullDeltaRequest>( + .on_latency_sensitive::<lsp_request::SemanticTokensFullDeltaRequest>( handlers::handle_semantic_tokens_full_delta, ) - .on_latency_sensitive::<lsp_types::request::SemanticTokensRangeRequest>( + .on_latency_sensitive::<lsp_request::SemanticTokensRangeRequest>( handlers::handle_semantic_tokens_range, ) // All other request handlers @@ -729,29 +763,25 @@ impl GlobalState { .on::<lsp_ext::OpenCargoToml>(handlers::handle_open_cargo_toml) .on::<lsp_ext::MoveItem>(handlers::handle_move_item) .on::<lsp_ext::WorkspaceSymbol>(handlers::handle_workspace_symbol) - .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol) - .on::<lsp_types::request::GotoDefinition>(handlers::handle_goto_definition) - .on::<lsp_types::request::GotoDeclaration>(handlers::handle_goto_declaration) - .on::<lsp_types::request::GotoImplementation>(handlers::handle_goto_implementation) - .on::<lsp_types::request::GotoTypeDefinition>(handlers::handle_goto_type_definition) - .on_no_retry::<lsp_types::request::InlayHintRequest>(handlers::handle_inlay_hints) - .on::<lsp_types::request::InlayHintResolveRequest>(handlers::handle_inlay_hints_resolve) - .on::<lsp_types::request::CodeLensRequest>(handlers::handle_code_lens) - .on::<lsp_types::request::CodeLensResolve>(handlers::handle_code_lens_resolve) - .on::<lsp_types::request::FoldingRangeRequest>(handlers::handle_folding_range) - .on::<lsp_types::request::SignatureHelpRequest>(handlers::handle_signature_help) - .on::<lsp_types::request::PrepareRenameRequest>(handlers::handle_prepare_rename) - .on::<lsp_types::request::Rename>(handlers::handle_rename) - .on::<lsp_types::request::References>(handlers::handle_references) - .on::<lsp_types::request::DocumentHighlightRequest>(handlers::handle_document_highlight) - .on::<lsp_types::request::CallHierarchyPrepare>(handlers::handle_call_hierarchy_prepare) - .on::<lsp_types::request::CallHierarchyIncomingCalls>( - handlers::handle_call_hierarchy_incoming, - ) - .on::<lsp_types::request::CallHierarchyOutgoingCalls>( - handlers::handle_call_hierarchy_outgoing, - ) - .on::<lsp_types::request::WillRenameFiles>(handlers::handle_will_rename_files) + .on::<lsp_request::DocumentSymbolRequest>(handlers::handle_document_symbol) + .on::<lsp_request::GotoDefinition>(handlers::handle_goto_definition) + .on::<lsp_request::GotoDeclaration>(handlers::handle_goto_declaration) + .on::<lsp_request::GotoImplementation>(handlers::handle_goto_implementation) + .on::<lsp_request::GotoTypeDefinition>(handlers::handle_goto_type_definition) + .on_no_retry::<lsp_request::InlayHintRequest>(handlers::handle_inlay_hints) + .on::<lsp_request::InlayHintResolveRequest>(handlers::handle_inlay_hints_resolve) + .on::<lsp_request::CodeLensRequest>(handlers::handle_code_lens) + .on::<lsp_request::CodeLensResolve>(handlers::handle_code_lens_resolve) + .on::<lsp_request::FoldingRangeRequest>(handlers::handle_folding_range) + .on::<lsp_request::SignatureHelpRequest>(handlers::handle_signature_help) + .on::<lsp_request::PrepareRenameRequest>(handlers::handle_prepare_rename) + .on::<lsp_request::Rename>(handlers::handle_rename) + .on::<lsp_request::References>(handlers::handle_references) + .on::<lsp_request::DocumentHighlightRequest>(handlers::handle_document_highlight) + .on::<lsp_request::CallHierarchyPrepare>(handlers::handle_call_hierarchy_prepare) + .on::<lsp_request::CallHierarchyIncomingCalls>(handlers::handle_call_hierarchy_incoming) + .on::<lsp_request::CallHierarchyOutgoingCalls>(handlers::handle_call_hierarchy_outgoing) + .on::<lsp_request::WillRenameFiles>(handlers::handle_will_rename_files) .on::<lsp_ext::Ssr>(handlers::handle_ssr) .on::<lsp_ext::ViewRecursiveMemoryLayout>(handlers::handle_view_recursive_memory_layout) .finish(); @@ -788,77 +818,4 @@ impl GlobalState { .finish(); Ok(()) } - - fn update_diagnostics(&mut self) { - let db = self.analysis_host.raw_database(); - let subscriptions = self - .mem_docs - .iter() - .map(|path| self.vfs.read().0.file_id(path).unwrap()) - .filter(|&file_id| { - let source_root = db.file_source_root(file_id); - // Only publish diagnostics for files in the workspace, not from crates.io deps - // or the sysroot. - // While theoretically these should never have errors, we have quite a few false - // positives particularly in the stdlib, and those diagnostics would stay around - // forever if we emitted them here. - !db.source_root(source_root).is_library - }) - .collect::<Vec<_>>(); - - tracing::trace!("updating notifications for {:?}", subscriptions); - - let snapshot = self.snapshot(); - - // Diagnostics are triggered by the user typing - // so we run them on a latency sensitive thread. - self.task_pool.handle.spawn(ThreadIntent::LatencySensitive, move || { - let _p = profile::span("publish_diagnostics"); - let _ctx = stdx::panic_context::enter("publish_diagnostics".to_owned()); - let diagnostics = subscriptions - .into_iter() - .filter_map(|file_id| { - let line_index = snapshot.file_line_index(file_id).ok()?; - Some(( - file_id, - line_index, - snapshot - .analysis - .diagnostics( - &snapshot.config.diagnostics(), - ide::AssistResolveStrategy::None, - file_id, - ) - .ok()?, - )) - }) - .map(|(file_id, line_index, it)| { - ( - file_id, - it.into_iter() - .map(move |d| lsp_types::Diagnostic { - range: crate::to_proto::range(&line_index, d.range), - severity: Some(crate::to_proto::diagnostic_severity(d.severity)), - code: Some(lsp_types::NumberOrString::String( - d.code.as_str().to_string(), - )), - code_description: Some(lsp_types::CodeDescription { - href: lsp_types::Url::parse(&d.code.url()).unwrap(), - }), - source: Some("rust-analyzer".to_string()), - message: d.message, - related_information: None, - tags: if d.unused { - Some(vec![lsp_types::DiagnosticTag::UNNECESSARY]) - } else { - None - }, - data: None, - }) - .collect::<Vec<_>>(), - ) - }); - Task::Diagnostics(diagnostics.collect()) - }); - } } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/markdown.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/markdown.rs deleted file mode 100644 index 58426c66a..000000000 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/markdown.rs +++ /dev/null @@ -1,165 +0,0 @@ -//! Transforms markdown -use ide_db::rust_doc::is_rust_fence; - -const RUSTDOC_FENCES: [&str; 2] = ["```", "~~~"]; - -pub(crate) fn format_docs(src: &str) -> String { - let mut processed_lines = Vec::new(); - let mut in_code_block = false; - let mut is_rust = false; - - for mut line in src.lines() { - if in_code_block && is_rust && code_line_ignored_by_rustdoc(line) { - continue; - } - - if let Some(header) = RUSTDOC_FENCES.into_iter().find_map(|fence| line.strip_prefix(fence)) - { - in_code_block ^= true; - - if in_code_block { - is_rust = is_rust_fence(header); - - if is_rust { - line = "```rust"; - } - } - } - - if in_code_block { - let trimmed = line.trim_start(); - if is_rust && trimmed.starts_with("##") { - line = &trimmed[1..]; - } - } - - processed_lines.push(line); - } - processed_lines.join("\n") -} - -fn code_line_ignored_by_rustdoc(line: &str) -> bool { - let trimmed = line.trim(); - trimmed == "#" || trimmed.starts_with("# ") || trimmed.starts_with("#\t") -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_format_docs_adds_rust() { - let comment = "```\nfn some_rust() {}\n```"; - assert_eq!(format_docs(comment), "```rust\nfn some_rust() {}\n```"); - } - - #[test] - fn test_format_docs_handles_plain_text() { - let comment = "```text\nthis is plain text\n```"; - assert_eq!(format_docs(comment), "```text\nthis is plain text\n```"); - } - - #[test] - fn test_format_docs_handles_non_rust() { - let comment = "```sh\nsupposedly shell code\n```"; - assert_eq!(format_docs(comment), "```sh\nsupposedly shell code\n```"); - } - - #[test] - fn test_format_docs_handles_rust_alias() { - let comment = "```ignore\nlet z = 55;\n```"; - assert_eq!(format_docs(comment), "```rust\nlet z = 55;\n```"); - } - - #[test] - fn test_format_docs_handles_complex_code_block_attrs() { - let comment = "```rust,no_run\nlet z = 55;\n```"; - assert_eq!(format_docs(comment), "```rust\nlet z = 55;\n```"); - } - - #[test] - fn test_format_docs_handles_error_codes() { - let comment = "```compile_fail,E0641\nlet b = 0 as *const _;\n```"; - assert_eq!(format_docs(comment), "```rust\nlet b = 0 as *const _;\n```"); - } - - #[test] - fn test_format_docs_skips_comments_in_rust_block() { - let comment = - "```rust\n # skip1\n# skip2\n#stay1\nstay2\n#\n #\n # \n #\tskip3\n\t#\t\n```"; - assert_eq!(format_docs(comment), "```rust\n#stay1\nstay2\n```"); - } - - #[test] - fn test_format_docs_does_not_skip_lines_if_plain_text() { - let comment = - "```text\n # stay1\n# stay2\n#stay3\nstay4\n#\n #\n # \n #\tstay5\n\t#\t\n```"; - assert_eq!( - format_docs(comment), - "```text\n # stay1\n# stay2\n#stay3\nstay4\n#\n #\n # \n #\tstay5\n\t#\t\n```", - ); - } - - #[test] - fn test_format_docs_keeps_comments_outside_of_rust_block() { - let comment = " # stay1\n# stay2\n#stay3\nstay4\n#\n #\n # \n #\tstay5\n\t#\t"; - assert_eq!(format_docs(comment), comment); - } - - #[test] - fn test_format_docs_preserves_newlines() { - let comment = "this\nis\nmultiline"; - assert_eq!(format_docs(comment), comment); - } - - #[test] - fn test_code_blocks_in_comments_marked_as_rust() { - let comment = r#"```rust -fn main(){} -``` -Some comment. -``` -let a = 1; -```"#; - - assert_eq!( - format_docs(comment), - "```rust\nfn main(){}\n```\nSome comment.\n```rust\nlet a = 1;\n```" - ); - } - - #[test] - fn test_code_blocks_in_comments_marked_as_text() { - let comment = r#"```text -filler -text -``` -Some comment. -``` -let a = 1; -```"#; - - assert_eq!( - format_docs(comment), - "```text\nfiller\ntext\n```\nSome comment.\n```rust\nlet a = 1;\n```" - ); - } - - #[test] - fn test_format_docs_handles_escape_double_hashes() { - let comment = r#"```rust -let s = "foo -## bar # baz"; -```"#; - - assert_eq!(format_docs(comment), "```rust\nlet s = \"foo\n# bar # baz\";\n```"); - } - - #[test] - fn test_format_docs_handles_double_hashes_non_rust() { - let comment = r#"```markdown -## A second-level heading -```"#; - assert_eq!(format_docs(comment), "```markdown\n## A second-level heading\n```"); - } -} diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs index 0a2bb8224..3fae08b82 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs @@ -12,6 +12,7 @@ //! correct. Instead, we try to provide a best-effort service. Even if the //! project is currently loading and we don't have a full project model, we //! still want to respond to various requests. +// FIXME: This is a mess that needs some untangling work use std::{iter, mem}; use flycheck::{FlycheckConfig, FlycheckHandle}; diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs index 0bb29e708..d59914298 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs @@ -29,7 +29,7 @@ use lsp_types::{ PartialResultParams, Position, Range, RenameFilesParams, TextDocumentItem, TextDocumentPositionParams, WorkDoneProgressParams, }; -use rust_analyzer::lsp_ext::{OnEnter, Runnables, RunnablesParams}; +use rust_analyzer::lsp::ext::{OnEnter, Runnables, RunnablesParams}; use serde_json::json; use test_utils::skip_slow_tests; @@ -861,6 +861,7 @@ edition = "2021" bar = {path = "../bar"} //- /foo/src/main.rs +#![allow(internal_features)] #![feature(rustc_attrs, decl_macro)] use bar::Bar; @@ -938,7 +939,7 @@ pub fn foo(_input: TokenStream) -> TokenStream { let res = server.send_request::<HoverRequest>(HoverParams { text_document_position_params: TextDocumentPositionParams::new( server.doc_id("foo/src/main.rs"), - Position::new(11, 9), + Position::new(12, 9), ), work_done_progress_params: Default::default(), }); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs index 3c52ef5ef..e49b5768f 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs @@ -9,7 +9,7 @@ use std::{ use crossbeam_channel::{after, select, Receiver}; use lsp_server::{Connection, Message, Notification, Request}; use lsp_types::{notification::Exit, request::Shutdown, TextDocumentIdentifier, Url}; -use rust_analyzer::{config::Config, lsp_ext, main_loop}; +use rust_analyzer::{config::Config, lsp, main_loop}; use serde::Serialize; use serde_json::{json, to_string_pretty, Value}; use test_utils::FixtureWithProjectMeta; @@ -260,9 +260,9 @@ impl Server { Message::Notification(n) if n.method == "experimental/serverStatus" => { let status = n .clone() - .extract::<lsp_ext::ServerStatusParams>("experimental/serverStatus") + .extract::<lsp::ext::ServerStatusParams>("experimental/serverStatus") .unwrap(); - if status.health != lsp_ext::Health::Ok { + if status.health != lsp::ext::Health::Ok { panic!("server errored/warned while loading workspace: {:?}", status.message); } status.quiescent diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/tidy.rs b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/tidy.rs index f230cba2b..8b5c92c66 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/tidy.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/tidy.rs @@ -35,7 +35,7 @@ fn check_lsp_extensions_docs() { let expected_hash = { let lsp_ext_rs = sh - .read_file(sourcegen::project_root().join("crates/rust-analyzer/src/lsp_ext.rs")) + .read_file(sourcegen::project_root().join("crates/rust-analyzer/src/lsp/ext.rs")) .unwrap(); stable_hash(lsp_ext_rs.as_str()) }; @@ -45,7 +45,7 @@ fn check_lsp_extensions_docs() { sh.read_file(sourcegen::project_root().join("docs/dev/lsp-extensions.md")).unwrap(); let text = lsp_extensions_md .lines() - .find_map(|line| line.strip_prefix("lsp_ext.rs hash:")) + .find_map(|line| line.strip_prefix("lsp/ext.rs hash:")) .unwrap() .trim(); u64::from_str_radix(text, 16).unwrap() @@ -54,7 +54,7 @@ fn check_lsp_extensions_docs() { if actual_hash != expected_hash { panic!( " -lsp_ext.rs was changed without touching lsp-extensions.md. +lsp/ext.rs was changed without touching lsp-extensions.md. Expected hash: {expected_hash:x} Actual hash: {actual_hash:x} @@ -158,7 +158,7 @@ Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT Apache-2.0/MIT BSD-3-Clause BlueOak-1.0.0 OR MIT OR Apache-2.0 -CC0-1.0 OR Artistic-2.0 +CC0-1.0 ISC MIT MIT / Apache-2.0 diff --git a/src/tools/rust-analyzer/crates/syntax/rust.ungram b/src/tools/rust-analyzer/crates/syntax/rust.ungram index 138ddd208..3603560d3 100644 --- a/src/tools/rust-analyzer/crates/syntax/rust.ungram +++ b/src/tools/rust-analyzer/crates/syntax/rust.ungram @@ -296,7 +296,7 @@ TypeParam = ConstParam = Attr* 'const' Name ':' Type - ('=' default_val:Expr)? + ('=' default_val:ConstArg)? LifetimeParam = Attr* Lifetime (':' TypeBoundList?)? @@ -340,10 +340,10 @@ ExprStmt = Expr = ArrayExpr +| AsmExpr | AwaitExpr | BinExpr | BlockExpr -| BoxExpr | BreakExpr | CallExpr | CastExpr @@ -351,6 +351,7 @@ Expr = | ContinueExpr | FieldExpr | ForExpr +| FormatArgsExpr | IfExpr | IndexExpr | Literal @@ -358,6 +359,7 @@ Expr = | MacroExpr | MatchExpr | MethodCallExpr +| OffsetOfExpr | ParenExpr | PathExpr | PrefixExpr @@ -373,6 +375,21 @@ Expr = | LetExpr | UnderscoreExpr +OffsetOfExpr = + Attr* 'builtin' '#' 'offset_of' '(' Type ',' fields:(NameRef ('.' NameRef)* ) ')' + +AsmExpr = + Attr* 'builtin' '#' 'asm' '(' Expr ')' + +FormatArgsExpr = + Attr* 'builtin' '#' 'format_args' '(' + template:Expr + (',' args:(FormatArgsArg (',' FormatArgsArg)* ','?)? )? + ')' + +FormatArgsArg = + (Name '=')? Expr + MacroExpr = MacroCall @@ -526,9 +543,6 @@ UnderscoreExpr = AwaitExpr = Attr* Expr '.' 'await' -BoxExpr = - Attr* 'box' Expr - //*************************// // Types // //*************************// diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs index 0b27faa53..7ba0d4dc6 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs @@ -709,7 +709,7 @@ impl ConstParam { pub fn colon_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![:]) } pub fn ty(&self) -> Option<Type> { support::child(&self.syntax) } pub fn eq_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![=]) } - pub fn default_val(&self) -> Option<Expr> { support::child(&self.syntax) } + pub fn default_val(&self) -> Option<ConstArg> { support::child(&self.syntax) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -805,6 +805,20 @@ impl ArrayExpr { } #[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct AsmExpr { + pub(crate) syntax: SyntaxNode, +} +impl ast::HasAttrs for AsmExpr {} +impl AsmExpr { + pub fn builtin_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![builtin]) } + pub fn pound_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![#]) } + pub fn asm_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![asm]) } + pub fn l_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['(']) } + pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) } + pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct AwaitExpr { pub(crate) syntax: SyntaxNode, } @@ -823,16 +837,6 @@ impl ast::HasAttrs for BinExpr {} impl BinExpr {} #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct BoxExpr { - pub(crate) syntax: SyntaxNode, -} -impl ast::HasAttrs for BoxExpr {} -impl BoxExpr { - pub fn box_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![box]) } - pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct BreakExpr { pub(crate) syntax: SyntaxNode, } @@ -916,6 +920,24 @@ impl ForExpr { } #[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct FormatArgsExpr { + pub(crate) syntax: SyntaxNode, +} +impl ast::HasAttrs for FormatArgsExpr {} +impl FormatArgsExpr { + pub fn builtin_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![builtin]) } + pub fn pound_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![#]) } + pub fn format_args_token(&self) -> Option<SyntaxToken> { + support::token(&self.syntax, T![format_args]) + } + pub fn l_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['(']) } + pub fn template(&self) -> Option<Expr> { support::child(&self.syntax) } + pub fn comma_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![,]) } + pub fn args(&self) -> AstChildren<FormatArgsArg> { support::children(&self.syntax) } + pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct IfExpr { pub(crate) syntax: SyntaxNode, } @@ -985,6 +1007,24 @@ impl MethodCallExpr { } #[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct OffsetOfExpr { + pub(crate) syntax: SyntaxNode, +} +impl ast::HasAttrs for OffsetOfExpr {} +impl OffsetOfExpr { + pub fn builtin_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![builtin]) } + pub fn pound_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![#]) } + pub fn offset_of_token(&self) -> Option<SyntaxToken> { + support::token(&self.syntax, T![offset_of]) + } + pub fn l_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['(']) } + pub fn ty(&self) -> Option<Type> { support::child(&self.syntax) } + pub fn comma_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![,]) } + pub fn fields(&self) -> AstChildren<NameRef> { support::children(&self.syntax) } + pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ParenExpr { pub(crate) syntax: SyntaxNode, } @@ -1127,6 +1167,16 @@ impl UnderscoreExpr { } #[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct FormatArgsArg { + pub(crate) syntax: SyntaxNode, +} +impl ast::HasName for FormatArgsArg {} +impl FormatArgsArg { + pub fn eq_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![=]) } + pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct StmtList { pub(crate) syntax: SyntaxNode, } @@ -1555,10 +1605,10 @@ pub enum Type { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Expr { ArrayExpr(ArrayExpr), + AsmExpr(AsmExpr), AwaitExpr(AwaitExpr), BinExpr(BinExpr), BlockExpr(BlockExpr), - BoxExpr(BoxExpr), BreakExpr(BreakExpr), CallExpr(CallExpr), CastExpr(CastExpr), @@ -1566,6 +1616,7 @@ pub enum Expr { ContinueExpr(ContinueExpr), FieldExpr(FieldExpr), ForExpr(ForExpr), + FormatArgsExpr(FormatArgsExpr), IfExpr(IfExpr), IndexExpr(IndexExpr), Literal(Literal), @@ -1573,6 +1624,7 @@ pub enum Expr { MacroExpr(MacroExpr), MatchExpr(MatchExpr), MethodCallExpr(MethodCallExpr), + OffsetOfExpr(OffsetOfExpr), ParenExpr(ParenExpr), PathExpr(PathExpr), PrefixExpr(PrefixExpr), @@ -2453,8 +2505,8 @@ impl AstNode for ArrayExpr { } fn syntax(&self) -> &SyntaxNode { &self.syntax } } -impl AstNode for AwaitExpr { - fn can_cast(kind: SyntaxKind) -> bool { kind == AWAIT_EXPR } +impl AstNode for AsmExpr { + fn can_cast(kind: SyntaxKind) -> bool { kind == ASM_EXPR } fn cast(syntax: SyntaxNode) -> Option<Self> { if Self::can_cast(syntax.kind()) { Some(Self { syntax }) @@ -2464,8 +2516,8 @@ impl AstNode for AwaitExpr { } fn syntax(&self) -> &SyntaxNode { &self.syntax } } -impl AstNode for BinExpr { - fn can_cast(kind: SyntaxKind) -> bool { kind == BIN_EXPR } +impl AstNode for AwaitExpr { + fn can_cast(kind: SyntaxKind) -> bool { kind == AWAIT_EXPR } fn cast(syntax: SyntaxNode) -> Option<Self> { if Self::can_cast(syntax.kind()) { Some(Self { syntax }) @@ -2475,8 +2527,8 @@ impl AstNode for BinExpr { } fn syntax(&self) -> &SyntaxNode { &self.syntax } } -impl AstNode for BoxExpr { - fn can_cast(kind: SyntaxKind) -> bool { kind == BOX_EXPR } +impl AstNode for BinExpr { + fn can_cast(kind: SyntaxKind) -> bool { kind == BIN_EXPR } fn cast(syntax: SyntaxNode) -> Option<Self> { if Self::can_cast(syntax.kind()) { Some(Self { syntax }) @@ -2563,6 +2615,17 @@ impl AstNode for ForExpr { } fn syntax(&self) -> &SyntaxNode { &self.syntax } } +impl AstNode for FormatArgsExpr { + fn can_cast(kind: SyntaxKind) -> bool { kind == FORMAT_ARGS_EXPR } + fn cast(syntax: SyntaxNode) -> Option<Self> { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} impl AstNode for IfExpr { fn can_cast(kind: SyntaxKind) -> bool { kind == IF_EXPR } fn cast(syntax: SyntaxNode) -> Option<Self> { @@ -2640,6 +2703,17 @@ impl AstNode for MethodCallExpr { } fn syntax(&self) -> &SyntaxNode { &self.syntax } } +impl AstNode for OffsetOfExpr { + fn can_cast(kind: SyntaxKind) -> bool { kind == OFFSET_OF_EXPR } + fn cast(syntax: SyntaxNode) -> Option<Self> { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} impl AstNode for ParenExpr { fn can_cast(kind: SyntaxKind) -> bool { kind == PAREN_EXPR } fn cast(syntax: SyntaxNode) -> Option<Self> { @@ -2794,6 +2868,17 @@ impl AstNode for UnderscoreExpr { } fn syntax(&self) -> &SyntaxNode { &self.syntax } } +impl AstNode for FormatArgsArg { + fn can_cast(kind: SyntaxKind) -> bool { kind == FORMAT_ARGS_ARG } + fn cast(syntax: SyntaxNode) -> Option<Self> { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} impl AstNode for StmtList { fn can_cast(kind: SyntaxKind) -> bool { kind == STMT_LIST } fn cast(syntax: SyntaxNode) -> Option<Self> { @@ -3373,6 +3458,9 @@ impl AstNode for Type { impl From<ArrayExpr> for Expr { fn from(node: ArrayExpr) -> Expr { Expr::ArrayExpr(node) } } +impl From<AsmExpr> for Expr { + fn from(node: AsmExpr) -> Expr { Expr::AsmExpr(node) } +} impl From<AwaitExpr> for Expr { fn from(node: AwaitExpr) -> Expr { Expr::AwaitExpr(node) } } @@ -3382,9 +3470,6 @@ impl From<BinExpr> for Expr { impl From<BlockExpr> for Expr { fn from(node: BlockExpr) -> Expr { Expr::BlockExpr(node) } } -impl From<BoxExpr> for Expr { - fn from(node: BoxExpr) -> Expr { Expr::BoxExpr(node) } -} impl From<BreakExpr> for Expr { fn from(node: BreakExpr) -> Expr { Expr::BreakExpr(node) } } @@ -3406,6 +3491,9 @@ impl From<FieldExpr> for Expr { impl From<ForExpr> for Expr { fn from(node: ForExpr) -> Expr { Expr::ForExpr(node) } } +impl From<FormatArgsExpr> for Expr { + fn from(node: FormatArgsExpr) -> Expr { Expr::FormatArgsExpr(node) } +} impl From<IfExpr> for Expr { fn from(node: IfExpr) -> Expr { Expr::IfExpr(node) } } @@ -3427,6 +3515,9 @@ impl From<MatchExpr> for Expr { impl From<MethodCallExpr> for Expr { fn from(node: MethodCallExpr) -> Expr { Expr::MethodCallExpr(node) } } +impl From<OffsetOfExpr> for Expr { + fn from(node: OffsetOfExpr) -> Expr { Expr::OffsetOfExpr(node) } +} impl From<ParenExpr> for Expr { fn from(node: ParenExpr) -> Expr { Expr::ParenExpr(node) } } @@ -3474,10 +3565,10 @@ impl AstNode for Expr { matches!( kind, ARRAY_EXPR + | ASM_EXPR | AWAIT_EXPR | BIN_EXPR | BLOCK_EXPR - | BOX_EXPR | BREAK_EXPR | CALL_EXPR | CAST_EXPR @@ -3485,6 +3576,7 @@ impl AstNode for Expr { | CONTINUE_EXPR | FIELD_EXPR | FOR_EXPR + | FORMAT_ARGS_EXPR | IF_EXPR | INDEX_EXPR | LITERAL @@ -3492,6 +3584,7 @@ impl AstNode for Expr { | MACRO_EXPR | MATCH_EXPR | METHOD_CALL_EXPR + | OFFSET_OF_EXPR | PAREN_EXPR | PATH_EXPR | PREFIX_EXPR @@ -3511,10 +3604,10 @@ impl AstNode for Expr { fn cast(syntax: SyntaxNode) -> Option<Self> { let res = match syntax.kind() { ARRAY_EXPR => Expr::ArrayExpr(ArrayExpr { syntax }), + ASM_EXPR => Expr::AsmExpr(AsmExpr { syntax }), AWAIT_EXPR => Expr::AwaitExpr(AwaitExpr { syntax }), BIN_EXPR => Expr::BinExpr(BinExpr { syntax }), BLOCK_EXPR => Expr::BlockExpr(BlockExpr { syntax }), - BOX_EXPR => Expr::BoxExpr(BoxExpr { syntax }), BREAK_EXPR => Expr::BreakExpr(BreakExpr { syntax }), CALL_EXPR => Expr::CallExpr(CallExpr { syntax }), CAST_EXPR => Expr::CastExpr(CastExpr { syntax }), @@ -3522,6 +3615,7 @@ impl AstNode for Expr { CONTINUE_EXPR => Expr::ContinueExpr(ContinueExpr { syntax }), FIELD_EXPR => Expr::FieldExpr(FieldExpr { syntax }), FOR_EXPR => Expr::ForExpr(ForExpr { syntax }), + FORMAT_ARGS_EXPR => Expr::FormatArgsExpr(FormatArgsExpr { syntax }), IF_EXPR => Expr::IfExpr(IfExpr { syntax }), INDEX_EXPR => Expr::IndexExpr(IndexExpr { syntax }), LITERAL => Expr::Literal(Literal { syntax }), @@ -3529,6 +3623,7 @@ impl AstNode for Expr { MACRO_EXPR => Expr::MacroExpr(MacroExpr { syntax }), MATCH_EXPR => Expr::MatchExpr(MatchExpr { syntax }), METHOD_CALL_EXPR => Expr::MethodCallExpr(MethodCallExpr { syntax }), + OFFSET_OF_EXPR => Expr::OffsetOfExpr(OffsetOfExpr { syntax }), PAREN_EXPR => Expr::ParenExpr(ParenExpr { syntax }), PATH_EXPR => Expr::PathExpr(PathExpr { syntax }), PREFIX_EXPR => Expr::PrefixExpr(PrefixExpr { syntax }), @@ -3550,10 +3645,10 @@ impl AstNode for Expr { fn syntax(&self) -> &SyntaxNode { match self { Expr::ArrayExpr(it) => &it.syntax, + Expr::AsmExpr(it) => &it.syntax, Expr::AwaitExpr(it) => &it.syntax, Expr::BinExpr(it) => &it.syntax, Expr::BlockExpr(it) => &it.syntax, - Expr::BoxExpr(it) => &it.syntax, Expr::BreakExpr(it) => &it.syntax, Expr::CallExpr(it) => &it.syntax, Expr::CastExpr(it) => &it.syntax, @@ -3561,6 +3656,7 @@ impl AstNode for Expr { Expr::ContinueExpr(it) => &it.syntax, Expr::FieldExpr(it) => &it.syntax, Expr::ForExpr(it) => &it.syntax, + Expr::FormatArgsExpr(it) => &it.syntax, Expr::IfExpr(it) => &it.syntax, Expr::IndexExpr(it) => &it.syntax, Expr::Literal(it) => &it.syntax, @@ -3568,6 +3664,7 @@ impl AstNode for Expr { Expr::MacroExpr(it) => &it.syntax, Expr::MatchExpr(it) => &it.syntax, Expr::MethodCallExpr(it) => &it.syntax, + Expr::OffsetOfExpr(it) => &it.syntax, Expr::ParenExpr(it) => &it.syntax, Expr::PathExpr(it) => &it.syntax, Expr::PrefixExpr(it) => &it.syntax, @@ -4028,9 +4125,9 @@ impl AstNode for AnyHasAttrs { | TYPE_PARAM | LET_STMT | ARRAY_EXPR + | ASM_EXPR | AWAIT_EXPR | BIN_EXPR - | BOX_EXPR | BREAK_EXPR | CALL_EXPR | CAST_EXPR @@ -4038,12 +4135,14 @@ impl AstNode for AnyHasAttrs { | CONTINUE_EXPR | FIELD_EXPR | FOR_EXPR + | FORMAT_ARGS_EXPR | IF_EXPR | INDEX_EXPR | LITERAL | LOOP_EXPR | MATCH_EXPR | METHOD_CALL_EXPR + | OFFSET_OF_EXPR | PAREN_EXPR | PATH_EXPR | PREFIX_EXPR @@ -4179,6 +4278,7 @@ impl AstNode for AnyHasName { | VARIANT | CONST_PARAM | TYPE_PARAM + | FORMAT_ARGS_ARG | IDENT_PAT ) } @@ -4620,17 +4720,17 @@ impl std::fmt::Display for ArrayExpr { std::fmt::Display::fmt(self.syntax(), f) } } -impl std::fmt::Display for AwaitExpr { +impl std::fmt::Display for AsmExpr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } -impl std::fmt::Display for BinExpr { +impl std::fmt::Display for AwaitExpr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } -impl std::fmt::Display for BoxExpr { +impl std::fmt::Display for BinExpr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } @@ -4670,6 +4770,11 @@ impl std::fmt::Display for ForExpr { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for FormatArgsExpr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for IfExpr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) @@ -4705,6 +4810,11 @@ impl std::fmt::Display for MethodCallExpr { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for OffsetOfExpr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for ParenExpr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) @@ -4775,6 +4885,11 @@ impl std::fmt::Display for UnderscoreExpr { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for FormatArgsArg { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for StmtList { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs index 4c6db0ef0..17e311c0c 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs @@ -503,11 +503,16 @@ pub fn hacky_block_expr( pub fn expr_unit() -> ast::Expr { expr_from_text("()") } + pub fn expr_literal(text: &str) -> ast::Literal { assert_eq!(text.trim(), text); ast_from_text(&format!("fn f() {{ let _ = {text}; }}")) } +pub fn expr_const_value(text: &str) -> ast::ConstArg { + ast_from_text(&format!("trait Foo<const N: usize = {text}> {{}}")) +} + pub fn expr_empty_block() -> ast::Expr { expr_from_text("{}") } @@ -1100,7 +1105,7 @@ pub mod tokens { pub(super) static SOURCE_FILE: Lazy<Parse<SourceFile>> = Lazy::new(|| { SourceFile::parse( - "const C: <()>::Item = (1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p, &p , &mut p)\n;\n\n", + "const C: <()>::Item = ( true && true , true || true , 1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p, &p , &mut p)\n;\n\n", ) }); diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs index 3308077da..691d0c618 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs @@ -61,6 +61,14 @@ impl ast::BlockExpr { pub fn tail_expr(&self) -> Option<ast::Expr> { self.stmt_list()?.tail_expr() } + /// Block expressions accept outer and inner attributes, but only when they are the outer + /// expression of an expression statement or the final expression of another block expression. + pub fn may_carry_attributes(&self) -> bool { + matches!( + self.syntax().parent().map(|it| it.kind()), + Some(SyntaxKind::BLOCK_EXPR | SyntaxKind::EXPR_STMT) + ) + } } #[derive(Debug, PartialEq, Eq, Clone)] diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/prec.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/prec.rs index 4ec388914..8e04ab8be 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/prec.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/prec.rs @@ -130,7 +130,8 @@ impl Expr { // ContinueExpr(_) => (0, 0), - ClosureExpr(_) | ReturnExpr(_) | YieldExpr(_) | YeetExpr(_) | BreakExpr(_) => (0, 1), + ClosureExpr(_) | ReturnExpr(_) | YieldExpr(_) | YeetExpr(_) | BreakExpr(_) + | OffsetOfExpr(_) | FormatArgsExpr(_) | AsmExpr(_) => (0, 1), RangeExpr(_) => (5, 5), @@ -160,7 +161,7 @@ impl Expr { CastExpr(_) => (25, 26), - BoxExpr(_) | RefExpr(_) | LetExpr(_) | PrefixExpr(_) => (0, 27), + RefExpr(_) | LetExpr(_) | PrefixExpr(_) => (0, 27), AwaitExpr(_) | CallExpr(_) | MethodCallExpr(_) | IndexExpr(_) | TryExpr(_) | MacroExpr(_) => (29, 0), @@ -202,7 +203,6 @@ impl Expr { let rhs = match self { RefExpr(e) => e.expr(), BinExpr(e) => e.rhs(), - BoxExpr(e) => e.expr(), BreakExpr(e) => e.expr(), LetExpr(e) => e.expr(), RangeExpr(e) => e.end(), @@ -279,7 +279,6 @@ impl Expr { CastExpr(e) => e.as_token(), FieldExpr(e) => e.dot_token(), AwaitExpr(e) => e.dot_token(), - BoxExpr(e) => e.box_token(), BreakExpr(e) => e.break_token(), CallExpr(e) => e.arg_list().and_then(|args| args.l_paren_token()), ClosureExpr(e) => e.param_list().and_then(|params| params.l_paren_token()), @@ -293,7 +292,9 @@ impl Expr { YieldExpr(e) => e.yield_token(), YeetExpr(e) => e.do_token(), LetExpr(e) => e.let_token(), - + OffsetOfExpr(e) => e.builtin_token(), + FormatArgsExpr(e) => e.builtin_token(), + AsmExpr(e) => e.builtin_token(), ArrayExpr(_) | TupleExpr(_) | Literal(_) | PathExpr(_) | ParenExpr(_) | IfExpr(_) | WhileExpr(_) | ForExpr(_) | LoopExpr(_) | MatchExpr(_) | BlockExpr(_) | RecordExpr(_) | UnderscoreExpr(_) | MacroExpr(_) => None, @@ -310,12 +311,12 @@ impl Expr { ArrayExpr(_) | AwaitExpr(_) | BlockExpr(_) | CallExpr(_) | CastExpr(_) | ClosureExpr(_) | FieldExpr(_) | IndexExpr(_) | Literal(_) | LoopExpr(_) | MacroExpr(_) | MethodCallExpr(_) | ParenExpr(_) | PathExpr(_) | RecordExpr(_) - | TryExpr(_) | TupleExpr(_) | UnderscoreExpr(_) => false, + | TryExpr(_) | TupleExpr(_) | UnderscoreExpr(_) | OffsetOfExpr(_) + | FormatArgsExpr(_) | AsmExpr(_) => false, // For BinExpr and RangeExpr this is technically wrong -- the child can be on the left... - BinExpr(_) | RangeExpr(_) | BoxExpr(_) | BreakExpr(_) | ContinueExpr(_) - | PrefixExpr(_) | RefExpr(_) | ReturnExpr(_) | YieldExpr(_) | YeetExpr(_) - | LetExpr(_) => self + BinExpr(_) | RangeExpr(_) | BreakExpr(_) | ContinueExpr(_) | PrefixExpr(_) + | RefExpr(_) | ReturnExpr(_) | YieldExpr(_) | YeetExpr(_) | LetExpr(_) => self .syntax() .parent() .and_then(Expr::cast) diff --git a/src/tools/rust-analyzer/crates/syntax/src/lib.rs b/src/tools/rust-analyzer/crates/syntax/src/lib.rs index 4cd668a0c..27c8a13e5 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/lib.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/lib.rs @@ -195,11 +195,16 @@ impl ast::TokenTree { // Tag the token as joint if it is float with a fractional part // we use this jointness to inform the parser about what token split // event to emit when we encounter a float literal in a field access - if kind == SyntaxKind::FLOAT_NUMBER && !t.text().ends_with('.') { - parser_input.was_joint(); + if kind == SyntaxKind::FLOAT_NUMBER { + if !t.text().ends_with('.') { + parser_input.was_joint(); + } else { + was_joint = false; + } + } else { + was_joint = true; } } - was_joint = true; } } @@ -250,6 +255,7 @@ impl ast::TokenTree { if has_pseudo_dot { assert!(right.is_empty(), "{left}.{right}"); } else { + assert!(!right.is_empty(), "{left}.{right}"); builder.start_node(SyntaxKind::NAME_REF); builder.token(SyntaxKind::INT_NUMBER, right); builder.finish_node(); diff --git a/src/tools/rust-analyzer/crates/syntax/src/tests/ast_src.rs b/src/tools/rust-analyzer/crates/syntax/src/tests/ast_src.rs index e4db33f1c..341bda892 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/tests/ast_src.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/tests/ast_src.rs @@ -70,7 +70,19 @@ pub(crate) const KINDS_SRC: KindsSrc<'_> = KindsSrc { "match", "mod", "move", "mut", "pub", "ref", "return", "self", "Self", "static", "struct", "super", "trait", "true", "try", "type", "unsafe", "use", "where", "while", "yield", ], - contextual_keywords: &["auto", "default", "existential", "union", "raw", "macro_rules", "yeet"], + contextual_keywords: &[ + "auto", + "builtin", + "default", + "existential", + "union", + "raw", + "macro_rules", + "yeet", + "offset_of", + "asm", + "format_args", + ], literals: &["INT_NUMBER", "FLOAT_NUMBER", "CHAR", "BYTE", "STRING", "BYTE_STRING", "C_STRING"], tokens: &["ERROR", "IDENT", "WHITESPACE", "LIFETIME_IDENT", "COMMENT", "SHEBANG"], nodes: &[ @@ -154,7 +166,10 @@ pub(crate) const KINDS_SRC: KindsSrc<'_> = KindsSrc { "RECORD_EXPR", "RECORD_EXPR_FIELD_LIST", "RECORD_EXPR_FIELD", - "BOX_EXPR", + "OFFSET_OF_EXPR", + "ASM_EXPR", + "FORMAT_ARGS_EXPR", + "FORMAT_ARGS_ARG", // postfix "CALL_EXPR", "INDEX_EXPR", diff --git a/src/tools/rust-analyzer/crates/syntax/src/tests/sourcegen_ast.rs b/src/tools/rust-analyzer/crates/syntax/src/tests/sourcegen_ast.rs index c49c5fa10..56227fce9 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/tests/sourcegen_ast.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/tests/sourcegen_ast.rs @@ -623,7 +623,7 @@ fn lower_enum(grammar: &Grammar, rule: &Rule) -> Option<Vec<String>> { } fn lower_rule(acc: &mut Vec<Field>, grammar: &Grammar, label: Option<&String>, rule: &Rule) { - if lower_comma_list(acc, grammar, label, rule) { + if lower_seperated_list(acc, grammar, label, rule) { return; } @@ -689,7 +689,7 @@ fn lower_rule(acc: &mut Vec<Field>, grammar: &Grammar, label: Option<&String>, r } // (T (',' T)* ','?) -fn lower_comma_list( +fn lower_seperated_list( acc: &mut Vec<Field>, grammar: &Grammar, label: Option<&String>, @@ -699,19 +699,23 @@ fn lower_comma_list( Rule::Seq(it) => it, _ => return false, }; - let (node, repeat, trailing_comma) = match rule.as_slice() { - [Rule::Node(node), Rule::Rep(repeat), Rule::Opt(trailing_comma)] => { - (node, repeat, trailing_comma) + let (node, repeat, trailing_sep) = match rule.as_slice() { + [Rule::Node(node), Rule::Rep(repeat), Rule::Opt(trailing_sep)] => { + (node, repeat, Some(trailing_sep)) } + [Rule::Node(node), Rule::Rep(repeat)] => (node, repeat, None), _ => return false, }; let repeat = match &**repeat { Rule::Seq(it) => it, _ => return false, }; - match repeat.as_slice() { - [comma, Rule::Node(n)] if comma == &**trailing_comma && n == node => (), - _ => return false, + if !matches!( + repeat.as_slice(), + [comma, Rule::Node(n)] + if trailing_sep.map_or(true, |it| comma == &**it) && n == node + ) { + return false; } let ty = grammar[*node].name.clone(); let name = label.cloned().unwrap_or_else(|| pluralize(&to_lower_snake_case(&ty))); diff --git a/src/tools/rust-analyzer/crates/test-utils/src/fixture.rs b/src/tools/rust-analyzer/crates/test-utils/src/fixture.rs index 75e7a3fec..3f8b5a089 100644 --- a/src/tools/rust-analyzer/crates/test-utils/src/fixture.rs +++ b/src/tools/rust-analyzer/crates/test-utils/src/fixture.rs @@ -313,7 +313,7 @@ impl FixtureWithProjectMeta { } impl MiniCore { - const RAW_SOURCE: &str = include_str!("./minicore.rs"); + const RAW_SOURCE: &'static str = include_str!("./minicore.rs"); fn has_flag(&self, flag: &str) -> bool { self.activated_flags.iter().any(|it| it == flag) diff --git a/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs b/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs index c765f4244..573f56b00 100644 --- a/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs +++ b/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs @@ -899,32 +899,90 @@ pub mod fmt { fn fmt(&self, f: &mut Formatter<'_>) -> Result; } - extern "C" { - type Opaque; - } + mod rt { - #[lang = "format_argument"] - pub struct Argument<'a> { - value: &'a Opaque, - formatter: fn(&Opaque, &mut Formatter<'_>) -> Result, - } + extern "C" { + type Opaque; + } + + #[lang = "format_argument"] + pub struct Argument<'a> { + value: &'a Opaque, + formatter: fn(&Opaque, &mut Formatter<'_>) -> Result, + } + + impl<'a> Argument<'a> { + pub fn new<'b, T>(x: &'b T, f: fn(&T, &mut Formatter<'_>) -> Result) -> Argument<'b> { + use crate::mem::transmute; + unsafe { Argument { formatter: transmute(f), value: transmute(x) } } + } + } + + #[lang = "format_alignment"] + pub enum Alignment { + Left, + Right, + Center, + Unknown, + } + + #[lang = "format_count"] + pub enum Count { + Is(usize), + Param(usize), + Implied, + } + + #[lang = "format_placeholder"] + pub struct Placeholder { + pub position: usize, + pub fill: char, + pub align: Alignment, + pub flags: u32, + pub precision: Count, + pub width: Count, + } + + impl Placeholder { + pub const fn new( + position: usize, + fill: char, + align: Alignment, + flags: u32, + precision: Count, + width: Count, + ) -> Self; + } + + #[lang = "format_unsafe_arg"] + pub struct UnsafeArg { + _private: (), + } - impl<'a> Argument<'a> { - pub fn new<'b, T>(x: &'b T, f: fn(&T, &mut Formatter<'_>) -> Result) -> Argument<'b> { - use crate::mem::transmute; - unsafe { Argument { formatter: transmute(f), value: transmute(x) } } + impl UnsafeArg { + pub unsafe fn new() -> Self; } } #[lang = "format_arguments"] pub struct Arguments<'a> { pieces: &'a [&'static str], - args: &'a [Argument<'a>], + fmt: Option<&'a [rt::Placeholder]>, + args: &'a [rt::Argument<'a>], } impl<'a> Arguments<'a> { pub const fn new_v1(pieces: &'a [&'static str], args: &'a [Argument<'a>]) -> Arguments<'a> { - Arguments { pieces, args } + Arguments { pieces, fmt: None, args } + } + + pub fn new_v1_formatted( + pieces: &'a [&'static str], + args: &'a [rt::Argument<'a>], + fmt: &'a [rt::Placeholder], + _unsafe_arg: rt::UnsafeArg, + ) -> Arguments<'a> { + Arguments { pieces, fmt: Some(fmt), args } } } @@ -1294,8 +1352,6 @@ mod macros { /* compiler built-in */ }; } - - pub(crate) use panic; // endregion:panic // region:fmt @@ -1306,7 +1362,20 @@ mod macros { ($fmt:expr, $($args:tt)*) => {{ /* compiler built-in */ }}; } - pub(crate) use const_format_args; + #[macro_export] + #[rustc_builtin_macro] + macro_rules! format_args { + ($fmt:expr) => {{ /* compiler built-in */ }}; + ($fmt:expr, $($args:tt)*) => {{ /* compiler built-in */ }}; + } + + #[macro_export] + macro_rules! print { + ($($arg:tt)*) => {{ + $crate::io::_print($crate::format_args!($($arg)*)); + }}; + } + // endregion:fmt // region:derive diff --git a/src/tools/rust-analyzer/crates/vfs-notify/Cargo.toml b/src/tools/rust-analyzer/crates/vfs-notify/Cargo.toml index 95c514251..11055f028 100644 --- a/src/tools/rust-analyzer/crates/vfs-notify/Cargo.toml +++ b/src/tools/rust-analyzer/crates/vfs-notify/Cargo.toml @@ -16,7 +16,7 @@ tracing = "0.1.35" walkdir = "2.3.2" crossbeam-channel = "0.5.5" # We demand 5.1.0 as any higher version pulls in a new windows-sys dupe -notify = "=5.1.0" +notify = "6.1.1" stdx.workspace = true vfs.workspace = true diff --git a/src/tools/rust-analyzer/crates/vfs/src/lib.rs b/src/tools/rust-analyzer/crates/vfs/src/lib.rs index fe3dfe619..06004adad 100644 --- a/src/tools/rust-analyzer/crates/vfs/src/lib.rs +++ b/src/tools/rust-analyzer/crates/vfs/src/lib.rs @@ -184,6 +184,11 @@ impl Vfs { mem::take(&mut self.changes) } + /// Provides a panic-less way to verify file_id validity. + pub fn exists(&self, file_id: FileId) -> bool { + self.get(file_id).is_some() + } + /// Returns the id associated with `path` /// /// - If `path` does not exists in the `Vfs`, allocate a new id for it, associated with a |