Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/hir-def/src/attrs.rs8
-rw-r--r--crates/hir-def/src/expr_store/lower/format_args.rs48
-rw-r--r--crates/hir-def/src/hir/format_args.rs9
-rw-r--r--crates/hir-expand/src/files.rs10
-rw-r--r--crates/hir-ty/src/diagnostics/expr.rs14
-rw-r--r--crates/hir-ty/src/display.rs21
-rw-r--r--crates/hir-ty/src/infer.rs86
-rw-r--r--crates/hir-ty/src/infer/coerce.rs17
-rw-r--r--crates/hir-ty/src/infer/expr.rs20
-rw-r--r--crates/hir-ty/src/infer/pat.rs7
-rw-r--r--crates/hir-ty/src/infer/unify.rs40
-rw-r--r--crates/hir-ty/src/lib.rs1
-rw-r--r--crates/hir-ty/src/method_resolution/confirm.rs7
-rw-r--r--crates/hir-ty/src/mir/lower.rs11
-rw-r--r--crates/hir-ty/src/next_solver/binder.rs26
-rw-r--r--crates/hir-ty/src/next_solver/consts.rs39
-rw-r--r--crates/hir-ty/src/next_solver/fulfill.rs2
-rw-r--r--crates/hir-ty/src/next_solver/infer/errors.rs704
-rw-r--r--crates/hir-ty/src/next_solver/infer/mod.rs1
-rw-r--r--crates/hir-ty/src/next_solver/infer/traits.rs3
-rw-r--r--crates/hir-ty/src/next_solver/inspect.rs6
-rw-r--r--crates/hir-ty/src/next_solver/normalize.rs1
-rw-r--r--crates/hir-ty/src/next_solver/predicate.rs1
-rw-r--r--crates/hir-ty/src/solver_errors.rs90
-rw-r--r--crates/hir-ty/src/tests.rs43
-rw-r--r--crates/hir-ty/src/tests/regression.rs1
-rw-r--r--crates/hir/src/diagnostics.rs121
-rw-r--r--crates/hir/src/display.rs10
-rw-r--r--crates/hir/src/lib.rs64
-rw-r--r--crates/ide-diagnostics/src/handlers/invalid_cast.rs2
-rw-r--r--crates/ide-diagnostics/src/handlers/missing_unsafe.rs2
-rw-r--r--crates/ide-diagnostics/src/handlers/remove_trailing_return.rs2
-rw-r--r--crates/ide-diagnostics/src/handlers/type_mismatch.rs25
-rw-r--r--crates/ide-diagnostics/src/handlers/type_must_be_known.rs4
-rw-r--r--crates/ide-diagnostics/src/handlers/undeclared_label.rs8
-rw-r--r--crates/ide-diagnostics/src/handlers/unimplemented_trait.rs53
-rw-r--r--crates/ide-diagnostics/src/lib.rs2
-rw-r--r--crates/ide-diagnostics/src/tests.rs4
-rw-r--r--crates/ide/src/references.rs3
-rw-r--r--crates/rust-analyzer/src/cli/analysis_stats.rs45
-rw-r--r--crates/test-utils/src/minicore.rs56
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::*;