8000 Incorporate toString handling from v5 · true-myth/true-myth@5bacc45 · GitHub
[go: up one dir, main page]

Skip to content

Commit 5bacc45

Browse files
committed
Incorporate toString handling from v5
(cherry picked from commit 25adb1a) --- Original message: Add support for TypeScript 4.7 TS 4.7 catches a class of error which 4.6 and earlier did not, courtesy of microsoft/TypeScript#43183: if the type passed does not implement `toString()`, it notices, because it no longer defaults to falling back to `{}`. To resolve this, *loosen* the constraints on what is acceptable as input to `toString`, by making the implementation itself more robust. Doing so also fixes a bug with the output for `toString` when working with strings: previously if you had `Maybe("a string")`, the `toString` output would be `'Maybe(a string)'`; it is now `'Maybe("a string")'`. Fixes #330
1 parent 4f139a3 commit 5bacc45

File tree

5 files changed

+85
-17
lines changed

5 files changed

+85
-17
lines changed

src/-private/utils.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,20 @@ export const isVoid = (value: unknown): value is undefined | null =>
1616
export function curry1<T, U>(op: (t: T) => U, item?: T) {
1717
return item !== undefined ? op(item) : op;
1818
}
19+
20+
/**
21+
* Check whether a given key is in an object
22+
* @internal
23+
*/
24+
function has<T, K extends PropertyKey>(value: T, key: K): value is T & { [Key in K]: unknown } {
25+
return typeof value === 'object' && value !== null && key in value;
26+
}
27+
28+
export function safeToString(value: unknown): string {
29+
if (has(value, 'toString') && typeof value['toString'] === 'function') {
30+
const fnResult = value.toString();
31+
return typeof fnResult === 'string' ? fnResult : JSON.stringify(value);
32+
} else {
33+
return JSON.stringify(value);
34+
}
35+
}

src/maybe.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
@module
55
*/
66

7-
import { curry1, isVoid } from './-private/utils.js';
7+
import { curry1, isVoid, safeToString } from './-private/utils.js';
88

99
/**
1010
Discriminant for the {@linkcode Just} and {@linkcode Nothing} type instances.
@@ -208,7 +208,7 @@ class MaybeImpl<T> {
208208

209209
/** Method variant for {@linkcode toString} */
210210
toString(): string {
211-
return this.repr[0] === 'Just' ? `Just(${this.repr[1]})` : 'Nothing';
211+
return this.repr[0] === 'Just' ? `Just(${safeToString(this.repr[1])})` : 'Nothing';
212212
}
213213

214214
/** Method variant for {@linkcode toJSON} */

src/result.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66

77
import Unit from './unit.js';
8-
import { curry1, isVoid } from './-private/utils.js';
8+
import { curry1, isVoid, safeToString } from './-private/utils.js';
99

1010
/**
1111
Discriminant for {@linkcode Ok} and {@linkcode Err} variants of the
@@ -195,7 +195,7 @@ class ResultImpl<T, E> {
195195

196196
/** Method variant for {@linkcode toString} */
197197
toString(): string {
198-
return `${this.repr[0]}(${this.repr[1]})`;
198+
return `${this.repr[0]}(${safeToString(this.repr[1])})`;
199199
}
200200

201201
/** Method variant for {@linkcode toJSON} */
@@ -952,10 +952,10 @@ export function unwrapOrElse<T, U, E>(
952952
`toString(err([1, 2, 3]))` | `Err(1,2,3)`
953953
`toString(err({ an: 'object' }))` | `Err([object Object])`
954954
955-
@typeparam T The type of the wrapped value; its own `.toString` will be used
956-
to print the interior contents of the `Just` variant.
957-
@param maybe The value to convert to a string.
958-
@returns The string representation of the `Maybe`.
955+
@typeparam T The type of the wrapped value; its own `.toString` will be used
956+
to print the interior contents of the `Just` variant.
957+
@param result The value to convert to a string.
958+
@returns The string representation of the `Maybe`.
959959
*/
960960
export const toString = <T, E>(result: Result<T, E>): string => {
961961
return result.toString();

test/maybe.test.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,34 @@ describe('`Maybe` pure functions', () => {
264264
});
265265
});
266266

267-
test('`toString`', () => {});
267+
describe('`toString`', () => {
268+
test('normal cases', () => {
269+
expect(MaybeNS.toString(MaybeNS.of(42))).toEqual('Just(42)');
270+
expect(MaybeNS.toString(MaybeNS.nothing<string>())).toEqual('Nothing');
271+
});
272+
273+
test('custom `toString`s', () => {
274+
const withNotAFunction = {
275+
whyThough: 'because JS bro',
276+
toString: '🤨',
277+
};
278+
279+
expect(MaybeNS.toString(Maybe.of(withNotAFunction))).toEqual(
280+
`Just(${JSON.stringify(withNotAFunction)})`
281+
);
282+
283+
const withBadFunction = {
284+
cueSobbing: true,
285+
toString() {
286+
return { lol: 123 };
287+
},
288+
};
289+
290+
expect(MaybeNS.toString(Maybe.of(withBadFunction))).toEqual(
291+
`Just(${JSON.stringify(withBadFunction)})`
292+
);
293+
});
294+
});
268295

269296
test('`toJSON`', () => {
270297
expect(MaybeNS.toJSON(MaybeNS.of(42))).toEqual({ variant: MaybeNS.Variant.Just, value: 42 });

test/result.test.ts

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -310,15 +310,39 @@ describe('`Result` pure functions', () => {
310310
expect(anErrOrUndefined).toEqual(undefined);
311311
});
312312

313-
test('toString', () => {
314-
const theValue = { thisIsReally: 'something' };
315-
const errValue = ['oh', 'no'];
313+
describe('toString', () => {
314+
test('normal cases', () => {
315+
const theValue = { thisIsReally: 'something' };
316+
const errValue = ['oh', 'no'];
316317

317-
const anOk = ResultNS.ok<typeof theValue, typeof errValue>(theValue);
318-
expect(ResultNS.toString(anOk)).toEqual(`Ok(${theValue.toString()})`);
318+
const anOk = ResultNS.ok<typeof theValue, typeof errValue>(theValue);
319+
expect(ResultNS.toString(anOk)).toEqual(`Ok(${theValue.toString()})`);
319320

320-
const anErr = ResultNS.err<typeof theValue, typeof errValue>(errValue);
321-
expect(ResultNS.toString(anErr)).toEqual(`Err(${errValue.toString()})`);
321+
const anErr = ResultNS.err<typeof theValue, typeof errValue>(errValue);
322+
expect(ResultNS.toString(anErr)).toEqual(`Err(${errValue.toString()})`);
323+
});
324+
325+
test('custom `toString`s', () => {
326+
const withNotAFunction = {
327+
whyThough: 'because JS bro',
328+
toString: '🤨',
329+
};
330+
331+
expect(ResultNS.toString(Result.ok(withNotAFunction))).toEqual(
332+
`Ok(${JSON.stringify(withNotAFunction)})`
333+
);
334+
335+
const withBadFunction = {
336+
cueSobbing: true,
337+
toString() {
338+
return { lol: 123 };
339+
},
340+
};
341+
342+
expect(ResultNS.toString(Result.err(withBadFunction))).toEqual(
343+
`Err(${JSON.stringify(withBadFunction)})`
344+
);
345+
});
322346
});
323347

324348
test('`toJSON`', () => {
@@ -818,6 +842,6 @@ describe('`ResultNS.Err` class', () => {
818842

819843
const result = fn.ap(val);
820844

821-
expect(result.toString()).toEqual(`Err(ERR_ALLURBASE)`);
845+
expect(result.toString()).toEqual(`Err("ERR_ALLURBASE")`);
822846
});
823847
});

0 commit comments

Comments
 (0)
0