From 2be4ce6e728197c55524fc1f009b6a2946af022f Mon Sep 17 00:00:00 2001 From: "Marc J. Schmidt" Date: Wed, 30 Oct 2024 16:50:18 +0100 Subject: [PATCH] fix(type): symbols as method names --- .../type-compiler/tests/transpile.spec.ts | 14 ++++ packages/type/src/reflection/processor.ts | 6 +- packages/type/src/serializer.ts | 4 +- packages/type/tests/type-spec.spec.ts | 24 +++++++ packages/type/tests/use-cases.spec.ts | 65 +++++++++++++++++++ 5 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 packages/type/tests/use-cases.spec.ts diff --git a/packages/type-compiler/tests/transpile.spec.ts b/packages/type-compiler/tests/transpile.spec.ts index 6cc8c1ba1..0140ea6d0 100644 --- a/packages/type-compiler/tests/transpile.spec.ts +++ b/packages/type-compiler/tests/transpile.spec.ts @@ -566,3 +566,17 @@ test('ReceiveType forward to type passing', () => { }); console.log(res.app); }); + +test('symbol function name', () => { + const res = transpile({ + 'app': ` + const a = Symbol('a'); + class MySet { + [a](): any {} + [Symbol.iterator](): any {} + } + ` + }); + console.log(res.app); + expect(res.app).toContain(`() => Symbol.iterator`); +}); diff --git a/packages/type/src/reflection/processor.ts b/packages/type/src/reflection/processor.ts index 04eb44a9e..9e42facf3 100644 --- a/packages/type/src/reflection/processor.ts +++ b/packages/type/src/reflection/processor.ts @@ -864,10 +864,11 @@ export class Processor { case ReflectionOp.callSignature: case ReflectionOp.function: { const types = this.popFrame() as Type[]; - const name = program.stack[this.eatParameter() as number] as string; + let name = program.stack[this.eatParameter() as number] as string; const returnType = types.length > 0 ? types[types.length - 1] as Type : { kind: ReflectionKind.any } as Type; const parameters = types.length > 1 ? types.slice(0, -1) as TypeParameter[] : []; + if (isFunction(name)) name = name(); let t = op === ReflectionOp.callSignature ? { kind: ReflectionKind.callSignature, @@ -925,10 +926,11 @@ export class Processor { } case ReflectionOp.method: case ReflectionOp.methodSignature: { - const name = program.stack[this.eatParameter() as number] as number | string | symbol; + let name = program.stack[this.eatParameter() as number] as number | string | symbol | (() => symbol); const types = this.popFrame() as Type[]; const returnType = types.length > 0 ? types[types.length - 1] as Type : { kind: ReflectionKind.any } as Type; const parameters: TypeParameter[] = types.length > 1 ? types.slice(0, -1) as TypeParameter[] : []; + if (isFunction(name)) name = name(); let t: TypeMethod | TypeMethodSignature = op === ReflectionOp.method ? { kind: ReflectionKind.method, parent: undefined as any, visibility: ReflectionVisibility.public, name, return: returnType, parameters } diff --git a/packages/type/src/serializer.ts b/packages/type/src/serializer.ts index 7b0aee376..0abcd577f 100644 --- a/packages/type/src/serializer.ts +++ b/packages/type/src/serializer.ts @@ -1491,15 +1491,17 @@ export function typeGuardObjectLiteral(type: TypeObjectLiteral | TypeClass, stat export function serializeArray(type: TypeArray, state: TemplateState) { state.setContext({ isIterable }); const v = state.compilerContext.reserveName('v'); + const tempIterable = state.compilerContext.reserveName('tempIterable'); const i = state.compilerContext.reserveName('i'); const item = state.compilerContext.reserveName('item'); //we just use `a.length` to check whether its array-like, because Array.isArray() is way too slow. state.addCodeForSetter(` if (isIterable(${state.accessor})) { + const ${tempIterable} = ${state.accessor}; ${state.setter} = []; let ${i} = 0; - for (const ${item} of ${state.accessor}) { + for (const ${item} of ${tempIterable}) { let ${v}; ${executeTemplates(state.fork(v, item).extendPath(new RuntimeCode(i)), type.type)} ${state.setter}.push(${v}); diff --git a/packages/type/tests/type-spec.spec.ts b/packages/type/tests/type-spec.spec.ts index a84920363..5667adfb7 100644 --- a/packages/type/tests/type-spec.spec.ts +++ b/packages/type/tests/type-spec.spec.ts @@ -858,3 +858,27 @@ test('constructor property not assigned as property', () => { }); expect(back).toEqual(derived); }); + +test('custom symbol names as method names', () => { + class Model { + [Symbol.for('foo')]() { + return 'bar'; + } + } + + const type = ReflectionClass.from(Model); + const method = type.getMethod(Symbol.for('foo')); + expect(method).toBeDefined(); +}); + +test('global symbol names as method names', () => { + class Model { + [Symbol.iterator]() { + return []; + } + } + + const type = ReflectionClass.from(Model); + const method = type.getMethod(Symbol.iterator); + expect(method).toBeDefined(); +}); diff --git a/packages/type/tests/use-cases.spec.ts b/packages/type/tests/use-cases.spec.ts new file mode 100644 index 000000000..752afd04a --- /dev/null +++ b/packages/type/tests/use-cases.spec.ts @@ -0,0 +1,65 @@ +import { expect, test } from '@jest/globals'; +import { forwardSetToArray, serializer } from '../src/serializer'; +import { deserialize, serialize } from '../src/serializer-facade'; +import { validate } from '@deepkit/type'; + +test('custom iterable', () => { + class MyIterable implements Iterable { + items: T[] = []; + + constructor(items: T[] = []) { + this.items = items; + } + + [Symbol.iterator](): Iterator { + return this.items[Symbol.iterator](); + } + + add(item: T) { + this.items.push(item); + } + } + + type T1 = MyIterable; + type T2 = MyIterable; + + serializer.deserializeRegistry.registerClass(MyIterable, (type, state) => { + // takes first argument and deserializes as array, just like Set. + // works because first template argument defined the iterable type. + // can not be used if the iterable type is not known or not the first template argument. + forwardSetToArray(type, state); + // at this point `value` contains the value of `forwardSetToArray`, which is T as array. + // we forward this value to OrderedSet constructor. + state.convert(value => { + return new MyIterable(value); + }); + }); + + serializer.serializeRegistry.registerClass(MyIterable, (type, state) => { + // Set `MyIterable.items` as current value, so that forwardSetToArray operates on it. + state.convert((value: MyIterable) => value.items); + // see explanation in deserializeRegistry + forwardSetToArray(type, state); + }); + + const obj1 = new MyIterable(); + obj1.add('a'); + obj1.add('b'); + + const json1 = serialize(obj1); + console.log(json1); + expect(json1).toEqual(['a', 'b']); + + const back1 = deserialize(json1); + console.log(back1); + expect(back1).toBeInstanceOf(MyIterable); + expect(back1.items).toEqual(['a', 'b']); + + const errors = validate(back1); + expect(errors).toEqual([]); + + const back2 = deserialize([1, '2']); + console.log(back2); + expect(back2).toBeInstanceOf(MyIterable); + expect(back2.items).toEqual([1, 2]); +});