Skip to content

Commit

Permalink
Revision 0.34.11 (#1110)
Browse files Browse the repository at this point in the history
* Fix Compile For Deep Referential Module Types

* Version

* ChangeLog
  • Loading branch information
sinclairzx81 authored Dec 5, 2024
1 parent 3976541 commit 88a4819
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 13 deletions.
2 changes: 2 additions & 0 deletions changelog/0.34.0.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
### 0.34.0
- [Revision 0.34.11](https://github.com/sinclairzx81/typebox/pull/1110)
- Fix Compiler Emit for Deeply Referential Module Types
- [Revision 0.34.10](https://github.com/sinclairzx81/typebox/pull/1107)
- Fix Declaration Emit for Index and Mapped Types
- Fix Record Inference Presentation when Embedded in Modules
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@sinclair/typebox",
"version": "0.34.10",
"version": "0.34.11",
"description": "Json Schema Type Builder with Static Type Resolution for TypeScript",
"keywords": [
"typescript",
Expand Down
21 changes: 11 additions & 10 deletions src/compiler/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ import type { TNumber } from '../type/number/index'
import type { TObject } from '../type/object/index'
import type { TPromise } from '../type/promise/index'
import type { TRecord } from '../type/record/index'
import type { TRef } from '../type/ref/index'
import { Ref, type TRef } from '../type/ref/index'
import type { TRegExp } from '../type/regexp/index'
import type { TTemplateLiteral } from '../type/template-literal/index'
import type { TThis } from '../type/recursive/index'
Expand Down Expand Up @@ -298,9 +298,10 @@ export namespace TypeCompiler {
yield `(typeof ${value} === 'function')`
}
function* FromImport(schema: TImport, references: TSchema[], value: string): IterableIterator<string> {
const definitions = globalThis.Object.values(schema.$defs) as TSchema[]
const target = schema.$defs[schema.$ref] as TSchema
yield* Visit(target, [...references, ...definitions], value)
const members = globalThis.Object.getOwnPropertyNames(schema.$defs).reduce((result, key) => {
return [...result, schema.$defs[key as never] as TSchema]
}, [] as TSchema[])
yield* Visit(Ref(schema.$ref), [...references, ...members], value)
}
function* FromInteger(schema: TInteger, references: TSchema[], value: string): IterableIterator<string> {
yield `Number.isInteger(${value})`
Expand Down Expand Up @@ -399,12 +400,8 @@ export namespace TypeCompiler {
function* FromRef(schema: TRef, references: TSchema[], value: string): IterableIterator<string> {
const target = Deref(schema, references)
// Reference: If we have seen this reference before we can just yield and return the function call.
// If this isn't the case we defer to visit to generate and set the _recursion_end_for_ for subsequent
// passes. This operation is very awkward as we are using the functions state to store values to
// enable self referential types to terminate. This needs to be refactored.
const recursiveEnd = `_recursion_end_for_${schema.$ref}`
if (state.functions.has(recursiveEnd)) return yield `${CreateFunctionName(schema.$ref)}(${value})`
state.functions.set(recursiveEnd, '') // terminate recursion here by setting the name.
// If this isn't the case we defer to visit to generate and set the function for subsequent passes.
if (state.functions.has(schema.$ref)) return yield `${CreateFunctionName(schema.$ref)}(${value})`
yield* Visit(target, references, value)
}
function* FromRegExp(schema: TRegExp, references: TSchema[], value: string): IterableIterator<string> {
Expand Down Expand Up @@ -481,6 +478,10 @@ export namespace TypeCompiler {
if (state.functions.has(functionName)) {
return yield `${functionName}(${value})`
} else {
// Note: In the case of cyclic types, we need to create a 'functions' record
// to prevent infinitely re-visiting the CreateFunction. Subsequent attempts
// to visit will be caught by the above condition.
state.functions.set(functionName, '<deferred>')
const functionCode = CreateFunction(functionName, schema, references, 'value', false)
state.functions.set(functionName, functionCode)
return yield `${functionName}(${value})`
Expand Down
35 changes: 35 additions & 0 deletions test/runtime/compiler-ajv/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,39 @@ describe('compiler-ajv/Module', () => {
Ok(T, { y: [null], w: [null] })
Fail(T, { x: [1], y: [null], w: [null] })
})
// ----------------------------------------------------------------
// https://github.com/sinclairzx81/typebox/issues/1109
// ----------------------------------------------------------------
it('Should validate deep referential 1', () => {
const Module = Type.Module({
A: Type.Union([Type.Literal('Foo'), Type.Literal('Bar')]),
B: Type.Ref('A'),
C: Type.Object({ ref: Type.Ref('B') }),
D: Type.Union([Type.Ref('B'), Type.Ref('C')]),
})
Ok(Module.Import('A') as never, 'Foo')
Ok(Module.Import('A') as never, 'Bar')
Ok(Module.Import('B') as never, 'Foo')
Ok(Module.Import('B') as never, 'Bar')
Ok(Module.Import('C') as never, { ref: 'Foo' })
Ok(Module.Import('C') as never, { ref: 'Bar' })
Ok(Module.Import('D') as never, 'Foo')
Ok(Module.Import('D') as never, 'Bar')
Ok(Module.Import('D') as never, { ref: 'Foo' })
Ok(Module.Import('D') as never, { ref: 'Bar' })
})
it('Should validate deep referential 2', () => {
const Module = Type.Module({
A: Type.Literal('Foo'),
B: Type.Ref('A'),
C: Type.Ref('B'),
D: Type.Ref('C'),
E: Type.Ref('D'),
})
Ok(Module.Import('A'), 'Foo')
Ok(Module.Import('B'), 'Foo')
Ok(Module.Import('C'), 'Foo')
Ok(Module.Import('D'), 'Foo')
Ok(Module.Import('E'), 'Foo')
})
})
35 changes: 35 additions & 0 deletions test/runtime/compiler/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,39 @@ describe('compiler/Module', () => {
Ok(T, { y: [null], w: [null] })
Fail(T, { x: [1], y: [null], w: [null] })
})
// ----------------------------------------------------------------
// https://github.com/sinclairzx81/typebox/issues/1109
// ----------------------------------------------------------------
it('Should validate deep referential 1', () => {
const Module = Type.Module({
A: Type.Union([Type.Literal('Foo'), Type.Literal('Bar')]),
B: Type.Ref('A'),
C: Type.Object({ ref: Type.Ref('B') }),
D: Type.Union([Type.Ref('B'), Type.Ref('C')]),
})
Ok(Module.Import('A') as never, 'Foo')
Ok(Module.Import('A') as never, 'Bar')
Ok(Module.Import('B') as never, 'Foo')
Ok(Module.Import('B') as never, 'Bar')
Ok(Module.Import('C') as never, { ref: 'Foo' })
Ok(Module.Import('C') as never, { ref: 'Bar' })
Ok(Module.Import('D') as never, 'Foo')
Ok(Module.Import('D') as never, 'Bar')
Ok(Module.Import('D') as never, { ref: 'Foo' })
Ok(Module.Import('D') as never, { ref: 'Bar' })
})
it('Should validate deep referential 2', () => {
const Module = Type.Module({
A: Type.Literal('Foo'),
B: Type.Ref('A'),
C: Type.Ref('B'),
D: Type.Ref('C'),
E: Type.Ref('D'),
})
Ok(Module.Import('A'), 'Foo')
Ok(Module.Import('B'), 'Foo')
Ok(Module.Import('C'), 'Foo')
Ok(Module.Import('D'), 'Foo')
Ok(Module.Import('E'), 'Foo')
})
})
35 changes: 35 additions & 0 deletions test/runtime/value/check/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,39 @@ describe('value/check/Module', () => {
Assert.IsTrue(Value.Check(T, { y: [null], w: [null] }))
Assert.IsFalse(Value.Check(T, { x: [1], y: [null], w: [null] }))
})
// ----------------------------------------------------------------
// https://github.com/sinclairzx81/typebox/issues/1109
// ----------------------------------------------------------------
it('Should validate deep referential 1', () => {
const Module = Type.Module({
A: Type.Union([Type.Literal('Foo'), Type.Literal('Bar')]),
B: Type.Ref('A'),
C: Type.Object({ ref: Type.Ref('B') }),
D: Type.Union([Type.Ref('B'), Type.Ref('C')]),
})
Assert.IsTrue(Value.Check(Module.Import('A') as never, 'Foo'))
Assert.IsTrue(Value.Check(Module.Import('A') as never, 'Bar'))
Assert.IsTrue(Value.Check(Module.Import('B') as never, 'Foo'))
Assert.IsTrue(Value.Check(Module.Import('B') as never, 'Bar'))
Assert.IsTrue(Value.Check(Module.Import('C') as never, { ref: 'Foo' }))
Assert.IsTrue(Value.Check(Module.Import('C') as never, { ref: 'Bar' }))
Assert.IsTrue(Value.Check(Module.Import('D') as never, 'Foo'))
Assert.IsTrue(Value.Check(Module.Import('D') as never, 'Bar'))
Assert.IsTrue(Value.Check(Module.Import('D') as never, { ref: 'Foo' }))
Assert.IsTrue(Value.Check(Module.Import('D') as never, { ref: 'Bar' }))
})
it('Should validate deep referential 2', () => {
const Module = Type.Module({
A: Type.Literal('Foo'),
B: Type.Ref('A'),
C: Type.Ref('B'),
D: Type.Ref('C'),
E: Type.Ref('D'),
})
Assert.IsTrue(Value.Check(Module.Import('A'), 'Foo'))
Assert.IsTrue(Value.Check(Module.Import('B'), 'Foo'))
Assert.IsTrue(Value.Check(Module.Import('C'), 'Foo'))
Assert.IsTrue(Value.Check(Module.Import('D'), 'Foo'))
Assert.IsTrue(Value.Check(Module.Import('E'), 'Foo'))
})
})

0 comments on commit 88a4819

Please sign in to comment.