about summary refs log tree commit diff
path: root/compiler
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-09-21 22:59:13 +0000
committerbors <bors@rust-lang.org>2023-09-21 22:59:13 +0000
commit99b63d068b0be31b4d8a28f93921b5d14f13cdc0 (patch)
treeb48f1a6e153d430af0eda54b484ca55ef338ccd0 /compiler
parentb3aa8e7168a3d940122db3561289ffbf3f587262 (diff)
parent85d61b01aea90d9201c35c4bd61a508c72807c92 (diff)
downloadrust-99b63d068b0be31b4d8a28f93921b5d14f13cdc0.tar.gz
rust-99b63d068b0be31b4d8a28f93921b5d14f13cdc0.zip
Auto merge of #115897 - eduardosm:check-fn-sig, r=compiler-errors
rustc_hir_analysis: add a helper to check function the signature mismatches

This function is now used to check `#[panic_handler]`, `start` lang item, `main`, `#[start]` and intrinsic functions.

The diagnosis produced are now closer to the ones produced by trait/impl method signature mismatch.

This is the first time I do anything with rustc_hir_analysis/rustc_hir_typeck, so comments and suggestions about things I did wrong or that could be improved will be appreciated.
Diffstat (limited to 'compiler')
-rw-r--r--compiler/rustc_hir_analysis/src/check/entry.rs54
-rw-r--r--compiler/rustc_hir_analysis/src/check/intrinsic.rs13
-rw-r--r--compiler/rustc_hir_analysis/src/check/mod.rs88
-rw-r--r--compiler/rustc_hir_analysis/src/lib.rs26
-rw-r--r--compiler/rustc_hir_typeck/messages.ftl10
-rw-r--r--compiler/rustc_hir_typeck/src/check.rs249
-rw-r--r--compiler/rustc_hir_typeck/src/errors.rs33
-rw-r--r--compiler/rustc_infer/messages.ftl6
-rw-r--r--compiler/rustc_infer/src/errors/mod.rs8
-rw-r--r--compiler/rustc_infer/src/infer/error_reporting/mod.rs6
-rw-r--r--compiler/rustc_middle/src/traits/mod.rs3
-rw-r--r--compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs1
12 files changed, 229 insertions, 268 deletions
diff --git a/compiler/rustc_hir_analysis/src/check/entry.rs b/compiler/rustc_hir_analysis/src/check/entry.rs
index cede7493093..3cd3f5bcfdd 100644
--- a/compiler/rustc_hir_analysis/src/check/entry.rs
+++ b/compiler/rustc_hir_analysis/src/check/entry.rs
@@ -11,8 +11,8 @@ use rustc_trait_selection::traits::{self, ObligationCause, ObligationCauseCode};
 
 use std::ops::Not;
 
+use super::check_function_signature;
 use crate::errors;
-use crate::require_same_types;
 
 pub(crate) fn check_for_entry_fn(tcx: TyCtxt<'_>) {
     match tcx.entry_fn(()) {
@@ -162,33 +162,33 @@ fn check_main_fn_ty(tcx: TyCtxt<'_>, main_def_id: DefId) {
             error = true;
         }
         // now we can take the return type of the given main function
-        expected_return_type = main_fnsig.output();
+        expected_return_type = norm_return_ty;
     } else {
         // standard () main return type
-        expected_return_type = ty::Binder::dummy(Ty::new_unit(tcx));
+        expected_return_type = tcx.types.unit;
     }
 
     if error {
         return;
     }
 
-    let se_ty = Ty::new_fn_ptr(
-        tcx,
-        expected_return_type.map_bound(|expected_return_type| {
-            tcx.mk_fn_sig([], expected_return_type, false, hir::Unsafety::Normal, Abi::Rust)
-        }),
-    );
+    let expected_sig = ty::Binder::dummy(tcx.mk_fn_sig(
+        [],
+        expected_return_type,
+        false,
+        hir::Unsafety::Normal,
+        Abi::Rust,
+    ));
 
-    require_same_types(
+    check_function_signature(
         tcx,
-        &ObligationCause::new(
+        ObligationCause::new(
             main_span,
             main_diagnostics_def_id,
             ObligationCauseCode::MainFunctionType,
         ),
-        param_env,
-        se_ty,
-        Ty::new_fn_ptr(tcx, main_fnsig),
+        main_def_id,
+        expected_sig,
     );
 }
 
@@ -247,27 +247,23 @@ fn check_start_fn_ty(tcx: TyCtxt<'_>, start_def_id: DefId) {
                 }
             }
 
-            let se_ty = Ty::new_fn_ptr(
-                tcx,
-                ty::Binder::dummy(tcx.mk_fn_sig(
-                    [tcx.types.isize, Ty::new_imm_ptr(tcx, Ty::new_imm_ptr(tcx, tcx.types.u8))],
-                    tcx.types.isize,
-                    false,
-                    hir::Unsafety::Normal,
-                    Abi::Rust,
-                )),
-            );
+            let expected_sig = ty::Binder::dummy(tcx.mk_fn_sig(
+                [tcx.types.isize, Ty::new_imm_ptr(tcx, Ty::new_imm_ptr(tcx, tcx.types.u8))],
+                tcx.types.isize,
+                false,
+                hir::Unsafety::Normal,
+                Abi::Rust,
+            ));
 
-            require_same_types(
+            check_function_signature(
                 tcx,
-                &ObligationCause::new(
+                ObligationCause::new(
                     start_span,
                     start_def_id,
                     ObligationCauseCode::StartFunctionType,
                 ),
-                ty::ParamEnv::empty(), // start should not have any where bounds.
-                se_ty,
-                Ty::new_fn_ptr(tcx, tcx.fn_sig(start_def_id).instantiate_identity()),
+                start_def_id.into(),
+                expected_sig,
             );
         }
         _ => {
diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs
index f89e2e5c25b..444103ffe8f 100644
--- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs
+++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs
@@ -1,11 +1,11 @@
 //! Type-checking for the rust-intrinsic and platform-intrinsic
 //! intrinsics that the compiler exposes.
 
+use crate::check::check_function_signature;
 use crate::errors::{
     UnrecognizedAtomicOperation, UnrecognizedIntrinsicFunction,
     WrongNumberOfGenericArgumentsToIntrinsic,
 };
-use crate::require_same_types;
 
 use hir::def_id::DefId;
 use rustc_errors::{struct_span_err, DiagnosticMessage};
@@ -53,15 +53,12 @@ fn equate_intrinsic_type<'tcx>(
         && gen_count_ok(own_counts.types, n_tps, "type")
         && gen_count_ok(own_counts.consts, 0, "const")
     {
-        let fty = Ty::new_fn_ptr(tcx, sig);
         let it_def_id = it.owner_id.def_id;
-        let cause = ObligationCause::new(it.span, it_def_id, ObligationCauseCode::IntrinsicType);
-        require_same_types(
+        check_function_signature(
             tcx,
-            &cause,
-            ty::ParamEnv::empty(), // FIXME: do all intrinsics have an empty param env?
-            Ty::new_fn_ptr(tcx, tcx.fn_sig(it.owner_id).instantiate_identity()),
-            fty,
+            ObligationCause::new(it.span, it_def_id, ObligationCauseCode::IntrinsicType),
+            it_def_id.into(),
+            sig,
         );
     }
 }
diff --git a/compiler/rustc_hir_analysis/src/check/mod.rs b/compiler/rustc_hir_analysis/src/check/mod.rs
index d8331661366..0cf3cee66b5 100644
--- a/compiler/rustc_hir_analysis/src/check/mod.rs
+++ b/compiler/rustc_hir_analysis/src/check/mod.rs
@@ -73,23 +73,31 @@ pub mod wfcheck;
 
 pub use check::check_abi;
 
+use std::num::NonZeroU32;
+
 use check::check_mod_item_types;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_errors::{pluralize, struct_span_err, Diagnostic, DiagnosticBuilder};
 use rustc_hir::def_id::{DefId, LocalDefId};
 use rustc_hir::intravisit::Visitor;
 use rustc_index::bit_set::BitSet;
+use rustc_infer::infer::error_reporting::ObligationCauseExt as _;
+use rustc_infer::infer::outlives::env::OutlivesEnvironment;
+use rustc_infer::infer::{self, TyCtxtInferExt as _};
+use rustc_infer::traits::ObligationCause;
 use rustc_middle::query::Providers;
+use rustc_middle::ty::error::{ExpectedFound, TypeError};
 use rustc_middle::ty::{self, Ty, TyCtxt};
 use rustc_middle::ty::{GenericArgs, GenericArgsRef};
 use rustc_session::parse::feature_err;
 use rustc_span::source_map::DUMMY_SP;
 use rustc_span::symbol::{kw, Ident};
-use rustc_span::{self, BytePos, Span, Symbol};
+use rustc_span::{self, def_id::CRATE_DEF_ID, BytePos, Span, Symbol};
 use rustc_target::abi::VariantIdx;
 use rustc_target::spec::abi::Abi;
 use rustc_trait_selection::traits::error_reporting::suggestions::ReturnsVisitor;
-use std::num::NonZeroU32;
+use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt as _;
+use rustc_trait_selection::traits::ObligationCtxt;
 
 use crate::errors;
 use crate::require_c_abi_if_c_variadic;
@@ -546,3 +554,79 @@ fn bad_non_zero_sized_fields<'tcx>(
 pub fn potentially_plural_count(count: usize, word: &str) -> String {
     format!("{} {}{}", count, word, pluralize!(count))
 }
+
+pub fn check_function_signature<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    mut cause: ObligationCause<'tcx>,
+    fn_id: DefId,
+    expected_sig: ty::PolyFnSig<'tcx>,
+) {
+    let local_id = fn_id.as_local().unwrap_or(CRATE_DEF_ID);
+
+    let param_env = ty::ParamEnv::empty();
+
+    let infcx = &tcx.infer_ctxt().build();
+    let ocx = ObligationCtxt::new(infcx);
+
+    let actual_sig = tcx.fn_sig(fn_id).instantiate_identity();
+
+    let norm_cause = ObligationCause::misc(cause.span, local_id);
+    let actual_sig = ocx.normalize(&norm_cause, param_env, actual_sig);
+
+    let expected_ty = Ty::new_fn_ptr(tcx, expected_sig);
+    let actual_ty = Ty::new_fn_ptr(tcx, actual_sig);
+
+    match ocx.eq(&cause, param_env, expected_ty, actual_ty) {
+        Ok(()) => {
+            let errors = ocx.select_all_or_error();
+            if !errors.is_empty() {
+                infcx.err_ctxt().report_fulfillment_errors(&errors);
+                return;
+            }
+        }
+        Err(err) => {
+            let err_ctxt = infcx.err_ctxt();
+            if fn_id.is_local() {
+                cause.span = extract_span_for_error_reporting(tcx, err, &cause, local_id);
+            }
+            let failure_code = cause.as_failure_code_diag(err, cause.span, vec![]);
+            let mut diag = tcx.sess.create_err(failure_code);
+            err_ctxt.note_type_err(
+                &mut diag,
+                &cause,
+                None,
+                Some(infer::ValuePairs::Sigs(ExpectedFound {
+                    expected: tcx.liberate_late_bound_regions(fn_id, expected_sig),
+                    found: tcx.liberate_late_bound_regions(fn_id, actual_sig),
+                })),
+                err,
+                false,
+                false,
+            );
+            diag.emit();
+            return;
+        }
+    }
+
+    let outlives_env = OutlivesEnvironment::new(param_env);
+    let _ = ocx.resolve_regions_and_report_errors(local_id, &outlives_env);
+
+    fn extract_span_for_error_reporting<'tcx>(
+        tcx: TyCtxt<'tcx>,
+        err: TypeError<'_>,
+        cause: &ObligationCause<'tcx>,
+        fn_id: LocalDefId,
+    ) -> rustc_span::Span {
+        let mut args = {
+            let node = tcx.hir().expect_owner(fn_id);
+            let decl = node.fn_decl().unwrap_or_else(|| bug!("expected fn decl, found {:?}", node));
+            decl.inputs.iter().map(|t| t.span).chain(std::iter::once(decl.output.span()))
+        };
+
+        match err {
+            TypeError::ArgumentMutability(i)
+            | TypeError::ArgumentSorts(ExpectedFound { .. }, i) => args.nth(i).unwrap(),
+            _ => cause.span(),
+        }
+    }
+}
diff --git a/compiler/rustc_hir_analysis/src/lib.rs b/compiler/rustc_hir_analysis/src/lib.rs
index ef788935efb..03963925d3d 100644
--- a/compiler/rustc_hir_analysis/src/lib.rs
+++ b/compiler/rustc_hir_analysis/src/lib.rs
@@ -99,7 +99,6 @@ use rustc_errors::ErrorGuaranteed;
 use rustc_errors::{DiagnosticMessage, SubdiagnosticMessage};
 use rustc_fluent_macro::fluent_messages;
 use rustc_hir as hir;
-use rustc_infer::infer::TyCtxtInferExt;
 use rustc_middle::middle;
 use rustc_middle::query::Providers;
 use rustc_middle::ty::{self, Ty, TyCtxt};
@@ -107,8 +106,7 @@ use rustc_middle::util;
 use rustc_session::parse::feature_err;
 use rustc_span::{symbol::sym, Span, DUMMY_SP};
 use rustc_target::spec::abi::Abi;
-use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt as _;
-use rustc_trait_selection::traits::{self, ObligationCause, ObligationCtxt};
+use rustc_trait_selection::traits;
 
 use astconv::{AstConv, OnlySelfBounds};
 use bounds::Bounds;
@@ -151,28 +149,6 @@ fn require_c_abi_if_c_variadic(tcx: TyCtxt<'_>, decl: &hir::FnDecl<'_>, abi: Abi
     tcx.sess.emit_err(errors::VariadicFunctionCompatibleConvention { span, conventions });
 }
 
-fn require_same_types<'tcx>(
-    tcx: TyCtxt<'tcx>,
-    cause: &ObligationCause<'tcx>,
-    param_env: ty::ParamEnv<'tcx>,
-    expected: Ty<'tcx>,
-    actual: Ty<'tcx>,
-) {
-    let infcx = &tcx.infer_ctxt().build();
-    let ocx = ObligationCtxt::new(infcx);
-    match ocx.eq(cause, param_env, expected, actual) {
-        Ok(()) => {
-            let errors = ocx.select_all_or_error();
-            if !errors.is_empty() {
-                infcx.err_ctxt().report_fulfillment_errors(&errors);
-            }
-        }
-        Err(err) => {
-            infcx.err_ctxt().report_mismatched_types(cause, expected, actual, err).emit();
-        }
-    }
-}
-
 pub fn provide(providers: &mut Providers) {
     collect::provide(providers);
     coherence::provide(providers);
diff --git a/compiler/rustc_hir_typeck/messages.ftl b/compiler/rustc_hir_typeck/messages.ftl
index 921a5f5154a..9950a226333 100644
--- a/compiler/rustc_hir_typeck/messages.ftl
+++ b/compiler/rustc_hir_typeck/messages.ftl
@@ -83,16 +83,6 @@ hir_typeck_int_to_fat_label_nightly = consider casting this expression to `*cons
 
 hir_typeck_invalid_callee = expected function, found {$ty}
 
-hir_typeck_lang_start_expected_sig_note = the `start` lang item should have the signature `fn(fn() -> T, isize, *const *const u8, u8) -> isize`
-hir_typeck_lang_start_incorrect_number_params = incorrect number of parameters for the `start` lang item
-hir_typeck_lang_start_incorrect_number_params_note_expected_count = the `start` lang item should have four parameters, but found {$found_param_count}
-
-hir_typeck_lang_start_incorrect_param = parameter {$param_num} of the `start` lang item is incorrect
-    .suggestion = change the type from `{$found_ty}` to `{$expected_ty}`
-
-hir_typeck_lang_start_incorrect_ret_ty = the return type of the `start` lang item is incorrect
-    .suggestion = change the type from `{$found_ty}` to `{$expected_ty}`
-
 hir_typeck_lossy_provenance_int2ptr =
     strict provenance disallows casting integer `{$expr_ty}` to pointer `{$cast_ty}`
     .suggestion = use `.with_addr()` to adjust a valid pointer in the same allocation, to this address
diff --git a/compiler/rustc_hir_typeck/src/check.rs b/compiler/rustc_hir_typeck/src/check.rs
index 1fc1e5aca2b..041cc1abd54 100644
--- a/compiler/rustc_hir_typeck/src/check.rs
+++ b/compiler/rustc_hir_typeck/src/check.rs
@@ -1,7 +1,6 @@
+use std::cell::RefCell;
+
 use crate::coercion::CoerceMany;
-use crate::errors::{
-    LangStartIncorrectNumberArgs, LangStartIncorrectParam, LangStartIncorrectRetTy,
-};
 use crate::gather_locals::GatherLocalsVisitor;
 use crate::FnCtxt;
 use crate::GeneratorTypes;
@@ -9,14 +8,15 @@ use rustc_hir as hir;
 use rustc_hir::def::DefKind;
 use rustc_hir::intravisit::Visitor;
 use rustc_hir::lang_items::LangItem;
-use rustc_hir_analysis::check::fn_maybe_err;
+use rustc_hir_analysis::check::{check_function_signature, fn_maybe_err};
 use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
 use rustc_infer::infer::RegionVariableOrigin;
 use rustc_middle::ty::{self, Binder, Ty, TyCtxt};
 use rustc_span::def_id::LocalDefId;
+use rustc_span::symbol::sym;
 use rustc_target::spec::abi::Abi;
 use rustc_trait_selection::traits;
-use std::cell::RefCell;
+use rustc_trait_selection::traits::{ObligationCause, ObligationCauseCode};
 
 /// Helper used for fns and closures. Does the grungy work of checking a function
 /// body and returns the function context used for that purpose, since in the case of a fn item
@@ -166,52 +166,17 @@ pub(super) fn check_fn<'a, 'tcx>(
     if let Some(panic_impl_did) = tcx.lang_items().panic_impl()
         && panic_impl_did == fn_def_id.to_def_id()
     {
-        check_panic_info_fn(tcx, panic_impl_did.expect_local(), fn_sig, decl, declared_ret_ty);
+        check_panic_info_fn(tcx, panic_impl_did.expect_local(), fn_sig);
     }
 
     if let Some(lang_start_defid) = tcx.lang_items().start_fn() && lang_start_defid == fn_def_id.to_def_id() {
-        check_lang_start_fn(tcx, fn_sig, decl, fn_def_id);
+        check_lang_start_fn(tcx, fn_sig, fn_def_id);
     }
 
     gen_ty
 }
 
-fn check_panic_info_fn(
-    tcx: TyCtxt<'_>,
-    fn_id: LocalDefId,
-    fn_sig: ty::FnSig<'_>,
-    decl: &hir::FnDecl<'_>,
-    declared_ret_ty: Ty<'_>,
-) {
-    let Some(panic_info_did) = tcx.lang_items().panic_info() else {
-        tcx.sess.err("language item required, but not found: `panic_info`");
-        return;
-    };
-
-    if *declared_ret_ty.kind() != ty::Never {
-        tcx.sess.span_err(decl.output.span(), "return type should be `!`");
-    }
-
-    let inputs = fn_sig.inputs();
-    if inputs.len() != 1 {
-        tcx.sess.span_err(tcx.def_span(fn_id), "function should have one argument");
-        return;
-    }
-
-    let arg_is_panic_info = match *inputs[0].kind() {
-        ty::Ref(region, ty, mutbl) => match *ty.kind() {
-            ty::Adt(ref adt, _) => {
-                adt.did() == panic_info_did && mutbl.is_not() && !region.is_static()
-            }
-            _ => false,
-        },
-        _ => false,
-    };
-
-    if !arg_is_panic_info {
-        tcx.sess.span_err(decl.inputs[0].span, "argument should be `&PanicInfo`");
-    }
-
+fn check_panic_info_fn(tcx: TyCtxt<'_>, fn_id: LocalDefId, fn_sig: ty::FnSig<'_>) {
     let DefKind::Fn = tcx.def_kind(fn_id) else {
         let span = tcx.def_span(fn_id);
         tcx.sess.span_err(span, "should be a function");
@@ -227,125 +192,87 @@ fn check_panic_info_fn(
         let span = tcx.def_span(fn_id);
         tcx.sess.span_err(span, "should have no const parameters");
     }
-}
-
-fn check_lang_start_fn<'tcx>(
-    tcx: TyCtxt<'tcx>,
-    fn_sig: ty::FnSig<'tcx>,
-    decl: &'tcx hir::FnDecl<'tcx>,
-    def_id: LocalDefId,
-) {
-    let inputs = fn_sig.inputs();
-
-    let arg_count = inputs.len();
-    if arg_count != 4 {
-        tcx.sess.emit_err(LangStartIncorrectNumberArgs {
-            params_span: tcx.def_span(def_id),
-            found_param_count: arg_count,
-        });
-    }
 
-    // only check args if they should exist by checking the count
-    // note: this does not handle args being shifted or their order swapped very nicely
-    // but it's a lang item, users shouldn't frequently encounter this
-
-    // first arg is `main: fn() -> T`
-    if let Some(&main_arg) = inputs.get(0) {
-        // make a Ty for the generic on the fn for diagnostics
-        // FIXME: make the lang item generic checks check for the right generic *kind*
-        // for example `start`'s generic should be a type parameter
-        let generics = tcx.generics_of(def_id);
-        let fn_generic = generics.param_at(0, tcx);
-        let generic_ty = Ty::new_param(tcx, fn_generic.index, fn_generic.name);
-        let expected_fn_sig =
-            tcx.mk_fn_sig([], generic_ty, false, hir::Unsafety::Normal, Abi::Rust);
-        let expected_ty = Ty::new_fn_ptr(tcx, Binder::dummy(expected_fn_sig));
-
-        // we emit the same error to suggest changing the arg no matter what's wrong with the arg
-        let emit_main_fn_arg_err = || {
-            tcx.sess.emit_err(LangStartIncorrectParam {
-                param_span: decl.inputs[0].span,
-                param_num: 1,
-                expected_ty: expected_ty,
-                found_ty: main_arg,
-            });
-        };
-
-        if let ty::FnPtr(main_fn_sig) = main_arg.kind() {
-            let main_fn_inputs = main_fn_sig.inputs();
-            if main_fn_inputs.iter().count() != 0 {
-                emit_main_fn_arg_err();
-            }
-
-            let output = main_fn_sig.output();
-            output.map_bound(|ret_ty| {
-                // if the output ty is a generic, it's probably the right one
-                if !matches!(ret_ty.kind(), ty::Param(_)) {
-                    emit_main_fn_arg_err();
-                }
-            });
-        } else {
-            emit_main_fn_arg_err();
-        }
-    }
-
-    // second arg is isize
-    if let Some(&argc_arg) = inputs.get(1) {
-        if argc_arg != tcx.types.isize {
-            tcx.sess.emit_err(LangStartIncorrectParam {
-                param_span: decl.inputs[1].span,
-                param_num: 2,
-                expected_ty: tcx.types.isize,
-                found_ty: argc_arg,
-            });
-        }
-    }
-
-    // third arg is `*const *const u8`
-    if let Some(&argv_arg) = inputs.get(2) {
-        let mut argv_is_okay = false;
-        if let ty::RawPtr(outer_ptr) = argv_arg.kind() {
-            if outer_ptr.mutbl.is_not() {
-                if let ty::RawPtr(inner_ptr) = outer_ptr.ty.kind() {
-                    if inner_ptr.mutbl.is_not() && inner_ptr.ty == tcx.types.u8 {
-                        argv_is_okay = true;
-                    }
-                }
-            }
-        }
-
-        if !argv_is_okay {
-            let inner_ptr_ty =
-                Ty::new_ptr(tcx, ty::TypeAndMut { mutbl: hir::Mutability::Not, ty: tcx.types.u8 });
-            let expected_ty =
-                Ty::new_ptr(tcx, ty::TypeAndMut { mutbl: hir::Mutability::Not, ty: inner_ptr_ty });
-            tcx.sess.emit_err(LangStartIncorrectParam {
-                param_span: decl.inputs[2].span,
-                param_num: 3,
-                expected_ty,
-                found_ty: argv_arg,
-            });
-        }
-    }
+    let Some(panic_info_did) = tcx.lang_items().panic_info() else {
+        tcx.sess.err("language item required, but not found: `panic_info`");
+        return;
+    };
 
-    // fourth arg is `sigpipe: u8`
-    if let Some(&sigpipe_arg) = inputs.get(3) {
-        if sigpipe_arg != tcx.types.u8 {
-            tcx.sess.emit_err(LangStartIncorrectParam {
-                param_span: decl.inputs[3].span,
-                param_num: 4,
-                expected_ty: tcx.types.u8,
-                found_ty: sigpipe_arg,
-            });
-        }
-    }
+    // build type `for<'a, 'b> fn(&'a PanicInfo<'b>) -> !`
+    let panic_info_ty = tcx.type_of(panic_info_did).instantiate(
+        tcx,
+        &[ty::GenericArg::from(ty::Region::new_late_bound(
+            tcx,
+            ty::INNERMOST,
+            ty::BoundRegion { var: ty::BoundVar::from_u32(1), kind: ty::BrAnon(None) },
+        ))],
+    );
+    let panic_info_ref_ty = Ty::new_imm_ref(
+        tcx,
+        ty::Region::new_late_bound(
+            tcx,
+            ty::INNERMOST,
+            ty::BoundRegion { var: ty::BoundVar::from_u32(0), kind: ty::BrAnon(None) },
+        ),
+        panic_info_ty,
+    );
+
+    let bounds = tcx.mk_bound_variable_kinds(&[
+        ty::BoundVariableKind::Region(ty::BrAnon(None)),
+        ty::BoundVariableKind::Region(ty::BrAnon(None)),
+    ]);
+    let expected_sig = ty::Binder::bind_with_vars(
+        tcx.mk_fn_sig([panic_info_ref_ty], tcx.types.never, false, fn_sig.unsafety, Abi::Rust),
+        bounds,
+    );
+
+    check_function_signature(
+        tcx,
+        ObligationCause::new(
+            tcx.def_span(fn_id),
+            fn_id,
+            ObligationCauseCode::LangFunctionType(sym::panic_impl),
+        ),
+        fn_id.into(),
+        expected_sig,
+    );
+}
 
-    // output type is isize
-    if fn_sig.output() != tcx.types.isize {
-        tcx.sess.emit_err(LangStartIncorrectRetTy {
-            ret_span: decl.output.span(),
-            expected_ty: tcx.types.isize,
-            found_ty: fn_sig.output(),
-        });
-    }
+fn check_lang_start_fn<'tcx>(tcx: TyCtxt<'tcx>, fn_sig: ty::FnSig<'tcx>, def_id: LocalDefId) {
+    // build type `fn(main: fn() -> T, argc: isize, argv: *const *const u8, sigpipe: u8)`
+
+    // make a Ty for the generic on the fn for diagnostics
+    // FIXME: make the lang item generic checks check for the right generic *kind*
+    // for example `start`'s generic should be a type parameter
+    let generics = tcx.generics_of(def_id);
+    let fn_generic = generics.param_at(0, tcx);
+    let generic_ty = Ty::new_param(tcx, fn_generic.index, fn_generic.name);
+    let main_fn_ty = Ty::new_fn_ptr(
+        tcx,
+        Binder::dummy(tcx.mk_fn_sig([], generic_ty, false, hir::Unsafety::Normal, Abi::Rust)),
+    );
+
+    let expected_sig = ty::Binder::dummy(tcx.mk_fn_sig(
+        [
+            main_fn_ty,
+            tcx.types.isize,
+            Ty::new_imm_ptr(tcx, Ty::new_imm_ptr(tcx, tcx.types.u8)),
+            tcx.types.u8,
+        ],
+        tcx.types.isize,
+        false,
+        fn_sig.unsafety,
+        Abi::Rust,
+    ));
+
+    check_function_signature(
+        tcx,
+        ObligationCause::new(
+            tcx.def_span(def_id),
+            def_id,
+            ObligationCauseCode::LangFunctionType(sym::start),
+        ),
+        def_id.into(),
+        expected_sig,
+    );
 }
diff --git a/compiler/rustc_hir_typeck/src/errors.rs b/compiler/rustc_hir_typeck/src/errors.rs
index 7152585d440..1526988fbd9 100644
--- a/compiler/rustc_hir_typeck/src/errors.rs
+++ b/compiler/rustc_hir_typeck/src/errors.rs
@@ -236,39 +236,6 @@ impl AddToDiagnostic for TypeMismatchFruTypo {
     }
 }
 
-#[derive(Diagnostic)]
-#[diag(hir_typeck_lang_start_incorrect_number_params)]
-#[note(hir_typeck_lang_start_incorrect_number_params_note_expected_count)]
-#[note(hir_typeck_lang_start_expected_sig_note)]
-pub struct LangStartIncorrectNumberArgs {
-    #[primary_span]
-    pub params_span: Span,
-    pub found_param_count: usize,
-}
-
-#[derive(Diagnostic)]
-#[diag(hir_typeck_lang_start_incorrect_param)]
-pub struct LangStartIncorrectParam<'tcx> {
-    #[primary_span]
-    #[suggestion(style = "short", code = "{expected_ty}", applicability = "machine-applicable")]
-    pub param_span: Span,
-
-    pub param_num: usize,
-    pub expected_ty: Ty<'tcx>,
-    pub found_ty: Ty<'tcx>,
-}
-
-#[derive(Diagnostic)]
-#[diag(hir_typeck_lang_start_incorrect_ret_ty)]
-pub struct LangStartIncorrectRetTy<'tcx> {
-    #[primary_span]
-    #[suggestion(style = "short", code = "{expected_ty}", applicability = "machine-applicable")]
-    pub ret_span: Span,
-
-    pub expected_ty: Ty<'tcx>,
-    pub found_ty: Ty<'tcx>,
-}
-
 #[derive(LintDiagnostic)]
 #[diag(hir_typeck_lossy_provenance_int2ptr)]
 #[help]
diff --git a/compiler/rustc_infer/messages.ftl b/compiler/rustc_infer/messages.ftl
index 4d0e7706367..46558997f72 100644
--- a/compiler/rustc_infer/messages.ftl
+++ b/compiler/rustc_infer/messages.ftl
@@ -198,6 +198,10 @@ infer_nothing = {""}
 infer_oc_cant_coerce = cannot coerce intrinsics to function pointers
 infer_oc_closure_selfref = closure/generator type that references itself
 infer_oc_const_compat = const not compatible with trait
+infer_oc_fn_lang_correct_type = {$lang_item_name ->
+        [panic_impl] `#[panic_handler]`
+        *[lang_item_name] lang item `{$lang_item_name}`
+    } function has wrong type
 infer_oc_fn_main_correct_type = `main` function has wrong type
 infer_oc_fn_start_correct_type = `#[start]` function has wrong type
 infer_oc_generic = mismatched types
@@ -337,6 +341,7 @@ infer_subtype = ...so that the {$requirement ->
     [no_else] `if` missing an `else` returns `()`
     [fn_main_correct_type] `main` function has the correct type
     [fn_start_correct_type] `#[start]` function has the correct type
+    [fn_lang_correct_type] lang item function has the correct type
     [intrinsic_correct_type] intrinsic has the correct type
     [method_correct_type] method receiver has the correct type
     *[other] types are compatible
@@ -350,6 +355,7 @@ infer_subtype_2 = ...so that {$requirement ->
     [no_else] `if` missing an `else` returns `()`
     [fn_main_correct_type] `main` function has the correct type
     [fn_start_correct_type] `#[start]` function has the correct type
+    [fn_lang_correct_type] lang item function has the correct type
     [intrinsic_correct_type] intrinsic has the correct type
     [method_correct_type] method receiver has the correct type
     *[other] types are compatible
diff --git a/compiler/rustc_infer/src/errors/mod.rs b/compiler/rustc_infer/src/errors/mod.rs
index a7e045e1e89..38910e45ecc 100644
--- a/compiler/rustc_infer/src/errors/mod.rs
+++ b/compiler/rustc_infer/src/errors/mod.rs
@@ -1463,6 +1463,14 @@ pub enum ObligationCauseFailureCode {
         #[subdiagnostic]
         subdiags: Vec<TypeErrorAdditionalDiags>,
     },
+    #[diag(infer_oc_fn_lang_correct_type, code = "E0308")]
+    FnLangCorrectType {
+        #[primary_span]
+        span: Span,
+        #[subdiagnostic]
+        subdiags: Vec<TypeErrorAdditionalDiags>,
+        lang_item_name: Symbol,
+    },
     #[diag(infer_oc_intrinsic_correct_type, code = "E0308")]
     IntrinsicCorrectType {
         #[primary_span]
diff --git a/compiler/rustc_infer/src/infer/error_reporting/mod.rs b/compiler/rustc_infer/src/infer/error_reporting/mod.rs
index 6dba5d2945d..2ca5b7d657d 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/mod.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/mod.rs
@@ -2927,6 +2927,7 @@ impl<'tcx> ObligationCauseExt<'tcx> for ObligationCause<'tcx> {
             | IfExpression { .. }
             | LetElse
             | StartFunctionType
+            | LangFunctionType(_)
             | IntrinsicType
             | MethodReceiver => Error0308,
 
@@ -2971,6 +2972,9 @@ impl<'tcx> ObligationCauseExt<'tcx> for ObligationCause<'tcx> {
             LetElse => ObligationCauseFailureCode::NoDiverge { span, subdiags },
             MainFunctionType => ObligationCauseFailureCode::FnMainCorrectType { span },
             StartFunctionType => ObligationCauseFailureCode::FnStartCorrectType { span, subdiags },
+            &LangFunctionType(lang_item_name) => {
+                ObligationCauseFailureCode::FnLangCorrectType { span, subdiags, lang_item_name }
+            }
             IntrinsicType => ObligationCauseFailureCode::IntrinsicCorrectType { span, subdiags },
             MethodReceiver => ObligationCauseFailureCode::MethodCorrectType { span, subdiags },
 
@@ -3006,6 +3010,7 @@ impl<'tcx> ObligationCauseExt<'tcx> for ObligationCause<'tcx> {
             IfExpressionWithNoElse => "`if` missing an `else` returns `()`",
             MainFunctionType => "`main` function has the correct type",
             StartFunctionType => "`#[start]` function has the correct type",
+            LangFunctionType(_) => "lang item function has the correct type",
             IntrinsicType => "intrinsic has the correct type",
             MethodReceiver => "method receiver has the correct type",
             _ => "types are compatible",
@@ -3028,6 +3033,7 @@ impl IntoDiagnosticArg for ObligationCauseAsDiagArg<'_> {
             IfExpressionWithNoElse => "no_else",
             MainFunctionType => "fn_main_correct_type",
             StartFunctionType => "fn_start_correct_type",
+            LangFunctionType(_) => "fn_lang_correct_type",
             IntrinsicType => "intrinsic_correct_type",
             MethodReceiver => "method_correct_type",
             _ => "other",
diff --git a/compiler/rustc_middle/src/traits/mod.rs b/compiler/rustc_middle/src/traits/mod.rs
index 1340e674568..eef193a657d 100644
--- a/compiler/rustc_middle/src/traits/mod.rs
+++ b/compiler/rustc_middle/src/traits/mod.rs
@@ -378,6 +378,9 @@ pub enum ObligationCauseCode<'tcx> {
     /// `start` has wrong type
     StartFunctionType,
 
+    /// language function has wrong type
+    LangFunctionType(Symbol),
+
     /// Intrinsic has wrong type
     IntrinsicType,
 
diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
index 17e0e4fcb48..cd1dadde1ba 100644
--- a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
+++ b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
@@ -2717,6 +2717,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
             | ObligationCauseCode::IfExpressionWithNoElse
             | ObligationCauseCode::MainFunctionType
             | ObligationCauseCode::StartFunctionType
+            | ObligationCauseCode::LangFunctionType(_)
             | ObligationCauseCode::IntrinsicType
             | ObligationCauseCode::MethodReceiver
             | ObligationCauseCode::ReturnNoExpression