diff --git a/.changeset/khaki-apples-retire.md b/.changeset/khaki-apples-retire.md
new file mode 100644
index 00000000000..d116392de85
--- /dev/null
+++ b/.changeset/khaki-apples-retire.md
@@ -0,0 +1,6 @@
+---
+"effect": minor
+"@effect/vitest": minor
+---
+
+Improve custom matchers
diff --git a/packages/effect/src/Equal.ts b/packages/effect/src/Equal.ts
index 440e36c9fa7..f6d7ac66b1d 100644
--- a/packages/effect/src/Equal.ts
+++ b/packages/effect/src/Equal.ts
@@ -4,7 +4,6 @@
import type { Equivalence } from "./Equivalence.js"
import * as Hash from "./Hash.js"
import { hasProperty } from "./Predicate.js"
-import { structuralRegionState } from "./Utils.js"
/**
* @since 2.0.0
@@ -44,41 +43,14 @@ function compareBoth(self: unknown, that: unknown): boolean {
if (selfType === "object" || selfType === "function") {
if (self !== null && that !== null) {
if (isEqual(self) && isEqual(that)) {
- if (Hash.hash(self) === Hash.hash(that) && self[symbol](that)) {
- return true
- } else {
- return structuralRegionState.enabled && structuralRegionState.tester
- ? structuralRegionState.tester(self, that)
- : false
- }
+ return Hash.hash(self) === Hash.hash(that) && self[symbol](that)
} else if (self instanceof Date && that instanceof Date) {
return self.toISOString() === that.toISOString()
}
}
- if (structuralRegionState.enabled) {
- if (Array.isArray(self) && Array.isArray(that)) {
- return self.length === that.length && self.every((v, i) => compareBoth(v, that[i]))
- }
- if (Object.getPrototypeOf(self) === Object.prototype && Object.getPrototypeOf(self) === Object.prototype) {
- const keysSelf = Object.keys(self as any)
- const keysThat = Object.keys(that as any)
- if (keysSelf.length === keysThat.length) {
- for (const key of keysSelf) {
- // @ts-expect-error
- if (!(key in that && compareBoth(self[key], that[key]))) {
- return structuralRegionState.tester ? structuralRegionState.tester(self, that) : false
- }
- }
- return true
- }
- }
- return structuralRegionState.tester ? structuralRegionState.tester(self, that) : false
- }
}
- return structuralRegionState.enabled && structuralRegionState.tester
- ? structuralRegionState.tester(self, that)
- : false
+ return false
}
/**
diff --git a/packages/effect/src/Hash.ts b/packages/effect/src/Hash.ts
index ec81836bafa..531f2ac70ec 100644
--- a/packages/effect/src/Hash.ts
+++ b/packages/effect/src/Hash.ts
@@ -4,7 +4,6 @@
import { pipe } from "./Function.js"
import { globalValue } from "./GlobalValue.js"
import { hasProperty } from "./Predicate.js"
-import { structuralRegionState } from "./Utils.js"
/** @internal */
const randomHashCache = globalValue(
@@ -31,10 +30,6 @@ export interface Hash {
* @category hashing
*/
export const hash: (self: A) => number = (self: A) => {
- if (structuralRegionState.enabled === true) {
- return 0
- }
-
switch (typeof self) {
case "number":
return number(self)
diff --git a/packages/effect/src/Utils.ts b/packages/effect/src/Utils.ts
index 618d0a6d7d6..003970d42fd 100644
--- a/packages/effect/src/Utils.ts
+++ b/packages/effect/src/Utils.ts
@@ -2,7 +2,6 @@
* @since 2.0.0
*/
import { identity } from "./Function.js"
-import { globalValue } from "./GlobalValue.js"
import type { Kind, TypeLambda } from "./HKT.js"
import { getBugErrorMessage } from "./internal/errors.js"
import { isNullable, isObject } from "./Predicate.js"
@@ -751,43 +750,6 @@ export function yieldWrapGet(self: YieldWrap): T {
throw new Error(getBugErrorMessage("yieldWrapGet"))
}
-/**
- * Note: this is an experimental feature made available to allow custom matchers in tests, not to be directly used yet in user code
- *
- * @since 3.1.1
- * @status experimental
- * @category modifiers
- */
-export const structuralRegionState = globalValue(
- "effect/Utils/isStructuralRegion",
- (): { enabled: boolean; tester: ((a: unknown, b: unknown) => boolean) | undefined } => ({
- enabled: false,
- tester: undefined
- })
-)
-
-/**
- * Note: this is an experimental feature made available to allow custom matchers in tests, not to be directly used yet in user code
- *
- * @since 3.1.1
- * @status experimental
- * @category modifiers
- */
-export const structuralRegion = (body: () => A, tester?: (a: unknown, b: unknown) => boolean): A => {
- const current = structuralRegionState.enabled
- const currentTester = structuralRegionState.tester
- structuralRegionState.enabled = true
- if (tester) {
- structuralRegionState.tester = tester
- }
- try {
- return body()
- } finally {
- structuralRegionState.enabled = current
- structuralRegionState.tester = currentTester
- }
-}
-
const tracingFunction = (name: string) => {
const wrap = {
[name](body: () => A) {
diff --git a/packages/effect/test/Hash.test.ts b/packages/effect/test/Hash.test.ts
index 3da943e6215..1ab9a62799c 100644
--- a/packages/effect/test/Hash.test.ts
+++ b/packages/effect/test/Hash.test.ts
@@ -1,36 +1,9 @@
-import * as Equal from "effect/Equal"
import { absurd, identity } from "effect/Function"
import * as Hash from "effect/Hash"
import * as HashSet from "effect/HashSet"
-import * as Option from "effect/Option"
-import * as Utils from "effect/Utils"
import { describe, expect, it } from "vitest"
describe("Hash", () => {
- it("structural", () => {
- const a = { foo: { bar: "ok", baz: { arr: [0, 1, 2] } } }
- const b = { foo: { bar: "ok", baz: { arr: [0, 1, 2] } } }
- expect(Hash.hash(a)).not.toBe(Hash.hash(b))
- expect(Equal.equals(a, b)).toBe(false)
- Utils.structuralRegion(() => {
- expect(Hash.hash(a)).toBe(Hash.hash(b))
- expect(Equal.equals(a, b)).toBe(true)
- })
- expect(Hash.hash(a)).not.toBe(Hash.hash(b))
- expect(Equal.equals(a, b)).toBe(false)
- })
- it("structural cached", () => {
- const a = Option.some({ foo: { bar: "ok", baz: { arr: [0, 1, 2] } } })
- const b = Option.some({ foo: { bar: "ok", baz: { arr: [0, 1, 2] } } })
- expect(Hash.hash(a)).not.toBe(Hash.hash(b))
- expect(Equal.equals(a, b)).toBe(false)
- Utils.structuralRegion(() => {
- expect(Hash.hash(a)).toBe(Hash.hash(b))
- expect(Equal.equals(a, b)).toBe(true)
- })
- expect(Hash.hash(a)).not.toBe(Hash.hash(b))
- expect(Equal.equals(a, b)).toBe(false)
- })
it("exports", () => {
expect(Hash.string).exist
expect(Hash.structureKeys).exist
diff --git a/packages/vitest/src/internal.ts b/packages/vitest/src/internal.ts
index 1b8cb9658a8..d4e138e0b00 100644
--- a/packages/vitest/src/internal.ts
+++ b/packages/vitest/src/internal.ts
@@ -3,18 +3,20 @@
*/
import type { Tester, TesterContext } from "@vitest/expect"
import * as Cause from "effect/Cause"
+import * as Chunk from "effect/Chunk"
import * as Duration from "effect/Duration"
import * as Effect from "effect/Effect"
-import * as Equal from "effect/Equal"
+import * as Either from "effect/Either"
import * as Exit from "effect/Exit"
import { flow, identity, pipe } from "effect/Function"
+import * as HashSet from "effect/HashSet"
import * as Layer from "effect/Layer"
import * as Logger from "effect/Logger"
+import * as Option from "effect/Option"
import * as Schedule from "effect/Schedule"
import type * as Scope from "effect/Scope"
import * as TestEnvironment from "effect/TestContext"
import type * as TestServices from "effect/TestServices"
-import * as Utils from "effect/Utils"
import * as V from "vitest"
import type * as Vitest from "./index.js"
@@ -41,14 +43,26 @@ const TestEnv = TestEnvironment.TestContext.pipe(
)
/** @internal */
-function customTester(this: TesterContext, a: unknown, b: unknown, customTesters: Array) {
- if (!Equal.isEqual(a) || !Equal.isEqual(b)) {
- return undefined
+function customTester(this: TesterContext, _a: unknown, _b: unknown, _customTesters: Array) {
+ if (Chunk.isChunk(_a) && Chunk.isChunk(_b)) {
+ return this.equals(Array.from(_a), Array.from(_b), _customTesters)
}
- return Utils.structuralRegion(
- () => Equal.equals(a, b),
- (x, y) => this.equals(x, y, customTesters.filter((t) => t !== customTester))
- )
+ if (Option.isOption(_a) && Option.isOption(_b)) {
+ return _a._tag === _b._tag && (_a._tag === "None" || this.equals(_a.value, (_b as any).value, _customTesters))
+ }
+ if (Either.isEither(_a) && Either.isEither(_b)) {
+ return (Either.isLeft(_a) && Either.isLeft(_b) && this.equals(_a.left, _b.left, _customTesters)) ||
+ (Either.isRight(_a) && Either.isRight(_b) && this.equals(_a.right, _b.right, _customTesters))
+ }
+ if (Cause.isCause(_a) && Cause.isCause(_b)) {
+ return this.equals(Cause.failures(_a), Cause.failures(_b), _customTesters) &&
+ this.equals(Cause.defects(_a), Cause.defects(_b), _customTesters) &&
+ this.equals(Cause.interruptors(_a), Cause.interruptors(_b), _customTesters)
+ }
+ if (HashSet.isHashSet(_a) && HashSet.isHashSet(_b)) {
+ return this.equals(new Set(_a), new Set(_b), _customTesters)
+ }
+ return undefined
}
/** @internal */