import { describe, expect, it } from 'vitest'; import { run, imports } from '../compile'; import { bundle } from '../project'; import { packageName } from '../../../src/package'; const schema = 'interface Box {value:pb<1,T>;} interface Pair {first:pb<1,T>;second:pb<2,U>;}'; const codec = `${schema} class Codec { roundtrip(value:Pair) {return protobuf_decode>(protobuf_encode>(value));} }`; describe('generic composition', () => { it('propagates two specializations across a 64-function call chain', () => { const functions = Array.from( { length: 64 }, (_, i) => `function f${i}(value:Box) {return ${i === 63 ? 'protobuf_encode>(value)' : `f${i + 1}(value)`};}`, ).join('\n'); expect( run( `${schema} ${functions} export const result=[[...f0({value:7})],[...f0({value:'x'})]];`, ).result, ).toEqual([ [8, 7], [10, 1, 120], ]); }); it('composes deferred mapped types, promises, classes and polymorphic callbacks', () => { expect( run(` interface Msg {id:pb<1,uint_32>;text:pb<2,string>;ignore:pb<3,bool>;} type Payload = Pick,'id'|'text'>; interface Envelope {value:pb<1,Payload>;} function encode(value:Envelope) {return protobuf_encode>(value);} function apply(value:Envelope,fn:(value:Envelope)=>Uint8Array) {return fn(value);} class Codec {encode(value:Envelope){return apply(value,encode);}} const codec=new Codec>(); export const result=protobuf_decode>(codec.encode({value:{id:7,text:'x'}})); `).result, ).toEqual({ value: { id: 7, text: 'x' } }); }); it('retains outer type arguments across nested local factories', () => { expect( run(`${codec} function outer() {function inner() {return new Codec();} return inner();} const n=outer(); const s=outer(); export const result=[n.roundtrip({first:7,second:'x'}),s.roundtrip({first:'y',second:8})]; `).result, ).toEqual([ { first: 7, second: 'x' }, { first: 'y', second: 8 }, ]); }); it('resolves instances through parameter properties and getters', () => { expect( run(`${codec} class Holder { constructor(readonly codec:Codec) {} get current():Codec {return this.codec;} } const holder=new Holder(new Codec()); export const result=holder.current.roundtrip({first:'x',second:7}); `).result, ).toEqual({ first: 'x', second: 7 }); }); it('resolves static element access without changing this', () => { expect( run(`${codec} const c=new Codec(); export const result=c['roundtrip']({first:7,second:'x'}); `).result, ).toEqual({ first: 7, second: 'x' }); }); it('supports overloaded generic methods and constructors', () => { expect( run(`${schema} class Codec { value:Box; constructor(value:Box); constructor(value:Box, label:string); constructor(value:Box, public label='default') {this.value=value;} encode() {return protobuf_encode>(this.value);} roundtrip(value:Box):Box; roundtrip(value:Box):Box {return protobuf_decode>(protobuf_encode>(value));} } const n:Box={value:7}; const c=new Codec(n); export const result=[[...c.encode()],c.roundtrip({value:'x'}),c.label]; `).result, ).toEqual([[8, 7], { value: 'x' }, 'default']); }); it('forwards shadowed generic names through deeply nested callbacks', () => { expect( run(`${schema} function roundtrip(value:Box) {return protobuf_decode>(protobuf_encode>(value));} function outer(first:Box, fn:(value:Box)=>Box) { function middle(next:Box) { function inner(value:Box) {return fn(value);} return [inner(first),inner(next)]; } return middle({value:'x'}); } export const result=outer({value:7},roundtrip); `).result, ).toEqual([{ value: 7 }, { value: 'x' }]); }); it('converges through mutually recursive generic functions', () => { expect( run(`${schema} function a(value:Box, depth:number):Box {return depth ? b(value,depth-1) : protobuf_decode>(protobuf_encode>(value));} function b(value:Box, depth:number):Box {return a(value,depth);} export const result=[a({value:7},32),a({value:'x'},33)]; `).result, ).toEqual([{ value: 7 }, { value: 'x' }]); }); it('keeps class factory specializations through cyclic re-export modules', async () => { expect( await bundle({ 'codec.ts': `${imports} ${codec} export const create=()=>new Codec();`, 'first.ts': `export * from './second'; export {create} from './codec';`, 'second.ts': `export * from './first';`, 'entry.ts': `import {create} from './second'; export const result=create().roundtrip({first:'x',second:7});`, }), ).toEqual({ result: { first: 'x', second: 7 } }); }); it('recognizes parenthesized runtime aliases and namespace element access', () => { expect( run(`${schema} import * as runtime from '${packageName}'; const encode=protobuf_encode; const alias=encode; export const result=[ [...(alias)>({value:7})], runtime['protobuf_decode']>(runtime['protobuf_encode']>({value:'x'})) ]; `).result, ).toEqual([[8, 7], { value: 'x' }]); }); it('adapts typed callbacks selected by static element access', () => { expect( run(`${codec} const c=new Codec(); const callback:(value:Pair)=>Pair = c['roundtrip'].bind(c); export const result=callback({first:7,second:'x'}); `).result, ).toEqual({ first: 7, second: 'x' }); }); it('captures outer generics in a local class returned from a factory', () => { expect( run(`${schema} function create() { class Local {roundtrip(value:Pair) {return protobuf_decode>(protobuf_encode>(value));}} return new Local(); } export const result=[create().roundtrip({first:7,second:'x'}),create().roundtrip({first:'y',second:8})]; `).result, ).toEqual([ { first: 7, second: 'x' }, { first: 'y', second: 8 }, ]); }); it('resolves namespaced base schemas and separates imported type and value names', async () => { expect( await bundle({ 'types.ts': `export interface Base {value:pb<1,T>;} export const Base=123;`, 'entry.ts': `${imports} import type * as types from './types'; interface Msg extends types.Base {text:pb<2,string>;} export const result=protobuf_decode>(protobuf_encode>({value:7,text:'x'}));`, }), ).toEqual({ result: { value: 7, text: 'x' } }); }); it('keeps valid recursive references through inherited generic fields', () => { expect( run(` interface Base {next:pb<1,Link|null>;} interface Link extends Base {value:pb<2,T>;} const value:Link={value:'a',next:{value:'b',next:null}}; export const result=protobuf_decode>(protobuf_encode>(value)); `).result, ).toEqual({ value: 'a', next: { value: 'b', next: null } }); }); it('resolves imported type and value names independently of declaration order', async () => { expect( await bundle({ 'types.ts': `export const Msg=42; export interface Msg {value:pb<1,uint_32>;}`, 'barrel.ts': `export {Msg} from './types';`, 'entry.ts': `${imports} import {Msg} from './barrel'; export const result=[Msg,[...protobuf_encode({value:7})]];`, }), ).toEqual({ result: [42, [8, 7]] }); }); });