Skip to main content

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