perf(tools): keep py-types oneOf rendering linear in schema depth
A deep oneOf chain joined the accumulated union string at every level (Array.join forces materialization), making it Theta(depth^2) — a 50,000-level chain took ~7.6s. Concatenate with `+` instead: V8 builds a lazy ConsString that materializes once at the root, matching the array arm's template-literal laziness and ts-types' composable-document approach. The whole walk is now linear in depth. Adds a 20,000-level oneOf test alongside the existing deep-array one; py-types.ts stays at 100% coverage.
This commit is contained in:
@@ -259,7 +259,17 @@ function renderType(schema: unknown, className: string, state: RenderState): str
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (frame.kind === 'oneOf') {
|
if (frame.kind === 'oneOf') {
|
||||||
finish(frame.childTypes.join(' | '))
|
// Concatenate with `+` (not `Array.join`): V8 builds a lazy
|
||||||
|
// ConsString, so a deep oneOf chain materializes once at the root
|
||||||
|
// instead of re-materializing the accumulated string at every level
|
||||||
|
// (which `join` would, making it Θ(depth²)). This matches the array
|
||||||
|
// arm's template-literal laziness and ts-types' composable-document
|
||||||
|
// approach — the whole walk stays linear in schema depth.
|
||||||
|
let union = ''
|
||||||
|
for (const [index, childType] of frame.childTypes.entries()) {
|
||||||
|
union = index === 0 ? childType : `${union} | ${childType}`
|
||||||
|
}
|
||||||
|
finish(union)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
/* jscpd:ignore-end */
|
/* jscpd:ignore-end */
|
||||||
|
|||||||
@@ -469,6 +469,20 @@ describe('renderToolsSdkPy', () => {
|
|||||||
expect(type.length).toBe('list['.length * 20000 + 'str'.length + ']'.repeat(20000).length)
|
expect(type.length).toBe('list['.length * 20000 + 'str'.length + ']'.repeat(20000).length)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('renders a deeply nested oneOf chain in linear time (no per-level re-materialization)', () => {
|
||||||
|
// Each level is a two-branch oneOf whose first branch recurses; joining the
|
||||||
|
// accumulated union string at every level would be Theta(depth^2). The `+`
|
||||||
|
// (ConsString) concatenation keeps it linear, like the array arm.
|
||||||
|
const depth = 20000
|
||||||
|
let deep: Record<string, unknown> = { type: 'string' }
|
||||||
|
for (let i = 0; i < depth; i++) deep = { oneOf: [deep, { type: 'null' }] }
|
||||||
|
const type = jsonSchemaToPy(deep)
|
||||||
|
// depth levels of ` | None` appended to the innermost `str`.
|
||||||
|
expect(type.startsWith('str | None')).toBe(true)
|
||||||
|
expect(type.endsWith(' | None')).toBe(true)
|
||||||
|
expect(type.length).toBe('str'.length + ' | None'.length * depth)
|
||||||
|
})
|
||||||
|
|
||||||
it('emits pass for a subscript-only tool set (comments are not statements)', () => {
|
it('emits pass for a subscript-only tool set (comments are not statements)', () => {
|
||||||
const t: ToolSdkSchema = {
|
const t: ToolSdkSchema = {
|
||||||
name: 'my-exotic.tool',
|
name: 'my-exotic.tool',
|
||||||
|
|||||||
Reference in New Issue
Block a user