hir/
diagnostics.rs

1//! Re-export diagnostics such that clients of `hir` don't have to depend on
2//! low-level crates.
3//!
4//! This probably isn't the best way to do this -- ideally, diagnostics should
5//! be expressed in terms of hir types themselves.
6use cfg::{CfgExpr, CfgOptions};
7use either::Either;
8use hir_def::{
9    DefWithBodyId, GenericParamId, SyntheticSyntax,
10    expr_store::{
11        ExprOrPatPtr, ExpressionStoreSourceMap, hir_assoc_type_binding_to_ast,
12        hir_generic_arg_to_ast, hir_segment_to_ast_segment,
13    },
14    hir::ExprOrPatId,
15};
16use hir_expand::{HirFileId, InFile, mod_path::ModPath, name::Name};
17use hir_ty::{
18    CastError, InferenceDiagnostic, InferenceTyDiagnosticSource, PathGenericsSource,
19    PathLoweringDiagnostic, TyLoweringDiagnostic, TyLoweringDiagnosticKind,
20    db::HirDatabase,
21    diagnostics::{BodyValidationDiagnostic, UnsafetyReason},
22};
23use syntax::{
24    AstNode, AstPtr, SyntaxError, SyntaxNodePtr, TextRange,
25    ast::{self, HasGenericArgs},
26    match_ast,
27};
28use triomphe::Arc;
29
30use crate::{AssocItem, Field, Function, GenericDef, Local, Trait, Type};
31
32pub use hir_def::VariantId;
33pub use hir_ty::{
34    GenericArgsProhibitedReason, IncorrectGenericsLenKind,
35    diagnostics::{CaseType, IncorrectCase},
36};
37
38macro_rules! diagnostics {
39    ($AnyDiagnostic:ident <$db:lifetime> -> $($diag:ident $(<$lt:lifetime>)?,)*) => {
40        #[derive(Debug)]
41        pub enum $AnyDiagnostic<$db> {$(
42            $diag(Box<$diag $(<$lt>)?>),
43        )*}
44
45        $(
46            impl<$db> From<$diag $(<$lt>)?> for $AnyDiagnostic<$db> {
47                fn from(d: $diag $(<$lt>)?) -> $AnyDiagnostic<$db> {
48                    $AnyDiagnostic::$diag(Box::new(d))
49                }
50            }
51        )*
52    };
53}
54// FIXME Accept something like the following in the macro call instead
55// diagnostics![
56// pub struct BreakOutsideOfLoop {
57//     pub expr: InFile<AstPtr<ast::Expr>>,
58//     pub is_break: bool,
59//     pub bad_value_break: bool,
60// }, ...
61// or more concisely
62// BreakOutsideOfLoop {
63//     expr: InFile<AstPtr<ast::Expr>>,
64//     is_break: bool,
65//     bad_value_break: bool,
66// }, ...
67// ]
68
69diagnostics![AnyDiagnostic<'db> ->
70    AwaitOutsideOfAsync,
71    BreakOutsideOfLoop,
72    CastToUnsized<'db>,
73    ExpectedFunction<'db>,
74    InactiveCode,
75    IncoherentImpl,
76    IncorrectCase,
77    InvalidCast<'db>,
78    InvalidDeriveTarget,
79    MacroDefError,
80    MacroError,
81    MacroExpansionParseError,
82    MalformedDerive,
83    MismatchedArgCount,
84    MismatchedTupleStructPatArgCount,
85    MissingFields,
86    MissingMatchArms,
87    MissingUnsafe,
88    MovedOutOfRef<'db>,
89    NeedMut,
90    NonExhaustiveLet,
91    NoSuchField,
92    PrivateAssocItem,
93    PrivateField,
94    RemoveTrailingReturn,
95    RemoveUnnecessaryElse,
96    ReplaceFilterMapNextWithFindMap,
97    TraitImplIncorrectSafety,
98    TraitImplMissingAssocItems,
99    TraitImplOrphan,
100    TraitImplRedundantAssocItems,
101    TypedHole<'db>,
102    TypeMismatch<'db>,
103    UndeclaredLabel,
104    UnimplementedBuiltinMacro,
105    UnreachableLabel,
106    UnresolvedAssocItem,
107    UnresolvedExternCrate,
108    UnresolvedField<'db>,
109    UnresolvedImport,
110    UnresolvedMacroCall,
111    UnresolvedMethodCall<'db>,
112    UnresolvedModule,
113    UnresolvedIdent,
114    UnusedMut,
115    UnusedVariable,
116    GenericArgsProhibited,
117    ParenthesizedGenericArgsWithoutFnTrait,
118    BadRtn,
119    IncorrectGenericsLen,
120    IncorrectGenericsOrder,
121    MissingLifetime,
122    ElidedLifetimesInPath,
123];
124
125#[derive(Debug)]
126pub struct BreakOutsideOfLoop {
127    pub expr: InFile<ExprOrPatPtr>,
128    pub is_break: bool,
129    pub bad_value_break: bool,
130}
131
132#[derive(Debug)]
133pub struct TypedHole<'db> {
134    pub expr: InFile<ExprOrPatPtr>,
135    pub expected: Type<'db>,
136}
137
138#[derive(Debug)]
139pub struct UnresolvedModule {
140    pub decl: InFile<AstPtr<ast::Module>>,
141    pub candidates: Box<[String]>,
142}
143
144#[derive(Debug)]
145pub struct UnresolvedExternCrate {
146    pub decl: InFile<AstPtr<ast::ExternCrate>>,
147}
148
149#[derive(Debug)]
150pub struct UnresolvedImport {
151    pub decl: InFile<AstPtr<ast::UseTree>>,
152}
153
154#[derive(Debug, Clone, Eq, PartialEq)]
155pub struct UnresolvedMacroCall {
156    pub range: InFile<TextRange>,
157    pub path: ModPath,
158    pub is_bang: bool,
159}
160#[derive(Debug, Clone, Eq, PartialEq)]
161pub struct UnreachableLabel {
162    pub node: InFile<AstPtr<ast::Lifetime>>,
163    pub name: Name,
164}
165
166#[derive(Debug)]
167pub struct AwaitOutsideOfAsync {
168    pub node: InFile<AstPtr<ast::AwaitExpr>>,
169    pub location: String,
170}
171
172#[derive(Debug, Clone, Eq, PartialEq)]
173pub struct UndeclaredLabel {
174    pub node: InFile<AstPtr<ast::Lifetime>>,
175    pub name: Name,
176}
177
178#[derive(Debug, Clone, Eq, PartialEq)]
179pub struct InactiveCode {
180    pub node: InFile<SyntaxNodePtr>,
181    pub cfg: CfgExpr,
182    pub opts: CfgOptions,
183}
184
185#[derive(Debug, Clone, Eq, PartialEq)]
186pub struct MacroError {
187    pub range: InFile<TextRange>,
188    pub message: String,
189    pub error: bool,
190    pub kind: &'static str,
191}
192
193#[derive(Debug, Clone, Eq, PartialEq)]
194pub struct MacroExpansionParseError {
195    pub range: InFile<TextRange>,
196    pub errors: Arc<[SyntaxError]>,
197}
198
199#[derive(Debug, Clone, Eq, PartialEq)]
200pub struct MacroDefError {
201    pub node: InFile<AstPtr<ast::Macro>>,
202    pub message: String,
203    pub name: Option<TextRange>,
204}
205
206#[derive(Debug)]
207pub struct UnimplementedBuiltinMacro {
208    pub node: InFile<SyntaxNodePtr>,
209}
210
211#[derive(Debug)]
212pub struct InvalidDeriveTarget {
213    pub range: InFile<TextRange>,
214}
215
216#[derive(Debug)]
217pub struct MalformedDerive {
218    pub range: InFile<TextRange>,
219}
220
221#[derive(Debug)]
222pub struct NoSuchField {
223    pub field: InFile<AstPtr<Either<ast::RecordExprField, ast::RecordPatField>>>,
224    pub private: Option<Field>,
225    pub variant: VariantId,
226}
227
228#[derive(Debug)]
229pub struct PrivateAssocItem {
230    pub expr_or_pat: InFile<ExprOrPatPtr>,
231    pub item: AssocItem,
232}
233
234#[derive(Debug)]
235pub struct MismatchedTupleStructPatArgCount {
236    pub expr_or_pat: InFile<ExprOrPatPtr>,
237    pub expected: usize,
238    pub found: usize,
239}
240
241#[derive(Debug)]
242pub struct ExpectedFunction<'db> {
243    pub call: InFile<ExprOrPatPtr>,
244    pub found: Type<'db>,
245}
246
247#[derive(Debug)]
248pub struct UnresolvedField<'db> {
249    pub expr: InFile<ExprOrPatPtr>,
250    pub receiver: Type<'db>,
251    pub name: Name,
252    pub method_with_same_name_exists: bool,
253}
254
255#[derive(Debug)]
256pub struct UnresolvedMethodCall<'db> {
257    pub expr: InFile<ExprOrPatPtr>,
258    pub receiver: Type<'db>,
259    pub name: Name,
260    pub field_with_same_name: Option<Type<'db>>,
261    pub assoc_func_with_same_name: Option<Function>,
262}
263
264#[derive(Debug)]
265pub struct UnresolvedAssocItem {
266    pub expr_or_pat: InFile<ExprOrPatPtr>,
267}
268
269#[derive(Debug)]
270pub struct UnresolvedIdent {
271    pub node: InFile<(ExprOrPatPtr, Option<TextRange>)>,
272}
273
274#[derive(Debug)]
275pub struct PrivateField {
276    pub expr: InFile<ExprOrPatPtr>,
277    pub field: Field,
278}
279
280#[derive(Debug, Clone, Copy, PartialEq, Eq)]
281pub enum UnsafeLint {
282    HardError,
283    UnsafeOpInUnsafeFn,
284    DeprecatedSafe2024,
285}
286
287#[derive(Debug)]
288pub struct MissingUnsafe {
289    pub node: InFile<ExprOrPatPtr>,
290    pub lint: UnsafeLint,
291    pub reason: UnsafetyReason,
292}
293
294#[derive(Debug)]
295pub struct MissingFields {
296    pub file: HirFileId,
297    pub field_list_parent: AstPtr<Either<ast::RecordExpr, ast::RecordPat>>,
298    pub field_list_parent_path: Option<AstPtr<ast::Path>>,
299    pub missed_fields: Vec<Name>,
300}
301
302#[derive(Debug)]
303pub struct ReplaceFilterMapNextWithFindMap {
304    pub file: HirFileId,
305    /// This expression is the whole method chain up to and including `.filter_map(..).next()`.
306    pub next_expr: AstPtr<ast::Expr>,
307}
308
309#[derive(Debug)]
310pub struct MismatchedArgCount {
311    pub call_expr: InFile<ExprOrPatPtr>,
312    pub expected: usize,
313    pub found: usize,
314}
315
316#[derive(Debug)]
317pub struct MissingMatchArms {
318    pub scrutinee_expr: InFile<AstPtr<ast::Expr>>,
319    pub uncovered_patterns: String,
320}
321
322#[derive(Debug)]
323pub struct NonExhaustiveLet {
324    pub pat: InFile<AstPtr<ast::Pat>>,
325    pub uncovered_patterns: String,
326}
327
328#[derive(Debug)]
329pub struct TypeMismatch<'db> {
330    pub expr_or_pat: InFile<ExprOrPatPtr>,
331    pub expected: Type<'db>,
332    pub actual: Type<'db>,
333}
334
335#[derive(Debug)]
336pub struct NeedMut {
337    pub local: Local,
338    pub span: InFile<SyntaxNodePtr>,
339}
340
341#[derive(Debug)]
342pub struct UnusedMut {
343    pub local: Local,
344}
345
346#[derive(Debug)]
347pub struct UnusedVariable {
348    pub local: Local,
349}
350
351#[derive(Debug)]
352pub struct MovedOutOfRef<'db> {
353    pub ty: Type<'db>,
354    pub span: InFile<SyntaxNodePtr>,
355}
356
357#[derive(Debug, PartialEq, Eq)]
358pub struct IncoherentImpl {
359    pub file_id: HirFileId,
360    pub impl_: AstPtr<ast::Impl>,
361}
362
363#[derive(Debug, PartialEq, Eq)]
364pub struct TraitImplOrphan {
365    pub file_id: HirFileId,
366    pub impl_: AstPtr<ast::Impl>,
367}
368
369// FIXME: Split this off into the corresponding 4 rustc errors
370#[derive(Debug, PartialEq, Eq)]
371pub struct TraitImplIncorrectSafety {
372    pub file_id: HirFileId,
373    pub impl_: AstPtr<ast::Impl>,
374    pub should_be_safe: bool,
375}
376
377#[derive(Debug, PartialEq, Eq)]
378pub struct TraitImplMissingAssocItems {
379    pub file_id: HirFileId,
380    pub impl_: AstPtr<ast::Impl>,
381    pub missing: Vec<(Name, AssocItem)>,
382}
383
384#[derive(Debug, PartialEq, Eq)]
385pub struct TraitImplRedundantAssocItems {
386    pub file_id: HirFileId,
387    pub trait_: Trait,
388    pub impl_: AstPtr<ast::Impl>,
389    pub assoc_item: (Name, AssocItem),
390}
391
392#[derive(Debug)]
393pub struct RemoveTrailingReturn {
394    pub return_expr: InFile<AstPtr<ast::ReturnExpr>>,
395}
396
397#[derive(Debug)]
398pub struct RemoveUnnecessaryElse {
399    pub if_expr: InFile<AstPtr<ast::IfExpr>>,
400}
401
402#[derive(Debug)]
403pub struct CastToUnsized<'db> {
404    pub expr: InFile<ExprOrPatPtr>,
405    pub cast_ty: Type<'db>,
406}
407
408#[derive(Debug)]
409pub struct InvalidCast<'db> {
410    pub expr: InFile<ExprOrPatPtr>,
411    pub error: CastError,
412    pub expr_ty: Type<'db>,
413    pub cast_ty: Type<'db>,
414}
415
416#[derive(Debug)]
417pub struct GenericArgsProhibited {
418    pub args: InFile<AstPtr<Either<ast::GenericArgList, ast::ParenthesizedArgList>>>,
419    pub reason: GenericArgsProhibitedReason,
420}
421
422#[derive(Debug)]
423pub struct ParenthesizedGenericArgsWithoutFnTrait {
424    pub args: InFile<AstPtr<ast::ParenthesizedArgList>>,
425}
426
427#[derive(Debug)]
428pub struct BadRtn {
429    pub rtn: InFile<AstPtr<ast::ReturnTypeSyntax>>,
430}
431
432#[derive(Debug)]
433pub struct IncorrectGenericsLen {
434    /// Points at the name if there are no generics.
435    pub generics_or_segment: InFile<AstPtr<Either<ast::GenericArgList, ast::NameRef>>>,
436    pub kind: IncorrectGenericsLenKind,
437    pub provided: u32,
438    pub expected: u32,
439    pub def: GenericDef,
440}
441
442#[derive(Debug)]
443pub struct MissingLifetime {
444    /// Points at the name if there are no generics.
445    pub generics_or_segment: InFile<AstPtr<Either<ast::GenericArgList, ast::NameRef>>>,
446    pub expected: u32,
447    pub def: GenericDef,
448}
449
450#[derive(Debug)]
451pub struct ElidedLifetimesInPath {
452    /// Points at the name if there are no generics.
453    pub generics_or_segment: InFile<AstPtr<Either<ast::GenericArgList, ast::NameRef>>>,
454    pub expected: u32,
455    pub def: GenericDef,
456    pub hard_error: bool,
457}
458
459#[derive(Debug, Clone, Copy, PartialEq, Eq)]
460pub enum GenericArgKind {
461    Lifetime,
462    Type,
463    Const,
464}
465
466impl GenericArgKind {
467    fn from_id(id: GenericParamId) -> Self {
468        match id {
469            GenericParamId::TypeParamId(_) => GenericArgKind::Type,
470            GenericParamId::ConstParamId(_) => GenericArgKind::Const,
471            GenericParamId::LifetimeParamId(_) => GenericArgKind::Lifetime,
472        }
473    }
474}
475
476#[derive(Debug)]
477pub struct IncorrectGenericsOrder {
478    pub provided_arg: InFile<AstPtr<ast::GenericArg>>,
479    pub expected_kind: GenericArgKind,
480}
481
482impl<'db> AnyDiagnostic<'db> {
483    pub(crate) fn body_validation_diagnostic(
484        db: &'db dyn HirDatabase,
485        diagnostic: BodyValidationDiagnostic,
486        source_map: &hir_def::expr_store::BodySourceMap,
487    ) -> Option<AnyDiagnostic<'db>> {
488        match diagnostic {
489            BodyValidationDiagnostic::RecordMissingFields { record, variant, missed_fields } => {
490                let variant_data = variant.fields(db);
491                let missed_fields = missed_fields
492                    .into_iter()
493                    .map(|idx| variant_data.fields()[idx].name.clone())
494                    .collect();
495
496                let record = match record {
497                    Either::Left(record_expr) => source_map.expr_syntax(record_expr).ok()?,
498                    Either::Right(record_pat) => source_map.pat_syntax(record_pat).ok()?,
499                };
500                let file = record.file_id;
501                let root = record.file_syntax(db);
502                match record.value.to_node(&root) {
503                    Either::Left(ast::Expr::RecordExpr(record_expr)) => {
504                        if record_expr.record_expr_field_list().is_some() {
505                            let field_list_parent_path =
506                                record_expr.path().map(|path| AstPtr::new(&path));
507                            return Some(
508                                MissingFields {
509                                    file,
510                                    field_list_parent: AstPtr::new(&Either::Left(record_expr)),
511                                    field_list_parent_path,
512                                    missed_fields,
513                                }
514                                .into(),
515                            );
516                        }
517                    }
518                    Either::Right(ast::Pat::RecordPat(record_pat)) => {
519                        if record_pat.record_pat_field_list().is_some() {
520                            let field_list_parent_path =
521                                record_pat.path().map(|path| AstPtr::new(&path));
522                            return Some(
523                                MissingFields {
524                                    file,
525                                    field_list_parent: AstPtr::new(&Either::Right(record_pat)),
526                                    field_list_parent_path,
527                                    missed_fields,
528                                }
529                                .into(),
530                            );
531                        }
532                    }
533                    _ => {}
534                }
535            }
536            BodyValidationDiagnostic::ReplaceFilterMapNextWithFindMap { method_call_expr } => {
537                if let Ok(next_source_ptr) = source_map.expr_syntax(method_call_expr) {
538                    return Some(
539                        ReplaceFilterMapNextWithFindMap {
540                            file: next_source_ptr.file_id,
541                            next_expr: next_source_ptr.value.cast()?,
542                        }
543                        .into(),
544                    );
545                }
546            }
547            BodyValidationDiagnostic::MissingMatchArms { match_expr, uncovered_patterns } => {
548                match source_map.expr_syntax(match_expr) {
549                    Ok(source_ptr) => {
550                        let root = source_ptr.file_syntax(db);
551                        if let Either::Left(ast::Expr::MatchExpr(match_expr)) =
552                            &source_ptr.value.to_node(&root)
553                        {
554                            match match_expr.expr() {
555                                Some(scrut_expr) if match_expr.match_arm_list().is_some() => {
556                                    return Some(
557                                        MissingMatchArms {
558                                            scrutinee_expr: InFile::new(
559                                                source_ptr.file_id,
560                                                AstPtr::new(&scrut_expr),
561                                            ),
562                                            uncovered_patterns,
563                                        }
564                                        .into(),
565                                    );
566                                }
567                                _ => {}
568                            }
569                        }
570                    }
571                    Err(SyntheticSyntax) => (),
572                }
573            }
574            BodyValidationDiagnostic::NonExhaustiveLet { pat, uncovered_patterns } => {
575                match source_map.pat_syntax(pat) {
576                    Ok(source_ptr) => {
577                        if let Some(ast_pat) = source_ptr.value.cast::<ast::Pat>() {
578                            return Some(
579                                NonExhaustiveLet {
580                                    pat: InFile::new(source_ptr.file_id, ast_pat),
581                                    uncovered_patterns,
582                                }
583                                .into(),
584                            );
585                        }
586                    }
587                    Err(SyntheticSyntax) => {}
588                }
589            }
590            BodyValidationDiagnostic::RemoveTrailingReturn { return_expr } => {
591                if let Ok(source_ptr) = source_map.expr_syntax(return_expr) {
592                    // Filters out desugared return expressions (e.g. desugared try operators).
593                    if let Some(ptr) = source_ptr.value.cast::<ast::ReturnExpr>() {
594                        return Some(
595                            RemoveTrailingReturn {
596                                return_expr: InFile::new(source_ptr.file_id, ptr),
597                            }
598                            .into(),
599                        );
600                    }
601                }
602            }
603            BodyValidationDiagnostic::RemoveUnnecessaryElse { if_expr } => {
604                if let Ok(source_ptr) = source_map.expr_syntax(if_expr)
605                    && let Some(ptr) = source_ptr.value.cast::<ast::IfExpr>()
606                {
607                    return Some(
608                        RemoveUnnecessaryElse { if_expr: InFile::new(source_ptr.file_id, ptr) }
609                            .into(),
610                    );
611                }
612            }
613        }
614        None
615    }
616
617    pub(crate) fn inference_diagnostic(
618        db: &'db dyn HirDatabase,
619        def: DefWithBodyId,
620        d: &InferenceDiagnostic<'db>,
621        source_map: &hir_def::expr_store::BodySourceMap,
622        sig_map: &hir_def::expr_store::ExpressionStoreSourceMap,
623    ) -> Option<AnyDiagnostic<'db>> {
624        let expr_syntax = |expr| {
625            source_map
626                .expr_syntax(expr)
627                .inspect_err(|_| stdx::never!("inference diagnostic in desugared expr"))
628                .ok()
629        };
630        let pat_syntax = |pat| {
631            source_map
632                .pat_syntax(pat)
633                .inspect_err(|_| stdx::never!("inference diagnostic in desugared pattern"))
634                .ok()
635        };
636        let expr_or_pat_syntax = |id| match id {
637            ExprOrPatId::ExprId(expr) => expr_syntax(expr),
638            ExprOrPatId::PatId(pat) => pat_syntax(pat),
639        };
640        Some(match d {
641            &InferenceDiagnostic::NoSuchField { field: expr, private, variant } => {
642                let expr_or_pat = match expr {
643                    ExprOrPatId::ExprId(expr) => {
644                        source_map.field_syntax(expr).map(AstPtr::wrap_left)
645                    }
646                    ExprOrPatId::PatId(pat) => source_map.pat_field_syntax(pat),
647                };
648                let private = private.map(|id| Field { id, parent: variant.into() });
649                NoSuchField { field: expr_or_pat, private, variant }.into()
650            }
651            &InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => {
652                MismatchedArgCount { call_expr: expr_syntax(call_expr)?, expected, found }.into()
653            }
654            &InferenceDiagnostic::PrivateField { expr, field } => {
655                let expr = expr_syntax(expr)?;
656                let field = field.into();
657                PrivateField { expr, field }.into()
658            }
659            &InferenceDiagnostic::PrivateAssocItem { id, item } => {
660                let expr_or_pat = expr_or_pat_syntax(id)?;
661                let item = item.into();
662                PrivateAssocItem { expr_or_pat, item }.into()
663            }
664            InferenceDiagnostic::ExpectedFunction { call_expr, found } => {
665                let call_expr = expr_syntax(*call_expr)?;
666                ExpectedFunction { call: call_expr, found: Type::new(db, def, *found) }.into()
667            }
668            InferenceDiagnostic::UnresolvedField {
669                expr,
670                receiver,
671                name,
672                method_with_same_name_exists,
673            } => {
674                let expr = expr_syntax(*expr)?;
675                UnresolvedField {
676                    expr,
677                    name: name.clone(),
678                    receiver: Type::new(db, def, *receiver),
679                    method_with_same_name_exists: *method_with_same_name_exists,
680                }
681                .into()
682            }
683            InferenceDiagnostic::UnresolvedMethodCall {
684                expr,
685                receiver,
686                name,
687                field_with_same_name,
688                assoc_func_with_same_name,
689            } => {
690                let expr = expr_syntax(*expr)?;
691                UnresolvedMethodCall {
692                    expr,
693                    name: name.clone(),
694                    receiver: Type::new(db, def, *receiver),
695                    field_with_same_name: (*field_with_same_name).map(|ty| Type::new(db, def, ty)),
696                    assoc_func_with_same_name: assoc_func_with_same_name.map(Into::into),
697                }
698                .into()
699            }
700            &InferenceDiagnostic::UnresolvedAssocItem { id } => {
701                let expr_or_pat = expr_or_pat_syntax(id)?;
702                UnresolvedAssocItem { expr_or_pat }.into()
703            }
704            &InferenceDiagnostic::UnresolvedIdent { id } => {
705                let node = match id {
706                    ExprOrPatId::ExprId(id) => match source_map.expr_syntax(id) {
707                        Ok(syntax) => syntax.map(|it| (it, None)),
708                        Err(SyntheticSyntax) => source_map
709                            .format_args_implicit_capture(id)?
710                            .map(|(node, range)| (node.wrap_left(), Some(range))),
711                    },
712                    ExprOrPatId::PatId(id) => pat_syntax(id)?.map(|it| (it, None)),
713                };
714                UnresolvedIdent { node }.into()
715            }
716            &InferenceDiagnostic::BreakOutsideOfLoop { expr, is_break, bad_value_break } => {
717                let expr = expr_syntax(expr)?;
718                BreakOutsideOfLoop { expr, is_break, bad_value_break }.into()
719            }
720            InferenceDiagnostic::TypedHole { expr, expected } => {
721                let expr = expr_syntax(*expr)?;
722                TypedHole { expr, expected: Type::new(db, def, *expected) }.into()
723            }
724            &InferenceDiagnostic::MismatchedTupleStructPatArgCount { pat, expected, found } => {
725                let expr_or_pat = match pat {
726                    ExprOrPatId::ExprId(expr) => expr_syntax(expr)?,
727                    ExprOrPatId::PatId(pat) => {
728                        let InFile { file_id, value } = pat_syntax(pat)?;
729
730                        // cast from Either<Pat, SelfParam> -> Either<_, Pat>
731                        let ptr = AstPtr::try_from_raw(value.syntax_node_ptr())?;
732                        InFile { file_id, value: ptr }
733                    }
734                };
735                MismatchedTupleStructPatArgCount { expr_or_pat, expected, found }.into()
736            }
737            InferenceDiagnostic::CastToUnsized { expr, cast_ty } => {
738                let expr = expr_syntax(*expr)?;
739                CastToUnsized { expr, cast_ty: Type::new(db, def, *cast_ty) }.into()
740            }
741            InferenceDiagnostic::InvalidCast { expr, error, expr_ty, cast_ty } => {
742                let expr = expr_syntax(*expr)?;
743                let expr_ty = Type::new(db, def, *expr_ty);
744                let cast_ty = Type::new(db, def, *cast_ty);
745                InvalidCast { expr, error: *error, expr_ty, cast_ty }.into()
746            }
747            InferenceDiagnostic::TyDiagnostic { source, diag } => {
748                let source_map = match source {
749                    InferenceTyDiagnosticSource::Body => source_map,
750                    InferenceTyDiagnosticSource::Signature => sig_map,
751                };
752                Self::ty_diagnostic(diag, source_map, db)?
753            }
754            InferenceDiagnostic::PathDiagnostic { node, diag } => {
755                let source = expr_or_pat_syntax(*node)?;
756                let syntax = source.value.to_node(&db.parse_or_expand(source.file_id));
757                let path = match_ast! {
758                    match (syntax.syntax()) {
759                        ast::RecordExpr(it) => it.path()?,
760                        ast::RecordPat(it) => it.path()?,
761                        ast::TupleStructPat(it) => it.path()?,
762                        ast::PathExpr(it) => it.path()?,
763                        ast::PathPat(it) => it.path()?,
764                        _ => return None,
765                    }
766                };
767                Self::path_diagnostic(diag, source.with_value(path))?
768            }
769            &InferenceDiagnostic::MethodCallIncorrectGenericsLen {
770                expr,
771                provided_count,
772                expected_count,
773                kind,
774                def,
775            } => {
776                let syntax = expr_syntax(expr)?;
777                let file_id = syntax.file_id;
778                let syntax =
779                    syntax.with_value(syntax.value.cast::<ast::MethodCallExpr>()?).to_node(db);
780                let generics_or_name = syntax
781                    .generic_arg_list()
782                    .map(Either::Left)
783                    .or_else(|| syntax.name_ref().map(Either::Right))?;
784                let generics_or_name = InFile::new(file_id, AstPtr::new(&generics_or_name));
785                IncorrectGenericsLen {
786                    generics_or_segment: generics_or_name,
787                    kind,
788                    provided: provided_count,
789                    expected: expected_count,
790                    def: def.into(),
791                }
792                .into()
793            }
794            &InferenceDiagnostic::MethodCallIncorrectGenericsOrder {
795                expr,
796                param_id,
797                arg_idx,
798                has_self_arg,
799            } => {
800                let syntax = expr_syntax(expr)?;
801                let file_id = syntax.file_id;
802                let syntax =
803                    syntax.with_value(syntax.value.cast::<ast::MethodCallExpr>()?).to_node(db);
804                let generic_args = syntax.generic_arg_list()?;
805                let provided_arg = hir_generic_arg_to_ast(&generic_args, arg_idx, has_self_arg)?;
806                let provided_arg = InFile::new(file_id, AstPtr::new(&provided_arg));
807                let expected_kind = GenericArgKind::from_id(param_id);
808                IncorrectGenericsOrder { provided_arg, expected_kind }.into()
809            }
810        })
811    }
812
813    fn path_diagnostic(
814        diag: &PathLoweringDiagnostic,
815        path: InFile<ast::Path>,
816    ) -> Option<AnyDiagnostic<'db>> {
817        Some(match *diag {
818            PathLoweringDiagnostic::GenericArgsProhibited { segment, reason } => {
819                let segment = hir_segment_to_ast_segment(&path.value, segment)?;
820
821                if let Some(rtn) = segment.return_type_syntax() {
822                    // RTN errors are emitted as `GenericArgsProhibited` or `ParenthesizedGenericArgsWithoutFnTrait`.
823                    return Some(BadRtn { rtn: path.with_value(AstPtr::new(&rtn)) }.into());
824                }
825
826                let args = if let Some(generics) = segment.generic_arg_list() {
827                    AstPtr::new(&generics).wrap_left()
828                } else {
829                    AstPtr::new(&segment.parenthesized_arg_list()?).wrap_right()
830                };
831                let args = path.with_value(args);
832                GenericArgsProhibited { args, reason }.into()
833            }
834            PathLoweringDiagnostic::ParenthesizedGenericArgsWithoutFnTrait { segment } => {
835                let segment = hir_segment_to_ast_segment(&path.value, segment)?;
836
837                if let Some(rtn) = segment.return_type_syntax() {
838                    // RTN errors are emitted as `GenericArgsProhibited` or `ParenthesizedGenericArgsWithoutFnTrait`.
839                    return Some(BadRtn { rtn: path.with_value(AstPtr::new(&rtn)) }.into());
840                }
841
842                let args = AstPtr::new(&segment.parenthesized_arg_list()?);
843                let args = path.with_value(args);
844                ParenthesizedGenericArgsWithoutFnTrait { args }.into()
845            }
846            PathLoweringDiagnostic::IncorrectGenericsLen {
847                generics_source,
848                provided_count,
849                expected_count,
850                kind,
851                def,
852            } => {
853                let generics_or_segment =
854                    path_generics_source_to_ast(&path.value, generics_source)?;
855                let generics_or_segment = path.with_value(AstPtr::new(&generics_or_segment));
856                IncorrectGenericsLen {
857                    generics_or_segment,
858                    kind,
859                    provided: provided_count,
860                    expected: expected_count,
861                    def: def.into(),
862                }
863                .into()
864            }
865            PathLoweringDiagnostic::IncorrectGenericsOrder {
866                generics_source,
867                param_id,
868                arg_idx,
869                has_self_arg,
870            } => {
871                let generic_args =
872                    path_generics_source_to_ast(&path.value, generics_source)?.left()?;
873                let provided_arg = hir_generic_arg_to_ast(&generic_args, arg_idx, has_self_arg)?;
874                let provided_arg = path.with_value(AstPtr::new(&provided_arg));
875                let expected_kind = GenericArgKind::from_id(param_id);
876                IncorrectGenericsOrder { provided_arg, expected_kind }.into()
877            }
878            PathLoweringDiagnostic::MissingLifetime { generics_source, expected_count, def }
879            | PathLoweringDiagnostic::ElisionFailure { generics_source, expected_count, def } => {
880                let generics_or_segment =
881                    path_generics_source_to_ast(&path.value, generics_source)?;
882                let generics_or_segment = path.with_value(AstPtr::new(&generics_or_segment));
883                MissingLifetime { generics_or_segment, expected: expected_count, def: def.into() }
884                    .into()
885            }
886            PathLoweringDiagnostic::ElidedLifetimesInPath {
887                generics_source,
888                expected_count,
889                def,
890                hard_error,
891            } => {
892                let generics_or_segment =
893                    path_generics_source_to_ast(&path.value, generics_source)?;
894                let generics_or_segment = path.with_value(AstPtr::new(&generics_or_segment));
895                ElidedLifetimesInPath {
896                    generics_or_segment,
897                    expected: expected_count,
898                    def: def.into(),
899                    hard_error,
900                }
901                .into()
902            }
903        })
904    }
905
906    pub(crate) fn ty_diagnostic(
907        diag: &TyLoweringDiagnostic,
908        source_map: &ExpressionStoreSourceMap,
909        db: &'db dyn HirDatabase,
910    ) -> Option<AnyDiagnostic<'db>> {
911        let Ok(source) = source_map.type_syntax(diag.source) else {
912            stdx::never!("error on synthetic type syntax");
913            return None;
914        };
915        let syntax = || source.value.to_node(&db.parse_or_expand(source.file_id));
916        Some(match &diag.kind {
917            TyLoweringDiagnosticKind::PathDiagnostic(diag) => {
918                let ast::Type::PathType(syntax) = syntax() else { return None };
919                Self::path_diagnostic(diag, source.with_value(syntax.path()?))?
920            }
921        })
922    }
923}
924
925fn path_generics_source_to_ast(
926    path: &ast::Path,
927    generics_source: PathGenericsSource,
928) -> Option<Either<ast::GenericArgList, ast::NameRef>> {
929    Some(match generics_source {
930        PathGenericsSource::Segment(segment) => {
931            let segment = hir_segment_to_ast_segment(path, segment)?;
932            segment
933                .generic_arg_list()
934                .map(Either::Left)
935                .or_else(|| segment.name_ref().map(Either::Right))?
936        }
937        PathGenericsSource::AssocType { segment, assoc_type } => {
938            let segment = hir_segment_to_ast_segment(path, segment)?;
939            let segment_args = segment.generic_arg_list()?;
940            let assoc = hir_assoc_type_binding_to_ast(&segment_args, assoc_type)?;
941            assoc
942                .generic_arg_list()
943                .map(Either::Left)
944                .or_else(|| assoc.name_ref().map(Either::Right))?
945        }
946    })
947}