mirror of
https://github.com/rust-lang/rust.git
synced 2025-05-10 08:57:36 +00:00

In the stabilization attempt of `#[unix_sigpipe = "sig_dfl"]`, a concern was raised related to using a language attribute for the feature: Long term, we want `fn lang_start()` to be definable by any crate, not just libstd. Having a special language attribute in that case becomes awkward. So as a first step towards towards the next stabilization attempt, this PR changes the `#[unix_sigpipe = "..."]` attribute to a compiler flag `-Zon-broken-pipe=...` to remove that concern, since now the language is not "contaminated" by this feature. Another point was also raised, namely that the ui should not leak **how** it does things, but rather what the **end effect** is. The new flag uses the proposed naming. This is of course something that can be iterated on further before stabilization.
177 lines
6.3 KiB
Rust
177 lines
6.3 KiB
Rust
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 that has attribute named `main`.
|
|
attr_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, attr_main_fn: None, start_fn: None, non_main_fns: Vec::new() };
|
|
|
|
for id in tcx.hir().items() {
|
|
find_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 find_item(id: ItemId, ctxt: &mut EntryContext<'_>) {
|
|
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 => (),
|
|
_ 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 });
|
|
}
|
|
}
|
|
}
|
|
EntryPointType::MainNamed => (),
|
|
EntryPointType::OtherMain => {
|
|
ctxt.non_main_fns.push(ctxt.tcx.def_span(id.owner_id));
|
|
}
|
|
EntryPointType::RustcMainAttr => {
|
|
if ctxt.attr_main_fn.is_none() {
|
|
ctxt.attr_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.attr_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.attr_main_fn {
|
|
let def_id = local_def_id.to_def_id();
|
|
Some((def_id, EntryFnType::Main { sigpipe: sigpipe(tcx) }))
|
|
} else {
|
|
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 };
|
|
}
|