Skip to content

Commit

Permalink
fix: Remove type information discarding. (#332)
Browse files Browse the repository at this point in the history
* chore: Add tests regarding type narrowing expectations.

* fix: Remove type information discarding.

This lead to probably unfixable type collisions, so the feature must go.
  • Loading branch information
Hannes Leutloff authored Jul 23, 2021
1 parent 8903bd5 commit bfa3e56
Show file tree
Hide file tree
Showing 2 changed files with 22 additions and 57 deletions.
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('');
};
});
});

0 comments on commit bfa3e56

Please sign in to comment.