Skip to content

Commit

Permalink
Auto merge of rust-lang#112887 - WaffleLapkin:become_unuwuable_in_hir…
Browse files Browse the repository at this point in the history
…, r=compiler-errors,Nilstrieb

`hir`: Add `Become` expression kind (explicit tail calls experiment)

This adds `hir::ExprKind::Become` alongside ast lowering. During hir-thir lowering we currently lower `become` as `return`, so that we can partially test `become` without ICEing.

cc `@scottmcm`
r? `@Nilstrieb`
  • Loading branch information
bors committed Jun 26, 2023
2 parents 27e10c5 + e38576a commit 6f8c27a
Show file tree
Hide file tree
Showing 35 changed files with 286 additions and 72 deletions.
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(explicit_tail_calls): Use `hir::ExprKind::Become` once we implemented it
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
23 changes: 22 additions & 1 deletion compiler/rustc_hir_typeck/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
use std::borrow::Cow;

use crate::fluent_generated as fluent;
use rustc_errors::{AddToDiagnostic, Applicability, Diagnostic, MultiSpan, SubdiagnosticMessage};
use rustc_errors::{
AddToDiagnostic, Applicability, Diagnostic, DiagnosticArgValue, IntoDiagnosticArg, MultiSpan,
SubdiagnosticMessage,
};
use rustc_macros::{Diagnostic, Subdiagnostic};
use rustc_middle::ty::Ty;
use rustc_span::{
Expand Down Expand Up @@ -31,6 +34,24 @@ pub struct ReturnStmtOutsideOfFnBody {
pub encl_body_span: Option<Span>,
#[label(hir_typeck_encl_fn_label)]
pub encl_fn_span: Option<Span>,
pub statement_kind: ReturnLikeStatementKind,
}

pub enum ReturnLikeStatementKind {
Return,
Become,
}

impl IntoDiagnosticArg for ReturnLikeStatementKind {
fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
let kind = match self {
Self::Return => "return",
Self::Become => "become",
}
.into();

DiagnosticArgValue::Str(kind)
}
}

#[derive(Diagnostic)]
Expand Down
132 changes: 88 additions & 44 deletions compiler/rustc_hir_typeck/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use crate::cast;
use crate::coercion::CoerceMany;
use crate::coercion::DynamicCoerceMany;
use crate::errors::ReturnLikeStatementKind;
use crate::errors::TypeMismatchFruTypo;
use crate::errors::{AddressOfTemporaryTaken, ReturnStmtOutsideOfFnBody, StructExprNonExhaustive};
use crate::errors::{
Expand Down Expand Up @@ -324,6 +325,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 +737,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, ReturnLikeStatementKind::Return);

if let Some(e) = expr_opt {
// We still have to type-check `e` (issue #86188), but calling
Expand Down Expand Up @@ -815,6 +777,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, ReturnLikeStatementKind::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 +825,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 +849,55 @@ 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 statement
/// either `Return` or `Become`.
fn emit_return_outside_of_fn_body(&self, expr: &hir::Expr<'_>, kind: ReturnLikeStatementKind) {
let mut err = ReturnStmtOutsideOfFnBody {
span: expr.span,
encl_body_span: None,
encl_fn_span: None,
statement_kind: kind,
};

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,8 @@ impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> {
}
}

ExprKind::Become(_call) => bug!("encountered a tail-call inside a generator"),

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
5 changes: 5 additions & 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,11 @@ 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) => {
// FIXME(explicit_tail_calls): use `ExprKind::Become` once we implemented it
// Temporary transform `become` into a `return`, so we can write tests for code before this stage
ExprKind::Return { value: Some(self.mirror_expr(call)) }
}
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
7 changes: 7 additions & 0 deletions compiler/rustc_passes/src/liveness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,7 @@ impl<'tcx> Visitor<'tcx> for IrMaps<'tcx> {
| hir::ExprKind::Lit(_)
| hir::ExprKind::ConstBlock(..)
| hir::ExprKind::Ret(..)
| hir::ExprKind::Become(..)
| hir::ExprKind::Block(..)
| hir::ExprKind::Assign(..)
| hir::ExprKind::AssignOp(..)
Expand Down Expand Up @@ -967,6 +968,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 +1414,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
6 changes: 6 additions & 0 deletions src/tools/clippy/clippy_lints/src/loops/never_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,12 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: H
NeverLoopResult::AlwaysBreak,
)
}),
ExprKind::Become(e) => {
combine_seq(
never_loop_expr(e, ignore_ids, main_loop_id),
NeverLoopResult::AlwaysBreak,
)
}
ExprKind::InlineAsm(asm) => asm
.operands
.iter()
Expand Down
Loading

0 comments on commit 6f8c27a

Please sign in to comment.