Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 757b8ef

Browse files
committedFeb 10, 2024
Auto merge of #120712 - compiler-errors:async-closures-harmonize, r=oli-obk
Harmonize `AsyncFn` implementations, make async closures conditionally impl `Fn*` traits This PR implements several changes to the built-in and libcore-provided implementations of `Fn*` and `AsyncFn*` to address two problems: 1. async closures do not implement the `Fn*` family traits, leading to breakage: https://crater-reports.s3.amazonaws.com/pr-120361/index.html 2. *references* to async closures do not implement `AsyncFn*`, as a consequence of the existing blanket impls of the shape `AsyncFn for F where F: Fn, F::Output: Future`. In order to fix (1.), we implement `Fn` traits appropriately for async closures. It turns out that async closures can: * always implement `FnOnce`, meaning that they're drop-in compatible with `FnOnce`-bound combinators like `Option::map`. * conditionally implement `Fn`/`FnMut` if they have no captures, which means that existing usages of async closures should *probably* work without breakage (crater checking this: #120712 (comment)). In order to fix (2.), we make all of the built-in callables implement `AsyncFn*` via built-in impls, and instead adjust the blanket impls for `AsyncFn*` provided by libcore to match the blanket impls for `Fn*`.
2 parents 68125c7 + 540be28 commit 757b8ef

File tree

21 files changed

+704
-261
lines changed

21 files changed

+704
-261
lines changed
 

‎compiler/rustc_hir_typeck/src/callee.rs

+34-17
Original file line numberDiff line numberDiff line change
@@ -261,23 +261,40 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
261261
adjusted_ty: Ty<'tcx>,
262262
opt_arg_exprs: Option<&'tcx [hir::Expr<'tcx>]>,
263263
) -> Option<(Option<Adjustment<'tcx>>, MethodCallee<'tcx>)> {
264+
// HACK(async_closures): For async closures, prefer `AsyncFn*`
265+
// over `Fn*`, since all async closures implement `FnOnce`, but
266+
// choosing that over `AsyncFn`/`AsyncFnMut` would be more restrictive.
267+
// For other callables, just prefer `Fn*` for perf reasons.
268+
//
269+
// The order of trait choices here is not that big of a deal,
270+
// since it just guides inference (and our choice of autoref).
271+
// Though in the future, I'd like typeck to choose:
272+
// `Fn > AsyncFn > FnMut > AsyncFnMut > FnOnce > AsyncFnOnce`
273+
// ...or *ideally*, we just have `LendingFn`/`LendingFnMut`, which
274+
// would naturally unify these two trait hierarchies in the most
275+
// general way.
276+
let call_trait_choices = if self.shallow_resolve(adjusted_ty).is_coroutine_closure() {
277+
[
278+
(self.tcx.lang_items().async_fn_trait(), sym::async_call, true),
279+
(self.tcx.lang_items().async_fn_mut_trait(), sym::async_call_mut, true),
280+
(self.tcx.lang_items().async_fn_once_trait(), sym::async_call_once, false),
281+
(self.tcx.lang_items().fn_trait(), sym::call, true),
282+
(self.tcx.lang_items().fn_mut_trait(), sym::call_mut, true),
283+
(self.tcx.lang_items().fn_once_trait(), sym::call_once, false),
284+
]
285+
} else {
286+
[
287+
(self.tcx.lang_items().fn_trait(), sym::call, true),
288+
(self.tcx.lang_items().fn_mut_trait(), sym::call_mut, true),
289+
(self.tcx.lang_items().fn_once_trait(), sym::call_once, false),
290+
(self.tcx.lang_items().async_fn_trait(), sym::async_call, true),
291+
(self.tcx.lang_items().async_fn_mut_trait(), sym::async_call_mut, true),
292+
(self.tcx.lang_items().async_fn_once_trait(), sym::async_call_once, false),
293+
]
294+
};
295+
264296
// Try the options that are least restrictive on the caller first.
265-
for (opt_trait_def_id, method_name, borrow) in [
266-
(self.tcx.lang_items().fn_trait(), Ident::with_dummy_span(sym::call), true),
267-
(self.tcx.lang_items().fn_mut_trait(), Ident::with_dummy_span(sym::call_mut), true),
268-
(self.tcx.lang_items().fn_once_trait(), Ident::with_dummy_span(sym::call_once), false),
269-
(self.tcx.lang_items().async_fn_trait(), Ident::with_dummy_span(sym::async_call), true),
270-
(
271-
self.tcx.lang_items().async_fn_mut_trait(),
272-
Ident::with_dummy_span(sym::async_call_mut),
273-
true,
274-
),
275-
(
276-
self.tcx.lang_items().async_fn_once_trait(),
277-
Ident::with_dummy_span(sym::async_call_once),
278-
false,
279-
),
280-
] {
297+
for (opt_trait_def_id, method_name, borrow) in call_trait_choices {
281298
let Some(trait_def_id) = opt_trait_def_id else { continue };
282299

283300
let opt_input_type = opt_arg_exprs.map(|arg_exprs| {
@@ -294,7 +311,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
294311

295312
if let Some(ok) = self.lookup_method_in_trait(
296313
self.misc(call_expr.span),
297-
method_name,
314+
Ident::with_dummy_span(method_name),
298315
trait_def_id,
299316
adjusted_ty,
300317
opt_input_type.as_ref().map(slice::from_ref),

‎compiler/rustc_hir_typeck/src/closure.rs

+12-5
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
5656
// It's always helpful for inference if we know the kind of
5757
// closure sooner rather than later, so first examine the expected
5858
// type, and see if can glean a closure kind from there.
59-
let (expected_sig, expected_kind) = match expected.to_option(self) {
60-
Some(ty) => {
61-
self.deduce_closure_signature(self.try_structurally_resolve_type(expr_span, ty))
62-
}
63-
None => (None, None),
59+
let (expected_sig, expected_kind) = match closure.kind {
60+
hir::ClosureKind::Closure => match expected.to_option(self) {
61+
Some(ty) => {
62+
self.deduce_closure_signature(self.try_structurally_resolve_type(expr_span, ty))
63+
}
64+
None => (None, None),
65+
},
66+
// We don't want to deduce a signature from `Fn` bounds for coroutines
67+
// or coroutine-closures, because the former does not implement `Fn`
68+
// ever, and the latter's signature doesn't correspond to the coroutine
69+
// type that it returns.
70+
hir::ClosureKind::Coroutine(_) | hir::ClosureKind::CoroutineClosure(_) => (None, None),
6471
};
6572

6673
let ClosureSignatures { bound_sig, mut liberated_sig } =

‎compiler/rustc_infer/src/traits/error_reporting/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -177,12 +177,13 @@ pub fn report_object_safety_error<'tcx>(
177177
)));
178178
}
179179
impls => {
180-
let types = impls
180+
let mut types = impls
181181
.iter()
182182
.map(|t| {
183183
with_no_trimmed_paths!(format!(" {}", tcx.type_of(*t).instantiate_identity(),))
184184
})
185185
.collect::<Vec<_>>();
186+
types.sort();
186187
err.help(format!(
187188
"the following types implement the trait, consider defining an enum where each \
188189
variant holds one of these types, implementing `{}` for this new enum and using \

‎compiler/rustc_trait_selection/src/solve/assembly/structural_traits.rs

+123-55
Original file line numberDiff line numberDiff line change
@@ -323,34 +323,27 @@ pub(in crate::solve) fn extract_tupled_inputs_and_output_from_async_callable<'tc
323323
self_ty: Ty<'tcx>,
324324
goal_kind: ty::ClosureKind,
325325
env_region: ty::Region<'tcx>,
326-
) -> Result<
327-
(ty::Binder<'tcx, (Ty<'tcx>, Ty<'tcx>, Ty<'tcx>)>, Option<ty::Predicate<'tcx>>),
328-
NoSolution,
329-
> {
326+
) -> Result<(ty::Binder<'tcx, (Ty<'tcx>, Ty<'tcx>, Ty<'tcx>)>, Vec<ty::Predicate<'tcx>>), NoSolution>
327+
{
330328
match *self_ty.kind() {
331329
ty::CoroutineClosure(def_id, args) => {
332330
let args = args.as_coroutine_closure();
333331
let kind_ty = args.kind_ty();
334-
335-
if let Some(closure_kind) = kind_ty.to_opt_closure_kind() {
332+
let sig = args.coroutine_closure_sig().skip_binder();
333+
let mut nested = vec![];
334+
let coroutine_ty = if let Some(closure_kind) = kind_ty.to_opt_closure_kind() {
336335
if !closure_kind.extends(goal_kind) {
337336
return Err(NoSolution);
338337
}
339-
Ok((
340-
args.coroutine_closure_sig().map_bound(|sig| {
341-
let coroutine_ty = sig.to_coroutine_given_kind_and_upvars(
342-
tcx,
343-
args.parent_args(),
344-
tcx.coroutine_for_closure(def_id),
345-
goal_kind,
346-
env_region,
347-
args.tupled_upvars_ty(),
348-
args.coroutine_captures_by_ref_ty(),
349-
);
350-
(sig.tupled_inputs_ty, sig.return_ty, coroutine_ty)
351-
}),
352-
None,
353-
))
338+
sig.to_coroutine_given_kind_and_upvars(
339+
tcx,
340+
args.parent_args(),
341+
tcx.coroutine_for_closure(def_id),
342+
goal_kind,
343+
env_region,
344+
args.tupled_upvars_ty(),
345+
args.coroutine_captures_by_ref_ty(),
346+
)
354347
} else {
355348
let async_fn_kind_trait_def_id =
356349
tcx.require_lang_item(LangItem::AsyncFnKindHelper, None);
@@ -367,42 +360,117 @@ pub(in crate::solve) fn extract_tupled_inputs_and_output_from_async_callable<'tc
367360
// the goal kind <= the closure kind. As a projection `AsyncFnKindHelper::Upvars`
368361
// will project to the right upvars for the generator, appending the inputs and
369362
// coroutine upvars respecting the closure kind.
370-
Ok((
371-
args.coroutine_closure_sig().map_bound(|sig| {
372-
let tupled_upvars_ty = Ty::new_projection(
373-
tcx,
374-
upvars_projection_def_id,
375-
[
376-
ty::GenericArg::from(kind_ty),
377-
Ty::from_closure_kind(tcx, goal_kind).into(),
378-
env_region.into(),
379-
sig.tupled_inputs_ty.into(),
380-
args.tupled_upvars_ty().into(),
381-
args.coroutine_captures_by_ref_ty().into(),
382-
],
383-
);
384-
let coroutine_ty = sig.to_coroutine(
385-
tcx,
386-
args.parent_args(),
387-
Ty::from_closure_kind(tcx, goal_kind),
388-
tcx.coroutine_for_closure(def_id),
389-
tupled_upvars_ty,
390-
);
391-
(sig.tupled_inputs_ty, sig.return_ty, coroutine_ty)
392-
}),
393-
Some(
394-
ty::TraitRef::new(
395-
tcx,
396-
async_fn_kind_trait_def_id,
397-
[kind_ty, Ty::from_closure_kind(tcx, goal_kind)],
398-
)
399-
.to_predicate(tcx),
400-
),
401-
))
402-
}
363+
nested.push(
364+
ty::TraitRef::new(
365+
tcx,
366+
async_fn_kind_trait_def_id,
367+
[kind_ty, Ty::from_closure_kind(tcx, goal_kind)],
368+
)
369+
.to_predicate(tcx),
370+
);
371+
let tupled_upvars_ty = Ty::new_projection(
372+
tcx,
373+
upvars_projection_def_id,
374+
[
375+
ty::GenericArg::from(kind_ty),
376+
Ty::from_closure_kind(tcx, goal_kind).into(),
377+
env_region.into(),
378+
sig.tupled_inputs_ty.into(),
379+
args.tupled_upvars_ty().into(),
380+
args.coroutine_captures_by_ref_ty().into(),
381+
],
382+
);
383+
sig.to_coroutine(
384+
tcx,
385+
args.parent_args(),
386+
Ty::from_closure_kind(tcx, goal_kind),
387+
tcx.coroutine_for_closure(def_id),
388+
tupled_upvars_ty,
389+
)
390+
};
391+
392+
Ok((
393+
args.coroutine_closure_sig().rebind((
394+
sig.tupled_inputs_ty,
395+
sig.return_ty,
396+
coroutine_ty,
397+
)),
398+
nested,
399+
))
403400
}
404401

405-
ty::FnDef(..) | ty::FnPtr(..) | ty::Closure(..) => Err(NoSolution),
402+
ty::FnDef(..) | ty::FnPtr(..) => {
403+
let bound_sig = self_ty.fn_sig(tcx);
404+
let sig = bound_sig.skip_binder();
405+
let future_trait_def_id = tcx.require_lang_item(LangItem::Future, None);
406+
// `FnDef` and `FnPtr` only implement `AsyncFn*` when their
407+
// return type implements `Future`.
408+
let nested = vec![
409+
bound_sig
410+
.rebind(ty::TraitRef::new(tcx, future_trait_def_id, [sig.output()]))
411+
.to_predicate(tcx),
412+
];
413+
let future_output_def_id = tcx
414+
.associated_items(future_trait_def_id)
415+
.filter_by_name_unhygienic(sym::Output)
416+
.next()
417+
.unwrap()
418+
.def_id;
419+
let future_output_ty = Ty::new_projection(tcx, future_output_def_id, [sig.output()]);
420+
Ok((
421+
bound_sig.rebind((Ty::new_tup(tcx, sig.inputs()), sig.output(), future_output_ty)),
422+
nested,
423+
))
424+
}
425+
ty::Closure(_, args) => {
426+
let args = args.as_closure();
427+
let bound_sig = args.sig();
428+
let sig = bound_sig.skip_binder();
429+
let future_trait_def_id = tcx.require_lang_item(LangItem::Future, None);
430+
// `Closure`s only implement `AsyncFn*` when their return type
431+
// implements `Future`.
432+
let mut nested = vec![
433+
bound_sig
434+
.rebind(ty::TraitRef::new(tcx, future_trait_def_id, [sig.output()]))
435+
.to_predicate(tcx),
436+
];
437+
438+
// Additionally, we need to check that the closure kind
439+
// is still compatible.
440+
let kind_ty = args.kind_ty();
441+
if let Some(closure_kind) = kind_ty.to_opt_closure_kind() {
442+
if !closure_kind.extends(goal_kind) {
443+
return Err(NoSolution);
444+
}
445+
} else {
446+
let async_fn_kind_trait_def_id =
447+
tcx.require_lang_item(LangItem::AsyncFnKindHelper, None);
448+
// When we don't know the closure kind (and therefore also the closure's upvars,
449+
// which are computed at the same time), we must delay the computation of the
450+
// generator's upvars. We do this using the `AsyncFnKindHelper`, which as a trait
451+
// goal functions similarly to the old `ClosureKind` predicate, and ensures that
452+
// the goal kind <= the closure kind. As a projection `AsyncFnKindHelper::Upvars`
453+
// will project to the right upvars for the generator, appending the inputs and
454+
// coroutine upvars respecting the closure kind.
455+
nested.push(
456+
ty::TraitRef::new(
457+
tcx,
458+
async_fn_kind_trait_def_id,
459+
[kind_ty, Ty::from_closure_kind(tcx, goal_kind)],
460+
)
461+
.to_predicate(tcx),
462+
);
463+
}
464+
465+
let future_output_def_id = tcx
466+
.associated_items(future_trait_def_id)
467+
.filter_by_name_unhygienic(sym::Output)
468+
.next()
469+
.unwrap()
470+
.def_id;
471+
let future_output_ty = Ty::new_projection(tcx, future_output_def_id, [sig.output()]);
472+
Ok((bound_sig.rebind((sig.inputs()[0], sig.output(), future_output_ty)), nested))
473+
}
406474

407475
ty::Bool
408476
| ty::Char
There was a problem loading the remainder of the diff.

0 commit comments

Comments
 (0)
Failed to load comments.