Skip to content

Pipe in Intersection after Union drops previous narrowing conditions #1608

@ShuviSchwarze

Description

@ShuviSchwarze

Report a bug

Related: #1596, #1468
Arktype is dropping previous narrowing functions after pipe.
Specifically, having multiple pipe > narrow operations in an intersection schema drops narrow condition of the member schemas with pipe > narrow and only run the narrowing condition of the intersection schema

(A_pipe_narrow | B_narrow) & C_pipe_narrow
A narrow will not work
B narrow will still work
C narrow will still work

(A_narrow | B_narrow) & C_pipe_narrow
A narrow will still work
B narrow will still work
C narrow will still work

(A_pipe_narrow | B_narrow) & C_pipe
A narrow will still work
B narrow will still work

(A_pipe_narrow | B_narrow) & C_narrow
A narrow will still work
B narrow will still work
C narrow will still work

🔎 Search Terms

🧩 Context

  • ArkType version: 2.2.0
  • TypeScript version (5.1+): 5.9.3
  • Other context you think may be relevant (JS flavor, OS, etc.):

🧑‍💻 Repro

Playground Link: https://arktype.io/playground

import { type } from "arktype"

const PipedItem = type("string.numeric & string.numeric.parse").narrow(
	(_data, ctx) => {
		ctx.reject("always reject")
		return true
	}
)

const PipedItem2 = type("string").narrow((_data, ctx) => {
	ctx.reject("always reject")
	return true
})

const Variant = type({
	kind: '"a"',
	value: PipedItem
})
	//Dropping this variant also fixes the issue
	.or({
		kind: '"b"',
		value: PipedItem2
	})

const Meta = type({
	// If you have this the narrowing breaks
	code: type("string.numeric.parse")
})
	//Dropping this narrow also fixes the issue
	.narrow((data, ctx) => {
		return true
	})

const Thing = Variant.and(Meta)

// This behaves the same as Variant.and(Meta) by forcing data to be validated with Variant and triggers Variant narrow.
// We do trigger Variant validation twice
const ThingWorkaround = Variant.and(Meta).narrow((data, ctx) => {
	const baseResult = Variant(data)
	if (baseResult instanceof type.errors) ctx.reject(baseResult.summary)
	return true
})

// This must reject and does
const goodThing = {
	kind: "b",
	code: "1",
	value: ""
}

// This must reject but doesnt
const badThing = {
	kind: "a",
	code: "1",
	value: "1"
}

const out = Thing(badThing)

Workaround pnpm patch:
Im not aware of how this will affect performance, but it seems to fix this and #1596 due to rawIn stripping narrow functions of child branches.

diff --git a/out/roots/union.js b/out/roots/union.js
index fbe3ccb58ef55e6922bd1ed79e8ab8e485f9a0c2..cf6482d23195bdeee743cbfbe565d06fe70ad394 100644
--- a/out/roots/union.js
+++ b/out/roots/union.js
@@ -31,7 +31,7 @@ const implementation = implementNode({
                                 const matchingMorph = branches[matchingMorphIndex];
                                 branches[matchingMorphIndex] = ctx.$.node("morph", {
                                     ...matchingMorph.inner,
-                                    in: matchingMorph.rawIn.rawOr(node.rawIn)
+                                    in: matchingMorph.inner.in.rawOr(node.inner.in)
                                 });
                             }
                         }

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

Status

To do

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions