rustc_passes/
entry.rs

1use rustc_ast::attr;
2use rustc_ast::entry::EntryPointType;
3use rustc_errors::codes::*;
4use rustc_hir::def::DefKind;
5use rustc_hir::def_id::{CRATE_DEF_ID, DefId, LOCAL_CRATE, LocalDefId};
6use rustc_hir::{CRATE_HIR_ID, ItemId, Node};
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, Symbol, sym};
12
13use crate::errors::{AttrOnlyInFunctions, 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().iter().any(|ty| *ty == 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().items() {
41        check_and_search_item(id, &mut ctxt);
42    }
43
44    configure_main(tcx, &ctxt)
45}
46
47fn attr_span_by_symbol(ctxt: &EntryContext<'_>, id: ItemId, sym: Symbol) -> Option<Span> {
48    let attrs = ctxt.tcx.hir().attrs(id.hir_id());
49    attr::find_by_name(attrs, sym).map(|attr| attr.span)
50}
51
52fn check_and_search_item(id: ItemId, ctxt: &mut EntryContext<'_>) {
53    if !matches!(ctxt.tcx.def_kind(id.owner_id), DefKind::Fn) {
54        for attr in [sym::rustc_main] {
55            if let Some(span) = attr_span_by_symbol(ctxt, id, attr) {
56                ctxt.tcx.dcx().emit_err(AttrOnlyInFunctions { span, attr });
57            }
58        }
59        return;
60    }
61
62    let at_root = ctxt.tcx.opt_local_parent(id.owner_id.def_id) == Some(CRATE_DEF_ID);
63
64    let attrs = ctxt.tcx.hir().attrs(id.hir_id());
65    let entry_point_type = rustc_ast::entry::entry_point_type(
66        attrs,
67        at_root,
68        ctxt.tcx.opt_item_name(id.owner_id.to_def_id()),
69    );
70
71    match entry_point_type {
72        EntryPointType::None => {}
73        EntryPointType::MainNamed => {}
74        EntryPointType::OtherMain => {
75            ctxt.non_main_fns.push(ctxt.tcx.def_span(id.owner_id));
76        }
77        EntryPointType::RustcMainAttr => {
78            if ctxt.rustc_main_fn.is_none() {
79                ctxt.rustc_main_fn = Some((id.owner_id.def_id, ctxt.tcx.def_span(id.owner_id)));
80            } else {
81                ctxt.tcx.dcx().emit_err(MultipleRustcMain {
82                    span: ctxt.tcx.def_span(id.owner_id.to_def_id()),
83                    first: ctxt.rustc_main_fn.unwrap().1,
84                    additional: ctxt.tcx.def_span(id.owner_id.to_def_id()),
85                });
86            }
87        }
88    }
89}
90
91fn configure_main(tcx: TyCtxt<'_>, visitor: &EntryContext<'_>) -> Option<(DefId, EntryFnType)> {
92    if let Some((local_def_id, _)) = visitor.rustc_main_fn {
93        let def_id = local_def_id.to_def_id();
94        Some((def_id, EntryFnType::Main { sigpipe: sigpipe(tcx) }))
95    } else {
96        // The actual resolution of main happens in the resolver, this here
97        if let Some(main_def) = tcx.resolutions(()).main_def
98            && let Some(def_id) = main_def.opt_fn_def_id()
99        {
100            // non-local main imports are handled below
101            if let Some(def_id) = def_id.as_local()
102                && matches!(tcx.hir_node_by_def_id(def_id), Node::ForeignItem(_))
103            {
104                tcx.dcx().emit_err(ExternMain { span: tcx.def_span(def_id) });
105                return None;
106            }
107
108            return Some((def_id, EntryFnType::Main { sigpipe: sigpipe(tcx) }));
109        }
110        no_main_err(tcx, visitor);
111        None
112    }
113}
114
115fn sigpipe(tcx: TyCtxt<'_>) -> u8 {
116    match tcx.sess.opts.unstable_opts.on_broken_pipe {
117        rustc_target::spec::OnBrokenPipe::Default => sigpipe::DEFAULT,
118        rustc_target::spec::OnBrokenPipe::Kill => sigpipe::SIG_DFL,
119        rustc_target::spec::OnBrokenPipe::Error => sigpipe::SIG_IGN,
120        rustc_target::spec::OnBrokenPipe::Inherit => sigpipe::INHERIT,
121    }
122}
123
124fn no_main_err(tcx: TyCtxt<'_>, visitor: &EntryContext<'_>) {
125    let sp = tcx.def_span(CRATE_DEF_ID);
126
127    // There is no main function.
128    let mut has_filename = true;
129    let filename = tcx
130        .sess
131        .local_crate_source_file()
132        .map(|src| src.for_scope(&tcx.sess, RemapPathScopeComponents::DIAGNOSTICS).to_path_buf())
133        .unwrap_or_else(|| {
134            has_filename = false;
135            Default::default()
136        });
137    let main_def_opt = tcx.resolutions(()).main_def;
138    let code = E0601;
139    let add_teach_note = tcx.sess.teach(code);
140    // The file may be empty, which leads to the diagnostic machinery not emitting this
141    // note. This is a relatively simple way to detect that case and emit a span-less
142    // note instead.
143    let file_empty = tcx.sess.source_map().lookup_line(sp.hi()).is_err();
144
145    tcx.dcx().emit_err(NoMainErr {
146        sp,
147        crate_name: tcx.crate_name(LOCAL_CRATE),
148        has_filename,
149        filename,
150        file_empty,
151        non_main_fns: visitor.non_main_fns.clone(),
152        main_def_opt,
153        add_teach_note,
154    });
155}
156
157pub fn provide(providers: &mut Providers) {
158    *providers = Providers { entry_fn, ..*providers };
159}