From f758f6d8637acf0b66d95bcf2dad31da57509218 Mon Sep 17 00:00:00 2001 From: Nicolas Guichard Date: Thu, 26 Jun 2025 16:06:18 +0200 Subject: [PATCH 1/3] Record inferred type for type references This adds a type_of_type arena to InferenceResult to record which type a given type reference gets inferred to. --- crates/hir-ty/src/infer.rs | 15 ++++++++- crates/hir-ty/src/tests.rs | 31 +++++++++++++++++++ .../hir-ty/src/tests/display_source_code.rs | 15 +++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index ce53198e966f..089ca9950fd6 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -470,6 +470,7 @@ pub struct InferenceResult { /// unresolved or missing subpatterns or subpatterns of mismatched types. pub type_of_pat: ArenaMap, pub type_of_binding: ArenaMap, + pub type_of_type: ArenaMap, pub type_of_rpit: ArenaMap, /// Type of the result of `.into_iter()` on the for. `ExprId` is the one of the whole for loop. pub type_of_for_iterator: FxHashMap, @@ -771,6 +772,7 @@ impl<'db> InferenceContext<'db> { type_of_expr, type_of_pat, type_of_binding, + type_of_type, type_of_rpit, type_of_for_iterator, type_mismatches, @@ -837,6 +839,11 @@ impl<'db> InferenceContext<'db> { *has_errors = *has_errors || ty.contains_unknown(); } type_of_for_iterator.shrink_to_fit(); + for ty in type_of_type.values_mut() { + *ty = table.resolve_completely(ty.clone()); + *has_errors = *has_errors || ty.contains_unknown(); + } + type_of_type.shrink_to_fit(); *has_errors |= !type_mismatches.is_empty(); @@ -1323,6 +1330,10 @@ impl<'db> InferenceContext<'db> { self.result.type_of_pat.insert(pat, ty); } + fn write_type_ty(&mut self, pat: TypeRefId, ty: Ty) { + self.result.type_of_type.insert(pat, ty); + } + fn write_binding_ty(&mut self, id: BindingId, ty: Ty) { self.result.type_of_binding.insert(id, ty); } @@ -1369,7 +1380,9 @@ impl<'db> InferenceContext<'db> { let ty = self .with_ty_lowering(store, type_source, lifetime_elision, |ctx| ctx.lower_ty(type_ref)); let ty = self.insert_type_vars(ty); - self.normalize_associated_types_in(ty) + let ty = self.normalize_associated_types_in(ty); + self.write_type_ty(type_ref, ty.clone()); + ty } fn make_body_ty(&mut self, type_ref: TypeRefId) -> Ty { diff --git a/crates/hir-ty/src/tests.rs b/crates/hir-ty/src/tests.rs index 79754bc8a09b..6b1fe8392d9d 100644 --- a/crates/hir-ty/src/tests.rs +++ b/crates/hir-ty/src/tests.rs @@ -25,6 +25,7 @@ use hir_def::{ item_scope::ItemScope, nameres::DefMap, src::HasSource, + type_ref::TypeRefId, }; use hir_expand::{FileRange, InFile, db::ExpandDatabase}; use itertools::Itertools; @@ -233,6 +234,22 @@ fn check_impl( None => format_to!(unexpected_type_mismatches, "{:?}: {}\n", range.range, actual), } } + + for (pat, ty) in inference_result.type_of_type.iter() { + let node = match type_node(&body_source_map, pat, &db) { + Some(value) => value, + None => continue, + }; + let range = node.as_ref().original_file_range_rooted(&db); + if let Some(expected) = types.remove(&range) { + let actual = if display_source { + ty.display_source_code(&db, def.module(&db), true).unwrap() + } else { + ty.display_test(&db, display_target).to_string() + }; + assert_eq!(actual, expected, "type annotation differs at {:#?}", range.range); + } + } } let mut buf = String::new(); @@ -288,6 +305,20 @@ fn pat_node( }) } +fn type_node( + body_source_map: &BodySourceMap, + type_ref: TypeRefId, + db: &TestDB, +) -> Option> { + Some(match body_source_map.type_syntax(type_ref) { + Ok(sp) => { + let root = db.parse_or_expand(sp.file_id); + sp.map(|ptr| ptr.to_node(&root).syntax().clone()) + } + Err(SyntheticSyntax) => return None, + }) +} + fn infer(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> String { infer_with_mismatches(ra_fixture, false) } diff --git a/crates/hir-ty/src/tests/display_source_code.rs b/crates/hir-ty/src/tests/display_source_code.rs index 6e3faa05a629..ce6b06c54fda 100644 --- a/crates/hir-ty/src/tests/display_source_code.rs +++ b/crates/hir-ty/src/tests/display_source_code.rs @@ -246,3 +246,18 @@ fn test() { "#, ); } + +#[test] +fn type_ref_type() { + check_types_source_code( + r#" +struct S(T); +fn test() { + let f: S<_> = S(3); + //^^^^ S + let f: [_; _] = [4, 5, 6]; + //^^^^^^ [i32; 3] +} +"#, + ); +} From 8e8d6154573b40e68af8a815c10202e578382926 Mon Sep 17 00:00:00 2001 From: Nicolas Guichard Date: Thu, 26 Jun 2025 16:06:18 +0200 Subject: [PATCH 2/3] Use inferred type in SourceAnalyzer::type_of_type This uses the new InferenceResult content to turn type references into completely resolved types instead of just returning the lexical type. --- crates/hir/src/source_analyzer.rs | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs index f18ca7cb2017..a56f53af095b 100644 --- a/crates/hir/src/source_analyzer.rs +++ b/crates/hir/src/source_analyzer.rs @@ -263,17 +263,24 @@ impl<'db> SourceAnalyzer<'db> { ty: &ast::Type, ) -> Option> { let type_ref = self.type_id(ty)?; - let ty = TyLoweringContext::new( - db, - &self.resolver, - self.store()?, - self.resolver.generic_def()?, - // FIXME: Is this correct here? Anyway that should impact mostly diagnostics, which we don't emit here - // (this can impact the lifetimes generated, e.g. in `const` they won't be `'static`, but this seems like a - // small problem). - LifetimeElisionKind::Infer, - ) - .lower_ty(type_ref); + let ty = self.infer().and_then(|infer| infer.type_of_type.get(type_ref)).cloned().or_else( + || { + Some( + TyLoweringContext::new( + db, + &self.resolver, + self.store()?, + self.resolver.generic_def()?, + // FIXME: Is this correct here? Anyway that should impact mostly diagnostics, which we don't emit here + // (this can impact the lifetimes generated, e.g. in `const` they won't be `'static`, but this seems like a + // small problem). + LifetimeElisionKind::Infer, + ) + .lower_ty(type_ref), + ) + }, + )?; + Some(Type::new_with_resolver(db, &self.resolver, ty)) } From f08ffb3b2cae8690165ce9476c6dd5826ad5bd43 Mon Sep 17 00:00:00 2001 From: Nicolas Guichard Date: Thu, 26 Jun 2025 16:06:18 +0200 Subject: [PATCH 3/3] extract_type_alias assist extracts inferred type when possible When met with types with placeholders, this ensures this assist extracts the inferred type instead of the type with placeholders. For instance, when met with this code: ``` fn main() { let vec: Vec<_> = vec![4]; } ``` selecting Vec<_> and extracting an alias will now yield `Vec` instead of `Vec<_>`. --- .../src/handlers/extract_type_alias.rs | 59 ++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/crates/ide-assists/src/handlers/extract_type_alias.rs b/crates/ide-assists/src/handlers/extract_type_alias.rs index d843ac64567a..f2c5fee29702 100644 --- a/crates/ide-assists/src/handlers/extract_type_alias.rs +++ b/crates/ide-assists/src/handlers/extract_type_alias.rs @@ -1,4 +1,5 @@ use either::Either; +use hir::HirDisplay; use ide_db::syntax_helpers::node_ext::walk_ty; use syntax::{ ast::{self, AstNode, HasGenericArgs, HasGenericParams, HasName, edit::IndentLevel, make}, @@ -39,6 +40,15 @@ pub(crate) fn extract_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) -> ); let target = ty.syntax().text_range(); + let module = ctx.sema.scope(ty.syntax())?.module(); + let resolved_ty = ctx.sema.resolve_type(&ty)?; + let resolved_ty = if !resolved_ty.contains_unknown() { + let resolved_ty = resolved_ty.display_source_code(ctx.db(), module.into(), false).ok()?; + make::ty(&resolved_ty) + } else { + ty.clone() + }; + acc.add( AssistId::refactor_extract("extract_type_alias"), "Extract type as type alias", @@ -69,8 +79,9 @@ pub(crate) fn extract_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) -> edit.replace(ty.syntax(), new_ty.syntax()); // Insert new alias - let ty_alias = make::ty_alias("Type", generic_params, None, None, Some((ty, None))) - .clone_for_update(); + let ty_alias = + make::ty_alias("Type", generic_params, None, None, Some((resolved_ty, None))) + .clone_for_update(); if let Some(cap) = ctx.config.snippet_cap { if let Some(name) = ty_alias.name() { @@ -390,4 +401,48 @@ where "#, ); } + + #[test] + fn inferred_generic_type_parameter() { + check_assist( + extract_type_alias, + r#" +struct Wrap(T); + +fn main() { + let wrap: $0Wrap<_>$0 = Wrap::<_>(3i32); +} + "#, + r#" +struct Wrap(T); + +type $0Type = Wrap; + +fn main() { + let wrap: Type = Wrap::<_>(3i32); +} + "#, + ) + } + + #[test] + fn inferred_generic_const_parameter() { + check_assist( + extract_type_alias, + r#" +fn main() { + let array: $0[i32; _]$0 = [3]; + dbg!(array); +} + "#, + r#" +type $0Type = [i32; 1]; + +fn main() { + let array: Type = [3]; + dbg!(array); +} + "#, + ) + } }