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.rs | 343 |
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() + } }) } |