Unnamed repository; edit this file 'description' to name the repository.
41 files changed, 1395 insertions, 222 deletions
diff --git a/crates/hir-def/src/attrs.rs b/crates/hir-def/src/attrs.rs index 7757d537e6..352c98a76a 100644 --- a/crates/hir-def/src/attrs.rs +++ b/crates/hir-def/src/attrs.rs @@ -265,6 +265,12 @@ fn match_attr_flags(attr_flags: &mut AttrFlags, attr: ast::Meta) -> ControlFlow< } _ => {} }, + "diagnostic" => match &*second_segment { + "do_not_recommend" => { + attr_flags.insert(AttrFlags::DIAGNOSTIC_DO_NOT_RECOMMEND) + } + _ => {} + }, _ => {} }, } @@ -339,6 +345,8 @@ bitflags::bitflags! { const PREFER_UNDERSCORE_IMPORT = 1 << 49; const IS_MUST_USE = 1 << 50; + + const DIAGNOSTIC_DO_NOT_RECOMMEND = 1 << 51; } } diff --git a/crates/hir-def/src/expr_store/lower/format_args.rs b/crates/hir-def/src/expr_store/lower/format_args.rs index beb1267173..b058ad6d9a 100644 --- a/crates/hir-def/src/expr_store/lower/format_args.rs +++ b/crates/hir-def/src/expr_store/lower/format_args.rs @@ -30,12 +30,14 @@ impl<'db> ExprCollector<'db> { ) -> ExprId { let mut args = FormatArgumentsCollector::default(); f.args().for_each(|arg| { + let expr = arg.expr(); args.add(FormatArgument { kind: match arg.arg_name() { Some(name) => FormatArgumentKind::Named(Name::new_root(name.name().text())), None => FormatArgumentKind::Normal, }, - expr: self.collect_expr_opt(arg.expr()), + syntax: expr.as_ref().map(AstPtr::new), + expr: self.collect_expr_opt(expr), }); }); let template = f.template(); @@ -53,8 +55,10 @@ impl<'db> ExprCollector<'db> { Some(((s, is_direct_literal), template)) => { let call_ctx = SyntaxContext::root(self.def_map.edition()); let hygiene = self.hygiene_id_for(s.syntax().text_range()); + let template_ptr = AstPtr::new(&template); let fmt = format_args::parse( &s, + template_ptr, fmt_snippet, args, is_direct_literal, @@ -65,10 +69,7 @@ impl<'db> ExprCollector<'db> { .template_map .get_or_insert_with(Default::default) .implicit_capture_to_source - .insert( - expr_id, - self.expander.in_file((AstPtr::new(&template), range)), - ); + .insert(expr_id, self.expander.in_file((template_ptr, range))); } if !hygiene.is_root() { self.store.ident_hygiene.insert(expr_id.into(), hygiene); @@ -340,7 +341,8 @@ impl<'db> ExprCollector<'db> { expr: args_ident_expr, name: Name::new_tuple_field(arg_index), }); - self.make_argument(arg, ty) + let arg_ptr = arguments.get(arg_index).and_then(|it| it.syntax); + self.make_argument(arg_ptr, arg, ty) }) .collect(); let args = @@ -557,7 +559,8 @@ impl<'db> ExprCollector<'db> { rawness: Rawness::Ref, mutability: Mutability::Shared, }); - self.make_argument(arg, ty) + let arg_ptr = arguments.get(arg_index).and_then(|it| it.syntax); + self.make_argument(arg_ptr, arg, ty) }) .collect(); let array = @@ -649,7 +652,8 @@ impl<'db> ExprCollector<'db> { rawness: Rawness::Ref, mutability: Mutability::Shared, }); - self.make_argument(ref_arg, ty) + let arg_ptr = arguments.get(arg_index).and_then(|it| it.syntax); + self.make_argument(arg_ptr, ref_arg, ty) }) .collect(); let args = @@ -716,7 +720,8 @@ impl<'db> ExprCollector<'db> { expr: args_ident_expr, name: Name::new_tuple_field(arg_index), }); - self.make_argument(arg, ty) + let arg_ptr = arguments.get(arg_index).and_then(|it| it.syntax); + self.make_argument(arg_ptr, arg, ty) }) .collect(); let array = @@ -976,11 +981,16 @@ impl<'db> ExprCollector<'db> { /// ```text /// <core::fmt::Argument>::new_…(arg) /// ``` - fn make_argument(&mut self, arg: ExprId, ty: ArgumentType) -> ExprId { + fn make_argument( + &mut self, + arg_ptr: Option<AstPtr<ast::Expr>>, + arg: ExprId, + ty: ArgumentType, + ) -> ExprId { use ArgumentType::*; use FormatTrait::*; - let new_fn = self.ty_rel_lang_path_desugared_expr( + let new_fn = self.ty_rel_lang_path( self.lang_items().FormatArgument, match ty { Format(Display) => sym::new_display, @@ -995,6 +1005,22 @@ impl<'db> ExprCollector<'db> { Usize => sym::from_usize, }, ); + let new_fn = match new_fn { + Some(new_fn) => { + let new_fn = self.store.exprs.alloc(Expr::Path(new_fn)); + if let Some(arg_ptr) = arg_ptr { + // Trait errors (the argument does not implement the expected fmt trait) will show + // on this path, so to not end up with synthetic syntax we insert this mapping. We + // don't want to insert the other way's mapping in order to not override the source + // for the argument. + self.store + .expr_map_back + .insert(new_fn, self.expander.in_file(arg_ptr.wrap_left())); + } + new_fn + } + None => self.missing_expr(), + }; self.alloc_expr_desugared(Expr::Call { callee: new_fn, args: Box::new([arg]) }) } diff --git a/crates/hir-def/src/hir/format_args.rs b/crates/hir-def/src/hir/format_args.rs index 271484da7b..366857f233 100644 --- a/crates/hir-def/src/hir/format_args.rs +++ b/crates/hir-def/src/hir/format_args.rs @@ -7,7 +7,7 @@ use rustc_parse_format as parse; use span::SyntaxContext; use stdx::TupleExt; use syntax::{ - TextRange, + AstPtr, TextRange, ast::{self, IsString}, }; @@ -146,6 +146,7 @@ pub enum FormatCount { pub struct FormatArgument { pub kind: FormatArgumentKind, pub expr: ExprId, + pub syntax: Option<AstPtr<ast::Expr>>, } #[derive(Clone, PartialEq, Eq, Debug)] @@ -171,6 +172,7 @@ use PositionUsedAs::*; #[allow(clippy::unnecessary_lazy_evaluations)] pub(crate) fn parse( s: &ast::String, + string_ptr: AstPtr<ast::Expr>, fmt_snippet: Option<String>, mut args: FormatArgumentsCollector, is_direct_literal: bool, @@ -273,6 +275,11 @@ pub(crate) fn parse( // FIXME: This is problematic, we might want to synthesize a dummy // expression proper and/or desugar these. expr: synth(name, span), + // FIXME: This will lead us to show failed trait bounds (e.g. `T: Display`) + // on the whole template string for implicit arguments instead of just their name. + // Fixing this is hard since we don't have an `AstPtr` for the arg, and it's + // only part of an expression. + syntax: Some(string_ptr), })) } } diff --git a/crates/hir-expand/src/files.rs b/crates/hir-expand/src/files.rs index 09a5c9326e..d64090f913 100644 --- a/crates/hir-expand/src/files.rs +++ b/crates/hir-expand/src/files.rs @@ -128,6 +128,16 @@ impl ErasedAstId { } } +impl<FileKind, N: AstNode> InFileWrapper<FileKind, AstPtr<N>> { + #[inline] + pub fn upcast<M: AstNode>(self) -> InFileWrapper<FileKind, AstPtr<M>> + where + N: Into<M>, + { + self.map(|it| it.upcast()) + } +} + impl<FileKind, T> InFileWrapper<FileKind, T> { pub fn new(file_id: FileKind, value: T) -> Self { Self { file_id, value } diff --git a/crates/hir-ty/src/diagnostics/expr.rs b/crates/hir-ty/src/diagnostics/expr.rs index be29926a38..768b185ae7 100644 --- a/crates/hir-ty/src/diagnostics/expr.rs +++ b/crates/hir-ty/src/diagnostics/expr.rs @@ -182,7 +182,7 @@ impl<'db> ExprValidator<'db> { } // Check that the number of arguments matches the number of parameters. - if self.infer.expr_type_mismatches().next().is_some() { + if self.infer.exprs_have_type_mismatches() { // FIXME: Due to shortcomings in the current type system implementation, only emit // this diagnostic if there are no type mismatches in the containing function. } else if let Expr::MethodCall { receiver, .. } = expr { @@ -355,7 +355,7 @@ impl<'db> ExprValidator<'db> { pat: PatId, initializer: Option<ExprId>, ) -> Option<BodyValidationDiagnostic> { - if self.infer.type_mismatch_for_pat(pat).is_some() { + if self.infer.pat_has_type_mismatch(pat) { return None; } let initializer = initializer?; @@ -683,13 +683,13 @@ pub fn record_pattern_missing_fields( fn types_of_subpatterns_do_match(pat: PatId, body: &Body, infer: &InferenceResult) -> bool { fn walk(pat: PatId, body: &Body, infer: &InferenceResult, has_type_mismatches: &mut bool) { - match infer.type_mismatch_for_pat(pat) { - Some(_) => *has_type_mismatches = true, - None if *has_type_mismatches => (), - None => { + match infer.pat_has_type_mismatch(pat) { + true => *has_type_mismatches = true, + false if *has_type_mismatches => (), + false => { let pat = &body[pat]; if let Pat::ConstBlock(expr) | Pat::Lit(expr) = *pat { - *has_type_mismatches |= infer.type_mismatch_for_expr(expr).is_some(); + *has_type_mismatches |= infer.expr_has_type_mismatch(expr); if *has_type_mismatches { return; } diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs index 97693435fe..fda572a02b 100644 --- a/crates/hir-ty/src/display.rs +++ b/crates/hir-ty/src/display.rs @@ -59,8 +59,8 @@ use crate::{ next_solver::{ AliasTy, Allocation, Clause, ClauseKind, Const, ConstKind, DbInterner, ExistentialPredicate, FnSig, GenericArg, GenericArgKind, GenericArgs, ParamEnv, PolyFnSig, - Region, SolverDefId, StoredEarlyBinder, StoredTy, Term, TermKind, TraitRef, Ty, TyKind, - TypingMode, ValTree, + Region, SolverDefId, StoredEarlyBinder, StoredTy, Term, TermKind, TraitPredicate, TraitRef, + Ty, TyKind, TypingMode, ValTree, abi::Safety, infer::{DbInternerInferExt, traits::ObligationCause}, }, @@ -2275,6 +2275,23 @@ impl<'db> HirDisplay<'db> for TraitRef<'db> { } } +impl<'db> HirDisplay<'db> for TraitPredicate<'db> { + fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result { + self.self_ty().hir_fmt(f)?; + f.write_str(": ")?; + match self.polarity { + rustc_type_ir::PredicatePolarity::Positive => {} + rustc_type_ir::PredicatePolarity::Negative => f.write_char('!')?, + } + let trait_ = self.def_id().0; + f.start_location_link(trait_.into()); + write!(f, "{}", TraitSignature::of(f.db, trait_).name.display(f.db, f.edition()))?; + f.end_location_link(); + let substs = &self.trait_ref.args[1..]; + hir_fmt_generic_args(f, substs, None, None) + } +} + impl<'db> HirDisplay<'db> for Region<'db> { fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result { match self.kind() { diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 83659dde17..4c860210a9 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -89,6 +89,7 @@ use crate::{ abi::Safety, infer::{InferCtxt, ObligationInspector, traits::ObligationCause}, }, + solver_errors::SolverDiagnostic, utils::TargetFeatureIsSafeInTarget, }; @@ -459,13 +460,13 @@ pub enum InferenceDiagnostic { #[type_visitable(ignore)] expr: ExprId, }, -} - -/// A mismatch between an expected and an inferred type. -#[derive(Clone, PartialEq, Eq, Debug, Hash, TypeVisitable, TypeFoldable)] -pub struct TypeMismatch { - pub expected: StoredTy, - pub actual: StoredTy, + TypeMismatch { + #[type_visitable(ignore)] + node: ExprOrPatId, + expected: StoredTy, + found: StoredTy, + }, + SolverDiagnostic(SolverDiagnostic), } /// Represents coercing a value to a different type of value. @@ -685,7 +686,6 @@ pub struct InferenceResult { pub(crate) type_of_type_placeholder: FxHashMap<TypeRefId, StoredTy>, pub(crate) type_of_opaque: FxHashMap<InternedOpaqueTyId, StoredTy>, - pub(crate) type_mismatches: Option<Box<FxHashMap<ExprOrPatId, TypeMismatch>>>, /// Whether there are any type-mismatching errors in the result. // FIXME: This isn't as useful as initially thought due to us falling back placeholders to // `TyKind::Error`. @@ -693,6 +693,8 @@ pub struct InferenceResult { pub(crate) has_errors: bool, /// During inference this field is empty and [`InferenceContext::diagnostics`] is filled instead. diagnostics: ThinVec<InferenceDiagnostic>, + // FIXME: Remove this, change it to be in `InferenceContext`: + nodes_with_type_mismatches: Option<Box<FxHashSet<ExprOrPatId>>>, /// Interned `Error` type to return references to. // FIXME: Remove this. @@ -986,12 +988,12 @@ impl InferenceResult { assoc_resolutions: Default::default(), tuple_field_access_types: Default::default(), diagnostics: Default::default(), + nodes_with_type_mismatches: Default::default(), type_of_expr: Default::default(), type_of_pat: Default::default(), type_of_binding: Default::default(), type_of_type_placeholder: Default::default(), type_of_opaque: Default::default(), - type_mismatches: Default::default(), skipped_ref_pats: Default::default(), has_errors: Default::default(), error_ty: error_ty.store(), @@ -1042,26 +1044,22 @@ impl InferenceResult { ExprOrPatId::PatId(id) => self.assoc_resolutions_for_pat(id), } } - pub fn type_mismatch_for_expr(&self, expr: ExprId) -> Option<&TypeMismatch> { - self.type_mismatches.as_deref()?.get(&expr.into()) + pub fn expr_or_pat_has_type_mismatch(&self, node: ExprOrPatId) -> bool { + self.nodes_with_type_mismatches.as_ref().is_some_and(|it| it.contains(&node)) } - pub fn type_mismatch_for_pat(&self, pat: PatId) -> Option<&TypeMismatch> { - self.type_mismatches.as_deref()?.get(&pat.into()) + pub fn expr_has_type_mismatch(&self, expr: ExprId) -> bool { + self.expr_or_pat_has_type_mismatch(expr.into()) } - pub fn type_mismatches(&self) -> impl Iterator<Item = (ExprOrPatId, &TypeMismatch)> { - self.type_mismatches - .as_deref() - .into_iter() - .flatten() - .map(|(expr_or_pat, mismatch)| (*expr_or_pat, mismatch)) - } - pub fn expr_type_mismatches(&self) -> impl Iterator<Item = (ExprId, &TypeMismatch)> { - self.type_mismatches.as_deref().into_iter().flatten().filter_map( - |(expr_or_pat, mismatch)| match *expr_or_pat { - ExprOrPatId::ExprId(expr) => Some((expr, mismatch)), - _ => None, - }, - ) + pub fn pat_has_type_mismatch(&self, pat: PatId) -> bool { + self.expr_or_pat_has_type_mismatch(pat.into()) + } + pub fn exprs_have_type_mismatches(&self) -> bool { + self.nodes_with_type_mismatches + .as_ref() + .is_some_and(|it| it.iter().any(|node| node.is_expr())) + } + pub fn has_type_mismatches(&self) -> bool { + self.nodes_with_type_mismatches.is_some() } pub fn placeholder_types<'db>(&self) -> impl Iterator<Item = (TypeRefId, Ty<'db>)> { self.type_of_type_placeholder.iter().map(|(&type_ref, ty)| (type_ref, ty.as_ref())) @@ -1408,7 +1406,6 @@ impl<'body, 'db> InferenceContext<'body, 'db> { type_of_type_placeholder, type_of_opaque, skipped_ref_pats, - type_mismatches, closures_data, has_errors, error_ty: _, @@ -1418,6 +1415,7 @@ impl<'body, 'db> InferenceContext<'body, 'db> { tuple_field_access_types, coercion_casts: _, diagnostics: result_diagnostics, + nodes_with_type_mismatches, } = &mut result; let mut resolver = WriteBackCtxt::new(table, diagnostics, vars_emitted_type_must_be_known_for); @@ -1441,12 +1439,9 @@ impl<'body, 'db> InferenceContext<'body, 'db> { type_of_type_placeholder.shrink_to_fit(); type_of_opaque.shrink_to_fit(); - if let Some(type_mismatches) = type_mismatches { + if let Some(nodes_with_type_mismatches) = nodes_with_type_mismatches { *has_errors = true; - for mismatch in type_mismatches.values_mut() { - resolver.resolve_type_mismatch(mismatch); - } - type_mismatches.shrink_to_fit(); + nodes_with_type_mismatches.shrink_to_fit(); } for (_, subst) in method_resolutions.values_mut() { resolver.resolve_completely(subst); @@ -1938,6 +1933,21 @@ impl<'body, 'db> InferenceContext<'body, 'db> { if result.is_ty_var() { self.type_must_be_known_at_this_point(node, ty) } else { result } } + pub(crate) fn emit_type_mismatch( + &mut self, + node: ExprOrPatId, + expected: Ty<'db>, + found: Ty<'db>, + ) { + if self.result.nodes_with_type_mismatches.get_or_insert_default().insert(node) { + self.diagnostics.push(InferenceDiagnostic::TypeMismatch { + node, + expected: expected.store(), + found: found.store(), + }); + } + } + fn demand_eqtype( &mut self, id: ExprOrPatId, @@ -1950,10 +1960,7 @@ impl<'body, 'db> InferenceContext<'body, 'db> { .eq(expected, actual) .map(|infer_ok| self.table.register_infer_ok(infer_ok)); if result.is_err() { - self.result - .type_mismatches - .get_or_insert_default() - .insert(id, TypeMismatch { expected: expected.store(), actual: actual.store() }); + self.emit_type_mismatch(id, expected, actual); } result.map_err(drop) } @@ -1983,10 +1990,7 @@ impl<'body, 'db> InferenceContext<'body, 'db> { .sup(expected, actual) .map(|infer_ok| self.table.register_infer_ok(infer_ok)); if result.is_err() { - self.result - .type_mismatches - .get_or_insert_default() - .insert(id, TypeMismatch { expected: expected.store(), actual: actual.store() }); + self.emit_type_mismatch(id, expected, actual); } result.map_err(drop) } diff --git a/crates/hir-ty/src/infer/coerce.rs b/crates/hir-ty/src/infer/coerce.rs index 15265b9104..e5767c2d46 100644 --- a/crates/hir-ty/src/infer/coerce.rs +++ b/crates/hir-ty/src/infer/coerce.rs @@ -55,9 +55,7 @@ use crate::{ Adjust, Adjustment, AutoBorrow, ParamEnvAndCrate, PointerCast, Span, TargetFeatures, autoderef::Autoderef, db::{HirDatabase, InternedClosure, InternedClosureId}, - infer::{ - AllowTwoPhase, AutoBorrowMutability, InferenceContext, TypeMismatch, expr::ExprIsRead, - }, + infer::{AllowTwoPhase, AutoBorrowMutability, InferenceContext, expr::ExprIsRead}, next_solver::{ Binder, BoundConst, BoundRegion, BoundRegionKind, BoundTy, BoundTyKind, CallableIdWrapper, Canonical, CoercePredicate, Const, ConstKind, DbInterner, ErrorGuaranteed, GenericArgs, @@ -1393,14 +1391,11 @@ impl<'db, 'exprs> CoerceMany<'db, 'exprs> { self.final_ty = Some(icx.types.types.error); - icx.result.type_mismatches.get_or_insert_default().insert( - expression.into(), - if label_expression_as_expected { - TypeMismatch { expected: found.store(), actual: expected.store() } - } else { - TypeMismatch { expected: expected.store(), actual: found.store() } - }, - ); + if label_expression_as_expected { + icx.emit_type_mismatch(expression.into(), found, expected); + } else { + icx.emit_type_mismatch(expression.into(), expected, found); + } } } diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index a03e891114..680800244b 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -42,7 +42,7 @@ use crate::{ }; use super::{ - BreakableContext, Diverges, Expectation, InferenceContext, InferenceDiagnostic, TypeMismatch, + BreakableContext, Diverges, Expectation, InferenceContext, InferenceDiagnostic, cast::CastCheck, find_breakable, }; @@ -117,10 +117,7 @@ impl<'db> InferenceContext<'_, 'db> { match self.coerce(expr, ty, target, AllowTwoPhase::No, is_read) { Ok(res) => res, Err(_) => { - self.result.type_mismatches.get_or_insert_default().insert( - expr.into(), - TypeMismatch { expected: target.store(), actual: ty.store() }, - ); + self.emit_type_mismatch(expr.into(), target, ty); target } } @@ -1592,13 +1589,7 @@ impl<'db> InferenceContext<'_, 'db> { ) .is_err() { - this.result.type_mismatches.get_or_insert_default().insert( - expr.into(), - TypeMismatch { - expected: t.store(), - actual: this.types.types.unit.store(), - }, - ); + this.emit_type_mismatch(expr.into(), t, this.types.types.unit); } t } else { @@ -2161,10 +2152,7 @@ impl<'db> InferenceContext<'_, 'db> { && args_count_matches { // Don't report type mismatches if there is a mismatch in args count. - self.result.type_mismatches.get_or_insert_default().insert( - (*arg).into(), - TypeMismatch { expected: expected.store(), actual: found.store() }, - ); + self.emit_type_mismatch((*arg).into(), expected, found); } } } diff --git a/crates/hir-ty/src/infer/pat.rs b/crates/hir-ty/src/infer/pat.rs index 97165b9f07..39fb0b8ab8 100644 --- a/crates/hir-ty/src/infer/pat.rs +++ b/crates/hir-ty/src/infer/pat.rs @@ -29,7 +29,7 @@ use crate::{ BindingMode, InferenceDiagnostic, Span, infer::{ AllowTwoPhase, ByRef, Expectation, InferenceContext, PatAdjust, PatAdjustment, - TypeMismatch, expr::ExprIsRead, + expr::ExprIsRead, }, next_solver::{ Const, TraitRef, Ty, TyKind, Tys, @@ -1695,10 +1695,7 @@ https://doc.rust-lang.org/reference/types.html#trait-objects"; match self.coerce(expr, expected, lhs_ty, AllowTwoPhase::No, expr_is_read) { Ok(ty) => ty, Err(_) => { - self.result.type_mismatches.get_or_insert_default().insert( - expr.into(), - TypeMismatch { expected: expected.store(), actual: lhs_ty.store() }, - ); + self.emit_type_mismatch(expr.into(), expected, lhs_ty); // `rhs_ty` is returned so no further type mismatches are // reported because of this mismatch. expected diff --git a/crates/hir-ty/src/infer/unify.rs b/crates/hir-ty/src/infer/unify.rs index c089335708..6a34c5b8e5 100644 --- a/crates/hir-ty/src/infer/unify.rs +++ b/crates/hir-ty/src/infer/unify.rs @@ -11,9 +11,10 @@ use rustc_type_ir::{ solve::Certainty, }; use smallvec::SmallVec; +use thin_vec::ThinVec; use crate::{ - Span, + InferenceDiagnostic, Span, db::HirDatabase, next_solver::{ Canonical, ClauseKind, Const, DbInterner, ErrorGuaranteed, GenericArg, GenericArgs, @@ -29,6 +30,7 @@ use crate::{ inspect::{InspectConfig, InspectGoal, ProofTreeVisitor}, obligation_ctxt::ObligationCtxt, }, + solver_errors::SolverDiagnostic, traits::ParamEnvAndCrate, }; @@ -133,6 +135,7 @@ pub(crate) struct InferenceTable<'db> { pub(crate) infer_ctxt: InferCtxt<'db>, pub(super) fulfillment_cx: FulfillmentCtxt<'db>, pub(super) diverging_type_vars: FxHashSet<Ty<'db>>, + trait_errors: Vec<NextSolverError<'db>>, } impl<'db> InferenceTable<'db> { @@ -153,6 +156,7 @@ impl<'db> InferenceTable<'db> { fulfillment_cx: FulfillmentCtxt::new(&infer_ctxt), infer_ctxt, diverging_type_vars: FxHashSet::default(), + trait_errors: Vec::new(), } } @@ -328,7 +332,10 @@ impl<'db> InferenceTable<'db> { .structurally_normalize_ty(ty, &mut self.fulfillment_cx); match result { Ok(normalized_ty) => normalized_ty, - Err(_errors) => Ty::new_error(self.interner(), ErrorGuaranteed), + Err(errors) => { + self.trait_errors.extend(errors); + Ty::new_error(self.interner(), ErrorGuaranteed) + } } } else { self.resolve_vars_with_obligations(ty) @@ -376,7 +383,8 @@ impl<'db> InferenceTable<'db> { } pub(crate) fn select_obligations_where_possible(&mut self) { - self.fulfillment_cx.try_evaluate_obligations(&self.infer_ctxt); + let errors = self.fulfillment_cx.try_evaluate_obligations(&self.infer_ctxt); + self.trait_errors.extend(errors); } pub(super) fn register_predicate(&mut self, obligation: PredicateObligation<'db>) { @@ -433,6 +441,16 @@ impl<'db> InferenceTable<'db> { // to normalize before inspecting the `TyKind`. self.try_structurally_resolve_type(Span::Dummy, ty) } + + fn emit_trait_errors(&mut self, diagnostics: &mut ThinVec<InferenceDiagnostic>) { + diagnostics.extend(std::mem::take(&mut self.trait_errors).into_iter().filter_map( + |error| { + let error = error.into_fulfillment_error(&self.infer_ctxt); + SolverDiagnostic::from_fulfillment_error(&error) + .map(InferenceDiagnostic::SolverDiagnostic) + }, + )); + } } impl fmt::Debug for InferenceTable<'_> { @@ -455,7 +473,7 @@ pub(super) mod resolve_completely { use crate::{ InferenceDiagnostic, Span, - infer::{TypeMismatch, unify::InferenceTable}, + infer::unify::InferenceTable, next_solver::{ Const, ConstKind, DbInterner, DefaultAny, GenericArg, Goal, Predicate, Region, Term, TermKind, Ty, TyKind, @@ -505,14 +523,6 @@ pub(super) mod resolve_completely { } } - pub(crate) fn resolve_type_mismatch(&mut self, value_ref: &mut TypeMismatch) { - // Ignore diagnostics from type mismatches, which are diagnostics themselves. - // FIXME: We should make type mismatches just regular diagnostics. - let prev_diagnostics_len = self.diagnostics.len(); - self.resolve_completely(value_ref); - self.diagnostics.truncate(prev_diagnostics_len); - } - pub(crate) fn resolve_completely<T>(&mut self, value_ref: &mut T) where T: TypeFoldable<DbInterner<'db>>, @@ -538,6 +548,8 @@ pub(super) mod resolve_completely { pub(crate) fn resolve_diagnostics(mut self) -> (ThinVec<InferenceDiagnostic>, bool) { let has_errors = self.has_errors; + self.table.emit_trait_errors(&mut self.diagnostics); + // Ignore diagnostics made from resolving diagnostics. let mut diagnostics = std::mem::take(&mut self.diagnostics); diagnostics.retain_mut(|diagnostic| { @@ -706,8 +718,8 @@ pub(super) mod resolve_completely { self.nested_goals.extend(goals); value } - Err(_errors) => { - // FIXME: Report the error. + Err(errors) => { + self.ctx.table.trait_errors.extend(errors); value } } diff --git a/crates/hir-ty/src/lib.rs b/crates/hir-ty/src/lib.rs index c6075b03da..5cb2a3d804 100644 --- a/crates/hir-ty/src/lib.rs +++ b/crates/hir-ty/src/lib.rs @@ -50,6 +50,7 @@ pub mod layout; pub mod method_resolution; pub mod mir; pub mod primitive; +pub mod solver_errors; pub mod traits; pub mod upvars; diff --git a/crates/hir-ty/src/method_resolution/confirm.rs b/crates/hir-ty/src/method_resolution/confirm.rs index 6601ff7a01..821d737cf9 100644 --- a/crates/hir-ty/src/method_resolution/confirm.rs +++ b/crates/hir-ty/src/method_resolution/confirm.rs @@ -18,7 +18,7 @@ use crate::{ Adjust, Adjustment, AutoBorrow, IncorrectGenericsLenKind, InferenceDiagnostic, LifetimeElisionKind, PointerCast, Span, db::HirDatabase, - infer::{AllowTwoPhase, AutoBorrowMutability, InferenceContext, TypeMismatch}, + infer::{AllowTwoPhase, AutoBorrowMutability, InferenceContext}, lower::{ GenericPredicates, path::{GenericArgsLowerer, TypeLikeConst, substs_from_args_and_bindings}, @@ -492,10 +492,7 @@ impl<'a, 'b, 'db> ConfirmContext<'a, 'b, 'db> { } Err(_) => { if self.ctx.features.arbitrary_self_types { - self.ctx.result.type_mismatches.get_or_insert_default().insert( - self.call_expr.into(), - TypeMismatch { expected: method_self_ty.store(), actual: self_ty.store() }, - ); + self.ctx.emit_type_mismatch(self.call_expr.into(), method_self_ty, self_ty); } } } diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs index b2a7eaa674..8f8f557716 100644 --- a/crates/hir-ty/src/mir/lower.rs +++ b/crates/hir-ty/src/mir/lower.rs @@ -33,7 +33,7 @@ use crate::{ display::{DisplayTarget, HirDisplay, hir_display_with_store}, generics::generics, infer::{ - CaptureSourceStack, CapturedPlace, TypeMismatch, UpvarCapture, + CaptureSourceStack, CapturedPlace, UpvarCapture, cast::CastTy, closure::analysis::expr_use_visitor::{ Place as HirPlace, PlaceBase as HirPlaceBase, ProjectionKind as HirProjectionKind, @@ -110,7 +110,6 @@ pub enum MirLowerError { UnresolvedField, UnsizedTemporary(StoredTy), MissingFunctionDefinition(DefWithBodyId, ExprId), - TypeMismatch(TypeMismatch), HasErrors, /// This should never happen. Type mismatch should catch everything. TypeError(&'static str), @@ -197,12 +196,6 @@ impl MirLowerError { )?; } MirLowerError::HasErrors => writeln!(f, "Type inference result contains errors")?, - MirLowerError::TypeMismatch(e) => writeln!( - f, - "Type mismatch: Expected {}, found {}", - e.expected.as_ref().display(db, display_target), - e.actual.as_ref().display(db, display_target), - )?, MirLowerError::GenericArgNotProvided(id, subst) => { let param_name = match *id { GenericParamId::TypeParamId(id) => { @@ -2402,7 +2395,7 @@ pub fn lower_to_mir_with_store<'db>( self_param: Option<(BindingId, Ty<'db>)>, is_root: bool, ) -> Result<'db, MirBody> { - if infer.type_mismatches().next().is_some() || infer.is_erroneous() { + if infer.has_type_mismatches() || infer.is_erroneous() { return Err(MirLowerError::HasErrors); } let mut ctx = MirLowerCtx::new(db, owner, store, infer); diff --git a/crates/hir-ty/src/next_solver/binder.rs b/crates/hir-ty/src/next_solver/binder.rs index 3645f8096c..84cfbf2767 100644 --- a/crates/hir-ty/src/next_solver/binder.rs +++ b/crates/hir-ty/src/next_solver/binder.rs @@ -1,8 +1,11 @@ +use hir_def::TraitId; +use macros::{TypeFoldable, TypeVisitable}; + use crate::{ FnAbi, next_solver::{ - Binder, Clauses, EarlyBinder, FnSig, PolyFnSig, StoredBoundVarKinds, StoredClauses, - StoredTy, StoredTys, Ty, abi::Safety, + Binder, Clauses, DbInterner, EarlyBinder, FnSig, PolyFnSig, StoredBoundVarKinds, + StoredClauses, StoredGenericArgs, StoredTy, StoredTys, TraitRef, Ty, abi::Safety, }, }; @@ -81,3 +84,22 @@ impl StoredPolyFnSig { ) } } + +#[derive(Debug, Clone, PartialEq, Eq, Hash, TypeVisitable, TypeFoldable)] +pub struct StoredTraitRef { + #[type_visitable(ignore)] + def_id: TraitId, + args: StoredGenericArgs, +} + +impl StoredTraitRef { + #[inline] + pub fn new(trait_ref: TraitRef<'_>) -> Self { + Self { def_id: trait_ref.def_id.0, args: trait_ref.args.store() } + } + + #[inline] + pub fn get<'db>(&'db self, interner: DbInterner<'db>) -> TraitRef<'db> { + TraitRef::new_from_args(interner, self.def_id.into(), self.args.as_ref()) + } +} diff --git a/crates/hir-ty/src/next_solver/consts.rs b/crates/hir-ty/src/next_solver/consts.rs index fa90e3d8a0..2df9a5259d 100644 --- a/crates/hir-ty/src/next_solver/consts.rs +++ b/crates/hir-ty/src/next_solver/consts.rs @@ -11,13 +11,14 @@ use rustc_ast_ir::visit::VisitorResult; use rustc_type_ir::{ BoundVar, BoundVarIndexKind, ConstVid, DebruijnIndex, FlagComputation, Flags, GenericTypeVisitable, InferConst, TypeFoldable, TypeSuperFoldable, TypeSuperVisitable, - TypeVisitable, WithCachedTypeInfo, inherent::IntoKind, relate::Relate, + TypeVisitable, TypeVisitableExt, WithCachedTypeInfo, inherent::IntoKind, relate::Relate, }; use crate::{ ParamEnvAndCrate, next_solver::{ - AllocationData, impl_foldable_for_interned_slice, impl_stored_interned, interned_slice, + AllocationData, ClauseKind, ParamEnv, impl_foldable_for_interned_slice, + impl_stored_interned, interned_slice, }, }; @@ -146,6 +147,40 @@ impl std::fmt::Debug for ParamConst { } } +impl ParamConst { + pub fn find_const_ty_from_env<'db>(self, env: ParamEnv<'db>) -> Ty<'db> { + let mut candidates = env.clauses.iter().filter_map(|clause| { + // `ConstArgHasType` are never desugared to be higher ranked. + match clause.kind().skip_binder() { + ClauseKind::ConstArgHasType(param_ct, ty) => { + assert!(!(param_ct, ty).has_escaping_bound_vars()); + + match param_ct.kind() { + ConstKind::Param(param_ct) if param_ct.index == self.index => Some(ty), + _ => None, + } + } + _ => None, + } + }); + + // N.B. it may be tempting to fix ICEs by making this function return + // `Option<Ty<'db>>` instead of `Ty<'db>`; however, this is generally + // considered to be a bandaid solution, since it hides more important + // underlying issues with how we construct generics and predicates of + // items. It's advised to fix the underlying issue rather than trying + // to modify this function. + let ty = candidates.next().unwrap_or_else(|| { + panic!("cannot find `{self:?}` in param-env: {env:#?}"); + }); + assert!( + candidates.next().is_none(), + "did not expect duplicate `ConstParamHasTy` for `{self:?}` in param-env: {env:#?}" + ); + ty + } +} + #[derive( Copy, Clone, Debug, Hash, PartialEq, Eq, TypeVisitable, TypeFoldable, GenericTypeVisitable, )] diff --git a/crates/hir-ty/src/next_solver/fulfill.rs b/crates/hir-ty/src/next_solver/fulfill.rs index ba9cd39d44..1fb4cb2b43 100644 --- a/crates/hir-ty/src/next_solver/fulfill.rs +++ b/crates/hir-ty/src/next_solver/fulfill.rs @@ -330,7 +330,7 @@ impl<'db> TypeVisitor<DbInterner<'db>> for StalledOnCoroutines<'_, 'db> { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum NextSolverError<'db> { TrueError(PredicateObligation<'db>), Ambiguity(PredicateObligation<'db>), diff --git a/crates/hir-ty/src/next_solver/infer/errors.rs b/crates/hir-ty/src/next_solver/infer/errors.rs new file mode 100644 index 0000000000..d10f70274c --- /dev/null +++ b/crates/hir-ty/src/next_solver/infer/errors.rs @@ -0,0 +1,704 @@ +use std::{fmt, ops::ControlFlow}; + +use hir_def::{GeneralConstId, attrs::AttrFlags}; +use rustc_next_trait_solver::solve::{GoalEvaluation, SolverDelegateEvalExt}; +use rustc_type_ir::{ + AliasRelationDirection, AliasTermKind, PredicatePolarity, + error::ExpectedFound, + inherent::{IntoKind as _, Ty as _}, + solve::{CandidateSource, Certainty, GoalSource, MaybeCause, NoSolution}, +}; +use tracing::{instrument, trace}; + +use crate::{ + Span, + next_solver::{ + AliasTerm, AnyImplId, Binder, ClauseKind, Const, ConstKind, DbInterner, EarlyBinder, + ErrorGuaranteed, HostEffectPredicate, PolyTraitPredicate, PredicateKind, SolverContext, + Term, TraitPredicate, Ty, TyKind, TypeError, + fulfill::NextSolverError, + infer::{ + InferCtxt, + select::SelectionError, + traits::{Obligation, ObligationCause, PredicateObligation, PredicateObligations}, + }, + inspect::{self, ProofTreeVisitor}, + normalize::deeply_normalize_for_diagnostics, + }, +}; + +#[derive(Debug)] +pub struct FulfillmentError<'db> { + pub obligation: PredicateObligation<'db>, + pub code: FulfillmentErrorCode<'db>, + /// Diagnostics only: the 'root' obligation which resulted in + /// the failure to process `obligation`. This is the obligation + /// that was initially passed to `register_predicate_obligation` + pub root_obligation: PredicateObligation<'db>, +} + +impl<'db> FulfillmentError<'db> { + pub fn new( + obligation: PredicateObligation<'db>, + code: FulfillmentErrorCode<'db>, + root_obligation: PredicateObligation<'db>, + ) -> FulfillmentError<'db> { + FulfillmentError { obligation, code, root_obligation } + } + + pub fn is_true_error(&self) -> bool { + match self.code { + FulfillmentErrorCode::Select(_) + | FulfillmentErrorCode::Project(_) + | FulfillmentErrorCode::Subtype(_, _) + | FulfillmentErrorCode::ConstEquate(_, _) => true, + FulfillmentErrorCode::Cycle(_) | FulfillmentErrorCode::Ambiguity { overflow: _ } => { + false + } + } + } +} + +#[derive(Clone)] +pub enum FulfillmentErrorCode<'db> { + /// Inherently impossible to fulfill; this trait is implemented if and only + /// if it is already implemented. + Cycle(PredicateObligations<'db>), + Select(SelectionError<'db>), + Project(MismatchedProjectionTypes<'db>), + Subtype(ExpectedFound<Ty<'db>>, TypeError<'db>), // always comes from a SubtypePredicate + ConstEquate(ExpectedFound<Const<'db>>, TypeError<'db>), + Ambiguity { + /// Overflow is only `Some(suggest_recursion_limit)` when using the next generation + /// trait solver `-Znext-solver`. With the old solver overflow is eagerly handled by + /// emitting a fatal error instead. + overflow: Option<bool>, + }, +} + +impl<'db> fmt::Debug for FulfillmentErrorCode<'db> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + FulfillmentErrorCode::Select(ref e) => write!(f, "{e:?}"), + FulfillmentErrorCode::Project(ref e) => write!(f, "{e:?}"), + FulfillmentErrorCode::Subtype(ref a, ref b) => { + write!(f, "CodeSubtypeError({a:?}, {b:?})") + } + FulfillmentErrorCode::ConstEquate(ref a, ref b) => { + write!(f, "CodeConstEquateError({a:?}, {b:?})") + } + FulfillmentErrorCode::Ambiguity { overflow: None } => write!(f, "Ambiguity"), + FulfillmentErrorCode::Ambiguity { overflow: Some(suggest_increasing_limit) } => { + write!(f, "Overflow({suggest_increasing_limit})") + } + FulfillmentErrorCode::Cycle(ref cycle) => write!(f, "Cycle({cycle:?})"), + } + } +} + +#[derive(Clone)] +pub struct MismatchedProjectionTypes<'db> { + pub err: TypeError<'db>, +} + +impl<'db> fmt::Debug for MismatchedProjectionTypes<'db> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "MismatchedProjectionTypes({:?})", self.err) + } +} + +impl<'db> NextSolverError<'db> { + pub fn into_fulfillment_error(self, infcx: &InferCtxt<'db>) -> FulfillmentError<'db> { + match self { + NextSolverError::TrueError(obligation) => { + fulfillment_error_for_no_solution(infcx, obligation) + } + NextSolverError::Ambiguity(obligation) => { + fulfillment_error_for_stalled(infcx, obligation) + } + NextSolverError::Overflow(obligation) => { + fulfillment_error_for_overflow(infcx, obligation) + } + } + } +} + +fn fulfillment_error_for_no_solution<'db>( + infcx: &InferCtxt<'db>, + root_obligation: PredicateObligation<'db>, +) -> FulfillmentError<'db> { + let interner = infcx.interner; + let db = interner.db; + let obligation = find_best_leaf_obligation(infcx, &root_obligation, false); + + let code = match obligation.predicate.kind().skip_binder() { + PredicateKind::Clause(ClauseKind::Projection(_)) => { + FulfillmentErrorCode::Project( + // FIXME: This could be a `Sorts` if the term is a type + MismatchedProjectionTypes { err: TypeError::Mismatch }, + ) + } + PredicateKind::Clause(ClauseKind::ConstArgHasType(ct, expected_ty)) => { + let ct_ty = match ct.kind() { + ConstKind::Unevaluated(uv) => { + let ct_ty = match uv.def.0 { + GeneralConstId::ConstId(konst) => db.value_ty(konst.into()).unwrap(), + GeneralConstId::StaticId(statik) => db.value_ty(statik.into()).unwrap(), + // FIXME: Return the type of the const here. + GeneralConstId::AnonConstId(_) => { + EarlyBinder::bind(Ty::new_error(interner, ErrorGuaranteed)) + } + }; + ct_ty.instantiate(interner, uv.args) + } + ConstKind::Param(param_ct) => param_ct.find_const_ty_from_env(obligation.param_env), + ConstKind::Value(cv) => cv.ty, + kind => panic!( + "ConstArgHasWrongType failed but we don't know how to compute type for {kind:?}" + ), + }; + FulfillmentErrorCode::Select(SelectionError::ConstArgHasWrongType { + ct, + ct_ty, + expected_ty, + }) + } + PredicateKind::NormalizesTo(..) => { + FulfillmentErrorCode::Project(MismatchedProjectionTypes { err: TypeError::Mismatch }) + } + PredicateKind::AliasRelate(_, _, _) => { + FulfillmentErrorCode::Project(MismatchedProjectionTypes { err: TypeError::Mismatch }) + } + PredicateKind::Subtype(pred) => { + let (a, b) = infcx.enter_forall_and_leak_universe( + obligation.predicate.kind().rebind((pred.a, pred.b)), + ); + let expected_found = ExpectedFound::new(a, b); + FulfillmentErrorCode::Subtype(expected_found, TypeError::Sorts(expected_found)) + } + PredicateKind::Coerce(pred) => { + let (a, b) = infcx.enter_forall_and_leak_universe( + obligation.predicate.kind().rebind((pred.a, pred.b)), + ); + let expected_found = ExpectedFound::new(b, a); + FulfillmentErrorCode::Subtype(expected_found, TypeError::Sorts(expected_found)) + } + PredicateKind::Clause(_) | PredicateKind::DynCompatible(_) | PredicateKind::Ambiguous => { + FulfillmentErrorCode::Select(SelectionError::Unimplemented) + } + PredicateKind::ConstEquate(..) => { + panic!("unexpected goal: {obligation:?}") + } + }; + + FulfillmentError { obligation, code, root_obligation } +} + +fn fulfillment_error_for_stalled<'db>( + infcx: &InferCtxt<'db>, + root_obligation: PredicateObligation<'db>, +) -> FulfillmentError<'db> { + let (code, refine_obligation) = infcx.probe(|_| { + match <&SolverContext<'db>>::from(infcx).evaluate_root_goal( + root_obligation.as_goal(), + root_obligation.cause.span(), + None, + ) { + Ok(GoalEvaluation { + certainty: Certainty::Maybe { cause: MaybeCause::Ambiguity, .. }, + .. + }) => (FulfillmentErrorCode::Ambiguity { overflow: None }, true), + Ok(GoalEvaluation { + certainty: + Certainty::Maybe { + cause: + MaybeCause::Overflow { suggest_increasing_limit, keep_constraints: _ }, + .. + }, + .. + }) => ( + FulfillmentErrorCode::Ambiguity { overflow: Some(suggest_increasing_limit) }, + // Don't look into overflows because we treat overflows weirdly anyways. + // We discard the inference constraints from overflowing goals, so + // recomputing the goal again during `find_best_leaf_obligation` may apply + // inference guidance that makes other goals go from ambig -> pass, for example. + // + // FIXME: We should probably just look into overflows here. + false, + ), + Ok(GoalEvaluation { certainty: Certainty::Yes, .. }) => { + panic!( + "did not expect successful goal when collecting ambiguity errors for `{:?}`", + infcx.resolve_vars_if_possible(root_obligation.predicate), + ) + } + Err(_) => { + panic!( + "did not expect selection error when collecting ambiguity errors for `{:?}`", + infcx.resolve_vars_if_possible(root_obligation.predicate), + ) + } + } + }); + + FulfillmentError { + obligation: if refine_obligation { + find_best_leaf_obligation(infcx, &root_obligation, true) + } else { + root_obligation.clone() + }, + code, + root_obligation, + } +} + +fn fulfillment_error_for_overflow<'db>( + infcx: &InferCtxt<'db>, + root_obligation: PredicateObligation<'db>, +) -> FulfillmentError<'db> { + FulfillmentError { + obligation: find_best_leaf_obligation(infcx, &root_obligation, true), + code: FulfillmentErrorCode::Ambiguity { overflow: Some(true) }, + root_obligation, + } +} + +#[instrument(level = "debug", skip(infcx), ret)] +fn find_best_leaf_obligation<'db>( + infcx: &InferCtxt<'db>, + obligation: &PredicateObligation<'db>, + consider_ambiguities: bool, +) -> PredicateObligation<'db> { + let obligation = infcx.resolve_vars_if_possible(obligation.clone()); + // FIXME: we use a probe here as the `BestObligation` visitor does not + // check whether it uses candidates which get shadowed by where-bounds. + // + // We should probably fix the visitor to not do so instead, as this also + // means the leaf obligation may be incorrect. + let obligation = infcx + .fudge_inference_if_ok(|| { + infcx + .visit_proof_tree( + obligation.as_goal(), + &mut BestObligation { obligation: obligation.clone(), consider_ambiguities }, + ) + .break_value() + .ok_or(()) + // walk around the fact that the cause in `Obligation` is ignored by folders so that + // we can properly fudge the infer vars in cause code. + .map(|o| (o.cause, o)) + }) + .map(|(cause, o)| PredicateObligation { cause, ..o }) + .unwrap_or(obligation); + deeply_normalize_for_diagnostics(infcx, obligation.param_env, obligation) +} + +struct BestObligation<'db> { + obligation: PredicateObligation<'db>, + consider_ambiguities: bool, +} + +impl<'db> BestObligation<'db> { + fn with_derived_obligation( + &mut self, + derived_obligation: PredicateObligation<'db>, + and_then: impl FnOnce(&mut Self) -> <Self as ProofTreeVisitor<'db>>::Result, + ) -> <Self as ProofTreeVisitor<'db>>::Result { + let old_obligation = std::mem::replace(&mut self.obligation, derived_obligation); + let res = and_then(self); + self.obligation = old_obligation; + res + } + + /// Filter out the candidates that aren't interesting to visit for the + /// purposes of reporting errors. For ambiguities, we only consider + /// candidates that may hold. For errors, we only consider candidates that + /// *don't* hold and which have impl-where clauses that also don't hold. + fn non_trivial_candidates<'a>( + &self, + goal: &'a inspect::InspectGoal<'a, 'db>, + ) -> Vec<inspect::InspectCandidate<'a, 'db>> { + let mut candidates = goal.candidates(); + match self.consider_ambiguities { + true => { + // If we have an ambiguous obligation, we must consider *all* candidates + // that hold, or else we may guide inference causing other goals to go + // from ambig -> pass/fail. + candidates.retain(|candidate| candidate.result().is_ok()); + } + false => { + // We always handle rigid alias candidates separately as we may not add them for + // aliases whose trait bound doesn't hold. + candidates.retain(|c| !matches!(c.kind(), inspect::ProbeKind::RigidAlias { .. })); + // If we have >1 candidate, one may still be due to "boring" reasons, like + // an alias-relate that failed to hold when deeply evaluated. We really + // don't care about reasons like this. + if candidates.len() > 1 { + candidates.retain(|candidate| { + goal.infcx().probe(|_| { + candidate.instantiate_nested_goals(self.span()).iter().any( + |nested_goal| { + matches!( + nested_goal.source(), + GoalSource::ImplWhereBound + | GoalSource::AliasBoundConstCondition + | GoalSource::AliasWellFormed + ) && nested_goal.result().is_err() + }, + ) + }) + }); + } + } + } + + candidates + } + + /// HACK: We walk the nested obligations for a well-formed arg manually, + /// since there's nontrivial logic in `wf.rs` to set up an obligation cause. + /// Ideally we'd be able to track this better. + fn visit_well_formed_goal( + &mut self, + candidate: &inspect::InspectCandidate<'_, 'db>, + term: Term<'db>, + ) -> ControlFlow<PredicateObligation<'db>> { + let _ = (candidate, term); + // FIXME: rustc does this, but we don't process WF obligations yet: + // let infcx = candidate.goal().infcx(); + // let param_env = candidate.goal().goal().param_env; + // let body_id = self.obligation.cause.body_id; + + // for obligation in wf::unnormalized_obligations(infcx, param_env, term, self.span(), body_id) + // .into_iter() + // .flatten() + // { + // let nested_goal = candidate.instantiate_proof_tree_for_nested_goal( + // GoalSource::Misc, + // obligation.as_goal(), + // self.span(), + // ); + // // Skip nested goals that aren't the *reason* for our goal's failure. + // match (self.consider_ambiguities, nested_goal.result()) { + // (true, Ok(Certainty::Maybe { cause: MaybeCause::Ambiguity, .. })) + // | (false, Err(_)) => {} + // _ => continue, + // } + + // self.with_derived_obligation(obligation, |this| nested_goal.visit_with(this))?; + // } + + ControlFlow::Break(self.obligation.clone()) + } + + /// If a normalization of an associated item or a trait goal fails without trying any + /// candidates it's likely that normalizing its self type failed. We manually detect + /// such cases here. + fn detect_error_in_self_ty_normalization( + &mut self, + goal: &inspect::InspectGoal<'_, 'db>, + self_ty: Ty<'db>, + ) -> ControlFlow<PredicateObligation<'db>> { + assert!(!self.consider_ambiguities); + let interner = goal.infcx().interner; + if let TyKind::Alias(..) = self_ty.kind() { + let infer_term = goal.infcx().next_ty_var(self.obligation.cause.span()); + let pred = PredicateKind::AliasRelate( + self_ty.into(), + infer_term.into(), + AliasRelationDirection::Equate, + ); + let obligation = + Obligation::new(interner, self.obligation.cause, goal.goal().param_env, pred); + self.with_derived_obligation(obligation, |this| { + goal.infcx().visit_proof_tree_at_depth( + goal.goal().with(interner, pred), + goal.depth() + 1, + this, + ) + }) + } else { + ControlFlow::Continue(()) + } + } + + /// When a higher-ranked projection goal fails, check that the corresponding + /// higher-ranked trait goal holds or not. This is because the process of + /// instantiating and then re-canonicalizing the binder of the projection goal + /// forces us to be unable to see that the leak check failed in the nested + /// `NormalizesTo` goal, so we don't fall back to the rigid projection check + /// that should catch when a projection goal fails due to an unsatisfied trait + /// goal. + fn detect_trait_error_in_higher_ranked_projection( + &mut self, + goal: &inspect::InspectGoal<'_, 'db>, + ) -> ControlFlow<PredicateObligation<'db>> { + let interner = goal.infcx().interner; + if let Some(projection_clause) = goal.goal().predicate.as_projection_clause() + && !projection_clause.bound_vars().is_empty() + { + let pred = projection_clause.map_bound(|proj| proj.projection_term.trait_ref(interner)); + let obligation = Obligation::new( + interner, + self.obligation.cause, + goal.goal().param_env, + deeply_normalize_for_diagnostics(goal.infcx(), goal.goal().param_env, pred), + ); + self.with_derived_obligation(obligation, |this| { + goal.infcx().visit_proof_tree_at_depth( + goal.goal().with(interner, pred), + goal.depth() + 1, + this, + ) + }) + } else { + ControlFlow::Continue(()) + } + } + + /// It is likely that `NormalizesTo` failed without any applicable candidates + /// because the alias is not well-formed. + /// + /// As we only enter `RigidAlias` candidates if the trait bound of the associated type + /// holds, we discard these candidates in `non_trivial_candidates` and always manually + /// check this here. + fn detect_non_well_formed_assoc_item( + &mut self, + goal: &inspect::InspectGoal<'_, 'db>, + alias: AliasTerm<'db>, + ) -> ControlFlow<PredicateObligation<'db>> { + let interner = goal.infcx().interner; + let obligation = Obligation::new( + interner, + self.obligation.cause, + goal.goal().param_env, + alias.trait_ref(interner), + ); + self.with_derived_obligation(obligation, |this| { + goal.infcx().visit_proof_tree_at_depth( + goal.goal().with(interner, alias.trait_ref(interner)), + goal.depth() + 1, + this, + ) + }) + } + + /// If we have no candidates, then it's likely that there is a + /// non-well-formed alias in the goal. + fn detect_error_from_empty_candidates( + &mut self, + goal: &inspect::InspectGoal<'_, 'db>, + ) -> ControlFlow<PredicateObligation<'db>> { + let interner = goal.infcx().interner; + let pred_kind = goal.goal().predicate.kind(); + + match pred_kind.no_bound_vars() { + Some(PredicateKind::Clause(ClauseKind::Trait(pred))) => { + self.detect_error_in_self_ty_normalization(goal, pred.self_ty())?; + } + Some(PredicateKind::NormalizesTo(pred)) + if let AliasTermKind::ProjectionTy | AliasTermKind::ProjectionConst = + pred.alias.kind(interner) => + { + self.detect_error_in_self_ty_normalization(goal, pred.alias.self_ty())?; + self.detect_non_well_formed_assoc_item(goal, pred.alias)?; + } + Some(_) | None => {} + } + + ControlFlow::Break(self.obligation.clone()) + } +} + +impl<'db> ProofTreeVisitor<'db> for BestObligation<'db> { + type Result = ControlFlow<PredicateObligation<'db>>; + + fn span(&self) -> Span { + self.obligation.cause.span() + } + + #[instrument(level = "trace", skip(self, goal), fields(goal = ?goal.goal()))] + fn visit_goal(&mut self, goal: &inspect::InspectGoal<'_, 'db>) -> Self::Result { + let interner = goal.infcx().interner; + // Skip goals that aren't the *reason* for our goal's failure. + match (self.consider_ambiguities, goal.result()) { + (true, Ok(Certainty::Maybe { cause: MaybeCause::Ambiguity, .. })) | (false, Err(_)) => { + } + _ => return ControlFlow::Continue(()), + } + + let pred = goal.goal().predicate; + + let candidates = self.non_trivial_candidates(goal); + let candidate = match candidates.as_slice() { + [candidate] => candidate, + [] => return self.detect_error_from_empty_candidates(goal), + _ => return ControlFlow::Break(self.obligation.clone()), + }; + + // Don't walk into impls that have `do_not_recommend`. + if let inspect::ProbeKind::TraitCandidate { + source: CandidateSource::Impl(impl_def_id), + result: _, + } = candidate.kind() + && let AnyImplId::ImplId(impl_def_id) = impl_def_id + && AttrFlags::query(interner.db, impl_def_id.into()) + .contains(AttrFlags::DIAGNOSTIC_DO_NOT_RECOMMEND) + { + trace!("#[diagnostic::do_not_recommend] -> exit"); + return ControlFlow::Break(self.obligation.clone()); + } + + // FIXME: Also, what about considering >1 layer up the stack? May be necessary + // for normalizes-to. + let child_mode = match pred.kind().skip_binder() { + PredicateKind::Clause(ClauseKind::Trait(trait_pred)) => { + ChildMode::Trait(pred.kind().rebind(trait_pred)) + } + PredicateKind::Clause(ClauseKind::HostEffect(host_pred)) => { + ChildMode::Host(pred.kind().rebind(host_pred)) + } + PredicateKind::NormalizesTo(normalizes_to) + if matches!( + normalizes_to.alias.kind(interner), + AliasTermKind::ProjectionTy | AliasTermKind::ProjectionConst + ) => + { + ChildMode::Trait(pred.kind().rebind(TraitPredicate { + trait_ref: normalizes_to.alias.trait_ref(interner), + polarity: PredicatePolarity::Positive, + })) + } + PredicateKind::Clause(ClauseKind::WellFormed(term)) => { + return self.visit_well_formed_goal(candidate, term); + } + _ => ChildMode::PassThrough, + }; + + let nested_goals = candidate.instantiate_nested_goals(self.span()); + + // If the candidate requires some `T: FnPtr` bound which does not hold should not be treated as + // an actual candidate, instead we should treat them as if the impl was never considered to + // have potentially applied. As if `impl<A, R> Trait for for<..> fn(..A) -> R` was written + // instead of `impl<T: FnPtr> Trait for T`. + // + // We do this as a separate loop so that we do not choose to tell the user about some nested + // goal before we encounter a `T: FnPtr` nested goal. + for nested_goal in &nested_goals { + if let Some(poly_trait_pred) = nested_goal.goal().predicate.as_trait_clause() + && Some(poly_trait_pred.def_id().0) == interner.lang_items().FnPtrTrait + && let Err(NoSolution) = nested_goal.result() + { + return ControlFlow::Break(self.obligation.clone()); + } + } + + let mut impl_where_bound_count = 0; + for nested_goal in nested_goals { + trace!(nested_goal = ?(nested_goal.goal(), nested_goal.source(), nested_goal.result())); + + let nested_pred = nested_goal.goal().predicate; + + let make_obligation = |cause| Obligation { + cause, + param_env: nested_goal.goal().param_env, + predicate: nested_pred, + recursion_depth: self.obligation.recursion_depth + 1, + }; + + let obligation; + match (child_mode, nested_goal.source()) { + ( + ChildMode::Trait(_) | ChildMode::Host(_), + GoalSource::Misc | GoalSource::TypeRelating | GoalSource::NormalizeGoal(_), + ) => { + continue; + } + (ChildMode::Trait(parent_trait_pred), GoalSource::ImplWhereBound) => { + obligation = make_obligation(derive_cause( + interner, + candidate.kind(), + self.obligation.cause, + impl_where_bound_count, + parent_trait_pred, + )); + impl_where_bound_count += 1; + } + ( + ChildMode::Host(parent_host_pred), + GoalSource::ImplWhereBound | GoalSource::AliasBoundConstCondition, + ) => { + obligation = make_obligation(derive_host_cause( + interner, + candidate.kind(), + self.obligation.cause, + impl_where_bound_count, + parent_host_pred, + )); + impl_where_bound_count += 1; + } + (ChildMode::PassThrough, _) + | (_, GoalSource::AliasWellFormed | GoalSource::AliasBoundConstCondition) => { + obligation = make_obligation(self.obligation.cause); + } + } + + self.with_derived_obligation(obligation, |this| nested_goal.visit_with(this))?; + } + + // alias-relate may fail because the lhs or rhs can't be normalized, + // and therefore is treated as rigid. + if let Some(PredicateKind::AliasRelate(lhs, rhs, _)) = pred.kind().no_bound_vars() { + goal.infcx().visit_proof_tree_at_depth( + goal.goal().with(interner, ClauseKind::WellFormed(lhs)), + goal.depth() + 1, + self, + )?; + goal.infcx().visit_proof_tree_at_depth( + goal.goal().with(interner, ClauseKind::WellFormed(rhs)), + goal.depth() + 1, + self, + )?; + } + + self.detect_trait_error_in_higher_ranked_projection(goal)?; + + ControlFlow::Break(self.obligation.clone()) + } +} + +#[derive(Debug, Copy, Clone)] +enum ChildMode<'db> { + // Try to derive an `ObligationCause::{ImplDerived,BuiltinDerived}`, + // and skip all `GoalSource::Misc`, which represent useless obligations + // such as alias-eq which may not hold. + Trait(PolyTraitPredicate<'db>), + // Try to derive an `ObligationCause::{ImplDerived,BuiltinDerived}`, + // and skip all `GoalSource::Misc`, which represent useless obligations + // such as alias-eq which may not hold. + Host(Binder<'db, HostEffectPredicate<'db>>), + // Skip trying to derive an `ObligationCause` from this obligation, and + // report *all* sub-obligations as if they came directly from the parent + // obligation. + PassThrough, +} + +fn derive_cause<'db>( + _interner: DbInterner<'db>, + _candidate_kind: inspect::ProbeKind<DbInterner<'db>>, + cause: ObligationCause, + _idx: usize, + _parent_trait_pred: PolyTraitPredicate<'db>, +) -> ObligationCause { + cause +} + +fn derive_host_cause<'db>( + _interner: DbInterner<'db>, + _candidate_kind: inspect::ProbeKind<DbInterner<'db>>, + cause: ObligationCause, + _idx: usize, + _parent_host_pred: Binder<'db, HostEffectPredicate<'db>>, +) -> ObligationCause { + cause +} diff --git a/crates/hir-ty/src/next_solver/infer/mod.rs b/crates/hir-ty/src/next_solver/infer/mod.rs index e1568d43bb..0bb980c906 100644 --- a/crates/hir-ty/src/next_solver/infer/mod.rs +++ b/crates/hir-ty/src/next_solver/infer/mod.rs @@ -53,6 +53,7 @@ use super::{ pub mod at; pub mod canonical; mod context; +pub mod errors; pub mod opaque_types; mod outlives; pub mod region_constraints; diff --git a/crates/hir-ty/src/next_solver/infer/traits.rs b/crates/hir-ty/src/next_solver/infer/traits.rs index 12a6652bf7..4584b35796 100644 --- a/crates/hir-ty/src/next_solver/infer/traits.rs +++ b/crates/hir-ty/src/next_solver/infer/traits.rs @@ -27,8 +27,9 @@ use crate::{ use super::InferCtxt; /// The reason why we incurred this obligation; used for error reporting. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, TypeVisitable, TypeFoldable)] pub struct ObligationCause { + #[type_visitable(ignore)] span: Span, } diff --git a/crates/hir-ty/src/next_solver/inspect.rs b/crates/hir-ty/src/next_solver/inspect.rs index 566f72fbd8..fdb1fa3d05 100644 --- a/crates/hir-ty/src/next_solver/inspect.rs +++ b/crates/hir-ty/src/next_solver/inspect.rs @@ -24,6 +24,8 @@ use crate::{ }, }; +pub(crate) use rustc_next_trait_solver::solve::inspect::*; + pub(crate) struct InspectConfig { pub(crate) max_depth: usize, } @@ -319,6 +321,10 @@ impl<'a, 'db> InspectGoal<'a, 'db> { self.result } + pub(crate) fn source(&self) -> GoalSource { + self.source + } + pub(crate) fn depth(&self) -> usize { self.depth } diff --git a/crates/hir-ty/src/next_solver/normalize.rs b/crates/hir-ty/src/next_solver/normalize.rs index c35434ed16..152b58baeb 100644 --- a/crates/hir-ty/src/next_solver/normalize.rs +++ b/crates/hir-ty/src/next_solver/normalize.rs @@ -229,7 +229,6 @@ impl<'db> FallibleTypeFolder<DbInterner<'db>> for NormalizationFolder<'_, 'db> { } // Deeply normalize a value and return it -#[expect(dead_code, reason = "rustc has this")] pub(crate) fn deeply_normalize_for_diagnostics<'db, T: TypeFoldable<DbInterner<'db>>>( infcx: &InferCtxt<'db>, param_env: ParamEnv<'db>, diff --git a/crates/hir-ty/src/next_solver/predicate.rs b/crates/hir-ty/src/next_solver/predicate.rs index 2abd9bdd94..e16428cd2e 100644 --- a/crates/hir-ty/src/next_solver/predicate.rs +++ b/crates/hir-ty/src/next_solver/predicate.rs @@ -31,6 +31,7 @@ pub type ExistentialPredicate<'db> = ty::ExistentialPredicate<DbInterner<'db>>; pub type ExistentialTraitRef<'db> = ty::ExistentialTraitRef<DbInterner<'db>>; pub type ExistentialProjection<'db> = ty::ExistentialProjection<DbInterner<'db>>; pub type TraitPredicate<'db> = ty::TraitPredicate<DbInterner<'db>>; +pub type HostEffectPredicate<'db> = ty::HostEffectPredicate<DbInterner<'db>>; pub type ClauseKind<'db> = ty::ClauseKind<DbInterner<'db>>; pub type PredicateKind<'db> = ty::PredicateKind<DbInterner<'db>>; pub type NormalizesTo<'db> = ty::NormalizesTo<DbInterner<'db>>; diff --git a/crates/hir-ty/src/solver_errors.rs b/crates/hir-ty/src/solver_errors.rs new file mode 100644 index 0000000000..e4e76fa67b --- /dev/null +++ b/crates/hir-ty/src/solver_errors.rs @@ -0,0 +1,90 @@ +//! Handling of trait solver errors and converting them to errors `hir` can pass to `ide-diagnostics`. +//! +//! Note that we also have [`crate::next_solver::infer::errors`], which takes the raw [`NextSolverError`], +//! and converts it into [`FulfillmentError`] that contains more details. +//! +//! [`NextSolverError`]: crate::next_solver::fulfill::NextSolverError + +use macros::{TypeFoldable, TypeVisitable}; +use rustc_type_ir::{PredicatePolarity, inherent::IntoKind}; + +use crate::{ + Span, + next_solver::{ + ClauseKind, DbInterner, PredicateKind, StoredTraitRef, TraitPredicate, + infer::{ + errors::{FulfillmentError, FulfillmentErrorCode}, + select::SelectionError, + }, + }, +}; + +#[derive(Debug, Clone, PartialEq, Eq, TypeVisitable, TypeFoldable)] +pub struct SolverDiagnostic { + pub span: Span, + pub kind: SolverDiagnosticKind, +} + +#[derive(Debug, Clone, PartialEq, Eq, TypeVisitable, TypeFoldable)] +pub enum SolverDiagnosticKind { + TraitUnimplemented { + trait_predicate: StoredTraitPredicate, + root_trait_predicate: Option<StoredTraitPredicate>, + }, +} + +#[derive(Debug, Clone, PartialEq, Eq, TypeVisitable, TypeFoldable)] +pub struct StoredTraitPredicate { + pub trait_ref: StoredTraitRef, + pub polarity: PredicatePolarity, +} + +impl StoredTraitPredicate { + #[inline] + pub fn get<'db>(&'db self, interner: DbInterner<'db>) -> TraitPredicate<'db> { + TraitPredicate { polarity: self.polarity, trait_ref: self.trait_ref.get(interner) } + } +} + +impl SolverDiagnostic { + pub fn from_fulfillment_error(error: &FulfillmentError<'_>) -> Option<Self> { + let span = error.obligation.cause.span(); + if span.is_dummy() { + return None; + } + + // FIXME: Handle more error kinds. + let kind = match &error.code { + FulfillmentErrorCode::Select(SelectionError::Unimplemented) => { + match error.obligation.predicate.kind().skip_binder() { + PredicateKind::Clause(ClauseKind::Trait(trait_pred)) => { + handle_trait_unimplemented(error, trait_pred)? + } + _ => return None, + } + } + _ => return None, + }; + Some(SolverDiagnostic { span, kind }) + } +} + +fn handle_trait_unimplemented<'db>( + error: &FulfillmentError<'db>, + trait_pred: TraitPredicate<'db>, +) -> Option<SolverDiagnosticKind> { + let trait_predicate = StoredTraitPredicate { + trait_ref: StoredTraitRef::new(trait_pred.trait_ref), + polarity: trait_pred.polarity, + }; + + let root_trait_predicate = match error.root_obligation.predicate.kind().skip_binder() { + PredicateKind::Clause(ClauseKind::Trait(trait_pred)) => Some(StoredTraitPredicate { + trait_ref: StoredTraitRef::new(trait_pred.trait_ref), + polarity: trait_pred.polarity, + }), + _ => None, + }; + + Some(SolverDiagnosticKind::TraitUnimplemented { trait_predicate, root_trait_predicate }) +} diff --git a/crates/hir-ty/src/tests.rs b/crates/hir-ty/src/tests.rs index 83767c42ea..2fa70cd3a8 100644 --- a/crates/hir-ty/src/tests.rs +++ b/crates/hir-ty/src/tests.rs @@ -36,9 +36,9 @@ use syntax::{ use test_fixture::WithFixture; use crate::{ - InferenceResult, + InferenceDiagnostic, InferenceResult, display::{DisplayTarget, HirDisplay}, - infer::{Adjustment, TypeMismatch}, + infer::Adjustment, next_solver::Ty, setup_tracing, test_db::TestDB, @@ -195,7 +195,14 @@ fn check_impl( } } - for (expr_or_pat, mismatch) in inference_result.type_mismatches() { + let type_mismatches = + inference_result.diagnostics().iter().filter_map(|diag| match diag { + InferenceDiagnostic::TypeMismatch { node, expected, found } => { + Some((*node, expected.as_ref(), found.as_ref())) + } + _ => None, + }); + for (expr_or_pat, expected, actual) in type_mismatches { let Some(node) = (match expr_or_pat { hir_def::hir::ExprOrPatId::ExprId(expr) => { expr_node(body_source_map, expr, &db) @@ -207,8 +214,8 @@ fn check_impl( let range = node.as_ref().original_file_range_rooted(&db); let actual = format!( "expected {}, got {}", - mismatch.expected.as_ref().display_test(&db, display_target), - mismatch.actual.as_ref().display_test(&db, display_target) + expected.display_test(&db, display_target), + actual.display_test(&db, display_target) ); match mismatches.remove(&range) { Some(annotation) => assert_eq!(actual, annotation), @@ -326,7 +333,17 @@ fn infer_with_mismatches(content: &str, include_mismatches: bool) -> String { krate: Crate| { let display_target = DisplayTarget::from_crate(&db, krate); let mut types: Vec<(InFile<SyntaxNode>, Ty<'_>)> = Vec::new(); - let mut mismatches: Vec<(InFile<SyntaxNode>, &TypeMismatch)> = Vec::new(); + let type_mismatch_for_node = inference_result + .diagnostics() + .iter() + .filter_map(|diag| match diag { + InferenceDiagnostic::TypeMismatch { node, expected, found } => { + Some((*node, (expected.as_ref(), found.as_ref()))) + } + _ => None, + }) + .collect::<FxHashMap<_, _>>(); + let mut mismatches: Vec<(InFile<SyntaxNode>, (Ty<'_>, Ty<'_>))> = Vec::new(); if let Some((binding_id, syntax_ptr)) = self_param { let ty = &inference_result.type_of_binding[binding_id]; @@ -349,8 +366,8 @@ fn infer_with_mismatches(content: &str, include_mismatches: bool) -> String { Err(SyntheticSyntax) => continue, }; types.push((node.clone(), ty.as_ref())); - if let Some(mismatch) = inference_result.type_mismatch_for_pat(pat) { - mismatches.push((node, mismatch)); + if let Some(mismatch) = type_mismatch_for_node.get(&pat.into()) { + mismatches.push((node, *mismatch)); } } @@ -363,8 +380,8 @@ fn infer_with_mismatches(content: &str, include_mismatches: bool) -> String { Err(SyntheticSyntax) => continue, }; types.push((node.clone(), ty.as_ref())); - if let Some(mismatch) = inference_result.type_mismatch_for_expr(expr) { - mismatches.push((node, mismatch)); + if let Some(mismatch) = type_mismatch_for_node.get(&expr.into()) { + mismatches.push((node, *mismatch)); } } @@ -395,7 +412,7 @@ fn infer_with_mismatches(content: &str, include_mismatches: bool) -> String { let range = node.value.text_range(); (range.start(), range.end()) }); - for (src_ptr, mismatch) in &mismatches { + for (src_ptr, (expected, actual)) in &mismatches { let range = src_ptr.value.text_range(); let macro_prefix = if src_ptr.file_id != file_id { "!" } else { "" }; format_to!( @@ -403,8 +420,8 @@ fn infer_with_mismatches(content: &str, include_mismatches: bool) -> String { "{}{:?}: expected {}, got {}\n", macro_prefix, range, - mismatch.expected.as_ref().display_test(&db, display_target), - mismatch.actual.as_ref().display_test(&db, display_target), + expected.display_test(&db, display_target), + actual.display_test(&db, display_target), ); } } diff --git a/crates/hir-ty/src/tests/regression.rs b/crates/hir-ty/src/tests/regression.rs index 1b63a4a2c0..a5f349e593 100644 --- a/crates/hir-ty/src/tests/regression.rs +++ b/crates/hir-ty/src/tests/regression.rs @@ -2363,7 +2363,6 @@ fn test() { } "#, expect![[r#" - 46..49 'Foo': Foo<N> 93..97 'self': Foo<N> 108..125 '{ ... }': usize 118..119 'N': usize diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index 6a0996fae6..0f903045c9 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -15,12 +15,15 @@ use hir_def::{ }; use hir_expand::{HirFileId, InFile, mod_path::ModPath, name::Name}; use hir_ty::{ - CastError, InferenceDiagnostic, InferenceTyDiagnosticSource, PathGenericsSource, - PathLoweringDiagnostic, TyLoweringDiagnostic, TyLoweringDiagnosticKind, + CastError, InferenceDiagnostic, InferenceTyDiagnosticSource, ParamEnvAndCrate, + PathGenericsSource, PathLoweringDiagnostic, TyLoweringDiagnostic, TyLoweringDiagnosticKind, db::HirDatabase, diagnostics::{BodyValidationDiagnostic, UnsafetyReason}, display::{DisplayTarget, HirDisplay}, + next_solver::DbInterner, + solver_errors::SolverDiagnosticKind, }; +use stdx::{impl_from, never}; use syntax::{ AstNode, AstPtr, SyntaxError, SyntaxNodePtr, TextRange, ast::{self, HasGenericArgs}, @@ -36,6 +39,49 @@ pub use hir_ty::{ diagnostics::{CaseType, IncorrectCase}, }; +#[derive(Debug, Clone)] +pub enum SpanAst { + Expr(ast::Expr), + Pat(ast::Pat), + Type(ast::Type), +} +const _: () = { + use syntax::ast::*; + impl_from!(Expr, Pat, Type for SpanAst); +}; + +impl From<Either<ast::Expr, ast::Pat>> for SpanAst { + fn from(value: Either<ast::Expr, ast::Pat>) -> Self { + match value { + Either::Left(it) => it.into(), + Either::Right(it) => it.into(), + } + } +} + +impl ast::AstNode for SpanAst { + fn can_cast(kind: syntax::SyntaxKind) -> bool { + ast::Expr::can_cast(kind) || ast::Pat::can_cast(kind) || ast::Type::can_cast(kind) + } + + fn cast(syntax: syntax::SyntaxNode) -> Option<Self> { + ast::Expr::cast(syntax.clone()) + .map(SpanAst::Expr) + .or_else(|| ast::Pat::cast(syntax.clone()).map(SpanAst::Pat)) + .or_else(|| ast::Type::cast(syntax).map(SpanAst::Type)) + } + + fn syntax(&self) -> &syntax::SyntaxNode { + match self { + SpanAst::Expr(it) => it.syntax(), + SpanAst::Pat(it) => it.syntax(), + SpanAst::Type(it) => it.syntax(), + } + } +} + +pub type SpanSyntax = InFile<AstPtr<SpanAst>>; + macro_rules! diagnostics { ($AnyDiagnostic:ident <$db:lifetime> -> $($diag:ident $(<$lt:lifetime>)?,)*) => { #[derive(Debug)] @@ -114,6 +160,7 @@ diagnostics![AnyDiagnostic<'db> -> ElidedLifetimesInPath, TypeMustBeKnown<'db>, UnionExprMustHaveExactlyOneField, + UnimplementedTrait<'db>, ]; #[derive(Debug)] @@ -462,7 +509,7 @@ pub struct ElidedLifetimesInPath { #[derive(Debug)] pub struct TypeMustBeKnown<'db> { - pub at_point: InFile<AstPtr<Either<ast::Type, Either<ast::Expr, ast::Pat>>>>, + pub at_point: SpanSyntax, pub top_term: Option<Either<Type<'db>, String>>, } @@ -510,6 +557,13 @@ pub struct PatternArgInExternFn { pub node: InFile<AstPtr<ast::Pat>>, } +#[derive(Debug)] +pub struct UnimplementedTrait<'db> { + pub span: SpanSyntax, + pub trait_predicate: crate::TraitPredicate<'db>, + pub root_trait_predicate: Option<crate::TraitPredicate<'db>>, +} + impl<'db> AnyDiagnostic<'db> { pub(crate) fn body_validation_diagnostic( db: &'db dyn HirDatabase, @@ -646,9 +700,10 @@ impl<'db> AnyDiagnostic<'db> { pub(crate) fn inference_diagnostic( db: &'db dyn HirDatabase, def: DefWithBodyId, - d: &InferenceDiagnostic, + d: &'db InferenceDiagnostic, source_map: &hir_def::expr_store::BodySourceMap, sig_map: &hir_def::expr_store::ExpressionStoreSourceMap, + env: ParamEnvAndCrate<'db>, ) -> Option<AnyDiagnostic<'db>> { let expr_syntax = |expr| { source_map @@ -672,6 +727,18 @@ impl<'db> AnyDiagnostic<'db> { ExprOrPatId::ExprId(expr) => expr_syntax(expr), ExprOrPatId::PatId(pat) => pat_syntax(pat), }; + let span_syntax = |span| match span { + hir_ty::Span::ExprId(idx) => expr_syntax(idx).map(|it| it.upcast()), + hir_ty::Span::PatId(idx) => pat_syntax(idx).map(|it| it.upcast()), + hir_ty::Span::TypeRefId(idx) => type_syntax(idx).map(|it| it.upcast()), + hir_ty::Span::BindingId(idx) => { + pat_syntax(source_map.patterns_for_binding(idx)[0]).map(|it| it.upcast()) + } + hir_ty::Span::Dummy => { + never!("should never create a diagnostic for dummy spans"); + None + } + }; Some(match d { &InferenceDiagnostic::NoSuchField { field: expr, private, variant } => { let expr_or_pat = match expr { @@ -847,18 +914,7 @@ impl<'db> AnyDiagnostic<'db> { InvalidLhsOfAssignment { lhs }.into() } &InferenceDiagnostic::TypeMustBeKnown { at_point, ref top_term } => { - let at_point = match at_point { - hir_ty::Span::ExprId(idx) => expr_syntax(idx)?.map(|it| it.wrap_right()), - hir_ty::Span::PatId(idx) => pat_syntax(idx)?.map(|it| it.wrap_right()), - hir_ty::Span::TypeRefId(idx) => type_syntax(idx)?.map(|it| it.wrap_left()), - hir_ty::Span::BindingId(idx) => { - pat_syntax(source_map.patterns_for_binding(idx)[0])? - .map(|it| it.wrap_right()) - } - hir_ty::Span::Dummy => unreachable!( - "should never create TypeMustBeKnown diagnostic for dummy spans" - ), - }; + let at_point = span_syntax(at_point)?; let top_term = top_term.as_ref().map(|top_term| match top_term.as_ref().kind() { rustc_type_ir::GenericArgKind::Type(ty) => Either::Left(Type { ty, @@ -878,6 +934,39 @@ impl<'db> AnyDiagnostic<'db> { let expr = expr_syntax(expr)?; UnionExprMustHaveExactlyOneField { expr }.into() } + InferenceDiagnostic::TypeMismatch { node, expected, found } => { + let expr_or_pat = expr_or_pat_syntax(*node)?; + TypeMismatch { + expr_or_pat, + expected: Type { env, ty: expected.as_ref() }, + actual: Type { env, ty: found.as_ref() }, + } + .into() + } + InferenceDiagnostic::SolverDiagnostic(d) => { + let span = span_syntax(d.span)?; + Self::solver_diagnostic(db, &d.kind, span, env)? + } + }) + } + + fn solver_diagnostic( + db: &'db dyn HirDatabase, + d: &'db SolverDiagnosticKind, + span: SpanSyntax, + env: ParamEnvAndCrate<'db>, + ) -> Option<AnyDiagnostic<'db>> { + let interner = DbInterner::new_no_crate(db); + Some(match d { + SolverDiagnosticKind::TraitUnimplemented { trait_predicate, root_trait_predicate } => { + let trait_predicate = + crate::TraitPredicate { inner: trait_predicate.get(interner), env }; + let root_trait_predicate = + root_trait_predicate.as_ref().map(|root_trait_predicate| { + crate::TraitPredicate { inner: root_trait_predicate.get(interner), env } + }); + UnimplementedTrait { span, trait_predicate, root_trait_predicate }.into() + } }) } diff --git a/crates/hir/src/display.rs b/crates/hir/src/display.rs index 139f078eef..880c9d9ae6 100644 --- a/crates/hir/src/display.rs +++ b/crates/hir/src/display.rs @@ -30,8 +30,8 @@ use rustc_type_ir::inherent::IntoKind; use crate::{ Adt, AnyFunctionId, AsAssocItem, AssocItem, AssocItemContainer, Const, ConstParam, Crate, Enum, EnumVariant, ExternCrateDecl, Field, Function, GenericParam, HasCrate, HasVisibility, Impl, - LifetimeParam, Macro, Module, SelfParam, Static, Struct, StructKind, Trait, TraitRef, - TupleField, Type, TypeAlias, TypeNs, TypeOrConstParam, TypeParam, Union, + LifetimeParam, Macro, Module, SelfParam, Static, Struct, StructKind, Trait, TraitPredicate, + TraitRef, TupleField, Type, TypeAlias, TypeNs, TypeOrConstParam, TypeParam, Union, }; fn write_builtin_derive_impl_method<'db>( @@ -853,6 +853,12 @@ impl<'db> HirDisplay<'db> for TraitRef<'db> { } } +impl<'db> HirDisplay<'db> for TraitPredicate<'db> { + fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result { + self.inner.hir_fmt(f) + } +} + impl<'db> HirDisplay<'db> for Trait { fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result { // FIXME(trait-alias) needs special handling to print the equal sign diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index b74f594ebe..7ab9bca697 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -2121,6 +2121,7 @@ impl DefWithBody { return; }; let krate = self.module(db).id.krate(db); + let env = body_param_env_from_has_crate(db, id); let (body, source_map) = Body::with_source_map(db, id); let sig_source_map = match self { @@ -2144,34 +2145,14 @@ impl DefWithBody { let infer = InferenceResult::of(db, id); for d in infer.diagnostics() { - acc.extend(AnyDiagnostic::inference_diagnostic(db, id, d, source_map, sig_source_map)); - } - - for (pat_or_expr, mismatch) in infer.type_mismatches() { - let expr_or_pat = match pat_or_expr { - ExprOrPatId::ExprId(expr) => source_map.expr_syntax(expr).map(Either::Left), - ExprOrPatId::PatId(pat) => source_map.pat_syntax(pat).map(Either::Right), - }; - let expr_or_pat = match expr_or_pat { - Ok(Either::Left(expr)) => expr, - Ok(Either::Right(InFile { file_id, value: pat })) => { - // cast from Either<Pat, SelfParam> -> Either<_, Pat> - let Some(ptr) = AstPtr::try_from_raw(pat.syntax_node_ptr()) else { - continue; - }; - InFile { file_id, value: ptr } - } - Err(SyntheticSyntax) => continue, - }; - - acc.push( - TypeMismatch { - expr_or_pat, - expected: Type::new(db, id, mismatch.expected.as_ref()), - actual: Type::new(db, id, mismatch.actual.as_ref()), - } - .into(), - ); + acc.extend(AnyDiagnostic::inference_diagnostic( + db, + id, + d, + source_map, + sig_source_map, + env, + )); } let missing_unsafe = hir_ty::diagnostics::missing_unsafe(db, id); @@ -6935,6 +6916,33 @@ pub trait HasVisibility { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum PredicatePolarity { + /// `T: Trait` + Positive, + /// `T: !Trait` + Negative, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TraitPredicate<'db> { + inner: hir_ty::next_solver::TraitPredicate<'db>, + env: ParamEnvAndCrate<'db>, +} + +impl<'db> TraitPredicate<'db> { + pub fn polarity(&self) -> PredicatePolarity { + match self.inner.polarity { + rustc_type_ir::PredicatePolarity::Positive => PredicatePolarity::Positive, + rustc_type_ir::PredicatePolarity::Negative => PredicatePolarity::Negative, + } + } + + pub fn trait_ref(&self) -> TraitRef<'db> { + TraitRef { env: self.env, trait_ref: self.inner.trait_ref } + } +} + /// Trait for obtaining the defining crate of an item. pub trait HasCrate { fn krate(&self, db: &dyn HirDatabase) -> Crate; diff --git a/crates/ide-diagnostics/src/handlers/invalid_cast.rs b/crates/ide-diagnostics/src/handlers/invalid_cast.rs index ba7556cd8b..c004ee31ae 100644 --- a/crates/ide-diagnostics/src/handlers/invalid_cast.rs +++ b/crates/ide-diagnostics/src/handlers/invalid_cast.rs @@ -390,7 +390,7 @@ struct Bar; impl Foo for Bar {} -fn to_raw<T>(_: *mut T) -> *mut () { +fn to_raw<T: ?Sized>(_: *mut T) -> *mut () { loop {} } diff --git a/crates/ide-diagnostics/src/handlers/missing_unsafe.rs b/crates/ide-diagnostics/src/handlers/missing_unsafe.rs index 38cf548cc6..aacbe72313 100644 --- a/crates/ide-diagnostics/src/handlers/missing_unsafe.rs +++ b/crates/ide-diagnostics/src/handlers/missing_unsafe.rs @@ -1062,7 +1062,7 @@ impl FooTrait for S2 { fn no_false_positive_on_format_args_since_1_89_0() { check_diagnostics( r#" -//- minicore: fmt +//- minicore: fmt, builtin_impls fn test() { let foo = 10; let bar = true; diff --git a/crates/ide-diagnostics/src/handlers/remove_trailing_return.rs b/crates/ide-diagnostics/src/handlers/remove_trailing_return.rs index fb1470b69f..b5a47e508e 100644 --- a/crates/ide-diagnostics/src/handlers/remove_trailing_return.rs +++ b/crates/ide-diagnostics/src/handlers/remove_trailing_return.rs @@ -333,7 +333,7 @@ fn foo(x: usize) -> u8 { } } "#, - std::iter::once("remove-unnecessary-else".to_owned()), + &["remove-unnecessary-else"], ); check_fix( r#" diff --git a/crates/ide-diagnostics/src/handlers/type_mismatch.rs b/crates/ide-diagnostics/src/handlers/type_mismatch.rs index d469405d61..250c692d16 100644 --- a/crates/ide-diagnostics/src/handlers/type_mismatch.rs +++ b/crates/ide-diagnostics/src/handlers/type_mismatch.rs @@ -338,7 +338,8 @@ fn str_ref_to_owned( #[cfg(test)] mod tests { use crate::tests::{ - check_diagnostics, check_diagnostics_with_disabled, check_fix, check_has_fix, check_no_fix, + check_diagnostics, check_diagnostics_with_disabled, check_fix, check_fix_with_disabled, + check_has_fix, check_no_fix, }; #[test] @@ -755,7 +756,7 @@ fn foo() -> Result<(), ()> { #[test] fn wrapped_unit_as_return_expr() { - check_fix( + check_fix_with_disabled( r#" //- minicore: result fn foo(b: bool) -> Result<(), String> { @@ -773,6 +774,7 @@ fn foo(b: bool) -> Result<(), String> { Err("oh dear".to_owned()) }"#, + &["E0599"], ); } @@ -822,7 +824,7 @@ fn foo() -> SomeOtherEnum { 0$0 } #[test] fn unwrap_return_type() { - check_fix( + check_fix_with_disabled( r#" //- minicore: option, result fn div(x: i32, y: i32) -> i32 { @@ -840,6 +842,7 @@ fn div(x: i32, y: i32) -> i32 { x / y } "#, + &["E0282"], ); } @@ -897,7 +900,7 @@ fn div(x: i32, y: i32) -> i32 { #[test] fn unwrap_return_type_option_tail_unit() { - check_fix( + check_fix_with_disabled( r#" //- minicore: option, result fn div(x: i32, y: i32) { @@ -915,12 +918,13 @@ fn div(x: i32, y: i32) { } } "#, + &["E0282"], ); } #[test] fn unwrap_return_type_handles_generic_functions() { - check_fix( + check_fix_with_disabled( r#" //- minicore: option, result fn div<T>(x: T) -> T { @@ -938,12 +942,13 @@ fn div<T>(x: T) -> T { x } "#, + &["E0282"], ); } #[test] fn unwrap_return_type_handles_type_aliases() { - check_fix( + check_fix_with_disabled( r#" //- minicore: option, result type MyResult<T> = T; @@ -965,12 +970,13 @@ fn div(x: i32, y: i32) -> MyResult<i32> { x / y } "#, + &["E0282"], ); } #[test] fn unwrap_tail_expr() { - check_fix( + check_fix_with_disabled( r#" //- minicore: result fn foo() -> () { @@ -983,12 +989,13 @@ fn foo() -> () { println!("Hello, world!"); } "#, + &["E0282"], ); } #[test] fn unwrap_to_empty_block() { - check_fix( + check_fix_with_disabled( r#" //- minicore: result fn foo() -> () { @@ -998,6 +1005,7 @@ fn foo() -> () { r#" fn foo() -> () {} "#, + &["E0282"], ); } @@ -1341,6 +1349,7 @@ pub fn foo<T: Foo>(_: T) -> (T::Out,) { loop { } } fn main() { let _x = foo(2); // ^^ error: type annotations needed + // ^^^ error: the trait bound `i32: Foo` is not satisfied } "#, ); diff --git a/crates/ide-diagnostics/src/handlers/type_must_be_known.rs b/crates/ide-diagnostics/src/handlers/type_must_be_known.rs index ad86df407a..30d8165e0a 100644 --- a/crates/ide-diagnostics/src/handlers/type_must_be_known.rs +++ b/crates/ide-diagnostics/src/handlers/type_must_be_known.rs @@ -1,5 +1,5 @@ use either::Either; -use hir::HirDisplay; +use hir::{HirDisplay, SpanAst}; use stdx::format_to; use syntax::{AstNode, SyntaxNodePtr, ast}; @@ -17,7 +17,7 @@ pub(crate) fn type_must_be_known<'db>( // Do some adjustments to the node: FIXME: We should probably do that at the emitting site. let node = ctx.sema.to_node(d.at_point); - if let Either::Right(Either::Left(expr)) = &node + if let SpanAst::Expr(expr) = &node && let Some(Either::Left(top_ty)) = &d.top_term && let Some(expr_ty) = ctx.sema.type_of_expr(expr) && expr_ty.original == *top_ty diff --git a/crates/ide-diagnostics/src/handlers/undeclared_label.rs b/crates/ide-diagnostics/src/handlers/undeclared_label.rs index 1bab4f453f..7efc8a7136 100644 --- a/crates/ide-diagnostics/src/handlers/undeclared_label.rs +++ b/crates/ide-diagnostics/src/handlers/undeclared_label.rs @@ -86,16 +86,18 @@ fn foo() { check_diagnostics( r#" //- minicore: option, try -fn foo() { +fn foo() -> Option<()> { None?; + None } "#, ); check_diagnostics( r#" //- minicore: option, try, future -async fn foo() { +async fn foo() -> Option<()> { None?; + None } "#, ); @@ -103,7 +105,7 @@ async fn foo() { r#" //- minicore: option, try, future, fn async fn foo() { - || None?; + || { None?; Some(()) }; } "#, ); diff --git a/crates/ide-diagnostics/src/handlers/unimplemented_trait.rs b/crates/ide-diagnostics/src/handlers/unimplemented_trait.rs new file mode 100644 index 0000000000..4326aec458 --- /dev/null +++ b/crates/ide-diagnostics/src/handlers/unimplemented_trait.rs @@ -0,0 +1,53 @@ +use hir::HirDisplay; + +use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext}; + +// Diagnostic: type-must-be-known +// +// This diagnostic is triggered when rust-analyzer cannot infer some type. +pub(crate) fn unimplemented_trait<'db>( + ctx: &DiagnosticsContext<'_, 'db>, + d: &hir::UnimplementedTrait<'db>, +) -> Diagnostic { + let message = match &d.root_trait_predicate { + Some(root_predicate) if *root_predicate != d.trait_predicate => format!( + "the trait bound `{}` is not satisfied\n\ + required by the bound `{}`\n", + d.trait_predicate.display(ctx.db(), ctx.display_target), + root_predicate.display(ctx.db(), ctx.display_target), + ), + _ => format!( + "the trait bound `{}` is not satisfied", + d.trait_predicate.display(ctx.db(), ctx.display_target), + ), + }; + Diagnostic::new_with_syntax_node_ptr( + ctx, + DiagnosticCode::RustcHardError("E0277"), + message, + d.span.map(Into::into), + ) +} + +#[cfg(test)] +mod tests { + use crate::tests::check_diagnostics; + + #[test] + fn smoke_test() { + check_diagnostics( + r#" +trait Trait {} +impl<T: Trait, const N: usize> Trait for [T; N] {} +fn foo(_v: impl Trait) {} +fn bar() { + foo(1); + // ^^^ error: the trait bound `i32: Trait` is not satisfied + foo([1]); + // ^^^ error: the trait bound `i32: Trait` is not satisfied + // | required by the bound `[i32; 1]: Trait` +} + "#, + ); + } +} diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs index 1d5811954c..451f79e828 100644 --- a/crates/ide-diagnostics/src/lib.rs +++ b/crates/ide-diagnostics/src/lib.rs @@ -72,6 +72,7 @@ mod handlers { pub(crate) mod typed_hole; pub(crate) mod undeclared_label; pub(crate) mod unimplemented_builtin_macro; + pub(crate) mod unimplemented_trait; pub(crate) mod union_expr_must_have_exactly_one_field; pub(crate) mod unreachable_label; pub(crate) mod unresolved_assoc_item; @@ -493,6 +494,7 @@ pub fn semantic_diagnostics( AnyDiagnostic::TypeMustBeKnown(d) => handlers::type_must_be_known::type_must_be_known(&ctx, &d), AnyDiagnostic::PatternArgInExternFn(d) => handlers::pattern_arg_in_extern_fn::pattern_arg_in_extern_fn(&ctx, &d), AnyDiagnostic::UnionExprMustHaveExactlyOneField(d) => handlers::union_expr_must_have_exactly_one_field::union_expr_must_have_exactly_one_field(&ctx, &d), + AnyDiagnostic::UnimplementedTrait(d) => handlers::unimplemented_trait::unimplemented_trait(&ctx, &d), }; res.push(d) } diff --git a/crates/ide-diagnostics/src/tests.rs b/crates/ide-diagnostics/src/tests.rs index fc49542e3c..4b9535ca06 100644 --- a/crates/ide-diagnostics/src/tests.rs +++ b/crates/ide-diagnostics/src/tests.rs @@ -57,11 +57,11 @@ fn check_nth_fix( pub(crate) fn check_fix_with_disabled( #[rust_analyzer::rust_fixture] ra_fixture_before: &str, #[rust_analyzer::rust_fixture] ra_fixture_after: &str, - disabled: impl Iterator<Item = String>, + disabled: &[&str], ) { let mut config = DiagnosticsConfig::test_sample(); config.expr_fill_default = ExprFillDefaultMode::Default; - config.disabled.extend(disabled); + config.disabled.extend(disabled.iter().map(|&disabled| disabled.to_owned())); check_nth_fix_with_config(config, 0, ra_fixture_before, ra_fixture_after) } diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index a2b317be58..3eb7867a3a 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs @@ -580,6 +580,7 @@ pub fn also_calls_foo() { "#, false, false, + // FIXME: The ranges here are volatile when minicore changes, that's not good. expect![[r#" foo Function FileId(1) 0..15 7..10 @@ -599,7 +600,7 @@ fn main() { false, false, expect![[r#" - Some Variant FileId(1) 6022..6054 6047..6051 + Some Variant FileId(1) 6737..6769 6762..6766 FileId(0) 46..50 "#]], diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs index 0314600405..d06690f203 100644 --- a/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -2,6 +2,7 @@ //! errors. use std::{ + cell::LazyCell, env, fmt, ops::AddAssign, panic::{AssertUnwindSafe, catch_unwind}, @@ -908,6 +909,18 @@ impl flags::AnalysisStats { // region:expressions let (previous_exprs, previous_unknown, previous_partially_unknown) = (num_exprs, num_exprs_unknown, num_exprs_partially_unknown); + let type_mismatch_for_node = LazyCell::new(|| { + inference_result + .diagnostics() + .iter() + .filter_map(|diag| match diag { + hir_ty::InferenceDiagnostic::TypeMismatch { node, expected, found } => { + Some((*node, (expected.as_ref(), found.as_ref()))) + } + _ => None, + }) + .collect::<FxHashMap<_, _>>() + }); for (expr_id, _) in body.exprs() { let ty = inference_result.expr_ty(expr_id); num_exprs += 1; @@ -964,9 +977,10 @@ impl flags::AnalysisStats { ty.display(db, display_target) ); } - if let Some(mismatch) = inference_result.type_mismatch_for_expr(expr_id) { + if inference_result.expr_has_type_mismatch(expr_id) { num_expr_type_mismatches += 1; if verbosity.is_verbose() { + let (expected, actual) = type_mismatch_for_node[&expr_id.into()]; if let Some((path, start, end)) = expr_syntax_range(db, vfs, sm(), expr_id) { bar.println(format!( @@ -976,24 +990,25 @@ impl flags::AnalysisStats { start.col, end.line + 1, end.col, - mismatch.expected.as_ref().display(db, display_target), - mismatch.actual.as_ref().display(db, display_target) + expected.display(db, display_target), + actual.display(db, display_target) )); } else { bar.println(format!( "{}: Expected {}, got {}", name.display(db, Edition::LATEST), - mismatch.expected.as_ref().display(db, display_target), - mismatch.actual.as_ref().display(db, display_target) + expected.display(db, display_target), + actual.display(db, display_target) )); } } if self.output == Some(OutputFormat::Csv) { + let (expected, actual) = type_mismatch_for_node[&expr_id.into()]; println!( r#"{},mismatch,"{}","{}""#, location_csv_expr(db, vfs, sm(), expr_id), - mismatch.expected.as_ref().display(db, display_target), - mismatch.actual.as_ref().display(db, display_target) + expected.display(db, display_target), + actual.display(db, display_target) ); } } @@ -1067,9 +1082,10 @@ impl flags::AnalysisStats { ty.display(db, display_target) ); } - if let Some(mismatch) = inference_result.type_mismatch_for_pat(pat_id) { + if inference_result.pat_has_type_mismatch(pat_id) { num_pat_type_mismatches += 1; if verbosity.is_verbose() { + let (expected, actual) = type_mismatch_for_node[&pat_id.into()]; if let Some((path, start, end)) = pat_syntax_range(db, vfs, sm(), pat_id) { bar.println(format!( "{} {}:{}-{}:{}: Expected {}, got {}", @@ -1078,24 +1094,25 @@ impl flags::AnalysisStats { start.col, end.line + 1, end.col, - mismatch.expected.as_ref().display(db, display_target), - mismatch.actual.as_ref().display(db, display_target) + expected.display(db, display_target), + actual.display(db, display_target) )); } else { bar.println(format!( "{}: Expected {}, got {}", name.display(db, Edition::LATEST), - mismatch.expected.as_ref().display(db, display_target), - mismatch.actual.as_ref().display(db, display_target) + expected.display(db, display_target), + actual.display(db, display_target) )); } } if self.output == Some(OutputFormat::Csv) { + let (expected, actual) = type_mismatch_for_node[&pat_id.into()]; println!( r#"{},mismatch,"{}","{}""#, location_csv_pat(db, vfs, sm(), pat_id), - mismatch.expected.as_ref().display(db, display_target), - mismatch.actual.as_ref().display(db, display_target) + expected.display(db, display_target), + actual.display(db, display_target) ); } } diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs index e9ab066160..29775590ea 100644 --- a/crates/test-utils/src/minicore.rs +++ b/crates/test-utils/src/minicore.rs @@ -738,6 +738,30 @@ pub mod ops { pub struct RangeToInclusive<Idx> { pub end: Idx, } + + // region:iterator + pub trait Step {} + macro_rules! impl_step { + ( $( $ty:ty ),* $(,)? ) => { + $( + impl Step for $ty {} + )* + }; + } + impl_step!(i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); + + macro_rules! impl_iterator { + ( $( $range:ident ),* $(,)? ) => { + $( + impl<Idx: Step> Iterator for $range<Idx> { + type Item = Idx; + fn next(&mut self) -> Option<Self::Item> { loop {} } + } + )* + }; + } + impl_iterator!(Range, RangeFrom, RangeTo, RangeInclusive, RangeToInclusive); + // endregion:iterator } pub use self::range::{Range, RangeFrom, RangeFull, RangeTo}; pub use self::range::{RangeInclusive, RangeToInclusive}; @@ -1292,6 +1316,38 @@ pub mod fmt { fn fmt(&self, f: &mut Formatter<'_>) -> Result; } + impl<T: ?Sized + Debug> Debug for &T { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + T::fmt(&**self, f) + } + } + impl<T: ?Sized + Display> Display for &T { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + T::fmt(&**self, f) + } + } + + macro_rules! impl_fmt_traits { + ( $($ty:ty),* $(,)? ) => { + $( + impl Debug for $ty { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { loop {} } + } + impl Display for $ty { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { loop {} } + } + )* + } + } + + impl_fmt_traits!(str); + + // region:builtin_impls + impl_fmt_traits!( + i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64, bool, char, + ); + // endregion:builtin_impls + mod rt { use super::*; |