From 4d24c8b33197e163ba75eb9483349d269502dc76 Mon Sep 17 00:00:00 2001 From: "Marc J. Schmidt" Date: Thu, 9 May 2024 00:42:58 +0200 Subject: [PATCH] feat(type-compiler): allow to use T from ReceiveType in function body as type reference This allows to have code like this more easily: ```typescript function mySerialize(type?: ReceiveType) { return cast({}); } ``` 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 --- packages/type-compiler/src/compiler.ts | 35 +++++++++++-------- .../type-compiler/tests/transpile.spec.ts | 15 ++++++++ packages/type/src/reflection/reflection.ts | 7 ++-- packages/type/tests/receive-type.spec.ts | 18 ++++++++++ 4 files changed, 58 insertions(+), 17 deletions(-) diff --git a/packages/type-compiler/src/compiler.ts b/packages/type-compiler/src/compiler.ts index 23e6682c0..96a828bf1 100644 --- a/packages/type-compiler/src/compiler.ts +++ b/packages/type-compiler/src/compiler.ts @@ -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 }, @@ -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, then we can just use T directly without trying to infer it from ReceiveType 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) { diff --git a/packages/type-compiler/tests/transpile.spec.ts b/packages/type-compiler/tests/transpile.spec.ts index c4c487af2..6cc8c1ba1 100644 --- a/packages/type-compiler/tests/transpile.spec.ts +++ b/packages/type-compiler/tests/transpile.spec.ts @@ -551,3 +551,18 @@ export const typeValidation = (type?: ReceiveType): 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(type?: ReceiveType) { + return resolveReceiveType(type); + } + + function mySerialize(type?: ReceiveType) { + return typeOf2(); + } + ` + }); + console.log(res.app); +}); diff --git a/packages/type/src/reflection/reflection.ts b/packages/type/src/reflection/reflection.ts index 928484d71..acd96029b 100644 --- a/packages/type/src/reflection/reflection.ts +++ b/packages/type/src/reflection/reflection.ts @@ -57,6 +57,7 @@ import { getClassName, isArray, isClass, + isFunction, isGlobalClass, isPrototypeOfBase, stringifyValueWithType, @@ -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 | undefined; + typeFn = (type as any)[0] as Function | any; + type = (isFunction(typeFn) ? typeFn() : typeFn) as Packed | Type | ClassType | AbstractClassType | ReflectionClass | 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.`); } } } diff --git a/packages/type/tests/receive-type.spec.ts b/packages/type/tests/receive-type.spec.ts index 2d9348356..1af37cd3f 100644 --- a/packages/type/tests/receive-type.spec.ts +++ b/packages/type/tests/receive-type.spec.ts @@ -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(type?: ReceiveType) { + console.log('typeOf2', type); + return resolveReceiveType(type); + } + + function mySerialize(type?: ReceiveType) { + console.log('mySerialize', type); + return typeOf2(); + } + + console.log('typeOf2', typeOf2.toString()); + console.log('mySerialize', mySerialize.toString()); + + const type = mySerialize(); + expect(type).toMatchObject({ kind: ReflectionKind.string }); +});