diff --git a/crates/hir-def/src/hir.rs b/crates/hir-def/src/hir.rs index 4c8d835ad7fc..34717833b191 100644 --- a/crates/hir-def/src/hir.rs +++ b/crates/hir-def/src/hir.rs @@ -32,7 +32,7 @@ use crate::{ HygieneId, path::{GenericArgs, Path}, }, - type_ref::{Mutability, Rawness}, + type_ref::{ConstRef, Mutability, Rawness}, }; pub use syntax::ast::{ArithOp, BinaryOp, CmpOp, LogicOp, Ordering, RangeOp, UnaryOp}; @@ -76,6 +76,38 @@ impl ExprOrPatId { } stdx::impl_from!(ExprId, PatId for ExprOrPatId); +// FIXME: Like ExprOrPatId above, eventually encode this as a single u32? +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, salsa::Update)] +pub enum TypeRefIdOrConstRef { + TypeRefId(TypeRefId), + ConstRef(ConstRef), +} + +impl TypeRefIdOrConstRef { + pub fn as_type_ref(self) -> Option { + match self { + Self::TypeRefId(v) => Some(v), + _ => None, + } + } + + pub fn is_type_ref(&self) -> bool { + matches!(self, Self::TypeRefId(_)) + } + + pub fn as_const_ref(self) -> Option { + match self { + Self::ConstRef(v) => Some(v), + _ => None, + } + } + + pub fn is_expr(&self) -> bool { + matches!(self, Self::ConstRef(_)) + } +} +stdx::impl_from!(TypeRefId, ConstRef for TypeRefIdOrConstRef); + #[derive(Debug, Clone, Eq, PartialEq)] pub struct Label { pub name: Name, diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 86a4f613b318..0b5f52d2a37b 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -45,7 +45,7 @@ use hir_def::{ TupleFieldId, TupleId, VariantId, attrs::AttrFlags, expr_store::{Body, ExpressionStore, HygieneId, path::Path}, - hir::{BindingId, ExprId, ExprOrPatId, LabelId, PatId}, + hir::{BindingId, ExprId, ExprOrPatId, LabelId, PatId, TypeRefIdOrConstRef}, lang_item::LangItems, layout::Integer, resolver::{HasResolver, ResolveValueResult, Resolver, TypeNs, ValueNs}, @@ -95,7 +95,7 @@ use crate::{ }, method_resolution::CandidateId, next_solver::{ - AliasTy, Const, ConstKind, DbInterner, ErrorGuaranteed, GenericArgs, Region, + AliasTy, Const, ConstKind, DbInterner, ErrorGuaranteed, GenericArgs, Region, StoredConst, StoredGenericArg, StoredGenericArgs, StoredTy, StoredTys, Term, Ty, TyKind, Tys, abi::Safety, infer::{InferCtxt, ObligationInspector, traits::ObligationCause}, @@ -705,6 +705,7 @@ pub struct InferenceResult { pub(crate) type_of_pat: ArenaMap, pub(crate) type_of_binding: ArenaMap, pub(crate) type_of_type_placeholder: FxHashMap, + pub(crate) const_of_const_placeholder: FxHashMap, pub(crate) type_of_opaque: FxHashMap, /// Whether there are any type-mismatching errors in the result. @@ -1007,6 +1008,7 @@ impl InferenceResult { type_of_pat: Default::default(), type_of_binding: Default::default(), type_of_type_placeholder: Default::default(), + const_of_const_placeholder: Default::default(), type_of_opaque: Default::default(), skipped_ref_pats: Default::default(), has_errors: Default::default(), @@ -1082,6 +1084,16 @@ impl InferenceResult { pub fn type_of_type_placeholder<'db>(&self, type_ref: TypeRefId) -> Option> { self.type_of_type_placeholder.get(&type_ref).map(|ty| ty.as_ref()) } + pub fn placeholder_consts<'db>( + &self, + ) -> impl Iterator)> { + self.const_of_const_placeholder + .iter() + .map(|(&type_ref_or_const, const_)| (type_ref_or_const, const_.as_ref())) + } + pub fn const_of_const_placeholder<'db>(&self, expr: TypeRefIdOrConstRef) -> Option> { + self.const_of_const_placeholder.get(&expr).map(|ty| ty.as_ref()) + } pub fn type_of_expr_or_pat<'db>(&self, id: ExprOrPatId) -> Option> { match id { ExprOrPatId::ExprId(id) => self.type_of_expr.get(id).map(|it| it.as_ref()), @@ -1365,6 +1377,7 @@ impl<'body, 'db> InferenceContext<'body, 'db> { type_of_pat, type_of_binding, type_of_type_placeholder, + const_of_const_placeholder, type_of_opaque, has_errors: _, diagnostics: _, @@ -1396,6 +1409,7 @@ impl<'body, 'db> InferenceContext<'body, 'db> { merge_arena_maps(type_of_pat, &other.type_of_pat); merge_arena_maps(type_of_binding, &other.type_of_binding); merge_hash_maps(type_of_type_placeholder, &other.type_of_type_placeholder); + merge_hash_maps(const_of_const_placeholder, &other.const_of_const_placeholder); merge_hash_maps(type_of_opaque, &other.type_of_opaque); merge_hash_maps(expr_adjustments, &other.expr_adjustments); merge_hash_maps(pat_adjustments, &other.pat_adjustments); @@ -1531,6 +1545,7 @@ impl<'body, 'db> InferenceContext<'body, 'db> { type_of_pat, type_of_binding, type_of_type_placeholder, + const_of_const_placeholder, type_of_opaque, skipped_ref_pats, closures_data, @@ -1569,6 +1584,10 @@ impl<'body, 'db> InferenceContext<'body, 'db> { resolver.resolve_completely(ty); } type_of_type_placeholder.shrink_to_fit(); + for const_ in const_of_const_placeholder.values_mut() { + resolver.resolve_completely(const_); + } + const_of_const_placeholder.shrink_to_fit(); type_of_opaque.shrink_to_fit(); if let Some(nodes_with_type_mismatches) = nodes_with_type_mismatches { @@ -1868,6 +1887,7 @@ impl<'body, 'db> InferenceContext<'body, 'db> { InferenceTyDiagnosticSource::Body => Some(&mut InferenceTyLoweringVarsCtx { table: &mut self.table, type_of_type_placeholder: &mut self.result.type_of_type_placeholder, + const_of_const_placeholder: &mut self.result.const_of_const_placeholder, } as _), InferenceTyDiagnosticSource::Signature => None, }; @@ -2203,6 +2223,7 @@ impl<'body, 'db> InferenceContext<'body, 'db> { let mut vars_ctx = InferenceTyLoweringVarsCtx { table: &mut self.table, type_of_type_placeholder: &mut self.result.type_of_type_placeholder, + const_of_const_placeholder: &mut self.result.const_of_const_placeholder, }; let mut ctx = TyLoweringContext::new( self.db, diff --git a/crates/hir-ty/src/infer/diagnostics.rs b/crates/hir-ty/src/infer/diagnostics.rs index dd0efea4d754..0ce0bbb5c7e5 100644 --- a/crates/hir-ty/src/infer/diagnostics.rs +++ b/crates/hir-ty/src/infer/diagnostics.rs @@ -7,6 +7,8 @@ use std::ops::{Deref, DerefMut}; use either::Either; use hir_def::expr_store::path::Path; +use hir_def::hir::TypeRefIdOrConstRef; +use hir_def::type_ref::ConstRef; use hir_def::{ExpressionStoreOwnerId, GenericDefId}; use hir_def::{expr_store::ExpressionStore, type_ref::TypeRefId}; use hir_def::{hir::ExprOrPatId, resolver::Resolver}; @@ -14,6 +16,7 @@ use la_arena::{Idx, RawIdx}; use rustc_hash::FxHashMap; use thin_vec::ThinVec; +use crate::next_solver::StoredConst; use crate::{ InferenceDiagnostic, InferenceTyDiagnosticSource, Span, TyLoweringDiagnostic, db::{AnonConstId, HirDatabase}, @@ -61,6 +64,7 @@ pub(crate) struct PathDiagnosticCallbackData<'a> { pub(super) struct InferenceTyLoweringVarsCtx<'a, 'db> { pub(super) table: &'a mut InferenceTable<'db>, pub(super) type_of_type_placeholder: &'a mut FxHashMap, + pub(super) const_of_const_placeholder: &'a mut FxHashMap, } impl<'db> TyLoweringInferVarsCtx<'db> for InferenceTyLoweringVarsCtx<'_, 'db> { @@ -74,7 +78,18 @@ impl<'db> TyLoweringInferVarsCtx<'db> for InferenceTyLoweringVarsCtx<'_, 'db> { ty } fn next_const_var(&mut self, span: Span) -> Const<'db> { - self.table.infer_ctxt.next_const_var(span) + let const_ = self.table.infer_ctxt.next_const_var(span); + + let type_ref_id_or_const_ref = match span { + Span::ExprId(expr) => Some(TypeRefIdOrConstRef::ConstRef(ConstRef { expr })), + Span::TypeRefId(type_ref) => Some(type_ref.into()), + _ => None, + }; + if let Some(key) = type_ref_id_or_const_ref { + self.const_of_const_placeholder.insert(key, const_.store()); + } + + const_ } fn next_region_var(&mut self, span: Span) -> Region<'db> { self.table.infer_ctxt.next_region_var(span) diff --git a/crates/hir-ty/src/infer/path.rs b/crates/hir-ty/src/infer/path.rs index 0ec72edc3d59..a2798488857f 100644 --- a/crates/hir-ty/src/infer/path.rs +++ b/crates/hir-ty/src/infer/path.rs @@ -152,6 +152,7 @@ impl<'db> InferenceContext<'_, 'db> { let mut vars_ctx = InferenceTyLoweringVarsCtx { table: &mut self.table, type_of_type_placeholder: &mut self.result.type_of_type_placeholder, + const_of_const_placeholder: &mut self.result.const_of_const_placeholder, }; let mut ctx = TyLoweringContext::new( self.db, diff --git a/crates/hir-ty/src/lib.rs b/crates/hir-ty/src/lib.rs index 1900a41f7f17..111d099401c7 100644 --- a/crates/hir-ty/src/lib.rs +++ b/crates/hir-ty/src/lib.rs @@ -67,7 +67,7 @@ use hir_def::{ TypeParamId, db::DefDatabase, expr_store::{Body, ExpressionStore}, - hir::{BindingId, ExprId, ExprOrPatId, PatId}, + hir::{BindingId, ExprId, ExprOrPatId, PatId, TypeRefIdOrConstRef}, resolver::{HasResolver, Resolver, TypeNs}, type_ref::{Rawness, TypeRefId}, }; @@ -539,6 +539,15 @@ impl From for Span { } } +impl From for Span { + fn from(value: TypeRefIdOrConstRef) -> Self { + match value { + TypeRefIdOrConstRef::TypeRefId(idx) => idx.into(), + TypeRefIdOrConstRef::ConstRef(idx) => idx.expr.into(), + } + } +} + impl Span { pub(crate) fn pick_best(a: Span, b: Span) -> Span { // We prefer dummy spans to minimize the risk of false errors. diff --git a/crates/hir-ty/src/next_solver/consts.rs b/crates/hir-ty/src/next_solver/consts.rs index 2df9a5259ddf..29b9fb7781eb 100644 --- a/crates/hir-ty/src/next_solver/consts.rs +++ b/crates/hir-ty/src/next_solver/consts.rs @@ -217,6 +217,15 @@ impl<'db> TypeVisitable> for Const<'db> { } } +impl<'db> TypeVisitable> for StoredConst { + fn visit_with>>( + &self, + visitor: &mut V, + ) -> V::Result { + self.as_ref().visit_with(visitor) + } +} + impl<'db> TypeSuperVisitable> for Const<'db> { fn super_visit_with>>( &self, @@ -248,6 +257,18 @@ impl<'db> TypeFoldable> for Const<'db> { } } +impl<'db> TypeFoldable> for StoredConst { + fn try_fold_with>>( + self, + folder: &mut F, + ) -> Result { + Ok(self.as_ref().try_fold_with(folder)?.store()) + } + fn fold_with>>(self, folder: &mut F) -> Self { + self.as_ref().fold_with(folder).store() + } +} + impl<'db> TypeSuperFoldable> for Const<'db> { fn try_super_fold_with>>( self, diff --git a/crates/hir-ty/src/tests.rs b/crates/hir-ty/src/tests.rs index ce4eff701c90..e973ca0736ef 100644 --- a/crates/hir-ty/src/tests.rs +++ b/crates/hir-ty/src/tests.rs @@ -19,7 +19,7 @@ use hir_def::{ AdtId, AssocItemId, DefWithBodyId, GenericDefId, HasModule, Lookup, ModuleDefId, ModuleId, SyntheticSyntax, VariantId, expr_store::{Body, BodySourceMap, ExpressionStore, ExpressionStoreSourceMap}, - hir::{ExprId, Pat, PatId}, + hir::{ExprId, Pat, PatId, TypeRefIdOrConstRef}, item_scope::ItemScope, nameres::DefMap, src::HasSource, @@ -83,6 +83,7 @@ fn check_impl( let mut had_annotations = false; let mut mismatches = FxHashMap::default(); let mut types = FxHashMap::default(); + let mut consts = FxHashMap::default(); let mut adjustments = FxHashMap::default(); for (file_id, annotations) in db.extract_annotations() { for (range, expected) in annotations { @@ -91,6 +92,8 @@ fn check_impl( types.insert(file_range, expected); } else if let Some(ty) = expected.strip_prefix("type: ") { types.insert(file_range, ty.to_owned()); + } else if let Some(const_) = expected.strip_prefix("const: ") { + consts.insert(file_range, const_.to_owned()); } else if expected.starts_with("expected") { mismatches.insert(file_range, expected); } else if let Some(adjs) = expected.strip_prefix("adjustments:") { @@ -243,6 +246,29 @@ fn check_impl( assert_eq!(actual, expected, "type annotation differs at {:#?}", range.range); } } + + for (type_ref_id_or_const_ref, const_) in inference_result.placeholder_consts() { + let node = match type_ref_id_or_const_ref { + TypeRefIdOrConstRef::TypeRefId(type_ref) => { + type_node(body_source_map, type_ref, &db) + } + TypeRefIdOrConstRef::ConstRef(const_ref) => { + expr_node(body_source_map, const_ref.expr, &db) + } + }; + let Some(node) = node else { continue }; + let range = node.as_ref().original_file_range_rooted(&db); + if let Some(expected) = consts.remove(&range) { + let actual = salsa::attach(&db, || { + if display_source { + const_.display_source_code(&db, def.module(&db), true).unwrap() + } else { + const_.display_test(&db, display_target).to_string() + } + }); + assert_eq!(actual, expected, "const annotation differs at {:#?}", range.range); + } + } } let mut buf = String::new(); @@ -261,6 +287,12 @@ fn check_impl( format_to!(buf, "{:?}: type {}\n", t.0.range, t.1); } } + if !consts.is_empty() { + format_to!(buf, "Unchecked const annotations:\n"); + for c in consts { + format_to!(buf, "{:?}: const {}\n", c.0.range, c.1); + } + } if !adjustments.is_empty() { format_to!(buf, "Unchecked adjustments annotations:\n"); for t in adjustments { diff --git a/crates/hir-ty/src/tests/display_source_code.rs b/crates/hir-ty/src/tests/display_source_code.rs index 37da7fc87563..1c3168dc66e8 100644 --- a/crates/hir-ty/src/tests/display_source_code.rs +++ b/crates/hir-ty/src/tests/display_source_code.rs @@ -1,4 +1,4 @@ -use super::check_types_source_code; +use super::{check, check_types_source_code}; #[test] fn qualify_path_to_submodule() { @@ -248,19 +248,21 @@ fn test() { } #[test] -fn type_placeholder_type() { - check_types_source_code( +fn type_and_const_placeholders() { + check( r#" -struct S(T); +struct S([T; N]); fn test() { - let f: S<_> = S(3); - //^ i32 + let f: S<_, _> = S([1, 2]); + //^ type: i32 + //^ const: 2 let f: [_; _] = [4_u32, 5, 6]; - //^ u32 + //^ type: u32 + //^ const: 3 let f: (_, _, _) = (1_u32, 1_i32, false); - //^ u32 - //^ i32 - //^ bool + //^ type: u32 + //^ type: i32 + //^ type: bool } "#, ); diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index d0202c1054c5..df91aa14742e 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -37,7 +37,7 @@ use hir_ty::{ diagnostics::unsafe_operations, infer_query_with_inspect, next_solver::{ - AnyImplId, DbInterner, + AnyImplId, Const as ResolvedConst, DbInterner, format_proof_tree::{ProofTreeData, dump_proof_tree_structured}, }, }; @@ -1713,6 +1713,18 @@ impl<'db> SemanticsImpl<'db> { analyze.type_of_type(self.db, ty) } + pub fn resolve_infer( + &self, + ty: &ast::InferType, + ) -> Option, ResolvedConst<'db>>> { + let analyze = self.analyze(ty.syntax())?; + if let Some(const_) = analyze.resolve_infer_type_as_const(ty) { + return Some(Either::Right(const_)); + } + let ty = analyze.type_of_type(self.db, &ast::Type::InferType(ty.clone()))?; + Some(Either::Left(ty)) + } + pub fn resolve_trait(&self, path: &ast::Path) -> Option { let parent_ty = path.syntax().parent().and_then(ast::Type::cast)?; let analyze = self.analyze(path.syntax())?; @@ -1874,6 +1886,13 @@ impl<'db> SemanticsImpl<'db> { self.analyze(try_expr.syntax())?.resolve_try_expr(self.db, try_expr) } + pub fn resolve_underscore_expr( + &self, + underscore_expr: &ast::UnderscoreExpr, + ) -> Option> { + self.analyze(underscore_expr.syntax())?.resolve_underscore_expr(underscore_expr) + } + /// The type that the associated `try` block, closure or function expects. pub fn try_expr_returned_type(&self, try_expr: &ast::TryExpr) -> Option> { self.ancestors_with_macros(try_expr.syntax().clone()).find_map(|parent| { diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs index ba62cc11c3c0..5f7faa6f6641 100644 --- a/crates/hir/src/source_analyzer.rs +++ b/crates/hir/src/source_analyzer.rs @@ -24,7 +24,7 @@ use hir_def::{ lang_item::LangItems, nameres::MacroSubNs, resolver::{Resolver, TypeNs, ValueNs, resolver_for_scope}, - type_ref::{Mutability, TypeRefId}, + type_ref::{ConstRef, Mutability, TypeRefId}, }; use hir_expand::{ HirFileId, InFile, @@ -41,8 +41,8 @@ use hir_ty::{ lang_items::lang_items_for_bin_op, method_resolution::{self, CandidateId}, next_solver::{ - AliasTy, DbInterner, DefaultAny, ErrorGuaranteed, GenericArgs, ParamEnv, Region, Ty, - TyKind, TypingMode, infer::DbInternerInferExt, + AliasTy, Const as ResolvedConst, DbInterner, DefaultAny, ErrorGuaranteed, GenericArgs, + ParamEnv, Region, Ty, TyKind, TypingMode, infer::DbInternerInferExt, }, traits::structurally_normalize_ty, }; @@ -411,7 +411,25 @@ impl<'db> SourceAnalyzer<'db> { self.types.types.error } } - fn next_const_var(&mut self, _span: hir_ty::Span) -> hir_ty::next_solver::Const<'db> { + fn next_const_var(&mut self, span: hir_ty::Span) -> hir_ty::next_solver::Const<'db> { + if let Some(infer) = self.infer { + match span { + hir_ty::Span::TypeRefId(type_ref) => { + if let Some(const_) = infer.const_of_const_placeholder(type_ref.into()) + { + return const_; + } + } + hir_ty::Span::ExprId(expr) => { + if let Some(const_) = + infer.const_of_const_placeholder(ConstRef { expr }.into()) + { + return const_; + } + } + _ => {} + } + } self.types.consts.error } fn next_region_var(&mut self, _span: hir_ty::Span) -> Region<'db> { @@ -839,6 +857,22 @@ impl<'db> SourceAnalyzer<'db> { Some(self.resolve_impl_method_or_trait_def(db, op_fn, substs)) } + pub(crate) fn resolve_underscore_expr( + &self, + underscore_expr: &ast::UnderscoreExpr, + ) -> Option> { + let expr = self.expr_id(ast::Expr::UnderscoreExpr(underscore_expr.clone()))?.as_expr()?; + self.infer()?.const_of_const_placeholder(ConstRef { expr }.into()) + } + + pub(crate) fn resolve_infer_type_as_const( + &self, + infer_type: &ast::InferType, + ) -> Option> { + let type_ref = self.type_id(&ast::Type::InferType(infer_type.clone()))?; + self.infer()?.const_of_const_placeholder(type_ref.into()) + } + pub(crate) fn resolve_record_field( &self, db: &'db dyn HirDatabase, diff --git a/crates/ide-assists/src/handlers/extract_type_alias.rs b/crates/ide-assists/src/handlers/extract_type_alias.rs index ecb031e42d7e..1feaee7facd5 100644 --- a/crates/ide-assists/src/handlers/extract_type_alias.rs +++ b/crates/ide-assists/src/handlers/extract_type_alias.rs @@ -427,6 +427,48 @@ fn main() { ) } + #[test] + fn inferred_array_length() { + check_assist( + extract_type_alias, + r#" +fn main() { + let a: $0[i32; _]$0 = [1, 2, 3]; +} + "#, + r#" +type $0Type = [i32; 3]; + +fn main() { + let a: Type = [1, 2, 3]; +} + "#, + ) + } + + #[test] + fn inferred_generic_const_parameter() { + check_assist( + extract_type_alias, + r#" +struct Wrap([i32; N]); + +fn main() { + let wrap: $0Wrap<_>$0 = Wrap([1, 2, 3]); +} + "#, + r#" +struct Wrap([i32; N]); + +type $0Type = Wrap<3>; + +fn main() { + let wrap: Type = Wrap([1, 2, 3]); +} + "#, + ) + } + #[test] fn inferred_type() { check_assist( diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs index a15366fea962..7cc28c4f698c 100644 --- a/crates/ide/src/inlay_hints.rs +++ b/crates/ide/src/inlay_hints.rs @@ -240,6 +240,7 @@ fn hints( }, ast::Expr::RangeExpr(it) => range_exclusive::hints(hints, famous_defs, config, it), ast::Expr::Literal(it) => ra_fixture::hints(hints, famous_defs.0, file_id, config, it), + ast::Expr::UnderscoreExpr(it) => placeholders::const_hints(hints, famous_defs, config, display_target, it), _ => Some(()), } }, diff --git a/crates/ide/src/inlay_hints/placeholders.rs b/crates/ide/src/inlay_hints/placeholders.rs index e535b92a5792..6b7c0ae64b4b 100644 --- a/crates/ide/src/inlay_hints/placeholders.rs +++ b/crates/ide/src/inlay_hints/placeholders.rs @@ -4,11 +4,12 @@ //! //^ = i32 //! ``` -use hir::DisplayTarget; +use either::Either; +use hir::{DisplayTarget, HirDisplay}; use ide_db::famous_defs::FamousDefs; use syntax::{ AstNode, - ast::{InferType, Type}, + ast::{InferType, UnderscoreExpr}, }; use crate::{InlayHint, InlayHintPosition, InlayHintsConfig, InlayKind, inlay_hints::label_of_ty}; @@ -27,9 +28,14 @@ pub(super) fn type_hints( let syntax = placeholder.syntax(); let range = syntax.text_range(); - let ty = sema.resolve_type(&Type::InferType(placeholder))?; + let type_or_const = sema.resolve_infer(&placeholder)?; - let mut label = label_of_ty(famous_defs, config, &ty, display_target)?; + let mut label = match type_or_const { + Either::Left(ty) => label_of_ty(famous_defs, config, &ty, display_target)?, + Either::Right(const_) => { + const_.display_truncated(sema.db, config.max_length, display_target).to_string().into() + } + }; label.prepend_str("= "); acc.push(InlayHint { @@ -45,6 +51,38 @@ pub(super) fn type_hints( Some(()) } +pub(super) fn const_hints( + acc: &mut Vec, + FamousDefs(sema, _): &FamousDefs<'_, '_>, + config: &InlayHintsConfig<'_>, + display_target: DisplayTarget, + placeholder: UnderscoreExpr, +) -> Option<()> { + if !config.type_hints || config.hide_inferred_type_hints { + return None; + } + + let syntax = placeholder.syntax(); + let range = syntax.text_range(); + + let const_ = sema.resolve_underscore_expr(&placeholder)?; + + let display = const_.display_truncated(sema.db, config.max_length, display_target); + let label = format!("= {display}").into(); + + acc.push(InlayHint { + range, + kind: InlayKind::Type, + label, + text_edit: None, + position: InlayHintPosition::After, + pad_left: true, + pad_right: false, + resolve_parent: None, + }); + Some(()) +} + #[cfg(test)] mod tests { use crate::{ @@ -58,17 +96,20 @@ mod tests { } #[test] - fn inferred_types() { + fn inferred_types_and_consts() { check_type_infer( r#" -struct S(T); +struct S([T; N]); fn foo() { - let t: (_, _, [_; _]) = (1_u32, S(2), [false] as _); + let t: (_, S<_, _>, [_; _]) = (1_u32, S([2, 3]) as _, [false] as _); //^ = u32 - //^ = S - //^ = bool - //^ = [bool; 1] + //^ = i32 + //^ = 2 + //^ = bool + //^ = 1 + //^ = S + //^ = [bool; 1] } "#, );