Skip to content

Commit

Permalink
fix(type): symbols as method names
Browse files Browse the repository at this point in the history
  • Loading branch information
marcj committed Oct 30, 2024
1 parent ab96f2f commit 2be4ce6
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 3 deletions.
14 changes: 14 additions & 0 deletions packages/type-compiler/tests/transpile.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`);
});
6 changes: 4 additions & 2 deletions packages/type/src/reflection/processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 }
Expand Down
4 changes: 3 additions & 1 deletion packages/type/src/serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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});
Expand Down
24 changes: 24 additions & 0 deletions packages/type/tests/type-spec.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
65 changes: 65 additions & 0 deletions packages/type/tests/use-cases.spec.ts
Original file line number Diff line number Diff line change
@@ -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<T> implements Iterable<T> {
items: T[] = [];

constructor(items: T[] = []) {
this.items = items;
}

[Symbol.iterator](): Iterator<T> {
return this.items[Symbol.iterator]();
}

add(item: T) {
this.items.push(item);
}
}

type T1 = MyIterable<string>;
type T2 = MyIterable<number>;

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<unknown>) => value.items);
// see explanation in deserializeRegistry
forwardSetToArray(type, state);
});

const obj1 = new MyIterable<string>();
obj1.add('a');
obj1.add('b');

const json1 = serialize<T1>(obj1);
console.log(json1);
expect(json1).toEqual(['a', 'b']);

const back1 = deserialize<T1>(json1);
console.log(back1);
expect(back1).toBeInstanceOf(MyIterable);
expect(back1.items).toEqual(['a', 'b']);

const errors = validate<T1>(back1);
expect(errors).toEqual([]);

const back2 = deserialize<T2>([1, '2']);
console.log(back2);
expect(back2).toBeInstanceOf(MyIterable);
expect(back2.items).toEqual([1, 2]);
});

0 comments on commit 2be4ce6

Please sign in to comment.