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