Skip to content

Commit

Permalink
fix(type): correctly materialize Promise in runtime checks.
Browse files Browse the repository at this point in the history
This makes sure that the circular ref detector works correctly.

fixes #495
  • Loading branch information
marcj committed Oct 27, 2023
1 parent 56fbef9 commit aa66460
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 7 deletions.
22 changes: 15 additions & 7 deletions packages/type/src/reflection/extends.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import {
addType,
emptyObject,
flatten,
flatten, getTypeJitContainer,
indexAccess,
isMember,
isOptional,
Expand All @@ -29,7 +29,7 @@ import {
TypeMethodSignature,
TypeNumber,
TypeObjectLiteral,
TypeParameter,
TypeParameter, TypePromise,
TypeString,
TypeTemplateLiteral,
TypeTuple,
Expand Down Expand Up @@ -98,8 +98,12 @@ export function _isExtendable(left: Type, right: Type, extendStack: StackEntry[]

if (left.kind === ReflectionKind.promise && right.kind === ReflectionKind.object) return true;

if (left.kind === ReflectionKind.promise) return _isExtendable(createPromiseObjectLiteral(left.type), right);
if (right.kind === ReflectionKind.promise) return _isExtendable(left, createPromiseObjectLiteral(right.type));
if (left.kind === ReflectionKind.promise) {
return _isExtendable(createPromiseObjectLiteral(left), right);
}
if (right.kind === ReflectionKind.promise) {
return _isExtendable(left, createPromiseObjectLiteral(right));
}

if (right.kind !== ReflectionKind.union) {
if (left.kind === ReflectionKind.null) {
Expand Down Expand Up @@ -329,8 +333,12 @@ export function _isExtendable(left: Type, right: Type, extendStack: StackEntry[]
* We don't want to embed in each and every file the type definition of Promise<t>,
* so we do it ondemand at runtime instead. This saves bundle size.
*/
export function createPromiseObjectLiteral(type: Type): TypeObjectLiteral {
export function createPromiseObjectLiteral(type: TypePromise): TypeObjectLiteral {
const jit = getTypeJitContainer(type);
if (jit.__promiseObjectLiteral) return jit.__promiseObjectLiteral;

const promise: TypeObjectLiteral = {} as any;
jit.__promiseObjectLiteral = promise;
Object.assign(promise, {
kind: ReflectionKind.objectLiteral,
types: [
Expand All @@ -343,8 +351,8 @@ export function createPromiseObjectLiteral(type: Type): TypeObjectLiteral {
type: {
kind: ReflectionKind.union, types: [{
kind: ReflectionKind.function, parameters: [
{ kind: ReflectionKind.parameter, name: 'value', type: type },
], return: { kind: ReflectionKind.union, types: [type, { kind: ReflectionKind.promise, type: type }] }
{ kind: ReflectionKind.parameter, name: 'value', type: type.type },
], return: { kind: ReflectionKind.union, types: [type.type, promise] }
}, { kind: ReflectionKind.null }, { kind: ReflectionKind.undefined }]
}
},
Expand Down
36 changes: 36 additions & 0 deletions packages/type/tests/type.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1511,3 +1511,39 @@ test('issue-430: referring to this', () => {
//this makes it possible that the code above works at least.
expect(stringifyResolvedType(typeOf<keys>())).toBe(`'someFunctionA' | 'someFunctionB' | 'someFunctionC'`);
});

test('issue-495: extend Promise in union', () => {
type Model = {
foo: string;
};

type Interface = {
modelBuilder(): Promise<Model> | Model;
};

class Implementation1 {
async modelBuilder(): Promise<Model> {
return {foo: 'bar'};
}
}

class Implementation2 {
async modelBuilder(): Promise<{foo2: string}> {
return {foo2: 'bar'};
}
}

{
type T = Implementation1 extends Interface ? true : false;
const type = typeOf<T>();
assertType(type, ReflectionKind.literal);
expect(type.literal).toBe(true);
}

{
type T = Implementation2 extends Interface ? true : false;
const type = typeOf<T>();
assertType(type, ReflectionKind.literal);
expect(type.literal).toBe(false);
}
});

0 comments on commit aa66460

Please sign in to comment.