Merge pull request #21572 from goffrie/try

Implement the new homogeneous & heterogeneous try blocks
This commit is contained in:
Chayim Refael Friedman 2026-02-03 02:31:38 +00:00 committed by GitHub
commit ed58edc701
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 238 additions and 37 deletions

View file

@ -426,7 +426,7 @@ pub struct ExprCollector<'db> {
/// and we need to find the current definition. So we track the number of definitions we saw.
current_block_legacy_macro_defs_count: FxHashMap<Name, usize>,
current_try_block_label: Option<LabelId>,
current_try_block: Option<TryBlock>,
label_ribs: Vec<LabelRib>,
unowned_bindings: Vec<BindingId>,
@ -472,6 +472,13 @@ enum Awaitable {
No(&'static str),
}
enum TryBlock {
// `try { ... }`
Homogeneous { label: LabelId },
// `try bikeshed Ty { ... }`
Heterogeneous { label: LabelId },
}
#[derive(Debug, Default)]
struct BindingList {
map: FxHashMap<(Name, HygieneId), BindingId>,
@ -532,7 +539,7 @@ impl<'db> ExprCollector<'db> {
lang_items: OnceCell::new(),
store: ExpressionStoreBuilder::default(),
expander,
current_try_block_label: None,
current_try_block: None,
is_lowering_coroutine: false,
label_ribs: Vec::new(),
unowned_bindings: Vec::new(),
@ -1069,7 +1076,9 @@ impl<'db> ExprCollector<'db> {
self.alloc_expr(Expr::Let { pat, expr }, syntax_ptr)
}
ast::Expr::BlockExpr(e) => match e.modifier() {
Some(ast::BlockModifier::Try(_)) => self.desugar_try_block(e),
Some(ast::BlockModifier::Try { try_token: _, bikeshed_token: _, result_type }) => {
self.desugar_try_block(e, result_type)
}
Some(ast::BlockModifier::Unsafe(_)) => {
self.collect_block_(e, |id, statements, tail| Expr::Unsafe {
id,
@ -1344,7 +1353,7 @@ impl<'db> ExprCollector<'db> {
.map(|it| this.lower_type_ref_disallow_impl_trait(it));
let prev_is_lowering_coroutine = mem::take(&mut this.is_lowering_coroutine);
let prev_try_block_label = this.current_try_block_label.take();
let prev_try_block = this.current_try_block.take();
let awaitable = if e.async_token().is_some() {
Awaitable::Yes
@ -1369,7 +1378,7 @@ impl<'db> ExprCollector<'db> {
let capture_by =
if e.move_token().is_some() { CaptureBy::Value } else { CaptureBy::Ref };
this.is_lowering_coroutine = prev_is_lowering_coroutine;
this.current_try_block_label = prev_try_block_label;
this.current_try_block = prev_try_block;
this.alloc_expr(
Expr::Closure {
args: args.into(),
@ -1686,11 +1695,15 @@ impl<'db> ExprCollector<'db> {
/// Desugar `try { <stmts>; <expr> }` into `'<new_label>: { <stmts>; ::std::ops::Try::from_output(<expr>) }`,
/// `try { <stmts>; }` into `'<new_label>: { <stmts>; ::std::ops::Try::from_output(()) }`
/// and save the `<new_label>` to use it as a break target for desugaring of the `?` operator.
fn desugar_try_block(&mut self, e: BlockExpr) -> ExprId {
fn desugar_try_block(&mut self, e: BlockExpr, result_type: Option<ast::Type>) -> ExprId {
let try_from_output = self.lang_path(self.lang_items().TryTraitFromOutput);
let label = self.generate_new_name();
let label = self.alloc_label_desugared(Label { name: label }, AstPtr::new(&e).wrap_right());
let old_label = self.current_try_block_label.replace(label);
let try_block_info = match result_type {
Some(_) => TryBlock::Heterogeneous { label },
None => TryBlock::Homogeneous { label },
};
let old_try_block = self.current_try_block.replace(try_block_info);
let ptr = AstPtr::new(&e).upcast();
let (btail, expr_id) = self.with_labeled_rib(label, HygieneId::ROOT, |this| {
@ -1720,8 +1733,38 @@ impl<'db> ExprCollector<'db> {
unreachable!("block was lowered to non-block");
};
*tail = Some(next_tail);
self.current_try_block_label = old_label;
expr_id
self.current_try_block = old_try_block;
match result_type {
Some(ty) => {
// `{ let <name>: <ty> = <expr>; <name> }`
let name = self.generate_new_name();
let type_ref = self.lower_type_ref_disallow_impl_trait(ty);
let binding = self.alloc_binding(
name.clone(),
BindingAnnotation::Unannotated,
HygieneId::ROOT,
);
let pat = self.alloc_pat_desugared(Pat::Bind { id: binding, subpat: None });
self.add_definition_to_binding(binding, pat);
let tail_expr =
self.alloc_expr_desugared_with_ptr(Expr::Path(Path::from(name)), ptr);
self.alloc_expr_desugared_with_ptr(
Expr::Block {
id: None,
statements: Box::new([Statement::Let {
pat,
type_ref: Some(type_ref),
initializer: Some(expr_id),
else_branch: None,
}]),
tail: Some(tail_expr),
label: None,
},
ptr,
)
}
None => expr_id,
}
}
/// Desugar `ast::WhileExpr` from: `[opt_ident]: while <cond> <body>` into:
@ -1863,6 +1906,8 @@ impl<'db> ExprCollector<'db> {
/// ControlFlow::Continue(val) => val,
/// ControlFlow::Break(residual) =>
/// // If there is an enclosing `try {...}`:
/// break 'catch_target Residual::into_try_type(residual),
/// // If there is an enclosing `try bikeshed Ty {...}`:
/// break 'catch_target Try::from_residual(residual),
/// // Otherwise:
/// return Try::from_residual(residual),
@ -1873,7 +1918,6 @@ impl<'db> ExprCollector<'db> {
let try_branch = self.lang_path(lang_items.TryTraitBranch);
let cf_continue = self.lang_path(lang_items.ControlFlowContinue);
let cf_break = self.lang_path(lang_items.ControlFlowBreak);
let try_from_residual = self.lang_path(lang_items.TryTraitFromResidual);
let operand = self.collect_expr_opt(e.expr());
let try_branch = self.alloc_expr(try_branch.map_or(Expr::Missing, Expr::Path), syntax_ptr);
let expr = self
@ -1910,13 +1954,23 @@ impl<'db> ExprCollector<'db> {
guard: None,
expr: {
let it = self.alloc_expr(Expr::Path(Path::from(break_name)), syntax_ptr);
let callee = self
.alloc_expr(try_from_residual.map_or(Expr::Missing, Expr::Path), syntax_ptr);
let convert_fn = match self.current_try_block {
Some(TryBlock::Homogeneous { .. }) => {
self.lang_path(lang_items.ResidualIntoTryType)
}
Some(TryBlock::Heterogeneous { .. }) | None => {
self.lang_path(lang_items.TryTraitFromResidual)
}
};
let callee =
self.alloc_expr(convert_fn.map_or(Expr::Missing, Expr::Path), syntax_ptr);
let result =
self.alloc_expr(Expr::Call { callee, args: Box::new([it]) }, syntax_ptr);
self.alloc_expr(
match self.current_try_block_label {
Some(label) => Expr::Break { expr: Some(result), label: Some(label) },
match self.current_try_block {
Some(
TryBlock::Heterogeneous { label } | TryBlock::Homogeneous { label },
) => Expr::Break { expr: Some(result), label: Some(label) },
None => Expr::Return { expr: Some(result) },
},
syntax_ptr,

View file

@ -456,6 +456,7 @@ language_item_table! { LangItems =>
TryTraitFromOutput, sym::from_output, FunctionId;
TryTraitBranch, sym::branch, FunctionId;
TryTraitFromYeet, sym::from_yeet, FunctionId;
ResidualIntoTryType, sym::into_try_type, FunctionId;
PointerLike, sym::pointer_like, TraitId;

View file

@ -2152,10 +2152,11 @@ async fn main() {
let z: core::ops::ControlFlow<(), _> = try { () };
let w = const { 92 };
let t = 'a: { 92 };
let u = try bikeshed core::ops::ControlFlow<(), _> { () };
}
"#,
expect![[r#"
16..193 '{ ...2 }; }': ()
16..256 '{ ...) }; }': ()
26..27 'x': i32
30..43 'unsafe { 92 }': i32
39..41 '92': i32
@ -2176,6 +2177,13 @@ async fn main() {
176..177 't': i32
180..190 ''a: { 92 }': i32
186..188 '92': i32
200..201 'u': ControlFlow<(), ()>
204..253 'try bi...{ () }': ControlFlow<(), ()>
204..253 'try bi...{ () }': fn from_output<ControlFlow<(), ()>>(<ControlFlow<(), ()> as Try>::Output) -> ControlFlow<(), ()>
204..253 'try bi...{ () }': ControlFlow<(), ()>
204..253 'try bi...{ () }': ControlFlow<(), ()>
204..253 'try bi...{ () }': ControlFlow<(), ()>
249..251 '()': ()
"#]],
)
}

View file

@ -219,14 +219,16 @@ fn test() {
#[test]
fn infer_try_block() {
// FIXME: We should test more cases, but it currently doesn't work, since
// our labeled block type inference is broken.
check_types(
r#"
//- minicore: try, option
//- minicore: try, option, result, from
fn test() {
let x: Option<_> = try { Some(2)?; };
//^ Option<()>
let homogeneous = try { Ok::<(), u32>(())?; "hi" };
//^^^^^^^^^^^ Result<&'? str, u32>
let heterogeneous = try bikeshed Result<_, u64> { 1 };
//^^^^^^^^^^^^^ Result<i32, u64>
}
"#,
);

View file

@ -132,7 +132,7 @@ pub(crate) fn convert_closure_to_fn(acc: &mut Assists, ctx: &AssistContext<'_>)
);
}
if block.try_token().is_none()
if block.try_block_modifier().is_none()
&& block.unsafe_token().is_none()
&& block.label().is_none()
&& block.const_token().is_none()

View file

@ -859,7 +859,7 @@ impl FunctionBody {
ast::BlockExpr(block_expr) => {
let (constness, block) = match block_expr.modifier() {
Some(ast::BlockModifier::Const(_)) => (true, block_expr),
Some(ast::BlockModifier::Try(_)) => (false, block_expr),
Some(ast::BlockModifier::Try { .. }) => (false, block_expr),
Some(ast::BlockModifier::Label(label)) if label.lifetime().is_some() => (false, block_expr),
_ => continue,
};

View file

@ -49,7 +49,7 @@ pub fn is_closure_or_blk_with_modif(expr: &ast::Expr) -> bool {
block_expr.modifier(),
Some(
ast::BlockModifier::Async(_)
| ast::BlockModifier::Try(_)
| ast::BlockModifier::Try { .. }
| ast::BlockModifier::Const(_)
)
)
@ -148,7 +148,7 @@ pub fn walk_patterns_in_expr(start: &ast::Expr, cb: &mut dyn FnMut(ast::Pat)) {
block_expr.modifier(),
Some(
ast::BlockModifier::Async(_)
| ast::BlockModifier::Try(_)
| ast::BlockModifier::Try { .. }
| ast::BlockModifier::Const(_)
)
)
@ -291,7 +291,7 @@ pub fn for_each_tail_expr(expr: &ast::Expr, cb: &mut dyn FnMut(&ast::Expr)) {
match b.modifier() {
Some(
ast::BlockModifier::Async(_)
| ast::BlockModifier::Try(_)
| ast::BlockModifier::Try { .. }
| ast::BlockModifier::Const(_),
) => return cb(expr),

View file

@ -332,7 +332,7 @@ pub(crate) fn find_fn_or_blocks(
ast::BlockExpr(blk) => {
match blk.modifier() {
Some(ast::BlockModifier::Async(_)) => blk.syntax().clone(),
Some(ast::BlockModifier::Try(_)) if token_kind != T![return] => blk.syntax().clone(),
Some(ast::BlockModifier::Try { .. }) if token_kind != T![return] => blk.syntax().clone(),
_ => continue,
}
},
@ -404,8 +404,8 @@ fn nav_for_exit_points(
let blk_in_file = InFile::new(file_id, blk.into());
Some(expr_to_nav(db, blk_in_file, Some(async_tok)))
},
Some(ast::BlockModifier::Try(_)) if token_kind != T![return] => {
let try_tok = blk.try_token()?.text_range();
Some(ast::BlockModifier::Try { .. }) if token_kind != T![return] => {
let try_tok = blk.try_block_modifier()?.try_token()?.text_range();
let blk_in_file = InFile::new(file_id, blk.into());
Some(expr_to_nav(db, blk_in_file, Some(try_tok)))
},

View file

@ -473,7 +473,7 @@ pub(crate) fn highlight_exit_points(
},
ast::BlockExpr(blk) => match blk.modifier() {
Some(ast::BlockModifier::Async(t)) => hl_exit_points(sema, Some(t), blk.into()),
Some(ast::BlockModifier::Try(t)) if token.kind() != T![return] => {
Some(ast::BlockModifier::Try { try_token: t, .. }) if token.kind() != T![return] => {
hl_exit_points(sema, Some(t), blk.into())
},
_ => continue,

View file

@ -74,7 +74,7 @@ pub(super) fn try_expr(
ast::Fn(fn_) => sema.to_def(&fn_)?.ret_type(sema.db),
ast::Item(__) => return None,
ast::ClosureExpr(closure) => sema.type_of_expr(&closure.body()?)?.original,
ast::BlockExpr(block_expr) => if matches!(block_expr.modifier(), Some(ast::BlockModifier::Async(_) | ast::BlockModifier::Try(_)| ast::BlockModifier::Const(_))) {
ast::BlockExpr(block_expr) => if matches!(block_expr.modifier(), Some(ast::BlockModifier::Async(_) | ast::BlockModifier::Try { .. } | ast::BlockModifier::Const(_))) {
sema.type_of_expr(&block_expr.into())?.original
} else {
continue;

View file

@ -285,6 +285,7 @@ define_symbols! {
Into,
into_future,
into_iter,
into_try_type,
IntoFuture,
IntoIter,
IntoIterator,

View file

@ -976,11 +976,17 @@ fn break_expr(p: &mut Parser<'_>, r: Restrictions) -> CompletedMarker {
// test try_block_expr
// fn foo() {
// let _ = try {};
// let _ = try bikeshed T<U> {};
// }
fn try_block_expr(p: &mut Parser<'_>, m: Option<Marker>) -> CompletedMarker {
assert!(p.at(T![try]));
let m = m.unwrap_or_else(|| p.start());
let try_modifier = p.start();
p.bump(T![try]);
if p.eat_contextual_kw(T![bikeshed]) {
type_(p);
}
try_modifier.complete(p, TRY_BLOCK_MODIFIER);
if p.at(T!['{']) {
stmt_list(p);
} else {

View file

@ -114,6 +114,7 @@ pub enum SyntaxKind {
ATT_SYNTAX_KW,
AUTO_KW,
AWAIT_KW,
BIKESHED_KW,
BUILTIN_KW,
CLOBBER_ABI_KW,
DEFAULT_KW,
@ -285,6 +286,7 @@ pub enum SyntaxKind {
STRUCT,
TOKEN_TREE,
TRAIT,
TRY_BLOCK_MODIFIER,
TRY_EXPR,
TUPLE_EXPR,
TUPLE_FIELD,
@ -458,6 +460,7 @@ impl SyntaxKind {
| STRUCT
| TOKEN_TREE
| TRAIT
| TRY_BLOCK_MODIFIER
| TRY_EXPR
| TUPLE_EXPR
| TUPLE_FIELD
@ -596,6 +599,7 @@ impl SyntaxKind {
ASM_KW => "asm",
ATT_SYNTAX_KW => "att_syntax",
AUTO_KW => "auto",
BIKESHED_KW => "bikeshed",
BUILTIN_KW => "builtin",
CLOBBER_ABI_KW => "clobber_abi",
DEFAULT_KW => "default",
@ -698,6 +702,7 @@ impl SyntaxKind {
ASM_KW => true,
ATT_SYNTAX_KW => true,
AUTO_KW => true,
BIKESHED_KW => true,
BUILTIN_KW => true,
CLOBBER_ABI_KW => true,
DEFAULT_KW => true,
@ -788,6 +793,7 @@ impl SyntaxKind {
ASM_KW => true,
ATT_SYNTAX_KW => true,
AUTO_KW => true,
BIKESHED_KW => true,
BUILTIN_KW => true,
CLOBBER_ABI_KW => true,
DEFAULT_KW => true,
@ -941,6 +947,7 @@ impl SyntaxKind {
"asm" => ASM_KW,
"att_syntax" => ATT_SYNTAX_KW,
"auto" => AUTO_KW,
"bikeshed" => BIKESHED_KW,
"builtin" => BUILTIN_KW,
"clobber_abi" => CLOBBER_ABI_KW,
"default" => DEFAULT_KW,
@ -1112,6 +1119,7 @@ macro_rules ! T_ {
[asm] => { $ crate :: SyntaxKind :: ASM_KW };
[att_syntax] => { $ crate :: SyntaxKind :: ATT_SYNTAX_KW };
[auto] => { $ crate :: SyntaxKind :: AUTO_KW };
[bikeshed] => { $ crate :: SyntaxKind :: BIKESHED_KW };
[builtin] => { $ crate :: SyntaxKind :: BUILTIN_KW };
[clobber_abi] => { $ crate :: SyntaxKind :: CLOBBER_ABI_KW };
[default] => { $ crate :: SyntaxKind :: DEFAULT_KW };

View file

@ -45,7 +45,8 @@ SOURCE_FILE
WHITESPACE " "
EXPR_STMT
BLOCK_EXPR
TRY_KW "try"
TRY_BLOCK_MODIFIER
TRY_KW "try"
WHITESPACE " "
LITERAL
INT_NUMBER "92"

View file

@ -21,7 +21,42 @@ SOURCE_FILE
EQ "="
WHITESPACE " "
BLOCK_EXPR
TRY_KW "try"
TRY_BLOCK_MODIFIER
TRY_KW "try"
WHITESPACE " "
STMT_LIST
L_CURLY "{"
R_CURLY "}"
SEMICOLON ";"
WHITESPACE "\n "
LET_STMT
LET_KW "let"
WHITESPACE " "
WILDCARD_PAT
UNDERSCORE "_"
WHITESPACE " "
EQ "="
WHITESPACE " "
BLOCK_EXPR
TRY_BLOCK_MODIFIER
TRY_KW "try"
WHITESPACE " "
BIKESHED_KW "bikeshed"
WHITESPACE " "
PATH_TYPE
PATH
PATH_SEGMENT
NAME_REF
IDENT "T"
GENERIC_ARG_LIST
L_ANGLE "<"
TYPE_ARG
PATH_TYPE
PATH
PATH_SEGMENT
NAME_REF
IDENT "U"
R_ANGLE ">"
WHITESPACE " "
STMT_LIST
L_CURLY "{"

View file

@ -1,3 +1,4 @@
fn foo() {
let _ = try {};
let _ = try bikeshed T<U> {};
}

View file

@ -472,8 +472,11 @@ RefExpr =
TryExpr =
Attr* Expr '?'
TryBlockModifier =
'try' ('bikeshed' Type)?
BlockExpr =
Attr* Label? ('try' | 'unsafe' | ('async' 'move'?) | ('gen' 'move'?) | 'const') StmtList
Attr* Label? (TryBlockModifier | 'unsafe' | ('async' 'move'?) | ('gen' 'move'?) | 'const') StmtList
PrefixExpr =
Attr* op:('-' | '!' | '*') Expr

View file

@ -375,7 +375,11 @@ impl ast::Literal {
pub enum BlockModifier {
Async(SyntaxToken),
Unsafe(SyntaxToken),
Try(SyntaxToken),
Try {
try_token: SyntaxToken,
bikeshed_token: Option<SyntaxToken>,
result_type: Option<ast::Type>,
},
Const(SyntaxToken),
AsyncGen(SyntaxToken),
Gen(SyntaxToken),
@ -394,7 +398,13 @@ impl ast::BlockExpr {
})
.or_else(|| self.async_token().map(BlockModifier::Async))
.or_else(|| self.unsafe_token().map(BlockModifier::Unsafe))
.or_else(|| self.try_token().map(BlockModifier::Try))
.or_else(|| {
let modifier = self.try_block_modifier()?;
let try_token = modifier.try_token()?;
let bikeshed_token = modifier.bikeshed_token();
let result_type = modifier.ty();
Some(BlockModifier::Try { try_token, bikeshed_token, result_type })
})
.or_else(|| self.const_token().map(BlockModifier::Const))
.or_else(|| self.label().map(BlockModifier::Label))
}

View file

@ -323,6 +323,8 @@ impl BlockExpr {
#[inline]
pub fn stmt_list(&self) -> Option<StmtList> { support::child(&self.syntax) }
#[inline]
pub fn try_block_modifier(&self) -> Option<TryBlockModifier> { support::child(&self.syntax) }
#[inline]
pub fn async_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![async]) }
#[inline]
pub fn const_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![const]) }
@ -331,8 +333,6 @@ impl BlockExpr {
#[inline]
pub fn move_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![move]) }
#[inline]
pub fn try_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![try]) }
#[inline]
pub fn unsafe_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![unsafe]) }
}
pub struct BoxPat {
@ -1630,6 +1630,19 @@ impl Trait {
#[inline]
pub fn unsafe_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![unsafe]) }
}
pub struct TryBlockModifier {
pub(crate) syntax: SyntaxNode,
}
impl TryBlockModifier {
#[inline]
pub fn ty(&self) -> Option<Type> { support::child(&self.syntax) }
#[inline]
pub fn bikeshed_token(&self) -> Option<SyntaxToken> {
support::token(&self.syntax, T![bikeshed])
}
#[inline]
pub fn try_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![try]) }
}
pub struct TryExpr {
pub(crate) syntax: SyntaxNode,
}
@ -6320,6 +6333,38 @@ impl fmt::Debug for Trait {
f.debug_struct("Trait").field("syntax", &self.syntax).finish()
}
}
impl AstNode for TryBlockModifier {
#[inline]
fn kind() -> SyntaxKind
where
Self: Sized,
{
TRY_BLOCK_MODIFIER
}
#[inline]
fn can_cast(kind: SyntaxKind) -> bool { kind == TRY_BLOCK_MODIFIER }
#[inline]
fn cast(syntax: SyntaxNode) -> Option<Self> {
if Self::can_cast(syntax.kind()) { Some(Self { syntax }) } else { None }
}
#[inline]
fn syntax(&self) -> &SyntaxNode { &self.syntax }
}
impl hash::Hash for TryBlockModifier {
fn hash<H: hash::Hasher>(&self, state: &mut H) { self.syntax.hash(state); }
}
impl Eq for TryBlockModifier {}
impl PartialEq for TryBlockModifier {
fn eq(&self, other: &Self) -> bool { self.syntax == other.syntax }
}
impl Clone for TryBlockModifier {
fn clone(&self) -> Self { Self { syntax: self.syntax.clone() } }
}
impl fmt::Debug for TryBlockModifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TryBlockModifier").field("syntax", &self.syntax).finish()
}
}
impl AstNode for TryExpr {
#[inline]
fn kind() -> SyntaxKind
@ -9979,6 +10024,11 @@ impl std::fmt::Display for Trait {
std::fmt::Display::fmt(self.syntax(), f)
}
}
impl std::fmt::Display for TryBlockModifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f)
}
}
impl std::fmt::Display for TryExpr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f)

View file

@ -953,6 +953,9 @@ pub mod ops {
#[lang = "from_residual"]
fn from_residual(residual: R) -> Self;
}
pub const trait Residual<O>: Sized {
type TryType: [const] Try<Output = O, Residual = Self>;
}
#[lang = "Try"]
pub trait Try: FromResidual<Self::Residual> {
type Output;
@ -962,6 +965,12 @@ pub mod ops {
#[lang = "branch"]
fn branch(self) -> ControlFlow<Self::Residual, Self::Output>;
}
#[lang = "into_try_type"]
pub const fn residual_into_try_type<R: [const] Residual<O>, O>(
r: R,
) -> <R as Residual<O>>::TryType {
FromResidual::from_residual(r)
}
impl<B, C> Try for ControlFlow<B, C> {
type Output = C;
@ -985,6 +994,10 @@ pub mod ops {
}
}
}
impl<B, C> Residual<C> for ControlFlow<B, Infallible> {
type TryType = ControlFlow<B, C>;
}
// region:option
impl<T> Try for Option<T> {
type Output = T;
@ -1008,6 +1021,10 @@ pub mod ops {
}
}
}
impl<T> const Residual<T> for Option<Infallible> {
type TryType = Option<T>;
}
// endregion:option
// region:result
// region:from
@ -1037,10 +1054,14 @@ pub mod ops {
}
}
}
impl<T, E> const Residual<T> for Result<Infallible, E> {
type TryType = Result<T, E>;
}
// endregion:from
// endregion:result
}
pub use self::try_::{ControlFlow, FromResidual, Try};
pub use self::try_::{ControlFlow, FromResidual, Residual, Try};
// endregion:try
// region:add

View file

@ -112,7 +112,7 @@ const RESERVED: &[&str] = &[
// keywords that are keywords only in specific parse contexts
#[doc(alias = "WEAK_KEYWORDS")]
const CONTEXTUAL_KEYWORDS: &[&str] =
&["macro_rules", "union", "default", "raw", "dyn", "auto", "yeet", "safe"];
&["macro_rules", "union", "default", "raw", "dyn", "auto", "yeet", "safe", "bikeshed"];
// keywords we use for special macro expansions
const CONTEXTUAL_BUILTIN_KEYWORDS: &[&str] = &[
"asm",