blob: b43c8282db1db1d5f30114aa24e5f3bc568672c6 [file] [log] [blame]
use rustc_ast::attr;
use rustc_ast::entry::EntryPointType;
use rustc_errors::codes::*;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_ID, LOCAL_CRATE};
use rustc_hir::{ItemId, Node, CRATE_HIR_ID};
use rustc_middle::query::Providers;
use rustc_middle::ty::TyCtxt;
use rustc_session::config::{sigpipe, CrateType, EntryFnType};
use rustc_session::{config::RemapPathScopeComponents, RemapFileNameExt};
use rustc_span::symbol::sym;
use rustc_span::{Span, Symbol};
use crate::errors::{
AttrOnlyInFunctions, ExternMain, MultipleRustcMain, MultipleStartFunctions, NoMainErr,
};
struct EntryContext<'tcx> {
tcx: TyCtxt<'tcx>,
/// The function has the `#[rustc_main]` attribute.
rustc_main_fn: Option<(LocalDefId, Span)>,
/// The function that has the attribute `#[start]` on it.
start_fn: Option<(LocalDefId, Span)>,
/// The functions that one might think are `main` but aren't, e.g.
/// main functions not defined at the top level. For diagnostics.
non_main_fns: Vec<Span>,
}
fn entry_fn(tcx: TyCtxt<'_>, (): ()) -> Option<(DefId, EntryFnType)> {
let any_exe = tcx.crate_types().iter().any(|ty| *ty == CrateType::Executable);
if !any_exe {
// No need to find a main function.
return None;
}
// If the user wants no main function at all, then stop here.
if attr::contains_name(tcx.hir().attrs(CRATE_HIR_ID), sym::no_main) {
return None;
}
let mut ctxt =
EntryContext { tcx, rustc_main_fn: None, start_fn: None, non_main_fns: Vec::new() };
for id in tcx.hir().items() {
check_and_search_item(id, &mut ctxt);
}
configure_main(tcx, &ctxt)
}
fn attr_span_by_symbol(ctxt: &EntryContext<'_>, id: ItemId, sym: Symbol) -> Option<Span> {
let attrs = ctxt.tcx.hir().attrs(id.hir_id());
attr::find_by_name(attrs, sym).map(|attr| attr.span)
}
fn check_and_search_item(id: ItemId, ctxt: &mut EntryContext<'_>) {
if !matches!(ctxt.tcx.def_kind(id.owner_id), DefKind::Fn) {
for attr in [sym::start, sym::rustc_main] {
if let Some(span) = attr_span_by_symbol(ctxt, id, attr) {
ctxt.tcx.dcx().emit_err(AttrOnlyInFunctions { span, attr });
}
}
return;
}
let at_root = ctxt.tcx.opt_local_parent(id.owner_id.def_id) == Some(CRATE_DEF_ID);
let attrs = ctxt.tcx.hir().attrs(id.hir_id());
let entry_point_type = rustc_ast::entry::entry_point_type(
attrs,
at_root,
ctxt.tcx.opt_item_name(id.owner_id.to_def_id()),
);
match entry_point_type {
EntryPointType::None => {}
EntryPointType::MainNamed => {}
EntryPointType::OtherMain => {
ctxt.non_main_fns.push(ctxt.tcx.def_span(id.owner_id));
}
EntryPointType::RustcMainAttr => {
if ctxt.rustc_main_fn.is_none() {
ctxt.rustc_main_fn = Some((id.owner_id.def_id, ctxt.tcx.def_span(id.owner_id)));
} else {
ctxt.tcx.dcx().emit_err(MultipleRustcMain {
span: ctxt.tcx.def_span(id.owner_id.to_def_id()),
first: ctxt.rustc_main_fn.unwrap().1,
additional: ctxt.tcx.def_span(id.owner_id.to_def_id()),
});
}
}
EntryPointType::Start => {
if ctxt.start_fn.is_none() {
ctxt.start_fn = Some((id.owner_id.def_id, ctxt.tcx.def_span(id.owner_id)));
} else {
ctxt.tcx.dcx().emit_err(MultipleStartFunctions {
span: ctxt.tcx.def_span(id.owner_id),
labeled: ctxt.tcx.def_span(id.owner_id.to_def_id()),
previous: ctxt.start_fn.unwrap().1,
});
}
}
}
}
#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
fn configure_main(tcx: TyCtxt<'_>, visitor: &EntryContext<'_>) -> Option<(DefId, EntryFnType)> {
if let Some((def_id, _)) = visitor.start_fn {
Some((def_id.to_def_id(), EntryFnType::Start))
} else if let Some((local_def_id, _)) = visitor.rustc_main_fn {
let def_id = local_def_id.to_def_id();
Some((def_id, EntryFnType::Main { sigpipe: sigpipe(tcx) }))
} else {
// The actual resolution of main happens in the resolver, this here
if let Some(main_def) = tcx.resolutions(()).main_def
&& let Some(def_id) = main_def.opt_fn_def_id()
{
// non-local main imports are handled below
if let Some(def_id) = def_id.as_local()
&& matches!(tcx.hir_node_by_def_id(def_id), Node::ForeignItem(_))
{
tcx.dcx().emit_err(ExternMain { span: tcx.def_span(def_id) });
return None;
}
return Some((def_id, EntryFnType::Main { sigpipe: sigpipe(tcx) }));
}
no_main_err(tcx, visitor);
None
}
}
fn sigpipe(tcx: TyCtxt<'_>) -> u8 {
match tcx.sess.opts.unstable_opts.on_broken_pipe {
rustc_target::spec::OnBrokenPipe::Default => sigpipe::DEFAULT,
rustc_target::spec::OnBrokenPipe::Kill => sigpipe::SIG_DFL,
rustc_target::spec::OnBrokenPipe::Error => sigpipe::SIG_IGN,
rustc_target::spec::OnBrokenPipe::Inherit => sigpipe::INHERIT,
}
}
fn no_main_err(tcx: TyCtxt<'_>, visitor: &EntryContext<'_>) {
let sp = tcx.def_span(CRATE_DEF_ID);
// There is no main function.
let mut has_filename = true;
let filename = tcx
.sess
.local_crate_source_file()
.map(|src| src.for_scope(&tcx.sess, RemapPathScopeComponents::DIAGNOSTICS).to_path_buf())
.unwrap_or_else(|| {
has_filename = false;
Default::default()
});
let main_def_opt = tcx.resolutions(()).main_def;
let code = E0601;
let add_teach_note = tcx.sess.teach(code);
// The file may be empty, which leads to the diagnostic machinery not emitting this
// note. This is a relatively simple way to detect that case and emit a span-less
// note instead.
let file_empty = tcx.sess.source_map().lookup_line(sp.hi()).is_err();
tcx.dcx().emit_err(NoMainErr {
sp,
crate_name: tcx.crate_name(LOCAL_CRATE),
has_filename,
filename,
file_empty,
non_main_fns: visitor.non_main_fns.clone(),
main_def_opt,
add_teach_note,
});
}
pub fn provide(providers: &mut Providers) {
*providers = Providers { entry_fn, ..*providers };
}