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

Add iter macro #137725

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
15 changes: 8 additions & 7 deletions compiler/rustc_ast_lowering/src/expr.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use std::assert_matches::assert_matches;
use std::ops::ControlFlow;
use std::sync::Arc;

Expand Down Expand Up @@ -1190,11 +1189,13 @@ impl<'hir> LoweringContext<'_, 'hir> {
let closure_def_id = self.local_def_id(closure_id);
let (binder_clause, generic_params) = self.lower_closure_binder(binder);

assert_matches!(
coroutine_kind,
CoroutineKind::Async { .. },
"only async closures are supported currently"
);
let coroutine_desugaring = match coroutine_kind {
CoroutineKind::Async { .. } => hir::CoroutineDesugaring::Async,
CoroutineKind::Gen { .. } => hir::CoroutineDesugaring::Gen,
CoroutineKind::AsyncGen { span, .. } => {
span_bug!(span, "only async closures and `iter!` closures are supported currently")
}
};

let body = self.with_new_scopes(fn_decl_span, |this| {
let inner_decl =
Expand Down Expand Up @@ -1238,7 +1239,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
// Lower this as a `CoroutineClosure`. That will ensure that HIR typeck
// knows that a `FnDecl` output type like `-> &str` actually means
// "coroutine that returns &str", rather than directly returning a `&str`.
kind: hir::ClosureKind::CoroutineClosure(hir::CoroutineDesugaring::Async),
kind: hir::ClosureKind::CoroutineClosure(coroutine_desugaring),
constness: hir::Constness::NotConst,
});
hir::ExprKind::Closure(c)
Expand Down
7 changes: 4 additions & 3 deletions compiler/rustc_ast_passes/src/feature_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -468,11 +468,12 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) {
for span in spans {
if (!visitor.features.coroutines() && !span.allows_unstable(sym::coroutines))
&& (!visitor.features.gen_blocks() && !span.allows_unstable(sym::gen_blocks))
&& (!visitor.features.yield_expr() && !span.allows_unstable(sym::yield_expr))
{
#[allow(rustc::untranslatable_diagnostic)]
// Don't know which of the two features to include in the
// error message, so I am arbitrarily picking one.
feature_err(&visitor.sess, sym::coroutines, *span, "yield syntax is experimental")
// Emit yield_expr as the error, since that will be sufficient. You can think of it
// as coroutines and gen_blocks imply yield_expr.
feature_err(&visitor.sess, sym::yield_expr, *span, "yield syntax is experimental")
.emit();
}
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_borrowck/src/type_check/input_output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
assert_matches!(
self.tcx().coroutine_kind(self.tcx().coroutine_for_closure(mir_def_id)),
Some(hir::CoroutineKind::Desugared(
hir::CoroutineDesugaring::Async,
hir::CoroutineDesugaring::Async | hir::CoroutineDesugaring::Gen,
hir::CoroutineSource::Closure
)),
"this needs to be modified if we're lowering non-async closures"
Expand Down
78 changes: 78 additions & 0 deletions compiler/rustc_builtin_macros/src/iter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use rustc_ast::ptr::P;
use rustc_ast::tokenstream::TokenStream;
use rustc_ast::{
CaptureBy, ClosureBinder, Const, CoroutineKind, DUMMY_NODE_ID, Expr, ExprKind, ast, token,
};
use rustc_errors::PResult;
use rustc_expand::base::{self, DummyResult, ExpandResult, ExtCtxt, MacroExpanderResult};
use rustc_span::Span;

pub(crate) fn expand<'cx>(
cx: &'cx mut ExtCtxt<'_>,
sp: Span,
tts: TokenStream,
) -> MacroExpanderResult<'cx> {
let closure = match parse_closure(cx, sp, tts) {
Ok(parsed) => parsed,
Err(err) => {
return ExpandResult::Ready(DummyResult::any(sp, err.emit()));
}
};

ExpandResult::Ready(base::MacEager::expr(closure))
}

fn parse_closure<'a>(
cx: &mut ExtCtxt<'a>,
span: Span,
stream: TokenStream,
) -> PResult<'a, P<Expr>> {
let mut parser = cx.new_parser_from_tts(stream);
let mut closure_parser = parser.clone();

let coroutine_kind = Some(CoroutineKind::Gen {
span,
closure_id: DUMMY_NODE_ID,
return_impl_trait_id: DUMMY_NODE_ID,
});

match closure_parser.parse_expr() {
Ok(mut closure) => {
if let ast::ExprKind::Closure(c) = &mut closure.kind {
if let Some(kind) = c.coroutine_kind {
cx.dcx().span_err(kind.span(), "only plain closures allowed in `iter!`");
}
c.coroutine_kind = coroutine_kind;
if closure_parser.token != token::Eof {
closure_parser.unexpected()?;
}
return Ok(closure);
}
}
Err(diag) => diag.cancel(),
}

let lo = parser.token.span.shrink_to_lo();
let block = parser.parse_block_tail(
lo,
ast::BlockCheckMode::Default,
rustc_parse::parser::AttemptLocalParseRecovery::No,
)?;
let fn_decl = cx.fn_decl(Default::default(), ast::FnRetTy::Default(span));
let closure = ast::Closure {
binder: ClosureBinder::NotPresent,
capture_clause: CaptureBy::Ref,
constness: Const::No,
coroutine_kind,
movability: ast::Movability::Movable,
fn_decl,
body: cx.expr_block(block),
fn_decl_span: span,
fn_arg_span: span,
};
if parser.token != token::Eof {
parser.unexpected()?;
}
let span = lo.to(parser.token.span);
Ok(cx.expr(span, ExprKind::Closure(Box::new(closure))))
Comment on lines +55 to +77
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this part can be ripped out now

}
2 changes: 2 additions & 0 deletions compiler/rustc_builtin_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ mod errors;
mod format;
mod format_foreign;
mod global_allocator;
mod iter;
mod log_syntax;
mod pattern_type;
mod source_util;
Expand Down Expand Up @@ -95,6 +96,7 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) {
include: source_util::expand_include,
include_bytes: source_util::expand_include_bytes,
include_str: source_util::expand_include_str,
iter: iter::expand,
line: source_util::expand_line,
log_syntax: log_syntax::expand_log_syntax,
module_path: source_util::expand_mod,
Expand Down
2 changes: 0 additions & 2 deletions compiler/rustc_hir/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1899,9 +1899,7 @@ impl CoroutineKind {
CoroutineKind::Coroutine(mov) => mov,
}
}
}

impl CoroutineKind {
pub fn is_fn_like(self) -> bool {
matches!(self, CoroutineKind::Desugared(_, CoroutineSource::Fn))
}
Expand Down
13 changes: 9 additions & 4 deletions compiler/rustc_hir_typeck/src/closure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,14 +199,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
)
}
hir::ClosureKind::CoroutineClosure(kind) => {
// async closures always return the type ascribed after the `->` (if present),
// and yield `()`.
let (bound_return_ty, bound_yield_ty) = match kind {
hir::CoroutineDesugaring::Gen => {
// `iter!` closures always return unit and yield the `Iterator::Item` type
// that we have to infer.
(tcx.types.unit, self.infcx.next_ty_var(expr_span))
}
hir::CoroutineDesugaring::Async => {
// async closures always return the type ascribed after the `->` (if present),
// and yield `()`.
(bound_sig.skip_binder().output(), tcx.types.unit)
}
hir::CoroutineDesugaring::Gen | hir::CoroutineDesugaring::AsyncGen => {
todo!("`gen` and `async gen` closures not supported yet")
hir::CoroutineDesugaring::AsyncGen => {
todo!("`async gen` closures not supported yet")
}
};
// Compute all of the variables that will be used to populate the coroutine.
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_parse/src/parser/stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -685,7 +685,7 @@ impl<'a> Parser<'a> {

/// Parses the rest of a block expression or function body.
/// Precondition: already parsed the '{'.
pub(crate) fn parse_block_tail(
pub fn parse_block_tail(
&mut self,
lo: Span,
s: BlockCheckMode,
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_trait_selection/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,6 @@ trait_selection_adjust_signature_remove_borrow = consider adjusting the signatur

trait_selection_ascribe_user_type_prove_predicate = ...so that the where clause holds

trait_selection_async_closure_not_fn = async closure does not implement `{$kind}` because it captures state from its environment

trait_selection_await_both_futures = consider `await`ing on both `Future`s
trait_selection_await_future = consider `await`ing on the `Future`
trait_selection_await_note = calling an async function returns a future
Expand Down Expand Up @@ -123,6 +121,8 @@ trait_selection_closure_kind_requirement = the requirement to implement `{$trait

trait_selection_compare_impl_item_obligation = ...so that the definition in impl matches the definition from the trait
trait_selection_consider_specifying_length = consider specifying the actual array length
trait_selection_coro_closure_not_fn = {$coro_kind}closure does not implement `{$kind}` because it captures state from its environment

trait_selection_data_flows = ...but data{$label_var1_exists ->
[true] {" "}from `{$label_var1}`
*[false] {""}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ use super::{
use crate::error_reporting::TypeErrCtxt;
use crate::error_reporting::infer::TyCategory;
use crate::error_reporting::traits::report_dyn_incompatibility;
use crate::errors::{
AsyncClosureNotFn, ClosureFnMutLabel, ClosureFnOnceLabel, ClosureKindMismatch,
};
use crate::errors::{ClosureFnMutLabel, ClosureFnOnceLabel, ClosureKindMismatch, CoroClosureNotFn};
use crate::infer::{self, InferCtxt, InferCtxtExt as _};
use crate::traits::query::evaluate_obligation::InferCtxtExt as _;
use crate::traits::{
Expand Down Expand Up @@ -904,9 +902,18 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
&& let ty::FnPtr(sig_tys, _) = by_ref_captures.kind()
&& !sig_tys.skip_binder().output().is_unit()
{
let mut err = self.dcx().create_err(AsyncClosureNotFn {
let coro_kind = match self
.tcx
.coroutine_kind(self.tcx.coroutine_for_closure(closure_def_id))
.unwrap()
{
rustc_hir::CoroutineKind::Desugared(desugaring, _) => format!("{desugaring:#}"),
coro => format!("{coro:#}"),
};
let mut err = self.dcx().create_err(CoroClosureNotFn {
span: self.tcx.def_span(closure_def_id),
kind: expected_kind.as_str(),
coro_kind,
});
self.note_obligation_cause(&mut err, &obligation);
return Some(err.emit());
Expand Down
5 changes: 3 additions & 2 deletions compiler/rustc_trait_selection/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,12 @@ pub struct ClosureFnMutLabel {
}

#[derive(Diagnostic)]
#[diag(trait_selection_async_closure_not_fn)]
pub(crate) struct AsyncClosureNotFn {
#[diag(trait_selection_coro_closure_not_fn)]
pub(crate) struct CoroClosureNotFn {
#[primary_span]
pub span: Span,
pub kind: &'static str,
pub coro_kind: String,
}

#[derive(Diagnostic)]
Expand Down
2 changes: 2 additions & 0 deletions library/core/src/iter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,8 @@ pub use self::adapters::{Intersperse, IntersperseWith};
issue = "42168"
)]
pub use self::range::Step;
#[unstable(feature = "iter_macro", issue = "none", reason = "generators are unstable")]
pub use self::sources::iter;
#[stable(feature = "iter_empty", since = "1.2.0")]
pub use self::sources::{Empty, empty};
#[unstable(
Expand Down
3 changes: 3 additions & 0 deletions library/core/src/iter/sources.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod empty;
mod from_coroutine;
mod from_fn;
mod generator;
mod once;
mod once_with;
mod repeat;
Expand All @@ -18,6 +19,8 @@ pub use self::empty::{Empty, empty};
pub use self::from_coroutine::{FromCoroutine, from_coroutine};
#[stable(feature = "iter_from_fn", since = "1.34.0")]
pub use self::from_fn::{FromFn, from_fn};
#[unstable(feature = "iter_macro", issue = "none", reason = "generators are unstable")]
pub use self::generator::iter;
#[stable(feature = "iter_once", since = "1.2.0")]
pub use self::once::{Once, once};
#[stable(feature = "iter_once_with", since = "1.43.0")]
Expand Down
26 changes: 26 additions & 0 deletions library/core/src/iter/sources/generator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/// Creates a new closure that returns an iterator where each iteration steps the given
/// generator to the next `yield` statement.
///
/// Similar to [`iter::from_fn`], but allows arbitrary control flow.
///
/// [`iter::from_fn`]: crate::iter::from_fn
///
/// # Examples
///
/// ```
/// #![feature(iter_macro, coroutines)]
///
/// let it = std::iter::iter!{
/// yield 1;
/// yield 2;
/// yield 3;
/// };
/// let v: Vec<_> = it.collect();
/// assert_eq!(v, [1, 2, 3]);
/// ```
#[unstable(feature = "iter_macro", issue = "none", reason = "generators are unstable")]
#[allow_internal_unstable(coroutines, iter_from_coroutine)]
#[cfg_attr(not(bootstrap), rustc_builtin_macro)]
pub macro iter($($t:tt)*) {
/* compiler-builtin */
}
24 changes: 24 additions & 0 deletions tests/ui/iterators/generator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//@ run-pass

#![feature(iter_macro, yield_expr)]

use std::iter::iter;

fn main() {
let i = iter! {
yield 0;
for x in 5..10 {
yield x * 2;
}
};
let mut i = i();
assert_eq!(i.next(), Some(0));
assert_eq!(i.next(), Some(10));
assert_eq!(i.next(), Some(12));
assert_eq!(i.next(), Some(14));
assert_eq!(i.next(), Some(16));
assert_eq!(i.next(), Some(18));
assert_eq!(i.next(), None);
assert_eq!(i.next(), None);
assert_eq!(i.next(), None);
}
24 changes: 24 additions & 0 deletions tests/ui/iterators/generator_args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//@ run-pass

#![feature(iter_macro, yield_expr)]

use std::iter::iter;

fn main() {
let i = iter! {|foo| {
yield foo;
for x in 5..10 {
yield x * 2;
}
}};
let mut i = i(3);
assert_eq!(i.next(), Some(3));
assert_eq!(i.next(), Some(10));
assert_eq!(i.next(), Some(12));
assert_eq!(i.next(), Some(14));
assert_eq!(i.next(), Some(16));
assert_eq!(i.next(), Some(18));
assert_eq!(i.next(), None);
assert_eq!(i.next(), None);
assert_eq!(i.next(), None);
}
27 changes: 27 additions & 0 deletions tests/ui/iterators/generator_capture.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//@ run-pass

#![feature(iter_macro, yield_expr)]

use std::iter::iter;

fn main() {
let i = {
let s = String::new();
iter! { move || {
yield s.len();
for x in 5..10 {
yield x * 2;
}
}}
};
let mut i = i();
assert_eq!(i.next(), Some(0));
assert_eq!(i.next(), Some(10));
assert_eq!(i.next(), Some(12));
assert_eq!(i.next(), Some(14));
assert_eq!(i.next(), Some(16));
assert_eq!(i.next(), Some(18));
assert_eq!(i.next(), None);
assert_eq!(i.next(), None);
assert_eq!(i.next(), None);
}
Loading
Loading