From b80bb583e43181bc4a4470a2c19481583edab10b Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Wed, 18 Dec 2024 00:57:38 +0200 Subject: [PATCH 001/123] Show substitution where hovering over generic things There are few things to note in the implementation: First, this is a best-effort implementation. Mainly, type aliases may not be shown (due to their eager nature it's harder) and partial pathes (aka. hovering over `Struct` in `Struct::method`) are not supported at all. Second, we only need to show substitutions in expression and pattern position, because in type position all generic arguments always have to be written explicitly. --- src/tools/rust-analyzer/crates/hir/src/lib.rs | 55 +++ .../rust-analyzer/crates/hir/src/semantics.rs | 34 +- .../crates/hir/src/source_analyzer.rs | 326 +++++++++++--- .../src/handlers/add_turbo_fish.rs | 2 +- .../src/handlers/convert_match_to_let_else.rs | 2 +- .../convert_tuple_struct_to_named_struct.rs | 4 +- .../src/handlers/expand_glob_import.rs | 1 + .../src/handlers/extract_function.rs | 4 +- .../src/handlers/extract_module.rs | 4 +- .../src/handlers/generate_constant.rs | 2 +- .../src/handlers/generate_function.rs | 2 +- .../rust-analyzer/crates/ide-db/src/defs.rs | 143 +++--- .../rust-analyzer/crates/ide-db/src/search.rs | 20 +- .../crates/ide/src/call_hierarchy.rs | 2 +- .../rust-analyzer/crates/ide/src/doc_links.rs | 6 +- .../crates/ide/src/goto_declaration.rs | 2 +- .../crates/ide/src/goto_definition.rs | 2 +- .../crates/ide/src/goto_implementation.rs | 2 +- .../rust-analyzer/crates/ide/src/hover.rs | 26 +- .../crates/ide/src/hover/render.rs | 45 +- .../crates/ide/src/hover/tests.rs | 415 ++++++++++++++++++ src/tools/rust-analyzer/crates/ide/src/lib.rs | 2 +- .../crates/ide/src/references.rs | 16 +- .../rust-analyzer/crates/ide/src/rename.rs | 8 +- .../crates/ide/src/static_index.rs | 7 +- .../ide/src/syntax_highlighting/highlight.rs | 4 +- .../crates/rust-analyzer/src/config.rs | 33 ++ .../docs/user/generated_config.adoc | 9 + .../rust-analyzer/editors/code/package.json | 23 + 29 files changed, 1015 insertions(+), 186 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index 3bc2eee1e7c2c..8186af296a697 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -3574,6 +3574,61 @@ impl GenericDef { } } +// We cannot call this `Substitution` unfortunately... +#[derive(Debug)] +pub struct GenericSubstitution { + def: GenericDefId, + subst: Substitution, + env: Arc, +} + +impl GenericSubstitution { + fn new(def: GenericDefId, subst: Substitution, env: Arc) -> Self { + Self { def, subst, env } + } + + pub fn types(&self, db: &dyn HirDatabase) -> Vec<(Symbol, Type)> { + let container = match self.def { + GenericDefId::ConstId(id) => Some(id.lookup(db.upcast()).container), + GenericDefId::FunctionId(id) => Some(id.lookup(db.upcast()).container), + GenericDefId::TypeAliasId(id) => Some(id.lookup(db.upcast()).container), + _ => None, + }; + let container_type_params = container + .and_then(|container| match container { + ItemContainerId::ImplId(container) => Some(container.into()), + ItemContainerId::TraitId(container) => Some(container.into()), + _ => None, + }) + .map(|container| { + db.generic_params(container) + .iter_type_or_consts() + .filter_map(|param| match param.1 { + TypeOrConstParamData::TypeParamData(param) => Some(param.name.clone()), + TypeOrConstParamData::ConstParamData(_) => None, + }) + .collect::>() + }); + let generics = db.generic_params(self.def); + let type_params = generics.iter_type_or_consts().filter_map(|param| match param.1 { + TypeOrConstParamData::TypeParamData(param) => Some(param.name.clone()), + TypeOrConstParamData::ConstParamData(_) => None, + }); + // The `Substitution` is first self then container, we want the reverse order. + let self_params = self.subst.type_parameters(Interner).zip(type_params); + let container_params = self.subst.as_slice(Interner)[generics.len()..] + .iter() + .filter_map(|param| param.ty(Interner).cloned()) + .zip(container_type_params.into_iter().flatten()); + container_params + .chain(self_params) + .filter_map(|(ty, name)| { + Some((name?.symbol().clone(), Type { ty, env: self.env.clone() })) + }) + .collect() + } +} + /// A single local definition. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct Local { diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs index b896cda9ddf7d..17b670f6a46ae 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs @@ -49,10 +49,10 @@ use crate::{ semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx}, source_analyzer::{name_hygiene, resolve_hir_path, SourceAnalyzer}, Access, Adjust, Adjustment, Adt, AutoBorrow, BindingMode, BuiltinAttr, Callable, Const, - ConstParam, Crate, DeriveHelper, Enum, Field, Function, HasSource, HirFileId, Impl, InFile, - InlineAsmOperand, ItemInNs, Label, LifetimeParam, Local, Macro, Module, ModuleDef, Name, - OverloadedDeref, Path, ScopeDef, Static, Struct, ToolModule, Trait, TraitAlias, TupleField, - Type, TypeAlias, TypeParam, Union, Variant, VariantDef, + ConstParam, Crate, DeriveHelper, Enum, Field, Function, GenericSubstitution, HasSource, + HirFileId, Impl, InFile, InlineAsmOperand, ItemInNs, Label, LifetimeParam, Local, Macro, + Module, ModuleDef, Name, OverloadedDeref, Path, ScopeDef, Static, Struct, ToolModule, Trait, + TraitAlias, TupleField, Type, TypeAlias, TypeParam, Union, Variant, VariantDef, }; const CONTINUE_NO_BREAKS: ControlFlow = ControlFlow::Continue(()); @@ -1415,7 +1415,7 @@ impl<'db> SemanticsImpl<'db> { pub fn resolve_method_call_fallback( &self, call: &ast::MethodCallExpr, - ) -> Option> { + ) -> Option<(Either, Option)> { self.analyze(call.syntax())?.resolve_method_call_fallback(self.db, call) } @@ -1458,7 +1458,7 @@ impl<'db> SemanticsImpl<'db> { pub fn resolve_field_fallback( &self, field: &ast::FieldExpr, - ) -> Option, Function>> { + ) -> Option<(Either, Function>, Option)> { self.analyze(field.syntax())?.resolve_field_fallback(self.db, field) } @@ -1466,10 +1466,25 @@ impl<'db> SemanticsImpl<'db> { &self, field: &ast::RecordExprField, ) -> Option<(Field, Option, Type)> { + self.resolve_record_field_with_substitution(field) + .map(|(field, local, ty, _)| (field, local, ty)) + } + + pub fn resolve_record_field_with_substitution( + &self, + field: &ast::RecordExprField, + ) -> Option<(Field, Option, Type, GenericSubstitution)> { self.analyze(field.syntax())?.resolve_record_field(self.db, field) } pub fn resolve_record_pat_field(&self, field: &ast::RecordPatField) -> Option<(Field, Type)> { + self.resolve_record_pat_field_with_subst(field).map(|(field, ty, _)| (field, ty)) + } + + pub fn resolve_record_pat_field_with_subst( + &self, + field: &ast::RecordPatField, + ) -> Option<(Field, Type, GenericSubstitution)> { self.analyze(field.syntax())?.resolve_record_pat_field(self.db, field) } @@ -1525,6 +1540,13 @@ impl<'db> SemanticsImpl<'db> { } pub fn resolve_path(&self, path: &ast::Path) -> Option { + self.resolve_path_with_subst(path).map(|(it, _)| it) + } + + pub fn resolve_path_with_subst( + &self, + path: &ast::Path, + ) -> Option<(PathResolution, Option)> { self.analyze(path.syntax())?.resolve_path(self.db, path) } 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 4329a888b392d..2f21685fb59d6 100644 --- a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs +++ b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs @@ -9,8 +9,8 @@ use std::iter::{self, once}; use crate::{ db::HirDatabase, semantics::PathResolution, Adt, AssocItem, BindingMode, BuiltinAttr, - BuiltinType, Callable, Const, DeriveHelper, Field, Function, Local, Macro, ModuleDef, Static, - Struct, ToolModule, Trait, TraitAlias, TupleField, Type, TypeAlias, Variant, + BuiltinType, Callable, Const, DeriveHelper, Field, Function, GenericSubstitution, Local, Macro, + ModuleDef, Static, Struct, ToolModule, Trait, TraitAlias, TupleField, Type, TypeAlias, Variant, }; use either::Either; use hir_def::{ @@ -18,15 +18,15 @@ use hir_def::{ scope::{ExprScopes, ScopeId}, Body, BodySourceMap, HygieneId, }, - hir::{BindingId, ExprId, ExprOrPatId, Pat, PatId}, + hir::{BindingId, Expr, ExprId, ExprOrPatId, Pat, PatId}, lang_item::LangItem, lower::LowerCtx, nameres::MacroSubNs, path::{ModPath, Path, PathKind}, resolver::{resolver_for_scope, Resolver, TypeNs, ValueNs}, type_ref::{Mutability, TypesMap, TypesSourceMap}, - AsMacroCall, AssocItemId, ConstId, DefWithBodyId, FieldId, FunctionId, ItemContainerId, - LocalFieldId, Lookup, ModuleDefId, StructId, TraitId, VariantId, + AsMacroCall, AssocItemId, CallableDefId, ConstId, DefWithBodyId, FieldId, FunctionId, + ItemContainerId, LocalFieldId, Lookup, ModuleDefId, StructId, TraitId, VariantId, }; use hir_expand::{ mod_path::path, @@ -38,9 +38,10 @@ use hir_ty::{ record_literal_missing_fields, record_pattern_missing_fields, unsafe_expressions, InsideUnsafeBlock, }, + from_assoc_type_id, lang_items::lang_items_for_bin_op, - method_resolution, Adjustment, InferenceResult, Interner, Substitution, Ty, TyExt, TyKind, - TyLoweringContext, + method_resolution, Adjustment, InferenceResult, Interner, Substitution, TraitEnvironment, Ty, + TyExt, TyKind, TyLoweringContext, }; use intern::sym; use itertools::Itertools; @@ -120,6 +121,13 @@ impl SourceAnalyzer { self.def.as_ref().map(|(_, body, _)| &**body) } + fn trait_environment(&self, db: &dyn HirDatabase) -> Arc { + self.def.as_ref().map(|(def, ..)| *def).map_or_else( + || TraitEnvironment::empty(self.resolver.krate()), + |def| db.trait_environment_for_body(def), + ) + } + fn expr_id(&self, db: &dyn HirDatabase, expr: &ast::Expr) -> Option { let src = match expr { ast::Expr::MacroExpr(expr) => { @@ -294,18 +302,23 @@ impl SourceAnalyzer { &self, db: &dyn HirDatabase, call: &ast::MethodCallExpr, - ) -> Option> { + ) -> Option<(Either, Option)> { let expr_id = self.expr_id(db, &call.clone().into())?.as_expr()?; let inference_result = self.infer.as_ref()?; match inference_result.method_resolution(expr_id) { - Some((f_in_trait, substs)) => Some(Either::Left( - self.resolve_impl_method_or_trait_def(db, f_in_trait, substs).into(), - )), - None => inference_result - .field_resolution(expr_id) - .and_then(Either::left) - .map(Into::into) - .map(Either::Right), + Some((f_in_trait, substs)) => { + let (fn_, subst) = + self.resolve_impl_method_or_trait_def_with_subst(db, f_in_trait, substs); + Some(( + Either::Left(fn_.into()), + Some(GenericSubstitution::new(fn_.into(), subst, self.trait_environment(db))), + )) + } + None => { + inference_result.field_resolution(expr_id).and_then(Either::left).map(|field| { + (Either::Right(field.into()), self.field_subst(expr_id, inference_result, db)) + }) + } } } @@ -330,22 +343,53 @@ impl SourceAnalyzer { }) } + fn field_subst( + &self, + field_expr: ExprId, + infer: &InferenceResult, + db: &dyn HirDatabase, + ) -> Option { + let body = self.body()?; + if let Expr::Field { expr: object_expr, name: _ } = body[field_expr] { + let (adt, subst) = type_of_expr_including_adjust(infer, object_expr)?.as_adt()?; + return Some(GenericSubstitution::new( + adt.into(), + subst.clone(), + self.trait_environment(db), + )); + } + None + } + pub(crate) fn resolve_field_fallback( &self, db: &dyn HirDatabase, field: &ast::FieldExpr, - ) -> Option, Function>> { + ) -> Option<(Either, Function>, Option)> { let &(def, ..) = self.def.as_ref()?; let expr_id = self.expr_id(db, &field.clone().into())?.as_expr()?; let inference_result = self.infer.as_ref()?; match inference_result.field_resolution(expr_id) { - Some(field) => Some(Either::Left(field.map_either(Into::into, |f| TupleField { - owner: def, - tuple: f.tuple, - index: f.index, - }))), + Some(field) => match field { + Either::Left(field) => Some(( + Either::Left(Either::Left(field.into())), + self.field_subst(expr_id, inference_result, db), + )), + Either::Right(field) => Some(( + Either::Left(Either::Right(TupleField { + owner: def, + tuple: field.tuple, + index: field.index, + })), + None, + )), + }, None => inference_result.method_resolution(expr_id).map(|(f, substs)| { - Either::Right(self.resolve_impl_method_or_trait_def(db, f, substs).into()) + let (f, subst) = self.resolve_impl_method_or_trait_def_with_subst(db, f, substs); + ( + Either::Right(f.into()), + Some(GenericSubstitution::new(f.into(), subst, self.trait_environment(db))), + ) }), } } @@ -557,7 +601,7 @@ impl SourceAnalyzer { &self, db: &dyn HirDatabase, field: &ast::RecordExprField, - ) -> Option<(Field, Option, Type)> { + ) -> Option<(Field, Option, Type, GenericSubstitution)> { let record_expr = ast::RecordExpr::cast(field.syntax().parent().and_then(|p| p.parent())?)?; let expr = ast::Expr::from(record_expr); let expr_id = self.body_source_map()?.node_expr(InFile::new(self.file_id, &expr))?; @@ -583,30 +627,39 @@ impl SourceAnalyzer { _ => None, } }; - let (_, subst) = self.infer.as_ref()?.type_of_expr_or_pat(expr_id)?.as_adt()?; + let (adt, subst) = self.infer.as_ref()?.type_of_expr_or_pat(expr_id)?.as_adt()?; let variant = self.infer.as_ref()?.variant_resolution_for_expr_or_pat(expr_id)?; let variant_data = variant.variant_data(db.upcast()); let field = FieldId { parent: variant, local_id: variant_data.field(&local_name)? }; let field_ty = db.field_types(variant).get(field.local_id)?.clone().substitute(Interner, subst); - Some((field.into(), local, Type::new_with_resolver(db, &self.resolver, field_ty))) + Some(( + field.into(), + local, + Type::new_with_resolver(db, &self.resolver, field_ty), + GenericSubstitution::new(adt.into(), subst.clone(), self.trait_environment(db)), + )) } pub(crate) fn resolve_record_pat_field( &self, db: &dyn HirDatabase, field: &ast::RecordPatField, - ) -> Option<(Field, Type)> { + ) -> Option<(Field, Type, GenericSubstitution)> { let field_name = field.field_name()?.as_name(); let record_pat = ast::RecordPat::cast(field.syntax().parent().and_then(|p| p.parent())?)?; let pat_id = self.pat_id(&record_pat.into())?; let variant = self.infer.as_ref()?.variant_resolution_for_pat(pat_id)?; let variant_data = variant.variant_data(db.upcast()); let field = FieldId { parent: variant, local_id: variant_data.field(&field_name)? }; - let (_, subst) = self.infer.as_ref()?.type_of_pat.get(pat_id)?.as_adt()?; + let (adt, subst) = self.infer.as_ref()?.type_of_pat.get(pat_id)?.as_adt()?; let field_ty = db.field_types(variant).get(field.local_id)?.clone().substitute(Interner, subst); - Some((field.into(), Type::new_with_resolver(db, &self.resolver, field_ty))) + Some(( + field.into(), + Type::new_with_resolver(db, &self.resolver, field_ty), + GenericSubstitution::new(adt.into(), subst.clone(), self.trait_environment(db)), + )) } pub(crate) fn resolve_macro_call( @@ -654,7 +707,7 @@ impl SourceAnalyzer { &self, db: &dyn HirDatabase, path: &ast::Path, - ) -> Option { + ) -> Option<(PathResolution, Option)> { let parent = path.syntax().parent(); let parent = || parent.clone(); @@ -664,60 +717,106 @@ impl SourceAnalyzer { if let Some(path_expr) = parent().and_then(ast::PathExpr::cast) { let expr_id = self.expr_id(db, &path_expr.into())?; if let Some((assoc, subs)) = infer.assoc_resolutions_for_expr_or_pat(expr_id) { - let assoc = match assoc { + let (assoc, subst) = match assoc { AssocItemId::FunctionId(f_in_trait) => { match infer.type_of_expr_or_pat(expr_id) { - None => assoc, + None => { + let subst = GenericSubstitution::new( + f_in_trait.into(), + subs, + self.trait_environment(db), + ); + (assoc, subst) + } Some(func_ty) => { if let TyKind::FnDef(_fn_def, subs) = func_ty.kind(Interner) { - self.resolve_impl_method_or_trait_def( - db, - f_in_trait, - subs.clone(), - ) - .into() + let (fn_, subst) = self + .resolve_impl_method_or_trait_def_with_subst( + db, + f_in_trait, + subs.clone(), + ); + let subst = GenericSubstitution::new( + fn_.into(), + subst, + self.trait_environment(db), + ); + (fn_.into(), subst) } else { - assoc + let subst = GenericSubstitution::new( + f_in_trait.into(), + subs, + self.trait_environment(db), + ); + (assoc, subst) } } } } AssocItemId::ConstId(const_id) => { - self.resolve_impl_const_or_trait_def(db, const_id, subs).into() + let (konst, subst) = + self.resolve_impl_const_or_trait_def_with_subst(db, const_id, subs); + let subst = GenericSubstitution::new( + konst.into(), + subst, + self.trait_environment(db), + ); + (konst.into(), subst) } - assoc => assoc, + AssocItemId::TypeAliasId(type_alias) => ( + assoc, + GenericSubstitution::new( + type_alias.into(), + subs, + self.trait_environment(db), + ), + ), }; - return Some(PathResolution::Def(AssocItem::from(assoc).into())); + return Some((PathResolution::Def(AssocItem::from(assoc).into()), Some(subst))); } if let Some(VariantId::EnumVariantId(variant)) = infer.variant_resolution_for_expr_or_pat(expr_id) { - return Some(PathResolution::Def(ModuleDef::Variant(variant.into()))); + return Some((PathResolution::Def(ModuleDef::Variant(variant.into())), None)); } prefer_value_ns = true; } else if let Some(path_pat) = parent().and_then(ast::PathPat::cast) { let pat_id = self.pat_id(&path_pat.into())?; if let Some((assoc, subs)) = infer.assoc_resolutions_for_pat(pat_id) { - let assoc = match assoc { + let (assoc, subst) = match assoc { AssocItemId::ConstId(const_id) => { - self.resolve_impl_const_or_trait_def(db, const_id, subs).into() + let (konst, subst) = + self.resolve_impl_const_or_trait_def_with_subst(db, const_id, subs); + let subst = GenericSubstitution::new( + konst.into(), + subst, + self.trait_environment(db), + ); + (konst.into(), subst) } - assoc => assoc, + assoc => ( + assoc, + GenericSubstitution::new( + assoc.into(), + subs, + self.trait_environment(db), + ), + ), }; - return Some(PathResolution::Def(AssocItem::from(assoc).into())); + return Some((PathResolution::Def(AssocItem::from(assoc).into()), Some(subst))); } if let Some(VariantId::EnumVariantId(variant)) = infer.variant_resolution_for_pat(pat_id) { - return Some(PathResolution::Def(ModuleDef::Variant(variant.into()))); + return Some((PathResolution::Def(ModuleDef::Variant(variant.into())), None)); } } else if let Some(rec_lit) = parent().and_then(ast::RecordExpr::cast) { let expr_id = self.expr_id(db, &rec_lit.into())?; if let Some(VariantId::EnumVariantId(variant)) = infer.variant_resolution_for_expr_or_pat(expr_id) { - return Some(PathResolution::Def(ModuleDef::Variant(variant.into()))); + return Some((PathResolution::Def(ModuleDef::Variant(variant.into())), None)); } } else { let record_pat = parent().and_then(ast::RecordPat::cast).map(ast::Pat::from); @@ -727,7 +826,10 @@ impl SourceAnalyzer { let pat_id = self.pat_id(&pat)?; let variant_res_for_pat = infer.variant_resolution_for_pat(pat_id); if let Some(VariantId::EnumVariantId(variant)) = variant_res_for_pat { - return Some(PathResolution::Def(ModuleDef::Variant(variant.into()))); + return Some(( + PathResolution::Def(ModuleDef::Variant(variant.into())), + None, + )); } } } @@ -747,7 +849,8 @@ impl SourceAnalyzer { // trying to resolve foo::bar. if let Some(use_tree) = parent().and_then(ast::UseTree::cast) { if use_tree.coloncolon_token().is_some() { - return resolve_hir_path_qualifier(db, &self.resolver, &hir_path, &types_map); + return resolve_hir_path_qualifier(db, &self.resolver, &hir_path, &types_map) + .map(|it| (it, None)); } } @@ -765,13 +868,18 @@ impl SourceAnalyzer { // trying to resolve foo::bar. if path.parent_path().is_some() { return match resolve_hir_path_qualifier(db, &self.resolver, &hir_path, &types_map) { - None if meta_path.is_some() => { - path.first_segment().and_then(|it| it.name_ref()).and_then(|name_ref| { + None if meta_path.is_some() => path + .first_segment() + .and_then(|it| it.name_ref()) + .and_then(|name_ref| { ToolModule::by_name(db, self.resolver.krate().into(), &name_ref.text()) .map(PathResolution::ToolModule) }) - } - res => res, + .map(|it| (it, None)), + // FIXME: We do not show substitutions for parts of path, because this is really complex + // due to the interactions with associated items of `impl`s and associated items of associated + // types. + res => res.map(|it| (it, None)), }; } else if let Some(meta_path) = meta_path { // Case where we are resolving the final path segment of a path in an attribute @@ -781,7 +889,7 @@ impl SourceAnalyzer { let builtin = BuiltinAttr::by_name(db, self.resolver.krate().into(), &name_ref.text()); if builtin.is_some() { - return builtin.map(PathResolution::BuiltinAttr); + return builtin.map(|it| (PathResolution::BuiltinAttr(it), None)); } if let Some(attr) = meta_path.parent_attr() { @@ -814,10 +922,13 @@ impl SourceAnalyzer { { if let Some(idx) = helpers.position(|(name, ..)| *name == name_ref) { - return Some(PathResolution::DeriveHelper(DeriveHelper { - derive: *macro_id, - idx: idx as u32, - })); + return Some(( + PathResolution::DeriveHelper(DeriveHelper { + derive: *macro_id, + idx: idx as u32, + }), + None, + )); } } } @@ -825,26 +936,79 @@ impl SourceAnalyzer { } } return match resolve_hir_path_as_attr_macro(db, &self.resolver, &hir_path) { - Some(m) => Some(PathResolution::Def(ModuleDef::Macro(m))), + Some(m) => Some((PathResolution::Def(ModuleDef::Macro(m)), None)), // this labels any path that starts with a tool module as the tool itself, this is technically wrong // but there is no benefit in differentiating these two cases for the time being - None => path.first_segment().and_then(|it| it.name_ref()).and_then(|name_ref| { - ToolModule::by_name(db, self.resolver.krate().into(), &name_ref.text()) - .map(PathResolution::ToolModule) - }), + None => path + .first_segment() + .and_then(|it| it.name_ref()) + .and_then(|name_ref| { + ToolModule::by_name(db, self.resolver.krate().into(), &name_ref.text()) + .map(PathResolution::ToolModule) + }) + .map(|it| (it, None)), }; } if parent().map_or(false, |it| ast::Visibility::can_cast(it.kind())) { + // No substitution because only modules can be inside visibilities, and those have no generics. resolve_hir_path_qualifier(db, &self.resolver, &hir_path, &types_map) + .map(|it| (it, None)) } else { - resolve_hir_path_( + // Probably a type, no need to show substitutions for those. + let res = resolve_hir_path_( db, &self.resolver, &hir_path, prefer_value_ns, name_hygiene(db, InFile::new(self.file_id, path.syntax())), &types_map, - ) + )?; + let subst = (|| { + let parent = parent()?; + let ty = if let Some(expr) = ast::Expr::cast(parent.clone()) { + let expr_id = self.expr_id(db, &expr)?; + self.infer.as_ref()?.type_of_expr_or_pat(expr_id)? + } else if let Some(pat) = ast::Pat::cast(parent) { + let pat_id = self.pat_id(&pat)?; + &self.infer.as_ref()?[pat_id] + } else { + return None; + }; + let env = self.trait_environment(db); + let (subst, expected_resolution) = match ty.kind(Interner) { + TyKind::Adt(adt_id, subst) => ( + GenericSubstitution::new(adt_id.0.into(), subst.clone(), env), + PathResolution::Def(ModuleDef::Adt(adt_id.0.into())), + ), + TyKind::AssociatedType(assoc_id, subst) => { + let assoc_id = from_assoc_type_id(*assoc_id); + ( + GenericSubstitution::new(assoc_id.into(), subst.clone(), env), + PathResolution::Def(ModuleDef::TypeAlias(assoc_id.into())), + ) + } + TyKind::FnDef(fn_id, subst) => { + let fn_id = hir_ty::db::InternedCallableDefId::from(*fn_id); + let fn_id = db.lookup_intern_callable_def(fn_id); + let generic_def_id = match fn_id { + CallableDefId::StructId(id) => id.into(), + CallableDefId::FunctionId(id) => id.into(), + CallableDefId::EnumVariantId(_) => return None, + }; + ( + GenericSubstitution::new(generic_def_id, subst.clone(), env), + PathResolution::Def(ModuleDefId::from(fn_id).into()), + ) + } + _ => return None, + }; + if res != expected_resolution { + // The user will not understand where we're coming from. This can happen (I think) with type aliases. + return None; + } + Some(subst) + })(); + Some((res, subst)) } } @@ -1041,26 +1205,35 @@ impl SourceAnalyzer { func: FunctionId, substs: Substitution, ) -> FunctionId { + self.resolve_impl_method_or_trait_def_with_subst(db, func, substs).0 + } + + fn resolve_impl_method_or_trait_def_with_subst( + &self, + db: &dyn HirDatabase, + func: FunctionId, + substs: Substitution, + ) -> (FunctionId, Substitution) { let owner = match self.resolver.body_owner() { Some(it) => it, - None => return func, + None => return (func, substs), }; let env = db.trait_environment_for_body(owner); - db.lookup_impl_method(env, func, substs).0 + db.lookup_impl_method(env, func, substs) } - fn resolve_impl_const_or_trait_def( + fn resolve_impl_const_or_trait_def_with_subst( &self, db: &dyn HirDatabase, const_id: ConstId, subs: Substitution, - ) -> ConstId { + ) -> (ConstId, Substitution) { let owner = match self.resolver.body_owner() { Some(it) => it, - None => return const_id, + None => return (const_id, subs), }; let env = db.trait_environment_for_body(owner); - method_resolution::lookup_impl_const(db, env, const_id, subs).0 + method_resolution::lookup_impl_const(db, env, const_id, subs) } fn lang_trait_fn( @@ -1413,3 +1586,10 @@ pub(crate) fn name_hygiene(db: &dyn HirDatabase, name: InFile<&SyntaxNode>) -> H let ctx = db.lookup_intern_syntax_context(ctx); HygieneId::new(ctx.opaque_and_semitransparent) } + +fn type_of_expr_including_adjust(infer: &InferenceResult, id: ExprId) -> Option<&Ty> { + match infer.expr_adjustments.get(&id).and_then(|adjustments| adjustments.last()) { + Some(adjustment) => Some(&adjustment.target), + None => infer.type_of_expr.get(id), + } +} diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_turbo_fish.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_turbo_fish.rs index 0f6970d9403e9..62700ab1809fb 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_turbo_fish.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_turbo_fish.rs @@ -69,7 +69,7 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti let ident = name_ref.ident_token()?; let def = match NameRefClass::classify(&ctx.sema, &name_ref)? { - NameRefClass::Definition(def) => def, + NameRefClass::Definition(def, _) => def, NameRefClass::FieldShorthand { .. } | NameRefClass::ExternCrateShorthand { .. } => { return None } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_match_to_let_else.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_match_to_let_else.rs index c7f41ffce046e..fd159eb824d6d 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_match_to_let_else.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_match_to_let_else.rs @@ -103,7 +103,7 @@ fn find_extracted_variable(ctx: &AssistContext<'_>, arm: &ast::MatchArm) -> Opti ast::Expr::PathExpr(path) => { let name_ref = path.syntax().descendants().find_map(ast::NameRef::cast)?; match NameRefClass::classify(&ctx.sema, &name_ref)? { - NameRefClass::Definition(Definition::Local(local)) => { + NameRefClass::Definition(Definition::Local(local), _) => { let source = local.sources(ctx.db()).into_iter().map(|x| x.into_ident_pat()?.name()); source.collect() diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs index 83f4a6b123c1e..3c84f83906a9b 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs @@ -163,8 +163,8 @@ fn edit_struct_references( // this also includes method calls like Foo::new(42), we should skip them if let Some(name_ref) = path.segment().and_then(|s| s.name_ref()) { match NameRefClass::classify(&ctx.sema, &name_ref) { - Some(NameRefClass::Definition(Definition::SelfType(_))) => {}, - Some(NameRefClass::Definition(def)) if def == strukt_def => {}, + Some(NameRefClass::Definition(Definition::SelfType(_), _)) => {}, + Some(NameRefClass::Definition(def, _)) if def == strukt_def => {}, _ => return None, }; } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/expand_glob_import.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/expand_glob_import.rs index 3d6d37ad93d26..094fdc46eb762 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/expand_glob_import.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/expand_glob_import.rs @@ -275,6 +275,7 @@ fn find_imported_defs(ctx: &AssistContext<'_>, star: SyntaxToken) -> Option Some(def), _ => None, }) 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 438769a0a875f..f9e54ca35c2c0 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 @@ -800,8 +800,8 @@ impl FunctionBody { let local_ref = match name_ref.and_then(|name_ref| NameRefClass::classify(sema, &name_ref)) { Some( - NameRefClass::Definition(Definition::Local(local_ref)) - | NameRefClass::FieldShorthand { local_ref, field_ref: _ }, + NameRefClass::Definition(Definition::Local(local_ref), _) + | NameRefClass::FieldShorthand { local_ref, field_ref: _, adt_subst: _ }, ) => local_ref, _ => return, }; diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_module.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_module.rs index e4cba666af749..6e3be0ce69279 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_module.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_module.rs @@ -425,7 +425,9 @@ impl Module { }) } else if let Some(name_ref) = ast::NameRef::cast(x) { NameRefClass::classify(&ctx.sema, &name_ref).and_then(|nc| match nc { - NameRefClass::Definition(def) => Some((name_ref.syntax().clone(), def)), + NameRefClass::Definition(def, _) => { + Some((name_ref.syntax().clone(), def)) + } _ => None, }) } else { diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_constant.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_constant.rs index 25076dd5255e5..7f7db07152d34 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_constant.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_constant.rs @@ -64,7 +64,7 @@ pub(crate) fn generate_constant(acc: &mut Assists, ctx: &AssistContext<'_>) -> O let name_ref_value = name_ref?; let name_ref_class = NameRefClass::classify(&ctx.sema, &name_ref_value); match name_ref_class { - Some(NameRefClass::Definition(Definition::Module(m))) => { + Some(NameRefClass::Definition(Definition::Module(m), _)) => { if !m.visibility(ctx.sema.db).is_visible_from(ctx.sema.db, constant_module.into()) { return None; } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs index 7b95c124e62d3..8f5daa4125a31 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs @@ -1077,7 +1077,7 @@ fn fn_arg_name(sema: &Semantics<'_, RootDatabase>, arg_expr: &ast::Expr) -> Stri .filter_map(ast::NameRef::cast) .filter(|name| name.ident_token().is_some()) .last()?; - if let Some(NameRefClass::Definition(Definition::Const(_) | Definition::Static(_))) = + if let Some(NameRefClass::Definition(Definition::Const(_) | Definition::Static(_), _)) = NameRefClass::classify(sema, &name_ref) { return Some(name_ref.to_string().to_lowercase()); 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 932ca373020d5..73b73736ce88b 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/defs.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/defs.rs @@ -13,9 +13,10 @@ use either::Either; use hir::{ Adt, AsAssocItem, AsExternAssocItem, AssocItem, AttributeTemplate, BuiltinAttr, BuiltinType, Const, Crate, DefWithBody, DeriveHelper, DocLinkDef, ExternAssocItem, ExternCrateDecl, Field, - Function, GenericParam, HasVisibility, HirDisplay, Impl, InlineAsmOperand, Label, Local, Macro, - Module, ModuleDef, Name, PathResolution, Semantics, Static, StaticLifetime, Struct, ToolModule, - Trait, TraitAlias, TupleField, TypeAlias, Variant, VariantDef, Visibility, + Function, GenericParam, GenericSubstitution, HasVisibility, HirDisplay, Impl, InlineAsmOperand, + Label, Local, Macro, Module, ModuleDef, Name, PathResolution, Semantics, Static, + StaticLifetime, Struct, ToolModule, Trait, TraitAlias, TupleField, TypeAlias, Variant, + VariantDef, Visibility, }; use span::Edition; use stdx::{format_to, impl_from}; @@ -359,24 +360,32 @@ impl IdentClass { .or_else(|| NameClass::classify_lifetime(sema, lifetime).map(IdentClass::NameClass)) } - pub fn definitions(self) -> ArrayVec { + pub fn definitions(self) -> ArrayVec<(Definition, Option), 2> { let mut res = ArrayVec::new(); match self { IdentClass::NameClass(NameClass::Definition(it) | NameClass::ConstReference(it)) => { - res.push(it) + res.push((it, None)) } - IdentClass::NameClass(NameClass::PatFieldShorthand { local_def, field_ref }) => { - res.push(Definition::Local(local_def)); - res.push(Definition::Field(field_ref)); + IdentClass::NameClass(NameClass::PatFieldShorthand { + local_def, + field_ref, + adt_subst, + }) => { + res.push((Definition::Local(local_def), None)); + res.push((Definition::Field(field_ref), Some(adt_subst))); } - IdentClass::NameRefClass(NameRefClass::Definition(it)) => res.push(it), - IdentClass::NameRefClass(NameRefClass::FieldShorthand { local_ref, field_ref }) => { - res.push(Definition::Local(local_ref)); - res.push(Definition::Field(field_ref)); + IdentClass::NameRefClass(NameRefClass::Definition(it, subst)) => res.push((it, subst)), + IdentClass::NameRefClass(NameRefClass::FieldShorthand { + local_ref, + field_ref, + adt_subst, + }) => { + res.push((Definition::Local(local_ref), None)); + res.push((Definition::Field(field_ref), Some(adt_subst))); } IdentClass::NameRefClass(NameRefClass::ExternCrateShorthand { decl, krate }) => { - res.push(Definition::ExternCrateDecl(decl)); - res.push(Definition::Module(krate.root_module())); + res.push((Definition::ExternCrateDecl(decl), None)); + res.push((Definition::Module(krate.root_module()), None)); } IdentClass::Operator( OperatorClass::Await(func) @@ -384,9 +393,9 @@ impl IdentClass { | OperatorClass::Bin(func) | OperatorClass::Index(func) | OperatorClass::Try(func), - ) => res.push(Definition::Function(func)), + ) => res.push((Definition::Function(func), None)), IdentClass::Operator(OperatorClass::Range(struct0)) => { - res.push(Definition::Adt(Adt::Struct(struct0))) + res.push((Definition::Adt(Adt::Struct(struct0)), None)) } } res @@ -398,12 +407,20 @@ impl IdentClass { IdentClass::NameClass(NameClass::Definition(it) | NameClass::ConstReference(it)) => { res.push(it) } - IdentClass::NameClass(NameClass::PatFieldShorthand { local_def, field_ref }) => { + IdentClass::NameClass(NameClass::PatFieldShorthand { + local_def, + field_ref, + adt_subst: _, + }) => { res.push(Definition::Local(local_def)); res.push(Definition::Field(field_ref)); } - IdentClass::NameRefClass(NameRefClass::Definition(it)) => res.push(it), - IdentClass::NameRefClass(NameRefClass::FieldShorthand { local_ref, field_ref }) => { + IdentClass::NameRefClass(NameRefClass::Definition(it, _)) => res.push(it), + IdentClass::NameRefClass(NameRefClass::FieldShorthand { + local_ref, + field_ref, + adt_subst: _, + }) => { res.push(Definition::Local(local_ref)); res.push(Definition::Field(field_ref)); } @@ -437,6 +454,7 @@ pub enum NameClass { PatFieldShorthand { local_def: Local, field_ref: Field, + adt_subst: GenericSubstitution, }, } @@ -446,7 +464,7 @@ impl NameClass { let res = match self { NameClass::Definition(it) => it, NameClass::ConstReference(_) => return None, - NameClass::PatFieldShorthand { local_def, field_ref: _ } => { + NameClass::PatFieldShorthand { local_def, field_ref: _, adt_subst: _ } => { Definition::Local(local_def) } }; @@ -517,10 +535,13 @@ impl NameClass { let pat_parent = ident_pat.syntax().parent(); if let Some(record_pat_field) = pat_parent.and_then(ast::RecordPatField::cast) { if record_pat_field.name_ref().is_none() { - if let Some((field, _)) = sema.resolve_record_pat_field(&record_pat_field) { + if let Some((field, _, adt_subst)) = + sema.resolve_record_pat_field_with_subst(&record_pat_field) + { return Some(NameClass::PatFieldShorthand { local_def: local, field_ref: field, + adt_subst, }); } } @@ -629,10 +650,11 @@ impl OperatorClass { /// reference to point to two different defs. #[derive(Debug)] pub enum NameRefClass { - Definition(Definition), + Definition(Definition, Option), FieldShorthand { local_ref: Local, field_ref: Field, + adt_subst: GenericSubstitution, }, /// The specific situation where we have an extern crate decl without a rename /// Here we have both a declaration and a reference. @@ -657,12 +679,16 @@ impl NameRefClass { let parent = name_ref.syntax().parent()?; if let Some(record_field) = ast::RecordExprField::for_field_name(name_ref) { - if let Some((field, local, _)) = sema.resolve_record_field(&record_field) { + if let Some((field, local, _, adt_subst)) = + sema.resolve_record_field_with_substitution(&record_field) + { let res = match local { - None => NameRefClass::Definition(Definition::Field(field)), - Some(local) => { - NameRefClass::FieldShorthand { field_ref: field, local_ref: local } - } + None => NameRefClass::Definition(Definition::Field(field), Some(adt_subst)), + Some(local) => NameRefClass::FieldShorthand { + field_ref: field, + local_ref: local, + adt_subst, + }, }; return Some(res); } @@ -674,44 +700,43 @@ impl NameRefClass { // Only use this to resolve to macro calls for last segments as qualifiers resolve // to modules below. if let Some(macro_def) = sema.resolve_macro_call(¯o_call) { - return Some(NameRefClass::Definition(Definition::Macro(macro_def))); + return Some(NameRefClass::Definition(Definition::Macro(macro_def), None)); } } } - return sema.resolve_path(&path).map(Into::into).map(NameRefClass::Definition); + return sema + .resolve_path_with_subst(&path) + .map(|(res, subst)| NameRefClass::Definition(res.into(), subst)); } match_ast! { match parent { ast::MethodCallExpr(method_call) => { sema.resolve_method_call_fallback(&method_call) - .map(|it| { - it.map_left(Definition::Function) - .map_right(Definition::Field) - .either(NameRefClass::Definition, NameRefClass::Definition) + .map(|(def, subst)| { + match def { + Either::Left(def) => NameRefClass::Definition(def.into(), subst), + Either::Right(def) => NameRefClass::Definition(def.into(), subst), + } }) }, ast::FieldExpr(field_expr) => { sema.resolve_field_fallback(&field_expr) - .map(|it| { - NameRefClass::Definition(match it { - Either::Left(Either::Left(field)) => Definition::Field(field), - Either::Left(Either::Right(field)) => Definition::TupleField(field), - Either::Right(fun) => Definition::Function(fun), + .map(|(def, subst)| { + match def { + Either::Left(Either::Left(def)) => NameRefClass::Definition(def.into(), subst), + Either::Left(Either::Right(def)) => NameRefClass::Definition(Definition::TupleField(def), subst), + Either::Right(def) => NameRefClass::Definition(def.into(), subst), + } }) - }) }, ast::RecordPatField(record_pat_field) => { - sema.resolve_record_pat_field(&record_pat_field) - .map(|(field, ..)|field) - .map(Definition::Field) - .map(NameRefClass::Definition) + sema.resolve_record_pat_field_with_subst(&record_pat_field) + .map(|(field, _, subst)| NameRefClass::Definition(Definition::Field(field), Some(subst))) }, ast::RecordExprField(record_expr_field) => { - sema.resolve_record_field(&record_expr_field) - .map(|(field, ..)|field) - .map(Definition::Field) - .map(NameRefClass::Definition) + sema.resolve_record_field_with_substitution(&record_expr_field) + .map(|(field, _, _, subst)| NameRefClass::Definition(Definition::Field(field), Some(subst))) }, ast::AssocTypeArg(_) => { // `Trait` @@ -728,28 +753,30 @@ impl NameRefClass { }) .find(|alias| alias.name(sema.db).eq_ident(name_ref.text().as_str())) { - return Some(NameRefClass::Definition(Definition::TypeAlias(ty))); + // No substitution, this can only occur in type position. + return Some(NameRefClass::Definition(Definition::TypeAlias(ty), None)); } } None }, ast::UseBoundGenericArgs(_) => { + // No substitution, this can only occur in type position. sema.resolve_use_type_arg(name_ref) .map(GenericParam::TypeParam) .map(Definition::GenericParam) - .map(NameRefClass::Definition) + .map(|it| NameRefClass::Definition(it, None)) }, ast::ExternCrate(extern_crate_ast) => { let extern_crate = sema.to_def(&extern_crate_ast)?; let krate = extern_crate.resolved_crate(sema.db)?; Some(if extern_crate_ast.rename().is_some() { - NameRefClass::Definition(Definition::Module(krate.root_module())) + NameRefClass::Definition(Definition::Module(krate.root_module()), None) } else { NameRefClass::ExternCrateShorthand { krate, decl: extern_crate } }) }, ast::AsmRegSpec(_) => { - Some(NameRefClass::Definition(Definition::InlineAsmRegOrRegClass(()))) + Some(NameRefClass::Definition(Definition::InlineAsmRegOrRegClass(()), None)) }, _ => None } @@ -762,13 +789,17 @@ impl NameRefClass { ) -> Option { let _p = tracing::info_span!("NameRefClass::classify_lifetime", ?lifetime).entered(); if lifetime.text() == "'static" { - return Some(NameRefClass::Definition(Definition::BuiltinLifetime(StaticLifetime))); + return Some(NameRefClass::Definition( + Definition::BuiltinLifetime(StaticLifetime), + None, + )); } let parent = lifetime.syntax().parent()?; match parent.kind() { - SyntaxKind::BREAK_EXPR | SyntaxKind::CONTINUE_EXPR => { - sema.resolve_label(lifetime).map(Definition::Label).map(NameRefClass::Definition) - } + SyntaxKind::BREAK_EXPR | SyntaxKind::CONTINUE_EXPR => sema + .resolve_label(lifetime) + .map(Definition::Label) + .map(|it| NameRefClass::Definition(it, None)), SyntaxKind::LIFETIME_ARG | SyntaxKind::USE_BOUND_GENERIC_ARGS | SyntaxKind::SELF_PARAM @@ -778,7 +809,7 @@ impl NameRefClass { .resolve_lifetime_param(lifetime) .map(GenericParam::LifetimeParam) .map(Definition::GenericParam) - .map(NameRefClass::Definition), + .map(|it| NameRefClass::Definition(it, None)), _ => None, } } 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 c5215eb3e6303..24b4d26ec7204 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/search.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/search.rs @@ -1081,7 +1081,7 @@ impl<'a> FindUsages<'a> { }; match NameRefClass::classify(self.sema, name_ref) { - Some(NameRefClass::Definition(Definition::SelfType(impl_))) + Some(NameRefClass::Definition(Definition::SelfType(impl_), _)) if ty_eq(impl_.self_ty(self.sema.db)) => { let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax()); @@ -1102,7 +1102,7 @@ impl<'a> FindUsages<'a> { sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool, ) -> bool { match NameRefClass::classify(self.sema, name_ref) { - Some(NameRefClass::Definition(def @ Definition::Module(_))) if def == self.def => { + Some(NameRefClass::Definition(def @ Definition::Module(_), _)) if def == self.def => { let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax()); let category = if is_name_ref_in_import(name_ref) { ReferenceCategory::IMPORT @@ -1147,7 +1147,7 @@ impl<'a> FindUsages<'a> { sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool, ) -> bool { match NameRefClass::classify_lifetime(self.sema, lifetime) { - Some(NameRefClass::Definition(def)) if def == self.def => { + Some(NameRefClass::Definition(def, _)) if def == self.def => { let FileRange { file_id, range } = self.sema.original_range(lifetime.syntax()); let reference = FileReference { range, @@ -1166,7 +1166,7 @@ impl<'a> FindUsages<'a> { sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool, ) -> bool { match NameRefClass::classify(self.sema, name_ref) { - Some(NameRefClass::Definition(def)) + Some(NameRefClass::Definition(def, _)) if self.def == def // is our def a trait assoc item? then we want to find all assoc items from trait impls of our trait || matches!(self.assoc_item_container, Some(hir::AssocItemContainer::Trait(_))) @@ -1182,7 +1182,7 @@ impl<'a> FindUsages<'a> { } // FIXME: special case type aliases, we can't filter between impl and trait defs here as we lack the substitutions // so we always resolve all assoc type aliases to both their trait def and impl defs - Some(NameRefClass::Definition(def)) + Some(NameRefClass::Definition(def, _)) if self.assoc_item_container.is_some() && matches!(self.def, Definition::TypeAlias(_)) && convert_to_def_in_trait(self.sema.db, def) @@ -1196,7 +1196,7 @@ impl<'a> FindUsages<'a> { }; sink(file_id, reference) } - Some(NameRefClass::Definition(def)) if self.include_self_kw_refs.is_some() => { + Some(NameRefClass::Definition(def, _)) if self.include_self_kw_refs.is_some() => { if self.include_self_kw_refs == def_to_ty(self.sema, &def) { let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax()); let reference = FileReference { @@ -1209,7 +1209,11 @@ impl<'a> FindUsages<'a> { false } } - Some(NameRefClass::FieldShorthand { local_ref: local, field_ref: field }) => { + Some(NameRefClass::FieldShorthand { + local_ref: local, + field_ref: field, + adt_subst: _, + }) => { let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax()); let field = Definition::Field(field); @@ -1240,7 +1244,7 @@ impl<'a> FindUsages<'a> { sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool, ) -> bool { match NameClass::classify(self.sema, name) { - Some(NameClass::PatFieldShorthand { local_def: _, field_ref }) + Some(NameClass::PatFieldShorthand { local_def: _, field_ref, adt_subst: _ }) if matches!( self.def, Definition::Field(_) if Definition::Field(field_ref) == self.def ) => 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 e5b4ed17b2a46..8066894cd8372 100644 --- a/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs +++ b/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs @@ -47,7 +47,7 @@ pub(crate) fn incoming_calls( .find_nodes_at_offset_with_descend(file, offset) .filter_map(move |node| match node { ast::NameLike::NameRef(name_ref) => match NameRefClass::classify(sema, &name_ref)? { - NameRefClass::Definition(def @ Definition::Function(_)) => Some(def), + NameRefClass::Definition(def @ Definition::Function(_), _) => Some(def), _ => None, }, ast::NameLike::Name(name) => match NameClass::classify(sema, &name)? { 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 ea16a11d56d02..72fcac54177f2 100644 --- a/src/tools/rust-analyzer/crates/ide/src/doc_links.rs +++ b/src/tools/rust-analyzer/crates/ide/src/doc_links.rs @@ -147,8 +147,8 @@ pub(crate) fn external_docs( let definition = match_ast! { match node { ast::NameRef(name_ref) => match NameRefClass::classify(sema, &name_ref)? { - NameRefClass::Definition(def) => def, - NameRefClass::FieldShorthand { local_ref: _, field_ref } => { + NameRefClass::Definition(def, _) => def, + NameRefClass::FieldShorthand { local_ref: _, field_ref, adt_subst: _ } => { Definition::Field(field_ref) } NameRefClass::ExternCrateShorthand { decl, .. } => { @@ -157,7 +157,7 @@ pub(crate) fn external_docs( }, ast::Name(name) => match NameClass::classify(sema, &name)? { NameClass::Definition(it) | NameClass::ConstReference(it) => it, - NameClass::PatFieldShorthand { local_def: _, field_ref } => Definition::Field(field_ref), + NameClass::PatFieldShorthand { local_def: _, field_ref, adt_subst: _ } => Definition::Field(field_ref), }, _ => return None } 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 9dacbd8badf3c..7b6a5ef13e524 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs @@ -36,7 +36,7 @@ pub(crate) fn goto_declaration( let def = match_ast! { match parent { ast::NameRef(name_ref) => match NameRefClass::classify(&sema, &name_ref)? { - NameRefClass::Definition(it) => Some(it), + NameRefClass::Definition(it, _) => Some(it), NameRefClass::FieldShorthand { field_ref, .. } => return field_ref.try_to_nav(db), NameRefClass::ExternCrateShorthand { decl, .. } => 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 363f852e0e4be..6c66907ec3efc 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs @@ -103,7 +103,7 @@ pub(crate) fn goto_definition( IdentClass::classify_node(sema, &parent)? .definitions() .into_iter() - .flat_map(|def| { + .flat_map(|(def, _)| { if let Definition::ExternCrateDecl(crate_def) = def { return crate_def .resolved_crate(db) 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 e36c8ee2f3f73..04da1f67e9574 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs @@ -48,7 +48,7 @@ pub(crate) fn goto_implementation( } ast::NameLike::NameRef(name_ref) => NameRefClass::classify(&sema, name_ref) .and_then(|class| match class { - NameRefClass::Definition(def) => Some(def), + NameRefClass::Definition(def, _) => Some(def), NameRefClass::FieldShorthand { .. } | NameRefClass::ExternCrateShorthand { .. } => None, }), diff --git a/src/tools/rust-analyzer/crates/ide/src/hover.rs b/src/tools/rust-analyzer/crates/ide/src/hover.rs index 332dfacbb43fe..e3d50fdfa9089 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover.rs @@ -6,7 +6,7 @@ mod tests; use std::{iter, ops::Not}; use either::Either; -use hir::{db::DefDatabase, HasCrate, HasSource, LangItem, Semantics}; +use hir::{db::DefDatabase, GenericSubstitution, HasCrate, HasSource, LangItem, Semantics}; use ide_db::{ defs::{Definition, IdentClass, NameRefClass, OperatorClass}, famous_defs::FamousDefs, @@ -35,6 +35,14 @@ pub struct HoverConfig { pub max_trait_assoc_items_count: Option, pub max_fields_count: Option, pub max_enum_variants_count: Option, + pub max_subst_ty_len: SubstTyLen, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum SubstTyLen { + Unlimited, + LimitTo(usize), + Hide, } #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -158,7 +166,8 @@ fn hover_offset( if let Some(doc_comment) = token_as_doc_comment(&original_token) { cov_mark::hit!(no_highlight_on_comment_hover); return doc_comment.get_definition_with_descend_at(sema, offset, |def, node, range| { - let res = hover_for_definition(sema, file_id, def, &node, None, false, config, edition); + let res = + hover_for_definition(sema, file_id, def, None, &node, None, false, config, edition); Some(RangeInfo::new(range, res)) }); } @@ -170,6 +179,7 @@ fn hover_offset( sema, file_id, Definition::from(resolution?), + None, &original_token.parent()?, None, false, @@ -217,7 +227,7 @@ fn hover_offset( { if let Some(macro_) = sema.resolve_macro_call(¯o_call) { break 'a vec![( - Definition::Macro(macro_), + (Definition::Macro(macro_), None), sema.resolve_macro_call_arm(¯o_call), false, node, @@ -236,7 +246,7 @@ fn hover_offset( decl, .. }) => { - vec![(Definition::ExternCrateDecl(decl), None, false, node)] + vec![((Definition::ExternCrateDecl(decl), None), None, false, node)] } class => { @@ -252,12 +262,13 @@ fn hover_offset( } } .into_iter() - .unique_by(|&(def, _, _, _)| def) - .map(|(def, macro_arm, hovered_definition, node)| { + .unique_by(|&((def, _), _, _, _)| def) + .map(|((def, subst), macro_arm, hovered_definition, node)| { hover_for_definition( sema, file_id, def, + subst, &node, macro_arm, hovered_definition, @@ -381,6 +392,7 @@ pub(crate) fn hover_for_definition( sema: &Semantics<'_, RootDatabase>, file_id: FileId, def: Definition, + subst: Option, scope_node: &SyntaxNode, macro_arm: Option, hovered_definition: bool, @@ -408,6 +420,7 @@ pub(crate) fn hover_for_definition( _ => None, }; let notable_traits = def_ty.map(|ty| notable_traits(db, &ty)).unwrap_or_default(); + let subst_types = subst.map(|subst| subst.types(db)); let markup = render::definition( sema.db, @@ -416,6 +429,7 @@ pub(crate) fn hover_for_definition( ¬able_traits, macro_arm, hovered_definition, + subst_types, config, edition, ); 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 e617d462ecd68..8c5c27c47cb52 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs @@ -5,7 +5,7 @@ use either::Either; use hir::{ db::ExpandDatabase, Adt, AsAssocItem, AsExternAssocItem, AssocItemContainer, CaptureKind, DynCompatibilityViolation, HasCrate, HasSource, HirDisplay, Layout, LayoutError, - MethodViolationCode, Name, Semantics, Trait, Type, TypeInfo, + MethodViolationCode, Name, Semantics, Symbol, Trait, Type, TypeInfo, }; use ide_db::{ base_db::SourceDatabase, @@ -27,7 +27,7 @@ use syntax::{algo, ast, match_ast, AstNode, AstToken, Direction, SyntaxToken, T} use crate::{ doc_links::{remove_links, rewrite_links}, - hover::{notable_traits, walk_and_push_ty}, + hover::{notable_traits, walk_and_push_ty, SubstTyLen}, interpret::render_const_eval_error, HoverAction, HoverConfig, HoverResult, Markup, MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, @@ -274,7 +274,7 @@ pub(super) fn keyword( let markup = process_markup( sema.db, Definition::Module(doc_owner), - &markup(Some(docs.into()), description, None, None), + &markup(Some(docs.into()), description, None, None, String::new()), config, ); Some(HoverResult { markup, actions }) @@ -421,6 +421,7 @@ pub(super) fn definition( notable_traits: &[(Trait, Vec<(Option, Name)>)], macro_arm: Option, hovered_definition: bool, + subst_types: Option>, config: &HoverConfig, edition: Edition, ) -> Markup { @@ -604,7 +605,38 @@ pub(super) fn definition( desc.push_str(&value); } - markup(docs.map(Into::into), desc, extra.is_empty().not().then_some(extra), mod_path) + let subst_types = match config.max_subst_ty_len { + SubstTyLen::Hide => String::new(), + SubstTyLen::LimitTo(_) | SubstTyLen::Unlimited => { + let limit = if let SubstTyLen::LimitTo(limit) = config.max_subst_ty_len { + Some(limit) + } else { + None + }; + subst_types + .map(|subst_type| { + subst_type + .iter() + .filter(|(_, ty)| !ty.is_unknown()) + .format_with(", ", |(name, ty), fmt| { + fmt(&format_args!( + "`{name}` = `{}`", + ty.display_truncated(db, limit, edition) + )) + }) + .to_string() + }) + .unwrap_or_default() + } + }; + + markup( + docs.map(Into::into), + desc, + extra.is_empty().not().then_some(extra), + mod_path, + subst_types, + ) } pub(super) fn literal( @@ -872,6 +904,7 @@ fn markup( rust: String, extra: Option, mod_path: Option, + subst_types: String, ) -> Markup { let mut buf = String::new(); @@ -886,6 +919,10 @@ fn markup( buf.push_str(&extra); } + if !subst_types.is_empty() { + format_to!(buf, "\n___\n{subst_types}"); + } + if let Some(doc) = docs { format_to!(buf, "\n___\n\n{}", doc); } 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 50d0d4c5df657..cc926a5b568f3 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -20,6 +20,7 @@ const HOVER_BASE_CONFIG: HoverConfig = HoverConfig { max_trait_assoc_items_count: None, max_fields_count: Some(5), max_enum_variants_count: Some(5), + max_subst_ty_len: super::SubstTyLen::Unlimited, }; fn check_hover_no_result(ra_fixture: &str) { @@ -5176,6 +5177,10 @@ fn main() { --- + `Self` = `()` + + --- + false "#]], ); @@ -5208,6 +5213,10 @@ fn main() { --- + `Self` = `i32` + + --- + false "#]], ); @@ -9501,3 +9510,409 @@ fn main() { "#]], ); } + +#[test] +fn subst_fn() { + check( + r#" +struct Foo(T); +impl Foo { + fn foo(v: T, u: U) {} +} + +fn bar() { + Foo::fo$0o(123, false); +} + "#, + expect![[r#" + *foo* + + ```rust + ra_test_fixture::Foo + ``` + + ```rust + impl Foo + fn foo(v: T, u: U) + ``` + + --- + + `T` = `i32`, `U` = `bool` + "#]], + ); + check( + r#" +fn foo(v: T) {} + +fn bar() { + fo$0o(123); +} + "#, + expect![[r#" + *foo* + + ```rust + ra_test_fixture + ``` + + ```rust + fn foo(v: T) + ``` + + --- + + `T` = `i32` + "#]], + ); +} + +#[test] +fn subst_record_constructor() { + check( + r#" +struct Foo { field: T } + +fn bar() { + let v = $0Foo { field: 123 }; +} + "#, + expect![[r#" + *Foo* + + ```rust + ra_test_fixture + ``` + + ```rust + struct Foo { + field: T, + } + ``` + + --- + + `T` = `i32` + "#]], + ); + check( + r#" +struct Foo { field: T } + +fn bar() { + let v = Foo { field: 123 }; + let $0Foo { field: _ } = v; +} + "#, + expect![[r#" + *Foo* + + ```rust + ra_test_fixture + ``` + + ```rust + struct Foo { + field: T, + } + ``` + + --- + + `T` = `i32` + "#]], + ); +} + +#[test] +fn subst_method_call() { + check( + r#" +struct Foo(T); + +impl Foo { + fn bar(self, v: T) {} +} + +fn baz() { + Foo(123).bar$0("hello"); +} + "#, + expect![[r#" + *bar* + + ```rust + ra_test_fixture::Foo + ``` + + ```rust + impl Foo + fn bar(self, v: T) + ``` + + --- + + `U` = `i32`, `T` = `&str` + "#]], + ); +} + +#[test] +fn subst_type_alias_do_not_work() { + // It is very hard to support subst for type aliases properly in all places because they are eagerly evaluated. + // We can show the user the subst for the underlying type instead but that'll be very confusing. + check( + r#" +struct Foo { a: T, b: U } +type Alias = Foo; + +fn foo() { + let _ = Alias$0 { a: true, b: 123 }; +} + "#, + expect![[r#" + *Alias* + + ```rust + ra_test_fixture + ``` + + ```rust + type Alias = Foo + ``` + "#]], + ); +} + +#[test] +fn subst_self() { + check( + r#" +trait Trait { + fn foo(&self, v: U) {} +} +struct Struct(T); +impl Trait for Struct {} + +fn bar() { + Struct(123).foo$0(true); +} + "#, + expect![[r#" + *foo* + + ```rust + ra_test_fixture::Trait + ``` + + ```rust + trait Trait + fn foo(&self, v: U) + ``` + + --- + + `Self` = `Struct`, `T` = `i64`, `U` = `bool` + "#]], + ); +} + +#[test] +fn subst_with_lifetimes_and_consts() { + check( + r#" +struct Foo<'a, const N: usize, T>(&[T; N]); + +impl<'a, T, const N: usize> Foo<'a, N, T> { + fn foo<'b, const Z: u32, U>(&self, v: U) {} +} + +fn bar() { + Foo(&[1i8]).fo$0o::<456, _>(""); +} + "#, + expect![[r#" + *foo* + + ```rust + ra_test_fixture::Foo + ``` + + ```rust + impl<'a, T, const N: usize> Foo<'a, N, T> + fn foo<'b, const Z: u32, U>(&self, v: U) + ``` + + --- + + `T` = `i8`, `U` = `&str` + "#]], + ); +} + +#[test] +fn subst_field() { + check( + r#" +struct Foo { field: T } + +fn bar() { + let v = Foo { $0field: 123 }; +} + "#, + expect![[r#" + *field* + + ```rust + ra_test_fixture::Foo + ``` + + ```rust + field: T + ``` + + --- + + `T` = `i32` + "#]], + ); + check( + r#" +struct Foo { field: T } + +fn bar() { + let field = 123; + let v = Foo { field$0 }; +} + "#, + expect![[r#" + *field* + + ```rust + let field: i32 + ``` + --- + + ```rust + ra_test_fixture::Foo + ``` + + ```rust + field: T + ``` + + --- + + `T` = `i32` + "#]], + ); + check( + r#" +struct Foo { field: T } + +fn bar() { + let v = Foo { field: 123 }; + let Foo { field$0 } = v; +} + "#, + expect![[r#" + *field* + + ```rust + let field: i32 + ``` + + --- + + size = 4, align = 4 + --- + + ```rust + ra_test_fixture::Foo + ``` + + ```rust + field: T + ``` + + --- + + `T` = `i32` + "#]], + ); + check( + r#" +struct Foo { field: T } + +fn bar() { + let v = Foo { field: 123 }; + let Foo { field$0: _ } = v; +} + "#, + expect![[r#" + *field* + + ```rust + ra_test_fixture::Foo + ``` + + ```rust + field: T + ``` + + --- + + `T` = `i32` + "#]], + ); + check( + r#" +struct Foo { field: T } + +fn bar() { + let v = Foo { field: 123 }; + let _ = (&v).$0field; +} + "#, + expect![[r#" + *field* + + ```rust + ra_test_fixture::Foo + ``` + + ```rust + field: T + ``` + + --- + + `T` = `i32` + "#]], + ); + check( + r#" +struct Foo(T); + +fn bar() { + let v = Foo(123); + let _ = v.$00; +} + "#, + expect![[r#" + *0* + + ```rust + ra_test_fixture::Foo + ``` + + ```rust + 0: T + ``` + + --- + + `T` = `i32` + "#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs index c13fc843568c5..fe2760d2ba6fe 100644 --- a/src/tools/rust-analyzer/crates/ide/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide/src/lib.rs @@ -86,7 +86,7 @@ pub use crate::{ highlight_related::{HighlightRelatedConfig, HighlightedRange}, hover::{ HoverAction, HoverConfig, HoverDocFormat, HoverGotoTypeData, HoverResult, - MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, + MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, SubstTyLen, }, inlay_hints::{ AdjustmentHints, AdjustmentHintsMode, ClosureReturnTypeHints, DiscriminantHints, diff --git a/src/tools/rust-analyzer/crates/ide/src/references.rs b/src/tools/rust-analyzer/crates/ide/src/references.rs index 339315db57109..04b5f27703709 100644 --- a/src/tools/rust-analyzer/crates/ide/src/references.rs +++ b/src/tools/rust-analyzer/crates/ide/src/references.rs @@ -112,7 +112,7 @@ pub(crate) fn find_all_refs( Some(name) => { let def = match NameClass::classify(sema, &name)? { NameClass::Definition(it) | NameClass::ConstReference(it) => it, - NameClass::PatFieldShorthand { local_def: _, field_ref } => { + NameClass::PatFieldShorthand { local_def: _, field_ref, adt_subst: _ } => { Definition::Field(field_ref) } }; @@ -156,10 +156,12 @@ pub(crate) fn find_defs<'a>( let def = match name_like { ast::NameLike::NameRef(name_ref) => { match NameRefClass::classify(sema, &name_ref)? { - NameRefClass::Definition(def) => def, - NameRefClass::FieldShorthand { local_ref, field_ref: _ } => { - Definition::Local(local_ref) - } + NameRefClass::Definition(def, _) => def, + NameRefClass::FieldShorthand { + local_ref, + field_ref: _, + adt_subst: _, + } => Definition::Local(local_ref), NameRefClass::ExternCrateShorthand { decl, .. } => { Definition::ExternCrateDecl(decl) } @@ -167,14 +169,14 @@ pub(crate) fn find_defs<'a>( } ast::NameLike::Name(name) => match NameClass::classify(sema, &name)? { NameClass::Definition(it) | NameClass::ConstReference(it) => it, - NameClass::PatFieldShorthand { local_def, field_ref: _ } => { + NameClass::PatFieldShorthand { local_def, field_ref: _, adt_subst: _ } => { Definition::Local(local_def) } }, ast::NameLike::Lifetime(lifetime) => { NameRefClass::classify_lifetime(sema, &lifetime) .and_then(|class| match class { - NameRefClass::Definition(it) => Some(it), + NameRefClass::Definition(it, _) => Some(it), _ => None, }) .or_else(|| { diff --git a/src/tools/rust-analyzer/crates/ide/src/rename.rs b/src/tools/rust-analyzer/crates/ide/src/rename.rs index a9519c03b3226..b146df6d0b4f9 100644 --- a/src/tools/rust-analyzer/crates/ide/src/rename.rs +++ b/src/tools/rust-analyzer/crates/ide/src/rename.rs @@ -242,7 +242,7 @@ fn find_definitions( ast::NameLike::Name(name) => NameClass::classify(sema, name) .map(|class| match class { NameClass::Definition(it) | NameClass::ConstReference(it) => it, - NameClass::PatFieldShorthand { local_def, field_ref: _ } => { + NameClass::PatFieldShorthand { local_def, field_ref: _, adt_subst: _ } => { Definition::Local(local_def) } }) @@ -250,8 +250,8 @@ fn find_definitions( ast::NameLike::NameRef(name_ref) => { NameRefClass::classify(sema, name_ref) .map(|class| match class { - NameRefClass::Definition(def) => def, - NameRefClass::FieldShorthand { local_ref, field_ref: _ } => { + NameRefClass::Definition(def, _) => def, + NameRefClass::FieldShorthand { local_ref, field_ref: _, adt_subst: _ } => { Definition::Local(local_ref) } NameRefClass::ExternCrateShorthand { decl, .. } => { @@ -276,7 +276,7 @@ fn find_definitions( ast::NameLike::Lifetime(lifetime) => { NameRefClass::classify_lifetime(sema, lifetime) .and_then(|class| match class { - NameRefClass::Definition(def) => Some(def), + NameRefClass::Definition(def, _) => Some(def), _ => None, }) .or_else(|| { 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 0f4b5e7d87a3a..53eeffaf97d0c 100644 --- a/src/tools/rust-analyzer/crates/ide/src/static_index.rs +++ b/src/tools/rust-analyzer/crates/ide/src/static_index.rs @@ -13,11 +13,10 @@ use ide_db::{ use span::Edition; use syntax::{AstNode, SyntaxKind::*, SyntaxNode, TextRange, T}; -use crate::inlay_hints::InlayFieldsToResolve; use crate::navigation_target::UpmappingResult; use crate::{ - hover::hover_for_definition, - inlay_hints::AdjustmentHintsMode, + hover::{hover_for_definition, SubstTyLen}, + inlay_hints::{AdjustmentHintsMode, InlayFieldsToResolve}, moniker::{def_to_kind, def_to_moniker, MonikerResult, SymbolInformationKind}, parent_module::crates_for, Analysis, Fold, HoverConfig, HoverResult, InlayHint, InlayHintsConfig, TryToNav, @@ -186,6 +185,7 @@ impl StaticIndex<'_> { max_trait_assoc_items_count: None, max_fields_count: Some(5), max_enum_variants_count: Some(5), + max_subst_ty_len: SubstTyLen::Unlimited, }; let tokens = tokens.filter(|token| { matches!( @@ -210,6 +210,7 @@ impl StaticIndex<'_> { &sema, file_id, def, + None, &node, None, false, 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 3767a3917ce71..89510e7062eb9 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 @@ -76,7 +76,7 @@ pub(super) fn name_like( Some(IdentClass::NameClass(NameClass::Definition(def))) => { highlight_def(sema, krate, def) | HlMod::Definition } - Some(IdentClass::NameRefClass(NameRefClass::Definition(def))) => { + Some(IdentClass::NameRefClass(NameRefClass::Definition(def, _))) => { highlight_def(sema, krate, def) } // FIXME: Fallback for 'static and '_, as we do not resolve these yet @@ -260,7 +260,7 @@ fn highlight_name_ref( None => return HlTag::UnresolvedReference.into(), }; let mut h = match name_class { - NameRefClass::Definition(def) => { + NameRefClass::Definition(def, _) => { if let Definition::Local(local) = &def { let name = local.name(db); let shadow_count = bindings_shadow_count.entry(name.clone()).or_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 40fd294e72a33..5358452a4ea08 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -50,6 +50,14 @@ mod patch_old_style; // - Don't use abbreviations unless really necessary // - foo_command = overrides the subcommand, foo_overrideCommand allows full overwriting, extra args only applies for foo_command +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum MaxSubstitutionLength { + Hide, + #[serde(untagged)] + Limit(usize), +} + // Defines the server-side configuration of the rust-analyzer. We generate // *parts* of VS Code's `package.json` config from this. Run `cargo test` to // re-generate that file. @@ -119,6 +127,12 @@ config_data! { hover_documentation_keywords_enable: bool = true, /// Use markdown syntax for links on hover. hover_links_enable: bool = true, + /// Whether to show what types are used as generic arguments in calls etc. on hover, and what is their max length to show such types, beyond it they will be shown with ellipsis. + /// + /// This can take three values: `null` means "unlimited", the string `"hide"` means to not show generic substitutions at all, and a number means to limit them to X characters. + /// + /// The default is 20 characters. + hover_maxSubstitutionLength: Option = Some(MaxSubstitutionLength::Limit(20)), /// How to render the align information in a memory layout hover. hover_memoryLayout_alignment: Option = Some(MemoryLayoutHoverRenderKindDef::Hexadecimal), /// Whether to show memory layout data on hover. @@ -1532,6 +1546,11 @@ impl Config { max_trait_assoc_items_count: self.hover_show_traitAssocItems().to_owned(), max_fields_count: self.hover_show_fields().to_owned(), max_enum_variants_count: self.hover_show_enumVariants().to_owned(), + max_subst_ty_len: match self.hover_maxSubstitutionLength() { + Some(MaxSubstitutionLength::Hide) => ide::SubstTyLen::Hide, + Some(MaxSubstitutionLength::Limit(limit)) => ide::SubstTyLen::LimitTo(*limit), + None => ide::SubstTyLen::Unlimited, + }, } } @@ -3433,6 +3452,20 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json "Use `cargo metadata` to query sysroot metadata." ], }, + "Option" => set! { + "anyOf": [ + { + "type": "null" + }, + { + "type": "string", + "enum": ["hide"] + }, + { + "type": "integer" + } + ] + }, _ => panic!("missing entry for {ty}: {default} (field {field})"), } diff --git a/src/tools/rust-analyzer/docs/user/generated_config.adoc b/src/tools/rust-analyzer/docs/user/generated_config.adoc index 1195a85cf70a6..ce3d0e3d8039e 100644 --- a/src/tools/rust-analyzer/docs/user/generated_config.adoc +++ b/src/tools/rust-analyzer/docs/user/generated_config.adoc @@ -512,6 +512,15 @@ Whether to show keyword hover popups. Only applies when -- Use markdown syntax for links on hover. -- +[[rust-analyzer.hover.maxSubstitutionLength]]rust-analyzer.hover.maxSubstitutionLength (default: `20`):: ++ +-- +Whether to show what types are used as generic arguments in calls etc. on hover, and what is their max length to show such types, beyond it they will be shown with ellipsis. + +This can take three values: `null` means "unlimited", the string `"hide"` means to not show generic substitutions at all, and a number means to limit them to X characters. + +The default is 20 characters. +-- [[rust-analyzer.hover.memoryLayout.alignment]]rust-analyzer.hover.memoryLayout.alignment (default: `"hexadecimal"`):: + -- diff --git a/src/tools/rust-analyzer/editors/code/package.json b/src/tools/rust-analyzer/editors/code/package.json index 469c1b458d52a..b1daaff27a2a4 100644 --- a/src/tools/rust-analyzer/editors/code/package.json +++ b/src/tools/rust-analyzer/editors/code/package.json @@ -1530,6 +1530,29 @@ } } }, + { + "title": "hover", + "properties": { + "rust-analyzer.hover.maxSubstitutionLength": { + "markdownDescription": "Whether to show what types are used as generic arguments in calls etc. on hover, and what is their max length to show such types, beyond it they will be shown with ellipsis.\n\nThis can take three values: `null` means \"unlimited\", the string `\"hide\"` means to not show generic substitutions at all, and a number means to limit them to X characters.\n\nThe default is 20 characters.", + "default": 20, + "anyOf": [ + { + "type": "null" + }, + { + "type": "string", + "enum": [ + "hide" + ] + }, + { + "type": "integer" + } + ] + } + } + }, { "title": "hover", "properties": { From 932a6d366b531ecedf5cf3eeb488ce2dafb46bc3 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Wed, 4 Dec 2024 15:52:15 +0200 Subject: [PATCH 002/123] Move ty lowering diagnostic definitions into a separate module To keep them organized. --- .../rust-analyzer/crates/hir-ty/src/infer.rs | 2 +- .../rust-analyzer/crates/hir-ty/src/lib.rs | 6 ++-- .../rust-analyzer/crates/hir-ty/src/lower.rs | 28 ++----------------- .../crates/hir-ty/src/lower/diagnostics.rs | 27 ++++++++++++++++++ 4 files changed, 34 insertions(+), 29 deletions(-) create mode 100644 src/tools/rust-analyzer/crates/hir-ty/src/lower/diagnostics.rs 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 dbee5a1a919fc..5720539d34e3f 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs @@ -58,7 +58,7 @@ use crate::{ fold_tys, generics::Generics, infer::{coerce::CoerceMany, expr::ExprIsRead, unify::InferenceTable}, - lower::{ImplTraitLoweringMode, TyLoweringDiagnostic}, + lower::{diagnostics::TyLoweringDiagnostic, ImplTraitLoweringMode}, mir::MirSpan, to_assoc_type_id, traits::FnTrait, 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 8bb90ca31e471..224fcf313a4b5 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs @@ -88,10 +88,10 @@ pub use infer::{ PointerCast, }; pub use interner::Interner; +pub use lower::diagnostics::*; pub use lower::{ - associated_type_shorthand_candidates, GenericArgsProhibitedReason, ImplTraitLoweringMode, - ParamLoweringMode, TyDefId, TyLoweringContext, TyLoweringDiagnostic, TyLoweringDiagnosticKind, - ValueTyDefId, + associated_type_shorthand_candidates, ImplTraitLoweringMode, ParamLoweringMode, TyDefId, + TyLoweringContext, ValueTyDefId, }; pub use mapping::{ from_assoc_type_id, from_chalk_trait_id, from_foreign_def_id, from_placeholder_idx, 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 b23f2749ab285..2610bb704e26d 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs @@ -5,6 +5,8 @@ //! - Building the type for an item: This happens through the `ty` query. //! //! This usually involves resolving names, collecting generic arguments etc. +pub(crate) mod diagnostics; + use std::{ cell::OnceCell, iter, mem, @@ -59,6 +61,7 @@ use crate::{ db::HirDatabase, error_lifetime, generics::{generics, trait_self_param_idx, Generics}, + lower::diagnostics::*, make_binders, mapping::{from_chalk_trait_id, lt_to_placeholder_idx, ToChalk}, static_lifetime, to_assoc_type_id, to_chalk_trait_id, to_placeholder_idx, @@ -102,31 +105,6 @@ impl ImplTraitLoweringState { } } -type TypeSource = Either; - -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct TyLoweringDiagnostic { - pub source: TypeSource, - pub kind: TyLoweringDiagnosticKind, -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum TyLoweringDiagnosticKind { - GenericArgsProhibited { segment: u32, reason: GenericArgsProhibitedReason }, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum GenericArgsProhibitedReason { - Module, - TyParam, - SelfTy, - PrimitiveTy, - /// When there is a generic enum, within the expression `Enum::Variant`, - /// either `Enum` or `Variant` are allowed to have generic arguments, but not both. - // FIXME: This is not used now but it should be. - EnumVariant, -} - #[derive(Debug)] pub struct TyLoweringContext<'a> { pub db: &'a dyn HirDatabase, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lower/diagnostics.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lower/diagnostics.rs new file mode 100644 index 0000000000000..61fedc8c3ac2d --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower/diagnostics.rs @@ -0,0 +1,27 @@ +use either::Either; +use hir_def::type_ref::TypeRefId; + +type TypeSource = Either; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct TyLoweringDiagnostic { + pub source: TypeSource, + pub kind: TyLoweringDiagnosticKind, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum TyLoweringDiagnosticKind { + GenericArgsProhibited { segment: u32, reason: GenericArgsProhibitedReason }, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum GenericArgsProhibitedReason { + Module, + TyParam, + SelfTy, + PrimitiveTy, + /// When there is a generic enum, within the expression `Enum::Variant`, + /// either `Enum` or `Variant` are allowed to have generic arguments, but not both. + // FIXME: This is not used now but it should be. + EnumVariant, +} From b52066619702f8a2a25101e3a6b19df542377bbf Mon Sep 17 00:00:00 2001 From: Giga Bowser <45986823+Giga-Bowser@users.noreply.github.com> Date: Wed, 18 Dec 2024 11:03:19 -0500 Subject: [PATCH 003/123] internal: Standardize how we take iterator parameters in `SyntaxFactory` --- .../src/ast/syntax_factory/constructors.rs | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs index 280c5c25cb950..bea6bfeafcf2a 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -1,6 +1,4 @@ //! Wrappers over [`make`] constructors -use itertools::Itertools; - use crate::{ ast::{self, make, HasGenericParams, HasName, HasTypeBounds, HasVisibility}, syntax_editor::SyntaxMappingBuilder, @@ -62,13 +60,12 @@ impl SyntaxFactory { pub fn block_expr( &self, - stmts: impl IntoIterator, + statements: impl IntoIterator, tail_expr: Option, ) -> ast::BlockExpr { - let stmts = stmts.into_iter().collect_vec(); - let mut input = stmts.iter().map(|it| it.syntax().clone()).collect_vec(); + let (statements, mut input) = iterator_input(statements); - let ast = make::block_expr(stmts, tail_expr.clone()).clone_for_update(); + let ast = make::block_expr(statements, tail_expr.clone()).clone_for_update(); if let Some(mut mapping) = self.mappings() { let stmt_list = ast.stmt_list().unwrap(); @@ -257,14 +254,15 @@ impl SyntaxFactory { pub fn turbofish_generic_arg_list( &self, - args: impl IntoIterator + Clone, + generic_args: impl IntoIterator, ) -> ast::GenericArgList { - let ast = make::turbofish_generic_arg_list(args.clone()).clone_for_update(); + let (generic_args, input) = iterator_input(generic_args); + let ast = make::turbofish_generic_arg_list(generic_args.clone()).clone_for_update(); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); builder.map_children( - args.into_iter().map(|arg| arg.syntax().clone()), + input.into_iter(), ast.generic_args().map(|arg| arg.syntax().clone()), ); builder.finish(&mut mapping); @@ -277,8 +275,7 @@ impl SyntaxFactory { &self, fields: impl IntoIterator, ) -> ast::RecordFieldList { - let fields: Vec = fields.into_iter().collect(); - let input: Vec<_> = fields.iter().map(|it| it.syntax().clone()).collect(); + let (fields, input) = iterator_input(fields); let ast = make::record_field_list(fields).clone_for_update(); if let Some(mut mapping) = self.mappings() { @@ -323,8 +320,7 @@ impl SyntaxFactory { &self, fields: impl IntoIterator, ) -> ast::TupleFieldList { - let fields: Vec = fields.into_iter().collect(); - let input: Vec<_> = fields.iter().map(|it| it.syntax().clone()).collect(); + let (fields, input) = iterator_input(fields); let ast = make::tuple_field_list(fields).clone_for_update(); if let Some(mut mapping) = self.mappings() { @@ -419,8 +415,7 @@ impl SyntaxFactory { &self, variants: impl IntoIterator, ) -> ast::VariantList { - let variants: Vec = variants.into_iter().collect(); - let input: Vec<_> = variants.iter().map(|it| it.syntax().clone()).collect(); + let (variants, input) = iterator_input(variants); let ast = make::variant_list(variants).clone_for_update(); if let Some(mut mapping) = self.mappings() { @@ -481,7 +476,7 @@ impl SyntaxFactory { pub fn token_tree( &self, delimiter: SyntaxKind, - tt: Vec>, + tt: impl IntoIterator>, ) -> ast::TokenTree { let tt: Vec<_> = tt.into_iter().collect(); let input: Vec<_> = tt.iter().cloned().filter_map(only_nodes).collect(); @@ -512,3 +507,20 @@ impl SyntaxFactory { make::tokens::whitespace(text) } } + +// We need to collect `input` here instead of taking `impl IntoIterator + Clone`, +// because if we took `impl IntoIterator + Clone`, that could be something like an +// `Iterator::map` with a closure that also makes use of a `SyntaxFactory` constructor. +// +// In that case, the iterator would be evaluated inside of the call to `map_children`, +// and the inner constructor would try to take a mutable borrow of the mappings `RefCell`, +// which would panic since it's already being mutably borrowed in the outer constructor. +fn iterator_input(input: impl IntoIterator) -> (Vec, Vec) { + input + .into_iter() + .map(|it| { + let syntax = it.syntax().clone(); + (it, syntax) + }) + .collect() +} From b35a8467b6da2178ceade881ab1b97f6a7bc12d0 Mon Sep 17 00:00:00 2001 From: Mark Murphy Date: Thu, 19 Dec 2024 16:41:35 -0500 Subject: [PATCH 004/123] change config rust-analyzer.statusBar.documentSelector to showStatusBar --- .../rust-analyzer/editors/code/package.json | 73 +++++++++++-------- .../rust-analyzer/editors/code/src/config.ts | 11 ++- .../rust-analyzer/editors/code/src/ctx.ts | 13 +++- 3 files changed, 62 insertions(+), 35 deletions(-) diff --git a/src/tools/rust-analyzer/editors/code/package.json b/src/tools/rust-analyzer/editors/code/package.json index 469c1b458d52a..bd1045f038f20 100644 --- a/src/tools/rust-analyzer/editors/code/package.json +++ b/src/tools/rust-analyzer/editors/code/package.json @@ -426,40 +426,55 @@ "default": "openLogs", "markdownDescription": "Action to run when clicking the extension status bar item." }, - "rust-analyzer.statusBar.documentSelector": { - "type": [ - "array", - "null" - ], - "items": { - "type": "object", - "properties": { - "language": { - "type": [ - "string", - "null" - ] - }, - "pattern": { - "type": [ - "string", - "null" - ] - } - } - }, - "default": [ - { - "language": "rust" - }, + "rust-analyzer.statusBar.showStatusBar": { + "markdownDescription": "When to show the extension status bar.\n\n`\"always\"` Always show the status bar.\n\n`\"never\"` Never show the status bar.\n\n`{ documentSelector: [] }` Show the status bar if the open file matches any of the given document selectors.\n\nSee [VS Code -- DocumentSelector](https://code.visualstudio.com/api/references/document-selector) for more information.", + "anyOf": [ { - "pattern": "**/Cargo.toml" + "type": "string", + "enum": [ + "always", + "never" + ] }, { - "pattern": "**/Cargo.lock" + "type": "object", + "properties": { + "documentSelector": { + "type": "array", + "items": { + "type": "object", + "properties": { + "language": { + "type": "string" + }, + "notebookType": { + "type": "string" + }, + "scheme": { + "type": "string" + }, + "pattern": { + "type": "string" + } + } + } + } + } } ], - "markdownDescription": "Determines when to show the extension status bar item based on the currently open file. Use `{ \"pattern\": \"**\" }` to always show. Use `null` to never show." + "default": { + "documentSelector": [ + { + "language": "rust" + }, + { + "pattern": "**/Cargo.toml" + }, + { + "pattern": "**/Cargo.lock" + } + ] + } } } }, diff --git a/src/tools/rust-analyzer/editors/code/src/config.ts b/src/tools/rust-analyzer/editors/code/src/config.ts index f7ef80df2baaf..a97d4beab51b5 100644 --- a/src/tools/rust-analyzer/editors/code/src/config.ts +++ b/src/tools/rust-analyzer/editors/code/src/config.ts @@ -13,6 +13,13 @@ export type RunnableEnvCfgItem = { }; export type RunnableEnvCfg = Record | RunnableEnvCfgItem[]; +type ShowStatusBar = + | "always" + | "never" + | { + documentSelector: vscode.DocumentSelector; + }; + export class Config { readonly extensionId = "rust-lang.rust-analyzer"; configureLang: vscode.Disposable | undefined; @@ -348,8 +355,8 @@ export class Config { return this.get("statusBar.clickAction"); } - get statusBarDocumentSelector() { - return this.get("statusBar.documentSelector"); + get statusBarShowStatusBar() { + return this.get("statusBar.showStatusBar"); } get initializeStopped() { diff --git a/src/tools/rust-analyzer/editors/code/src/ctx.ts b/src/tools/rust-analyzer/editors/code/src/ctx.ts index 4a3f66b00d006..4eecfc66f4147 100644 --- a/src/tools/rust-analyzer/editors/code/src/ctx.ts +++ b/src/tools/rust-analyzer/editors/code/src/ctx.ts @@ -478,14 +478,19 @@ export class Ctx implements RustAnalyzerExtensionApi { } private updateStatusBarVisibility(editor: vscode.TextEditor | undefined) { - const documentSelector = this.config.statusBarDocumentSelector; - if (documentSelector != null) { + const showStatusBar = this.config.statusBarShowStatusBar; + if (showStatusBar == null || showStatusBar === "never") { + this.statusBar.hide(); + } else if (showStatusBar === "always") { + this.statusBar.show(); + } else { + const documentSelector = showStatusBar.documentSelector; if (editor != null && vscode.languages.match(documentSelector, editor.document) > 0) { this.statusBar.show(); - return; + } else { + this.statusBar.hide(); } } - this.statusBar.hide(); } pushExtCleanup(d: Disposable) { From 0d5b4801594f88ae7bd1730e155e5b152c96767c Mon Sep 17 00:00:00 2001 From: Brian Bosak Date: Sun, 22 Dec 2024 16:44:01 -0600 Subject: [PATCH 005/123] Treat ; as a terminator rather than something that can be glued together in an expression --- src/tools/rust-analyzer/crates/tt/src/iter.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/rust-analyzer/crates/tt/src/iter.rs b/src/tools/rust-analyzer/crates/tt/src/iter.rs index 587b903aa97a5..4d7fe0b5a06e6 100644 --- a/src/tools/rust-analyzer/crates/tt/src/iter.rs +++ b/src/tools/rust-analyzer/crates/tt/src/iter.rs @@ -132,6 +132,7 @@ impl<'a, S: Copy> TtIter<'a, S> { } ('-' | '!' | '*' | '/' | '&' | '%' | '^' | '+' | '<' | '=' | '>' | '|', '=', _) | ('-' | '=' | '>', '>', _) + | (_, _, Some(';')) | ('<', '-', _) | (':', ':', _) | ('.', '.', _) From 5e7ce339669d059014898c610771d0e96a5d3ab1 Mon Sep 17 00:00:00 2001 From: Nicholas Rishel Date: Mon, 23 Dec 2024 14:23:01 -0800 Subject: [PATCH 006/123] minor: Break out of waiting for debugger on Windows using native debugger check API. For Windows, this removes the need to add a breakpoint and modify a value to exit the debugger wait loop. As a ridealong, this adds a 100ms sleep for all platforms such that waiting for the debugger doesn't hog the CPU thread. --- .../crates/rust-analyzer/Cargo.toml | 2 +- .../crates/rust-analyzer/src/bin/main.rs | 30 +++++++++++++++---- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml b/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml index 7c8610280b3ac..fb1f0b683d2bd 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml +++ b/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml @@ -75,7 +75,7 @@ vfs.workspace = true paths.workspace = true [target.'cfg(windows)'.dependencies] -windows-sys = { version = "0.52", features = ["Win32_System_Threading"] } +windows-sys = { version = "0.52", features = ["Win32_System_Diagnostics_Debug", "Win32_System_Threading"] } [target.'cfg(not(target_env = "msvc"))'.dependencies] jemallocator = { version = "0.5.0", package = "tikv-jemallocator", optional = true } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs index a753621eca8ee..caa42dd62cd25 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs @@ -10,7 +10,7 @@ extern crate rustc_driver as _; mod rustc_wrapper; -use std::{env, fs, path::PathBuf, process::ExitCode, sync::Arc}; +use std::{env, fs, path::PathBuf, process::ExitCode, sync::Arc, thread::sleep}; use anyhow::Context; use lsp_server::Connection; @@ -44,11 +44,7 @@ fn actual_main() -> anyhow::Result { #[cfg(debug_assertions)] if flags.wait_dbg || env::var("RA_WAIT_DBG").is_ok() { - #[allow(unused_mut)] - let mut d = 4; - while d == 4 { - d = 4; - } + wait_for_debugger(); } if let Err(e) = setup_logging(flags.log_file.clone()) { @@ -96,6 +92,28 @@ fn actual_main() -> anyhow::Result { Ok(ExitCode::SUCCESS) } +#[cfg(debug_assertions)] +fn wait_for_debugger() { + #[cfg(target_os = "windows")] + { + use windows_sys::Win32::System::Diagnostics::Debug::IsDebuggerPresent; + // SAFETY: WinAPI generated code that is defensively marked `unsafe` but + // in practice can not be used in an unsafe way. + while unsafe { IsDebuggerPresent() } == 0 { + sleep(std::time::Duration::from_millis(100)); + } + } + #[cfg(not(target_os = "windows"))] + { + #[allow(unused_mut)] + let mut d = 4; + while d == 4 { + d = 4; + sleep(std::time::Duration::from_millis(100)); + } + } +} + fn setup_logging(log_file_flag: Option) -> anyhow::Result<()> { if cfg!(windows) { // This is required so that windows finds our pdb that is placed right beside the exe. From 91fb189652512205589b14c2e70c895b7c50995f Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 24 Dec 2024 10:35:10 +0100 Subject: [PATCH 007/123] Remove dangling outdated test module --- .../crates/mbe/src/syntax_bridge/tests.rs | 101 ------------------ 1 file changed, 101 deletions(-) delete mode 100644 src/tools/rust-analyzer/crates/mbe/src/syntax_bridge/tests.rs diff --git a/src/tools/rust-analyzer/crates/mbe/src/syntax_bridge/tests.rs b/src/tools/rust-analyzer/crates/mbe/src/syntax_bridge/tests.rs deleted file mode 100644 index 2988fb3cf154e..0000000000000 --- a/src/tools/rust-analyzer/crates/mbe/src/syntax_bridge/tests.rs +++ /dev/null @@ -1,101 +0,0 @@ -use rustc_hash::FxHashMap; -use span::Span; -use syntax::{ast, AstNode}; -use test_utils::extract_annotations; -use tt::{ - buffer::{TokenBuffer, TokenTreeRef}, - Leaf, Punct, Spacing, -}; - -use crate::{syntax_node_to_token_tree, DocCommentDesugarMode, DummyTestSpanMap, DUMMY}; - -fn check_punct_spacing(fixture: &str) { - let source_file = ast::SourceFile::parse(fixture, span::Edition::CURRENT).ok().unwrap(); - let subtree = syntax_node_to_token_tree( - source_file.syntax(), - DummyTestSpanMap, - DUMMY, - DocCommentDesugarMode::Mbe, - ); - let mut annotations: FxHashMap<_, _> = extract_annotations(fixture) - .into_iter() - .map(|(range, annotation)| { - let spacing = match annotation.as_str() { - "Alone" => Spacing::Alone, - "Joint" => Spacing::Joint, - a => panic!("unknown annotation: {a}"), - }; - (range, spacing) - }) - .collect(); - - let buf = TokenBuffer::from_subtree(&subtree); - let mut cursor = buf.begin(); - while !cursor.eof() { - while let Some(token_tree) = cursor.token_tree() { - if let TokenTreeRef::Leaf( - Leaf::Punct(Punct { spacing, span: Span { range, .. }, .. }), - _, - ) = token_tree - { - if let Some(expected) = annotations.remove(range) { - assert_eq!(expected, *spacing); - } - } - cursor = cursor.bump_subtree(); - } - cursor = cursor.bump(); - } - - assert!(annotations.is_empty(), "unchecked annotations: {annotations:?}"); -} - -#[test] -fn punct_spacing() { - check_punct_spacing( - r#" -fn main() { - 0+0; - //^ Alone - 0+(0); - //^ Alone - 0<=0; - //^ Joint - // ^ Alone - 0<=(0); - // ^ Alone - a=0; - //^ Alone - a=(0); - //^ Alone - a+=0; - //^ Joint - // ^ Alone - a+=(0); - // ^ Alone - a&&b; - //^ Joint - // ^ Alone - a&&(b); - // ^ Alone - foo::bar; - // ^ Joint - // ^ Alone - use foo::{bar,baz,}; - // ^ Alone - // ^ Alone - // ^ Alone - struct Struct<'a> {}; - // ^ Joint - // ^ Joint - Struct::<0>; - // ^ Alone - Struct::<{0}>; - // ^ Alone - ;; - //^ Joint - // ^ Alone -} - "#, - ); -} From 9251d422b8c7bfad59d1efd7c4f9fa613c577cd2 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 24 Dec 2024 10:35:27 +0100 Subject: [PATCH 008/123] Back out "internal: Disable rustc test metrics" This backs out commit d9a08624aad55a91f839e6ee3acf7117d197cda9. --- src/tools/rust-analyzer/.github/workflows/metrics.yaml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/.github/workflows/metrics.yaml b/src/tools/rust-analyzer/.github/workflows/metrics.yaml index a4146d602185e..9313ca237d911 100644 --- a/src/tools/rust-analyzer/.github/workflows/metrics.yaml +++ b/src/tools/rust-analyzer/.github/workflows/metrics.yaml @@ -54,7 +54,7 @@ jobs: other_metrics: strategy: matrix: - names: [self, ripgrep-13.0.0, webrender-2022, diesel-1.4.8, hyper-0.14.18] + names: [self, rustc_tests, ripgrep-13.0.0, webrender-2022, diesel-1.4.8, hyper-0.14.18] runs-on: ubuntu-latest needs: build_metrics @@ -101,6 +101,11 @@ jobs: with: name: self-${{ github.sha }} + - name: Download rustc_tests metrics + uses: actions/download-artifact@v3 + with: + name: rustc_tests-${{ github.sha }} + - name: Download ripgrep-13.0.0 metrics uses: actions/download-artifact@v4 with: @@ -129,7 +134,7 @@ jobs: chmod 700 ~/.ssh git clone --depth 1 git@github.com:rust-analyzer/metrics.git - jq -s ".[0] * .[1] * .[2] * .[3] * .[4] * .[5]" build.json self.json ripgrep-13.0.0.json webrender-2022.json diesel-1.4.8.json hyper-0.14.18.json -c >> metrics/metrics.json + jq -s ".[0] * .[1] * .[2] * .[3] * .[4] * .[5] * .[6]" build.json self.json rustc_tests.json ripgrep-13.0.0.json webrender-2022.json diesel-1.4.8.json hyper-0.14.18.json -c >> metrics/metrics.json cd metrics git add . git -c user.name=Bot -c user.email=dummy@example.com commit --message 📈 From 4be8178a76c1fe9fb295642a19b4da7c54849eed Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 24 Dec 2024 17:21:46 +0100 Subject: [PATCH 009/123] Cleanup target fetching for cargo metadata --- .../project-model/src/cargo_workspace.rs | 105 +++--------------- .../crates/project-model/src/lib.rs | 16 ++- .../crates/project-model/src/sysroot.rs | 31 ++++-- .../crates/project-model/src/target_triple.rs | 82 ++++++++++++++ .../crates/project-model/src/tests.rs | 4 +- .../crates/project-model/src/workspace.rs | 83 +++++++++----- .../crates/rust-analyzer/src/bin/main.rs | 6 +- .../rust-analyzer/src/cli/analysis_stats.rs | 9 +- .../rust-analyzer/src/cli/rustc_tests.rs | 2 +- .../crates/rust-analyzer/src/config.rs | 11 +- 10 files changed, 209 insertions(+), 140 deletions(-) create mode 100644 src/tools/rust-analyzer/crates/project-model/src/target_triple.rs diff --git a/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs b/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs index ba4946bf0b99a..836879c75bffa 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs @@ -13,8 +13,8 @@ use serde_json::from_value; use span::Edition; use toolchain::Tool; -use crate::{utf8_stdout, ManifestPath, Sysroot, SysrootQueryMetadata}; use crate::{CfgOverrides, InvocationStrategy}; +use crate::{ManifestPath, Sysroot, SysrootQueryMetadata}; /// [`CargoWorkspace`] represents the logical structure of, well, a Cargo /// workspace. It pretty closely mirrors `cargo metadata` output. @@ -251,6 +251,18 @@ impl TargetKind { } } +#[derive(Default, Clone, Debug, PartialEq, Eq)] +pub struct CargoMetadataConfig { + /// List of features to activate. + pub features: CargoFeatures, + /// rustc targets + pub targets: Vec, + /// Extra args to pass to the cargo command. + pub extra_args: Vec, + /// Extra env vars to set when invoking the cargo command + pub extra_env: FxHashMap, +} + // Deserialize helper for the cargo metadata #[derive(Deserialize, Default)] struct PackageMetadata { @@ -265,7 +277,7 @@ impl CargoWorkspace { pub fn fetch_metadata( cargo_toml: &ManifestPath, current_dir: &AbsPath, - config: &CargoConfig, + config: &CargoMetadataConfig, sysroot: &Sysroot, locked: bool, progress: &dyn Fn(String), @@ -276,14 +288,12 @@ impl CargoWorkspace { fn fetch_metadata_( cargo_toml: &ManifestPath, current_dir: &AbsPath, - config: &CargoConfig, + config: &CargoMetadataConfig, sysroot: &Sysroot, locked: bool, no_deps: bool, progress: &dyn Fn(String), ) -> anyhow::Result<(cargo_metadata::Metadata, Option)> { - let targets = find_list_of_build_targets(config, cargo_toml, sysroot); - let cargo = sysroot.tool(Tool::Cargo); let mut meta = MetadataCommand::new(); meta.cargo_path(cargo.get_program()); @@ -319,12 +329,9 @@ impl CargoWorkspace { } } - if !targets.is_empty() { - other_options.append( - &mut targets - .into_iter() - .flat_map(|target| ["--filter-platform".to_owned(), target]) - .collect(), + if !config.targets.is_empty() { + other_options.extend( + config.targets.iter().flat_map(|it| ["--filter-platform".to_owned(), it.clone()]), ); } // The manifest is a rust file, so this means its a script manifest @@ -596,79 +603,3 @@ impl CargoWorkspace { self.is_virtual_workspace } } - -fn find_list_of_build_targets( - config: &CargoConfig, - cargo_toml: &ManifestPath, - sysroot: &Sysroot, -) -> Vec { - if let Some(target) = &config.target { - return [target.into()].to_vec(); - } - - let build_targets = cargo_config_build_target(cargo_toml, &config.extra_env, sysroot); - if !build_targets.is_empty() { - return build_targets; - } - - rustc_discover_host_triple(cargo_toml, &config.extra_env, sysroot).into_iter().collect() -} - -fn rustc_discover_host_triple( - cargo_toml: &ManifestPath, - extra_env: &FxHashMap, - sysroot: &Sysroot, -) -> Option { - let mut rustc = sysroot.tool(Tool::Rustc); - rustc.envs(extra_env); - rustc.current_dir(cargo_toml.parent()).arg("-vV"); - tracing::debug!("Discovering host platform by {:?}", rustc); - match utf8_stdout(rustc) { - Ok(stdout) => { - let field = "host: "; - let target = stdout.lines().find_map(|l| l.strip_prefix(field)); - if let Some(target) = target { - Some(target.to_owned()) - } else { - // If we fail to resolve the host platform, it's not the end of the world. - tracing::info!("rustc -vV did not report host platform, got:\n{}", stdout); - None - } - } - Err(e) => { - tracing::warn!("Failed to discover host platform: {}", e); - None - } - } -} - -fn cargo_config_build_target( - cargo_toml: &ManifestPath, - extra_env: &FxHashMap, - sysroot: &Sysroot, -) -> Vec { - let mut cargo_config = sysroot.tool(Tool::Cargo); - cargo_config.envs(extra_env); - cargo_config - .current_dir(cargo_toml.parent()) - .args(["-Z", "unstable-options", "config", "get", "build.target"]) - .env("RUSTC_BOOTSTRAP", "1"); - // if successful we receive `build.target = "target-triple"` - // or `build.target = ["", ..]` - tracing::debug!("Discovering cargo config target by {:?}", cargo_config); - utf8_stdout(cargo_config).map(parse_output_cargo_config_build_target).unwrap_or_default() -} - -fn parse_output_cargo_config_build_target(stdout: String) -> Vec { - let trimmed = stdout.trim_start_matches("build.target = ").trim_matches('"'); - - if !trimmed.starts_with('[') { - return [trimmed.to_owned()].to_vec(); - } - - let res = serde_json::from_str(trimmed); - if let Err(e) = &res { - tracing::warn!("Failed to parse `build.target` as an array of target: {}`", e); - } - res.unwrap_or_default() -} diff --git a/src/tools/rust-analyzer/crates/project-model/src/lib.rs b/src/tools/rust-analyzer/crates/project-model/src/lib.rs index da8afc5d3a184..9a024f6b962e6 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/lib.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/lib.rs @@ -23,6 +23,7 @@ pub mod project_json; mod rustc_cfg; mod sysroot; pub mod target_data_layout; +mod target_triple; mod workspace; #[cfg(test)] @@ -42,8 +43,8 @@ use rustc_hash::FxHashSet; pub use crate::{ build_dependencies::WorkspaceBuildScripts, cargo_workspace::{ - CargoConfig, CargoFeatures, CargoWorkspace, Package, PackageData, PackageDependency, - RustLibSource, Target, TargetData, TargetKind, + CargoConfig, CargoFeatures, CargoMetadataConfig, CargoWorkspace, Package, PackageData, + PackageDependency, RustLibSource, Target, TargetData, TargetKind, }, manifest_path::ManifestPath, project_json::{ProjectJson, ProjectJsonData}, @@ -241,9 +242,14 @@ fn parse_cfg(s: &str) -> Result { Ok(res) } -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum SysrootQueryMetadata { - #[default] - CargoMetadata, + CargoMetadata(CargoMetadataConfig), None, } + +impl Default for SysrootQueryMetadata { + fn default() -> Self { + SysrootQueryMetadata::CargoMetadata(Default::default()) + } +} 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 8426e689a64d1..d8186a23f76b5 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs @@ -14,7 +14,10 @@ use paths::{AbsPath, AbsPathBuf, Utf8PathBuf}; use rustc_hash::FxHashMap; use toolchain::{probe_for_binary, Tool}; -use crate::{utf8_stdout, CargoConfig, CargoWorkspace, ManifestPath, SysrootQueryMetadata}; +use crate::{ + cargo_workspace::CargoMetadataConfig, utf8_stdout, CargoWorkspace, ManifestPath, + SysrootQueryMetadata, +}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct Sysroot { @@ -126,7 +129,7 @@ impl Sysroot { pub fn discover( dir: &AbsPath, extra_env: &FxHashMap, - sysroot_query_metadata: SysrootQueryMetadata, + sysroot_query_metadata: &SysrootQueryMetadata, ) -> Sysroot { let sysroot_dir = discover_sysroot_dir(dir, extra_env); let sysroot_src_dir = sysroot_dir.as_ref().ok().map(|sysroot_dir| { @@ -139,7 +142,7 @@ impl Sysroot { current_dir: &AbsPath, extra_env: &FxHashMap, sysroot_src_dir: AbsPathBuf, - sysroot_query_metadata: SysrootQueryMetadata, + sysroot_query_metadata: &SysrootQueryMetadata, ) -> Sysroot { let sysroot_dir = discover_sysroot_dir(current_dir, extra_env); Sysroot::load_core_check( @@ -151,7 +154,7 @@ impl Sysroot { pub fn discover_sysroot_src_dir( sysroot_dir: AbsPathBuf, - sysroot_query_metadata: SysrootQueryMetadata, + sysroot_query_metadata: &SysrootQueryMetadata, ) -> Sysroot { let sysroot_src_dir = discover_sysroot_src_dir(&sysroot_dir) .ok_or_else(|| format_err!("can't find standard library sources in {sysroot_dir}")); @@ -205,7 +208,7 @@ impl Sysroot { pub fn load( sysroot_dir: Option, sysroot_src_dir: Option, - sysroot_query_metadata: SysrootQueryMetadata, + sysroot_query_metadata: &SysrootQueryMetadata, ) -> Sysroot { Self::load_core_check(sysroot_dir.map(Ok), sysroot_src_dir.map(Ok), sysroot_query_metadata) } @@ -213,7 +216,7 @@ impl Sysroot { fn load_core_check( sysroot_dir: Option>, sysroot_src_dir: Option>, - sysroot_query_metadata: SysrootQueryMetadata, + sysroot_query_metadata: &SysrootQueryMetadata, ) -> Sysroot { let mut sysroot = Self::load_(sysroot_dir, sysroot_src_dir, sysroot_query_metadata); if sysroot.error.is_none() { @@ -241,7 +244,7 @@ impl Sysroot { fn load_( sysroot_dir: Option>, sysroot_src_dir: Option>, - sysroot_query_metadata: SysrootQueryMetadata, + sysroot_query_metadata: &SysrootQueryMetadata, ) -> Sysroot { let sysroot_dir = match sysroot_dir { Some(Ok(sysroot_dir)) => Some(sysroot_dir), @@ -274,13 +277,16 @@ impl Sysroot { } } }; - if sysroot_query_metadata == SysrootQueryMetadata::CargoMetadata { + if let SysrootQueryMetadata::CargoMetadata(cargo_config) = sysroot_query_metadata { let library_manifest = ManifestPath::try_from(sysroot_src_dir.join("Cargo.toml")).unwrap(); if fs::metadata(&library_manifest).is_ok() { - if let Some(sysroot) = - Self::load_library_via_cargo(library_manifest, &sysroot_dir, &sysroot_src_dir) - { + if let Some(sysroot) = Self::load_library_via_cargo( + library_manifest, + &sysroot_dir, + &sysroot_src_dir, + cargo_config, + ) { return sysroot; } } @@ -341,9 +347,10 @@ impl Sysroot { library_manifest: ManifestPath, sysroot_dir: &Option, sysroot_src_dir: &AbsPathBuf, + cargo_config: &CargoMetadataConfig, ) -> Option { tracing::debug!("Loading library metadata: {library_manifest}"); - let mut cargo_config = CargoConfig::default(); + let mut cargo_config = cargo_config.clone(); // the sysroot uses `public-dependency`, so we make cargo think it's a nightly cargo_config.extra_env.insert( "__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS".to_owned(), diff --git a/src/tools/rust-analyzer/crates/project-model/src/target_triple.rs b/src/tools/rust-analyzer/crates/project-model/src/target_triple.rs new file mode 100644 index 0000000000000..92e0c49885f62 --- /dev/null +++ b/src/tools/rust-analyzer/crates/project-model/src/target_triple.rs @@ -0,0 +1,82 @@ +//! Runs `rustc --print -vV` to get the host target. +use anyhow::Context; +use rustc_hash::FxHashMap; +use toolchain::Tool; + +use crate::{utf8_stdout, ManifestPath, Sysroot}; + +pub(super) enum TargetTipleConfig<'a> { + #[expect(dead_code)] + Rustc(&'a Sysroot), + Cargo(&'a Sysroot, &'a ManifestPath), +} + +pub(super) fn get( + config: TargetTipleConfig<'_>, + target: Option<&str>, + extra_env: &FxHashMap, +) -> anyhow::Result> { + if let Some(target) = target { + return Ok(vec![target.to_owned()]); + } + + let sysroot = match config { + TargetTipleConfig::Cargo(sysroot, cargo_toml) => { + match cargo_config_build_target(cargo_toml, extra_env, sysroot) { + Ok(it) => return Ok(it), + Err(e) => { + tracing::warn!("failed to run `cargo rustc --print cfg`, falling back to invoking rustc directly: {e}"); + sysroot + } + } + } + TargetTipleConfig::Rustc(sysroot) => sysroot, + }; + rustc_discover_host_triple(extra_env, sysroot).map(|it| vec![it]) +} + +fn rustc_discover_host_triple( + extra_env: &FxHashMap, + sysroot: &Sysroot, +) -> anyhow::Result { + let mut rustc = sysroot.tool(Tool::Rustc); + rustc.envs(extra_env); + rustc.arg("-vV"); + tracing::debug!("Discovering host platform by {:?}", rustc); + let stdout = utf8_stdout(rustc).context("Failed to discover host platform")?; + let field = "host: "; + let target = stdout.lines().find_map(|l| l.strip_prefix(field)); + if let Some(target) = target { + Ok(target.to_owned()) + } else { + // If we fail to resolve the host platform, it's not the end of the world. + Err(anyhow::format_err!("rustc -vV did not report host platform, got:\n{}", stdout)) + } +} + +fn cargo_config_build_target( + cargo_toml: &ManifestPath, + extra_env: &FxHashMap, + sysroot: &Sysroot, +) -> anyhow::Result> { + let mut cargo_config = sysroot.tool(Tool::Cargo); + cargo_config.envs(extra_env); + cargo_config + .current_dir(cargo_toml.parent()) + .args(["-Z", "unstable-options", "config", "get", "build.target"]) + .env("RUSTC_BOOTSTRAP", "1"); + // if successful we receive `build.target = "target-triple"` + // or `build.target = ["", ..]` + tracing::debug!("Discovering cargo config target by {:?}", cargo_config); + utf8_stdout(cargo_config).and_then(parse_output_cargo_config_build_target) +} + +fn parse_output_cargo_config_build_target(stdout: String) -> anyhow::Result> { + let trimmed = stdout.trim_start_matches("build.target = ").trim_matches('"'); + + if !trimmed.starts_with('[') { + return Ok([trimmed.to_owned()].to_vec()); + } + + serde_json::from_str(trimmed).context("Failed to parse `build.target` as an array of target") +} diff --git a/src/tools/rust-analyzer/crates/project-model/src/tests.rs b/src/tools/rust-analyzer/crates/project-model/src/tests.rs index f3cf2d83eaca6..9fc2d6116a465 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/tests.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/tests.rs @@ -117,7 +117,7 @@ fn get_fake_sysroot() -> Sysroot { // fake sysroot, so we give them both the same path: let sysroot_dir = AbsPathBuf::assert(sysroot_path); let sysroot_src_dir = sysroot_dir.clone(); - Sysroot::load(Some(sysroot_dir), Some(sysroot_src_dir), SysrootQueryMetadata::CargoMetadata) + Sysroot::load(Some(sysroot_dir), Some(sysroot_src_dir), &SysrootQueryMetadata::default()) } fn rooted_project_json(data: ProjectJsonData) -> ProjectJson { @@ -232,7 +232,7 @@ fn smoke_test_real_sysroot_cargo() { let sysroot = Sysroot::discover( AbsPath::assert(Utf8Path::new(env!("CARGO_MANIFEST_DIR"))), &Default::default(), - SysrootQueryMetadata::CargoMetadata, + &SysrootQueryMetadata::default(), ); assert!(matches!(sysroot.mode(), SysrootMode::Workspace(_))); let project_workspace = ProjectWorkspace { 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 71ddee3091002..d747a8086b466 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, iter, sync}; +use std::{collections::VecDeque, fmt, fs, iter, ops::Deref, sync}; use anyhow::Context; use base_db::{ @@ -22,12 +22,13 @@ use triomphe::Arc; use crate::{ build_dependencies::BuildScriptOutput, - cargo_workspace::{DepKind, PackageData, RustLibSource}, + cargo_workspace::{CargoMetadataConfig, DepKind, PackageData, RustLibSource}, env::{cargo_config_env, inject_cargo_env, inject_cargo_package_env, inject_rustc_tool_env}, project_json::{Crate, CrateArrayIdx}, rustc_cfg::{self, RustcCfgConfig}, sysroot::{SysrootCrate, SysrootMode}, target_data_layout::{self, RustcDataLayoutConfig}, + target_triple::{self, TargetTipleConfig}, utf8_stdout, CargoConfig, CargoWorkspace, CfgOverrides, InvocationStrategy, ManifestPath, Package, ProjectJson, ProjectManifest, Sysroot, TargetData, TargetKind, WorkspaceBuildScripts, }; @@ -220,28 +221,31 @@ impl ProjectWorkspace { ProjectWorkspace::load_detached_file(rust_file, config)? } ProjectManifest::CargoToml(cargo_toml) => { + // FIXME: Split sysroot discovery from sysroot loading, as to load the sysroot we + // want to pass the analysis target, but to discover the target we need to know the + // sysroot location so we know which cargo to use let sysroot = match (&config.sysroot, &config.sysroot_src) { (Some(RustLibSource::Discover), None) => Sysroot::discover( cargo_toml.parent(), &config.extra_env, - config.sysroot_query_metadata, + &config.sysroot_query_metadata, ), (Some(RustLibSource::Discover), Some(sysroot_src)) => { Sysroot::discover_with_src_override( cargo_toml.parent(), &config.extra_env, sysroot_src.clone(), - config.sysroot_query_metadata, + &config.sysroot_query_metadata, ) } (Some(RustLibSource::Path(path)), None) => Sysroot::discover_sysroot_src_dir( path.clone(), - config.sysroot_query_metadata, + &config.sysroot_query_metadata, ), (Some(RustLibSource::Path(sysroot)), Some(sysroot_src)) => Sysroot::load( Some(sysroot.clone()), Some(sysroot_src.clone()), - config.sysroot_query_metadata, + &config.sysroot_query_metadata, ), (None, _) => Sysroot::empty(), }; @@ -257,15 +261,22 @@ impl ProjectWorkspace { } None => Err(None), }; - + let targets = target_triple::get( + TargetTipleConfig::Cargo(&sysroot, cargo_toml), + config.target.as_deref(), + &config.extra_env, + ) + .unwrap_or_default(); let rustc = rustc_dir.and_then(|rustc_dir| { info!(workspace = %cargo_toml, rustc_dir = %rustc_dir, "Using rustc source"); match CargoWorkspace::fetch_metadata( &rustc_dir, cargo_toml.parent(), - &CargoConfig { + &CargoMetadataConfig { features: crate::CargoFeatures::default(), - ..config.clone() + targets: targets.clone(), + extra_args: config.extra_args.clone(), + extra_env: config.extra_env.clone(), }, &sysroot, false, @@ -301,7 +312,7 @@ impl ProjectWorkspace { "cargo ", )?; let rustc_cfg = rustc_cfg::get( - config.target.as_deref(), + targets.first().map(Deref::deref), &config.extra_env, RustcCfgConfig::Cargo(&sysroot, cargo_toml), ); @@ -309,7 +320,7 @@ impl ProjectWorkspace { let cfg_overrides = config.cfg_overrides.clone(); let data_layout = target_data_layout::get( RustcDataLayoutConfig::Cargo(&sysroot, cargo_toml), - config.target.as_deref(), + targets.first().map(Deref::deref), &config.extra_env, ); if let Err(e) = &data_layout { @@ -319,7 +330,12 @@ impl ProjectWorkspace { let (meta, error) = CargoWorkspace::fetch_metadata( cargo_toml, cargo_toml.parent(), - config, + &CargoMetadataConfig { + features: config.features.clone(), + targets, + extra_args: config.extra_args.clone(), + extra_env: config.extra_env.clone(), + }, &sysroot, false, progress, @@ -360,7 +376,7 @@ impl ProjectWorkspace { let sysroot = Sysroot::load( project_json.sysroot.clone(), project_json.sysroot_src.clone(), - config.sysroot_query_metadata, + &config.sysroot_query_metadata, ); let cfg_config = RustcCfgConfig::Rustc(&sysroot); let data_layout_config = RustcDataLayoutConfig::Rustc(&sysroot); @@ -398,10 +414,10 @@ impl ProjectWorkspace { let dir = detached_file.parent(); let sysroot = match &config.sysroot { Some(RustLibSource::Path(path)) => { - Sysroot::discover_sysroot_src_dir(path.clone(), config.sysroot_query_metadata) + Sysroot::discover_sysroot_src_dir(path.clone(), &config.sysroot_query_metadata) } Some(RustLibSource::Discover) => { - Sysroot::discover(dir, &config.extra_env, config.sysroot_query_metadata) + Sysroot::discover(dir, &config.extra_env, &config.sysroot_query_metadata) } None => Sysroot::empty(), }; @@ -415,6 +431,12 @@ impl ProjectWorkspace { } }; + let targets = target_triple::get( + TargetTipleConfig::Cargo(&sysroot, detached_file), + config.target.as_deref(), + &config.extra_env, + ) + .unwrap_or_default(); let rustc_cfg = rustc_cfg::get(None, &config.extra_env, RustcCfgConfig::Rustc(&sysroot)); let data_layout = target_data_layout::get( RustcDataLayoutConfig::Rustc(&sysroot), @@ -422,16 +444,27 @@ impl ProjectWorkspace { &config.extra_env, ); - let cargo_script = - CargoWorkspace::fetch_metadata(detached_file, dir, config, &sysroot, false, &|_| ()) - .ok() - .map(|(ws, error)| { - ( - CargoWorkspace::new(ws, detached_file.clone()), - WorkspaceBuildScripts::default(), - error.map(Arc::new), - ) - }); + let cargo_script = CargoWorkspace::fetch_metadata( + detached_file, + dir, + &CargoMetadataConfig { + features: config.features.clone(), + targets, + extra_args: config.extra_args.clone(), + extra_env: config.extra_env.clone(), + }, + &sysroot, + false, + &|_| (), + ) + .ok() + .map(|(ws, error)| { + ( + CargoWorkspace::new(ws, detached_file.clone()), + WorkspaceBuildScripts::default(), + error.map(Arc::new), + ) + }); let cargo_config_extra_env = cargo_config_env(detached_file, &config.extra_env, &sysroot); Ok(ProjectWorkspace { diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs index caa42dd62cd25..1a9cdef256d28 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs @@ -10,7 +10,7 @@ extern crate rustc_driver as _; mod rustc_wrapper; -use std::{env, fs, path::PathBuf, process::ExitCode, sync::Arc, thread::sleep}; +use std::{env, fs, path::PathBuf, process::ExitCode, sync::Arc}; use anyhow::Context; use lsp_server::Connection; @@ -100,7 +100,7 @@ fn wait_for_debugger() { // SAFETY: WinAPI generated code that is defensively marked `unsafe` but // in practice can not be used in an unsafe way. while unsafe { IsDebuggerPresent() } == 0 { - sleep(std::time::Duration::from_millis(100)); + std::thread::sleep(std::time::Duration::from_millis(100)); } } #[cfg(not(target_os = "windows"))] @@ -109,7 +109,7 @@ fn wait_for_debugger() { let mut d = 4; while d == 4 { d = 4; - sleep(std::time::Duration::from_millis(100)); + std::thread::sleep(std::time::Duration::from_millis(100)); } } } 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 66cd2e424e385..9b428871c4086 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 @@ -33,7 +33,10 @@ use itertools::Itertools; use load_cargo::{load_workspace, LoadCargoConfig, ProcMacroServerChoice}; use oorandom::Rand32; use profile::{Bytes, StopWatch}; -use project_model::{CargoConfig, CfgOverrides, ProjectManifest, ProjectWorkspace, RustLibSource}; +use project_model::{ + CargoConfig, CargoMetadataConfig, CfgOverrides, ProjectManifest, ProjectWorkspace, + RustLibSource, +}; use rayon::prelude::*; use rustc_hash::{FxHashMap, FxHashSet}; use syntax::{AstNode, SyntaxNode}; @@ -68,7 +71,9 @@ impl flags::AnalysisStats { }, sysroot_query_metadata: match self.no_query_sysroot_metadata { true => project_model::SysrootQueryMetadata::None, - false => project_model::SysrootQueryMetadata::CargoMetadata, + false => project_model::SysrootQueryMetadata::CargoMetadata( + CargoMetadataConfig::default(), + ), }, all_targets: true, set_test: !self.no_test, diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs index 6483afc85b21d..db792ade574da 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs @@ -77,7 +77,7 @@ impl Tester { let sysroot = Sysroot::discover( tmp_file.parent().unwrap(), &cargo_config.extra_env, - SysrootQueryMetadata::CargoMetadata, + &SysrootQueryMetadata::CargoMetadata(Default::default()), ); let data_layout = target_data_layout::get( RustcDataLayoutConfig::Rustc(&sysroot), 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 b06117f73835a..4160dd16becf9 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -21,8 +21,8 @@ use ide_db::{ use itertools::Itertools; use paths::{Utf8Path, Utf8PathBuf}; use project_model::{ - CargoConfig, CargoFeatures, ProjectJson, ProjectJsonData, ProjectJsonFromCommand, - ProjectManifest, RustLibSource, + CargoConfig, CargoFeatures, CargoMetadataConfig, ProjectJson, ProjectJsonData, + ProjectJsonFromCommand, ProjectManifest, RustLibSource, }; use rustc_hash::{FxHashMap, FxHashSet}; use semver::Version; @@ -1862,7 +1862,12 @@ impl Config { sysroot, sysroot_query_metadata: match self.cargo_sysrootQueryMetadata(None) { SysrootQueryMetadata::CargoMetadata => { - project_model::SysrootQueryMetadata::CargoMetadata + project_model::SysrootQueryMetadata::CargoMetadata(CargoMetadataConfig { + features: Default::default(), + targets: self.cargo_target(source_root).clone().into_iter().collect(), + extra_args: Default::default(), + extra_env: Default::default(), + }) } SysrootQueryMetadata::None => project_model::SysrootQueryMetadata::None, }, From 9d44ee13129fe77067ccc9d44d42a0be7f2a41e8 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Sun, 22 Dec 2024 18:07:27 +0200 Subject: [PATCH 010/123] Unify handling of path diagnostics in hir-ty Because it was a mess. Previously, pretty much you had to handle all path diagnostics manually: remember to check for them and handle them. Now, we wrap the resolver in `TyLoweringContext` and ensure proper error reporting. This means that you don't have to worry about them: most of the things are handled automatically, and things that cannot will create a compile-time error (forcing you top `drop(ty_lowering_context);`) if forgotten, instead of silently dropping the diagnostics. The real place for error reporting is in the hir-def resolver, because there are other things resolving, both in hir-ty and in hir-def, and they all need to ensure proper diagnostics. But this is a good start, and future compatible. This commit also ensures proper path diagnostics for value/pattern paths, which is why it's marked "feat". --- .../crates/hir-def/src/nameres.rs | 8 +- .../crates/hir-def/src/nameres/collector.rs | 4 +- .../hir-def/src/nameres/path_resolution.rs | 79 +++- .../rust-analyzer/crates/hir-def/src/path.rs | 15 + .../crates/hir-def/src/resolver.rs | 103 +++-- .../rust-analyzer/crates/hir-ty/src/infer.rs | 127 ++++--- .../crates/hir-ty/src/infer/diagnostics.rs | 128 +++++++ .../crates/hir-ty/src/infer/expr.rs | 15 +- .../crates/hir-ty/src/infer/pat.rs | 4 +- .../crates/hir-ty/src/infer/path.rs | 54 +-- .../rust-analyzer/crates/hir-ty/src/lower.rs | 355 +++++++++++++----- .../crates/hir-ty/src/lower/diagnostics.rs | 11 +- .../crates/hir/src/diagnostics.rs | 52 ++- .../src/handlers/generic_args_prohibited.rs | 140 +++++++ 14 files changed, 848 insertions(+), 247 deletions(-) create mode 100644 src/tools/rust-analyzer/crates/hir-ty/src/infer/diagnostics.rs 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 9bd7d38f0a64d..39d383f0159b1 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs @@ -85,6 +85,8 @@ use crate::{ FxIndexMap, LocalModuleId, Lookup, MacroExpander, MacroId, ModuleId, ProcMacroId, UseId, }; +pub use self::path_resolution::ResolvePathResultPrefixInfo; + const PREDEFINED_TOOLS: &[SmolStr] = &[ SmolStr::new_static("clippy"), SmolStr::new_static("rustfmt"), @@ -615,13 +617,15 @@ impl DefMap { (res.resolved_def, res.segment_index) } + /// The first `Option` points at the `Enum` segment in case of `Enum::Variant`, the second + /// points at the unresolved segments. pub(crate) fn resolve_path_locally( &self, db: &dyn DefDatabase, original_module: LocalModuleId, path: &ModPath, shadow: BuiltinShadowMode, - ) -> (PerNs, Option) { + ) -> (PerNs, Option, ResolvePathResultPrefixInfo) { let res = self.resolve_path_fp_with_macro_single( db, ResolveMode::Other, @@ -630,7 +634,7 @@ impl DefMap { shadow, None, // Currently this function isn't used for macro resolution. ); - (res.resolved_def, res.segment_index) + (res.resolved_def, res.segment_index, res.prefix_info) } /// Ascends the `DefMap` hierarchy and calls `f` with every `DefMap` and containing module. 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 f391cc41c18f7..318a8d5aa1f73 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 @@ -38,7 +38,7 @@ use crate::{ attr_resolution::{attr_macro_as_call_id, derive_macro_as_call_id}, diagnostics::DefDiagnostic, mod_resolution::ModDir, - path_resolution::ReachedFixedPoint, + path_resolution::{ReachedFixedPoint, ResolvePathResultPrefixInfo}, proc_macro::{parse_macro_name_and_helper_attrs, ProcMacroDef, ProcMacroKind}, sub_namespace_match, BuiltinShadowMode, DefMap, MacroSubNs, ModuleData, ModuleOrigin, ResolveMode, @@ -797,7 +797,7 @@ impl DefCollector<'_> { return PartialResolvedImport::Unresolved; } - if res.from_differing_crate { + if let ResolvePathResultPrefixInfo::DifferingCrate = res.prefix_info { return PartialResolvedImport::Resolved( def.filter_visibility(|v| matches!(v, Visibility::Public)), ); 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 8eb195680d195..9573697a87156 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 @@ -43,21 +43,34 @@ pub(super) struct ResolvePathResult { pub(super) resolved_def: PerNs, pub(super) segment_index: Option, pub(super) reached_fixedpoint: ReachedFixedPoint, - pub(super) from_differing_crate: bool, + pub(super) prefix_info: ResolvePathResultPrefixInfo, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ResolvePathResultPrefixInfo { + None, + DifferingCrate, + /// Path of the form `Enum::Variant` (and not `Variant` alone). + Enum, } impl ResolvePathResult { fn empty(reached_fixedpoint: ReachedFixedPoint) -> ResolvePathResult { - ResolvePathResult::new(PerNs::none(), reached_fixedpoint, None, false) + ResolvePathResult::new( + PerNs::none(), + reached_fixedpoint, + None, + ResolvePathResultPrefixInfo::None, + ) } fn new( resolved_def: PerNs, reached_fixedpoint: ReachedFixedPoint, segment_index: Option, - from_differing_crate: bool, + prefix_info: ResolvePathResultPrefixInfo, ) -> ResolvePathResult { - ResolvePathResult { resolved_def, segment_index, reached_fixedpoint, from_differing_crate } + ResolvePathResult { resolved_def, segment_index, reached_fixedpoint, prefix_info } } } @@ -157,7 +170,17 @@ impl DefMap { if result.reached_fixedpoint == ReachedFixedPoint::No { result.reached_fixedpoint = new.reached_fixedpoint; } - result.from_differing_crate |= new.from_differing_crate; + result.prefix_info = match (result.prefix_info, new.prefix_info) { + (ResolvePathResultPrefixInfo::None, it) => it, + (ResolvePathResultPrefixInfo::DifferingCrate, _) => { + ResolvePathResultPrefixInfo::DifferingCrate + } + ( + ResolvePathResultPrefixInfo::Enum, + ResolvePathResultPrefixInfo::DifferingCrate, + ) => ResolvePathResultPrefixInfo::DifferingCrate, + (ResolvePathResultPrefixInfo::Enum, _) => ResolvePathResultPrefixInfo::Enum, + }; result.segment_index = match (result.segment_index, new.segment_index) { (Some(idx), None) => Some(idx), (Some(old), Some(new)) => Some(old.max(new)), @@ -403,14 +426,14 @@ impl DefMap { fn resolve_remaining_segments<'a>( &self, - segments: impl Iterator, + mut segments: impl Iterator, mut curr_per_ns: PerNs, path: &ModPath, db: &dyn DefDatabase, shadow: BuiltinShadowMode, original_module: LocalModuleId, ) -> ResolvePathResult { - for (i, segment) in segments { + while let Some((i, segment)) = segments.next() { let curr = match curr_per_ns.take_types_full() { Some(r) => r, None => { @@ -443,7 +466,7 @@ impl DefMap { def, ReachedFixedPoint::Yes, s.map(|s| s + i), - true, + ResolvePathResultPrefixInfo::DifferingCrate, ); } @@ -488,17 +511,28 @@ impl DefMap { ), }) }); - match res { - Some(res) => res, - None => { - return ResolvePathResult::new( - PerNs::types(e.into(), curr.vis, curr.import), - ReachedFixedPoint::Yes, - Some(i), - false, - ) + // FIXME: Need to filter visibility here and below? Not sure. + return match res { + Some(res) => { + if segments.next().is_some() { + // Enum variants are in value namespace, segments left => no resolution. + ResolvePathResult::empty(ReachedFixedPoint::No) + } else { + ResolvePathResult::new( + res, + ReachedFixedPoint::Yes, + None, + ResolvePathResultPrefixInfo::Enum, + ) + } } - } + None => ResolvePathResult::new( + PerNs::types(e.into(), curr.vis, curr.import), + ReachedFixedPoint::Yes, + Some(i), + ResolvePathResultPrefixInfo::None, + ), + }; } s => { // could be an inherent method call in UFCS form @@ -513,7 +547,7 @@ impl DefMap { PerNs::types(s, curr.vis, curr.import), ReachedFixedPoint::Yes, Some(i), - false, + ResolvePathResultPrefixInfo::None, ); } }; @@ -522,7 +556,12 @@ impl DefMap { .filter_visibility(|vis| vis.is_visible_from_def_map(db, self, original_module)); } - ResolvePathResult::new(curr_per_ns, ReachedFixedPoint::Yes, None, false) + ResolvePathResult::new( + curr_per_ns, + ReachedFixedPoint::Yes, + None, + ResolvePathResultPrefixInfo::None, + ) } fn resolve_name_in_module( 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 44e132061ad41..e59c37104dd60 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/path.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/path.rs @@ -240,6 +240,7 @@ pub struct PathSegment<'a> { pub args_and_bindings: Option<&'a GenericArgs>, } +#[derive(Debug, Clone, Copy)] pub struct PathSegments<'a> { segments: &'a [Name], generic_args: Option<&'a [Option]>, @@ -259,6 +260,7 @@ impl<'a> PathSegments<'a> { pub fn last(&self) -> Option> { self.get(self.len().checked_sub(1)?) } + pub fn get(&self, idx: usize) -> Option> { let res = PathSegment { name: self.segments.get(idx)?, @@ -266,24 +268,37 @@ impl<'a> PathSegments<'a> { }; Some(res) } + pub fn skip(&self, len: usize) -> PathSegments<'a> { PathSegments { segments: self.segments.get(len..).unwrap_or(&[]), generic_args: self.generic_args.and_then(|it| it.get(len..)), } } + pub fn take(&self, len: usize) -> PathSegments<'a> { PathSegments { segments: self.segments.get(..len).unwrap_or(self.segments), generic_args: self.generic_args.map(|it| it.get(..len).unwrap_or(it)), } } + pub fn strip_last(&self) -> PathSegments<'a> { PathSegments { segments: self.segments.split_last().map_or(&[], |it| it.1), generic_args: self.generic_args.map(|it| it.split_last().map_or(&[][..], |it| it.1)), } } + + pub fn strip_last_two(&self) -> PathSegments<'a> { + PathSegments { + segments: self.segments.get(..self.segments.len().saturating_sub(2)).unwrap_or(&[]), + generic_args: self + .generic_args + .map(|it| it.get(..it.len().saturating_sub(2)).unwrap_or(&[])), + } + } + pub fn iter(&self) -> impl Iterator> { self.segments .iter() 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 f4dfd42a30e94..4f15c8091c9c9 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs @@ -21,7 +21,7 @@ use crate::{ hir::{BindingId, ExprId, LabelId}, item_scope::{BuiltinShadowMode, ImportId, ImportOrExternCrate, BUILTIN_SCOPE}, lang_item::LangItemTarget, - nameres::{DefMap, MacroSubNs}, + nameres::{DefMap, MacroSubNs, ResolvePathResultPrefixInfo}, path::{ModPath, Path, PathKind}, per_ns::PerNs, type_ref::{LifetimeRef, TypesMap}, @@ -263,25 +263,37 @@ impl Resolver { &self, db: &dyn DefDatabase, path: &Path, - mut hygiene_id: HygieneId, + hygiene_id: HygieneId, ) -> Option { + self.resolve_path_in_value_ns_with_prefix_info(db, path, hygiene_id).map(|(it, _)| it) + } + + pub fn resolve_path_in_value_ns_with_prefix_info( + &self, + db: &dyn DefDatabase, + path: &Path, + mut hygiene_id: HygieneId, + ) -> Option<(ResolveValueResult, ResolvePathResultPrefixInfo)> { let path = match path { Path::BarePath(mod_path) => mod_path, Path::Normal(it) => it.mod_path(), 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, + 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, + ), + ResolvePathResultPrefixInfo::None, )) } Path::LangItem(l, Some(_)) => { @@ -296,7 +308,10 @@ impl Resolver { | LangItemTarget::ImplDef(_) | LangItemTarget::Static(_) => return None, }; - return Some(ResolveValueResult::Partial(type_ns, 1, None)); + return Some(( + ResolveValueResult::Partial(type_ns, 1, None), + ResolvePathResultPrefixInfo::None, + )); } }; let n_segments = path.segments().len(); @@ -326,9 +341,12 @@ impl Resolver { }); if let Some(e) = entry { - return Some(ResolveValueResult::ValueNs( - ValueNs::LocalBinding(e.binding()), - None, + return Some(( + ResolveValueResult::ValueNs( + ValueNs::LocalBinding(e.binding()), + None, + ), + ResolvePathResultPrefixInfo::None, )); } } @@ -350,14 +368,17 @@ impl Resolver { 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, None)); + return Some(( + ResolveValueResult::ValueNs(val, None), + ResolvePathResultPrefixInfo::None, + )); } } &Scope::ImplDefScope(impl_) => { if *first_name == sym::Self_.clone() { - return Some(ResolveValueResult::ValueNs( - ValueNs::ImplSelf(impl_), - None, + return Some(( + ResolveValueResult::ValueNs(ValueNs::ImplSelf(impl_), None), + ResolvePathResultPrefixInfo::None, )); } } @@ -377,22 +398,27 @@ 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, None)); + return Some(( + ResolveValueResult::Partial(ty, 1, None), + ResolvePathResultPrefixInfo::None, + )); } } &Scope::ImplDefScope(impl_) => { if *first_name == sym::Self_.clone() { - return Some(ResolveValueResult::Partial( - TypeNs::SelfType(impl_), - 1, - None, + return Some(( + ResolveValueResult::Partial(TypeNs::SelfType(impl_), 1, None), + ResolvePathResultPrefixInfo::None, )); } } Scope::AdtScope(adt) => { if *first_name == sym::Self_.clone() { let ty = TypeNs::AdtSelfType(*adt); - return Some(ResolveValueResult::Partial(ty, 1, None)); + return Some(( + ResolveValueResult::Partial(ty, 1, None), + ResolvePathResultPrefixInfo::None, + )); } } Scope::BlockScope(m) => { @@ -413,7 +439,10 @@ 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, None)); + return Some(( + ResolveValueResult::Partial(TypeNs::BuiltinType(builtin), 1, None), + ResolvePathResultPrefixInfo::None, + )); } } @@ -924,15 +953,15 @@ impl ModuleItemMap { &self, db: &dyn DefDatabase, path: &ModPath, - ) -> Option { - let (module_def, idx) = + ) -> Option<(ResolveValueResult, ResolvePathResultPrefixInfo)> { + let (module_def, unresolved_idx, prefix_info) = self.def_map.resolve_path_locally(db, self.module_id, path, BuiltinShadowMode::Other); - match idx { + match unresolved_idx { None => { let (value, import) = to_value_ns(module_def)?; - Some(ResolveValueResult::ValueNs(value, import)) + Some((ResolveValueResult::ValueNs(value, import), prefix_info)) } - Some(idx) => { + Some(unresolved_idx) => { let def = module_def.take_types_full()?; let ty = match def.def { ModuleDefId::AdtId(it) => TypeNs::AdtId(it), @@ -948,7 +977,7 @@ impl ModuleItemMap { | ModuleDefId::MacroId(_) | ModuleDefId::StaticId(_) => return None, }; - Some(ResolveValueResult::Partial(ty, idx, def.import)) + Some((ResolveValueResult::Partial(ty, unresolved_idx, def.import), prefix_info)) } } } @@ -958,7 +987,7 @@ impl ModuleItemMap { db: &dyn DefDatabase, path: &ModPath, ) -> Option<(TypeNs, Option, Option)> { - let (module_def, idx) = + let (module_def, idx, _) = self.def_map.resolve_path_locally(db, self.module_id, path, BuiltinShadowMode::Other); let (res, import) = to_type_ns(module_def)?; Some((res, idx, import)) 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 5720539d34e3f..25bb3a76de2c5 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs @@ -16,6 +16,7 @@ pub(crate) mod cast; pub(crate) mod closure; mod coerce; +mod diagnostics; mod expr; mod mutability; mod pat; @@ -57,15 +58,20 @@ use crate::{ db::HirDatabase, fold_tys, generics::Generics, - infer::{coerce::CoerceMany, expr::ExprIsRead, unify::InferenceTable}, + infer::{ + coerce::CoerceMany, + diagnostics::{Diagnostics, InferenceTyLoweringContext as TyLoweringContext}, + expr::ExprIsRead, + unify::InferenceTable, + }, lower::{diagnostics::TyLoweringDiagnostic, ImplTraitLoweringMode}, mir::MirSpan, to_assoc_type_id, traits::FnTrait, utils::{InTypeConstIdMetadata, UnevaluatedConstEvaluatorFolder}, AliasEq, AliasTy, Binders, ClosureId, Const, DomainGoal, GenericArg, Goal, ImplTraitId, - ImplTraitIdx, InEnvironment, Interner, Lifetime, OpaqueTyId, ParamLoweringMode, ProjectionTy, - Substitution, TraitEnvironment, Ty, TyBuilder, TyExt, + ImplTraitIdx, InEnvironment, Interner, Lifetime, OpaqueTyId, ParamLoweringMode, + PathLoweringDiagnostic, ProjectionTy, Substitution, TraitEnvironment, Ty, TyBuilder, TyExt, }; // This lint has a false positive here. See the link below for details. @@ -276,6 +282,10 @@ pub enum InferenceDiagnostic { source: InferenceTyDiagnosticSource, diag: TyLoweringDiagnostic, }, + PathDiagnostic { + node: ExprOrPatId, + diag: PathLoweringDiagnostic, + }, } /// A mismatch between an expected and an inferred type. @@ -442,6 +452,7 @@ pub struct InferenceResult { /// [`InferenceContext`] and store the tuples substitution there. This map is the reverse of /// that which allows us to resolve a [`TupleFieldId`]s type. pub tuple_field_access_types: FxHashMap, + /// During inference this field is empty and [`InferenceContext::diagnostics`] is filled instead. pub diagnostics: Vec, pub type_of_expr: ArenaMap, /// For each pattern record the type it resolves to. @@ -579,6 +590,8 @@ pub(crate) struct InferenceContext<'a> { pub(crate) db: &'a dyn HirDatabase, pub(crate) owner: DefWithBodyId, pub(crate) body: &'a Body, + /// Generally you should not resolve things via this resolver. Instead create a TyLoweringContext + /// and resolve the path via its methods. This will ensure proper error reporting. pub(crate) resolver: Resolver, generics: OnceCell>, table: unify::InferenceTable<'a>, @@ -620,6 +633,8 @@ pub(crate) struct InferenceContext<'a> { /// comment on `InferenceContext::sort_closures` closure_dependencies: FxHashMap>, deferred_closures: FxHashMap, ExprId)>>, + + diagnostics: Diagnostics, } #[derive(Clone, Debug)] @@ -701,6 +716,7 @@ impl<'a> InferenceContext<'a> { deferred_closures: FxHashMap::default(), closure_dependencies: FxHashMap::default(), inside_assignment: false, + diagnostics: Diagnostics::default(), } } @@ -724,8 +740,10 @@ impl<'a> InferenceContext<'a> { mut result, mut deferred_cast_checks, tuple_field_accesses_rev, + diagnostics, .. } = self; + let mut diagnostics = diagnostics.finish(); // Destructure every single field so whenever new fields are added to `InferenceResult` we // don't forget to handle them here. let InferenceResult { @@ -733,7 +751,6 @@ impl<'a> InferenceContext<'a> { field_resolutions: _, variant_resolutions: _, assoc_resolutions, - diagnostics, type_of_expr, type_of_pat, type_of_binding, @@ -752,6 +769,7 @@ impl<'a> InferenceContext<'a> { mutated_bindings_in_closure: _, tuple_field_access_types: _, coercion_casts, + diagnostics: _, } = &mut result; table.fallback_if_possible(); @@ -866,6 +884,9 @@ impl<'a> InferenceContext<'a> { *has_errors || subst.type_parameters(Interner).any(|ty| ty.contains_unknown()); }) .collect(); + + result.diagnostics = diagnostics; + result } @@ -1238,41 +1259,28 @@ impl<'a> InferenceContext<'a> { self.result.type_of_binding.insert(id, ty); } - fn push_diagnostic(&mut self, diagnostic: InferenceDiagnostic) { - self.result.diagnostics.push(diagnostic); - } - - fn push_ty_diagnostics( - &mut self, - source: InferenceTyDiagnosticSource, - diagnostics: Vec, - ) { - self.result.diagnostics.extend( - diagnostics.into_iter().map(|diag| InferenceDiagnostic::TyDiagnostic { source, diag }), - ); + fn push_diagnostic(&self, diagnostic: InferenceDiagnostic) { + self.diagnostics.push(diagnostic); } fn with_ty_lowering( &mut self, types_map: &TypesMap, types_source: InferenceTyDiagnosticSource, - f: impl FnOnce(&mut crate::lower::TyLoweringContext<'_>) -> R, + f: impl FnOnce(&mut TyLoweringContext<'_>) -> R, ) -> R { - let mut ctx = crate::lower::TyLoweringContext::new( + let mut ctx = TyLoweringContext::new( self.db, &self.resolver, types_map, self.owner.into(), + &self.diagnostics, + types_source, ); - let result = f(&mut ctx); - self.push_ty_diagnostics(types_source, ctx.diagnostics); - result + f(&mut ctx) } - fn with_body_ty_lowering( - &mut self, - f: impl FnOnce(&mut crate::lower::TyLoweringContext<'_>) -> R, - ) -> R { + fn with_body_ty_lowering(&mut self, f: impl FnOnce(&mut TyLoweringContext<'_>) -> R) -> R { self.with_ty_lowering(&self.body.types, InferenceTyDiagnosticSource::Body, f) } @@ -1451,51 +1459,55 @@ impl<'a> InferenceContext<'a> { } } - fn resolve_variant(&mut self, path: Option<&Path>, value_ns: bool) -> (Ty, Option) { + fn resolve_variant( + &mut self, + node: ExprOrPatId, + path: Option<&Path>, + value_ns: bool, + ) -> (Ty, Option) { let path = match path { Some(path) => path, None => return (self.err_ty(), None), }; - let mut ctx = crate::lower::TyLoweringContext::new( + let mut ctx = TyLoweringContext::new( self.db, &self.resolver, &self.body.types, self.owner.into(), + &self.diagnostics, + InferenceTyDiagnosticSource::Body, ); let (resolution, unresolved) = if value_ns { - match self.resolver.resolve_path_in_value_ns(self.db.upcast(), path, HygieneId::ROOT) { - Some(ResolveValueResult::ValueNs(value, _)) => match value { + let Some(res) = ctx.resolve_path_in_value_ns(path, node, HygieneId::ROOT) else { + return (self.err_ty(), None); + }; + match res { + ResolveValueResult::ValueNs(value, _) => match value { ValueNs::EnumVariantId(var) => { let substs = ctx.substs_from_path(path, var.into(), true); - self.push_ty_diagnostics( - InferenceTyDiagnosticSource::Body, - ctx.diagnostics, - ); + drop(ctx); let ty = self.db.ty(var.lookup(self.db.upcast()).parent.into()); let ty = self.insert_type_vars(ty.substitute(Interner, &substs)); return (ty, Some(var.into())); } ValueNs::StructId(strukt) => { let substs = ctx.substs_from_path(path, strukt.into(), true); - self.push_ty_diagnostics( - InferenceTyDiagnosticSource::Body, - ctx.diagnostics, - ); + drop(ctx); let ty = self.db.ty(strukt.into()); let ty = self.insert_type_vars(ty.substitute(Interner, &substs)); return (ty, Some(strukt.into())); } ValueNs::ImplSelf(impl_id) => (TypeNs::SelfType(impl_id), None), - _ => return (self.err_ty(), None), + _ => { + drop(ctx); + return (self.err_ty(), None); + } }, - Some(ResolveValueResult::Partial(typens, unresolved, _)) => { - (typens, Some(unresolved)) - } - None => return (self.err_ty(), None), + ResolveValueResult::Partial(typens, unresolved, _) => (typens, Some(unresolved)), } } else { - match self.resolver.resolve_path_in_type_ns(self.db.upcast(), path) { - Some((it, idx, _)) => (it, idx), + match ctx.resolve_path_in_type_ns(path, node) { + Some((it, idx)) => (it, idx), None => return (self.err_ty(), None), } }; @@ -1506,21 +1518,21 @@ impl<'a> InferenceContext<'a> { return match resolution { TypeNs::AdtId(AdtId::StructId(strukt)) => { let substs = ctx.substs_from_path(path, strukt.into(), true); - self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics); + drop(ctx); let ty = self.db.ty(strukt.into()); let ty = self.insert_type_vars(ty.substitute(Interner, &substs)); forbid_unresolved_segments((ty, Some(strukt.into())), unresolved) } TypeNs::AdtId(AdtId::UnionId(u)) => { let substs = ctx.substs_from_path(path, u.into(), true); - self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics); + drop(ctx); let ty = self.db.ty(u.into()); let ty = self.insert_type_vars(ty.substitute(Interner, &substs)); forbid_unresolved_segments((ty, Some(u.into())), unresolved) } TypeNs::EnumVariantId(var) => { let substs = ctx.substs_from_path(path, var.into(), true); - self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics); + drop(ctx); let ty = self.db.ty(var.lookup(self.db.upcast()).parent.into()); let ty = self.insert_type_vars(ty.substitute(Interner, &substs)); forbid_unresolved_segments((ty, Some(var.into())), unresolved) @@ -1531,6 +1543,7 @@ impl<'a> InferenceContext<'a> { let mut ty = self.db.impl_self_ty(impl_id).substitute(Interner, &substs); let Some(mut remaining_idx) = unresolved else { + drop(ctx); return self.resolve_variant_on_alias(ty, None, mod_path); }; @@ -1538,6 +1551,7 @@ impl<'a> InferenceContext<'a> { // We need to try resolving unresolved segments one by one because each may resolve // to a projection, which `TyLoweringContext` cannot handle on its own. + let mut tried_resolving_once = false; while !remaining_segments.is_empty() { let resolved_segment = path.segments().get(remaining_idx - 1).unwrap(); let current_segment = remaining_segments.take(1); @@ -1558,18 +1572,27 @@ impl<'a> InferenceContext<'a> { } } + if tried_resolving_once { + // FIXME: with `inherent_associated_types` this is allowed, but our `lower_partly_resolved_path()` + // will need to be updated to err at the correct segment. + // + // We need to stop here because otherwise the segment index passed to `lower_partly_resolved_path()` + // will be incorrect, and that can mess up error reporting. + break; + } + // `lower_partly_resolved_path()` returns `None` as type namespace unless // `remaining_segments` is empty, which is never the case here. We don't know // which namespace the new `ty` is in until normalized anyway. (ty, _) = ctx.lower_partly_resolved_path( + node, resolution, resolved_segment, current_segment, + (remaining_idx - 1) as u32, false, - &mut |_, _reason| { - // FIXME: Report an error. - }, ); + tried_resolving_once = true; ty = self.table.insert_type_vars(ty); ty = self.table.normalize_associated_types_in(ty); @@ -1582,7 +1605,7 @@ impl<'a> InferenceContext<'a> { remaining_idx += 1; remaining_segments = remaining_segments.skip(1); } - self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics); + drop(ctx); let variant = ty.as_adt().and_then(|(id, _)| match id { AdtId::StructId(s) => Some(VariantId::StructId(s)), @@ -1601,7 +1624,7 @@ impl<'a> InferenceContext<'a> { }; let substs = ctx.substs_from_path_segment(resolved_seg, Some(it.into()), true, None); - self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics); + drop(ctx); let ty = self.db.ty(it.into()); let ty = self.insert_type_vars(ty.substitute(Interner, &substs)); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/diagnostics.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/diagnostics.rs new file mode 100644 index 0000000000000..032dc37899dea --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/diagnostics.rs @@ -0,0 +1,128 @@ +//! This file contains the [`Diagnostics`] type used during inference, +//! and a wrapper around [`TyLoweringContext`] ([`InferenceTyLoweringContext`]) that replaces +//! it and takes care of diagnostics in inference. + +use std::cell::RefCell; +use std::ops::{Deref, DerefMut}; + +use hir_def::body::HygieneId; +use hir_def::hir::ExprOrPatId; +use hir_def::path::{Path, PathSegment, PathSegments}; +use hir_def::resolver::{ResolveValueResult, Resolver, TypeNs}; +use hir_def::type_ref::TypesMap; +use hir_def::TypeOwnerId; + +use crate::db::HirDatabase; +use crate::{ + InferenceDiagnostic, InferenceTyDiagnosticSource, Ty, TyLoweringContext, TyLoweringDiagnostic, +}; + +// Unfortunately, this struct needs to use interior mutability (but we encapsulate it) +// because when lowering types and paths we hold a `TyLoweringContext` that holds a reference +// to our resolver and so we cannot have mutable reference, but we really want to have +// ability to dispatch diagnostics during this work otherwise the code becomes a complete mess. +#[derive(Debug, Default, Clone)] +pub(super) struct Diagnostics(RefCell>); + +impl Diagnostics { + pub(super) fn push(&self, diagnostic: InferenceDiagnostic) { + self.0.borrow_mut().push(diagnostic); + } + + fn push_ty_diagnostics( + &self, + source: InferenceTyDiagnosticSource, + diagnostics: Vec, + ) { + self.0.borrow_mut().extend( + diagnostics.into_iter().map(|diag| InferenceDiagnostic::TyDiagnostic { source, diag }), + ); + } + + pub(super) fn finish(self) -> Vec { + self.0.into_inner() + } +} + +pub(super) struct InferenceTyLoweringContext<'a> { + ctx: TyLoweringContext<'a>, + diagnostics: &'a Diagnostics, + source: InferenceTyDiagnosticSource, +} + +impl<'a> InferenceTyLoweringContext<'a> { + pub(super) fn new( + db: &'a dyn HirDatabase, + resolver: &'a Resolver, + types_map: &'a TypesMap, + owner: TypeOwnerId, + diagnostics: &'a Diagnostics, + source: InferenceTyDiagnosticSource, + ) -> Self { + Self { ctx: TyLoweringContext::new(db, resolver, types_map, owner), diagnostics, source } + } + + pub(super) fn resolve_path_in_type_ns( + &mut self, + path: &Path, + node: ExprOrPatId, + ) -> Option<(TypeNs, Option)> { + let diagnostics = self.diagnostics; + self.ctx.resolve_path_in_type_ns(path, &mut |_, diag| { + diagnostics.push(InferenceDiagnostic::PathDiagnostic { node, diag }) + }) + } + + pub(super) fn resolve_path_in_value_ns( + &mut self, + path: &Path, + node: ExprOrPatId, + hygiene_id: HygieneId, + ) -> Option { + let diagnostics = self.diagnostics; + self.ctx.resolve_path_in_value_ns(path, hygiene_id, &mut |_, diag| { + diagnostics.push(InferenceDiagnostic::PathDiagnostic { node, diag }) + }) + } + + pub(super) fn lower_partly_resolved_path( + &mut self, + node: ExprOrPatId, + resolution: TypeNs, + resolved_segment: PathSegment<'_>, + remaining_segments: PathSegments<'_>, + resolved_segment_idx: u32, + infer_args: bool, + ) -> (Ty, Option) { + let diagnostics = self.diagnostics; + self.ctx.lower_partly_resolved_path( + resolution, + resolved_segment, + remaining_segments, + resolved_segment_idx, + infer_args, + &mut |_, diag| diagnostics.push(InferenceDiagnostic::PathDiagnostic { node, diag }), + ) + } +} + +impl<'a> Deref for InferenceTyLoweringContext<'a> { + type Target = TyLoweringContext<'a>; + + fn deref(&self) -> &Self::Target { + &self.ctx + } +} + +impl DerefMut for InferenceTyLoweringContext<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.ctx + } +} + +impl Drop for InferenceTyLoweringContext<'_> { + fn drop(&mut self) { + self.diagnostics + .push_ty_diagnostics(self.source, std::mem::take(&mut self.ctx.diagnostics)); + } +} 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 a13541be6958d..3dccd536bef83 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 @@ -531,7 +531,7 @@ impl InferenceContext<'_> { (params, ret_ty) } None => { - self.result.diagnostics.push(InferenceDiagnostic::ExpectedFunction { + self.push_diagnostic(InferenceDiagnostic::ExpectedFunction { call_expr: tgt_expr, found: callee_ty.clone(), }); @@ -707,7 +707,7 @@ impl InferenceContext<'_> { self.result.standard_types.never.clone() } Expr::RecordLit { path, fields, spread, .. } => { - let (ty, def_id) = self.resolve_variant(path.as_deref(), false); + let (ty, def_id) = self.resolve_variant(tgt_expr.into(), path.as_deref(), false); if let Some(t) = expected.only_has_type(&mut self.table) { self.unify(&ty, &t); @@ -1816,9 +1816,10 @@ impl InferenceContext<'_> { if !is_public { if let Either::Left(field) = field_id { // FIXME: Merge this diagnostic into UnresolvedField? - self.result - .diagnostics - .push(InferenceDiagnostic::PrivateField { expr: tgt_expr, field }); + self.push_diagnostic(InferenceDiagnostic::PrivateField { + expr: tgt_expr, + field, + }); } } ty @@ -1835,7 +1836,7 @@ impl InferenceContext<'_> { VisibleFromModule::Filter(self.resolver.module()), name, ); - self.result.diagnostics.push(InferenceDiagnostic::UnresolvedField { + self.push_diagnostic(InferenceDiagnostic::UnresolvedField { expr: tgt_expr, receiver: receiver_ty.clone(), name: name.clone(), @@ -1927,7 +1928,7 @@ impl InferenceContext<'_> { }, ); - self.result.diagnostics.push(InferenceDiagnostic::UnresolvedMethodCall { + self.push_diagnostic(InferenceDiagnostic::UnresolvedMethodCall { expr: tgt_expr, receiver: receiver_ty.clone(), name: method_name.clone(), 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 50e761196ec1b..00398f019da52 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 @@ -35,7 +35,7 @@ impl InferenceContext<'_> { ellipsis: Option, subs: &[PatId], ) -> Ty { - let (ty, def) = self.resolve_variant(path, true); + let (ty, def) = self.resolve_variant(id.into(), path, true); let var_data = def.map(|it| it.variant_data(self.db.upcast())); if let Some(variant) = def { self.write_variant_resolution(id.into(), variant); @@ -115,7 +115,7 @@ impl InferenceContext<'_> { id: PatId, subs: impl ExactSizeIterator, ) -> Ty { - let (ty, def) = self.resolve_variant(path, false); + let (ty, def) = self.resolve_variant(id.into(), path, false); if let Some(variant) = def { self.write_variant_resolution(id.into(), variant); } 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 a6296c3af233b..d79bf9a05e835 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 @@ -14,6 +14,7 @@ use crate::{ builder::ParamKind, consteval, error_lifetime, generics::generics, + infer::diagnostics::InferenceTyLoweringContext as TyLoweringContext, method_resolution::{self, VisibleFromModule}, to_chalk_trait_id, InferenceDiagnostic, Interner, Substitution, TraitRef, TraitRefExt, Ty, TyBuilder, TyExt, TyKind, ValueTyDefId, @@ -147,36 +148,38 @@ impl InferenceContext<'_> { path: &Path, id: ExprOrPatId, ) -> Option<(ValueNs, Option>)> { + // Don't use `self.make_ty()` here as we need `orig_ns`. + let mut ctx = TyLoweringContext::new( + self.db, + &self.resolver, + &self.body.types, + self.owner.into(), + &self.diagnostics, + InferenceTyDiagnosticSource::Body, + ); let (value, self_subst) = if let Some(type_ref) = path.type_anchor() { let last = path.segments().last()?; - // Don't use `self.make_ty()` here as we need `orig_ns`. - let mut ctx = crate::lower::TyLoweringContext::new( - self.db, - &self.resolver, - &self.body.types, - self.owner.into(), - ); let (ty, orig_ns) = ctx.lower_ty_ext(type_ref); let ty = self.table.insert_type_vars(ty); let ty = self.table.normalize_associated_types_in(ty); let remaining_segments_for_ty = path.segments().take(path.segments().len() - 1); let (ty, _) = ctx.lower_ty_relative_path(ty, orig_ns, remaining_segments_for_ty); - self.push_ty_diagnostics(InferenceTyDiagnosticSource::Body, ctx.diagnostics); + drop(ctx); let ty = self.table.insert_type_vars(ty); let ty = self.table.normalize_associated_types_in(ty); self.resolve_ty_assoc_item(ty, last.name, id).map(|(it, substs)| (it, Some(substs)))? } else { let hygiene = self.body.expr_or_pat_path_hygiene(id); // FIXME: report error, unresolved first path segment - let value_or_partial = - self.resolver.resolve_path_in_value_ns(self.db.upcast(), path, hygiene)?; + let value_or_partial = ctx.resolve_path_in_value_ns(path, id, hygiene)?; + drop(ctx); match value_or_partial { ResolveValueResult::ValueNs(it, _) => (it, None), ResolveValueResult::Partial(def, remaining_index, _) => self - .resolve_assoc_item(def, path, remaining_index, id) + .resolve_assoc_item(id, def, path, remaining_index, id) .map(|(it, substs)| (it, Some(substs)))?, } }; @@ -212,6 +215,7 @@ impl InferenceContext<'_> { fn resolve_assoc_item( &mut self, + node: ExprOrPatId, def: TypeNs, path: &Path, remaining_index: usize, @@ -260,17 +264,23 @@ impl InferenceContext<'_> { // as Iterator>::Item::default`) let remaining_segments_for_ty = remaining_segments.take(remaining_segments.len() - 1); - let (ty, _) = self.with_body_ty_lowering(|ctx| { - ctx.lower_partly_resolved_path( - def, - resolved_segment, - remaining_segments_for_ty, - true, - &mut |_, _reason| { - // FIXME: Report an error. - }, - ) - }); + let mut ctx = TyLoweringContext::new( + self.db, + &self.resolver, + &self.body.types, + self.owner.into(), + &self.diagnostics, + InferenceTyDiagnosticSource::Body, + ); + let (ty, _) = ctx.lower_partly_resolved_path( + node, + def, + resolved_segment, + remaining_segments_for_ty, + (remaining_index - 1) as u32, + true, + ); + drop(ctx); if ty.is_unknown() { return None; } 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 2610bb704e26d..4bfa51c849018 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs @@ -23,6 +23,7 @@ use chalk_ir::{ use either::Either; use hir_def::{ + body::HygieneId, builtin_type::BuiltinType, data::adt::StructKind, expander::Expander, @@ -31,9 +32,9 @@ use hir_def::{ WherePredicateTypeTarget, }, lang_item::LangItem, - nameres::MacroSubNs, + nameres::{MacroSubNs, ResolvePathResultPrefixInfo}, path::{GenericArg, GenericArgs, ModPath, Path, PathKind, PathSegment, PathSegments}, - resolver::{HasResolver, LifetimeNs, Resolver, TypeNs}, + resolver::{HasResolver, LifetimeNs, ResolveValueResult, Resolver, TypeNs, ValueNs}, type_ref::{ ConstRef, LifetimeRef, PathId, TraitBoundModifier, TraitRef as HirTraitRef, TypeBound, TypeRef, TypeRefId, TypesMap, TypesSourceMap, @@ -514,8 +515,8 @@ impl<'a> TyLoweringContext<'a> { /// This is only for `generic_predicates_for_param`, where we can't just /// lower the self types of the predicates since that could lead to cycles. /// So we just check here if the `type_ref` resolves to a generic param, and which. - fn lower_ty_only_param(&self, type_ref: TypeRefId) -> Option { - let type_ref = &self.types_map[type_ref]; + fn lower_ty_only_param(&mut self, type_ref_id: TypeRefId) -> Option { + let type_ref = &self.types_map[type_ref_id]; let path = match type_ref { TypeRef::Path(path) => path, _ => return None, @@ -526,8 +527,10 @@ impl<'a> TyLoweringContext<'a> { if path.segments().len() > 1 { return None; } - let resolution = match self.resolver.resolve_path_in_type_ns(self.db.upcast(), path) { - Some((it, None, _)) => it, + let resolution = match self + .resolve_path_in_type_ns(path, &mut Self::on_path_diagnostic_callback(type_ref_id)) + { + Some((it, None)) => it, _ => return None, }; match resolution { @@ -562,11 +565,9 @@ impl<'a> TyLoweringContext<'a> { resolution: TypeNs, resolved_segment: PathSegment<'_>, remaining_segments: PathSegments<'_>, + _resolved_segment_idx: u32, infer_args: bool, - on_prohibited_generics_for_resolved_segment: &mut dyn FnMut( - &mut Self, - GenericArgsProhibitedReason, - ), + _on_diagnostic: &mut dyn FnMut(&mut Self, PathLoweringDiagnostic), ) -> (Ty, Option) { let ty = match resolution { TypeNs::TraitId(trait_) => { @@ -633,44 +634,28 @@ impl<'a> TyLoweringContext<'a> { // FIXME(trait_alias): Implement trait alias. return (TyKind::Error.intern(Interner), None); } - TypeNs::GenericParam(param_id) => { - if resolved_segment.args_and_bindings.is_some() { - on_prohibited_generics_for_resolved_segment( - self, - GenericArgsProhibitedReason::TyParam, - ); + TypeNs::GenericParam(param_id) => match self.type_param_mode { + ParamLoweringMode::Placeholder => { + TyKind::Placeholder(to_placeholder_idx(self.db, param_id.into())) } + ParamLoweringMode::Variable => { + let idx = match self + .generics() + .expect("generics in scope") + .type_or_const_param_idx(param_id.into()) + { + None => { + never!("no matching generics"); + return (TyKind::Error.intern(Interner), None); + } + Some(idx) => idx, + }; - match self.type_param_mode { - ParamLoweringMode::Placeholder => { - TyKind::Placeholder(to_placeholder_idx(self.db, param_id.into())) - } - ParamLoweringMode::Variable => { - let idx = match self - .generics() - .expect("generics in scope") - .type_or_const_param_idx(param_id.into()) - { - None => { - never!("no matching generics"); - return (TyKind::Error.intern(Interner), None); - } - Some(idx) => idx, - }; - - TyKind::BoundVar(BoundVar::new(self.in_binders, idx)) - } + TyKind::BoundVar(BoundVar::new(self.in_binders, idx)) } - .intern(Interner) } + .intern(Interner), TypeNs::SelfType(impl_id) => { - if resolved_segment.args_and_bindings.is_some() { - on_prohibited_generics_for_resolved_segment( - self, - GenericArgsProhibitedReason::SelfTy, - ); - } - let generics = self.generics().expect("impl should have generic param scope"); match self.type_param_mode { @@ -696,13 +681,6 @@ impl<'a> TyLoweringContext<'a> { } } TypeNs::AdtSelfType(adt) => { - if resolved_segment.args_and_bindings.is_some() { - on_prohibited_generics_for_resolved_segment( - self, - GenericArgsProhibitedReason::SelfTy, - ); - } - let generics = generics(self.db.upcast(), adt.into()); let substs = match self.type_param_mode { ParamLoweringMode::Placeholder => generics.placeholder_subst(self.db), @@ -715,12 +693,6 @@ impl<'a> TyLoweringContext<'a> { TypeNs::AdtId(it) => self.lower_path_inner(resolved_segment, it.into(), infer_args), TypeNs::BuiltinType(it) => { - if resolved_segment.args_and_bindings.is_some() { - on_prohibited_generics_for_resolved_segment( - self, - GenericArgsProhibitedReason::PrimitiveTy, - ); - } self.lower_path_inner(resolved_segment, it.into(), infer_args) } TypeNs::TypeAliasId(it) => { @@ -732,6 +704,220 @@ impl<'a> TyLoweringContext<'a> { self.lower_ty_relative_path(ty, Some(resolution), remaining_segments) } + fn handle_type_ns_resolution( + &mut self, + resolution: &TypeNs, + resolved_segment: PathSegment<'_>, + resolved_segment_idx: usize, + on_diagnostic: &mut dyn FnMut(&mut Self, PathLoweringDiagnostic), + ) { + let mut prohibit_generics_on_resolved = |reason| { + if resolved_segment.args_and_bindings.is_some() { + on_diagnostic( + self, + PathLoweringDiagnostic::GenericArgsProhibited { + segment: resolved_segment_idx as u32, + reason, + }, + ); + } + }; + + match resolution { + TypeNs::SelfType(_) => { + prohibit_generics_on_resolved(GenericArgsProhibitedReason::SelfTy) + } + TypeNs::GenericParam(_) => { + prohibit_generics_on_resolved(GenericArgsProhibitedReason::TyParam) + } + TypeNs::AdtSelfType(_) => { + prohibit_generics_on_resolved(GenericArgsProhibitedReason::SelfTy) + } + TypeNs::BuiltinType(_) => { + prohibit_generics_on_resolved(GenericArgsProhibitedReason::PrimitiveTy) + } + TypeNs::AdtId(_) + | TypeNs::EnumVariantId(_) + | TypeNs::TypeAliasId(_) + | TypeNs::TraitId(_) + | TypeNs::TraitAliasId(_) => {} + } + } + + pub(crate) fn resolve_path_in_type_ns_fully( + &mut self, + path: &Path, + on_diagnostic: &mut dyn FnMut(&mut Self, PathLoweringDiagnostic), + ) -> Option { + let (res, unresolved) = self.resolve_path_in_type_ns(path, on_diagnostic)?; + if unresolved.is_some() { + return None; + } + Some(res) + } + + pub(crate) fn resolve_path_in_type_ns( + &mut self, + path: &Path, + on_diagnostic: &mut dyn FnMut(&mut Self, PathLoweringDiagnostic), + ) -> Option<(TypeNs, Option)> { + let (resolution, remaining_index, _) = + self.resolver.resolve_path_in_type_ns(self.db.upcast(), path)?; + let segments = path.segments(); + + match path { + // `segments.is_empty()` can occur with `self`. + Path::Normal(..) if !segments.is_empty() => (), + _ => return Some((resolution, remaining_index)), + }; + + let (module_segments, resolved_segment_idx, resolved_segment) = match remaining_index { + None => ( + segments.strip_last(), + segments.len() - 1, + segments.last().expect("resolved path has at least one element"), + ), + Some(i) => (segments.take(i - 1), i - 1, segments.get(i - 1).unwrap()), + }; + + for (i, mod_segment) in module_segments.iter().enumerate() { + if mod_segment.args_and_bindings.is_some() { + on_diagnostic( + self, + PathLoweringDiagnostic::GenericArgsProhibited { + segment: i as u32, + reason: GenericArgsProhibitedReason::Module, + }, + ); + } + } + + self.handle_type_ns_resolution( + &resolution, + resolved_segment, + resolved_segment_idx, + on_diagnostic, + ); + + Some((resolution, remaining_index)) + } + + pub(crate) fn resolve_path_in_value_ns( + &mut self, + path: &Path, + hygiene_id: HygieneId, + on_diagnostic: &mut dyn FnMut(&mut Self, PathLoweringDiagnostic), + ) -> Option { + let (res, prefix_info) = self.resolver.resolve_path_in_value_ns_with_prefix_info( + self.db.upcast(), + path, + hygiene_id, + )?; + + let segments = path.segments(); + match path { + // `segments.is_empty()` can occur with `self`. + Path::Normal(..) if !segments.is_empty() => (), + _ => return Some(res), + }; + + let (mod_segments, enum_segment) = match res { + ResolveValueResult::Partial(_, unresolved_segment, _) => { + (segments.take(unresolved_segment - 1), None) + } + ResolveValueResult::ValueNs(ValueNs::EnumVariantId(_), _) + if prefix_info == ResolvePathResultPrefixInfo::Enum => + { + (segments.strip_last_two(), segments.len().checked_sub(2)) + } + ResolveValueResult::ValueNs(..) => (segments.strip_last(), None), + }; + for (i, mod_segment) in mod_segments.iter().enumerate() { + if mod_segment.args_and_bindings.is_some() { + on_diagnostic( + self, + PathLoweringDiagnostic::GenericArgsProhibited { + segment: i as u32, + reason: GenericArgsProhibitedReason::Module, + }, + ); + } + } + + if let Some(enum_segment) = enum_segment { + if segments.get(enum_segment).is_some_and(|it| it.args_and_bindings.is_some()) + && segments.get(enum_segment + 1).is_some_and(|it| it.args_and_bindings.is_some()) + { + on_diagnostic( + self, + PathLoweringDiagnostic::GenericArgsProhibited { + segment: (enum_segment + 1) as u32, + reason: GenericArgsProhibitedReason::EnumVariant, + }, + ); + } + } + + match &res { + ResolveValueResult::ValueNs(resolution, _) => { + let resolved_segment_idx = + segments.len().checked_sub(1).unwrap_or_else(|| panic!("{path:?}")); + let resolved_segment = segments.last().unwrap(); + + let mut prohibit_generics_on_resolved = |reason| { + if resolved_segment.args_and_bindings.is_some() { + on_diagnostic( + self, + PathLoweringDiagnostic::GenericArgsProhibited { + segment: resolved_segment_idx as u32, + reason, + }, + ); + } + }; + + match resolution { + ValueNs::ImplSelf(_) => { + prohibit_generics_on_resolved(GenericArgsProhibitedReason::SelfTy) + } + // FIXME: rustc generates E0107 (incorrect number of generic arguments) and not + // E0109 (generic arguments provided for a type that doesn't accept them) for + // consts and statics, presumably as a defense against future in which consts + // and statics can be generic, or just because it was easier for rustc implementors. + // That means we'll show the wrong error code. Because of us it's easier to do it + // this way :) + ValueNs::GenericParam(_) | ValueNs::ConstId(_) => { + prohibit_generics_on_resolved(GenericArgsProhibitedReason::Const) + } + ValueNs::StaticId(_) => { + prohibit_generics_on_resolved(GenericArgsProhibitedReason::Static) + } + ValueNs::FunctionId(_) | ValueNs::StructId(_) | ValueNs::EnumVariantId(_) => {} + ValueNs::LocalBinding(_) => {} + } + } + ResolveValueResult::Partial(resolution, unresolved_idx, _) => { + let resolved_segment_idx = unresolved_idx - 1; + let resolved_segment = segments.get(resolved_segment_idx).unwrap(); + self.handle_type_ns_resolution( + resolution, + resolved_segment, + resolved_segment_idx, + on_diagnostic, + ); + } + }; + Some(res) + } + + fn on_path_diagnostic_callback( + type_ref: TypeRefId, + ) -> impl FnMut(&mut Self, PathLoweringDiagnostic) { + move |this, diag| { + this.push_diagnostic(type_ref, TyLoweringDiagnosticKind::PathDiagnostic(diag)) + } + } + pub(crate) fn lower_path(&mut self, path: &Path, path_id: PathId) -> (Ty, Option) { // Resolve the path (in type namespace) if let Some(type_ref) = path.type_anchor() { @@ -739,11 +925,13 @@ impl<'a> TyLoweringContext<'a> { return self.lower_ty_relative_path(ty, res, path.segments()); } - 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), - }; + let (resolution, remaining_index) = match self.resolve_path_in_type_ns( + path, + &mut Self::on_path_diagnostic_callback(path_id.type_ref()), + ) { + Some(it) => it, + None => return (TyKind::Error.intern(Interner), None), + }; if matches!(resolution, TypeNs::TraitId(_)) && remaining_index.is_none() { // trait object type without dyn @@ -752,38 +940,22 @@ impl<'a> TyLoweringContext<'a> { return (ty, None); } - let (module_segments, resolved_segment_idx, resolved_segment, remaining_segments) = - match remaining_index { - None => ( - path.segments().strip_last(), - path.segments().len() - 1, - path.segments().last().expect("resolved path has at least one element"), - PathSegments::EMPTY, - ), - Some(i) => ( - path.segments().take(i - 1), - i - 1, - path.segments().get(i - 1).unwrap(), - path.segments().skip(i), - ), - }; - - self.prohibit_generics(path_id, 0, module_segments, GenericArgsProhibitedReason::Module); + let (resolved_segment_idx, resolved_segment, remaining_segments) = match remaining_index { + None => ( + path.segments().len() - 1, + path.segments().last().expect("resolved path has at least one element"), + PathSegments::EMPTY, + ), + Some(i) => (i - 1, path.segments().get(i - 1).unwrap(), path.segments().skip(i)), + }; self.lower_partly_resolved_path( resolution, resolved_segment, remaining_segments, + resolved_segment_idx as u32, false, - &mut |this, reason| { - this.push_diagnostic( - path_id.type_ref(), - TyLoweringDiagnosticKind::GenericArgsProhibited { - segment: resolved_segment_idx as u32, - reason, - }, - ) - }, + &mut Self::on_path_diagnostic_callback(path_id.type_ref()), ) } @@ -1085,7 +1257,9 @@ impl<'a> TyLoweringContext<'a> { if segment.args_and_bindings.is_some() { self.push_diagnostic( path_id.type_ref(), - TyLoweringDiagnosticKind::GenericArgsProhibited { segment: idx, reason }, + TyLoweringDiagnosticKind::PathDiagnostic( + PathLoweringDiagnostic::GenericArgsProhibited { segment: idx, reason }, + ), ); } }); @@ -1097,7 +1271,10 @@ impl<'a> TyLoweringContext<'a> { explicit_self_ty: Ty, ) -> Option { let path = &self.types_map[path_id]; - let resolved = match self.resolver.resolve_path_in_type_ns_fully(self.db.upcast(), path)? { + let resolved = match self.resolve_path_in_type_ns_fully( + path, + &mut Self::on_path_diagnostic_callback(path_id.type_ref()), + )? { // FIXME(trait_alias): We need to handle trait alias here. TypeNs::TraitId(tr) => tr, _ => return None, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lower/diagnostics.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lower/diagnostics.rs index 61fedc8c3ac2d..7fe196cdbb59a 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lower/diagnostics.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower/diagnostics.rs @@ -1,3 +1,5 @@ +//! This files contains the declaration of diagnostics kinds for ty and path lowering. + use either::Either; use hir_def::type_ref::TypeRefId; @@ -11,7 +13,7 @@ pub struct TyLoweringDiagnostic { #[derive(Debug, PartialEq, Eq, Clone)] pub enum TyLoweringDiagnosticKind { - GenericArgsProhibited { segment: u32, reason: GenericArgsProhibitedReason }, + PathDiagnostic(PathLoweringDiagnostic), } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -20,8 +22,15 @@ pub enum GenericArgsProhibitedReason { TyParam, SelfTy, PrimitiveTy, + Const, + Static, /// When there is a generic enum, within the expression `Enum::Variant`, /// either `Enum` or `Variant` are allowed to have generic arguments, but not both. // FIXME: This is not used now but it should be. EnumVariant, } + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum PathLoweringDiagnostic { + GenericArgsProhibited { segment: u32, reason: GenericArgsProhibitedReason }, +} diff --git a/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs b/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs index cbb1ed95ed64d..fc77d1889c88e 100644 --- a/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs @@ -15,12 +15,12 @@ use hir_expand::{name::Name, HirFileId, InFile}; use hir_ty::{ db::HirDatabase, diagnostics::{BodyValidationDiagnostic, UnsafetyReason}, - CastError, InferenceDiagnostic, InferenceTyDiagnosticSource, TyLoweringDiagnostic, - TyLoweringDiagnosticKind, + CastError, InferenceDiagnostic, InferenceTyDiagnosticSource, PathLoweringDiagnostic, + TyLoweringDiagnostic, TyLoweringDiagnosticKind, }; use syntax::{ ast::{self, HasGenericArgs}, - AstPtr, SyntaxError, SyntaxNodePtr, TextRange, + match_ast, AstNode, AstPtr, SyntaxError, SyntaxNodePtr, TextRange, }; use triomphe::Arc; @@ -674,6 +674,39 @@ impl AnyDiagnostic { }; Self::ty_diagnostic(diag, source_map, db)? } + InferenceDiagnostic::PathDiagnostic { node, diag } => { + let source = expr_or_pat_syntax(*node)?; + let syntax = source.value.to_node(&db.parse_or_expand(source.file_id)); + let path = match_ast! { + match (syntax.syntax()) { + ast::RecordExpr(it) => it.path()?, + ast::RecordPat(it) => it.path()?, + ast::TupleStructPat(it) => it.path()?, + ast::PathExpr(it) => it.path()?, + ast::PathPat(it) => it.path()?, + _ => return None, + } + }; + Self::path_diagnostic(diag, source.with_value(path))? + } + }) + } + + fn path_diagnostic( + diag: &PathLoweringDiagnostic, + path: InFile, + ) -> Option { + Some(match diag { + &PathLoweringDiagnostic::GenericArgsProhibited { segment, reason } => { + let segment = hir_segment_to_ast_segment(&path.value, segment)?; + let args = if let Some(generics) = segment.generic_arg_list() { + AstPtr::new(&generics).wrap_left() + } else { + AstPtr::new(&segment.parenthesized_arg_list()?).wrap_right() + }; + let args = path.with_value(args); + GenericArgsProhibited { args, reason }.into() + } }) } @@ -693,17 +726,10 @@ impl AnyDiagnostic { Either::Right(source) => source, }; let syntax = || source.value.to_node(&db.parse_or_expand(source.file_id)); - Some(match diag.kind { - TyLoweringDiagnosticKind::GenericArgsProhibited { segment, reason } => { + Some(match &diag.kind { + TyLoweringDiagnosticKind::PathDiagnostic(diag) => { let ast::Type::PathType(syntax) = syntax() else { return None }; - let segment = hir_segment_to_ast_segment(&syntax.path()?, segment)?; - let args = if let Some(generics) = segment.generic_arg_list() { - AstPtr::new(&generics).wrap_left() - } else { - AstPtr::new(&segment.parenthesized_arg_list()?).wrap_right() - }; - let args = source.with_value(args); - GenericArgsProhibited { args, reason }.into() + Self::path_diagnostic(diag, source.with_value(syntax.path()?))? } }) } diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/generic_args_prohibited.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/generic_args_prohibited.rs index a319a0bcf6d60..a6d2ed322357a 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/generic_args_prohibited.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/generic_args_prohibited.rs @@ -34,6 +34,8 @@ fn describe_reason(reason: GenericArgsProhibitedReason) -> String { return "you can specify generic arguments on either the enum or the variant, but not both" .to_owned(); } + GenericArgsProhibitedReason::Const => "constants", + GenericArgsProhibitedReason::Static => "statics", }; format!("generic arguments are not allowed on {kind}") } @@ -435,6 +437,144 @@ type T = bool; impl Trait for () { type Assoc = i32; // ^^^^^^ 💡 error: generic arguments are not allowed on builtin types +} + "#, + ); + } + + #[test] + fn in_record_expr() { + check_diagnostics( + r#" +mod foo { + pub struct Bar { pub field: i32 } +} +fn baz() { + let _ = foo::<()>::Bar { field: 0 }; + // ^^^^^^ 💡 error: generic arguments are not allowed on modules +} + "#, + ); + } + + #[test] + fn in_record_pat() { + check_diagnostics( + r#" +mod foo { + pub struct Bar { field: i32 } +} +fn baz(v: foo::Bar) { + let foo::<()>::Bar { .. } = v; + // ^^^^^^ 💡 error: generic arguments are not allowed on modules +} + "#, + ); + } + + #[test] + fn in_tuple_struct_pat() { + check_diagnostics( + r#" +mod foo { + pub struct Bar(i32); +} +fn baz(v: foo::Bar) { + let foo::<()>::Bar(..) = v; + // ^^^^^^ 💡 error: generic arguments are not allowed on modules +} + "#, + ); + } + + #[test] + fn in_path_pat() { + check_diagnostics( + r#" +mod foo { + pub struct Bar; +} +fn baz(v: foo::Bar) { + let foo::<()>::Bar = v; + // ^^^^^^ 💡 error: generic arguments are not allowed on modules +} + "#, + ); + } + + #[test] + fn in_path_expr() { + check_diagnostics( + r#" +mod foo { + pub struct Bar; +} +fn baz() { + let _ = foo::<()>::Bar; + // ^^^^^^ 💡 error: generic arguments are not allowed on modules +} + "#, + ); + } + + #[test] + fn const_and_static() { + check_diagnostics( + r#" +const CONST: i32 = 0; +static STATIC: i32 = 0; +fn baz() { + let _ = CONST::<()>; + // ^^^^^^ 💡 error: generic arguments are not allowed on constants + let _ = STATIC::<()>; + // ^^^^^^ 💡 error: generic arguments are not allowed on statics +} + "#, + ); + } + + #[test] + fn enum_variant() { + check_diagnostics( + r#" +enum Enum { + Variant(A), +} +mod enum_ { + pub(super) use super::Enum::Variant as V; +} +fn baz() { + let v = Enum::<()>::Variant::<()>(()); + // ^^^^^^ 💡 error: you can specify generic arguments on either the enum or the variant, but not both + let Enum::<()>::Variant::<()>(..) = v; + // ^^^^^^ 💡 error: you can specify generic arguments on either the enum or the variant, but not both + let _ = Enum::<()>::Variant(()); + let _ = Enum::Variant::<()>(()); +} +fn foo() { + use Enum::Variant; + let _ = Variant::<()>(()); + let _ = enum_::V::<()>(()); + let _ = enum_::<()>::V::<()>(()); + // ^^^^^^ 💡 error: generic arguments are not allowed on modules +} + "#, + ); + } + + #[test] + fn dyn_trait() { + check_diagnostics( + r#" +mod foo { + pub trait Trait {} +} + +fn bar() { + let _: &dyn foo::<()>::Trait; + // ^^^^^^ 💡 error: generic arguments are not allowed on modules + let _: &foo::<()>::Trait; + // ^^^^^^ 💡 error: generic arguments are not allowed on modules } "#, ); From 1287b9362e35b981b49fa58322030e8500f1e8d6 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 24 Dec 2024 18:59:49 +0100 Subject: [PATCH 011/123] fix: Fix metrics workflow using the wrong download-artifact version --- src/tools/rust-analyzer/.github/workflows/metrics.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/.github/workflows/metrics.yaml b/src/tools/rust-analyzer/.github/workflows/metrics.yaml index 9313ca237d911..0bc65fa6ce59b 100644 --- a/src/tools/rust-analyzer/.github/workflows/metrics.yaml +++ b/src/tools/rust-analyzer/.github/workflows/metrics.yaml @@ -102,7 +102,7 @@ jobs: name: self-${{ github.sha }} - name: Download rustc_tests metrics - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: rustc_tests-${{ github.sha }} From 9e7d29688f8d3bf45ea6587b0f5b3398f7f70e56 Mon Sep 17 00:00:00 2001 From: roife Date: Wed, 25 Dec 2024 07:17:45 +0800 Subject: [PATCH 012/123] fix missing name enum when hovering on fields in variants --- .../rust-analyzer/crates/ide/src/hover/render.rs | 15 +++++++++++++-- .../rust-analyzer/crates/ide/src/hover/tests.rs | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) 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 8c5c27c47cb52..119a864eb9649 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs @@ -5,7 +5,7 @@ use either::Either; use hir::{ db::ExpandDatabase, Adt, AsAssocItem, AsExternAssocItem, AssocItemContainer, CaptureKind, DynCompatibilityViolation, HasCrate, HasSource, HirDisplay, Layout, LayoutError, - MethodViolationCode, Name, Semantics, Symbol, Trait, Type, TypeInfo, + MethodViolationCode, Name, Semantics, Symbol, Trait, Type, TypeInfo, VariantDef, }; use ide_db::{ base_db::SourceDatabase, @@ -378,7 +378,18 @@ pub(super) fn process_markup( fn definition_owner_name(db: &RootDatabase, def: &Definition, edition: Edition) -> Option { match def { - Definition::Field(f) => Some(f.parent_def(db).name(db)), + Definition::Field(f) => { + let parent = f.parent_def(db); + let parent_name = parent.name(db); + let parent_name = parent_name.display(db, edition).to_string(); + return match parent { + VariantDef::Variant(variant) => { + let enum_name = variant.parent_enum(db).name(db); + Some(format!("{}::{parent_name}", enum_name.display(db, edition))) + } + _ => Some(parent_name), + }; + } Definition::Local(l) => l.parent(db).name(db), Definition::Variant(e) => Some(e.parent_enum(db).name(db)), 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 cc926a5b568f3..ed8cd64cdbee0 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -7294,7 +7294,7 @@ enum Enum { *field* ```rust - ra_test_fixture::RecordV + ra_test_fixture::Enum::RecordV ``` ```rust From b1c091c5738d0a11e10c89a238eabaada15c8c48 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Wed, 25 Dec 2024 18:32:23 -0700 Subject: [PATCH 013/123] Improve SCIP symbols In particular, the symbol generation before this change creates a lot of symbols with the same name for different definitions. This change makes progress on symbol uniqueness, but does not fix a couple cases where it was unclear to me how to fix (see TODOs in `scip.rs`) Behavior changes: * `scip` command now reports symbol information omitted due to symbol collisions. Iterating with this on a large codebase (Zed!) resulted in the other improvements in this change. * Generally fixes providing the path to nested definitions in symbols. Instead of having special cases for a couple limited cases of nesting, implements `Definition::enclosing_definition` and uses this to walk definitions. * Parameter variables are now treated like locals. - This fixes a bug where closure captures also received symbols scoped to the containing function. To bring back parameter symbols I would want a way to filter these out, since they can cause symbol collisions. - Having symbols for them seems to be intentional in 27e2eea54fbd1edeefa2b47ddd4f552a04b86582, but no particular use is specified there. For the typical indexing purposes of SCIP I don't see why parameter symbols are useful or sensible, as function parameters are not referencable by anything but position. I can imagine they might be useful in representing diagnostics or something. * Inherent impls are now represented as `impl#[SelfType]` - a type named `impl` which takes a single type parameter. * Trait impls are now represented as `impl#[SelfType][TraitType]` - a type named `impl` which takes two type parameters. * Associated types in traits and impls are now treated like types instead of type parameters, and so are now suffixed with `#` instead of wrapped with `[]`. Treating them as type parameters seems to have been intentional in 73d9c77f2aeed394cf131dce55807be2d3f54064 but it doesn't make sense to me, so changing it. * Static variables are now treated as terms instead of `Meta`, and so receive `.` suffix instead of `:`. * Attributes are now treated as `Meta` instead of `Macro`, and so receive `:` suffix instead of `!`. * `enclosing_symbol` is now provided for labels and generic params, which are local symbols. * Fixes a bug where presence of `'` causes a descriptor name to get double wrapped in backticks, since both `fn new_descriptor` and `scip::symbol::format_symbol` have logic for wrapping in backticks. Solution is to simply delete the redundant logic. * Deletes a couple tests in moniker.rs because the cases are adequeately covered in scip.rs and the format for identifiers used in moniker.rs is clunky with the new representation for trait impls --- .../crates/hir-ty/src/display.rs | 42 ++- .../rust-analyzer/crates/hir/src/display.rs | 17 +- src/tools/rust-analyzer/crates/hir/src/lib.rs | 3 +- .../rust-analyzer/crates/ide-db/src/defs.rs | 58 ++- src/tools/rust-analyzer/crates/ide/src/lib.rs | 4 +- .../rust-analyzer/crates/ide/src/moniker.rs | 343 ++++++++++-------- .../crates/ide/src/static_index.rs | 4 - .../crates/rust-analyzer/src/cli/lsif.rs | 7 +- .../crates/rust-analyzer/src/cli/scip.rs | 314 +++++++++++----- 9 files changed, 522 insertions(+), 270 deletions(-) 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 de8ce56df641a..d8db47368267c 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/display.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/display.rs @@ -474,7 +474,7 @@ impl HirDisplay for ProjectionTy { let trait_ref = self.trait_ref(f.db); write!(f, "<")?; - fmt_trait_ref(f, &trait_ref, true)?; + fmt_trait_ref(f, &trait_ref, TraitRefFormat::SelfAsTrait)?; write!( f, ">::{}", @@ -1775,21 +1775,34 @@ fn write_bounds_like_dyn_trait( Ok(()) } +#[derive(Clone, Copy)] +pub enum TraitRefFormat { + SelfAsTrait, + SelfImplementsTrait, + OnlyTrait, +} + fn fmt_trait_ref( f: &mut HirFormatter<'_>, tr: &TraitRef, - use_as: bool, + format: TraitRefFormat, ) -> Result<(), HirDisplayError> { if f.should_truncate() { return write!(f, "{TYPE_HINT_TRUNCATION}"); } - tr.self_type_parameter(Interner).hir_fmt(f)?; - if use_as { - write!(f, " as ")?; - } else { - write!(f, ": ")?; + match format { + TraitRefFormat::SelfAsTrait => { + tr.self_type_parameter(Interner).hir_fmt(f)?; + write!(f, " as ")?; + } + TraitRefFormat::SelfImplementsTrait => { + tr.self_type_parameter(Interner).hir_fmt(f)?; + write!(f, ": ")?; + } + TraitRefFormat::OnlyTrait => {} } + let trait_ = tr.hir_trait_id(); f.start_location_link(trait_.into()); write!(f, "{}", f.db.trait_data(trait_).name.display(f.db.upcast(), f.edition()))?; @@ -1798,9 +1811,14 @@ fn fmt_trait_ref( hir_fmt_generics(f, &substs[1..], None, substs[0].ty(Interner)) } -impl HirDisplay for TraitRef { +pub struct TraitRefDisplayWrapper { + pub trait_ref: TraitRef, + pub format: TraitRefFormat, +} + +impl HirDisplay for TraitRefDisplayWrapper { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { - fmt_trait_ref(f, self, false) + fmt_trait_ref(f, &self.trait_ref, self.format) } } @@ -1811,10 +1829,12 @@ impl HirDisplay for WhereClause { } match self { - WhereClause::Implemented(trait_ref) => trait_ref.hir_fmt(f)?, + WhereClause::Implemented(trait_ref) => { + fmt_trait_ref(f, trait_ref, TraitRefFormat::SelfImplementsTrait)?; + } WhereClause::AliasEq(AliasEq { alias: AliasTy::Projection(projection_ty), ty }) => { write!(f, "<")?; - fmt_trait_ref(f, &projection_ty.trait_ref(f.db), true)?; + fmt_trait_ref(f, &projection_ty.trait_ref(f.db), TraitRefFormat::SelfAsTrait)?; write!(f, ">::",)?; let type_alias = from_assoc_type_id(projection_ty.associated_ty_id); f.start_location_link(type_alias.into()); diff --git a/src/tools/rust-analyzer/crates/hir/src/display.rs b/src/tools/rust-analyzer/crates/hir/src/display.rs index 959d62d595194..0d97585476e7e 100644 --- a/src/tools/rust-analyzer/crates/hir/src/display.rs +++ b/src/tools/rust-analyzer/crates/hir/src/display.rs @@ -22,7 +22,7 @@ use itertools::Itertools; use crate::{ Adt, AsAssocItem, AssocItem, AssocItemContainer, Const, ConstParam, Enum, ExternCrateDecl, Field, Function, GenericParam, HasCrate, HasVisibility, Impl, LifetimeParam, Macro, Module, - SelfParam, Static, Struct, Trait, TraitAlias, TupleField, TyBuilder, Type, TypeAlias, + SelfParam, Static, Struct, Trait, TraitAlias, TraitRef, TupleField, TyBuilder, Type, TypeAlias, TypeOrConstParam, TypeParam, Union, Variant, }; @@ -743,6 +743,21 @@ impl HirDisplay for Static { } } +pub struct TraitRefDisplayWrapper { + pub trait_ref: TraitRef, + pub format: hir_ty::display::TraitRefFormat, +} + +impl HirDisplay for TraitRefDisplayWrapper { + fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { + hir_ty::display::TraitRefDisplayWrapper { + format: self.format, + trait_ref: self.trait_ref.trait_ref.clone(), + } + .hir_fmt(f) + } +} + impl HirDisplay for Trait { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { write_trait_header(self, f)?; diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index ac8a62ee85060..d0fd2e8e055bd 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -96,6 +96,7 @@ use crate::db::{DefDatabase, HirDatabase}; pub use crate::{ attrs::{resolve_doc_path_on, HasAttrs}, diagnostics::*, + display::TraitRefDisplayWrapper, has_source::HasSource, semantics::{ PathResolution, Semantics, SemanticsImpl, SemanticsScope, TypeInfo, VisibleTraits, @@ -148,7 +149,7 @@ pub use { hir_ty::{ consteval::ConstEvalError, diagnostics::UnsafetyReason, - display::{ClosureStyle, HirDisplay, HirDisplayError, HirWrite}, + display::{ClosureStyle, HirDisplay, HirDisplayError, HirWrite, TraitRefFormat}, dyn_compatibility::{DynCompatibilityViolation, MethodViolationCode}, layout::LayoutError, mir::{MirEvalError, MirLowerError}, 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 73b73736ce88b..91d5cf294550c 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/defs.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/defs.rs @@ -13,10 +13,10 @@ use either::Either; use hir::{ Adt, AsAssocItem, AsExternAssocItem, AssocItem, AttributeTemplate, BuiltinAttr, BuiltinType, Const, Crate, DefWithBody, DeriveHelper, DocLinkDef, ExternAssocItem, ExternCrateDecl, Field, - Function, GenericParam, GenericSubstitution, HasVisibility, HirDisplay, Impl, InlineAsmOperand, - Label, Local, Macro, Module, ModuleDef, Name, PathResolution, Semantics, Static, - StaticLifetime, Struct, ToolModule, Trait, TraitAlias, TupleField, TypeAlias, Variant, - VariantDef, Visibility, + Function, GenericDef, GenericParam, GenericSubstitution, HasContainer, HasVisibility, + HirDisplay, Impl, InlineAsmOperand, ItemContainer, Label, Local, Macro, Module, ModuleDef, + Name, PathResolution, Semantics, Static, StaticLifetime, Struct, ToolModule, Trait, TraitAlias, + TupleField, TypeAlias, Variant, VariantDef, Visibility, }; use span::Edition; use stdx::{format_to, impl_from}; @@ -98,8 +98,30 @@ impl Definition { pub fn enclosing_definition(&self, db: &RootDatabase) -> Option { match self { + Definition::Macro(it) => Some(it.module(db).into()), + Definition::Module(it) => it.parent(db).map(Definition::Module), + Definition::Field(it) => Some(it.parent_def(db).into()), + Definition::Function(it) => it.container(db).try_into().ok(), + Definition::Adt(it) => Some(it.module(db).into()), + Definition::Const(it) => it.container(db).try_into().ok(), + Definition::Static(it) => it.container(db).try_into().ok(), + Definition::Trait(it) => it.container(db).try_into().ok(), + Definition::TraitAlias(it) => it.container(db).try_into().ok(), + Definition::TypeAlias(it) => it.container(db).try_into().ok(), + Definition::Variant(it) => Some(Adt::Enum(it.parent_enum(db)).into()), + Definition::SelfType(it) => Some(it.module(db).into()), Definition::Local(it) => it.parent(db).try_into().ok(), - _ => None, + Definition::GenericParam(it) => Some(it.parent().into()), + Definition::Label(it) => it.parent(db).try_into().ok(), + Definition::ExternCrateDecl(it) => it.container(db).try_into().ok(), + Definition::DeriveHelper(it) => Some(it.derive().module(db).into()), + Definition::InlineAsmOperand(it) => it.parent(db).try_into().ok(), + Definition::BuiltinAttr(_) + | Definition::BuiltinType(_) + | Definition::BuiltinLifetime(_) + | Definition::TupleField(_) + | Definition::ToolModule(_) + | Definition::InlineAsmRegOrRegClass(_) => None, } } @@ -932,3 +954,29 @@ impl TryFrom for Definition { } } } + +impl TryFrom for Definition { + type Error = (); + fn try_from(container: ItemContainer) -> Result { + match container { + ItemContainer::Trait(it) => Ok(it.into()), + ItemContainer::Impl(it) => Ok(it.into()), + ItemContainer::Module(it) => Ok(it.into()), + ItemContainer::ExternBlock() | ItemContainer::Crate(_) => Err(()), + } + } +} + +impl From for Definition { + fn from(def: GenericDef) -> Self { + match def { + GenericDef::Function(it) => it.into(), + GenericDef::Adt(it) => it.into(), + GenericDef::Trait(it) => it.into(), + GenericDef::TraitAlias(it) => it.into(), + GenericDef::TypeAlias(it) => it.into(), + GenericDef::Impl(it) => it.into(), + GenericDef::Const(it) => it.into(), + } + } +} diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs index fe2760d2ba6fe..1637d578fdf1e 100644 --- a/src/tools/rust-analyzer/crates/ide/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide/src/lib.rs @@ -96,8 +96,8 @@ pub use crate::{ join_lines::JoinLinesConfig, markup::Markup, moniker::{ - MonikerDescriptorKind, MonikerKind, MonikerResult, PackageInformation, - SymbolInformationKind, + Moniker, MonikerDescriptorKind, MonikerIdentifier, MonikerKind, MonikerResult, + PackageInformation, SymbolInformationKind, }, move_item::Direction, navigation_target::{NavigationTarget, TryToNav, UpmappingResult}, diff --git a/src/tools/rust-analyzer/crates/ide/src/moniker.rs b/src/tools/rust-analyzer/crates/ide/src/moniker.rs index 14781b2129693..adb4850338134 100644 --- a/src/tools/rust-analyzer/crates/ide/src/moniker.rs +++ b/src/tools/rust-analyzer/crates/ide/src/moniker.rs @@ -3,7 +3,10 @@ use core::fmt; -use hir::{Adt, AsAssocItem, AssocItemContainer, Crate, MacroKind, Semantics}; +use hir::{ + Adt, AsAssocItem, Crate, HirDisplay, MacroKind, Semantics, TraitRefDisplayWrapper, + TraitRefFormat, +}; use ide_db::{ base_db::{CrateOrigin, LangCrateOrigin}, defs::{Definition, IdentClass}, @@ -11,6 +14,7 @@ use ide_db::{ FilePosition, RootDatabase, }; use itertools::Itertools; +use span::Edition; use syntax::{AstNode, SyntaxKind::*, T}; use crate::{doc_links::token_as_doc_comment, parent_module::crates_for, RangeInfo}; @@ -57,8 +61,8 @@ pub enum SymbolInformationKind { impl From for MonikerDescriptorKind { fn from(value: SymbolInformationKind) -> Self { match value { - SymbolInformationKind::AssociatedType => Self::TypeParameter, - SymbolInformationKind::Attribute => Self::Macro, + SymbolInformationKind::AssociatedType => Self::Type, + SymbolInformationKind::Attribute => Self::Meta, SymbolInformationKind::Constant => Self::Term, SymbolInformationKind::Enum => Self::Type, SymbolInformationKind::EnumMember => Self::Type, @@ -70,7 +74,7 @@ impl From for MonikerDescriptorKind { SymbolInformationKind::Parameter => Self::Parameter, SymbolInformationKind::SelfParameter => Self::Parameter, SymbolInformationKind::StaticMethod => Self::Method, - SymbolInformationKind::StaticVariable => Self::Meta, + SymbolInformationKind::StaticVariable => Self::Term, SymbolInformationKind::Struct => Self::Type, SymbolInformationKind::Trait => Self::Type, SymbolInformationKind::TraitMethod => Self::Method, @@ -109,10 +113,12 @@ pub enum MonikerKind { } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct MonikerResult { - pub identifier: MonikerIdentifier, - pub kind: MonikerKind, - pub package_information: PackageInformation, +pub enum MonikerResult { + /// Uniquely identifies a definition. + Moniker(Moniker), + /// Specifies that the definition is a local, and so does not have a unique identifier. Provides + /// a unique identifier for the container. + Local { enclosing_moniker: Option }, } impl MonikerResult { @@ -121,6 +127,15 @@ impl MonikerResult { } } +/// Information which uniquely identifies a definition which might be referenceable outside of the +/// source file. Visibility declarations do not affect presence. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Moniker { + pub identifier: MonikerIdentifier, + pub kind: MonikerKind, + pub package_information: PackageInformation, +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct PackageInformation { pub name: String, @@ -232,157 +247,129 @@ pub(crate) fn def_to_kind(db: &RootDatabase, def: Definition) -> SymbolInformati } } +/// Computes a `MonikerResult` for a definition. Result cases: +/// +/// `Some(MonikerResult::Moniker(_))` provides a unique `Moniker` which refers to a definition. +/// +/// `Some(MonikerResult::Local { .. })` provides a `Moniker` for the definition enclosing a local. +/// +/// `None` is returned in the following cases: +/// +/// * Inherent impl definitions, as they cannot be uniquely identified (multiple are allowed for the +/// same type). +/// +/// * Definitions which are not in a module: `BuiltinAttr`, `BuiltinType`, `BuiltinLifetime`, +/// `TupleField`, `ToolModule`, and `InlineAsmRegOrRegClass`. TODO: it might be sensible to +/// provide monikers that refer to some non-existent crate of compiler builtin definitions. pub(crate) fn def_to_moniker( db: &RootDatabase, - def: Definition, + definition: Definition, from_crate: Crate, ) -> Option { - if matches!( - def, - Definition::GenericParam(_) - | Definition::Label(_) - | Definition::DeriveHelper(_) - | Definition::BuiltinAttr(_) - | Definition::ToolModule(_) - ) { - return None; + match definition { + // Not possible to give sensible unique symbols for inherent impls, as multiple can be + // defined for the same type. + Definition::SelfType(impl_) if impl_.trait_(db).is_none() => { + return None; + } + Definition::Local(_) | Definition::Label(_) | Definition::GenericParam(_) => { + return Some(MonikerResult::Local { + enclosing_moniker: enclosing_def_to_moniker(db, definition, from_crate), + }); + } + _ => {} } + Some(MonikerResult::Moniker(def_to_non_local_moniker(db, definition, from_crate)?)) +} - let module = def.module(db)?; - let krate = module.krate(); - let edition = krate.edition(db); - let mut description = vec![]; - description.extend(module.path_to_root(db).into_iter().filter_map(|x| { - Some(MonikerDescriptor { - name: x.name(db)?.display(db, edition).to_string(), - desc: def_to_kind(db, x.into()).into(), - }) - })); - - // Handle associated items within a trait - if let Some(assoc) = def.as_assoc_item(db) { - let container = assoc.container(db); - match container { - AssocItemContainer::Trait(trait_) => { - // Because different traits can have functions with the same name, - // we have to include the trait name as part of the moniker for uniqueness. - description.push(MonikerDescriptor { - name: trait_.name(db).display(db, edition).to_string(), - desc: def_to_kind(db, trait_.into()).into(), - }); - } - AssocItemContainer::Impl(impl_) => { - // Because a struct can implement multiple traits, for implementations - // we add both the struct name and the trait name to the path - if let Some(adt) = impl_.self_ty(db).as_adt() { - description.push(MonikerDescriptor { - name: adt.name(db).display(db, edition).to_string(), - desc: def_to_kind(db, adt.into()).into(), - }); - } - - if let Some(trait_) = impl_.trait_(db) { - description.push(MonikerDescriptor { - name: trait_.name(db).display(db, edition).to_string(), - desc: def_to_kind(db, trait_.into()).into(), - }); - } - } +fn enclosing_def_to_moniker( + db: &RootDatabase, + mut def: Definition, + from_crate: Crate, +) -> Option { + loop { + let enclosing_def = def.enclosing_definition(db)?; + if let Some(enclosing_moniker) = def_to_non_local_moniker(db, enclosing_def, from_crate) { + return Some(enclosing_moniker); } + def = enclosing_def; } +} - if let Definition::Field(it) = def { - description.push(MonikerDescriptor { - name: it.parent_def(db).name(db).display(db, edition).to_string(), - desc: def_to_kind(db, it.parent_def(db).into()).into(), - }); +fn def_to_non_local_moniker( + db: &RootDatabase, + definition: Definition, + from_crate: Crate, +) -> Option { + match definition { + // Not possible to give sensible unique symbols for inherent impls, as multiple can be + // defined for the same type. + Definition::SelfType(impl_) if impl_.trait_(db).is_none() => { + return None; + } + _ => {} } - // Qualify locals/parameters by their parent definition name. - if let Definition::Local(it) = def { - let parent = Definition::try_from(it.parent(db)).ok(); - if let Some(parent) = parent { - let parent_name = parent.name(db); - if let Some(name) = parent_name { - description.push(MonikerDescriptor { - name: name.display(db, edition).to_string(), - desc: def_to_kind(db, parent).into(), + let module = definition.module(db)?; + let krate = module.krate(); + let edition = krate.edition(db); + + // Add descriptors for this definition and every enclosing definition. + let mut reverse_description = vec![]; + let mut def = definition; + loop { + match def { + Definition::SelfType(impl_) => { + if let Some(trait_ref) = impl_.trait_ref(db) { + // Trait impls use `trait_type` constraint syntax for the 2nd parameter. + let trait_ref_for_display = + TraitRefDisplayWrapper { trait_ref, format: TraitRefFormat::OnlyTrait }; + reverse_description.push(MonikerDescriptor { + name: display(db, edition, module, trait_ref_for_display), + desc: MonikerDescriptorKind::TypeParameter, + }); + } + // Both inherent and trait impls use `self_type` as the first parameter. + reverse_description.push(MonikerDescriptor { + name: display(db, edition, module, impl_.self_ty(db)), + desc: MonikerDescriptorKind::TypeParameter, + }); + reverse_description.push(MonikerDescriptor { + name: "impl".to_owned(), + desc: MonikerDescriptorKind::Type, }); } - } - } - - let desc = def_to_kind(db, def).into(); - - let name_desc = match def { - // These are handled by top-level guard (for performance). - Definition::GenericParam(_) - | Definition::Label(_) - | Definition::DeriveHelper(_) - | Definition::BuiltinLifetime(_) - | Definition::BuiltinAttr(_) - | Definition::ToolModule(_) - | Definition::InlineAsmRegOrRegClass(_) - | Definition::InlineAsmOperand(_) => return None, - - Definition::Local(local) => { - if !local.is_param(db) { - return None; + _ => { + if let Some(name) = def.name(db) { + reverse_description.push(MonikerDescriptor { + name: name.display(db, edition).to_string(), + desc: def_to_kind(db, def).into(), + }); + } else if reverse_description.is_empty() { + // Don't allow the last descriptor to be absent. + return None; + } else { + match def { + Definition::Module(module) if module.is_crate_root() => {} + _ => { + tracing::error!( + "Encountered enclosing definition with no name: {:?}", + def + ); + } + } + } } - - MonikerDescriptor { name: local.name(db).display(db, edition).to_string(), desc } - } - Definition::Macro(m) => { - MonikerDescriptor { name: m.name(db).display(db, edition).to_string(), desc } - } - Definition::Function(f) => { - MonikerDescriptor { name: f.name(db).display(db, edition).to_string(), desc } - } - Definition::Variant(v) => { - MonikerDescriptor { name: v.name(db).display(db, edition).to_string(), desc } - } - Definition::Const(c) => { - MonikerDescriptor { name: c.name(db)?.display(db, edition).to_string(), desc } - } - Definition::Trait(trait_) => { - MonikerDescriptor { name: trait_.name(db).display(db, edition).to_string(), desc } - } - Definition::TraitAlias(ta) => { - MonikerDescriptor { name: ta.name(db).display(db, edition).to_string(), desc } - } - Definition::TypeAlias(ta) => { - MonikerDescriptor { name: ta.name(db).display(db, edition).to_string(), desc } - } - Definition::Module(m) => { - MonikerDescriptor { name: m.name(db)?.display(db, edition).to_string(), desc } } - Definition::BuiltinType(b) => { - MonikerDescriptor { name: b.name().display(db, edition).to_string(), desc } - } - Definition::SelfType(imp) => MonikerDescriptor { - name: imp.self_ty(db).as_adt()?.name(db).display(db, edition).to_string(), - desc, - }, - Definition::Field(it) => { - MonikerDescriptor { name: it.name(db).display(db, edition).to_string(), desc } - } - Definition::TupleField(it) => { - MonikerDescriptor { name: it.name().display(db, edition).to_string(), desc } - } - Definition::Adt(adt) => { - MonikerDescriptor { name: adt.name(db).display(db, edition).to_string(), desc } - } - Definition::Static(s) => { - MonikerDescriptor { name: s.name(db).display(db, edition).to_string(), desc } - } - Definition::ExternCrateDecl(m) => { - MonikerDescriptor { name: m.name(db).display(db, edition).to_string(), desc } - } - }; - - description.push(name_desc); + let Some(next_def) = def.enclosing_definition(db) else { + break; + }; + def = next_def; + } + reverse_description.reverse(); + let description = reverse_description; - Some(MonikerResult { + Some(Moniker { identifier: MonikerIdentifier { crate_name: krate.display_name(db)?.crate_name().to_string(), description, @@ -417,17 +404,58 @@ pub(crate) fn def_to_moniker( }) } +fn display( + db: &RootDatabase, + edition: Edition, + module: hir::Module, + it: T, +) -> String { + match it.display_source_code(db, module.into(), true) { + Ok(result) => result, + // Fallback on display variant that always succeeds + Err(_) => { + let fallback_result = it.display(db, edition).to_string(); + tracing::error!( + "display_source_code failed. Falling back to using display, which has result: {}", + fallback_result + ); + fallback_result + } + } +} + #[cfg(test)] mod tests { - use crate::fixture; + use crate::{fixture, MonikerResult}; use super::MonikerKind; + #[allow(dead_code)] #[track_caller] fn no_moniker(ra_fixture: &str) { let (analysis, position) = fixture::position(ra_fixture); if let Some(x) = analysis.moniker(position).unwrap() { - assert_eq!(x.info.len(), 0, "Moniker founded but no moniker expected: {x:?}"); + assert_eq!(x.info.len(), 0, "Moniker found but no moniker expected: {x:?}"); + } + } + + #[track_caller] + fn check_local_moniker(ra_fixture: &str, identifier: &str, package: &str, kind: MonikerKind) { + let (analysis, position) = fixture::position(ra_fixture); + let x = analysis.moniker(position).unwrap().expect("no moniker found").info; + assert_eq!(x.len(), 1); + match x.into_iter().next().unwrap() { + MonikerResult::Local { enclosing_moniker: Some(x) } => { + assert_eq!(identifier, x.identifier.to_string()); + assert_eq!(package, format!("{:?}", x.package_information)); + assert_eq!(kind, x.kind); + } + MonikerResult::Local { enclosing_moniker: None } => { + panic!("Unexpected local with no enclosing moniker"); + } + MonikerResult::Moniker(_) => { + panic!("Unexpected non-local moniker"); + } } } @@ -436,10 +464,16 @@ mod tests { let (analysis, position) = fixture::position(ra_fixture); let x = analysis.moniker(position).unwrap().expect("no moniker found").info; assert_eq!(x.len(), 1); - let x = x.into_iter().next().unwrap(); - assert_eq!(identifier, x.identifier.to_string()); - assert_eq!(package, format!("{:?}", x.package_information)); - assert_eq!(kind, x.kind); + match x.into_iter().next().unwrap() { + MonikerResult::Local { enclosing_moniker } => { + panic!("Unexpected local enclosed in {:?}", enclosing_moniker); + } + MonikerResult::Moniker(x) => { + assert_eq!(identifier, x.identifier.to_string()); + assert_eq!(package, format!("{:?}", x.package_information)); + assert_eq!(kind, x.kind); + } + } } #[test] @@ -538,15 +572,13 @@ pub mod module { pub trait MyTrait { pub fn func() {} } - struct MyStruct {} - impl MyTrait for MyStruct { pub fn func$0() {} } } "#, - "foo::module::MyStruct::MyTrait::func", + "foo::module::impl::MyStruct::MyTrait::func", r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#, MonikerKind::Export, ); @@ -573,8 +605,8 @@ pub struct St { } #[test] - fn no_moniker_for_local() { - no_moniker( + fn local() { + check_local_moniker( r#" //- /lib.rs crate:main deps:foo use foo::module::func; @@ -588,6 +620,9 @@ pub mod module { } } "#, + "foo::module::func", + r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#, + MonikerKind::Export, ); } } 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 53eeffaf97d0c..700e166b2384d 100644 --- a/src/tools/rust-analyzer/crates/ide/src/static_index.rs +++ b/src/tools/rust-analyzer/crates/ide/src/static_index.rs @@ -48,7 +48,6 @@ pub struct TokenStaticData { pub references: Vec, pub moniker: Option, pub display_name: Option, - pub enclosing_moniker: Option, pub signature: Option, pub kind: SymbolInformationKind, } @@ -225,9 +224,6 @@ impl StaticIndex<'_> { display_name: def .name(self.db) .map(|name| name.display(self.db, edition).to_string()), - enclosing_moniker: current_crate - .zip(def.enclosing_definition(self.db)) - .and_then(|(cc, enclosing_def)| def_to_moniker(self.db, enclosing_def, cc)), signature: Some(def.label(self.db, edition)), kind: def_to_kind(self.db, def), }); 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 33c4f31fbee4d..eb5c44418b72d 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 @@ -4,8 +4,9 @@ use std::env; use std::time::Instant; use ide::{ - Analysis, AnalysisHost, FileId, FileRange, MonikerKind, PackageInformation, RootDatabase, - StaticIndex, StaticIndexedFile, TokenId, TokenStaticData, VendoredLibrariesConfig, + Analysis, AnalysisHost, FileId, FileRange, MonikerKind, MonikerResult, PackageInformation, + RootDatabase, StaticIndex, StaticIndexedFile, TokenId, TokenStaticData, + VendoredLibrariesConfig, }; use ide_db::{line_index::WideEncoding, LineIndexDatabase}; use load_cargo::{load_workspace, LoadCargoConfig, ProcMacroServerChoice}; @@ -167,7 +168,7 @@ impl LsifManager<'_, '_> { out_v: result_set_id.into(), })); } - if let Some(moniker) = token.moniker { + if let Some(MonikerResult::Moniker(moniker)) = token.moniker { let package_id = self.get_package_id(moniker.package_information); let moniker_id = self.add_vertex(lsif::Vertex::Moniker(lsp_types::Moniker { scheme: "rust-analyzer".to_owned(), 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 ff009e69547a8..6b0aafc24824e 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 @@ -3,8 +3,9 @@ use std::{path::PathBuf, time::Instant}; use ide::{ - AnalysisHost, LineCol, MonikerDescriptorKind, MonikerResult, StaticIndex, StaticIndexedFile, - SymbolInformationKind, TextRange, TokenId, VendoredLibrariesConfig, + AnalysisHost, LineCol, Moniker, MonikerDescriptorKind, MonikerIdentifier, MonikerResult, + StaticIndex, StaticIndexedFile, SymbolInformationKind, TextRange, TokenId, TokenStaticData, + VendoredLibrariesConfig, }; use ide_db::LineIndexDatabase; use load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice}; @@ -85,19 +86,13 @@ impl flags::Scip { }; let mut documents = Vec::new(); - let mut symbols_emitted: FxHashSet = FxHashSet::default(); - let mut tokens_to_symbol: FxHashMap = FxHashMap::default(); - let mut tokens_to_enclosing_symbol: FxHashMap> = - FxHashMap::default(); + let mut token_ids_emitted: FxHashSet = FxHashSet::default(); + let mut global_symbols_emitted: FxHashSet = FxHashSet::default(); + let mut duplicate_symbols: Vec<(String, String)> = Vec::new(); + let mut symbol_generator = SymbolGenerator::new(); for StaticIndexedFile { file_id, tokens, .. } in si.files { - let mut local_count = 0; - let mut new_local_symbol = || { - let new_symbol = scip::types::Symbol::new_local(local_count); - local_count += 1; - - new_symbol - }; + symbol_generator.clear_document_local_state(); let relative_path = match get_relative_filepath(&vfs, &root, file_id) { Some(relative_path) => relative_path, @@ -116,39 +111,23 @@ impl flags::Scip { tokens.into_iter().for_each(|(text_range, id)| { let token = si.tokens.get(id).unwrap(); - let range = text_range_to_scip_range(&line_index, text_range); - let symbol = tokens_to_symbol - .entry(id) - .or_insert_with(|| { - let symbol = token - .moniker - .as_ref() - .map(moniker_to_symbol) - .unwrap_or_else(&mut new_local_symbol); - scip::symbol::format_symbol(symbol) - }) - .clone(); - let enclosing_symbol = tokens_to_enclosing_symbol - .entry(id) - .or_insert_with(|| { - token - .enclosing_moniker - .as_ref() - .map(moniker_to_symbol) - .map(scip::symbol::format_symbol) - }) - .clone(); - - let mut symbol_roles = Default::default(); - - if let Some(def) = token.definition { - // if the range of the def and the range of the token are the same, this must be the definition. - // they also must be in the same file. See https://github.com/rust-lang/rust-analyzer/pull/17988 - if def.file_id == file_id && def.range == text_range { - symbol_roles |= scip_types::SymbolRole::Definition as i32; - } - - if symbols_emitted.insert(id) { + let (symbol, enclosing_symbol) = + if let Some(TokenSymbols { symbol, enclosing_symbol }) = + symbol_generator.token_symbols(id, token) + { + (symbol, enclosing_symbol) + } else { + ("".to_owned(), None) + }; + + if !symbol.is_empty() && token_ids_emitted.insert(id) { + if !symbol.starts_with("local ") + && !global_symbols_emitted.insert(symbol.clone()) + { + let source_location = + text_range_to_string(relative_path.as_str(), &line_index, text_range); + duplicate_symbols.push((source_location, symbol.clone())); + } else { let documentation = match &token.documentation { Some(doc) => vec![doc.as_str().to_owned()], None => vec![], @@ -179,8 +158,18 @@ impl flags::Scip { } } + // If the range of the def and the range of the token are the same, this must be the definition. + // they also must be in the same file. See https://github.com/rust-lang/rust-analyzer/pull/17988 + let mut symbol_roles = Default::default(); + match token.definition { + Some(def) if def.file_id == file_id && def.range == text_range => { + symbol_roles |= scip_types::SymbolRole::Definition as i32; + } + _ => {} + }; + occurrences.push(scip_types::Occurrence { - range, + range: text_range_to_scip_range(&line_index, text_range), symbol, symbol_roles, override_documentation: Vec::new(), @@ -215,6 +204,15 @@ impl flags::Scip { special_fields: Default::default(), }; + if !duplicate_symbols.is_empty() { + eprintln!("{}", DUPLICATE_SYMBOLS_MESSAGE); + for (source_location, symbol) in duplicate_symbols { + eprintln!("{}", source_location); + eprintln!(" Duplicate symbol: {}", symbol); + eprintln!(); + } + } + let out_path = self.output.unwrap_or_else(|| PathBuf::from(r"index.scip")); scip::write_message_to_file(out_path, index) .map_err(|err| anyhow::format_err!("Failed to write scip to file: {}", err))?; @@ -224,6 +222,23 @@ impl flags::Scip { } } +// TODO: Fix the known buggy cases described here. +const DUPLICATE_SYMBOLS_MESSAGE: &str = " +Encountered duplicate scip symbols, indicating an internal rust-analyzer bug. These duplicates are +included in the output, but this causes information lookup to be ambiguous and so information about +these symbols presented by downstream tools may be incorrect. + +Known cases that can cause this: + + * Definitions in crate example binaries which have the same symbol as definitions in the library + or some other example. + + * When a struct/enum/const/static/impl is defined with a function, it erroneously appears to be + defined at the same level as the function. + +Duplicate symbols encountered: +"; + fn get_relative_filepath( vfs: &vfs::Vfs, rootpath: &vfs::AbsPathBuf, @@ -247,6 +262,13 @@ fn text_range_to_scip_range(line_index: &LineIndex, range: TextRange) -> Vec String { + let LineCol { line: start_line, col: start_col } = line_index.index.line_col(range.start()); + let LineCol { line: end_line, col: end_col } = line_index.index.line_col(range.end()); + + format!("{relative_path}:{start_line}:{start_col}-{end_line}:{end_col}") +} + fn new_descriptor_str( name: &str, suffix: scip_types::descriptor::Suffix, @@ -259,14 +281,6 @@ fn new_descriptor_str( } } -fn new_descriptor(name: &str, suffix: scip_types::descriptor::Suffix) -> scip_types::Descriptor { - if name.contains('\'') { - new_descriptor_str(&format!("`{name}`"), suffix) - } else { - new_descriptor_str(name, suffix) - } -} - fn symbol_kind(kind: SymbolInformationKind) -> scip_types::symbol_information::Kind { use scip_types::symbol_information::Kind as ScipKind; match kind { @@ -295,17 +309,79 @@ fn symbol_kind(kind: SymbolInformationKind) -> scip_types::symbol_information::K } } -fn moniker_to_symbol(moniker: &MonikerResult) -> scip_types::Symbol { - use scip_types::descriptor::Suffix::*; +#[derive(Clone)] +struct TokenSymbols { + symbol: String, + /// Definition that contains this one. Only set when `symbol` is local. + enclosing_symbol: Option, +} + +struct SymbolGenerator { + token_to_symbols: FxHashMap>, + local_count: usize, +} + +impl SymbolGenerator { + fn new() -> Self { + SymbolGenerator { token_to_symbols: FxHashMap::default(), local_count: 0 } + } + + fn clear_document_local_state(&mut self) { + self.local_count = 0; + } + + fn token_symbols(&mut self, id: TokenId, token: &TokenStaticData) -> Option { + let mut local_count = self.local_count; + let token_symbols = self + .token_to_symbols + .entry(id) + .or_insert_with(|| { + Some(match token.moniker.as_ref()? { + MonikerResult::Moniker(moniker) => TokenSymbols { + symbol: scip::symbol::format_symbol(moniker_to_symbol(moniker)), + enclosing_symbol: None, + }, + MonikerResult::Local { enclosing_moniker } => { + let local_symbol = scip::types::Symbol::new_local(local_count); + local_count += 1; + TokenSymbols { + symbol: scip::symbol::format_symbol(local_symbol), + enclosing_symbol: enclosing_moniker + .as_ref() + .map(moniker_to_symbol) + .map(scip::symbol::format_symbol), + } + } + }) + }) + .clone(); + self.local_count = local_count; + token_symbols + } +} - let package_name = moniker.package_information.name.clone(); - let version = moniker.package_information.version.clone(); - let descriptors = moniker - .identifier +fn moniker_to_symbol(moniker: &Moniker) -> scip_types::Symbol { + scip_types::Symbol { + scheme: "rust-analyzer".into(), + package: Some(scip_types::Package { + manager: "cargo".to_owned(), + name: moniker.package_information.name.clone(), + version: moniker.package_information.version.clone().unwrap_or_else(|| ".".to_owned()), + special_fields: Default::default(), + }) + .into(), + descriptors: moniker_descriptors(&moniker.identifier), + special_fields: Default::default(), + } +} + +fn moniker_descriptors(identifier: &MonikerIdentifier) -> Vec { + use scip_types::descriptor::Suffix::*; + identifier .description .iter() .map(|desc| { - new_descriptor( + new_descriptor_str( &desc.name, match desc.desc { MonikerDescriptorKind::Namespace => Namespace, @@ -319,27 +395,13 @@ fn moniker_to_symbol(moniker: &MonikerResult) -> scip_types::Symbol { }, ) }) - .collect(); - - scip_types::Symbol { - scheme: "rust-analyzer".into(), - package: Some(scip_types::Package { - manager: "cargo".to_owned(), - name: package_name, - version: version.unwrap_or_else(|| ".".to_owned()), - special_fields: Default::default(), - }) - .into(), - descriptors, - special_fields: Default::default(), - } + .collect() } #[cfg(test)] mod test { use super::*; use ide::{FilePosition, TextSize}; - use scip::symbol::format_symbol; use test_fixture::ChangeFixture; use vfs::VfsPath; @@ -376,7 +438,21 @@ mod test { for &(range, id) in &file.tokens { if range.contains(offset - TextSize::from(1)) { let token = si.tokens.get(id).unwrap(); - found_symbol = token.moniker.as_ref().map(moniker_to_symbol); + found_symbol = match token.moniker.as_ref() { + None => None, + Some(MonikerResult::Moniker(moniker)) => { + Some(scip::symbol::format_symbol(moniker_to_symbol(moniker))) + } + Some(MonikerResult::Local { enclosing_moniker: Some(moniker) }) => { + Some(format!( + "local enclosed by {}", + scip::symbol::format_symbol(moniker_to_symbol(moniker)) + )) + } + Some(MonikerResult::Local { enclosing_moniker: None }) => { + Some("unenclosed local".to_owned()) + } + }; break; } } @@ -388,9 +464,7 @@ mod test { } assert!(found_symbol.is_some(), "must have one symbol {found_symbol:?}"); - let res = found_symbol.unwrap(); - let formatted = format_symbol(res); - assert_eq!(formatted, expected); + assert_eq!(found_symbol.unwrap(), expected); } #[test] @@ -467,8 +541,7 @@ pub mod module { } } "#, - // "foo::module::MyTrait::MyType", - "rust-analyzer cargo foo 0.1.0 module/MyTrait#[MyType]", + "rust-analyzer cargo foo 0.1.0 module/MyTrait#MyType#", ); } @@ -489,8 +562,7 @@ pub mod module { } } "#, - // "foo::module::MyStruct::MyTrait::func", - "rust-analyzer cargo foo 0.1.0 module/MyStruct#MyTrait#func().", + "rust-analyzer cargo foo 0.1.0 module/impl#[MyStruct][MyTrait]func().", ); } @@ -526,7 +598,7 @@ pub mod example_mod { pub fn func(x$0: usize) {} } "#, - "rust-analyzer cargo foo 0.1.0 example_mod/func().(x)", + "local enclosed by rust-analyzer cargo foo 0.1.0 example_mod/func().", ); } @@ -546,7 +618,7 @@ pub mod example_mod { } } "#, - "rust-analyzer cargo foo 0.1.0 example_mod/func().(x)", + "local enclosed by rust-analyzer cargo foo 0.1.0 example_mod/func().", ); } @@ -566,7 +638,7 @@ pub mod example_mod { } } "#, - "", + "local enclosed by rust-analyzer cargo foo 0.1.0 module/func().", ); } @@ -609,7 +681,7 @@ pub mod example_mod { } #[test] - fn symbol_for_for_type_alias() { + fn symbol_for_type_alias() { check_symbol( r#" //- /workspace/lib.rs crate:main @@ -619,6 +691,70 @@ pub mod example_mod { ); } + // TODO: This test represents current misbehavior. + #[test] + fn symbol_for_nested_function() { + check_symbol( + r#" + //- /workspace/lib.rs crate:main + pub fn func() { + pub fn inner_func$0() {} + } + "#, + "rust-analyzer cargo main . inner_func().", + // TODO: This should be a local: + // "local enclosed by rust-analyzer cargo main . func().", + ); + } + + // TODO: This test represents current misbehavior. + #[test] + fn symbol_for_struct_in_function() { + check_symbol( + r#" + //- /workspace/lib.rs crate:main + pub fn func() { + struct SomeStruct$0 {} + } + "#, + "rust-analyzer cargo main . SomeStruct#", + // TODO: This should be a local: + // "local enclosed by rust-analyzer cargo main . func().", + ); + } + + // TODO: This test represents current misbehavior. + #[test] + fn symbol_for_const_in_function() { + check_symbol( + r#" + //- /workspace/lib.rs crate:main + pub fn func() { + const SOME_CONST$0: u32 = 1; + } + "#, + "rust-analyzer cargo main . SOME_CONST.", + // TODO: This should be a local: + // "local enclosed by rust-analyzer cargo main . func().", + ); + } + + // TODO: This test represents current misbehavior. + #[test] + fn symbol_for_static_in_function() { + check_symbol( + r#" + //- /workspace/lib.rs crate:main + pub fn func() { + static SOME_STATIC$0: u32 = 1; + } + "#, + "rust-analyzer cargo main . SOME_STATIC.", + // TODO: This should be a local: + // "local enclosed by rust-analyzer cargo main . func().", + ); + } + #[test] fn documentation_matches_doc_comment() { let s = "/// foo\nfn bar() {}"; From bbc6242b4cfe3a80633874e01d606d8e35b85f31 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Thu, 26 Dec 2024 01:08:03 -0700 Subject: [PATCH 014/123] Provide SCIP `external_symbols` + fix symbols provided with Document Before this change `SymbolInformation` provided by a document was the info for all encountered symbols that have not yet been emitted. So, the symbol information on a `Document` was a mishmash of symbols defined in the documents, symbols from other documents, and external symbols. After this change, the `SymbolInformation` on documents is just the locals and defined symbols from the document. All symbols referenced and not from emitted documents are included in `external_symbols`. --- .../crates/rust-analyzer/src/cli/scip.rs | 204 +++++++++++++----- 1 file changed, 146 insertions(+), 58 deletions(-) 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 6b0aafc24824e..6526fd965a969 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 @@ -4,14 +4,15 @@ use std::{path::PathBuf, time::Instant}; use ide::{ AnalysisHost, LineCol, Moniker, MonikerDescriptorKind, MonikerIdentifier, MonikerResult, - StaticIndex, StaticIndexedFile, SymbolInformationKind, TextRange, TokenId, TokenStaticData, - VendoredLibrariesConfig, + RootDatabase, StaticIndex, StaticIndexedFile, SymbolInformationKind, TextRange, TokenId, + TokenStaticData, VendoredLibrariesConfig, }; use ide_db::LineIndexDatabase; use load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice}; use rustc_hash::{FxHashMap, FxHashSet}; -use scip::types as scip_types; +use scip::types::{self as scip_types, SymbolInformation}; use tracing::error; +use vfs::FileId; use crate::{ cli::flags, @@ -84,26 +85,40 @@ impl flags::Scip { text_document_encoding: scip_types::TextEncoding::UTF8.into(), special_fields: Default::default(), }; + let mut documents = Vec::new(); + // All TokenIds where an Occurrence has been emitted that references a symbol. + let mut token_ids_referenced: FxHashSet = FxHashSet::default(); + // All TokenIds where the SymbolInformation has been written to the document. let mut token_ids_emitted: FxHashSet = FxHashSet::default(); - let mut global_symbols_emitted: FxHashSet = FxHashSet::default(); - let mut duplicate_symbols: Vec<(String, String)> = Vec::new(); + // All FileIds emitted as documents. + let mut file_ids_emitted: FxHashSet = FxHashSet::default(); + + // All non-local symbols encountered, for detecting duplicate symbol errors. + let mut nonlocal_symbols_emitted: FxHashSet = FxHashSet::default(); + // List of (source_location, symbol) for duplicate symbol errors to report. + let mut duplicate_symbol_errors: Vec<(String, String)> = Vec::new(); + // This is called after definitions have been deduplicated by token_ids_emitted. The purpose + // is to detect reuse of symbol names because this causes ambiguity about their meaning. + let mut record_error_if_symbol_already_used = + |symbol: String, relative_path: &str, line_index: &LineIndex, text_range: TextRange| { + let is_local = symbol.starts_with("local "); + if !is_local && !nonlocal_symbols_emitted.insert(symbol.clone()) { + let source_location = + text_range_to_string(relative_path, line_index, text_range); + duplicate_symbol_errors.push((source_location, symbol)); + } + }; + + // Generates symbols from token monikers. let mut symbol_generator = SymbolGenerator::new(); for StaticIndexedFile { file_id, tokens, .. } in si.files { symbol_generator.clear_document_local_state(); - let relative_path = match get_relative_filepath(&vfs, &root, file_id) { - Some(relative_path) => relative_path, - None => continue, - }; - - let line_index = LineIndex { - index: db.line_index(file_id), - encoding: PositionEncoding::Utf8, - endings: LineEndings::Unix, - }; + let Some(relative_path) = get_relative_filepath(&vfs, &root, file_id) else { continue }; + let line_index = get_line_index(db, file_id); let mut occurrences = Vec::new(); let mut symbols = Vec::new(); @@ -120,54 +135,45 @@ impl flags::Scip { ("".to_owned(), None) }; - if !symbol.is_empty() && token_ids_emitted.insert(id) { - if !symbol.starts_with("local ") - && !global_symbols_emitted.insert(symbol.clone()) - { - let source_location = - text_range_to_string(relative_path.as_str(), &line_index, text_range); - duplicate_symbols.push((source_location, symbol.clone())); + if !symbol.is_empty() { + let is_defined_in_this_document = match token.definition { + Some(def) => def.file_id == file_id, + _ => false, + }; + if is_defined_in_this_document { + if token_ids_emitted.insert(id) { + // token_ids_emitted does deduplication. This checks that this results + // in unique emitted symbols, as otherwise references are ambiguous. + record_error_if_symbol_already_used( + symbol.clone(), + relative_path.as_str(), + &line_index, + text_range, + ); + symbols.push(compute_symbol_info( + relative_path.clone(), + symbol.clone(), + enclosing_symbol, + token, + )); + } } else { - let documentation = match &token.documentation { - Some(doc) => vec![doc.as_str().to_owned()], - None => vec![], - }; - - let position_encoding = - scip_types::PositionEncoding::UTF8CodeUnitOffsetFromLineStart.into(); - let signature_documentation = - token.signature.clone().map(|text| scip_types::Document { - relative_path: relative_path.clone(), - language: "rust".to_owned(), - text, - position_encoding, - ..Default::default() - }); - let symbol_info = scip_types::SymbolInformation { - symbol: symbol.clone(), - documentation, - relationships: Vec::new(), - special_fields: Default::default(), - kind: symbol_kind(token.kind).into(), - display_name: token.display_name.clone().unwrap_or_default(), - signature_documentation: signature_documentation.into(), - enclosing_symbol: enclosing_symbol.unwrap_or_default(), - }; - - symbols.push(symbol_info) + token_ids_referenced.insert(id); } } // If the range of the def and the range of the token are the same, this must be the definition. // they also must be in the same file. See https://github.com/rust-lang/rust-analyzer/pull/17988 - let mut symbol_roles = Default::default(); - match token.definition { - Some(def) if def.file_id == file_id && def.range == text_range => { - symbol_roles |= scip_types::SymbolRole::Definition as i32; - } - _ => {} + let is_definition = match token.definition { + Some(def) => def.file_id == file_id && def.range == text_range, + _ => false, }; + let mut symbol_roles = Default::default(); + if is_definition { + symbol_roles |= scip_types::SymbolRole::Definition as i32; + } + occurrences.push(scip_types::Occurrence { range: text_range_to_scip_range(&line_index, text_range), symbol, @@ -195,18 +201,61 @@ impl flags::Scip { position_encoding, special_fields: Default::default(), }); + if !file_ids_emitted.insert(file_id) { + panic!("Invariant violation: file emitted multiple times."); + } + } + + // Collect all symbols referenced by the files but not defined within them. + let mut external_symbols = Vec::new(); + for id in token_ids_referenced.difference(&token_ids_emitted) { + let id = *id; + let token = si.tokens.get(id).unwrap(); + + let Some(definition) = token.definition else { + break; + }; + + let file_id = definition.file_id; + let Some(relative_path) = get_relative_filepath(&vfs, &root, file_id) else { continue }; + let line_index = get_line_index(db, file_id); + let text_range = definition.range; + if file_ids_emitted.contains(&file_id) { + tracing::error!( + "Bug: definition at {} should have been in an SCIP document but was not.", + text_range_to_string(relative_path.as_str(), &line_index, text_range) + ); + continue; + } + + let TokenSymbols { symbol, enclosing_symbol } = symbol_generator + .token_symbols(id, token) + .expect("To have been referenced, the symbol must be in the cache."); + + record_error_if_symbol_already_used( + symbol.clone(), + relative_path.as_str(), + &line_index, + text_range, + ); + external_symbols.push(compute_symbol_info( + relative_path.clone(), + symbol.clone(), + enclosing_symbol, + token, + )); } let index = scip_types::Index { metadata: Some(metadata).into(), documents, - external_symbols: Vec::new(), + external_symbols, special_fields: Default::default(), }; - if !duplicate_symbols.is_empty() { + if !duplicate_symbol_errors.is_empty() { eprintln!("{}", DUPLICATE_SYMBOLS_MESSAGE); - for (source_location, symbol) in duplicate_symbols { + for (source_location, symbol) in duplicate_symbol_errors { eprintln!("{}", source_location); eprintln!(" Duplicate symbol: {}", symbol); eprintln!(); @@ -239,6 +288,37 @@ Known cases that can cause this: Duplicate symbols encountered: "; +fn compute_symbol_info( + relative_path: String, + symbol: String, + enclosing_symbol: Option, + token: &TokenStaticData, +) -> SymbolInformation { + let documentation = match &token.documentation { + Some(doc) => vec![doc.as_str().to_owned()], + None => vec![], + }; + + let position_encoding = scip_types::PositionEncoding::UTF8CodeUnitOffsetFromLineStart.into(); + let signature_documentation = token.signature.clone().map(|text| scip_types::Document { + relative_path, + language: "rust".to_owned(), + text, + position_encoding, + ..Default::default() + }); + scip_types::SymbolInformation { + symbol, + documentation, + relationships: Vec::new(), + special_fields: Default::default(), + kind: symbol_kind(token.kind).into(), + display_name: token.display_name.clone().unwrap_or_default(), + signature_documentation: signature_documentation.into(), + enclosing_symbol: enclosing_symbol.unwrap_or_default(), + } +} + fn get_relative_filepath( vfs: &vfs::Vfs, rootpath: &vfs::AbsPathBuf, @@ -247,6 +327,14 @@ fn get_relative_filepath( Some(vfs.file_path(file_id).as_path()?.strip_prefix(rootpath)?.as_str().to_owned()) } +fn get_line_index(db: &RootDatabase, file_id: FileId) -> LineIndex { + LineIndex { + index: db.line_index(file_id), + encoding: PositionEncoding::Utf8, + endings: LineEndings::Unix, + } +} + // SCIP Ranges have a (very large) optimization that ranges if they are on the same line // only encode as a vector of [start_line, start_col, end_col]. // From cd8522ef4ed240d489ef2c8c66400a6a2cc91837 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Thu, 26 Dec 2024 01:50:12 -0700 Subject: [PATCH 015/123] Use empty `SymbolInformation.signature_documentation.relative_path` I'm fairly sure this is more correct, and saves space(~90mb to 82mb for Zed's index). I'm checking in about this with SCIP folks in https://github.com/sourcegraph/scip/pull/299. --- .../crates/rust-analyzer/src/cli/scip.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) 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 6526fd965a969..e096f3f5180dd 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 @@ -151,7 +151,6 @@ impl flags::Scip { text_range, ); symbols.push(compute_symbol_info( - relative_path.clone(), symbol.clone(), enclosing_symbol, token, @@ -238,12 +237,7 @@ impl flags::Scip { &line_index, text_range, ); - external_symbols.push(compute_symbol_info( - relative_path.clone(), - symbol.clone(), - enclosing_symbol, - token, - )); + external_symbols.push(compute_symbol_info(symbol.clone(), enclosing_symbol, token)); } let index = scip_types::Index { @@ -289,7 +283,6 @@ Duplicate symbols encountered: "; fn compute_symbol_info( - relative_path: String, symbol: String, enclosing_symbol: Option, token: &TokenStaticData, @@ -301,7 +294,7 @@ fn compute_symbol_info( let position_encoding = scip_types::PositionEncoding::UTF8CodeUnitOffsetFromLineStart.into(); let signature_documentation = token.signature.clone().map(|text| scip_types::Document { - relative_path, + relative_path: "".to_owned(), language: "rust".to_owned(), text, position_encoding, From 08677cb70dce85e1ad4f0e5a5e3dfecf77ec16fd Mon Sep 17 00:00:00 2001 From: roife Date: Wed, 25 Dec 2024 15:50:35 +0800 Subject: [PATCH 016/123] feat: Add TestDefs to find usage of Expect, Insta and Snapbox --- src/tools/rust-analyzer/crates/hir/src/lib.rs | 6 + .../rust-analyzer/crates/ide/src/runnables.rs | 183 ++++++++++++++++-- 2 files changed, 168 insertions(+), 21 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index ac8a62ee85060..be424c2cd9c3c 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -5917,6 +5917,12 @@ impl HasCrate for Adt { } } +impl HasCrate for Impl { + fn krate(&self, db: &dyn HirDatabase) -> Crate { + self.module(db).krate() + } +} + impl HasCrate for Module { fn krate(&self, _: &dyn HirDatabase) -> Crate { Module::krate(*self) diff --git a/src/tools/rust-analyzer/crates/ide/src/runnables.rs b/src/tools/rust-analyzer/crates/ide/src/runnables.rs index d385e453e2138..487cd6daeba82 100644 --- a/src/tools/rust-analyzer/crates/ide/src/runnables.rs +++ b/src/tools/rust-analyzer/crates/ide/src/runnables.rs @@ -1,4 +1,4 @@ -use std::fmt; +use std::{fmt, ops::Not}; use ast::HasName; use cfg::{CfgAtom, CfgExpr}; @@ -15,6 +15,7 @@ use ide_db::{ FilePosition, FxHashMap, FxHashSet, RootDatabase, SymbolKind, }; use itertools::Itertools; +use smallvec::SmallVec; use span::{Edition, TextSize}; use stdx::format_to; use syntax::{ @@ -30,6 +31,7 @@ pub struct Runnable { pub nav: NavigationTarget, pub kind: RunnableKind, pub cfg: Option, + pub update_test: UpdateTest, } #[derive(Debug, Clone, Hash, PartialEq, Eq)] @@ -334,14 +336,19 @@ pub(crate) fn runnable_fn( } }; + let fn_source = def.source(sema.db)?; let nav = NavigationTarget::from_named( sema.db, - def.source(sema.db)?.as_ref().map(|it| it as &dyn ast::HasName), + fn_source.as_ref().map(|it| it as &dyn ast::HasName), SymbolKind::Function, ) .call_site(); + + let file_range = fn_source.syntax().original_file_range_with_macro_call_body(sema.db); + let update_test = TestDefs::new(sema, def.krate(sema.db), file_range).update_test(); + let cfg = def.attrs(sema.db).cfg(); - Some(Runnable { use_name_in_title: false, nav, kind, cfg }) + Some(Runnable { use_name_in_title: false, nav, kind, cfg, update_test }) } pub(crate) fn runnable_mod( @@ -366,7 +373,22 @@ pub(crate) fn runnable_mod( let attrs = def.attrs(sema.db); let cfg = attrs.cfg(); let nav = NavigationTarget::from_module_to_decl(sema.db, def).call_site(); - Some(Runnable { use_name_in_title: false, nav, kind: RunnableKind::TestMod { path }, cfg }) + + let file_range = { + let src = def.definition_source(sema.db); + let file_id = src.file_id.original_file(sema.db); + let range = src.file_syntax(sema.db).text_range(); + hir::FileRange { file_id, range } + }; + let update_test = TestDefs::new(sema, def.krate(), file_range).update_test(); + + Some(Runnable { + use_name_in_title: false, + nav, + kind: RunnableKind::TestMod { path }, + cfg, + update_test, + }) } pub(crate) fn runnable_impl( @@ -392,7 +414,17 @@ pub(crate) fn runnable_impl( test_id.retain(|c| c != ' '); let test_id = TestId::Path(test_id); - Some(Runnable { use_name_in_title: false, nav, kind: RunnableKind::DocTest { test_id }, cfg }) + let impl_source = + def.source(sema.db)?.syntax().original_file_range_with_macro_call_body(sema.db); + let update_test = TestDefs::new(sema, def.krate(sema.db), impl_source).update_test(); + + Some(Runnable { + use_name_in_title: false, + nav, + kind: RunnableKind::DocTest { test_id }, + cfg, + update_test, + }) } fn has_cfg_test(attrs: AttrsWithOwner) -> bool { @@ -404,6 +436,8 @@ fn runnable_mod_outline_definition( sema: &Semantics<'_, RootDatabase>, def: hir::Module, ) -> Option { + def.as_source_file_id(sema.db)?; + if !has_test_function_or_multiple_test_submodules(sema, &def, has_cfg_test(def.attrs(sema.db))) { return None; @@ -421,16 +455,22 @@ fn runnable_mod_outline_definition( let attrs = def.attrs(sema.db); let cfg = attrs.cfg(); - if def.as_source_file_id(sema.db).is_some() { - Some(Runnable { - use_name_in_title: false, - nav: def.to_nav(sema.db).call_site(), - kind: RunnableKind::TestMod { path }, - cfg, - }) - } else { - None - } + + let file_range = { + let src = def.definition_source(sema.db); + let file_id = src.file_id.original_file(sema.db); + let range = src.file_syntax(sema.db).text_range(); + hir::FileRange { file_id, range } + }; + let update_test = TestDefs::new(sema, def.krate(), file_range).update_test(); + + Some(Runnable { + use_name_in_title: false, + nav: def.to_nav(sema.db).call_site(), + kind: RunnableKind::TestMod { path }, + cfg, + update_test, + }) } fn module_def_doctest(db: &RootDatabase, def: Definition) -> Option { @@ -495,6 +535,7 @@ fn module_def_doctest(db: &RootDatabase, def: Definition) -> Option { nav, kind: RunnableKind::DocTest { test_id }, cfg: attrs.cfg(), + update_test: UpdateTest::default(), }; Some(res) } @@ -575,6 +616,106 @@ fn has_test_function_or_multiple_test_submodules( number_of_test_submodules > 1 } +struct TestDefs<'a, 'b>(&'a Semantics<'b, RootDatabase>, hir::Crate, hir::FileRange); + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] +pub struct UpdateTest { + pub expect_test: bool, + pub insta: bool, + pub snapbox: bool, +} + +impl UpdateTest { + pub fn label(&self) -> Option { + let mut builder: SmallVec<[_; 3]> = SmallVec::new(); + if self.expect_test { + builder.push("Expect"); + } + if self.insta { + builder.push("Insta"); + } + if self.snapbox { + builder.push("Snapbox"); + } + + let res: SmolStr = builder.join(" + ").into(); + res.is_empty().not().then_some(res) + } +} + +impl<'a, 'b> TestDefs<'a, 'b> { + fn new( + sema: &'a Semantics<'b, RootDatabase>, + current_krate: hir::Crate, + file_range: hir::FileRange, + ) -> Self { + Self(sema, current_krate, file_range) + } + + fn update_test(&self) -> UpdateTest { + UpdateTest { expect_test: self.expect_test(), insta: self.insta(), snapbox: self.snapbox() } + } + + fn expect_test(&self) -> bool { + self.find_macro("expect_test:expect") || self.find_macro("expect_test::expect_file") + } + + fn insta(&self) -> bool { + self.find_macro("insta:assert_snapshot") + || self.find_macro("insta:assert_debug_snapshot") + || self.find_macro("insta:assert_display_snapshot") + || self.find_macro("insta:assert_json_snapshot") + || self.find_macro("insta:assert_yaml_snapshot") + || self.find_macro("insta:assert_ron_snapshot") + || self.find_macro("insta:assert_toml_snapshot") + || self.find_macro("insta:assert_csv_snapshot") + || self.find_macro("insta:assert_compact_json_snapshot") + || self.find_macro("insta:assert_compact_debug_snapshot") + || self.find_macro("insta:assert_binary_snapshot") + } + + fn snapbox(&self) -> bool { + self.find_macro("snapbox:assert_data_eq") + || self.find_macro("snapbox:file") + || self.find_macro("snapbox:str") + } + + fn find_macro(&self, path: &str) -> bool { + let Some(hir::ScopeDef::ModuleDef(hir::ModuleDef::Macro(it))) = self.find_def(path) else { + return false; + }; + + Definition::Macro(it) + .usages(self.0) + .in_scope(&SearchScope::file_range(self.2)) + .at_least_one() + } + + fn find_def(&self, path: &str) -> Option { + let db = self.0.db; + + let mut path = path.split(':'); + let item = path.next_back()?; + let krate = path.next()?; + let dep = self.1.dependencies(db).into_iter().find(|dep| dep.name.eq_ident(krate))?; + + let mut module = dep.krate.root_module(); + for segment in path { + module = module.children(db).find_map(|child| { + let name = child.name(db)?; + if name.eq_ident(segment) { + Some(child) + } else { + None + } + })?; + } + + let (_, def) = module.scope(db, None).into_iter().find(|(name, _)| name.eq_ident(item))?; + Some(def) + } +} + #[cfg(test)] mod tests { use expect_test::{expect, Expect}; @@ -1337,18 +1478,18 @@ mod tests { file_id: FileId( 0, ), - full_range: 52..115, - focus_range: 67..75, - name: "foo_test", + full_range: 121..185, + focus_range: 136..145, + name: "foo2_test", kind: Function, }, NavigationTarget { file_id: FileId( 0, ), - full_range: 121..185, - focus_range: 136..145, - name: "foo2_test", + full_range: 52..115, + focus_range: 67..75, + name: "foo_test", kind: Function, }, ] From 60b4ed5bd36e88b454c8833f76a7481e01cca66a Mon Sep 17 00:00:00 2001 From: roife Date: Wed, 25 Dec 2024 15:56:06 +0800 Subject: [PATCH 017/123] feat: support UpdateTest in codelens --- .../crates/ide/src/annotations.rs | 68 +++++++++++++++---- .../crates/ide/src/hover/tests.rs | 52 ++++++++------ .../crates/rust-analyzer/src/config.rs | 10 ++- .../crates/rust-analyzer/src/lsp/ext.rs | 12 ++-- .../crates/rust-analyzer/src/lsp/to_proto.rs | 38 ++++++++++- .../rust-analyzer/docs/dev/lsp-extensions.md | 2 +- .../docs/user/generated_config.adoc | 6 ++ .../rust-analyzer/editors/code/package.json | 15 ++++ .../rust-analyzer/editors/code/src/client.ts | 17 +++-- .../editors/code/src/commands.ts | 26 +++++++ .../rust-analyzer/editors/code/src/config.ts | 7 ++ .../rust-analyzer/editors/code/src/main.ts | 8 +-- 12 files changed, 204 insertions(+), 57 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide/src/annotations.rs b/src/tools/rust-analyzer/crates/ide/src/annotations.rs index 121a463c9f155..6a4e5ba290ec8 100644 --- a/src/tools/rust-analyzer/crates/ide/src/annotations.rs +++ b/src/tools/rust-analyzer/crates/ide/src/annotations.rs @@ -316,6 +316,11 @@ fn main() { }, kind: Bin, cfg: None, + update_test: UpdateTest { + expect_test: false, + insta: false, + snapbox: false, + }, }, ), }, @@ -401,6 +406,11 @@ fn main() { }, kind: Bin, cfg: None, + update_test: UpdateTest { + expect_test: false, + insta: false, + snapbox: false, + }, }, ), }, @@ -537,6 +547,11 @@ fn main() { }, kind: Bin, cfg: None, + update_test: UpdateTest { + expect_test: false, + insta: false, + snapbox: false, + }, }, ), }, @@ -597,6 +612,11 @@ fn main() {} }, kind: Bin, cfg: None, + update_test: UpdateTest { + expect_test: false, + insta: false, + snapbox: false, + }, }, ), }, @@ -709,6 +729,11 @@ fn main() { }, kind: Bin, cfg: None, + update_test: UpdateTest { + expect_test: false, + insta: false, + snapbox: false, + }, }, ), }, @@ -744,6 +769,20 @@ mod tests { "#, expect![[r#" [ + Annotation { + range: 3..7, + kind: HasReferences { + pos: FilePositionWrapper { + file_id: FileId( + 0, + ), + offset: 3, + }, + data: Some( + [], + ), + }, + }, Annotation { range: 3..7, kind: Runnable( @@ -760,23 +799,14 @@ mod tests { }, kind: Bin, cfg: None, + update_test: UpdateTest { + expect_test: false, + insta: false, + snapbox: false, + }, }, ), }, - Annotation { - range: 3..7, - kind: HasReferences { - pos: FilePositionWrapper { - file_id: FileId( - 0, - ), - offset: 3, - }, - data: Some( - [], - ), - }, - }, Annotation { range: 18..23, kind: Runnable( @@ -796,6 +826,11 @@ mod tests { path: "tests", }, cfg: None, + update_test: UpdateTest { + expect_test: false, + insta: false, + snapbox: false, + }, }, ), }, @@ -822,6 +857,11 @@ mod tests { }, }, cfg: None, + update_test: UpdateTest { + expect_test: false, + insta: false, + snapbox: false, + }, }, ), }, 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 ed8cd64cdbee0..a7a5b8fb5a1d0 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -3213,6 +3213,11 @@ fn foo_$0test() {} }, }, cfg: None, + update_test: UpdateTest { + expect_test: false, + insta: false, + snapbox: false, + }, }, ), ] @@ -3230,28 +3235,33 @@ mod tests$0 { } "#, expect![[r#" - [ - Runnable( - Runnable { - use_name_in_title: false, - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 0..46, - focus_range: 4..9, - name: "tests", - kind: Module, - description: "mod tests", - }, - kind: TestMod { - path: "tests", - }, - cfg: None, + [ + Runnable( + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 0..46, + focus_range: 4..9, + name: "tests", + kind: Module, + description: "mod tests", }, - ), - ] - "#]], + kind: TestMod { + path: "tests", + }, + cfg: None, + update_test: UpdateTest { + expect_test: false, + insta: false, + snapbox: false, + }, + }, + ), + ] + "#]], ); } 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 c182952c731dc..1b37ab0aab2da 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -243,6 +243,9 @@ config_data! { /// Whether to show `Run` lens. Only applies when /// `#rust-analyzer.lens.enable#` is set. lens_run_enable: bool = true, + /// Whether to show `Update Test` lens. Only applies when + /// `#rust-analyzer.lens.enable#` and `#rust-analyzer.lens.run.enable#` are set. + lens_update_test_enable: bool = true, /// Disable project auto-discovery in favor of explicitly specified set /// of projects. @@ -1161,6 +1164,7 @@ pub struct LensConfig { // runnables pub run: bool, pub debug: bool, + pub update_test: bool, pub interpret: bool, // implementations @@ -1196,6 +1200,7 @@ impl LensConfig { pub fn any(&self) -> bool { self.run || self.debug + || self.update_test || self.implementations || self.method_refs || self.refs_adt @@ -1208,7 +1213,7 @@ impl LensConfig { } pub fn runnable(&self) -> bool { - self.run || self.debug + self.run || self.debug || self.update_test } pub fn references(&self) -> bool { @@ -2120,6 +2125,9 @@ impl Config { LensConfig { run: *self.lens_enable() && *self.lens_run_enable(), debug: *self.lens_enable() && *self.lens_debug_enable(), + update_test: *self.lens_enable() + && *self.lens_update_test_enable() + && *self.lens_run_enable(), interpret: *self.lens_enable() && *self.lens_run_enable() && *self.interpret_tests(), implementations: *self.lens_enable() && *self.lens_implementations_enable(), method_refs: *self.lens_enable() && *self.lens_references_method_enable(), 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 c0173d9c2470f..e1677cbcda80d 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 @@ -427,14 +427,14 @@ impl Request for Runnables { const METHOD: &'static str = "experimental/runnables"; } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct RunnablesParams { pub text_document: TextDocumentIdentifier, pub position: Option, } -#[derive(Deserialize, Serialize, Debug)] +#[derive(Deserialize, Serialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct Runnable { pub label: String, @@ -444,7 +444,7 @@ pub struct Runnable { pub args: RunnableArgs, } -#[derive(Deserialize, Serialize, Debug)] +#[derive(Deserialize, Serialize, Debug, Clone)] #[serde(rename_all = "camelCase")] #[serde(untagged)] pub enum RunnableArgs { @@ -452,14 +452,14 @@ pub enum RunnableArgs { Shell(ShellRunnableArgs), } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "lowercase")] pub enum RunnableKind { Cargo, Shell, } -#[derive(Deserialize, Serialize, Debug)] +#[derive(Deserialize, Serialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct CargoRunnableArgs { #[serde(skip_serializing_if = "FxHashMap::is_empty")] @@ -475,7 +475,7 @@ pub struct CargoRunnableArgs { pub executable_args: Vec, } -#[derive(Deserialize, Serialize, Debug)] +#[derive(Deserialize, Serialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct ShellRunnableArgs { #[serde(skip_serializing_if = "FxHashMap::is_empty")] diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs index 05e93b4e6acf0..091ac773323ad 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs @@ -20,6 +20,7 @@ use itertools::Itertools; use paths::{Utf8Component, Utf8Prefix}; use semver::VersionReq; use serde_json::to_value; +use syntax::SmolStr; use vfs::AbsPath; use crate::{ @@ -1567,6 +1568,7 @@ pub(crate) fn code_lens( let line_index = snap.file_line_index(run.nav.file_id)?; let annotation_range = range(&line_index, annotation.range); + let update_test = run.update_test; let title = run.title(); let can_debug = match run.kind { ide::RunnableKind::DocTest { .. } => false, @@ -1602,6 +1604,17 @@ pub(crate) fn code_lens( data: None, }) } + if lens_config.update_test && client_commands_config.run_single { + let label = update_test.label(); + if let Some(r) = make_update_runnable(&r, &label) { + let command = command::run_single(&r, label.unwrap().as_str()); + acc.push(lsp_types::CodeLens { + range: annotation_range, + command: Some(command), + data: None, + }) + } + } } if lens_config.interpret { @@ -1786,7 +1799,7 @@ pub(crate) mod command { pub(crate) fn debug_single(runnable: &lsp_ext::Runnable) -> lsp_types::Command { lsp_types::Command { - title: "Debug".into(), + title: "âš™\u{fe0e} Debug".into(), command: "rust-analyzer.debugSingle".into(), arguments: Some(vec![to_value(runnable).unwrap()]), } @@ -1838,6 +1851,29 @@ pub(crate) mod command { } } +fn make_update_runnable( + runnable: &lsp_ext::Runnable, + label: &Option, +) -> Option { + if !matches!(runnable.args, lsp_ext::RunnableArgs::Cargo(_)) { + return None; + } + let label = label.as_ref()?; + + let mut runnable = runnable.clone(); + runnable.label = format!("{} + {}", runnable.label, label); + + let lsp_ext::RunnableArgs::Cargo(r) = &mut runnable.args else { + unreachable!(); + }; + + let environment_vars = + [("UPDATE_EXPECT", "1"), ("INSTA_UPDATE", "always"), ("SNAPSHOTS", "overwrite")]; + r.environment.extend(environment_vars.into_iter().map(|(k, v)| (k.to_owned(), v.to_owned()))); + + Some(runnable) +} + pub(crate) fn implementation_title(count: usize) -> String { if count == 1 { "1 implementation".into() diff --git a/src/tools/rust-analyzer/docs/dev/lsp-extensions.md b/src/tools/rust-analyzer/docs/dev/lsp-extensions.md index 0e37611a5493e..826ce11244864 100644 --- a/src/tools/rust-analyzer/docs/dev/lsp-extensions.md +++ b/src/tools/rust-analyzer/docs/dev/lsp-extensions.md @@ -1,5 +1,5 @@