rustc_passes/
entry.rs

1use rustc_ast::attr;
2use rustc_ast::entry::EntryPointType;
3use rustc_errors::codes::*;
4use rustc_hir::attrs::AttributeKind;
5use rustc_hir::def_id::{CRATE_DEF_ID, DefId, LOCAL_CRATE, LocalDefId};
6use rustc_hir::{CRATE_HIR_ID, ItemId, Node, find_attr};
7use rustc_middle::query::Providers;
8use rustc_middle::ty::TyCtxt;
9use rustc_session::RemapFileNameExt;
10use rustc_session::config::{CrateType, EntryFnType, RemapPathScopeComponents, sigpipe};
11use rustc_span::{Span, sym};
12
13use crate::errors::{ExternMain, MultipleRustcMain, NoMainErr};
14
15struct EntryContext<'tcx> {
16    tcx: TyCtxt<'tcx>,
17
18    /// The function has the `#[rustc_main]` attribute.
19    rustc_main_fn: Option<(LocalDefId, Span)>,
20
21    /// The functions that one might think are `main` but aren't, e.g.
22    /// main functions not defined at the top level. For diagnostics.
23    non_main_fns: Vec<Span>,
24}
25
26fn entry_fn(tcx: TyCtxt<'_>, (): ()) -> Option<(DefId, EntryFnType)> {
27    let any_exe = tcx.crate_types().contains(&CrateType::Executable);
28    if !any_exe {
29        // No need to find a main function.
30        return None;
31    }
32
33    // If the user wants no main function at all, then stop here.
34    if attr::contains_name(tcx.hir_attrs(CRATE_HIR_ID), sym::no_main) {
35        return None;
36    }
37
38    let mut ctxt = EntryContext { tcx, rustc_main_fn: None, non_main_fns: Vec::new() };
39
40    for id in tcx.hir_free_items() {
41        check_and_search_item(id, &mut ctxt);
42    }
43
44    configure_main(tcx, &ctxt)
45}
46
47fn check_and_search_item(id: ItemId, ctxt: &mut EntryContext<'_>) {
48    let at_root = ctxt.tcx.opt_local_parent(id.owner_id.def_id) == Some(CRATE_DEF_ID);
49
50    let attrs = ctxt.tcx.hir_attrs(id.hir_id());
51    let entry_point_type = rustc_ast::entry::entry_point_type(
52        find_attr!(attrs, AttributeKind::RustcMain),
53        at_root,
54        ctxt.tcx.opt_item_name(id.owner_id.to_def_id()),
55    );
56
57    match entry_point_type {
58        EntryPointType::None => {}
59        EntryPointType::MainNamed => {}
60        EntryPointType::OtherMain => {
61            ctxt.non_main_fns.push(ctxt.tcx.def_span(id.owner_id));
62        }
63        EntryPointType::RustcMainAttr => {
64            if ctxt.rustc_main_fn.is_none() {
65                ctxt.rustc_main_fn = Some((id.owner_id.def_id, ctxt.tcx.def_span(id.owner_id)));
66            } else {
67                ctxt.tcx.dcx().emit_err(MultipleRustcMain {
68                    span: ctxt.tcx.def_span(id.owner_id.to_def_id()),
69                    first: ctxt.rustc_main_fn.unwrap().1,
70                    additional: ctxt.tcx.def_span(id.owner_id.to_def_id()),
71                });
72            }
73        }
74    }
75}
76
77fn configure_main(tcx: TyCtxt<'_>, visitor: &EntryContext<'_>) -> Option<(DefId, EntryFnType)> {
78    if let Some((local_def_id, _)) = visitor.rustc_main_fn {
79        let def_id = local_def_id.to_def_id();
80        Some((def_id, EntryFnType::Main { sigpipe: sigpipe(tcx) }))
81    } else {
82        // The actual resolution of main happens in the resolver, this here
83        if let Some(main_def) = tcx.resolutions(()).main_def
84            && let Some(def_id) = main_def.opt_fn_def_id()
85        {
86            // non-local main imports are handled below
87            if let Some(def_id) = def_id.as_local()
88                && matches!(tcx.hir_node_by_def_id(def_id), Node::ForeignItem(_))
89            {
90                tcx.dcx().emit_err(ExternMain { span: tcx.def_span(def_id) });
91                return None;
92            }
93
94            return Some((def_id, EntryFnType::Main { sigpipe: sigpipe(tcx) }));
95        }
96        no_main_err(tcx, visitor);
97        None
98    }
99}
100
101fn sigpipe(tcx: TyCtxt<'_>) -> u8 {
102    match tcx.sess.opts.unstable_opts.on_broken_pipe {
103        rustc_target::spec::OnBrokenPipe::Default => sigpipe::DEFAULT,
104        rustc_target::spec::OnBrokenPipe::Kill => sigpipe::SIG_DFL,
105        rustc_target::spec::OnBrokenPipe::Error => sigpipe::SIG_IGN,
106        rustc_target::spec::OnBrokenPipe::Inherit => sigpipe::INHERIT,
107    }
108}
109
110fn no_main_err(tcx: TyCtxt<'_>, visitor: &EntryContext<'_>) {
111    let sp = tcx.def_span(CRATE_DEF_ID);
112
113    // There is no main function.
114    let mut has_filename = true;
115    let filename = tcx
116        .sess
117        .local_crate_source_file()
118        .map(|src| src.for_scope(&tcx.sess, RemapPathScopeComponents::DIAGNOSTICS).to_path_buf())
119        .unwrap_or_else(|| {
120            has_filename = false;
121            Default::default()
122        });
123    let main_def_opt = tcx.resolutions(()).main_def;
124    let code = E0601;
125    let add_teach_note = tcx.sess.teach(code);
126    // The file may be empty, which leads to the diagnostic machinery not emitting this
127    // note. This is a relatively simple way to detect that case and emit a span-less
128    // note instead.
129    let file_empty = tcx.sess.source_map().lookup_line(sp.hi()).is_err();
130
131    tcx.dcx().emit_err(NoMainErr {
132        sp,
133        crate_name: tcx.crate_name(LOCAL_CRATE),
134        has_filename,
135        filename,
136        file_empty,
137        non_main_fns: visitor.non_main_fns.clone(),
138        main_def_opt,
139        add_teach_note,
140    });
141}
142
143pub fn provide(providers: &mut Providers) {
144    *providers = Providers { entry_fn, ..*providers };
145}