Skip to content

Commit

Permalink
feat(type-compiler): allow to use T from ReceiveType<T> in function b…
Browse files Browse the repository at this point in the history
…ody as type reference

This allows to have code like this more easily:

```typescript
function mySerialize<T>(type?: ReceiveType<T>) {
    return cast<T>({});
}
```

It is still necessary to mark this function via ReceiveType though. this is the tradeoff we have in order to not embed too much JS code.

ref #565
  • Loading branch information
marcj committed May 8, 2024
1 parent 3c66064 commit 4d24c8b
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 17 deletions.
35 changes: 21 additions & 14 deletions packages/type-compiler/src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ const OPs: { [op in ReflectionOp]?: { params: number } } = {
[ReflectionOp.inline]: { params: 1 },
[ReflectionOp.inlineCall]: { params: 2 },
[ReflectionOp.loads]: { params: 2 },
[ReflectionOp.extends]: { params: 0 },
[ReflectionOp.infer]: { params: 2 },
[ReflectionOp.defaultValue]: { params: 1 },
[ReflectionOp.parameter]: { params: 1 },
Expand Down Expand Up @@ -2431,23 +2432,29 @@ export class ReflectionTransformer implements CustomTransformer {
//todo: intersection start
}

for (const foundUser of foundUsers) {
program.pushConditionalFrame();
const isReceiveType = foundUsers.find(v => isTypeReferenceNode(v.type) && isIdentifier(v.type.typeName) && getIdentifierName(v.type.typeName) === 'ReceiveType');
if (isReceiveType) {
// If it's used in ReceiveType<T>, then we can just use T directly without trying to infer it from ReceiveType<T> itself
program.pushOp(ReflectionOp.inline, program.pushStack(isReceiveType.parameterName));
} else {
for (const foundUser of foundUsers) {
program.pushConditionalFrame();

program.pushOp(ReflectionOp.typeof, program.pushStack(this.f.createArrowFunction(undefined, undefined, [], undefined, undefined, foundUser.parameterName)));
this.extractPackStructOfType(foundUser.type, program);
program.pushOp(ReflectionOp.extends);
program.pushOp(ReflectionOp.typeof, program.pushStack(this.f.createArrowFunction(undefined, undefined, [], undefined, undefined, foundUser.parameterName)));
this.extractPackStructOfType(foundUser.type, program);
program.pushOp(ReflectionOp.extends);

const found = program.findVariable(getIdentifierName(declaration.name));
if (found) {
this.extractPackStructOfType(declaration.name, program);
} else {
//type parameter was never found in X of `Y extends X` (no `infer X` was created), probably due to a not supported parameter type expression.
program.pushOp(ReflectionOp.any);
const found = program.findVariable(getIdentifierName(declaration.name));
if (found) {
this.extractPackStructOfType(declaration.name, program);
} else {
//type parameter was never found in X of `Y extends X` (no `infer X` was created), probably due to a not supported parameter type expression.
program.pushOp(ReflectionOp.any);
}
this.extractPackStructOfType({ kind: SyntaxKind.NeverKeyword } as TypeNode, program);
program.pushOp(ReflectionOp.condition);
program.popFrameImplicit();
}
this.extractPackStructOfType({ kind: SyntaxKind.NeverKeyword } as TypeNode, program);
program.pushOp(ReflectionOp.condition);
program.popFrameImplicit();
}

if (foundUsers.length > 1) {
Expand Down
15 changes: 15 additions & 0 deletions packages/type-compiler/tests/transpile.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -551,3 +551,18 @@ export const typeValidation = <T>(type?: ReceiveType<T>): ValidatorFn => (contro
console.log(res.app);
expect(res.app).toContain(`exports.typeValidation.Ω = undefined; return __assignType((control) =>`);
});

test('ReceiveType forward to type passing', () => {
const res = transpile({
'app': `
function typeOf2<T>(type?: ReceiveType<T>) {
return resolveReceiveType(type);
}
function mySerialize<T>(type?: ReceiveType<T>) {
return typeOf2<T>();
}
`
});
console.log(res.app);
});
7 changes: 4 additions & 3 deletions packages/type/src/reflection/reflection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import {
getClassName,
isArray,
isClass,
isFunction,
isGlobalClass,
isPrototypeOfBase,
stringifyValueWithType,
Expand Down Expand Up @@ -94,10 +95,10 @@ export function resolveReceiveType(type?: Packed | Type | ClassType | AbstractCl
if (type[type.length - 1] === 'n!' || type[type.length - 1] === 'P7!') {
//n! represents a simple inline: [Op.inline, 0]
//P7! represents a class reference: [Op.Frame, Op.classReference, 0] (Op.Frame seems unnecessary)
typeFn = (type as any)[0] as Function;
type = typeFn() as Packed | Type | ClassType | AbstractClassType | ReflectionClass<any> | undefined;
typeFn = (type as any)[0] as Function | any;
type = (isFunction(typeFn) ? typeFn() : typeFn) as Packed | Type | ClassType | AbstractClassType | ReflectionClass<any> | undefined;
if (!type) {
throw new Error(`No type resolved for ${typeFn.toString()}. Circular import or no runtime type available.`);
throw new Error(`No type resolved for ${String(typeFn)}. Circular import or no runtime type available.`);
}
}
}
Expand Down
18 changes: 18 additions & 0 deletions packages/type/tests/receive-type.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,21 @@ test('function with ReceiveType return expression', () => {
expect(validateString('hello')).toBe(true);
expect(validateString(2)).toBe(false);
});

test('ReceiveType forward to type passing', () => {
function typeOf2<T>(type?: ReceiveType<T>) {
console.log('typeOf2', type);
return resolveReceiveType(type);
}

function mySerialize<T>(type?: ReceiveType<T>) {
console.log('mySerialize', type);
return typeOf2<T>();
}

console.log('typeOf2', typeOf2.toString());
console.log('mySerialize', mySerialize.toString());

const type = mySerialize<string>();
expect(type).toMatchObject({ kind: ReflectionKind.string });
});

0 comments on commit 4d24c8b

Please sign in to comment.