Skip to content

Implement explicit tail calls #112657

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
hir: Add Become expression kind
  • Loading branch information
WaffleLapkin committed Jun 15, 2023
commit 47ab1e5d609e09685ac059164c40a31311af29e9
4 changes: 1 addition & 3 deletions compiler/rustc_ast_lowering/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,9 +277,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
ExprKind::Yeet(sub_expr) => self.lower_expr_yeet(e.span, sub_expr.as_deref()),
ExprKind::Become(sub_expr) => {
let sub_expr = self.lower_expr(sub_expr);

// FIXME(waffle): this is obviously wrong
hir::ExprKind::Ret(Some(sub_expr))
hir::ExprKind::Become(sub_expr)
}
ExprKind::InlineAsm(asm) => {
hir::ExprKind::InlineAsm(self.lower_inline_asm(e.span, asm))
Expand Down
5 changes: 5 additions & 0 deletions compiler/rustc_hir/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1719,6 +1719,7 @@ impl Expr<'_> {
ExprKind::Break(..) => ExprPrecedence::Break,
ExprKind::Continue(..) => ExprPrecedence::Continue,
ExprKind::Ret(..) => ExprPrecedence::Ret,
ExprKind::Become(..) => ExprPrecedence::Become,
ExprKind::InlineAsm(..) => ExprPrecedence::InlineAsm,
ExprKind::OffsetOf(..) => ExprPrecedence::OffsetOf,
ExprKind::Struct(..) => ExprPrecedence::Struct,
Expand Down Expand Up @@ -1776,6 +1777,7 @@ impl Expr<'_> {
| ExprKind::Break(..)
| ExprKind::Continue(..)
| ExprKind::Ret(..)
| ExprKind::Become(..)
| ExprKind::Let(..)
| ExprKind::Loop(..)
| ExprKind::Assign(..)
Expand Down Expand Up @@ -1866,6 +1868,7 @@ impl Expr<'_> {
| ExprKind::Break(..)
| ExprKind::Continue(..)
| ExprKind::Ret(..)
| ExprKind::Become(..)
| ExprKind::Let(..)
| ExprKind::Loop(..)
| ExprKind::Assign(..)
Expand Down Expand Up @@ -2025,6 +2028,8 @@ pub enum ExprKind<'hir> {
Continue(Destination),
/// A `return`, with an optional value to be returned.
Ret(Option<&'hir Expr<'hir>>),
/// A `become`, with the value to be returned.
Become(&'hir Expr<'hir>),

/// Inline assembly (from `asm!`), with its outputs and inputs.
InlineAsm(&'hir InlineAsm<'hir>),
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_hir/src/intravisit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,7 @@ pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr<'v>)
ExprKind::Ret(ref optional_expression) => {
walk_list!(visitor, visit_expr, optional_expression);
}
ExprKind::Become(ref expr) => visitor.visit_expr(expr),
ExprKind::InlineAsm(ref asm) => {
visitor.visit_inline_asm(asm, expression.hir_id);
}
Expand Down
5 changes: 5 additions & 0 deletions compiler/rustc_hir_pretty/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1554,6 +1554,11 @@ impl<'a> State<'a> {
self.print_expr_maybe_paren(expr, parser::PREC_JUMP);
}
}
hir::ExprKind::Become(result) => {
self.word("become");
self.word(" ");
self.print_expr_maybe_paren(result, parser::PREC_JUMP);
}
hir::ExprKind::InlineAsm(asm) => {
self.word("asm!");
self.print_inline_asm(asm);
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_hir_typeck/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ hir_typeck_note_edition_guide = for more on editions, read https://doc.rust-lang
hir_typeck_op_trait_generic_params = `{$method_name}` must not have any generic parameters

hir_typeck_return_stmt_outside_of_fn_body =
return statement outside of function body
.encl_body_label = the return is part of this body...
`{$statement_kind}` statement outside of function body
.encl_body_label = the `{$statement_kind}` is part of this body...
.encl_fn_label = ...not the enclosing function body

hir_typeck_struct_expr_non_exhaustive =
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_hir_typeck/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ pub struct ReturnStmtOutsideOfFnBody {
pub encl_body_span: Option<Span>,
#[label(hir_typeck_encl_fn_label)]
pub encl_fn_span: Option<Span>,
// "return" or "become"
pub statement_kind: String,
}

#[derive(Diagnostic)]
Expand Down
133 changes: 89 additions & 44 deletions compiler/rustc_hir_typeck/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}
ExprKind::Ret(ref expr_opt) => self.check_expr_return(expr_opt.as_deref(), expr),
ExprKind::Become(call) => self.check_expr_become(call, expr),
ExprKind::Let(let_expr) => self.check_expr_let(let_expr),
ExprKind::Loop(body, _, source, _) => {
self.check_expr_loop(body, source, expected, expr)
Expand Down Expand Up @@ -735,47 +736,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
expr: &'tcx hir::Expr<'tcx>,
) -> Ty<'tcx> {
if self.ret_coercion.is_none() {
let mut err = ReturnStmtOutsideOfFnBody {
span: expr.span,
encl_body_span: None,
encl_fn_span: None,
};

let encl_item_id = self.tcx.hir().get_parent_item(expr.hir_id);

if let Some(hir::Node::Item(hir::Item {
kind: hir::ItemKind::Fn(..),
span: encl_fn_span,
..
}))
| Some(hir::Node::TraitItem(hir::TraitItem {
kind: hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(_)),
span: encl_fn_span,
..
}))
| Some(hir::Node::ImplItem(hir::ImplItem {
kind: hir::ImplItemKind::Fn(..),
span: encl_fn_span,
..
})) = self.tcx.hir().find_by_def_id(encl_item_id.def_id)
{
// We are inside a function body, so reporting "return statement
// outside of function body" needs an explanation.

let encl_body_owner_id = self.tcx.hir().enclosing_body_owner(expr.hir_id);

// If this didn't hold, we would not have to report an error in
// the first place.
assert_ne!(encl_item_id.def_id, encl_body_owner_id);

let encl_body_id = self.tcx.hir().body_owned_by(encl_body_owner_id);
let encl_body = self.tcx.hir().body(encl_body_id);

err.encl_body_span = Some(encl_body.value.span);
err.encl_fn_span = Some(*encl_fn_span);
}

self.tcx.sess.emit_err(err);
self.emit_return_outside_of_fn_body(expr, "return");

if let Some(e) = expr_opt {
// We still have to type-check `e` (issue #86188), but calling
Expand Down Expand Up @@ -815,6 +776,38 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self.tcx.types.never
}

fn check_expr_become(
&self,
call: &'tcx hir::Expr<'tcx>,
expr: &'tcx hir::Expr<'tcx>,
) -> Ty<'tcx> {
match &self.ret_coercion {
Some(ret_coercion) => {
let ret_ty = ret_coercion.borrow().expected_ty();
let call_expr_ty = self.check_expr_with_hint(call, ret_ty);

// N.B. don't coerce here, as tail calls can't support most/all coercions
// FIXME(explicit_tail_calls): add a diagnostic note that `become` doesn't allow coercions
self.demand_suptype(expr.span, ret_ty, call_expr_ty);
}
None => {
self.emit_return_outside_of_fn_body(expr, "become");

// Fallback to simply type checking `call` without hint/demanding the right types.
// Best effort to highlight more errors.
self.check_expr(call);
}
}

self.tcx.types.never
}

/// Check an expression that _is being returned_.
/// For example, this is called with `return_expr: $expr` when `return $expr`
/// is encountered.
///
/// Note that this function must only be called in function bodies.
///
/// `explicit_return` is `true` if we're checking an explicit `return expr`,
/// and `false` if we're checking a trailing expression.
pub(super) fn check_return_expr(
Expand All @@ -831,10 +824,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let mut span = return_expr.span;
// Use the span of the trailing expression for our cause,
// not the span of the entire function
if !explicit_return {
if let ExprKind::Block(body, _) = return_expr.kind && let Some(last_expr) = body.expr {
if !explicit_return
&& let ExprKind::Block(body, _) = return_expr.kind
&& let Some(last_expr) = body.expr
{
span = last_expr.span;
}
}
ret_coercion.borrow_mut().coerce(
self,
Expand All @@ -854,6 +848,57 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}

/// Emit an error because `return` or `become` is used outside of a function body.
///
/// `expr` is the `return` (`become`) "statement", `kind` is the kind of the return
/// either "return" or "become"
///
/// FIXME(explicit_tail_calls): come up with a better way than passing strings lol
fn emit_return_outside_of_fn_body(&self, expr: &hir::Expr<'_>, kind: &'static str) {
let mut err = ReturnStmtOutsideOfFnBody {
span: expr.span,
encl_body_span: None,
encl_fn_span: None,
statement_kind: kind.to_string(),
};

let encl_item_id = self.tcx.hir().get_parent_item(expr.hir_id);

if let Some(hir::Node::Item(hir::Item {
kind: hir::ItemKind::Fn(..),
span: encl_fn_span,
..
}))
| Some(hir::Node::TraitItem(hir::TraitItem {
kind: hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(_)),
span: encl_fn_span,
..
}))
| Some(hir::Node::ImplItem(hir::ImplItem {
kind: hir::ImplItemKind::Fn(..),
span: encl_fn_span,
..
})) = self.tcx.hir().find_by_def_id(encl_item_id.def_id)
{
// We are inside a function body, so reporting "return statement
// outside of function body" needs an explanation.

let encl_body_owner_id = self.tcx.hir().enclosing_body_owner(expr.hir_id);

// If this didn't hold, we would not have to report an error in
// the first place.
assert_ne!(encl_item_id.def_id, encl_body_owner_id);

let encl_body_id = self.tcx.hir().body_owned_by(encl_body_owner_id);
let encl_body = self.tcx.hir().body(encl_body_id);

err.encl_body_span = Some(encl_body.value.span);
err.encl_fn_span = Some(*encl_fn_span);
}

self.tcx.sess.emit_err(err);
}

fn point_at_return_for_opaque_ty_error(
&self,
errors: &mut Vec<traits::FulfillmentError<'tcx>>,
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_hir_typeck/src/expr_use_visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,10 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
}
}

hir::ExprKind::Become(call) => {
self.consume_expr(call);
}

hir::ExprKind::Assign(lhs, rhs, _) => {
self.mutate_expr(lhs);
self.consume_expr(rhs);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> {
| ExprKind::Break(..)
| ExprKind::Continue(..)
| ExprKind::Ret(..)
| ExprKind::Become(..)
| ExprKind::InlineAsm(..)
| ExprKind::OffsetOf(..)
| ExprKind::Struct(..)
Expand Down Expand Up @@ -451,6 +452,9 @@ impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> {
}
}

// FIXME(explicit_tail_calls): we should record the "drop everything that is not passed by-value" here, I think
ExprKind::Become(_call) => intravisit::walk_expr(self, expr),

ExprKind::Call(f, args) => {
self.visit_expr(f);
for arg in args {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_hir_typeck/src/mem_categorization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> {
| hir::ExprKind::AssignOp(..)
| hir::ExprKind::Closure { .. }
| hir::ExprKind::Ret(..)
| hir::ExprKind::Become(..)
| hir::ExprKind::Unary(..)
| hir::ExprKind::Yield(..)
| hir::ExprKind::MethodCall(..)
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_mir_build/src/thir/cx/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,7 @@ impl<'tcx> Cx<'tcx> {
ExprKind::Repeat { value: self.mirror_expr(v), count: *count }
}
hir::ExprKind::Ret(ref v) => ExprKind::Return { value: v.map(|v| self.mirror_expr(v)) },
hir::ExprKind::Become(_call) => unimplemented!("lol (lmao)"),
hir::ExprKind::Break(dest, ref value) => match dest.target_id {
Ok(target_id) => ExprKind::Break {
label: region::Scope { id: target_id.local_id, data: region::ScopeData::Node },
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_passes/src/hir_stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,8 +302,8 @@ impl<'v> hir_visit::Visitor<'v> for StatCollector<'v> {
[
ConstBlock, Array, Call, MethodCall, Tup, Binary, Unary, Lit, Cast, Type,
DropTemps, Let, If, Loop, Match, Closure, Block, Assign, AssignOp, Field, Index,
Path, AddrOf, Break, Continue, Ret, InlineAsm, OffsetOf, Struct, Repeat, Yield,
Err
Path, AddrOf, Break, Continue, Ret, Become, InlineAsm, OffsetOf, Struct, Repeat,
Yield, Err
]
);
hir_visit::walk_expr(self, e)
Expand Down
8 changes: 8 additions & 0 deletions compiler/rustc_passes/src/liveness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,8 @@ impl<'tcx> Visitor<'tcx> for IrMaps<'tcx> {
| hir::ExprKind::Lit(_)
| hir::ExprKind::ConstBlock(..)
| hir::ExprKind::Ret(..)
// FIXME(explicit_tail_calls): this may or may not be wrong
| hir::ExprKind::Become(..)
| hir::ExprKind::Block(..)
| hir::ExprKind::Assign(..)
| hir::ExprKind::AssignOp(..)
Expand Down Expand Up @@ -967,6 +969,11 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
self.propagate_through_opt_expr(o_e.as_deref(), self.exit_ln)
}

hir::ExprKind::Become(ref e) => {
// Ignore succ and subst exit_ln.
self.propagate_through_expr(e, self.exit_ln)
}

hir::ExprKind::Break(label, ref opt_expr) => {
// Find which label this break jumps to
let target = match label.target_id {
Expand Down Expand Up @@ -1408,6 +1415,7 @@ fn check_expr<'tcx>(this: &mut Liveness<'_, 'tcx>, expr: &'tcx Expr<'tcx>) {
| hir::ExprKind::DropTemps(..)
| hir::ExprKind::Unary(..)
| hir::ExprKind::Ret(..)
| hir::ExprKind::Become(..)
| hir::ExprKind::Break(..)
| hir::ExprKind::Continue(..)
| hir::ExprKind::Lit(_)
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_passes/src/naked_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ impl<'tcx> CheckInlineAssembly<'tcx> {
| ExprKind::Continue(..)
| ExprKind::Ret(..)
| ExprKind::OffsetOf(..)
| ExprKind::Become(..)
| ExprKind::Struct(..)
| ExprKind::Repeat(..)
| ExprKind::Yield(..) => {
Expand Down
2 changes: 1 addition & 1 deletion src/tools/tidy/src/ui_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use std::path::{Path, PathBuf};
const ENTRY_LIMIT: usize = 900;
// FIXME: The following limits should be reduced eventually.
const ISSUES_ENTRY_LIMIT: usize = 1896;
const ROOT_ENTRY_LIMIT: usize = 870;
const ROOT_ENTRY_LIMIT: usize = 871;

const EXPECTED_TEST_FILE_EXTENSIONS: &[&str] = &[
"rs", // test source files
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/error-codes/E0572.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error[E0572]: return statement outside of function body
error[E0572]: `return` statement outside of function body
--> $DIR/E0572.rs:1:18
|
LL | const FOO: u32 = return 0;
Expand Down
9 changes: 9 additions & 0 deletions tests/ui/explicit-tail-calls/become-outside.array.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
error[E0572]: `become` statement outside of function body
--> $DIR/become-outside.rs:10:17
|
LL | struct Bad([(); become f()]);
| ^^^^^^^^^^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0572`.
9 changes: 9 additions & 0 deletions tests/ui/explicit-tail-calls/become-outside.constant.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
error[E0572]: `become` statement outside of function body
--> $DIR/become-outside.rs:6:5
|
LL | become f();
| ^^^^^^^^^^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0572`.
14 changes: 14 additions & 0 deletions tests/ui/explicit-tail-calls/become-outside.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// revisions: constant array
#![feature(explicit_tail_calls)]

#[cfg(constant)]
const _: () = {
become f(); //[constant]~ error: `become` statement outside of function body
};

#[cfg(array)]
struct Bad([(); become f()]); //[array]~ error: `become` statement outside of function body

fn f() {}

fn main() {}
Loading