Skip to content
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

Harmonize AsyncFn implementations, make async closures conditionally impl Fn* traits #120712

Merged
merged 7 commits into from
Feb 10, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
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
51 changes: 34 additions & 17 deletions compiler/rustc_hir_typeck/src/callee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,23 +260,40 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
adjusted_ty: Ty<'tcx>,
opt_arg_exprs: Option<&'tcx [hir::Expr<'tcx>]>,
) -> Option<(Option<Adjustment<'tcx>>, MethodCallee<'tcx>)> {
// HACK(async_closures): For async closures, prefer `AsyncFn*`
// over `Fn*`, since all async closures implement `FnOnce`, but
// choosing that over `AsyncFn`/`AsyncFnMut` would be more restrictive.
// For other callables, just prefer `Fn*` for perf reasons.
//
// The order of trait choices here is not that big of a deal,
// since it just guides inference (and our choice of autoref).
// Though in the future, I'd like typeck to choose:
// `Fn > AsyncFn > FnMut > AsyncFnMut > FnOnce > AsyncFnOnce`
// ...or *ideally*, we just have `LendingFn`/`LendingFnMut`, which
// would naturally unify these two trait hierarchies in the most
// general way.
let call_trait_choices = if self.shallow_resolve(adjusted_ty).is_coroutine_closure() {
[
(self.tcx.lang_items().async_fn_trait(), sym::async_call, true),
(self.tcx.lang_items().async_fn_mut_trait(), sym::async_call_mut, true),
(self.tcx.lang_items().async_fn_once_trait(), sym::async_call_once, false),
(self.tcx.lang_items().fn_trait(), sym::call, true),
(self.tcx.lang_items().fn_mut_trait(), sym::call_mut, true),
(self.tcx.lang_items().fn_once_trait(), sym::call_once, false),
]
} else {
[
(self.tcx.lang_items().fn_trait(), sym::call, true),
(self.tcx.lang_items().fn_mut_trait(), sym::call_mut, true),
(self.tcx.lang_items().fn_once_trait(), sym::call_once, false),
(self.tcx.lang_items().async_fn_trait(), sym::async_call, true),
(self.tcx.lang_items().async_fn_mut_trait(), sym::async_call_mut, true),
(self.tcx.lang_items().async_fn_once_trait(), sym::async_call_once, false),
]
};

// Try the options that are least restrictive on the caller first.
for (opt_trait_def_id, method_name, borrow) in [
(self.tcx.lang_items().fn_trait(), Ident::with_dummy_span(sym::call), true),
(self.tcx.lang_items().fn_mut_trait(), Ident::with_dummy_span(sym::call_mut), true),
(self.tcx.lang_items().fn_once_trait(), Ident::with_dummy_span(sym::call_once), false),
(self.tcx.lang_items().async_fn_trait(), Ident::with_dummy_span(sym::async_call), true),
(
self.tcx.lang_items().async_fn_mut_trait(),
Ident::with_dummy_span(sym::async_call_mut),
true,
),
(
self.tcx.lang_items().async_fn_once_trait(),
Ident::with_dummy_span(sym::async_call_once),
false,
),
] {
for (opt_trait_def_id, method_name, borrow) in call_trait_choices {
let Some(trait_def_id) = opt_trait_def_id else { continue };

let opt_input_type = opt_arg_exprs.map(|arg_exprs| {
Expand All @@ -293,7 +310,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {

if let Some(ok) = self.lookup_method_in_trait(
self.misc(call_expr.span),
method_name,
Ident::with_dummy_span(method_name),
trait_def_id,
adjusted_ty,
opt_input_type.as_ref().map(slice::from_ref),
Expand Down
17 changes: 12 additions & 5 deletions compiler/rustc_hir_typeck/src/closure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// It's always helpful for inference if we know the kind of
// closure sooner rather than later, so first examine the expected
// type, and see if can glean a closure kind from there.
let (expected_sig, expected_kind) = match expected.to_option(self) {
Some(ty) => {
self.deduce_closure_signature(self.try_structurally_resolve_type(expr_span, ty))
}
None => (None, None),
let (expected_sig, expected_kind) = match closure.kind {
hir::ClosureKind::Closure => match expected.to_option(self) {
Some(ty) => {
self.deduce_closure_signature(self.try_structurally_resolve_type(expr_span, ty))
}
None => (None, None),
},
// We don't want to deduce a signature from `Fn` bounds for coroutines
// or coroutine-closures, because the former does not implement `Fn`
// ever, and the latter's signature doesn't correspond to the coroutine
// type that it returns.
hir::ClosureKind::Coroutine(_) | hir::ClosureKind::CoroutineClosure(_) => (None, None),
};

let ClosureSignatures { bound_sig, mut liberated_sig } =
Expand Down
178 changes: 123 additions & 55 deletions compiler/rustc_trait_selection/src/solve/assembly/structural_traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,34 +318,27 @@ pub(in crate::solve) fn extract_tupled_inputs_and_output_from_async_callable<'tc
self_ty: Ty<'tcx>,
goal_kind: ty::ClosureKind,
env_region: ty::Region<'tcx>,
) -> Result<
(ty::Binder<'tcx, (Ty<'tcx>, Ty<'tcx>, Ty<'tcx>)>, Option<ty::Predicate<'tcx>>),
NoSolution,
> {
) -> Result<(ty::Binder<'tcx, (Ty<'tcx>, Ty<'tcx>, Ty<'tcx>)>, Vec<ty::Predicate<'tcx>>), NoSolution>
{
match *self_ty.kind() {
ty::CoroutineClosure(def_id, args) => {
let args = args.as_coroutine_closure();
let kind_ty = args.kind_ty();

if let Some(closure_kind) = kind_ty.to_opt_closure_kind() {
let sig = args.coroutine_closure_sig().skip_binder();
let mut nested = vec![];
let coroutine_ty = if let Some(closure_kind) = kind_ty.to_opt_closure_kind() {
if !closure_kind.extends(goal_kind) {
return Err(NoSolution);
}
Ok((
args.coroutine_closure_sig().map_bound(|sig| {
let coroutine_ty = sig.to_coroutine_given_kind_and_upvars(
tcx,
args.parent_args(),
tcx.coroutine_for_closure(def_id),
goal_kind,
env_region,
args.tupled_upvars_ty(),
args.coroutine_captures_by_ref_ty(),
);
(sig.tupled_inputs_ty, sig.return_ty, coroutine_ty)
}),
None,
))
sig.to_coroutine_given_kind_and_upvars(
tcx,
args.parent_args(),
tcx.coroutine_for_closure(def_id),
goal_kind,
env_region,
args.tupled_upvars_ty(),
args.coroutine_captures_by_ref_ty(),
)
} else {
let async_fn_kind_trait_def_id =
tcx.require_lang_item(LangItem::AsyncFnKindHelper, None);
Expand All @@ -362,42 +355,117 @@ pub(in crate::solve) fn extract_tupled_inputs_and_output_from_async_callable<'tc
// the goal kind <= the closure kind. As a projection `AsyncFnKindHelper::Upvars`
// will project to the right upvars for the generator, appending the inputs and
// coroutine upvars respecting the closure kind.
Ok((
args.coroutine_closure_sig().map_bound(|sig| {
let tupled_upvars_ty = Ty::new_projection(
tcx,
upvars_projection_def_id,
[
ty::GenericArg::from(kind_ty),
Ty::from_closure_kind(tcx, goal_kind).into(),
env_region.into(),
sig.tupled_inputs_ty.into(),
args.tupled_upvars_ty().into(),
args.coroutine_captures_by_ref_ty().into(),
],
);
let coroutine_ty = sig.to_coroutine(
tcx,
args.parent_args(),
Ty::from_closure_kind(tcx, goal_kind),
tcx.coroutine_for_closure(def_id),
tupled_upvars_ty,
);
(sig.tupled_inputs_ty, sig.return_ty, coroutine_ty)
}),
Some(
ty::TraitRef::new(
tcx,
async_fn_kind_trait_def_id,
[kind_ty, Ty::from_closure_kind(tcx, goal_kind)],
)
.to_predicate(tcx),
),
))
}
nested.push(
ty::TraitRef::new(
tcx,
async_fn_kind_trait_def_id,
[kind_ty, Ty::from_closure_kind(tcx, goal_kind)],
)
.to_predicate(tcx),
);
let tupled_upvars_ty = Ty::new_projection(
tcx,
upvars_projection_def_id,
[
ty::GenericArg::from(kind_ty),
Ty::from_closure_kind(tcx, goal_kind).into(),
env_region.into(),
sig.tupled_inputs_ty.into(),
args.tupled_upvars_ty().into(),
args.coroutine_captures_by_ref_ty().into(),
],
);
sig.to_coroutine(
tcx,
args.parent_args(),
Ty::from_closure_kind(tcx, goal_kind),
tcx.coroutine_for_closure(def_id),
tupled_upvars_ty,
)
};

Ok((
args.coroutine_closure_sig().rebind((
sig.tupled_inputs_ty,
sig.return_ty,
coroutine_ty,
)),
nested,
))
}

ty::FnDef(..) | ty::FnPtr(..) => {
let bound_sig = self_ty.fn_sig(tcx);
let sig = bound_sig.skip_binder();
let future_trait_def_id = tcx.require_lang_item(LangItem::Future, None);
// `FnDef` and `FnPtr` only implement `AsyncFn*` when their
// return type implements `Future`.
let nested = vec![
bound_sig
.rebind(ty::TraitRef::new(tcx, future_trait_def_id, [sig.output()]))
.to_predicate(tcx),
];
let future_output_def_id = tcx
.associated_items(future_trait_def_id)
.filter_by_name_unhygienic(sym::Output)
.next()
.unwrap()
.def_id;
let future_output_ty = Ty::new_projection(tcx, future_output_def_id, [sig.output()]);
Ok((
bound_sig.rebind((Ty::new_tup(tcx, sig.inputs()), sig.output(), future_output_ty)),
nested,
))
}
ty::Closure(_, args) => {
let args = args.as_closure();
let bound_sig = args.sig();
let sig = bound_sig.skip_binder();
let future_trait_def_id = tcx.require_lang_item(LangItem::Future, None);
// `Closure`s only implement `AsyncFn*` when their return type
// implements `Future`.
let mut nested = vec![
bound_sig
.rebind(ty::TraitRef::new(tcx, future_trait_def_id, [sig.output()]))
.to_predicate(tcx),
];

// Additionally, we need to check that the closure kind
// is still compatible.
let kind_ty = args.kind_ty();
if let Some(closure_kind) = kind_ty.to_opt_closure_kind() {
if !closure_kind.extends(goal_kind) {
return Err(NoSolution);
}
} else {
let async_fn_kind_trait_def_id =
tcx.require_lang_item(LangItem::AsyncFnKindHelper, None);
// When we don't know the closure kind (and therefore also the closure's upvars,
// which are computed at the same time), we must delay the computation of the
// generator's upvars. We do this using the `AsyncFnKindHelper`, which as a trait
// goal functions similarly to the old `ClosureKind` predicate, and ensures that
// the goal kind <= the closure kind. As a projection `AsyncFnKindHelper::Upvars`
// will project to the right upvars for the generator, appending the inputs and
// coroutine upvars respecting the closure kind.
nested.push(
ty::TraitRef::new(
tcx,
async_fn_kind_trait_def_id,
[kind_ty, Ty::from_closure_kind(tcx, goal_kind)],
)
.to_predicate(tcx),
);
}
compiler-errors marked this conversation as resolved.
Show resolved Hide resolved

ty::FnDef(..) | ty::FnPtr(..) | ty::Closure(..) => Err(NoSolution),
let future_output_def_id = tcx
.associated_items(future_trait_def_id)
.filter_by_name_unhygienic(sym::Output)
.next()
.unwrap()
.def_id;
let future_output_ty = Ty::new_projection(tcx, future_output_def_id, [sig.output()]);
Ok((bound_sig.rebind((sig.inputs()[0], sig.output(), future_output_ty)), nested))
}

ty::Bool
| ty::Char
Expand Down
Loading
Loading