diff --git a/stew/results.nim b/stew/results.nim index f60af4c8..30622d0f 100644 --- a/stew/results.nim +++ b/stew/results.nim @@ -1051,18 +1051,44 @@ template `?`*[T, E](self: Result[T, E]): auto = ## let v = ? funcWithResult() ## echo v # prints value, not Result! ## ``` + ## + ## ``assignResult?`` extension point + ## + ## If a template / function named ``assignResult?`` exists in the scope, it will + ## be called instead of assigning the result - this can be used to intercept + ## the assignment to `result` that implicitly happens on early return in case + ## of error. + ## + ## To prevent conflicts, it is recommended that ``assignResult?`` is declared + ## close to the scope of `?` (as opposed to a globally visible symbol) + ## + ## ```nim + ## proc f(): Result[...] = + ## template assignResult(v: Result) = ... + ## let a = ? f2() + ## ``` + ## ## Experimental - # TODO the v copy is here to prevent multiple evaluations of self - could - # probably avoid it with some fancy macro magic.. - let v = (self) + mixin `assignResult?` + + let v = (self) # TODO avoid copy if not v.oResultPrivate: - when typeof(result) is typeof(v): - return v - else: - when E is void: - return err(typeof(result)) + when declared(`assignResult?`): + when typeof(result) is typeof(v): + `assignResult?`(v) + elif E is void: + `assignResult?`(err(typeof(result))) else: - return err(typeof(result), v.eResultPrivate) + `assignResult?`(err(typeof(result), v.eResultPrivate)) + return + else: + return + when typeof(result) is typeof(v): + v + elif E is void: + err(typeof(result)) + else: + err(typeof(result), v.eResultPrivate) when not(T is void): v.vResultPrivate diff --git a/tests/test_results.nim b/tests/test_results.nim index ef767c8b..b08e9b05 100644 --- a/tests/test_results.nim +++ b/tests/test_results.nim @@ -440,3 +440,44 @@ block: # Constants proc checkIt(v: WithOpt) = doAssert v.opt.isNone() checkIt(noneWithOpt) + +proc testAssignResult() = + var assigned: bool + template `assignResult?`(v: Result[int, string]) = + assigned = true + result = v + + proc failed(): Result[int, string] = + err("fail") + + proc calling(): Result[int, string] = + let _ = ? failed() + doAssert false + + let r = calling() + doAssert assigned + doAssert r == Result[int, string].err("fail") + + +proc testAssignResultGeneric[T]() = + var assigned: bool + template `assignResult?`(v: Result[T, string]) = + mixin result + assigned = true + result = v + + proc failed(): Result[int, string] = + err("fail") + + proc calling(): Result[int, string] = + let _ = ? failed() + doAssert false + + let r = calling() + doAssert assigned + doAssert r == Result[int, string].err("fail") + + +testAssignResult() + +testAssignResultGeneric[int]()