diff --git a/.changeset/mean-eagles-develop.md b/.changeset/mean-eagles-develop.md new file mode 100644 index 00000000..6bf9afdc --- /dev/null +++ b/.changeset/mean-eagles-develop.md @@ -0,0 +1,5 @@ +--- +"neverthrow": patch +--- + +fix: change type definitions to make inferring types of safeTry more strict diff --git a/src/result.ts b/src/result.ts index f0dac3dc..cc10a379 100644 --- a/src/result.ts +++ b/src/result.ts @@ -77,6 +77,16 @@ export const err = (err: E): Err => new Err(err) * @returns The first occurence of either an yielded Err or a returned Result. */ export function safeTry(body: () => Generator, Result>): Result +export function safeTry< + YieldErr extends Err, + GeneratorReturnResult extends Result +>( + body: () => Generator, +): Result< + InferOkTypes, + InferErrTypes | InferErrTypes +> + /** * Evaluates the given generator to a Result returned or an Err yielded from it, * whichever comes first. @@ -95,6 +105,17 @@ export function safeTry(body: () => Generator, Result> export function safeTry( body: () => AsyncGenerator, Result>, ): Promise> +export function safeTry< + YieldErr extends Err, + GeneratorReturnResult extends Result +>( + body: () => AsyncGenerator, +): Promise< + Result< + InferOkTypes, + InferErrTypes | InferErrTypes + > +> export function safeTry( body: | (() => Generator, Result>) diff --git a/tests/typecheck-tests.ts b/tests/typecheck-tests.ts index 849b2247..31be6bf5 100644 --- a/tests/typecheck-tests.ts +++ b/tests/typecheck-tests.ts @@ -13,7 +13,7 @@ import { Result, ResultAsync, } from '../src' -import { Transpose } from '../src/result' +import { safeTry, Transpose } from '../src/result' import { type N, Test } from 'ts-toolbelt' type CreateTuple = @@ -1961,6 +1961,158 @@ type CreateTuple = }); (function describe(_ = 'Utility types') { + (function describe(_ = 'safeTry') { + (function describe(_ = 'sync generator') { + (function it(_ = 'should correctly infer the result type when generator returns Ok') { + interface ReturnMyError { + name: 'ReturnMyError' + } + + type Expectation = Result + + const result = safeTry(function *() { + return ok('string'); + }) + Test.checks([ + Test.check(), + ]) + }); + + (function it(_ = 'should correctly infer the result type when generator returns Err') { + interface ReturnMyError { + name: 'ReturnMyError'; + } + + type Expectation = Result + + const result = safeTry(function *() { + return err({ name: 'ReturnMyError' }); + }) + Test.checks([ + Test.check(), + ]) + }); + + (function it(_ = 'infers the value type when calling "yield*"') { + interface YieldMyError { + name: 'YieldMyError'; + } + interface ReturnMyError { + name: 'ReturnMyError'; + } + + safeTry(function *() { + type Expectation = number + + const unwrapped = yield* ok(123).safeUnwrap(); + Test.checks([ + Test.check(), + ]) + + return ok('string'); + }) + }); + + (function it(_ = 'should correctly infer the result type with multiple "yield*"') { + interface FirstYieldMyError { + name: 'FirstYieldMyError'; + } + interface SecondYieldMyError { + name: 'SecondYieldMyError'; + } + interface ReturnMyError { + name: 'ReturnMyError'; + } + + type Expectation = Result + + const result = safeTry(function *() { + yield* ok(123).safeUnwrap(); + yield* err({ name: 'SecondYieldMyError' }).safeUnwrap(); + return ok('string'); + }) + Test.checks([ + Test.check(), + ]) + }); + }); + + (function describe(_ = 'async generator') { + (function it(_ = 'should correctly infer the result type when generator returns OkAsync') { + interface ReturnMyError { + name: 'ReturnMyError' + } + + type Expectation = Promise> + + const result = safeTry(async function *() { + return okAsync('string'); + }) + Test.checks([ + Test.check(), + ]) + }); + + (function it(_ = 'should correctly infer the result type when generator returns ErrAsync') { + interface ReturnMyError { + name: 'ReturnMyError'; + } + + type Expectation = Promise> + + const result = safeTry(async function *() { + return errAsync({ name: 'ReturnMyError' }); + }) + Test.checks([ + Test.check(), + ]) + }); + + (function it(_ = 'infers the value type when calling "yield*"') { + interface YieldMyError { + name: 'YieldMyError'; + } + interface ReturnMyError { + name: 'ReturnMyError'; + } + + safeTry(async function *() { + type Expectation = number + + const unwrapped = yield* okAsync(123).safeUnwrap(); + Test.checks([ + Test.check(), + ]) + + return ok('string'); + }) + }); + + (function it(_ = 'should correctly infer the result type with multiple "yield*"') { + interface FirstYieldMyError { + name: 'FirstYieldMyError'; + } + interface SecondYieldMyError { + name: 'SecondYieldMyError'; + } + interface ReturnMyError { + name: 'ReturnMyError'; + } + + type Expectation = Promise> + + const result = safeTry(async function *() { + yield* okAsync(123).safeUnwrap(); + yield* errAsync({ name: 'SecondYieldMyError' }).safeUnwrap(); + return okAsync('string'); + }) + Test.checks([ + Test.check(), + ]) + }); + }); + }); + (function describe(_ = 'Transpose') { (function it(_ = 'should transpose an array') { const input: [