From b5b86f31133d040ec7f54a1f9adea25ec978e06b Mon Sep 17 00:00:00 2001 From: Dmitrii Baranov Date: Mon, 24 Nov 2025 15:58:39 +0100 Subject: [PATCH 1/4] fix: Don't skip required undefined properties on object --- src/value/default/from-object.ts | 5 ++--- test/typebox/runtime/value/default/object.ts | 10 ++++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/value/default/from-object.ts b/src/value/default/from-object.ts index 070ce115d..972975b4f 100644 --- a/src/value/default/from-object.ts +++ b/src/value/default/from-object.ts @@ -29,9 +29,8 @@ THE SOFTWARE. // deno-fmt-ignore-file // deno-lint-ignore-file -import type { TProperties, TObject } from '../../type/index.ts' +import { type TProperties, type TObject, IsOptional } from '../../type/index.ts' import { Guard } from '../../guard/index.ts' -import { FromDefault } from './from-default.ts' import { FromType } from './from-type.ts' import { IsAdditionalProperties } from '../../schema/types/index.ts' @@ -46,7 +45,7 @@ export function FromObject(context: TProperties, type: TObject, value: unknown): // yielded a non undefined result. Here we interpret an undefined result as // a non assignable property and continue. const propertyValue = FromType(context, type.properties[key], value[key]) - if (Guard.IsUndefined(propertyValue)) continue + if (Guard.IsUndefined(propertyValue) && (IsOptional(type.properties[key]) || !Guard.HasPropertyKey(type.properties[key], 'default') )) continue value[key] = FromType(context, type.properties[key], value[key]) } // return if not additional properties diff --git a/test/typebox/runtime/value/default/object.ts b/test/typebox/runtime/value/default/object.ts index d9bff9da7..498199603 100644 --- a/test/typebox/runtime/value/default/object.ts +++ b/test/typebox/runtime/value/default/object.ts @@ -260,3 +260,13 @@ Test('Should Default 22', () => { const R = Value.Default(X, { x: 2 }) Assert.IsEqual(R, { x: 2 }) }) +Test('Should Default 23', () => { + const X = Type.Object({ x: Type.Undefined({ default: undefined }) }) + const R = Value.Default(X, {}) + Assert.IsEqual(R, { x: undefined }) +}) +Test('Should Default 24', () => { + const X = Type.Object({ x: Type.Optional({ default: undefined }) }) + const R = Value.Default(X, {}) + Assert.IsEqual(R, {}) +}) From d020c9d8c6a9184bdaccaca90bc6703558258034 Mon Sep 17 00:00:00 2001 From: sinclair Date: Tue, 2 Dec 2025 21:30:41 +0900 Subject: [PATCH 2/4] UnassignableUndefined | Union Tests --- src/value/default/from-object.ts | 11 ++- test/typebox/runtime/value/default/object.ts | 84 ++++++++++++++++++++ 2 files changed, 91 insertions(+), 4 deletions(-) diff --git a/src/value/default/from-object.ts b/src/value/default/from-object.ts index 972975b4f..2cb4facac 100644 --- a/src/value/default/from-object.ts +++ b/src/value/default/from-object.ts @@ -41,11 +41,14 @@ export function FromObject(context: TProperties, type: TObject, value: unknown): const knownPropertyKeys = Guard.Keys(type.properties) // Properties for (const key of knownPropertyKeys) { - // note: we need to traverse into the object and test if the return value - // yielded a non undefined result. Here we interpret an undefined result as - // a non assignable property and continue. + // Resolve: Value from properties. const propertyValue = FromType(context, type.properties[key], value[key]) - if (Guard.IsUndefined(propertyValue) && (IsOptional(type.properties[key]) || !Guard.HasPropertyKey(type.properties[key], 'default') )) continue + + // Ambiguious Undefined: If the value is undefined, the type is optional there's no default. ignore. + const isUnassignableUndefined = Guard.IsUndefined(propertyValue) && (IsOptional(type.properties[key]) || !Guard.HasPropertyKey(type.properties[key], 'default') ) + if (isUnassignableUndefined) continue + + // Assign value[key] = FromType(context, type.properties[key], value[key]) } // return if not additional properties diff --git a/test/typebox/runtime/value/default/object.ts b/test/typebox/runtime/value/default/object.ts index 498199603..5662816b1 100644 --- a/test/typebox/runtime/value/default/object.ts +++ b/test/typebox/runtime/value/default/object.ts @@ -260,6 +260,11 @@ Test('Should Default 22', () => { const R = Value.Default(X, { x: 2 }) Assert.IsEqual(R, { x: 2 }) }) +// ------------------------------------------------------------------ +// Undefined +// +// https://github.com/sinclairzx81/typebox/pull/1463 +// ------------------------------------------------------------------ Test('Should Default 23', () => { const X = Type.Object({ x: Type.Undefined({ default: undefined }) }) const R = Value.Default(X, {}) @@ -270,3 +275,82 @@ Test('Should Default 24', () => { const R = Value.Default(X, {}) Assert.IsEqual(R, {}) }) +// ------------------------------------------------------------------ +// Additional: Undefined (Union Ordering Left-Right) +// ------------------------------------------------------------------ +Test('Should Default 25', () => { + const T = Type.Object({ + id: Type.Union([Type.Undefined(), Type.String()], { default: undefined }) + }) + const R = Value.Default(T, {}) + Assert.IsEqual(R, { id: undefined }) +}) +Test('Should Default 26', () => { + const T = Type.Object({ + id: Type.Union([Type.Undefined(), Type.String()], { default: undefined }) + }) + const R = Value.Default(T, { id: 'hello' }) + Assert.IsEqual(R, { id: 'hello' }) +}) +Test('Should Default 27', () => { + const T = Type.Object({ + id: Type.Union([Type.Undefined(), Type.String()], { default: undefined }) + }) + const R = Value.Default(T, { id: undefined }) + Assert.IsEqual(R, { id: undefined }) +}) +Test('Should Default 28', () => { + const T = Type.Object({ + id: Type.Union([Type.Undefined(), Type.String()], { default: undefined }) + }) + const R = Value.Default(T, { id: null }) + Assert.IsEqual(R, { id: null }) +}) +// ------------------------------------------------------------------ +// Additional: Undefined (Union Ordering Right-Left) +// ------------------------------------------------------------------ +Test('Should Default 29', () => { + const T = Type.Object({ + id: Type.Union([Type.String(), Type.Undefined()], { default: undefined }) + }) + const R = Value.Default(T, {}) + Assert.IsEqual(R, { id: undefined }) +}) +Test('Should Default 30', () => { + const T = Type.Object({ + id: Type.Union([Type.String(), Type.Undefined()], { default: undefined }) + }) + const R = Value.Default(T, { id: 'hello' }) + Assert.IsEqual(R, { id: 'hello' }) +}) +Test('Should Default 31', () => { + const T = Type.Object({ + id: Type.Union([Type.String(), Type.Undefined()], { default: undefined }) + }) + const R = Value.Default(T, { id: undefined }) + Assert.IsEqual(R, { id: undefined }) +}) +Test('Should Default 32', () => { + const T = Type.Object({ + id: Type.Union([Type.String(), Type.Undefined()], { default: undefined }) + }) + const R = Value.Default(T, { id: null }) + Assert.IsEqual(R, { id: null }) +}) +// ------------------------------------------------------------------ +// Sub Schema Resolution Match +// ------------------------------------------------------------------ +Test('Should Default 31', () => { + const T = Type.Object({ + id: Type.Union([Type.String({ default: 'hello' }), Type.Undefined()], { default: undefined }) + }) + const R = Value.Default(T, {}) + Assert.IsEqual(R, { id: 'hello' }) // pick first union match +}) +Test('Should Default 32', () => { + const T = Type.Object({ + id: Type.Union([Type.Undefined(), Type.String({ default: 'hello' })], { default: undefined }) + }) + const R = Value.Default(T, {}) + Assert.IsEqual(R, { id: undefined }) // pick first union match +}) From 9454a795dd2c5fa29685b4f8c89e526fdaa3bf9a Mon Sep 17 00:00:00 2001 From: sinclair Date: Tue, 2 Dec 2025 21:32:21 +0900 Subject: [PATCH 3/4] Comment --- src/type/extends/base.ts | 72 ++++++++++ src/type/extends/extends-left.ts | 4 + src/type/extends/extends-right.ts | 25 ++++ src/type/types/base.ts | 9 ++ src/value/default/from-object.ts | 2 +- .../runtime/type/engine/action/evaluate.ts | 4 +- test/typebox/runtime/type/extends/base.ts | 12 +- .../runtime/type/types/base-evaluate.ts | 128 ++++++++++++++++++ 8 files changed, 246 insertions(+), 10 deletions(-) create mode 100644 src/type/extends/base.ts create mode 100644 test/typebox/runtime/type/types/base-evaluate.ts diff --git a/src/type/extends/base.ts b/src/type/extends/base.ts new file mode 100644 index 000000000..4eda854a2 --- /dev/null +++ b/src/type/extends/base.ts @@ -0,0 +1,72 @@ +/*-------------------------------------------------------------------------- + +TypeBox + +The MIT License (MIT) + +Copyright (c) 2017-2025 Haydn Paterson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------------------------------------------------------------------*/ + +// deno-fmt-ignore-file + +import { type TSchema } from '../types/schema.ts' +import { type TProperties } from '../types/properties.ts' +import { type Base, IsBase } from '../types/base.ts' +import { type TExtendsRight, ExtendsRight } from './extends-right.ts' + +import * as Result from './result.ts' + +// ------------------------------------------------------------------ +// ExtendsBase +// +// Two Base instances extend each other if they are instances of +// the same class (i.e., they have the same constructor). This +// models TypeScript's structural type checking at runtime for +// custom Base types. +// +// Note: At the type level, TypeScript cannot determine if two Base +// instances have the same constructor, so we check if they're +// mutually assignable (both ways) which indicates they're likely +// the same type. +// ------------------------------------------------------------------ +export type TExtendsBase = ( + Right extends Base + ? [Left] extends [Right] + ? [Right] extends [Left] + ? Result.TExtendsTrue + : Result.TExtendsFalse + : Result.TExtendsFalse + : TExtendsRight +) + +export function ExtendsBase + (inferred: Inferred, left: Left, right: Right): + TExtendsBase { + return ( + IsBase(right) + // Use Equals method for checking equality between Base instances + ? (left.Equals(right) + ? Result.ExtendsTrue(inferred) + : Result.ExtendsFalse()) + : ExtendsRight(inferred, left, right) + ) as never +} diff --git a/src/type/extends/extends-left.ts b/src/type/extends/extends-left.ts index 4d57a2fa4..b191b92bf 100644 --- a/src/type/extends/extends-left.ts +++ b/src/type/extends/extends-left.ts @@ -31,6 +31,7 @@ THE SOFTWARE. import { type TExtendsAny, ExtendsAny } from './any.ts' import { type TExtendsArray, ExtendsArray } from './array.ts' import { type TExtendsAsyncIterator, ExtendsAsyncIterator } from './async-iterator.ts' +import { type TExtendsBase, ExtendsBase } from './base.ts' import { type TExtendsBigInt, ExtendsBigInt } from './bigint.ts' import { type TExtendsBoolean, ExtendsBoolean } from './boolean.ts' import { type TExtendsConstructor, ExtendsConstructor } from './constructor.ts' @@ -60,6 +61,7 @@ import { type TExtendsVoid, ExtendsVoid } from './void.ts' import { type TAny, IsAny } from '../types/any.ts' import { type TArray, IsArray } from '../types/array.ts' import { type TAsyncIterator, IsAsyncIterator } from '../types/async-iterator.ts' +import { type Base, IsBase } from '../types/base.ts' import { type TBigInt, IsBigInt } from '../types/bigint.ts' import { type TBoolean, IsBoolean } from '../types/boolean.ts' import { type TConstructor, IsConstructor } from '../types/constructor.ts' @@ -94,6 +96,7 @@ export type TExtendsLeft : Left extends TArray ? TExtendsArray : Left extends TAsyncIterator ? TExtendsAsyncIterator : + Left extends Base ? TExtendsBase : Left extends TBigInt ? TExtendsBigInt : Left extends TBoolean ? TExtendsBoolean : Left extends TConstructor ? TExtendsConstructor : @@ -125,6 +128,7 @@ export function ExtendsLeft return Result.ExtendsTrue(inferred) } // ---------------------------------------------------------------------------- +// ExtendsRightBase +// ---------------------------------------------------------------------------- +type TExtendsRightBase = ( + Left extends Base + ? Left extends Right + ? Result.TExtendsTrue + : Result.TExtendsFalse + : Result.TExtendsFalse +) +function ExtendsRightBase + (inferred: Inferred, left: Left, right: Right): + TExtendsRightBase { + return ( + IsBase(left) + // Use Equals method for checking equality between Base instances + ? (right.Equals(left) + ? Result.ExtendsTrue(inferred) + : Result.ExtendsFalse()) + : Result.ExtendsFalse() + ) as never +} +// ---------------------------------------------------------------------------- // ExtendsRightEnum // ---------------------------------------------------------------------------- type TExtendsRightEnum = ( Right extends TAny ? TExtendsRightAny : + Right extends Base ? TExtendsRightBase : Right extends TEnum ? TExtendsRightEnum : Right extends TInfer ? TExtendsRightInfer : Right extends TTemplateLiteral ? TExtendsRightTemplateLiteral : @@ -172,6 +196,7 @@ export function ExtendsRight { return ( IsAny(right) ? ExtendsRightAny(inferred, left) : + IsBase(right) ? ExtendsRightBase(inferred, left, right) : IsEnum(right) ? ExtendsRightEnum(inferred, left, right.enum) : IsInfer(right) ? ExtendsRightInfer(inferred, right.name, left, right.extends) : IsIntersect(right) ? ExtendsRightIntersect(inferred, left, right.allOf) : diff --git a/src/type/types/base.ts b/src/type/types/base.ts index 5f9ca3bee..aefeb2600 100644 --- a/src/type/types/base.ts +++ b/src/type/types/base.ts @@ -92,6 +92,15 @@ export class Base implements TSchema, XGuard { Type.Object({ value: Type.Optional(new Foo()) }) ])) Assert.IsTrue(Type.IsOptional(T.properties.value)) - Assert.IsTrue(Type.IsNever(T.properties.value)) + Assert.IsTrue(T.properties.value instanceof Foo) }) Test('Should Evaluate 65', () => { const T = Type.Evaluate(Type.Intersect([ @@ -814,7 +814,7 @@ Test('Should Evaluate 65', () => { Type.Object({ value: new Foo() }) ])) Assert.IsFalse(Type.IsOptional(T.properties.value)) - Assert.IsTrue(Type.IsNever(T.properties.value)) + Assert.IsTrue(T.properties.value instanceof Foo) }) Test('Should Evaluate 66', () => { const T = Type.Evaluate(Type.Intersect([ diff --git a/test/typebox/runtime/type/extends/base.ts b/test/typebox/runtime/type/extends/base.ts index 93448e1c1..640eac019 100644 --- a/test/typebox/runtime/type/extends/base.ts +++ b/test/typebox/runtime/type/extends/base.ts @@ -14,15 +14,13 @@ export class TBase extends Type.Base { const Base = new TBase() // ------------------------------------------------------------------ -// Identity: Note, it's not obvious to how to handle Base checks -// without relying on nominal typing, while technically possible -// to check for the constructor, bundlers and downlevel targets -// make this a unreliable check. For now, Base is considered a -// distinct type of itself. +// Identity: Base types extend each other if they are instances +// of the same class (constructor check). This models TypeScript's +// structural type checking at runtime. // ------------------------------------------------------------------ Test('Should Extends 0', () => { - const R: ExtendsResult.TExtendsFalse = Extends({}, Base, Base) - Assert.IsTrue(ExtendsResult.IsExtendsFalse(R)) + const R: ExtendsResult.TExtendsTrue<{}> = Extends({}, Base, Base) + Assert.IsTrue(ExtendsResult.IsExtendsTrue(R)) }) // ------------------------------------------------------------------ // Invariant diff --git a/test/typebox/runtime/type/types/base-evaluate.ts b/test/typebox/runtime/type/types/base-evaluate.ts new file mode 100644 index 000000000..331c70625 --- /dev/null +++ b/test/typebox/runtime/type/types/base-evaluate.ts @@ -0,0 +1,128 @@ +import { Assert } from 'test' +import * as Type from 'typebox' + +const Test = Assert.Context('Type.Base.Evaluate') + +// ------------------------------------------------------------------ +// Base Type Intersection with Evaluate +// ------------------------------------------------------------------ +class TId extends Type.Base { + public override Check(value: unknown): value is string { + return typeof value === 'string' + } + public override Clone(): TId { + return new TId() + } +} + +Test('Should evaluate intersect of same Base class instances as equal 1', () => { + const schemaNonEvaluated = Type.Intersect([ + new TId(), + new TId(), + ]) + const schemaEvaluated = Type.Evaluate(schemaNonEvaluated) + + // Should not be never + Assert.IsFalse(Type.IsNever(schemaEvaluated)) + // Should be TId (Base type) + Assert.IsTrue(Type.IsBase(schemaEvaluated)) +}) + +Test('Should evaluate intersect of same Base class instances as equal 2', () => { + const T1 = new TId() + const T2 = new TId() + const schemaNonEvaluated = Type.Intersect([T1, T2]) + const schemaEvaluated = Type.Evaluate(schemaNonEvaluated) + + // Should not be never + Assert.IsFalse(Type.IsNever(schemaEvaluated)) + // Should be TId (Base type) + Assert.IsTrue(Type.IsBase(schemaEvaluated)) + // Should be instance of TId + Assert.IsTrue(schemaEvaluated instanceof TId) +}) + +Test('Should evaluate intersect of different Base class instances as never', () => { + class TId2 extends Type.Base { + public override Check(value: unknown): value is number { + return typeof value === 'number' + } + public override Clone(): TId2 { + return new TId2() + } + } + + const schemaNonEvaluated = Type.Intersect([ + new TId(), + new TId2(), + ]) + const schemaEvaluated = Type.Evaluate(schemaNonEvaluated) + + // Should be never + Assert.IsTrue(Type.IsNever(schemaEvaluated)) +}) + +Test('Should evaluate single Base type in intersect', () => { + const schemaNonEvaluated = Type.Intersect([ + new TId(), + ]) + const schemaEvaluated = Type.Evaluate(schemaNonEvaluated) + + // Should not be never + Assert.IsFalse(Type.IsNever(schemaEvaluated)) + // Should be TId (Base type) + Assert.IsTrue(Type.IsBase(schemaEvaluated)) + // Should be instance of TId + Assert.IsTrue(schemaEvaluated instanceof TId) +}) + +Test('Should evaluate intersect of Base type with Object', () => { + const schemaNonEvaluated = Type.Intersect([ + new TId(), + Type.Object({ foo: Type.String() }), + ]) + const schemaEvaluated = Type.Evaluate(schemaNonEvaluated) + + // Should be an object with foo property + Assert.IsTrue(Type.IsObject(schemaEvaluated)) + Assert.IsTrue('foo' in schemaEvaluated.properties) + Assert.IsTrue(Type.IsString(schemaEvaluated.properties.foo)) +}) + +Test('Should evaluate triple intersect of same Base class instances', () => { + const schemaNonEvaluated = Type.Intersect([ + new TId(), + new TId(), + new TId(), + ]) + const schemaEvaluated = Type.Evaluate(schemaNonEvaluated) + + // Should not be never + Assert.IsFalse(Type.IsNever(schemaEvaluated)) + // Should be TId (Base type) + Assert.IsTrue(Type.IsBase(schemaEvaluated)) + // Should be instance of TId + Assert.IsTrue(schemaEvaluated instanceof TId) +}) + +// Type-level check (from the original issue) +Test('Should preserve type-level types correctly', () => { + const schemaNonEvaluated = Type.Intersect([ + new TId(), + new TId(), + ]) + type TNonEvaluated = Type.StaticDecode + + const schemaEvaluated = Type.Evaluate(schemaNonEvaluated) + type TEvaluated = Type.StaticDecode + + // Runtime checks + Assert.IsFalse(Type.IsNever(schemaEvaluated)) + Assert.IsTrue(Type.IsBase(schemaEvaluated)) + + // Type-level assertions (compile-time checks) + const _nonEval: TNonEvaluated = 'test' // should be string + const _eval: TEvaluated = 'test' // should be string, not never + + Assert.IsTrue(true) // If compiles, test passes +}) From a31aef674e4581bd3d09a37866a26d72ececb22e Mon Sep 17 00:00:00 2001 From: Dmitrii Baranov Date: Mon, 8 Dec 2025 16:10:03 +0100 Subject: [PATCH 4/4] fix: EType.Evaluate respects Type.Base during Type.Intersect --- src/system/memory/discard.ts | 6 ++-- .../runtime/type/engine/action/evaluate.ts | 36 +++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/system/memory/discard.ts b/src/system/memory/discard.ts index a4862b516..912a8fe33 100644 --- a/src/system/memory/discard.ts +++ b/src/system/memory/discard.ts @@ -30,14 +30,16 @@ THE SOFTWARE. import { Metrics } from "./metrics.ts" import { Clone } from './clone.ts' +import { IsBase } from '../../type/types/base.ts' type ObjectLike = Record /** Discards multiple property keys from the given object value */ export function Discard(value: ObjectLike, propertyKeys: PropertyKey[]): ObjectLike { Metrics.discard += 1 - const result: ObjectLike = {} - const descriptors = Object.getOwnPropertyDescriptors(Clone(value)) + const clone = Clone(value) + const result: ObjectLike = IsBase(value) ? clone : {} + const descriptors = Object.getOwnPropertyDescriptors(clone) const keysToDiscard = new Set(propertyKeys) for (const key of Object.keys(descriptors)) { if (keysToDiscard.has(key)) continue diff --git a/test/typebox/runtime/type/engine/action/evaluate.ts b/test/typebox/runtime/type/engine/action/evaluate.ts index c81c28527..93e93cba8 100644 --- a/test/typebox/runtime/type/engine/action/evaluate.ts +++ b/test/typebox/runtime/type/engine/action/evaluate.ts @@ -806,6 +806,7 @@ Test('Should Evaluate 64', () => { Type.Object({ value: Type.Optional(new Foo()) }) ])) Assert.IsTrue(Type.IsOptional(T.properties.value)) + Assert.IsTrue(Type.IsBase(T.properties.value)) Assert.IsTrue(T.properties.value instanceof Foo) }) Test('Should Evaluate 65', () => { @@ -814,6 +815,7 @@ Test('Should Evaluate 65', () => { Type.Object({ value: new Foo() }) ])) Assert.IsFalse(Type.IsOptional(T.properties.value)) + Assert.IsTrue(Type.IsBase(T.properties.value)) Assert.IsTrue(T.properties.value instanceof Foo) }) Test('Should Evaluate 66', () => { @@ -825,3 +827,37 @@ Test('Should Evaluate 66', () => { Assert.IsTrue(Type.IsBase(T.properties.value)) Assert.IsTrue(T.properties.value instanceof Foo) }) +class Bar extends Type.Base { + public constructor(private kind: T) { + super(); + } + public override Check(value: unknown): value is unknown { + return true + } + public override Errors(value: unknown): object[] { + return [] + } + public override Clone(): Bar { + return new Bar(this.kind) + } + public override Equals(other: unknown): other is this { + return other instanceof Bar && other.kind === this.kind + } +} +Test('Should Evaluate 67', () => { + const T = Type.Evaluate(Type.Intersect([ + Type.Object({ value: Type.Optional(new Bar('1')) }), + Type.Object({ value: Type.Optional(new Bar('1')) }) + ])) + Assert.IsTrue(Type.IsOptional(T.properties.value)) + Assert.IsTrue(Type.IsBase(T.properties.value)) + Assert.IsTrue(T.properties.value instanceof Bar) +}) +Test('Should Evaluate 68', () => { + const T = Type.Evaluate(Type.Intersect([ + Type.Object({ value: Type.Optional(new Bar('1')) }), + Type.Object({ value: Type.Optional(new Bar('2')) }) + ])) + Assert.IsTrue(Type.IsOptional(T.properties.value)) + Assert.IsTrue(Type.IsNever(T.properties.value)) +}) \ No newline at end of file