Skip to content

Commit

Permalink
Auto merge of #99943 - compiler-errors:tuple-trait, r=jackh726
Browse files Browse the repository at this point in the history
Implement `std::marker::Tuple`, use it in `extern "rust-call"` and `Fn`-family traits

Implements rust-lang/compiler-team#537

I made a few opinionated decisions in this implementation, specifically:
1. Enforcing `extern "rust-call"` on fn items during wfcheck,
2. Enforcing this for all functions (not just ones that have bodies),
3. Gating this `Tuple` marker trait behind its own feature, instead of grouping it into (e.g.) `unboxed_closures`.

Still needing to be done:
1. Enforce that `extern "rust-call"` `fn`-ptrs are well-formed only if they have 1/2 args and the second one implements `Tuple`. (Doing this would fix ICE in #66696.)
2. Deny all explicit/user `impl`s of the `Tuple` trait, kinda like `Sized`.
3. Fixing `Tuple` trait built-in impl for chalk, so that chalkification tests are un-broken.

Open questions:
1. Does this need t-lang or t-libs signoff?

Fixes #99820
  • Loading branch information
bors committed Nov 6, 2022
2 parents 1e1e5b8 + ff8f84c commit 7eef946
Show file tree
Hide file tree
Showing 53 changed files with 841 additions and 242 deletions.
28 changes: 28 additions & 0 deletions compiler/rustc_hir_analysis/src/check/wfcheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use rustc_middle::ty::{GenericArgKind, InternalSubsts};
use rustc_session::parse::feature_err;
use rustc_span::symbol::{sym, Ident, Symbol};
use rustc_span::{Span, DUMMY_SP};
use rustc_target::spec::abi::Abi;
use rustc_trait_selection::autoderef::Autoderef;
use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt;
use rustc_trait_selection::traits::outlives_bounds::InferCtxtExt as _;
Expand Down Expand Up @@ -1542,6 +1543,33 @@ fn check_fn_or_method<'tcx>(
sig.output(),
hir_decl.output.span(),
);

if sig.abi == Abi::RustCall {
let span = tcx.def_span(def_id);
let has_implicit_self = hir_decl.implicit_self != hir::ImplicitSelfKind::None;
let mut inputs = sig.inputs().iter().skip(if has_implicit_self { 1 } else { 0 });
// Check that the argument is a tuple
if let Some(ty) = inputs.next() {
wfcx.register_bound(
ObligationCause::new(span, wfcx.body_id, ObligationCauseCode::RustCall),
wfcx.param_env,
*ty,
tcx.require_lang_item(hir::LangItem::Tuple, Some(span)),
);
} else {
tcx.sess.span_err(
hir_decl.inputs.last().map_or(span, |input| input.span),
"functions with the \"rust-call\" ABI must take a single non-self tuple argument",
);
}
// No more inputs other than the `self` type and the tuple type
if inputs.next().is_some() {
tcx.sess.span_err(
hir_decl.inputs.last().map_or(span, |input| input.span),
"functions with the \"rust-call\" ABI must take a single non-self tuple argument",
);
}
}
}

/// Basically `check_associated_type_bounds`, but separated for now and should be
Expand Down
16 changes: 16 additions & 0 deletions compiler/rustc_hir_typeck/src/callee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
def_id,
);

if fn_sig.abi == abi::Abi::RustCall {
let sp = arg_exprs.last().map_or(call_expr.span, |expr| expr.span);
if let Some(ty) = fn_sig.inputs().last().copied() {
self.register_bound(
ty,
self.tcx.require_lang_item(hir::LangItem::Tuple, Some(sp)),
traits::ObligationCause::new(sp, self.body_id, traits::RustCall),
);
} else {
self.tcx.sess.span_err(
sp,
"functions with the \"rust-call\" ABI must take a single non-self tuple argument",
);
}
}

fn_sig.output()
}

Expand Down
37 changes: 0 additions & 37 deletions compiler/rustc_hir_typeck/src/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@ use rustc_hir as hir;
use rustc_hir::def::DefKind;
use rustc_hir::intravisit::Visitor;
use rustc_hir::lang_items::LangItem;
use rustc_hir::{ImplicitSelfKind, ItemKind, Node};
use rustc_hir_analysis::check::fn_maybe_err;
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
use rustc_infer::infer::RegionVariableOrigin;
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_span::def_id::LocalDefId;
use rustc_target::spec::abi::Abi;
use rustc_trait_selection::traits;
use std::cell::RefCell;

Expand Down Expand Up @@ -56,41 +54,6 @@ pub(super) fn check_fn<'a, 'tcx>(

fn_maybe_err(tcx, span, fn_sig.abi);

if fn_sig.abi == Abi::RustCall {
let expected_args = if let ImplicitSelfKind::None = decl.implicit_self { 1 } else { 2 };

let err = || {
let item = match tcx.hir().get(fn_id) {
Node::Item(hir::Item { kind: ItemKind::Fn(header, ..), .. }) => Some(header),
Node::ImplItem(hir::ImplItem {
kind: hir::ImplItemKind::Fn(header, ..), ..
}) => Some(header),
Node::TraitItem(hir::TraitItem {
kind: hir::TraitItemKind::Fn(header, ..),
..
}) => Some(header),
// Closures are RustCall, but they tuple their arguments, so shouldn't be checked
Node::Expr(hir::Expr { kind: hir::ExprKind::Closure { .. }, .. }) => None,
node => bug!("Item being checked wasn't a function/closure: {:?}", node),
};

if let Some(header) = item {
tcx.sess.span_err(header.span, "functions with the \"rust-call\" ABI must take a single non-self argument that is a tuple");
}
};

if fn_sig.inputs().len() != expected_args {
err()
} else {
// FIXME(CraftSpider) Add a check on parameter expansion, so we don't just make the ICE happen later on
// This will probably require wide-scale changes to support a TupleKind obligation
// We can't resolve this without knowing the type of the param
if !matches!(fn_sig.inputs()[expected_args - 1].kind(), ty::Tuple(_) | ty::Param(_)) {
err()
}
}
}

if body.generator_kind.is_some() && can_be_generator.is_some() {
let yield_ty = fcx
.next_ty_var(TypeVariableOrigin { kind: TypeVariableOriginKind::TypeInference, span });
Expand Down
3 changes: 2 additions & 1 deletion compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
tuple_arguments,
Some(method.def_id),
);

method.sig.output()
}

Expand Down Expand Up @@ -214,7 +215,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
"cannot use call notation; the first type parameter \
for the function trait is neither a tuple nor unit"
)
.emit();
.delay_as_bug();
(self.err_args(provided_args.len()), None)
}
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_hir_typeck/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ fn report_unexpected_variant_res(tcx: TyCtxt<'_>, res: Res, qpath: &hir::QPath<'
/// # fn f(x: (isize, isize)) {}
/// f((1, 2));
/// ```
#[derive(Clone, Eq, PartialEq)]
#[derive(Copy, Clone, Eq, PartialEq)]
enum TupleArgumentsFlag {
DontTupleArguments,
TupleArguments,
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_middle/src/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,8 @@ pub enum ObligationCauseCode<'tcx> {
},

AscribeUserTypeProvePredicate(Span),

RustCall,
}

/// The 'location' at which we try to perform HIR-based wf checking.
Expand Down
27 changes: 21 additions & 6 deletions compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,25 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
}
}

if Some(trait_ref.def_id()) == tcx.lang_items().tuple_trait() {
match obligation.cause.code().peel_derives() {
ObligationCauseCode::RustCall => {
err.set_primary_message("functions with the \"rust-call\" ABI must take a single non-self tuple argument");
}
ObligationCauseCode::BindingObligation(def_id, _)
| ObligationCauseCode::ItemObligation(def_id)
if ty::ClosureKind::from_def_id(tcx, *def_id).is_some() =>
{
err.code(rustc_errors::error_code!(E0059));
err.set_primary_message(format!(
"type parameter to bare `{}` trait must be a tuple",
tcx.def_path_str(*def_id)
));
}
_ => {}
}
}

if Some(trait_ref.def_id()) == tcx.lang_items().drop_trait()
&& predicate_is_const
{
Expand Down Expand Up @@ -848,12 +867,8 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
);
}

let is_fn_trait = [
self.tcx.lang_items().fn_trait(),
self.tcx.lang_items().fn_mut_trait(),
self.tcx.lang_items().fn_once_trait(),
]
.contains(&Some(trait_ref.def_id()));
let is_fn_trait =
ty::ClosureKind::from_def_id(tcx, trait_ref.def_id()).is_some();
let is_target_feature_fn = if let ty::FnDef(def_id, _) =
*trait_ref.skip_binder().self_ty().kind()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2407,7 +2407,8 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
| ObligationCauseCode::CheckAssociatedTypeBounds { .. }
| ObligationCauseCode::LetElse
| ObligationCauseCode::BinOp { .. }
| ObligationCauseCode::AscribeUserTypeProvePredicate(..) => {}
| ObligationCauseCode::AscribeUserTypeProvePredicate(..)
| ObligationCauseCode::RustCall => {}
ObligationCauseCode::SliceOrArrayElem => {
err.note("slice and array elements must have `Sized` type");
}
Expand Down
31 changes: 31 additions & 0 deletions library/alloc/src/boxed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ use core::hash::{Hash, Hasher};
#[cfg(not(no_global_oom_handling))]
use core::iter::FromIterator;
use core::iter::{FusedIterator, Iterator};
#[cfg(not(bootstrap))]
use core::marker::Tuple;
use core::marker::{Destruct, Unpin, Unsize};
use core::mem;
use core::ops::{
Expand Down Expand Up @@ -1979,6 +1981,7 @@ impl<I: ExactSizeIterator + ?Sized, A: Allocator> ExactSizeIterator for Box<I, A
#[stable(feature = "fused", since = "1.26.0")]
impl<I: FusedIterator + ?Sized, A: Allocator> FusedIterator for Box<I, A> {}

#[cfg(bootstrap)]
#[stable(feature = "boxed_closure_impls", since = "1.35.0")]
impl<Args, F: FnOnce<Args> + ?Sized, A: Allocator> FnOnce<Args> for Box<F, A> {
type Output = <F as FnOnce<Args>>::Output;
Expand All @@ -1988,20 +1991,48 @@ impl<Args, F: FnOnce<Args> + ?Sized, A: Allocator> FnOnce<Args> for Box<F, A> {
}
}

#[cfg(not(bootstrap))]
#[stable(feature = "boxed_closure_impls", since = "1.35.0")]
impl<Args: Tuple, F: FnOnce<Args> + ?Sized, A: Allocator> FnOnce<Args> for Box<F, A> {
type Output = <F as FnOnce<Args>>::Output;

extern "rust-call" fn call_once(self, args: Args) -> Self::Output {
<F as FnOnce<Args>>::call_once(*self, args)
}
}

#[cfg(bootstrap)]
#[stable(feature = "boxed_closure_impls", since = "1.35.0")]
impl<Args, F: FnMut<Args> + ?Sized, A: Allocator> FnMut<Args> for Box<F, A> {
extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output {
<F as FnMut<Args>>::call_mut(self, args)
}
}

#[cfg(not(bootstrap))]
#[stable(feature = "boxed_closure_impls", since = "1.35.0")]
impl<Args: Tuple, F: FnMut<Args> + ?Sized, A: Allocator> FnMut<Args> for Box<F, A> {
extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output {
<F as FnMut<Args>>::call_mut(self, args)
}
}

#[cfg(bootstrap)]
#[stable(feature = "boxed_closure_impls", since = "1.35.0")]
impl<Args, F: Fn<Args> + ?Sized, A: Allocator> Fn<Args> for Box<F, A> {
extern "rust-call" fn call(&self, args: Args) -> Self::Output {
<F as Fn<Args>>::call(self, args)
}
}

#[cfg(not(bootstrap))]
#[stable(feature = "boxed_closure_impls", since = "1.35.0")]
impl<Args: Tuple, F: Fn<Args> + ?Sized, A: Allocator> Fn<Args> for Box<F, A> {
extern "rust-call" fn call(&self, args: Args) -> Self::Output {
<F as Fn<Args>>::call(self, args)
}
}

#[unstable(feature = "coerce_unsized", issue = "27732")]
impl<T: ?Sized + Unsize<U>, U: ?Sized, A: Allocator> CoerceUnsized<Box<U, A>> for Box<T, A> {}

Expand Down
1 change: 1 addition & 0 deletions library/alloc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@
#![feature(trusted_len)]
#![feature(trusted_random_access)]
#![feature(try_trait_v2)]
#![cfg_attr(not(bootstrap), feature(tuple_trait))]
#![feature(unchecked_math)]
#![feature(unicode_internals)]
#![feature(unsize)]
Expand Down
30 changes: 30 additions & 0 deletions library/core/src/const_closure.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::marker::Destruct;
#[cfg(not(bootstrap))]
use crate::marker::Tuple;

/// Struct representing a closure with mutably borrowed data.
///
Expand Down Expand Up @@ -44,6 +46,7 @@ impl<'a, CapturedData: ?Sized, Function> ConstFnMutClosure<&'a mut CapturedData,

macro_rules! impl_fn_mut_tuple {
($($var:ident)*) => {
#[cfg(bootstrap)]
#[allow(unused_parens)]
impl<'a, $($var,)* ClosureArguments, Function, ClosureReturnValue> const
FnOnce<ClosureArguments> for ConstFnMutClosure<($(&'a mut $var),*), Function>
Expand All @@ -56,6 +59,7 @@ macro_rules! impl_fn_mut_tuple {
self.call_mut(args)
}
}
#[cfg(bootstrap)]
#[allow(unused_parens)]
impl<'a, $($var,)* ClosureArguments, Function, ClosureReturnValue> const
FnMut<ClosureArguments> for ConstFnMutClosure<($(&'a mut $var),*), Function>
Expand All @@ -68,6 +72,32 @@ macro_rules! impl_fn_mut_tuple {
(self.func)(($($var),*), args)
}
}
#[cfg(not(bootstrap))]
#[allow(unused_parens)]
impl<'a, $($var,)* ClosureArguments: Tuple, Function, ClosureReturnValue> const
FnOnce<ClosureArguments> for ConstFnMutClosure<($(&'a mut $var),*), Function>
where
Function: ~const Fn(($(&mut $var),*), ClosureArguments) -> ClosureReturnValue+ ~const Destruct,
{
type Output = ClosureReturnValue;

extern "rust-call" fn call_once(mut self, args: ClosureArguments) -> Self::Output {
self.call_mut(args)
}
}
#[cfg(not(bootstrap))]
#[allow(unused_parens)]
impl<'a, $($var,)* ClosureArguments: Tuple, Function, ClosureReturnValue> const
FnMut<ClosureArguments> for ConstFnMutClosure<($(&'a mut $var),*), Function>
where
Function: ~const Fn(($(&mut $var),*), ClosureArguments)-> ClosureReturnValue,
{
extern "rust-call" fn call_mut(&mut self, args: ClosureArguments) -> Self::Output {
#[allow(non_snake_case)]
let ($($var),*) = &mut self.data;
(self.func)(($($var),*), args)
}
}
};
}
impl_fn_mut_tuple!(A);
Expand Down
Loading

0 comments on commit 7eef946

Please sign in to comment.