use crate::coercion::CoerceMany;
use crate::errors::{
LangStartIncorrectNumberArgs, LangStartIncorrectParam, LangStartIncorrectRetTy,
};
use crate::gather_locals::GatherLocalsVisitor;
use crate::FnCtxt;
use crate::GeneratorTypes;
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_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_target::spec::abi::Abi;
use rustc_trait_selection::traits;
use std::cell::RefCell;
#[instrument(skip(fcx, body), level = "debug")]
pub(super) fn check_fn<'a, 'tcx>(
fcx: &mut FnCtxt<'a, 'tcx>,
fn_sig: ty::FnSig<'tcx>,
decl: &'tcx hir::FnDecl<'tcx>,
fn_def_id: LocalDefId,
body: &'tcx hir::Body<'tcx>,
can_be_generator: Option<hir::Movability>,
params_can_be_unsized: bool,
) -> Option<GeneratorTypes<'tcx>> {
let fn_id = fcx.tcx.hir().local_def_id_to_hir_id(fn_def_id);
let tcx = fcx.tcx;
let hir = tcx.hir();
let declared_ret_ty = fn_sig.output();
let ret_ty =
fcx.register_infer_ok_obligations(fcx.infcx.replace_opaque_types_with_inference_vars(
declared_ret_ty,
fn_def_id,
decl.output.span(),
fcx.param_env,
));
fcx.ret_coercion = Some(RefCell::new(CoerceMany::new(ret_ty)));
let span = body.value.span;
fn_maybe_err(tcx, span, fn_sig.abi);
if let Some(kind) = body.generator_kind && can_be_generator.is_some() {
let yield_ty = if kind == hir::GeneratorKind::Gen {
let yield_ty = fcx
.next_ty_var(TypeVariableOrigin { kind: TypeVariableOriginKind::TypeInference, span });
fcx.require_type_is_sized(yield_ty, span, traits::SizedYieldType);
yield_ty
} else {
tcx.mk_unit()
};
let resume_ty = fn_sig.inputs().get(0).copied().unwrap_or_else(|| tcx.mk_unit());
fcx.resume_yield_tys = Some((resume_ty, yield_ty));
}
GatherLocalsVisitor::new(&fcx).visit_body(body);
let maybe_va_list = fn_sig.c_variadic.then(|| {
let span = body.params.last().unwrap().span;
let va_list_did = tcx.require_lang_item(LangItem::VaList, Some(span));
let region = fcx.next_region_var(RegionVariableOrigin::MiscVariable(span));
tcx.type_of(va_list_did).subst(tcx, &[region.into()])
});
let inputs_hir = hir.fn_decl_by_hir_id(fn_id).map(|decl| &decl.inputs);
let inputs_fn = fn_sig.inputs().iter().copied();
for (idx, (param_ty, param)) in inputs_fn.chain(maybe_va_list).zip(body.params).enumerate() {
let ty_span = try { inputs_hir?.get(idx)?.span };
fcx.check_pat_top(¶m.pat, param_ty, ty_span, None);
if param.pat.simple_ident().is_none() && !params_can_be_unsized {
fcx.require_type_is_sized(param_ty, param.pat.span, traits::SizedArgumentType(ty_span));
}
fcx.write_ty(param.hir_id, param_ty);
}
fcx.typeck_results.borrow_mut().liberated_fn_sigs_mut().insert(fn_id, fn_sig);
fcx.require_type_is_sized(declared_ret_ty, decl.output.span(), traits::SizedReturnType);
fcx.check_return_expr(&body.value, false);
let gen_ty = if let (Some(_), Some(gen_kind)) = (can_be_generator, body.generator_kind) {
let interior = fcx
.next_ty_var(TypeVariableOrigin { kind: TypeVariableOriginKind::MiscVariable, span });
fcx.deferred_generator_interiors.borrow_mut().push((
fn_def_id,
body.id(),
interior,
gen_kind,
));
let (resume_ty, yield_ty) = fcx.resume_yield_tys.unwrap();
Some(GeneratorTypes {
resume_ty,
yield_ty,
interior,
movability: can_be_generator.unwrap(),
})
} else {
None
};
let coercion = fcx.ret_coercion.take().unwrap().into_inner();
let mut actual_return_ty = coercion.complete(&fcx);
debug!("actual_return_ty = {:?}", actual_return_ty);
if let ty::Dynamic(..) = declared_ret_ty.kind() {
actual_return_ty =
fcx.next_ty_var(TypeVariableOrigin { kind: TypeVariableOriginKind::DynReturnFn, span });
debug!("actual_return_ty replaced with {:?}", actual_return_ty);
}
fcx.demand_suptype(span, ret_ty, actual_return_ty);
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);
}
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);
}
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`");
}
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");
return;
};
let generic_counts = tcx.generics_of(fn_id).own_counts();
if generic_counts.types != 0 {
let span = tcx.def_span(fn_id);
tcx.sess.span_err(span, "should have no type parameters");
}
if generic_counts.consts != 0 {
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,
});
}
if let Some(&main_arg) = inputs.get(0) {
let generics = tcx.generics_of(def_id);
let fn_generic = generics.param_at(0, tcx);
let generic_ty = tcx.mk_ty_param(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 = tcx.mk_fn_ptr(Binder::dummy(expected_fn_sig));
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 !matches!(ret_ty.kind(), ty::Param(_)) {
emit_main_fn_arg_err();
}
});
} else {
emit_main_fn_arg_err();
}
}
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,
});
}
}
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 =
tcx.mk_ptr(ty::TypeAndMut { mutbl: hir::Mutability::Not, ty: tcx.types.u8 });
let expected_ty =
tcx.mk_ptr(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,
});
}
}
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,
});
}
}
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(),
});
}
}