Skip to content

Commit

Permalink
fix: partial fix for type inference when accepting specialized valida…
Browse files Browse the repository at this point in the history
…tor in function (#43)

* fix: partial fix for type inference when accepting specialized validator in function

* cleaner markdown
  • Loading branch information
pavadeli authored Jun 29, 2021
1 parent a0c7e79 commit 5263475
Show file tree
Hide file tree
Showing 7 changed files with 41 additions and 21 deletions.
8 changes: 4 additions & 4 deletions etc/types.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class ArrayType<ElementType extends BaseTypeImpl<Element>, Element, Resul

// @public
export abstract class BaseObjectLikeTypeImpl<ResultType> extends BaseTypeImpl<ResultType> {
and<Other extends BaseObjectLikeTypeImpl<any>>(_other: Other): TypeImpl<BaseObjectLikeTypeImpl<MergeIntersection<ResultType & Other[typeof designType]>>> & TypedPropertyInformation<this['props'] & Other['props']>;
and<Other extends BaseObjectLikeTypeImpl<any>>(_other: Other): ObjectType<MergeIntersection<ResultType & Other[typeof designType]>> & TypedPropertyInformation<this['props'] & Other['props']>;
// (undocumented)
abstract readonly isDefaultName: boolean;
// (undocumented)
Expand Down Expand Up @@ -58,12 +58,12 @@ export abstract class BaseTypeImpl<ResultType> implements TypeLink<ResultType> {
is(input: unknown): input is ResultType;
literal(input: DeepUnbranded<ResultType>): ResultType;
abstract readonly name: string;
or<Other>(_other: BaseTypeImpl<Other>): TypeImpl<BaseTypeImpl<ResultType | Other>>;
or<Other>(_other: BaseTypeImpl<Other>): Type<ResultType | Other>;
protected typeParser?(input: unknown, options: ValidationOptions): Result<unknown>;
protected abstract typeValidator(input: unknown, options: ValidationOptions): Result<ResultType>;
validate(input: unknown, options?: ValidationOptions): Result<ResultType>;
withBrand<BrandName extends string>(name: BrandName): TypeImpl<BaseTypeImpl<Branded<ResultType, BrandName>>>;
withConstraint<BrandName extends string>(name: BrandName, constraint: Validator<ResultType>): TypeImpl<BaseTypeImpl<Branded<ResultType, BrandName>>>;
withBrand<BrandName extends string>(name: BrandName): Type<Branded<ResultType, BrandName>>;
withConstraint<BrandName extends string>(name: BrandName, constraint: Validator<ResultType>): Type<Branded<ResultType, BrandName>>;
withName(name: string): this;
withParser(...args: [name: string, newConstructor: (i: unknown) => unknown] | [newConstructor: (i: unknown) => unknown]): this;
withValidation(validation: Validator<ResultType>): this;
Expand Down
4 changes: 2 additions & 2 deletions markdown/types.baseobjectliketypeimpl.and.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Intersect this Type with another Type.
<b>Signature:</b>

```typescript
and<Other extends BaseObjectLikeTypeImpl<any>>(_other: Other): TypeImpl<BaseObjectLikeTypeImpl<MergeIntersection<ResultType & Other[typeof designType]>>> & TypedPropertyInformation<this['props'] & Other['props']>;
and<Other extends BaseObjectLikeTypeImpl<any>>(_other: Other): ObjectType<MergeIntersection<ResultType & Other[typeof designType]>> & TypedPropertyInformation<this['props'] & Other['props']>;
```
## Parameters
Expand All @@ -20,7 +20,7 @@ and<Other extends BaseObjectLikeTypeImpl<any>>(_other: Other): TypeImpl<BaseObje
<b>Returns:</b>
[TypeImpl](./types.typeimpl.md)<!-- -->&lt;[BaseObjectLikeTypeImpl](./types.baseobjectliketypeimpl.md)<!-- -->&lt;[MergeIntersection](./types.mergeintersection.md)<!-- -->&lt;ResultType &amp; Other\[typeof designType\]&gt;&gt;&gt; &amp; [TypedPropertyInformation](./types.typedpropertyinformation.md)<!-- -->&lt;this\['props'\] &amp; Other\['props'\]&gt;
[ObjectType](./types.objecttype.md)<!-- -->&lt;[MergeIntersection](./types.mergeintersection.md)<!-- -->&lt;ResultType &amp; Other\[typeof designType\]&gt;&gt; &amp; [TypedPropertyInformation](./types.typedpropertyinformation.md)<!-- -->&lt;this\['props'\] &amp; Other\['props'\]&gt;
## Remarks
Expand Down
4 changes: 2 additions & 2 deletions markdown/types.basetypeimpl.or.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Union this Type with another Type.
<b>Signature:</b>

```typescript
or<Other>(_other: BaseTypeImpl<Other>): TypeImpl<BaseTypeImpl<ResultType | Other>>;
or<Other>(_other: BaseTypeImpl<Other>): Type<ResultType | Other>;
```

## Parameters
Expand All @@ -20,7 +20,7 @@ or<Other>(_other: BaseTypeImpl<Other>): TypeImpl<BaseTypeImpl<ResultType | Other

<b>Returns:</b>

[TypeImpl](./types.typeimpl.md)<!-- -->&lt;[BaseTypeImpl](./types.basetypeimpl.md)<!-- -->&lt;ResultType \| Other&gt;&gt;
[Type](./types.type.md)<!-- -->&lt;ResultType \| Other&gt;

## Remarks

Expand Down
4 changes: 2 additions & 2 deletions markdown/types.basetypeimpl.withbrand.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Create a new instance of this Type with the given name.
<b>Signature:</b>

```typescript
withBrand<BrandName extends string>(name: BrandName): TypeImpl<BaseTypeImpl<Branded<ResultType, BrandName>>>;
withBrand<BrandName extends string>(name: BrandName): Type<Branded<ResultType, BrandName>>;
```
## Parameters
Expand All @@ -20,7 +20,7 @@ withBrand<BrandName extends string>(name: BrandName): TypeImpl<BaseTypeImpl<Bran
<b>Returns:</b>
[TypeImpl](./types.typeimpl.md)<!-- -->&lt;[BaseTypeImpl](./types.basetypeimpl.md)<!-- -->&lt;[Branded](./types.branded.md)<!-- -->&lt;ResultType, BrandName&gt;&gt;&gt;
[Type](./types.type.md)<!-- -->&lt;[Branded](./types.branded.md)<!-- -->&lt;ResultType, BrandName&gt;&gt;
## Remarks
Expand Down
4 changes: 2 additions & 2 deletions markdown/types.basetypeimpl.withconstraint.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Create a new type use the given constraint function to restrict the current type
<b>Signature:</b>

```typescript
withConstraint<BrandName extends string>(name: BrandName, constraint: Validator<ResultType>): TypeImpl<BaseTypeImpl<Branded<ResultType, BrandName>>>;
withConstraint<BrandName extends string>(name: BrandName, constraint: Validator<ResultType>): Type<Branded<ResultType, BrandName>>;
```
## Parameters
Expand All @@ -21,7 +21,7 @@ withConstraint<BrandName extends string>(name: BrandName, constraint: Validator<
<b>Returns:</b>
[TypeImpl](./types.typeimpl.md)<!-- -->&lt;[BaseTypeImpl](./types.basetypeimpl.md)<!-- -->&lt;[Branded](./types.branded.md)<!-- -->&lt;ResultType, BrandName&gt;&gt;&gt;
[Type](./types.type.md)<!-- -->&lt;[Branded](./types.branded.md)<!-- -->&lt;ResultType, BrandName&gt;&gt;
## Remarks
Expand Down
16 changes: 7 additions & 9 deletions src/base-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import type {
DeepUnbranded,
LiteralValue,
MergeIntersection,
ObjectType,
Properties,
PropertiesInfo,
Result,
Type,
TypeImpl,
TypeLink,
ValidationOptions,
Expand Down Expand Up @@ -273,7 +275,7 @@ export abstract class BaseTypeImpl<ResultType> implements TypeLink<ResultType> {
*
* @param name - the new name to use in error messages
*/
withBrand<BrandName extends string>(name: BrandName): TypeImpl<BaseTypeImpl<Branded<ResultType, BrandName>>> {
withBrand<BrandName extends string>(name: BrandName): Type<Branded<ResultType, BrandName>> {
return createType(branded<ResultType, BrandName>(this), { name: { configurable: true, value: name } });
}

Expand Down Expand Up @@ -353,12 +355,9 @@ export abstract class BaseTypeImpl<ResultType> implements TypeLink<ResultType> {
* @param name - the new name to use in error messages
* @param constraint - the additional validation to restrict the current type
*/
withConstraint<BrandName extends string>(
name: BrandName,
constraint: Validator<ResultType>,
): TypeImpl<BaseTypeImpl<Branded<ResultType, BrandName>>> {
withConstraint<BrandName extends string>(name: BrandName, constraint: Validator<ResultType>): Type<Branded<ResultType, BrandName>> {
// Ignoring Brand here, because that is a typings-only feature.
const fn: TypeImpl<BaseTypeImpl<Branded<ResultType, BrandName>>>['typeValidator'] = (input, options) => {
const fn: BaseTypeImpl<Branded<ResultType, BrandName>>['typeValidator'] = (input, options) => {
const baseResult = this.typeValidator(input, options);
if (!baseResult.ok) {
return newType.createResult(input, undefined, prependContextToDetails(baseResult, 'base type'));
Expand Down Expand Up @@ -400,7 +399,7 @@ export abstract class BaseTypeImpl<ResultType> implements TypeLink<ResultType> {
* 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>): TypeImpl<BaseTypeImpl<ResultType | Other>> {
or<Other>(_other: BaseTypeImpl<Other>): Type<ResultType | Other> {
throw new Error('stub');
}

Expand Down Expand Up @@ -455,8 +454,7 @@ export abstract class BaseObjectLikeTypeImpl<ResultType> extends BaseTypeImpl<Re
// istanbul ignore next: using ordinary stub instead of module augmentation to lighten the load on the TypeScript compiler
and<Other extends BaseObjectLikeTypeImpl<any>>(
_other: Other,
): TypeImpl<BaseObjectLikeTypeImpl<MergeIntersection<ResultType & Other[typeof designType]>>> &
TypedPropertyInformation<this['props'] & Other['props']> {
): ObjectType<MergeIntersection<ResultType & Other[typeof designType]>> & TypedPropertyInformation<this['props'] & Other['props']> {
throw new Error('stub');
}
}
Expand Down
22 changes: 22 additions & 0 deletions src/types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ testTypeImpl({
});

/** RestrictedUser is the User type with additional validation logic. */
type RestrictedUser = The<typeof RestrictedUser>;
const RestrictedUser = User.withConstraint('RestrictedUser', user => {
const errors: MessageDetails[] = [];
if (user.name.first === 'Bobby' && user.name.last === 'Tables') {
Expand Down Expand Up @@ -297,6 +298,7 @@ const NumberFromString = number.withParser(
return +n;
}),
);
type NestedFromString = The<typeof NestedFromString>;
const NestedFromString = object('NestedFromString', { a: NumberFromString, b: NumberFromString });

testTypeImpl({
Expand Down Expand Up @@ -356,6 +358,7 @@ testTypeImpl({
],
});

type ComplexNesting = The<typeof ComplexNesting>;
const ComplexNesting = object('ComplexNesting', {
pos: NumberFromString.withValidation(n => n > 0 || 'should be positive'),
neg: NumberFromString.withValidation(n => n < 0 || 'should be negative'),
Expand Down Expand Up @@ -679,3 +682,22 @@ testTypes('usability of assert', () => {
// Does work:
MyExplicitType.assert(value);
});

testTypes('type inference', () => {
function elementOfType<T>(_type: Type<T>): T {
return 0 as any;
}

assignableTo<number>(elementOfType(number));
assignableTo<ConfirmedAge>(elementOfType(ConfirmedAge));
assignableTo<User>(elementOfType(User));
assignableTo<RestrictedUser>(elementOfType(RestrictedUser));
assignableTo<NestedFromString>(elementOfType(NestedFromString));
assignableTo<ComplexNesting>(elementOfType(ComplexNesting));
assignableTo<IntersectionTest>(elementOfType(IntersectionTest));
assignableTo<MyGenericWrapper<User>>(elementOfType(MyGenericWrapper(User)));
assignableTo<GenericAugmentation<User>>(elementOfType(GenericAugmentation(User)));

// @ts-expect-error I still don't know how to fix this for `extendWith`:
assignableTo<Age>(elementOfType(Age));
});

0 comments on commit 5263475

Please sign in to comment.