Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir/src/diagnostics.rs')
-rw-r--r--crates/hir/src/diagnostics.rs343
1 files changed, 282 insertions, 61 deletions
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index 6cfb79d5a1..a044f24587 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -6,7 +6,7 @@
use cfg::{CfgExpr, CfgOptions};
use either::Either;
use hir_def::{
- DefWithBodyId, GenericParamId, SyntheticSyntax,
+ DefWithBodyId, GenericParamId, HasModule, SyntheticSyntax,
expr_store::{
ExprOrPatPtr, ExpressionStoreSourceMap, hir_assoc_type_binding_to_ast,
hir_generic_arg_to_ast, hir_segment_to_ast_segment,
@@ -15,11 +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},
@@ -27,7 +31,7 @@ use syntax::{
};
use triomphe::Arc;
-use crate::{AssocItem, Field, Function, GenericDef, Local, Trait, Type};
+use crate::{AssocItem, Field, Function, GenericDef, Local, Trait, Type, Variant};
pub use hir_def::VariantId;
pub use hir_ty::{
@@ -35,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)]
@@ -56,12 +103,18 @@ diagnostics![AnyDiagnostic<'db> ->
AwaitOutsideOfAsync,
BreakOutsideOfLoop,
CastToUnsized<'db>,
+ ExpectedArrayOrSlicePat<'db>,
ExpectedFunction<'db>,
+ FunctionalRecordUpdateOnNonStruct,
+ GenericDefaultRefersToSelf,
InactiveCode,
IncoherentImpl,
IncorrectCase,
+ IncorrectGenericsLen,
+ IncorrectGenericsOrder,
InvalidCast<'db>,
InvalidDeriveTarget,
+ InvalidLhsOfAssignment,
MacroDefError,
MacroError,
MacroExpansionParseError,
@@ -74,11 +127,16 @@ diagnostics![AnyDiagnostic<'db> ->
MovedOutOfRef<'db>,
NeedMut,
NonExhaustiveLet,
+ NonExhaustiveRecordExpr,
NoSuchField,
+ MismatchedArrayPatLen,
+ DuplicateField,
+ PatternArgInExternFn,
PrivateAssocItem,
PrivateField,
RemoveTrailingReturn,
RemoveUnnecessaryElse,
+ UnusedMustUse<'db>,
ReplaceFilterMapNextWithFindMap,
TraitImplIncorrectSafety,
TraitImplMissingAssocItems,
@@ -102,10 +160,11 @@ diagnostics![AnyDiagnostic<'db> ->
GenericArgsProhibited,
ParenthesizedGenericArgsWithoutFnTrait,
BadRtn,
- IncorrectGenericsLen,
- IncorrectGenericsOrder,
MissingLifetime,
ElidedLifetimesInPath,
+ TypeMustBeKnown<'db>,
+ UnionExprMustHaveExactlyOneField,
+ UnimplementedTrait<'db>,
];
#[derive(Debug)]
@@ -212,6 +271,12 @@ pub struct NoSuchField {
}
#[derive(Debug)]
+pub struct DuplicateField {
+ pub field: InFile<AstPtr<Either<ast::RecordExprField, ast::RecordPatField>>>,
+ pub variant: Variant,
+}
+
+#[derive(Debug)]
pub struct PrivateAssocItem {
pub expr_or_pat: InFile<ExprOrPatPtr>,
pub item: AssocItem,
@@ -225,12 +290,31 @@ pub struct MismatchedTupleStructPatArgCount {
}
#[derive(Debug)]
+pub struct MismatchedArrayPatLen {
+ pub pat: InFile<ExprOrPatPtr>,
+ pub expected: u128,
+ pub found: u128,
+ pub has_rest: bool,
+}
+
+#[derive(Debug)]
+pub struct ExpectedArrayOrSlicePat<'db> {
+ pub pat: InFile<ExprOrPatPtr>,
+ pub found: Type<'db>,
+}
+
+#[derive(Debug)]
pub struct ExpectedFunction<'db> {
pub call: InFile<ExprOrPatPtr>,
pub found: Type<'db>,
}
#[derive(Debug)]
+pub struct FunctionalRecordUpdateOnNonStruct {
+ pub base_expr: InFile<ExprOrPatPtr>,
+}
+
+#[derive(Debug)]
pub struct UnresolvedField<'db> {
pub expr: InFile<ExprOrPatPtr>,
pub receiver: Type<'db>,
@@ -312,6 +396,11 @@ pub struct NonExhaustiveLet {
}
#[derive(Debug)]
+pub struct NonExhaustiveRecordExpr {
+ pub expr: InFile<ExprOrPatPtr>,
+}
+
+#[derive(Debug)]
pub struct TypeMismatch<'db> {
pub expr_or_pat: InFile<ExprOrPatPtr>,
pub expected: Type<'db>,
@@ -386,6 +475,12 @@ pub struct RemoveUnnecessaryElse {
}
#[derive(Debug)]
+pub struct UnusedMustUse<'db> {
+ pub expr: InFile<ExprOrPatPtr>,
+ pub message: Option<&'db str>,
+}
+
+#[derive(Debug)]
pub struct CastToUnsized<'db> {
pub expr: InFile<ExprOrPatPtr>,
pub cast_ty: Type<'db>,
@@ -442,6 +537,12 @@ pub struct ElidedLifetimesInPath {
pub hard_error: bool,
}
+#[derive(Debug)]
+pub struct TypeMustBeKnown<'db> {
+ pub at_point: SpanSyntax,
+ pub top_term: Option<Either<Type<'db>, String>>,
+}
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GenericArgKind {
Lifetime,
@@ -465,10 +566,38 @@ pub struct IncorrectGenericsOrder {
pub expected_kind: GenericArgKind,
}
+#[derive(Debug)]
+pub struct GenericDefaultRefersToSelf {
+ /// The `Self` segment.
+ pub segment: InFile<AstPtr<ast::PathSegment>>,
+}
+
+#[derive(Debug)]
+pub struct UnionExprMustHaveExactlyOneField {
+ pub expr: InFile<ExprOrPatPtr>,
+}
+
+#[derive(Debug)]
+pub struct InvalidLhsOfAssignment {
+ pub lhs: InFile<AstPtr<Either<ast::Expr, ast::Pat>>>,
+}
+
+#[derive(Debug)]
+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,
- diagnostic: BodyValidationDiagnostic,
+ diagnostic: BodyValidationDiagnostic<'db>,
source_map: &hir_def::expr_store::BodySourceMap,
) -> Option<AnyDiagnostic<'db>> {
match diagnostic {
@@ -536,59 +665,47 @@ impl<'db> AnyDiagnostic<'db> {
}
}
BodyValidationDiagnostic::MissingMatchArms { match_expr, uncovered_patterns } => {
- match source_map.expr_syntax(match_expr) {
- Ok(source_ptr) => {
- let root = source_ptr.file_syntax(db);
- if let Either::Left(ast::Expr::MatchExpr(match_expr)) =
- &source_ptr.value.to_node(&root)
- {
- match match_expr.expr() {
- Some(scrut_expr) if match_expr.match_arm_list().is_some() => {
- return Some(
- MissingMatchArms {
- scrutinee_expr: InFile::new(
- source_ptr.file_id,
- AstPtr::new(&scrut_expr),
- ),
- uncovered_patterns,
- }
- .into(),
- );
- }
- _ => {}
- }
+ if let Ok(source_ptr) = source_map.expr_syntax(match_expr)
+ && let root = source_ptr.file_syntax(db)
+ && let Either::Left(ast::Expr::MatchExpr(match_expr)) =
+ source_ptr.value.to_node(&root)
+ && let Some(scrut_expr) = match_expr.expr()
+ && match_expr.match_arm_list().is_some()
+ {
+ return Some(
+ MissingMatchArms {
+ scrutinee_expr: InFile::new(
+ source_ptr.file_id,
+ AstPtr::new(&scrut_expr),
+ ),
+ uncovered_patterns,
}
- }
- Err(SyntheticSyntax) => (),
+ .into(),
+ );
}
}
BodyValidationDiagnostic::NonExhaustiveLet { pat, uncovered_patterns } => {
- match source_map.pat_syntax(pat) {
- Ok(source_ptr) => {
- if let Some(ast_pat) = source_ptr.value.cast::<ast::Pat>() {
- return Some(
- NonExhaustiveLet {
- pat: InFile::new(source_ptr.file_id, ast_pat),
- uncovered_patterns,
- }
- .into(),
- );
+ if let Ok(source_ptr) = source_map.pat_syntax(pat)
+ && let Some(ast_pat) = source_ptr.value.cast::<ast::Pat>()
+ {
+ return Some(
+ NonExhaustiveLet {
+ pat: InFile::new(source_ptr.file_id, ast_pat),
+ uncovered_patterns,
}
- }
- Err(SyntheticSyntax) => {}
+ .into(),
+ );
}
}
BodyValidationDiagnostic::RemoveTrailingReturn { return_expr } => {
- if let Ok(source_ptr) = source_map.expr_syntax(return_expr) {
+ if let Ok(source_ptr) = source_map.expr_syntax(return_expr)
// Filters out desugared return expressions (e.g. desugared try operators).
- if let Some(ptr) = source_ptr.value.cast::<ast::ReturnExpr>() {
- return Some(
- RemoveTrailingReturn {
- return_expr: InFile::new(source_ptr.file_id, ptr),
- }
+ && let Some(ptr) = source_ptr.value.cast::<ast::ReturnExpr>()
+ {
+ return Some(
+ RemoveTrailingReturn { return_expr: InFile::new(source_ptr.file_id, ptr) }
.into(),
- );
- }
+ );
}
}
BodyValidationDiagnostic::RemoveUnnecessaryElse { if_expr } => {
@@ -601,6 +718,11 @@ impl<'db> AnyDiagnostic<'db> {
);
}
}
+ BodyValidationDiagnostic::UnusedMustUse { expr, message } => {
+ if let Ok(source_ptr) = source_map.expr_syntax(expr) {
+ return Some(UnusedMustUse { expr: source_ptr, message }.into());
+ }
+ }
}
None
}
@@ -608,9 +730,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
@@ -624,10 +747,28 @@ impl<'db> AnyDiagnostic<'db> {
.inspect_err(|_| stdx::never!("inference diagnostic in desugared pattern"))
.ok()
};
+ let type_syntax = |pat| {
+ source_map
+ .type_syntax(pat)
+ .inspect_err(|_| stdx::never!("inference diagnostic in desugared type"))
+ .ok()
+ };
let expr_or_pat_syntax = |id| match id {
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 {
@@ -639,6 +780,23 @@ impl<'db> AnyDiagnostic<'db> {
let private = private.map(|id| Field { id, parent: variant.into() });
NoSuchField { field: expr_or_pat, private, variant }.into()
}
+ &InferenceDiagnostic::MismatchedArrayPatLen { pat, expected, found, has_rest } => {
+ let pat = pat_syntax(pat)?.map(Into::into);
+ MismatchedArrayPatLen { pat, expected, found, has_rest }.into()
+ }
+ InferenceDiagnostic::ExpectedArrayOrSlicePat { pat, found } => {
+ let pat = pat_syntax(*pat)?.map(Into::into);
+ ExpectedArrayOrSlicePat { pat, found: Type::new(db, def, found.as_ref()) }.into()
+ }
+ &InferenceDiagnostic::DuplicateField { field: expr, variant } => {
+ let expr_or_pat = match expr {
+ ExprOrPatId::ExprId(expr) => {
+ source_map.field_syntax(expr).map(AstPtr::wrap_left)
+ }
+ ExprOrPatId::PatId(pat) => source_map.pat_field_syntax(pat),
+ };
+ DuplicateField { field: expr_or_pat, variant: variant.into() }.into()
+ }
&InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => {
MismatchedArgCount { call_expr: expr_syntax(call_expr)?, expected, found }.into()
}
@@ -711,21 +869,21 @@ impl<'db> AnyDiagnostic<'db> {
let expr = expr_syntax(expr)?;
BreakOutsideOfLoop { expr, is_break, bad_value_break }.into()
}
+ &InferenceDiagnostic::NonExhaustiveRecordExpr { expr } => {
+ NonExhaustiveRecordExpr { expr: expr_syntax(expr)? }.into()
+ }
+ &InferenceDiagnostic::FunctionalRecordUpdateOnNonStruct { base_expr } => {
+ FunctionalRecordUpdateOnNonStruct { base_expr: expr_syntax(base_expr)? }.into()
+ }
InferenceDiagnostic::TypedHole { expr, expected } => {
let expr = expr_syntax(*expr)?;
TypedHole { expr, expected: Type::new(db, def, expected.as_ref()) }.into()
}
&InferenceDiagnostic::MismatchedTupleStructPatArgCount { pat, expected, found } => {
- let expr_or_pat = match pat {
- ExprOrPatId::ExprId(expr) => expr_syntax(expr)?,
- ExprOrPatId::PatId(pat) => {
- let InFile { file_id, value } = pat_syntax(pat)?;
-
- // cast from Either<Pat, SelfParam> -> Either<_, Pat>
- let ptr = AstPtr::try_from_raw(value.syntax_node_ptr())?;
- InFile { file_id, value: ptr }
- }
- };
+ let InFile { file_id, value } = pat_syntax(pat)?;
+ // cast from Either<Pat, SelfParam> -> Either<_, Pat>
+ let ptr = AstPtr::try_from_raw(value.syntax_node_ptr())?;
+ let expr_or_pat = InFile { file_id, value: ptr };
MismatchedTupleStructPatArgCount { expr_or_pat, expected, found }.into()
}
InferenceDiagnostic::CastToUnsized { expr, cast_ty } => {
@@ -801,6 +959,64 @@ impl<'db> AnyDiagnostic<'db> {
let expected_kind = GenericArgKind::from_id(param_id);
IncorrectGenericsOrder { provided_arg, expected_kind }.into()
}
+ &InferenceDiagnostic::InvalidLhsOfAssignment { lhs } => {
+ let lhs = expr_syntax(lhs)?;
+ InvalidLhsOfAssignment { lhs }.into()
+ }
+ &InferenceDiagnostic::TypeMustBeKnown { at_point, ref top_term } => {
+ 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,
+ env: crate::body_param_env_from_has_crate(db, def),
+ }),
+ // FIXME: Printing the const to string is definitely not the correct thing to do here.
+ rustc_type_ir::GenericArgKind::Const(konst) => Either::Right(
+ konst.display(db, DisplayTarget::from_crate(db, def.krate(db))).to_string(),
+ ),
+ rustc_type_ir::GenericArgKind::Lifetime(_) => {
+ unreachable!("we currently don't emit TypeMustBeKnown for lifetimes")
+ }
+ });
+ TypeMustBeKnown { at_point, top_term }.into()
+ }
+ &InferenceDiagnostic::UnionExprMustHaveExactlyOneField { expr } => {
+ 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()
+ }
})
}
@@ -894,6 +1110,11 @@ impl<'db> AnyDiagnostic<'db> {
}
.into()
}
+ PathLoweringDiagnostic::GenericDefaultRefersToSelf { segment } => {
+ let segment = hir_segment_to_ast_segment(&path.value, segment)?;
+ let segment = path.with_value(AstPtr::new(&segment));
+ GenericDefaultRefersToSelf { segment }.into()
+ }
})
}