From 381366f1dd820a2d3c827cc123cb28e6d5f678f1 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 5 Oct 2022 19:15:07 +0200 Subject: [PATCH] Diagnose incorrect usages of the question mark operator --- crates/hir-expand/src/mod_path.rs | 1 + crates/hir-expand/src/name.rs | 2 + crates/hir-ty/src/infer.rs | 13 +-- crates/hir-ty/src/infer/expr.rs | 99 +++++++++++++++++-- crates/hir-ty/src/method_resolution.rs | 18 ++++ crates/hir-ty/src/tests/traits.rs | 92 +---------------- crates/hir/src/diagnostics.rs | 14 ++- crates/hir/src/lib.rs | 38 ++++--- .../src/handlers/incorrect_try_expr.rs | 37 +++++++ .../src/handlers/not_implemented.rs | 35 +++++++ crates/ide-diagnostics/src/lib.rs | 4 + crates/ide/src/hover/tests.rs | 22 ++++- crates/test-utils/src/minicore.rs | 39 +++++++- 13 files changed, 289 insertions(+), 125 deletions(-) create mode 100644 crates/ide-diagnostics/src/handlers/incorrect_try_expr.rs create mode 100644 crates/ide-diagnostics/src/handlers/not_implemented.rs diff --git a/crates/hir-expand/src/mod_path.rs b/crates/hir-expand/src/mod_path.rs index d7586d129b..68413df420 100644 --- a/crates/hir-expand/src/mod_path.rs +++ b/crates/hir-expand/src/mod_path.rs @@ -259,6 +259,7 @@ macro_rules! __known_path { (core::future::Future) => {}; (core::future::IntoFuture) => {}; (core::ops::Try) => {}; + (core::ops::FromResidual) => {}; ($path:path) => { compile_error!("Please register your known path in the path module") }; diff --git a/crates/hir-expand/src/name.rs b/crates/hir-expand/src/name.rs index 2679a1c360..8a735b965a 100644 --- a/crates/hir-expand/src/name.rs +++ b/crates/hir-expand/src/name.rs @@ -279,6 +279,8 @@ pub mod known { RangeToInclusive, RangeTo, Range, + Residual, + FromResidual, Neg, Not, None, diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 31e56dec62..4315ccab6c 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -190,7 +190,9 @@ pub(crate) type InferResult = Result, TypeError>; pub enum InferenceDiagnostic { NoSuchField { expr: ExprId }, BreakOutsideOfLoop { expr: ExprId, is_break: bool }, + IncorrectTryTarget { expr: ExprId }, MismatchedArgCount { call_expr: ExprId, expected: usize, found: usize }, + DoesNotImplement { expr: ExprId, trait_: TraitId, ty: Ty }, } /// A mismatch between an expected and an inferred type. @@ -905,17 +907,6 @@ impl<'a> InferenceContext<'a> { self.db.trait_data(trait_).associated_type_by_name(&name![Item]) } - fn resolve_ops_try_ok(&self) -> Option { - // FIXME resolve via lang_item once try v2 is stable - let path = path![core::ops::Try]; - let trait_ = self.resolver.resolve_known_trait(self.db.upcast(), &path)?; - let trait_data = self.db.trait_data(trait_); - trait_data - // FIXME remove once try v2 is stable - .associated_type_by_name(&name![Ok]) - .or_else(|| trait_data.associated_type_by_name(&name![Output])) - } - fn resolve_ops_neg_output(&self) -> Option { let trait_ = self.resolve_lang_item(name![neg])?.as_trait()?; self.db.trait_data(trait_).associated_type_by_name(&name![Output]) diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index f56108b26c..59ab50d071 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -19,24 +19,24 @@ use hir_def::{ resolver::resolver_for_expr, ConstParamId, FieldId, ItemContainerId, Lookup, }; -use hir_expand::name::Name; +use hir_expand::{name, name::Name}; use stdx::always; use syntax::ast::RangeOp; use crate::{ autoderef::{self, Autoderef}, consteval, - infer::{coerce::CoerceMany, find_continuable, BreakableKind}, + infer::{coerce::CoerceMany, find_continuable, path, BreakableKind}, lower::{ const_or_path_to_chalk, generic_arg_to_chalk, lower_to_chalk_mutability, ParamLoweringMode, }, mapping::{from_chalk, ToChalk}, method_resolution::{self, lang_names_for_bin_op, VisibleFromModule}, primitive::{self, UintTy}, - static_lifetime, to_chalk_trait_id, + static_lifetime, to_assoc_type_id, to_chalk_trait_id, utils::{generics, Generics}, - AdtId, Binders, CallableDefId, FnPointer, FnSig, FnSubst, Interner, Rawness, Scalar, - Substitution, TraitRef, Ty, TyBuilder, TyExt, TyKind, + AdtId, AliasEq, AliasTy, Binders, CallableDefId, FnPointer, FnSig, FnSubst, Interner, + ProjectionTy, Rawness, Scalar, Substitution, TraitRef, Ty, TyBuilder, TyExt, TyKind, }; use super::{ @@ -564,9 +564,29 @@ impl<'a> InferenceContext<'a> { let inner_ty = self.infer_expr_inner(*expr, &Expectation::none()); self.resolve_associated_type(inner_ty, self.resolve_future_future_output()) } - Expr::Try { expr } => { - let inner_ty = self.infer_expr_inner(*expr, &Expectation::none()); - self.resolve_associated_type(inner_ty, self.resolve_ops_try_ok()) + &Expr::Try { expr } => { + let inner_ty = self.infer_expr_inner(expr, &Expectation::none()); + match self.resolve_try_impl_for(inner_ty.clone()) { + Some((_, Some((output, residual)))) => { + if let Some((_trait, false)) = + self.implements_from_residual(self.return_ty.clone(), residual) + { + self.push_diagnostic(InferenceDiagnostic::IncorrectTryTarget { + expr: tgt_expr, + }); + } + output + } + Some((trait_, None)) => { + self.push_diagnostic(InferenceDiagnostic::DoesNotImplement { + expr, + trait_, + ty: inner_ty, + }); + self.err_ty() + } + None => self.err_ty(), + } } Expr::Cast { expr, type_ref } => { // FIXME: propagate the "castable to" expectation (and find a test case that shows this is necessary) @@ -1530,4 +1550,67 @@ impl<'a> InferenceContext<'a> { let ctx = self.breakables.pop().expect("breakable stack broken"); (ctx.may_break.then(|| ctx.coerce.complete()), res) } + + /// Check whether `ty` implements `FromResidual` + fn implements_from_residual(&mut self, ty: Ty, r: Ty) -> Option<(hir_def::TraitId, bool)> { + let from_residual_trait = self + .resolver + .resolve_known_trait(self.db.upcast(), &(super::path![core::ops::FromResidual]))?; + let r = GenericArgData::Ty(r).intern(Interner); + let b = TyBuilder::trait_ref(self.db, from_residual_trait); + if b.remaining() != 2 { + return Some((from_residual_trait, false)); + } + let trait_ref = b.push(ty).push(r).build(); + Some((from_residual_trait, self.table.try_obligation(trait_ref.cast(Interner)).is_some())) + } + + fn resolve_try_impl_for(&mut self, ty: Ty) -> Option<(hir_def::TraitId, Option<(Ty, Ty)>)> { + let path = path![core::ops::Try]; + let trait_ = self.resolver.resolve_known_trait(self.db.upcast(), &path)?; + + let trait_ref = TyBuilder::trait_ref(self.db, trait_).push(ty).build(); + let substitution = trait_ref.substitution.clone(); + self.push_obligation(trait_ref.clone().cast(Interner)); + + let trait_data = self.db.trait_data(trait_); + let output = trait_data.associated_type_by_name(&name![Output]); + let residual = trait_data.associated_type_by_name(&name![Residual]); + + let output_ty = match output { + Some(output) => { + let output_ty = self.table.new_type_var(); + let alias_eq = AliasEq { + alias: AliasTy::Projection(ProjectionTy { + associated_ty_id: to_assoc_type_id(output), + substitution: substitution.clone(), + }), + ty: output_ty.clone(), + }; + self.push_obligation(alias_eq.cast(Interner)); + output_ty + } + None => self.err_ty(), + }; + let residual_ty = match residual { + Some(residual) => { + let residual_ty = self.table.new_type_var(); + let alias_eq = AliasEq { + alias: AliasTy::Projection(ProjectionTy { + associated_ty_id: to_assoc_type_id(residual), + substitution, + }), + ty: residual_ty.clone(), + }; + self.push_obligation(alias_eq.cast(Interner)); + residual_ty + } + None => self.err_ty(), + }; + // FIXME: We are doing the work twice here I think? + Some(( + trait_, + self.table.try_obligation(trait_ref.cast(Interner)).map(|_| (output_ty, residual_ty)), + )) + } } diff --git a/crates/hir-ty/src/method_resolution.rs b/crates/hir-ty/src/method_resolution.rs index 5998680dcd..224dc21e94 100644 --- a/crates/hir-ty/src/method_resolution.rs +++ b/crates/hir-ty/src/method_resolution.rs @@ -1111,6 +1111,24 @@ pub fn resolve_indexing_op( } None } +/// Returns the receiver type for the try branch trait call. +pub fn resolve_branch_op( + db: &dyn HirDatabase, + env: Arc, + ty: Canonical, + try_trait: TraitId, +) -> Option { + let mut table = InferenceTable::new(db, env.clone()); + let ty = table.instantiate_canonical(ty); + let (deref_chain, adj) = autoderef_method_receiver(&mut table, ty); + for (ty, adj) in deref_chain.into_iter().zip(adj) { + let goal = generic_implements_goal(db, env.clone(), try_trait, &ty); + if db.trait_solve(env.krate, goal.cast(Interner)).is_some() { + return Some(adj); + } + } + None +} macro_rules! check_that { ($cond:expr) => { diff --git a/crates/hir-ty/src/tests/traits.rs b/crates/hir-ty/src/tests/traits.rs index 555b6972fb..b91172e334 100644 --- a/crates/hir-ty/src/tests/traits.rs +++ b/crates/hir-ty/src/tests/traits.rs @@ -162,98 +162,16 @@ fn test() { ); } -#[test] -fn infer_try() { - check_types( - r#" -//- /main.rs crate:main deps:core -fn test() { - let r: Result = Result::Ok(1); - let v = r?; - v; -} //^ i32 - -//- /core.rs crate:core -pub mod ops { - pub trait Try { - type Ok; - type Error; - } -} - -pub mod result { - pub enum Result { - Ok(O), - Err(E) - } - - impl crate::ops::Try for Result { - type Ok = O; - type Error = E; - } -} - -pub mod prelude { - pub mod rust_2018 { - pub use crate::{result::*, ops::*}; - } -} -"#, - ); -} - #[test] fn infer_try_trait_v2() { check_types( r#" -//- /main.rs crate:main deps:core -fn test() { - let r: Result = Result::Ok(1); +//- minicore: try +fn test() -> core::ops::ControlFlow { + let r: core::ops::ControlFlow = core::ops::ControlFlow::Continue(1.0); let v = r?; - v; -} //^ i32 - -//- /core.rs crate:core -mod ops { - mod try_trait { - pub trait Try: FromResidual { - type Output; - type Residual; - } - pub trait FromResidual::Residual> {} - } - - pub use self::try_trait::FromResidual; - pub use self::try_trait::Try; -} - -mod convert { - pub trait From {} - impl From for T {} -} - -pub mod result { - use crate::convert::From; - use crate::ops::{Try, FromResidual}; - - pub enum Infallible {} - pub enum Result { - Ok(O), - Err(E) - } - - impl Try for Result { - type Output = O; - type Error = Result; - } - - impl> FromResidual> for Result {} -} - -pub mod prelude { - pub mod rust_2018 { - pub use crate::result::*; - } + //^ f32 + r } "#, ); diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index c5dc60f1ec..6c8b3088ad 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -6,7 +6,7 @@ use base_db::CrateId; use cfg::{CfgExpr, CfgOptions}; use either::Either; -use hir_def::path::ModPath; +use hir_def::{path::ModPath, TraitId}; use hir_expand::{name::Name, HirFileId, InFile}; use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange}; @@ -33,6 +33,7 @@ diagnostics![ BreakOutsideOfLoop, InactiveCode, IncorrectCase, + IncorrectTryExpr, InvalidDeriveTarget, MacroError, MalformedDerive, @@ -40,6 +41,7 @@ diagnostics![ MissingFields, MissingMatchArms, MissingUnsafe, + NotImplemented, NoSuchField, ReplaceFilterMapNextWithFindMap, TypeMismatch, @@ -153,6 +155,16 @@ pub struct MismatchedArgCount { pub expected: usize, pub found: usize, } +#[derive(Debug)] +pub struct IncorrectTryExpr { + pub expr: InFile>, +} +#[derive(Debug)] +pub struct NotImplemented { + pub expr: InFile>, + pub trait_: TraitId, + pub ty: Type, +} #[derive(Debug)] pub struct MissingMatchArms { diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index f5324208c9..e6c5c6b583 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -81,11 +81,12 @@ use crate::db::{DefDatabase, HirDatabase}; pub use crate::{ attrs::{HasAttrs, Namespace}, diagnostics::{ - AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, IncorrectCase, InvalidDeriveTarget, - MacroError, MalformedDerive, MismatchedArgCount, MissingFields, MissingMatchArms, - MissingUnsafe, NoSuchField, ReplaceFilterMapNextWithFindMap, TypeMismatch, - UnimplementedBuiltinMacro, UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall, - UnresolvedModule, UnresolvedProcMacro, + AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, IncorrectCase, IncorrectTryExpr, + InvalidDeriveTarget, MacroError, MalformedDerive, MismatchedArgCount, MissingFields, + MissingMatchArms, MissingUnsafe, NoSuchField, NotImplemented, + ReplaceFilterMapNextWithFindMap, TypeMismatch, UnimplementedBuiltinMacro, + UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall, UnresolvedModule, + UnresolvedProcMacro, }, has_source::HasSource, semantics::{PathResolution, Semantics, SemanticsScope, TypeInfo, VisibleTraits}, @@ -1282,30 +1283,45 @@ impl DefWithBody { let infer = db.infer(self.into()); let source_map = Lazy::new(|| db.body_with_source_map(self.into()).1); for d in &infer.diagnostics { - match d { + match *d { hir_ty::InferenceDiagnostic::NoSuchField { expr } => { - let field = source_map.field_syntax(*expr); + let field = source_map.field_syntax(expr); acc.push(NoSuchField { field }.into()) } - &hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { expr, is_break } => { + hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { expr, is_break } => { let expr = source_map .expr_syntax(expr) .expect("break outside of loop in synthetic syntax"); acc.push(BreakOutsideOfLoop { expr, is_break }.into()) } hir_ty::InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => { - match source_map.expr_syntax(*call_expr) { + match source_map.expr_syntax(call_expr) { Ok(source_ptr) => acc.push( MismatchedArgCount { call_expr: source_ptr, - expected: *expected, - found: *found, + expected: expected, + found: found, } .into(), ), Err(SyntheticSyntax) => (), } } + hir_ty::InferenceDiagnostic::IncorrectTryTarget { expr } => { + let expr = source_map.expr_syntax(expr).expect("try in synthetic syntax"); + acc.push(IncorrectTryExpr { expr }.into()) + } + hir_ty::InferenceDiagnostic::DoesNotImplement { expr, trait_, ref ty } => { + let expr = source_map.expr_syntax(expr).expect("try in synthetic syntax"); + acc.push( + NotImplemented { + expr, + trait_, + ty: Type::new(db, DefWithBodyId::from(self), ty.clone()), + } + .into(), + ) + } } } for (expr, mismatch) in infer.expr_type_mismatches() { diff --git a/crates/ide-diagnostics/src/handlers/incorrect_try_expr.rs b/crates/ide-diagnostics/src/handlers/incorrect_try_expr.rs new file mode 100644 index 0000000000..085d8d3259 --- /dev/null +++ b/crates/ide-diagnostics/src/handlers/incorrect_try_expr.rs @@ -0,0 +1,37 @@ +use hir::InFile; + +use crate::{Diagnostic, DiagnosticsContext}; + +// Diagnostic: incorrect-try-target +// +// This diagnostic is triggered if a question mark operator was used in a context where it is not applicable. +pub(crate) fn incorrect_try_expr( + ctx: &DiagnosticsContext<'_>, + d: &hir::IncorrectTryExpr, +) -> Diagnostic { + Diagnostic::new( + "incorrect-try-target", + format!("the return type of the containing function does not implement `FromResidual`"), + ctx.sema + .diagnostics_display_range(InFile::new(d.expr.file_id, d.expr.value.clone().into())) + .range, + ) +} + +#[cfg(test)] +mod tests { + use crate::tests::check_diagnostics; + + #[test] + fn try_ops_diag() { + check_diagnostics( + r#" +//- minicore: try +fn test() { + core::ops::ControlFlow::::Continue(1.0)?; + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: the return type of the containing function does not implement `FromResidual` +} +"#, + ); + } +} diff --git a/crates/ide-diagnostics/src/handlers/not_implemented.rs b/crates/ide-diagnostics/src/handlers/not_implemented.rs new file mode 100644 index 0000000000..3bf6a42322 --- /dev/null +++ b/crates/ide-diagnostics/src/handlers/not_implemented.rs @@ -0,0 +1,35 @@ +use hir::{db::DefDatabase, HirDisplay}; + +use crate::{Diagnostic, DiagnosticsContext}; + +// Diagnostic: not-implemented +// +// This diagnostic is triggered if a type doesn't implement a necessary trait. +pub(crate) fn not_implemented(ctx: &DiagnosticsContext<'_>, d: &hir::NotImplemented) -> Diagnostic { + Diagnostic::new( + "not-implemented", + format!( + "the trait `{}` is not implemented for `{}`", + ctx.sema.db.trait_data(d.trait_).name, + d.ty.display(ctx.sema.db) + ), + ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range, + ) +} + +#[cfg(test)] +mod tests { + use crate::tests::check_diagnostics; + + #[test] + fn missing_try_impl() { + check_diagnostics( + r#" +//- minicore: try +fn main() { + ()?; +} //^^ error: the trait `Try` is not implemented for `()` +"#, + ) + } +} diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs index ae299f0584..4577072149 100644 --- a/crates/ide-diagnostics/src/lib.rs +++ b/crates/ide-diagnostics/src/lib.rs @@ -29,6 +29,7 @@ mod handlers { pub(crate) mod break_outside_of_loop; pub(crate) mod inactive_code; pub(crate) mod incorrect_case; + pub(crate) mod incorrect_try_expr; pub(crate) mod invalid_derive_target; pub(crate) mod macro_error; pub(crate) mod malformed_derive; @@ -36,6 +37,7 @@ mod handlers { pub(crate) mod missing_fields; pub(crate) mod missing_match_arms; pub(crate) mod missing_unsafe; + pub(crate) mod not_implemented; pub(crate) mod no_such_field; pub(crate) mod replace_filter_map_next_with_find_map; pub(crate) mod type_mismatch; @@ -225,12 +227,14 @@ pub fn diagnostics( let d = match diag { AnyDiagnostic::BreakOutsideOfLoop(d) => handlers::break_outside_of_loop::break_outside_of_loop(&ctx, &d), AnyDiagnostic::IncorrectCase(d) => handlers::incorrect_case::incorrect_case(&ctx, &d), + AnyDiagnostic::IncorrectTryExpr(d) => handlers::incorrect_try_expr::incorrect_try_expr(&ctx, &d), AnyDiagnostic::MacroError(d) => handlers::macro_error::macro_error(&ctx, &d), AnyDiagnostic::MalformedDerive(d) => handlers::malformed_derive::malformed_derive(&ctx, &d), AnyDiagnostic::MismatchedArgCount(d) => handlers::mismatched_arg_count::mismatched_arg_count(&ctx, &d), AnyDiagnostic::MissingFields(d) => handlers::missing_fields::missing_fields(&ctx, &d), AnyDiagnostic::MissingMatchArms(d) => handlers::missing_match_arms::missing_match_arms(&ctx, &d), AnyDiagnostic::MissingUnsafe(d) => handlers::missing_unsafe::missing_unsafe(&ctx, &d), + AnyDiagnostic::NotImplemented(d) => handlers::not_implemented::not_implemented(&ctx, &d), AnyDiagnostic::NoSuchField(d) => handlers::no_such_field::no_such_field(&ctx, &d), AnyDiagnostic::ReplaceFilterMapNextWithFindMap(d) => handlers::replace_filter_map_next_with_find_map::replace_filter_map_next_with_find_map(&ctx, &d), AnyDiagnostic::TypeMismatch(d) => handlers::type_mismatch::type_mismatch(&ctx, &d), diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs index eb997e6fef..5cab017a58 100644 --- a/crates/ide/src/hover/tests.rs +++ b/crates/ide/src/hover/tests.rs @@ -4913,6 +4913,22 @@ fn foo() -> NotResult<(), Short> { ``` "#]], ); + check_hover_range( + r#" +//- minicore: try +use core::ops::ControlFlow; +fn foo() -> ControlFlow<()> { + $0ControlFlow::Break(())?$0; + ControlFlow::Continue(()) +} +"#, + expect![[r#" + ```text + Try Target Type: ControlFlow<(), {unknown}> + Propagated as: ControlFlow<(), ()> + ``` + "#]], + ); } #[test] @@ -4928,9 +4944,9 @@ fn foo() -> Option<()> { } "#, expect![[r#" - ```rust - as Try>::Output - ```"#]], + ```rust + i32 + ```"#]], ); } diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs index 69d2e62b25..59b1c147d7 100644 --- a/crates/test-utils/src/minicore.rs +++ b/crates/test-utils/src/minicore.rs @@ -27,6 +27,7 @@ //! generator: pin //! hash: //! index: sized +//! infallible: //! iterator: option //! iterators: iterator, fn //! option: @@ -36,7 +37,7 @@ //! result: //! sized: //! slice: -//! try: +//! try: infallible //! unsize: sized pub mod marker { @@ -150,6 +151,9 @@ pub mod convert { fn as_ref(&self) -> &T; } // endregion:as_ref + // region:infallible + pub enum Infallible {} + // endregion:infallible } pub mod ops { @@ -326,7 +330,7 @@ pub mod ops { Continue(C), Break(B), } - pub trait FromResidual { + pub trait FromResidual::Residual> { #[lang = "from_residual"] fn from_residual(residual: R) -> Self; } @@ -342,13 +346,13 @@ pub mod ops { impl Try for ControlFlow { type Output = C; - type Residual = ControlFlow; + type Residual = ControlFlow; fn from_output(output: Self::Output) -> Self {} fn branch(self) -> ControlFlow {} } impl FromResidual for ControlFlow { - fn from_residual(residual: ControlFlow) -> Self {} + fn from_residual(residual: ControlFlow) -> Self {} } } pub use self::try_::{ControlFlow, FromResidual, Try}; @@ -469,6 +473,33 @@ pub mod option { } } } + // region:try + impl crate::ops::Try for Option { + type Output = T; + type Residual = Option; + + #[inline] + fn from_output(output: Self::Output) -> Self { + Some(output) + } + + #[inline] + fn branch(self) -> crate::ops::ControlFlow { + match self { + Some(v) => crate::ops::ControlFlow::Continue(v), + None => crate::ops::ControlFlow::Break(None), + } + } + } + impl crate::ops::FromResidual for Option { + #[inline] + fn from_residual(residual: Option) -> Self { + match residual { + None => None, + } + } + } + // endregion:try } // endregion:option