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

extract expected return type for async fn generators #64999

Merged
Merged
Show file tree
Hide file tree
Changes from 4 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
22 changes: 14 additions & 8 deletions src/librustc/hir/lowering/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,14 @@ impl LoweringContext<'_> {
hir::MatchSource::Normal,
),
ExprKind::Async(capture_clause, closure_node_id, ref block) => {
self.make_async_expr(capture_clause, closure_node_id, None, block.span, |this| {
this.with_new_scopes(|this| this.lower_block_expr(block))
})
self.make_async_expr(
capture_clause,
closure_node_id,
None,
block.span,
hir::AsyncGeneratorKind::Block,
|this| this.with_new_scopes(|this| this.lower_block_expr(block)),
)
}
ExprKind::Await(ref expr) => self.lower_expr_await(e.span, expr),
ExprKind::Closure(
Expand Down Expand Up @@ -440,6 +445,7 @@ impl LoweringContext<'_> {
closure_node_id: NodeId,
ret_ty: Option<AstP<Ty>>,
span: Span,
async_gen_kind: hir::AsyncGeneratorKind,
body: impl FnOnce(&mut LoweringContext<'_>) -> hir::Expr,
) -> hir::ExprKind {
let capture_clause = self.lower_capture_clause(capture_clause);
Expand All @@ -453,7 +459,7 @@ impl LoweringContext<'_> {
};
let decl = self.lower_fn_decl(&ast_decl, None, /* impl trait allowed */ false, None);
let body_id = self.lower_fn_body(&ast_decl, |this| {
this.generator_kind = Some(hir::GeneratorKind::Async);
this.generator_kind = Some(hir::GeneratorKind::Async(async_gen_kind));
body(this)
});

Expand Down Expand Up @@ -505,7 +511,7 @@ impl LoweringContext<'_> {
/// ```
fn lower_expr_await(&mut self, await_span: Span, expr: &Expr) -> hir::ExprKind {
match self.generator_kind {
Some(hir::GeneratorKind::Async) => {},
Some(hir::GeneratorKind::Async(_)) => {},
Some(hir::GeneratorKind::Gen) |
None => {
let mut err = struct_span_err!(
Expand Down Expand Up @@ -710,7 +716,7 @@ impl LoweringContext<'_> {
Movability::Static => hir::GeneratorMovability::Static,
})
},
Some(hir::GeneratorKind::Async) => {
Some(hir::GeneratorKind::Async(_)) => {
bug!("non-`async` closure body turned `async` during lowering");
},
None => {
Expand Down Expand Up @@ -769,7 +775,7 @@ impl LoweringContext<'_> {
None
};
let async_body = this.make_async_expr(
capture_clause, closure_id, async_ret_ty, body.span,
capture_clause, closure_id, async_ret_ty, body.span, hir::AsyncGeneratorKind::Closure,
|this| {
this.with_new_scopes(|this| this.lower_expr(body))
}
Expand Down Expand Up @@ -988,7 +994,7 @@ impl LoweringContext<'_> {
fn lower_expr_yield(&mut self, span: Span, opt_expr: Option<&Expr>) -> hir::ExprKind {
match self.generator_kind {
Some(hir::GeneratorKind::Gen) => {},
Some(hir::GeneratorKind::Async) => {
Some(hir::GeneratorKind::Async(_)) => {
span_err!(
self.sess,
span,
Expand Down
6 changes: 5 additions & 1 deletion src/librustc/hir/lowering/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1222,7 +1222,11 @@ impl LoweringContext<'_> {
}

let async_expr = this.make_async_expr(
CaptureBy::Value, closure_id, None, body.span,
CaptureBy::Value,
closure_id,
None,
body.span,
hir::AsyncGeneratorKind::Fn,
|this| {
// Create a block from the user's function body:
let user_body = this.lower_block_expr(body);
Expand Down
35 changes: 31 additions & 4 deletions src/librustc/hir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1366,17 +1366,43 @@ impl Body {
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, HashStable,
RustcEncodable, RustcDecodable, Hash, Debug, Copy)]
pub enum GeneratorKind {
/// An `async` block or function.
Async,
/// An explicit `async` block or the body of an async function.
Async(AsyncGeneratorKind),

/// A generator literal created via a `yield` inside a closure.
Gen,
}

impl fmt::Display for GeneratorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
GeneratorKind::Async(k) => fmt::Display::fmt(k, f),
GeneratorKind::Gen => f.write_str("generator"),
}
}
}

/// The type of source expression that caused this generator to be created.
// Not `IsAsync` because we want to eventually add support for `AsyncGen`
nikomatsakis marked this conversation as resolved.
Show resolved Hide resolved
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, HashStable,
RustcEncodable, RustcDecodable, Hash, Debug, Copy)]
pub enum AsyncGeneratorKind {
/// An explicit `async` block written by the user.
Block,

/// An explicit `async` block written by the user.
Closure,

/// The `async` block generated as the body of an async function.
Fn,
}

impl fmt::Display for AsyncGeneratorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
GeneratorKind::Async => "`async` object",
GeneratorKind::Gen => "generator",
AsyncGeneratorKind::Block => "`async` block",
AsyncGeneratorKind::Closure => "`async` closure body",
AsyncGeneratorKind::Fn => "`async` fn body",
nikomatsakis marked this conversation as resolved.
Show resolved Hide resolved
})
}
}
Expand Down Expand Up @@ -1758,6 +1784,7 @@ pub struct Destination {
pub enum GeneratorMovability {
/// May contain self-references, `!Unpin`.
Static,

/// Must not contain self-references, `Unpin`.
Movable,
}
Expand Down
121 changes: 118 additions & 3 deletions src/librustc_typeck/check/closure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
) -> ClosureSignatures<'tcx> {
debug!("sig_of_closure_no_expectation()");

let bound_sig = self.supplied_sig_of_closure(expr_def_id, decl);
let bound_sig = self.supplied_sig_of_closure(expr_def_id, decl, body);

self.closure_sigs(expr_def_id, body, bound_sig)
}
Expand Down Expand Up @@ -490,7 +490,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
//
// (See comment on `sig_of_closure_with_expectation` for the
// meaning of these letters.)
let supplied_sig = self.supplied_sig_of_closure(expr_def_id, decl);
let supplied_sig = self.supplied_sig_of_closure(expr_def_id, decl, body);

debug!(
"check_supplied_sig_against_expectation: supplied_sig={:?}",
Expand Down Expand Up @@ -591,14 +591,31 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
&self,
expr_def_id: DefId,
decl: &hir::FnDecl,
body: &hir::Body,
) -> ty::PolyFnSig<'tcx> {
let astconv: &dyn AstConv<'_> = self;

debug!(
"supplied_sig_of_closure(decl={:?}, body.generator_kind={:?})",
decl,
body.generator_kind,
);

// First, convert the types that the user supplied (if any).
let supplied_arguments = decl.inputs.iter().map(|a| astconv.ast_ty_to_ty(a));
let supplied_return = match decl.output {
hir::Return(ref output) => astconv.ast_ty_to_ty(&output),
hir::DefaultReturn(_) => astconv.ty_infer(None, decl.output.span()),
hir::DefaultReturn(_) => match body.generator_kind {
// In the case of the async block that we create for a function body,
// we expect the return type of the block to match that of the enclosing
// function.
Some(hir::GeneratorKind::Async(hir::AsyncGeneratorKind::Fn)) => {
debug!("supplied_sig_of_closure: closure is async fn body");
self.deduce_future_output_from_obligations(expr_def_id)
}

_ => astconv.ty_infer(None, decl.output.span()),
}
};

let result = ty::Binder::bind(self.tcx.mk_fn_sig(
Expand All @@ -620,6 +637,104 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
result
}

/// Invoked when we are translating the generator that results
/// from desugaring an `async fn`. Returns the "sugared" return
/// type of the `async fn` -- that is, the return type that the
/// user specified. The "desugared" return type is a `impl
/// Future<Output = T>`, so we do this by searching through the
/// obligations to extract the `T`.
Copy link
Member

@eddyb eddyb Oct 3, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe leave a FIXME/open an issue somewhere about generalizing this so it can rely entirely on the impls involved and not have to know about the Future trait at all?
(This would be long-term and probably significantly more complicated than this targeted fix)

fn deduce_future_output_from_obligations(
nikomatsakis marked this conversation as resolved.
Show resolved Hide resolved
&self,
expr_def_id: DefId,
) -> Ty<'tcx> {
debug!("deduce_future_output_from_obligations(expr_def_id={:?})", expr_def_id);

let ret_coercion =
self.ret_coercion
nikomatsakis marked this conversation as resolved.
Show resolved Hide resolved
.as_ref()
.unwrap_or_else(|| span_bug!(
self.tcx.def_span(expr_def_id),
"async fn generator outside of a fn"
));

// In practice, the return type of the surrounding function is
// always a (not yet resolved) inference variable, because it
// is the hidden type for an `impl Trait` that we are going to
// be inferring.
let ret_ty = ret_coercion.borrow().expected_ty();
let ret_ty = self.inh.infcx.shallow_resolve(ret_ty);
nikomatsakis marked this conversation as resolved.
Show resolved Hide resolved
let ret_vid = match ret_ty.sty {
ty::Infer(ty::TyVar(ret_vid)) => ret_vid,
_ => {
span_bug!(
self.tcx.def_span(expr_def_id),
"async fn generator return type not an inference variable"
)
}
};

// Search for a pending obligation like
//
// `<R as Future>::Output = T`
//
// where R is the return type we are expecting. This type `T`
// will be our output.
let output_ty = self.obligations_for_self_ty(ret_vid)
.find_map(|(_, obligation)| {
if let ty::Predicate::Projection(ref proj_predicate) = obligation.predicate {
self.deduce_future_output_from_projection(
obligation.cause.span,
proj_predicate
)
} else {
None
}
})
.unwrap();

debug!("deduce_future_output_from_obligations: output_ty={:?}", output_ty);
output_ty
}

/// Given a projection like
///
/// `<_ as Future>::Output = T`
///
/// returns `Some(T)`. If the projection is for some other trait,
/// returns `None`.
fn deduce_future_output_from_projection(
nikomatsakis marked this conversation as resolved.
Show resolved Hide resolved
&self,
cause_span: Span,
projection: &ty::PolyProjectionPredicate<'tcx>,
) -> Option<Ty<'tcx>> {
debug!("deduce_future_output_from_projection(projection={:?})", projection);

let trait_ref = projection.to_poly_trait_ref(self.tcx);
let future_trait = self.tcx.lang_items().future_trait().unwrap();
if trait_ref.def_id() != future_trait {
debug!("deduce_future_output_from_projection: not a future");
return None;
}

// The `Future` trait has only one associted item, `Output`,
// so check that this is what we see.
let output_assoc_item = self.tcx.associated_items(future_trait).nth(0).unwrap().def_id;
if output_assoc_item != projection.projection_def_id() {
span_bug!(
cause_span,
"projecting associated item `{:?}` from future, which is not Output `{:?}`",
projection.projection_def_id(),
output_assoc_item,
);
}

// Extract the type from the projection.
let output_ty = projection.skip_binder().ty;
nikomatsakis marked this conversation as resolved.
Show resolved Hide resolved
let output_ty = self.resolve_vars_if_possible(&output_ty);
debug!("deduce_future_output_from_projection: output_ty={:?}", output_ty);
Some(output_ty)
}

/// Converts the types that the user supplied, in case that doing
/// so should yield an error, but returns back a signature where
/// all parameters are of type `TyErr`.
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_typeck/check/generator_interior.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> {
expr_and_pat_count: 0,
source: match self.kind { // Guess based on the kind of the current generator.
hir::GeneratorKind::Gen => hir::YieldSource::Yield,
hir::GeneratorKind::Async => hir::YieldSource::Await,
hir::GeneratorKind::Async(_) => hir::YieldSource::Await,
},
}));

Expand Down
2 changes: 1 addition & 1 deletion src/librustc_typeck/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4534,7 +4534,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let item_id = self.tcx().hir().get_parent_node(self.body_id);
if let Some(body_id) = self.tcx().hir().maybe_body_owned_by(item_id) {
let body = self.tcx().hir().body(body_id);
if let Some(hir::GeneratorKind::Async) = body.generator_kind {
if let Some(hir::GeneratorKind::Async(_)) = body.generator_kind {
let sp = expr.span;
// Check for `Future` implementations by constructing a predicate to
// prove: `<T as Future>::Output == U`
Expand Down
25 changes: 25 additions & 0 deletions src/test/ui/async-await/return-ty-raw-ptr-coercion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Check that we apply unsizing coercions based on the return type.
//
// Also serves as a regression test for #60424.
//
// edition:2018
// check-pass

#![allow(warnings)]

use std::fmt::Debug;

const TMP: u32 = 22;

// Coerce from `Box<"asdf">` to `Box<dyn Debug>`.
nikomatsakis marked this conversation as resolved.
Show resolved Hide resolved
fn raw_pointer_coercion() {
fn sync_example() -> *const u32 {
&TMP
}

async fn async_example() -> *const u32 {
&TMP
}
}

fn main() {}
34 changes: 34 additions & 0 deletions src/test/ui/async-await/return-ty-unsize-coercion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Check that we apply unsizing coercions based on the return type.
nikomatsakis marked this conversation as resolved.
Show resolved Hide resolved
//
// Also serves as a regression test for #60424.
//
// edition:2018
// check-pass

#![allow(warnings)]

use std::fmt::Debug;

// Coerce from `Box<"asdf">` to `Box<dyn Debug>`.
nikomatsakis marked this conversation as resolved.
Show resolved Hide resolved
fn unsize_trait_coercion() {
fn sync_example() -> Box<dyn Debug> {
Box::new("asdf")
}

async fn async_example() -> Box<dyn Debug> {
Box::new("asdf")
}
}

// Coerce from `Box<[u32; N]>` to `Box<[32]>`.
fn unsize_slice_coercion() {
fn sync_example() -> Box<[u32]> {
Box::new([0])
}

async fn async_example() -> Box<[u32]> {
Box::new([0])
}
}

fn main() {}