diff --git a/.changeset/cyan-sloths-lick.md b/.changeset/cyan-sloths-lick.md index 22789ec9f9..3a8407dea9 100644 --- a/.changeset/cyan-sloths-lick.md +++ b/.changeset/cyan-sloths-lick.md @@ -7,3 +7,5 @@ Re-export modules from `effect`. `ArrayFormatter` / `TreeFormatter` merged into `ParseResult` module. `Serializable` module merged into `Schema` module. + +`Equivalence` module merged into `Schema` module. diff --git a/.changeset/six-crabs-itch.md b/.changeset/six-crabs-itch.md index 5a49f35cd9..49eb2c240f 100644 --- a/.changeset/six-crabs-itch.md +++ b/.changeset/six-crabs-itch.md @@ -12,7 +12,6 @@ Before import { Arbitrary, AST, - Equivalence, FastCheck, JSONSchema, ParseResult, @@ -27,7 +26,6 @@ After import { Arbitrary, SchemaAST, // changed - SchemaEquivalence, // changed FastCheck, JSONSchema, ParseResult, @@ -55,3 +53,23 @@ import { ArrayFormatter, TreeFormatter } from "effect/ParseResult" ### Serializable Merged into `Schema` module. + +### Equivalence + +Merged into `Schema` module. + +Before + +```ts +import { Equivalence } from "@effect/schema" + +Equivalence.make(myschema) +``` + +After + +```ts +import { Schema } from "@effect/schema" + +Schema.equivalence(myschema) +``` diff --git a/packages/effect/src/Arbitrary.ts b/packages/effect/src/Arbitrary.ts index 6d0ccbc7d3..5f98b93f26 100644 --- a/packages/effect/src/Arbitrary.ts +++ b/packages/effect/src/Arbitrary.ts @@ -57,7 +57,7 @@ export const makeLazy = (schema: Schema.Schema): LazyArbitrary */ export const make = (schema: Schema.Schema): FastCheck.Arbitrary => makeLazy(schema)(FastCheck) -const getAnnotation = AST.getAnnotation>(AST.ArbitraryAnnotationId) +const getArbitraryAnnotation = AST.getAnnotation>(AST.ArbitraryAnnotationId) const getRefinementFromArbitrary = ( ast: AST.Refinement, @@ -113,7 +113,7 @@ const go = ( ctx: Context, path: ReadonlyArray ): LazyArbitrary => { - const hook = getAnnotation(ast) + const hook = getArbitraryAnnotation(ast) if (Option.isSome(hook)) { switch (ast._tag) { case "Declaration": diff --git a/packages/effect/src/Pretty.ts b/packages/effect/src/Pretty.ts index c769b8437e..c301c0c938 100644 --- a/packages/effect/src/Pretty.ts +++ b/packages/effect/src/Pretty.ts @@ -31,10 +31,10 @@ export type PrettyAnnotation = read */ export const make = (schema: Schema.Schema): (a: A) => string => compile(schema.ast, []) -const getAnnotation = AST.getAnnotation>(AST.PrettyAnnotationId) +const getPrettyAnnotation = AST.getAnnotation>(AST.PrettyAnnotationId) const getMatcher = (defaultPretty: Pretty) => (ast: AST.AST): Pretty => - Option.match(getAnnotation(ast), { + Option.match(getPrettyAnnotation(ast), { onNone: () => defaultPretty, onSome: (handler) => handler() }) @@ -50,7 +50,7 @@ const formatUnknown = getMatcher(util_.formatUnknown) */ export const match: AST.Match> = { "Declaration": (ast, go, path) => { - const annotation = getAnnotation(ast) + const annotation = getPrettyAnnotation(ast) if (Option.isSome(annotation)) { return annotation.value(...ast.typeParameters.map((tp) => go(tp, path))) } @@ -78,7 +78,7 @@ export const match: AST.Match> = { "BigIntKeyword": getMatcher((a) => `${String(a)}n`), "Enums": stringify, "TupleType": (ast, go, path) => { - const hook = getAnnotation(ast) + const hook = getPrettyAnnotation(ast) if (Option.isSome(hook)) { return hook.value() } @@ -120,7 +120,7 @@ export const match: AST.Match> = { } }, "TypeLiteral": (ast, go, path) => { - const hook = getAnnotation(ast) + const hook = getPrettyAnnotation(ast) if (Option.isSome(hook)) { return hook.value() } @@ -165,7 +165,7 @@ export const match: AST.Match> = { } }, "Union": (ast, go, path) => { - const hook = getAnnotation(ast) + const hook = getPrettyAnnotation(ast) if (Option.isSome(hook)) { return hook.value() } @@ -179,7 +179,7 @@ export const match: AST.Match> = { } }, "Suspend": (ast, go, path) => { - return Option.match(getAnnotation(ast), { + return Option.match(getPrettyAnnotation(ast), { onNone: () => { const get = util_.memoizeThunk(() => go(ast.f(), path)) return (a) => get()(a) @@ -188,13 +188,13 @@ export const match: AST.Match> = { }) }, "Refinement": (ast, go, path) => { - return Option.match(getAnnotation(ast), { + return Option.match(getPrettyAnnotation(ast), { onNone: () => go(ast.from, path), onSome: (handler) => handler() }) }, "Transformation": (ast, go, path) => { - return Option.match(getAnnotation(ast), { + return Option.match(getPrettyAnnotation(ast), { onNone: () => go(ast.to, path), onSome: (handler) => handler() }) diff --git a/packages/effect/src/Schema.ts b/packages/effect/src/Schema.ts index 1301778a74..512cd08b52 100644 --- a/packages/effect/src/Schema.ts +++ b/packages/effect/src/Schema.ts @@ -45,7 +45,6 @@ import * as redacted_ from "./Redacted.js" import * as Request from "./Request.js" import type { ParseOptions } from "./SchemaAST.js" import * as AST from "./SchemaAST.js" -import type * as equivalence_ from "./SchemaEquivalence.js" import * as sortedSet_ from "./SortedSet.js" import * as string_ from "./String.js" import * as struct_ from "./Struct.js" @@ -3869,7 +3868,7 @@ export declare namespace Annotations { readonly jsonSchema?: AST.JSONSchemaAnnotation readonly arbitrary?: ArbitraryAnnotation readonly pretty?: pretty_.PrettyAnnotation - readonly equivalence?: equivalence_.EquivalenceAnnotation + readonly equivalence?: AST.EquivalenceAnnotation readonly concurrency?: AST.ConcurrencyAnnotation readonly batching?: AST.BatchingAnnotation readonly parseIssueTitle?: AST.ParseIssueTitleAnnotation @@ -9655,3 +9654,191 @@ export const TaggedRequest = } } as any } + +// ------------------------------------------------------------------------------------------------- +// Equivalence compiler +// ------------------------------------------------------------------------------------------------- + +/** + * Given a schema `Schema`, returns an `Equivalence` instance for `A`. + * + * @category Equivalence + * @since 3.10.0 + */ +export const equivalence = (schema: Schema): Equivalence.Equivalence => go(schema.ast, []) + +const getEquivalenceAnnotation = AST.getAnnotation>(AST.EquivalenceAnnotationId) + +const go = (ast: AST.AST, path: ReadonlyArray): Equivalence.Equivalence => { + const hook = getEquivalenceAnnotation(ast) + if (option_.isSome(hook)) { + switch (ast._tag) { + case "Declaration": + return hook.value(...ast.typeParameters.map((tp) => go(tp, path))) + case "Refinement": + return hook.value(go(ast.from, path)) + default: + return hook.value() + } + } + switch (ast._tag) { + case "NeverKeyword": + throw new Error(errors_.getEquivalenceUnsupportedErrorMessage(ast, path)) + case "Transformation": + return go(ast.to, path) + case "Declaration": + case "Literal": + case "StringKeyword": + case "TemplateLiteral": + case "UniqueSymbol": + case "SymbolKeyword": + case "UnknownKeyword": + case "AnyKeyword": + case "NumberKeyword": + case "BooleanKeyword": + case "BigIntKeyword": + case "UndefinedKeyword": + case "VoidKeyword": + case "Enums": + case "ObjectKeyword": + return Equal.equals + case "Refinement": + return go(ast.from, path) + case "Suspend": { + const get = util_.memoizeThunk(() => go(ast.f(), path)) + return (a, b) => get()(a, b) + } + case "TupleType": { + const elements = ast.elements.map((element, i) => go(element.type, path.concat(i))) + const rest = ast.rest.map((annotatedAST) => go(annotatedAST.type, path)) + return Equivalence.make((a, b) => { + const len = a.length + if (len !== b.length) { + return false + } + // --------------------------------------------- + // handle elements + // --------------------------------------------- + let i = 0 + for (; i < Math.min(len, ast.elements.length); i++) { + if (!elements[i](a[i], b[i])) { + return false + } + } + // --------------------------------------------- + // handle rest element + // --------------------------------------------- + if (array_.isNonEmptyReadonlyArray(rest)) { + const [head, ...tail] = rest + for (; i < len - tail.length; i++) { + if (!head(a[i], b[i])) { + return false + } + } + // --------------------------------------------- + // handle post rest elements + // --------------------------------------------- + for (let j = 0; j < tail.length; j++) { + i += j + if (!tail[j](a[i], b[i])) { + return false + } + } + } + return true + }) + } + case "TypeLiteral": { + if (ast.propertySignatures.length === 0 && ast.indexSignatures.length === 0) { + return Equal.equals + } + const propertySignatures = ast.propertySignatures.map((ps) => go(ps.type, path.concat(ps.name))) + const indexSignatures = ast.indexSignatures.map((is) => go(is.type, path)) + return Equivalence.make((a, b) => { + const aStringKeys = Object.keys(a) + const aSymbolKeys = Object.getOwnPropertySymbols(a) + // --------------------------------------------- + // handle property signatures + // --------------------------------------------- + for (let i = 0; i < propertySignatures.length; i++) { + const ps = ast.propertySignatures[i] + const name = ps.name + const aHas = Object.prototype.hasOwnProperty.call(a, name) + const bHas = Object.prototype.hasOwnProperty.call(b, name) + if (ps.isOptional) { + if (aHas !== bHas) { + return false + } + } + if (aHas && bHas && !propertySignatures[i](a[name], b[name])) { + return false + } + } + // --------------------------------------------- + // handle index signatures + // --------------------------------------------- + let bSymbolKeys: Array | undefined + let bStringKeys: Array | undefined + for (let i = 0; i < indexSignatures.length; i++) { + const is = ast.indexSignatures[i] + const base = AST.getParameterBase(is.parameter) + const isSymbol = AST.isSymbolKeyword(base) + if (isSymbol) { + bSymbolKeys = bSymbolKeys || Object.getOwnPropertySymbols(b) + if (aSymbolKeys.length !== bSymbolKeys.length) { + return false + } + } else { + bStringKeys = bStringKeys || Object.keys(b) + if (aStringKeys.length !== bStringKeys.length) { + return false + } + } + const aKeys = isSymbol ? aSymbolKeys : aStringKeys + for (let j = 0; j < aKeys.length; j++) { + const key = aKeys[j] + if ( + !Object.prototype.hasOwnProperty.call(b, key) || !indexSignatures[i](a[key], b[key]) + ) { + return false + } + } + } + return true + }) + } + case "Union": { + const searchTree = ParseResult.getSearchTree(ast.types, true) + const ownKeys = util_.ownKeys(searchTree.keys) + const len = ownKeys.length + return Equivalence.make((a, b) => { + let candidates: Array = [] + if (len > 0 && Predicate.isRecord(a)) { + for (let i = 0; i < len; i++) { + const name = ownKeys[i] + const buckets = searchTree.keys[name].buckets + if (Object.prototype.hasOwnProperty.call(a, name)) { + const literal = String(a[name]) + if (Object.prototype.hasOwnProperty.call(buckets, literal)) { + candidates = candidates.concat(buckets[literal]) + } + } + } + } + if (searchTree.otherwise.length > 0) { + candidates = candidates.concat(searchTree.otherwise) + } + const tuples = candidates.map((ast) => [go(ast, path), ParseResult.is({ ast } as any)] as const) + for (let i = 0; i < tuples.length; i++) { + const [equivalence, is] = tuples[i] + if (is(a) && is(b)) { + if (equivalence(a, b)) { + return true + } + } + } + return false + }) + } + } +} diff --git a/packages/effect/src/SchemaAST.ts b/packages/effect/src/SchemaAST.ts index d4d0b041e4..ddd8679a06 100644 --- a/packages/effect/src/SchemaAST.ts +++ b/packages/effect/src/SchemaAST.ts @@ -4,6 +4,7 @@ import * as Arr from "./Array.js" import type { Effect } from "./Effect.js" +import type { Equivalence } from "./Equivalence.js" import { dual, identity } from "./Function.js" import { globalValue } from "./GlobalValue.js" import * as errors_ from "./internal/schema/errors.js" @@ -185,6 +186,14 @@ export const ArbitraryAnnotationId: unique symbol = Symbol.for("effect/annotatio */ export const PrettyAnnotationId: unique symbol = Symbol.for("effect/annotation/Pretty") +/** + * @category annotations + * @since 3.10.0 + */ +export type EquivalenceAnnotation = readonly []> = ( + ...equivalences: { readonly [K in keyof TypeParameters]: Equivalence } +) => Equivalence + /** * @category annotations * @since 3.10.0 diff --git a/packages/effect/src/SchemaEquivalence.ts b/packages/effect/src/SchemaEquivalence.ts deleted file mode 100644 index 2dbbcdee89..0000000000 --- a/packages/effect/src/SchemaEquivalence.ts +++ /dev/null @@ -1,204 +0,0 @@ -/** - * @since 3.10.0 - */ - -import * as Arr from "./Array.js" -import * as Equal from "./Equal.js" -import * as Equivalence from "./Equivalence.js" -import * as errors_ from "./internal/schema/errors.js" -import * as util_ from "./internal/schema/util.js" -import * as Option from "./Option.js" -import * as ParseResult from "./ParseResult.js" -import * as Predicate from "./Predicate.js" -import type * as Schema from "./Schema.js" -import * as AST from "./SchemaAST.js" - -/** - * @category annotations - * @since 3.10.0 - */ -export type EquivalenceAnnotation = readonly []> = ( - ...equivalences: { readonly [K in keyof TypeParameters]: Equivalence.Equivalence } -) => Equivalence.Equivalence - -/** - * @category Equivalence - * @since 3.10.0 - */ -export const make = (schema: Schema.Schema): Equivalence.Equivalence => go(schema.ast, []) - -const getAnnotation = AST.getAnnotation>(AST.EquivalenceAnnotationId) - -const go = (ast: AST.AST, path: ReadonlyArray): Equivalence.Equivalence => { - const hook = getAnnotation(ast) - if (Option.isSome(hook)) { - switch (ast._tag) { - case "Declaration": - return hook.value(...ast.typeParameters.map((tp) => go(tp, path))) - case "Refinement": - return hook.value(go(ast.from, path)) - default: - return hook.value() - } - } - switch (ast._tag) { - case "NeverKeyword": - throw new Error(errors_.getEquivalenceUnsupportedErrorMessage(ast, path)) - case "Transformation": - return go(ast.to, path) - case "Declaration": - case "Literal": - case "StringKeyword": - case "TemplateLiteral": - case "UniqueSymbol": - case "SymbolKeyword": - case "UnknownKeyword": - case "AnyKeyword": - case "NumberKeyword": - case "BooleanKeyword": - case "BigIntKeyword": - case "UndefinedKeyword": - case "VoidKeyword": - case "Enums": - case "ObjectKeyword": - return Equal.equals - case "Refinement": - return go(ast.from, path) - case "Suspend": { - const get = util_.memoizeThunk(() => go(ast.f(), path)) - return (a, b) => get()(a, b) - } - case "TupleType": { - const elements = ast.elements.map((element, i) => go(element.type, path.concat(i))) - const rest = ast.rest.map((annotatedAST) => go(annotatedAST.type, path)) - return Equivalence.make((a, b) => { - const len = a.length - if (len !== b.length) { - return false - } - // --------------------------------------------- - // handle elements - // --------------------------------------------- - let i = 0 - for (; i < Math.min(len, ast.elements.length); i++) { - if (!elements[i](a[i], b[i])) { - return false - } - } - // --------------------------------------------- - // handle rest element - // --------------------------------------------- - if (Arr.isNonEmptyReadonlyArray(rest)) { - const [head, ...tail] = rest - for (; i < len - tail.length; i++) { - if (!head(a[i], b[i])) { - return false - } - } - // --------------------------------------------- - // handle post rest elements - // --------------------------------------------- - for (let j = 0; j < tail.length; j++) { - i += j - if (!tail[j](a[i], b[i])) { - return false - } - } - } - return true - }) - } - case "TypeLiteral": { - if (ast.propertySignatures.length === 0 && ast.indexSignatures.length === 0) { - return Equal.equals - } - const propertySignatures = ast.propertySignatures.map((ps) => go(ps.type, path.concat(ps.name))) - const indexSignatures = ast.indexSignatures.map((is) => go(is.type, path)) - return Equivalence.make((a, b) => { - const aStringKeys = Object.keys(a) - const aSymbolKeys = Object.getOwnPropertySymbols(a) - // --------------------------------------------- - // handle property signatures - // --------------------------------------------- - for (let i = 0; i < propertySignatures.length; i++) { - const ps = ast.propertySignatures[i] - const name = ps.name - const aHas = Object.prototype.hasOwnProperty.call(a, name) - const bHas = Object.prototype.hasOwnProperty.call(b, name) - if (ps.isOptional) { - if (aHas !== bHas) { - return false - } - } - if (aHas && bHas && !propertySignatures[i](a[name], b[name])) { - return false - } - } - // --------------------------------------------- - // handle index signatures - // --------------------------------------------- - let bSymbolKeys: Array | undefined - let bStringKeys: Array | undefined - for (let i = 0; i < indexSignatures.length; i++) { - const is = ast.indexSignatures[i] - const base = AST.getParameterBase(is.parameter) - const isSymbol = AST.isSymbolKeyword(base) - if (isSymbol) { - bSymbolKeys = bSymbolKeys || Object.getOwnPropertySymbols(b) - if (aSymbolKeys.length !== bSymbolKeys.length) { - return false - } - } else { - bStringKeys = bStringKeys || Object.keys(b) - if (aStringKeys.length !== bStringKeys.length) { - return false - } - } - const aKeys = isSymbol ? aSymbolKeys : aStringKeys - for (let j = 0; j < aKeys.length; j++) { - const key = aKeys[j] - if ( - !Object.prototype.hasOwnProperty.call(b, key) || !indexSignatures[i](a[key], b[key]) - ) { - return false - } - } - } - return true - }) - } - case "Union": { - const searchTree = ParseResult.getSearchTree(ast.types, true) - const ownKeys = util_.ownKeys(searchTree.keys) - const len = ownKeys.length - return Equivalence.make((a, b) => { - let candidates: Array = [] - if (len > 0 && Predicate.isRecord(a)) { - for (let i = 0; i < len; i++) { - const name = ownKeys[i] - const buckets = searchTree.keys[name].buckets - if (Object.prototype.hasOwnProperty.call(a, name)) { - const literal = String(a[name]) - if (Object.prototype.hasOwnProperty.call(buckets, literal)) { - candidates = candidates.concat(buckets[literal]) - } - } - } - } - if (searchTree.otherwise.length > 0) { - candidates = candidates.concat(searchTree.otherwise) - } - const tuples = candidates.map((ast) => [go(ast, path), ParseResult.is({ ast } as any)] as const) - for (let i = 0; i < tuples.length; i++) { - const [equivalence, is] = tuples[i] - if (is(a) && is(b)) { - if (equivalence(a, b)) { - return true - } - } - } - return false - }) - } - } -} diff --git a/packages/effect/src/index.ts b/packages/effect/src/index.ts index e38dc4864f..52f1a01328 100644 --- a/packages/effect/src/index.ts +++ b/packages/effect/src/index.ts @@ -770,11 +770,6 @@ export * as Schema from "./Schema.js" */ export * as SchemaAST from "./SchemaAST.js" -/** - * @since 3.10.0 - */ -export * as SchemaEquivalence from "./SchemaEquivalence.js" - /** * @since 2.0.0 */ diff --git a/packages/effect/test/Schema/Schema/BigDecimal/BigDecimalFromSelf.test.ts b/packages/effect/test/Schema/Schema/BigDecimal/BigDecimalFromSelf.test.ts index 9092297cbd..81967aac85 100644 --- a/packages/effect/test/Schema/Schema/BigDecimal/BigDecimalFromSelf.test.ts +++ b/packages/effect/test/Schema/Schema/BigDecimal/BigDecimalFromSelf.test.ts @@ -1,7 +1,6 @@ import { BigDecimal } from "effect" import * as Pretty from "effect/Pretty" import * as S from "effect/Schema" -import * as Equivalence from "effect/SchemaEquivalence" import * as Util from "effect/test/Schema/TestUtils" import { describe, expect, it } from "vitest" @@ -43,7 +42,7 @@ describe("BigDecimalFromSelf", () => { it("equivalence", () => { const schema = S.BigDecimalFromSelf - const equivalence = Equivalence.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence(BigDecimal.fromNumber(1), BigDecimal.unsafeFromString("1"))).toBe(true) expect(equivalence(BigDecimal.fromNumber(2), BigDecimal.unsafeFromString("1"))).toBe(false) diff --git a/packages/effect/test/Schema/Schema/Chunk/NonEmptyChunkFromSelf.test.ts b/packages/effect/test/Schema/Schema/Chunk/NonEmptyChunkFromSelf.test.ts index 8b3d7b1018..620dff6f5e 100644 --- a/packages/effect/test/Schema/Schema/Chunk/NonEmptyChunkFromSelf.test.ts +++ b/packages/effect/test/Schema/Schema/Chunk/NonEmptyChunkFromSelf.test.ts @@ -3,7 +3,6 @@ import * as C from "effect/Chunk" import * as FastCheck from "effect/FastCheck" import * as Pretty from "effect/Pretty" import * as S from "effect/Schema" -import * as Equivalence from "effect/SchemaEquivalence" import * as Util from "effect/test/Schema/TestUtils" import { describe, expect, it } from "vitest" @@ -64,7 +63,7 @@ describe("NonEmptyChunkFromSelf", () => { it("equivalence", () => { const schema = S.NonEmptyChunkFromSelf(S.String) - const equivalence = Equivalence.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence(C.make("a", "b"), C.make("a", "b"))).toEqual(true) expect(equivalence(C.make("a", "b"), C.make("a", "c"))).toEqual(false) expect(equivalence(C.make("a", "b"), C.make("a"))).toEqual(false) diff --git a/packages/effect/test/Schema/Schema/Class/TaggedClass.test.ts b/packages/effect/test/Schema/Schema/Class/TaggedClass.test.ts index f561b3cba3..31f501f903 100644 --- a/packages/effect/test/Schema/Schema/Class/TaggedClass.test.ts +++ b/packages/effect/test/Schema/Schema/Class/TaggedClass.test.ts @@ -1,6 +1,5 @@ import { pipe, Struct } from "effect" import * as S from "effect/Schema" -import * as Equivalence from "effect/SchemaEquivalence" import * as Util from "effect/test/Schema/TestUtils" import { describe, expect, it } from "vitest" @@ -153,7 +152,7 @@ details: Duplicate key "_tag"`) class A extends S.TaggedClass()("A", { a: S.String }) {} - const eqA = Equivalence.make(A) + const eqA = S.equivalence(A) expect(eqA(new A({ a: "a" }), new A({ a: "a" }))).toBe(true) expect(eqA(new A({ a: "a" }), new A({ a: "b" }))).toBe(false) @@ -161,7 +160,7 @@ details: Duplicate key "_tag"`) b: S.Number, as: S.Array(A) }) {} - const eqB = Equivalence.make(B) + const eqB = S.equivalence(B) expect(eqB(new B({ b: 1, as: [] }), new B({ b: 1, as: [] }))).toBe(true) expect(eqB(new B({ b: 1, as: [] }), new B({ b: 2, as: [] }))).toBe(false) expect(eqB(new B({ b: 1, as: [new A({ a: "a" })] }), new B({ b: 1, as: [new A({ a: "a" })] }))).toBe(true) diff --git a/packages/effect/test/Schema/Schema/SortedSet/SortedSetFromSelf.test.ts b/packages/effect/test/Schema/Schema/SortedSet/SortedSetFromSelf.test.ts index 61ec5447e6..bdb84b5ac1 100644 --- a/packages/effect/test/Schema/Schema/SortedSet/SortedSetFromSelf.test.ts +++ b/packages/effect/test/Schema/Schema/SortedSet/SortedSetFromSelf.test.ts @@ -2,7 +2,6 @@ import * as N from "effect/Number" import * as P from "effect/ParseResult" import * as Pretty from "effect/Pretty" import * as Schema from "effect/Schema" -import * as Equivalence from "effect/SchemaEquivalence" import * as SortedSet from "effect/SortedSet" import * as S from "effect/String" import * as Util from "effect/test/Schema/TestUtils" @@ -79,7 +78,7 @@ describe("SortedSetFromSelf", () => { it("equivalence", () => { const schema = Schema.SortedSetFromSelf(Schema.String, S.Order, S.Order) - const eq = Equivalence.make(schema) + const eq = Schema.equivalence(schema) const a = SortedSet.fromIterable([] as Array, S.Order) const b = SortedSet.fromIterable(["a"] as Array, S.Order) diff --git a/packages/effect/test/Schema/SchemaEquivalence.test.ts b/packages/effect/test/Schema/Schema/equivalence.test.ts similarity index 91% rename from packages/effect/test/Schema/SchemaEquivalence.test.ts rename to packages/effect/test/Schema/Schema/equivalence.test.ts index c372e21e76..9da821514a 100644 --- a/packages/effect/test/Schema/SchemaEquivalence.test.ts +++ b/packages/effect/test/Schema/Schema/equivalence.test.ts @@ -8,7 +8,6 @@ import * as Hash from "effect/Hash" import * as Option from "effect/Option" import { isUnknown } from "effect/Predicate" import * as S from "effect/Schema" -import * as E from "effect/SchemaEquivalence" import * as fc from "fast-check" import { describe, expect, it } from "vitest" @@ -21,7 +20,7 @@ export const propertyType = ( ) => { const arb = A.makeLazy(schema)(fc) // console.log(fc.sample(arb, 10)) - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) const reflexivity = fc.property(arb, (a) => equivalence(a, a)) const symmetry = fc.property(arb, arb, (a, b) => equivalence(a, b) === equivalence(b, a)) @@ -73,13 +72,13 @@ const MySymbol = S.SymbolFromSelf.annotations({ describe("SchemaEquivalence", () => { it("the errors should disply a path", () => { - expect(() => E.make(S.Tuple(S.Never as any))).toThrow( + expect(() => S.equivalence(S.Tuple(S.Never as any))).toThrow( new Error(`Unsupported schema at path: [0] details: Cannot build an Equivalence schema (NeverKeyword): never`) ) - expect(() => E.make(S.Struct({ a: S.Never as any }))).toThrow( + expect(() => S.equivalence(S.Struct({ a: S.Never as any }))).toThrow( new Error(`Unsupported schema at path: ["a"] details: Cannot build an Equivalence @@ -89,16 +88,16 @@ schema (NeverKeyword): never`) it("transformation", () => { const schema = S.NumberFromString - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence(1, 1)).toBe(true) expect(equivalence(1, 2)).toBe(false) }) - it("E.make(S.encodedSchema(schema))", () => { + it("S.equivalence(S.encodedSchema(schema))", () => { const schema = S.NumberFromString - const equivalence = E.make(S.encodedSchema(schema)) + const equivalence = S.equivalence(S.encodedSchema(schema)) expect(equivalence("a", "a")).toBe(true) @@ -106,7 +105,7 @@ schema (NeverKeyword): never`) }) it("never", () => { - expect(() => E.make(S.Never)).toThrow( + expect(() => S.equivalence(S.Never)).toThrow( new Error(`Unsupported schema details: Cannot build an Equivalence schema (NeverKeyword): never`) @@ -115,7 +114,7 @@ schema (NeverKeyword): never`) it("string", () => { const schema = MyString - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence("a", "a")).toBe(true) @@ -126,7 +125,7 @@ schema (NeverKeyword): never`) it("Refinement", () => { const schema = S.NonEmptyString - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence("a", "a")).toBe(true) @@ -138,7 +137,7 @@ schema (NeverKeyword): never`) describe("declaration", () => { it("should return Equal.equals when an annotation doesn't exist", () => { const schema = S.declare(isUnknown) - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence).toStrictEqual(Equal.equals) const make = (id: number, s: string) => { @@ -161,7 +160,7 @@ schema (NeverKeyword): never`) it("Chunk", () => { const schema = S.ChunkFromSelf(MyNumber) - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence(Chunk.empty(), Chunk.empty())).toBe(true) expect(equivalence(Chunk.make(1, 2, 3), Chunk.make(1, 2, 3))).toBe(true) @@ -174,7 +173,7 @@ schema (NeverKeyword): never`) it("Date", () => { const schema = S.DateFromSelf - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) const now = new Date() expect(equivalence(now, now)).toBe(true) @@ -187,7 +186,7 @@ schema (NeverKeyword): never`) it("Data", () => { const schema = S.DataFromSelf(S.Struct({ a: MyString, b: MyNumber })) - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence(Data.struct({ a: "ok", b: 0 }), Data.struct({ a: "ok", b: 0 }))).toBe(true) @@ -196,7 +195,7 @@ schema (NeverKeyword): never`) it("Either", () => { const schema = S.EitherFromSelf({ left: MyString, right: MyNumber }) - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence(Either.right(1), Either.right(1))).toBe(true) expect(equivalence(Either.left("a"), Either.left("a"))).toBe(true) @@ -209,7 +208,7 @@ schema (NeverKeyword): never`) it("Option", () => { const schema = S.OptionFromSelf(MyNumber) - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence(Option.none(), Option.none())).toBe(true) expect(equivalence(Option.some(1), Option.some(1))).toBe(true) @@ -221,7 +220,7 @@ schema (NeverKeyword): never`) it("ReadonlySet", () => { const schema = S.ReadonlySetFromSelf(MyNumber) - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence(new Set(), new Set())).toBe(true) expect(equivalence(new Set([1, 2, 3]), new Set([1, 2, 3]))).toBe(true) @@ -233,7 +232,7 @@ schema (NeverKeyword): never`) it("ReadonlyMap", () => { const schema = S.ReadonlyMapFromSelf({ key: MyString, value: MyNumber }) - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence(new Map(), new Map())).toBe(true) expect(equivalence(new Map([["a", 1], ["b", 2]]), new Map([["a", 1], ["b", 2]]))).toBe(true) @@ -246,7 +245,7 @@ schema (NeverKeyword): never`) it("Uint8Array", () => { const schema = S.Uint8ArrayFromSelf - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence(new Uint8Array(), new Uint8Array())).toBe(true) expect( @@ -264,7 +263,7 @@ schema (NeverKeyword): never`) const schema = S.instanceOf(URL, { equivalence: () => Equivalence.make((a, b) => a.href === b.href) }) - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence(new URL("https://example.com/page"), new URL("https://example.com/page"))) .toBe(true) @@ -277,7 +276,7 @@ schema (NeverKeyword): never`) describe("union", () => { it("primitives", () => { const schema = S.Union(MyString, MyNumber) - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence("a", "a")).toBe(true) expect(equivalence(1, 1)).toBe(true) @@ -292,7 +291,7 @@ schema (NeverKeyword): never`) const a = S.Struct({ a: MyString }) const ab = S.Struct({ a: MyString, b: S.Number }) const schema = S.Union(a, ab) - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence({ a: "a", b: 1 }, { a: "a", b: 1 })).toBe(true) expect(equivalence({ a: "a", b: 1 }, { a: "a", b: 2 })).toBe(true) @@ -307,7 +306,7 @@ schema (NeverKeyword): never`) S.Struct({ tag: S.Literal("a"), a: MyString }), S.Struct({ tag: S.Literal("b"), b: S.Number }) ) - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence({ tag: "a", a: "a" }, { tag: "a", a: "a" })).toBe(true) expect(equivalence({ tag: "b", b: 1 }, { tag: "b", b: 1 })).toBe(true) @@ -321,14 +320,14 @@ schema (NeverKeyword): never`) describe("tuple", () => { it("empty", () => { const schema = S.Tuple() - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence([], [])).toBe(true) }) it("e", () => { const schema = S.Tuple(MyString, MyNumber) - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence(["a", 1], ["a", 1])).toBe(true) @@ -340,7 +339,7 @@ schema (NeverKeyword): never`) it("e r", () => { const schema = S.Tuple([S.String], S.Number) - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence(["a"], ["a"])).toBe(true) expect(equivalence(["a", 1], ["a", 1])).toBe(true) @@ -354,7 +353,7 @@ schema (NeverKeyword): never`) it("r", () => { const schema = S.Array(MyNumber) - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence([], [])).toBe(true) expect(equivalence([1], [1])).toBe(true) @@ -368,7 +367,7 @@ schema (NeverKeyword): never`) it("r e", () => { const schema = S.Tuple([], MyString, MyNumber) - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence([1], [1])).toBe(true) expect(equivalence(["a", 1], ["a", 1])).toBe(true) @@ -384,7 +383,7 @@ schema (NeverKeyword): never`) describe("optional element support", () => { it("e?", () => { const schema = S.Tuple(S.optionalElement(MyString)) - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence([], [])).toBe(true) expect(equivalence(["a"], ["a"])).toBe(true) @@ -398,7 +397,7 @@ schema (NeverKeyword): never`) it("e? e?", () => { const schema = S.Tuple(S.optionalElement(MyString), S.optionalElement(MyNumber)) - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence([], [])).toBe(true) expect(equivalence(["a"], ["a"])).toBe(true) @@ -416,7 +415,7 @@ schema (NeverKeyword): never`) it("e e?", () => { const schema = S.Tuple(MyString, S.optionalElement(MyNumber)) - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence(["a"], ["a"])).toBe(true) expect(equivalence(["a", 1], ["a", 1])).toBe(true) @@ -430,7 +429,7 @@ schema (NeverKeyword): never`) it("e? r", () => { const schema = S.Tuple([S.optionalElement(S.String)], S.Number) - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence([], [])).toBe(true) expect(equivalence(["a"], ["a"])).toBe(true) @@ -449,14 +448,14 @@ schema (NeverKeyword): never`) describe("struct", () => { it("empty", () => { const schema = S.Struct({}) - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence({}, {})).toBe(false) }) it("string keys", () => { const schema = S.Struct({ a: MyString, b: MyNumber }) - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence({ a: "a", b: 1 }, { a: "a", b: 1 })).toBe(true) // should ignore excess properties @@ -479,7 +478,7 @@ schema (NeverKeyword): never`) const a = Symbol.for("effect/Schema/test/a") const b = Symbol.for("effect/Schema/test/b") const schema = S.Struct({ [a]: MyString, [b]: MyNumber }) - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence({ [a]: "a", [b]: 1 }, { [a]: "a", [b]: 1 })).toBe(true) // should ignore excess properties @@ -503,7 +502,7 @@ schema (NeverKeyword): never`) a: S.optionalWith(MyString, { exact: true }), b: S.optionalWith(S.Union(MyNumber, S.Undefined), { exact: true }) }) - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence({ a: "a", b: 1 }, { a: "a", b: 1 })).toBe(true) expect(equivalence({ b: 1 }, { b: 1 })).toBe(true) @@ -525,7 +524,7 @@ schema (NeverKeyword): never`) describe("record", () => { it("record(never, number)", () => { const schema = S.Record({ key: S.Never, value: MyNumber }) - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) const input = {} expect(equivalence(input, input)).toBe(true) @@ -534,7 +533,7 @@ schema (NeverKeyword): never`) it("record(string, number)", () => { const schema = S.Record({ key: MyString, value: MyNumber }) - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence({}, {})).toBe(true) expect(equivalence({ a: 1 }, { a: 1 })).toBe(true) @@ -553,7 +552,7 @@ schema (NeverKeyword): never`) it("record(symbol, number)", () => { const schema = S.Record({ key: MySymbol, value: MyNumber }) - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) const a = Symbol.for("effect/Schema/test/a") const b = Symbol.for("effect/Schema/test/b") @@ -574,7 +573,7 @@ schema (NeverKeyword): never`) it("struct record", () => { const schema = S.Struct({ a: MyString, b: MyString }, S.Record({ key: MyString, value: MyString })) - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence({ a: "a", b: "b" }, { a: "a", b: "b" })).toBe(true) expect(equivalence({ a: "a", b: "b", c: "c" }, { a: "a", b: "b", c: "c" })).toBe(true) @@ -590,7 +589,7 @@ schema (NeverKeyword): never`) const schema = S.Struct({ a: MyString, b: MyString }).annotations({ equivalence: () => Equivalence.make((x, y) => x.a === y.a) }) - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) expect(equivalence({ a: "a", b: "b" }, { a: "a", b: "b" })).toBe(true) expect(equivalence({ a: "a", b: "b" }, { a: "a", b: "c" })).toBe(true) @@ -612,7 +611,7 @@ schema (NeverKeyword): never`) as: S.Array(S.suspend((): S.Schema => schema)) }) - const equivalence = E.make(schema) + const equivalence = S.equivalence(schema) const a1: A = { a: "a1", as: [] } expect(equivalence(a1, a1)).toBe(true) @@ -653,7 +652,7 @@ schema (NeverKeyword): never`) right: Expression }) - const equivalence = E.make(Operation) + const equivalence = S.equivalence(Operation) const a1: Operation = { type: "operation", @@ -712,7 +711,7 @@ schema (NeverKeyword): never`) describe("should handle annotations", () => { const expectHook = (source: S.Schema) => { const schema = source.annotations({ equivalence: () => () => true }) - const eq = E.make(schema) + const eq = S.equivalence(schema) expect(eq("a" as any, "b" as any)).toEqual(true) } diff --git a/packages/schema/src/Equivalence.ts b/packages/schema/src/Equivalence.ts deleted file mode 100644 index edffa47537..0000000000 --- a/packages/schema/src/Equivalence.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * @since 0.67.0 - */ - -/** - * @category re-exports - * @since 0.76.0 - */ -export * from "effect/SchemaEquivalence" diff --git a/packages/schema/src/index.ts b/packages/schema/src/index.ts index 1c2004c88d..53259a4d3c 100644 --- a/packages/schema/src/index.ts +++ b/packages/schema/src/index.ts @@ -8,11 +8,6 @@ export * as AST from "./AST.js" */ export * as Arbitrary from "./Arbitrary.js" -/** - * @since 0.67.0 - */ -export * as Equivalence from "./Equivalence.js" - /** * @since 0.67.0 */