Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revision 0.34.11 #1110

Merged
merged 6 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'))
})
})
Loading