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

fix: Remove type information discarding. #332

Merged
merged 2 commits into from
Jul 23, 2021
Merged
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
22 changes: 11 additions & 11 deletions lib/Result.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
interface ResultBase<TValue, TError extends Error> {
hasError: () => this is ResultError<TError> & ResultBase<any, TError>;
hasValue: () => this is ResultValue<TValue> & ResultBase<TValue, any>;
hasError: () => this is ResultError<TValue, TError>;
hasValue: () => this is ResultValue<TValue, TError>;

unwrapOrThrow: (errorTransformer?: (err: TError) => Error) => TValue;
unwrapOrElse: (handleError: (error: Error) => TValue) => TValue;
unwrapOrDefault: (defaultValue: TValue) => TValue;
}

interface ResultError<TError extends Error> {
interface ResultError<TValue, TError extends Error> extends ResultBase<TValue, TError> {
error: TError;
}

const error = function <TError extends Error>(err: TError): ResultError<TError> & ResultBase<any, TError> {
const error = function <TValue, TError extends Error>(err: TError): ResultError<TValue, TError> {
return {
hasError (): boolean {
return true;
Expand All @@ -26,24 +26,24 @@ const error = function <TError extends Error>(err: TError): ResultError<TError>
// eslint-disable-next-line @typescript-eslint/no-throw-literal
throw err;
},
unwrapOrElse<TValue> (handleError: (error: TError) => TValue): TValue {
unwrapOrElse (handleError: (error: TError) => TValue): TValue {
return handleError(err);
},
unwrapOrDefault<TValue> (defaultValue: TValue): TValue {
unwrapOrDefault (defaultValue: TValue): TValue {
return defaultValue;
},
error: err
};
};

interface ResultValue<TValue> {
interface ResultValue<TValue, TError extends Error> extends ResultBase<TValue, TError> {
value: TValue;
}

const value: {
<TValue extends undefined>(): ResultValue<TValue> & ResultBase<TValue, any>;
<TValue>(value: TValue): ResultValue<TValue> & ResultBase<TValue, any>;
} = function <TValue>(val?: TValue): ResultValue<TValue | undefined> & ResultBase<TValue | undefined, any> {
<TValue extends undefined, TError extends Error>(): ResultValue<TValue, TError>;
<TValue, TError extends Error>(value: TValue): ResultValue<TValue, TError>;
} = function <TValue, TError extends Error>(val?: TValue): ResultValue<TValue | undefined, TError> {
return {
hasError (): boolean {
return false;
Expand All @@ -64,7 +64,7 @@ const value: {
};
};

type Result<TValue, TError extends Error> = ResultBase<TValue, TError>;
type Result<TValue, TError extends Error> = ResultValue<TValue, TError> | ResultError<TValue, TError>;

export type {
ResultValue,
Expand Down
57 changes: 11 additions & 46 deletions test/unit/ResultTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,15 @@ suite('Result', (): void => {
assert.that(resultHasError).is.false();
});

test(`narrows the type to the error case and discards value type information.`, async (): Promise<void> => {
test('extrapolates value type correctly.', async (): Promise<void> => {
const result = getValue();

if (result.hasError()) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const newResult: Result<{ something: 'elseEntirely' }, Error> = result;
return;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const containedValue: Value = result.value;
});
});

Expand All @@ -103,17 +105,15 @@ suite('Result', (): void => {
assert.that(resultHasValue).is.false();
});

test(`narrows the type to the value case and discards error type information.`, async (): Promise<void> => {
const result = getValue();

interface CustomError extends Error {
bar: string;
}
test('extrapolates error type correctly.', async (): Promise<void> => {
const result = getError();

if (result.hasValue()) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const newResult: Result<Value, CustomError> = result;
return;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const containedError = result.error;
});
});

Expand Down Expand Up @@ -246,39 +246,4 @@ suite('Result', (): void => {
}
});
});

test('is assignable to a result with the same error type if the result is known to be an error.', async (): Promise<void> => {
class CustomError extends Error {
public someProp = 0;
}

// This function compiling it enough for this test.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const someFunction = function (someResult: Result<number, CustomError>): Result<string, CustomError> {
if (someResult.hasError()) {
return someResult;
}

return error(new CustomError());
};
});

test('is assignable to a result with the same value type if the result is known to be an value.', async (): Promise<void> => {
class CustomError1 extends Error {
public someProp = 0;
}
class CustomError2 extends Error {
public someOtherProp = 'some string';
}

// This function compiling it enough for this test.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const someFunction = function (someResult: Result<string, CustomError1>): Result<string, CustomError2> {
if (someResult.hasValue()) {
return someResult;
}

return value('');
};
});
});