Skip to content

Commit

Permalink
fix: stop or() union constructor from stripping branding (#88)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: union types built with the `.or()` constructor are now
possibly stricter as the branding is not unintentionally stripped anymore.
  • Loading branch information
untio11 authored May 6, 2024
1 parent e6521cf commit 2751bad
Show file tree
Hide file tree
Showing 4 changed files with 30 additions and 8 deletions.
2 changes: 1 addition & 1 deletion etc/types.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export abstract class BaseTypeImpl<ResultType, TypeConfig = unknown> implements
literal(input: DeepUnbranded<ResultType>): ResultType;
maybeStringify(value: ResultType): string | undefined;
abstract readonly name: string;
or<Other>(_other: BaseTypeImpl<Other, any>): Type<ResultType | Other>;
or<Other extends BaseTypeImpl<unknown>>(_other: Other): Type<ResultType | TypeOf<Other>>;
stringify(value: ResultType): string;
abstract readonly typeConfig: TypeConfig;
protected typeParser?(input: unknown, options: ValidationOptions): Result<unknown>;
Expand Down
10 changes: 5 additions & 5 deletions markdown/types.basetypeimpl.or.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ Union this Type with another Type.
**Signature:**

```typescript
or<Other>(_other: BaseTypeImpl<Other, any>): Type<ResultType | Other>;
or<Other extends BaseTypeImpl<unknown>>(_other: Other): Type<ResultType | TypeOf<Other>>;
```
## Parameters
| Parameter | Type | Description |
| --------- | ----------------------------------------------------------------- | ----------- |
| \_other | [BaseTypeImpl](./types.basetypeimpl.md)<!-- -->&lt;Other, any&gt; | |
| Parameter | Type | Description |
| --------- | ----- | ----------- |
| \_other | Other | |
**Returns:**
[Type](./types.type.md)<!-- -->&lt;ResultType \| Other&gt;
[Type](./types.type.md)<!-- -->&lt;ResultType \| [TypeOf](./types.typeof.md)<!-- -->&lt;Other&gt;&gt;
## Remarks
Expand Down
3 changes: 2 additions & 1 deletion src/base-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
Type,
TypeImpl,
TypeLink,
TypeOf,
TypeguardFor,
TypeguardResult,
ValidationOptions,
Expand Down Expand Up @@ -457,7 +458,7 @@ export abstract class BaseTypeImpl<ResultType, TypeConfig = unknown> implements
* See {@link UnionType} for more information about unions.
*/
// istanbul ignore next: using ordinary stub instead of module augmentation to lighten the load on the TypeScript compiler
or<Other>(_other: BaseTypeImpl<Other, any>): Type<ResultType | Other> {
or<Other extends BaseTypeImpl<unknown>>(_other: Other): Type<ResultType | TypeOf<Other>> {
throw new Error('stub');
}

Expand Down
23 changes: 22 additions & 1 deletion src/types/union.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { DeepUnbranded, The } from '../interfaces';
import { createExample, defaultUsualSuspects, testTypeImpl } from '../testutils';
import { assignableTo, createExample, defaultUsualSuspects, testTypeImpl } from '../testutils';
import { boolean } from './boolean';
import { object, partial } from './interface';
import { keyof } from './keyof';
Expand Down Expand Up @@ -493,3 +493,24 @@ testTypeImpl({
],
],
});

const BrandedA = literal('A').withBrand('BrandA');
const BrandedB = literal('B').withBrand('BrandB');

type BrandedUnion = The<typeof BrandedUnion>;
const BrandedUnion = union('BrandedUnion', [BrandedA, BrandedB]);

type BrandedOr1 = The<typeof BrandedOr1>;
const BrandedOr1 = BrandedA.or(BrandedB);

type BrandedOr2 = The<typeof BrandedOr2>;
const BrandedOr2 = BrandedB.or(BrandedA);
test('equivalence between `union()` and `.or()`', () => {
// Validating issue #87
assignableTo<BrandedUnion>(BrandedOr1.literal('A'));
assignableTo<BrandedUnion>(BrandedOr2.literal('A'));
assignableTo<BrandedOr1>(BrandedUnion.literal('A'));
assignableTo<BrandedOr2>(BrandedUnion.literal('A'));
// @ts-expect-error Unbranded literal not assignable to branded literal union type
assignableTo<BrandedOr1>('B');
});

0 comments on commit 2751bad

Please sign in to comment.