Skip to content

Commit

Permalink
Merge pull request #657 from samchon/features/intersection
Browse files Browse the repository at this point in the history
Fix #654 - support intersection type with `atomic & object`
  • Loading branch information
samchon authored Jun 11, 2023
2 parents 7f627b7 + 3be8ef9 commit dba6351
Show file tree
Hide file tree
Showing 198 changed files with 6,984 additions and 27 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "typia",
"version": "4.0.5",
"version": "4.0.6-dev.20230611",
"description": "Superfast runtime validators with only one line",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
Expand Down
4 changes: 2 additions & 2 deletions packages/typescript-json/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "typescript-json",
"version": "4.0.5",
"version": "4.0.6-dev.20230611",
"description": "Superfast runtime validators with only one line",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
Expand Down Expand Up @@ -68,7 +68,7 @@
},
"homepage": "https://typia.io",
"dependencies": {
"typia": "4.0.5"
"typia": "4.0.6-dev.20230611"
},
"peerDependencies": {
"typescript": ">= 4.5.2"
Expand Down
8 changes: 7 additions & 1 deletion src/Primitive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,13 @@ export type Primitive<T> = Equal<T, PrimitiveMain<T>> extends true

type Equal<X, Y> = X extends Y ? (Y extends X ? true : false) : false;

type PrimitiveMain<Instance> = ValueOf<Instance> extends object
type PrimitiveMain<Instance> = ValueOf<Instance> extends
| boolean
| number
| bigint
| string
? ValueOf<Instance>
: ValueOf<Instance> extends object
? Instance extends object
? Instance extends NativeClass
? {}
Expand Down
7 changes: 7 additions & 0 deletions src/factories/internal/metadata/iterate_metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { iterate_metadata_atomic } from "./iterate_metadata_atomic";
import { iterate_metadata_coalesce } from "./iterate_metadata_coalesce";
import { iterate_metadata_constant } from "./iterate_metadata_constant";
import { iterate_metadata_definition } from "./iterate_metadata_definition";
import { iterate_metadata_intersection } from "./iterate_metadata_intersection";
import { iterate_metadata_map } from "./iterate_metadata_map";
import { iterate_metadata_native } from "./iterate_metadata_native";
import { iterate_metadata_object } from "./iterate_metadata_object";
Expand Down Expand Up @@ -40,6 +41,12 @@ export const iterate_metadata =
meta,
type,
)) ||
iterate_metadata_intersection(checker)(options)(collection)(
meta,
type,
parentResolved,
aliased,
) ||
iterate_metadata_union(checker)(options)(collection)(
meta,
type,
Expand Down
86 changes: 86 additions & 0 deletions src/factories/internal/metadata/iterate_metadata_intersection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import ts from "typescript";

import { Metadata } from "../../../metadata/Metadata";

import { MetadataCollection } from "../../MetadataCollection";
import { MetadataFactory } from "../../MetadataFactory";
import { explore_metadata } from "./explore_metadata";
import { iterate_metadata } from "./iterate_metadata";
import { iterate_metadata_object } from "./iterate_metadata_object";

export const iterate_metadata_intersection =
(checker: ts.TypeChecker) =>
(options: MetadataFactory.IOptions) =>
(collection: MetadataCollection) =>
(
meta: Metadata,
type: ts.Type,
resolved: boolean,
aliased: boolean,
): boolean => {
if (!type.isIntersection()) return false;

// COSTRUCT FAKE METADATA LIST
const fakeCollection: MetadataCollection = new MetadataCollection();
const children: Metadata[] = [
...new Map(
type.types.map((t) => {
const m: Metadata = explore_metadata(checker)(options)(
fakeCollection,
)(t, resolved);
return [m.getName(), m] as const;
}),
).values(),
];

// ONLY ONE CHILD AFTER REMOVING DUPLICATES
if (children.length === 1) {
iterate_metadata(checker)(options)(collection)(
meta,
type.types[0]!,
resolved,
aliased,
);
return true;
}

// ONLY OBJECT TYPES -> MERGE
const object: boolean = children.every(
(c) => c.objects.length && c.objects.length === c.size(),
);
if (object)
return iterate_metadata_object(checker)(options)(collection)(
meta,
type,
resolved,
true,
);

// ABSORB TO ATOMIC (OR CONSTANT) TYPE
const atomics: Metadata[] = children.filter(
(c) =>
(c.atomics.length ? 1 : 0 + c.constants.length ? 1 : 0) ===
c.bucket(),
);
const objects: Metadata[] = children.filter(
(c) => c.objects.length && c.objects.length === c.size(),
);
if (
atomics.length === 0 ||
atomics.length + objects.length !== children.length
)
throw new Error(message(children));

const least: Metadata = atomics.reduce((x, y) => {
if (Metadata.covers(x, y)) return y;
else if (Metadata.covers(y, x)) return x;
throw new Error(message(children));
});
Object.assign(meta, Metadata.merge(meta, least));
return true;
};

const message = (children: Metadata[]) =>
`Error on typia.MetadataFactory.analyze(): nonsensibl intersection type detected - ${children
.map((c) => c.getName())
.join(" & ")}.`;
54 changes: 31 additions & 23 deletions src/factories/internal/metadata/iterate_metadata_object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,39 @@ export const iterate_metadata_object =
(checker: ts.TypeChecker) =>
(options: MetadataFactory.IOptions) =>
(collection: MetadataCollection) =>
(meta: Metadata, type: ts.Type, parentResolved: boolean): boolean => {
const filter = (flag: ts.TypeFlags) => (type.getFlags() & flag) !== 0;
if (
!filter(ts.TypeFlags.Object) &&
!type.isIntersection() &&
(type as any).intrinsicName !== "object"
)
return false;
else if (type.isIntersection()) {
const fakeCollection = new MetadataCollection();
const fakeSchema: Metadata = Metadata.initialize();

type.types.forEach((t) =>
iterate_metadata(checker)(options)(fakeCollection)(
fakeSchema,
t,
parentResolved,
false,
),
);
(
meta: Metadata,
type: ts.Type,
parentResolved: boolean,
ensure: boolean = false,
): boolean => {
if (ensure === false) {
const filter = (flag: ts.TypeFlags) =>
(type.getFlags() & flag) !== 0;
if (
fakeSchema.objects.length === 0 ||
fakeSchema.objects.length !== fakeSchema.size()
!filter(ts.TypeFlags.Object) &&
!type.isIntersection() &&
(type as any).intrinsicName !== "object"
)
return true;
return false;
else if (type.isIntersection()) {
const fakeCollection = new MetadataCollection();
const fakeSchema: Metadata = Metadata.initialize();

type.types.forEach((t) =>
iterate_metadata(checker)(options)(fakeCollection)(
fakeSchema,
t,
parentResolved,
false,
),
);
if (
fakeSchema.objects.length === 0 ||
fakeSchema.objects.length !== fakeSchema.size()
)
return false;
}
}

const obj: MetadataObject = emplace_metadata_object(checker)(options)(
Expand Down
1 change: 1 addition & 0 deletions src/metadata/Metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,7 @@ export namespace Metadata {

// CONSTANTS
for (const yc of y.constants) {
if (x.atomics.some((type) => yc.type === type)) continue;
const xc: MetadataConstant | undefined = x.constants.find(
(elem) => elem.type === yc.type,
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import typia from "typia";

import { _test_application } from "../../../internal/_test_application";
import { AtomicIntersection } from "../../../structures/AtomicIntersection";

export const test_application_ajv_AtomicIntersection = _test_application("ajv")(
"AtomicIntersection",
typia.application<[AtomicIntersection], "ajv">(),
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import typia from "typia";

import { _test_application } from "../../../internal/_test_application";
import { ConstantIntersection } from "../../../structures/ConstantIntersection";

export const test_application_ajv_ConstantIntersection = _test_application(
"ajv",
)("ConstantIntersection", typia.application<[ConstantIntersection], "ajv">());
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import typia from "typia";

import { _test_application } from "../../../internal/_test_application";
import { AtomicIntersection } from "../../../structures/AtomicIntersection";

export const test_application_swagger_AtomicIntersection = _test_application(
"swagger",
)("AtomicIntersection", typia.application<[AtomicIntersection], "swagger">());
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import typia from "typia";

import { _test_application } from "../../../internal/_test_application";
import { ConstantIntersection } from "../../../structures/ConstantIntersection";

export const test_application_swagger_ConstantIntersection = _test_application(
"swagger",
)(
"ConstantIntersection",
typia.application<[ConstantIntersection], "swagger">(),
);
10 changes: 10 additions & 0 deletions test/features/assert/test_assert_AtomicIntersection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import typia from "../../../src";
import { _test_assert } from "../../internal/_test_assert";
import { AtomicIntersection } from "../../structures/AtomicIntersection";

export const test_assert_AtomicIntersection = _test_assert(
"AtomicIntersection",
AtomicIntersection.generate,
(input) => typia.assert(input),
AtomicIntersection.SPOILERS,
);
10 changes: 10 additions & 0 deletions test/features/assert/test_assert_ConstantIntersection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import typia from "../../../src";
import { _test_assert } from "../../internal/_test_assert";
import { ConstantIntersection } from "../../structures/ConstantIntersection";

export const test_assert_ConstantIntersection = _test_assert(
"ConstantIntersection",
ConstantIntersection.generate,
(input) => typia.assert(input),
ConstantIntersection.SPOILERS,
);
10 changes: 10 additions & 0 deletions test/features/assertClone/test_assertClone_AtomicIntersection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import typia from "../../../src";
import { _test_assertClone } from "../../internal/_test_assertClone";
import { AtomicIntersection } from "../../structures/AtomicIntersection";

export const test_assertClone_AtomicIntersection = _test_assertClone(
"AtomicIntersection",
AtomicIntersection.generate,
(input) => typia.assertClone(input),
AtomicIntersection.SPOILERS,
);
10 changes: 10 additions & 0 deletions test/features/assertClone/test_assertClone_ConstantIntersection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import typia from "../../../src";
import { _test_assertClone } from "../../internal/_test_assertClone";
import { ConstantIntersection } from "../../structures/ConstantIntersection";

export const test_assertClone_ConstantIntersection = _test_assertClone(
"ConstantIntersection",
ConstantIntersection.generate,
(input) => typia.assertClone(input),
ConstantIntersection.SPOILERS,
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import typia from "../../../src";
import { _test_assertEquals } from "../../internal/_test_assertEquals";
import { AtomicIntersection } from "../../structures/AtomicIntersection";

export const test_assertEquals_AtomicIntersection = _test_assertEquals(
"AtomicIntersection",
AtomicIntersection.generate,
(input) => typia.assertEquals(input),
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import typia from "../../../src";
import { _test_assertEquals } from "../../internal/_test_assertEquals";
import { ConstantIntersection } from "../../structures/ConstantIntersection";

export const test_assertEquals_ConstantIntersection = _test_assertEquals(
"ConstantIntersection",
ConstantIntersection.generate,
(input) => typia.assertEquals(input),
);
10 changes: 10 additions & 0 deletions test/features/assertParse/test_assertParse_AtomicIntersection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import typia from "../../../src";
import { _test_assertParse } from "../../internal/_test_assertParse";
import { AtomicIntersection } from "../../structures/AtomicIntersection";

export const test_assertParse_AtomicIntersection = _test_assertParse(
"AtomicIntersection",
AtomicIntersection.generate,
(input) => typia.assertParse<AtomicIntersection>(input),
AtomicIntersection.SPOILERS,
);
10 changes: 10 additions & 0 deletions test/features/assertParse/test_assertParse_ConstantIntersection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import typia from "../../../src";
import { _test_assertParse } from "../../internal/_test_assertParse";
import { ConstantIntersection } from "../../structures/ConstantIntersection";

export const test_assertParse_ConstantIntersection = _test_assertParse(
"ConstantIntersection",
ConstantIntersection.generate,
(input) => typia.assertParse<ConstantIntersection>(input),
ConstantIntersection.SPOILERS,
);
10 changes: 10 additions & 0 deletions test/features/assertPrune/test_assertPrune_AtomicIntersection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import typia from "../../../src";
import { _test_assertPrune } from "../../internal/_test_assertPrune";
import { AtomicIntersection } from "../../structures/AtomicIntersection";

export const test_assertPrune_AtomicIntersection = _test_assertPrune(
"AtomicIntersection",
AtomicIntersection.generate,
(input) => typia.assertPrune(input),
AtomicIntersection.SPOILERS,
);
10 changes: 10 additions & 0 deletions test/features/assertPrune/test_assertPrune_ConstantIntersection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import typia from "../../../src";
import { _test_assertPrune } from "../../internal/_test_assertPrune";
import { ConstantIntersection } from "../../structures/ConstantIntersection";

export const test_assertPrune_ConstantIntersection = _test_assertPrune(
"ConstantIntersection",
ConstantIntersection.generate,
(input) => typia.assertPrune(input),
ConstantIntersection.SPOILERS,
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import typia from "../../../src";
import { _test_assertStringify } from "../../internal/_test_assertStringify";
import { AtomicIntersection } from "../../structures/AtomicIntersection";

export const test_assertStringify_AtomicIntersection = _test_assertStringify(
"AtomicIntersection",
AtomicIntersection.generate,
(input) => typia.assertStringify(input),
AtomicIntersection.SPOILERS,
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import typia from "../../../src";
import { _test_assertStringify } from "../../internal/_test_assertStringify";
import { ConstantIntersection } from "../../structures/ConstantIntersection";

export const test_assertStringify_ConstantIntersection = _test_assertStringify(
"ConstantIntersection",
ConstantIntersection.generate,
(input) => typia.assertStringify(input),
ConstantIntersection.SPOILERS,
);
9 changes: 9 additions & 0 deletions test/features/clone/test_clone_AtomicIntersection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import typia from "../../../src";
import { _test_clone } from "../../internal/_test_clone";
import { AtomicIntersection } from "../../structures/AtomicIntersection";

export const test_clone_AtomicIntersection = _test_clone(
"AtomicIntersection",
AtomicIntersection.generate,
(input) => typia.clone(input),
);
Loading

0 comments on commit dba6351

Please sign in to comment.