type inference resolves types from scopes to never in union

When using types from scopes in a union, arktype will resolve some of them to be "never", however if you insert their definition directly it resolves the type correctly Here is an example. If you paste this in the playground and hover of the out, the type will resolve to "never" in some places. Interestingly the .expression tab in the playground resolves the native Typescript expression correctly Is this a bug or am I misusing scopes in some way?
import { type } from "arktype";

const formScope = type.scope({
"form.number": "string.numeric.parse",
"form.integer": "string.integer.parse",
"form.string": "string > 0",
"form.checkbox": type("true|false|undefined", "=>", (c) => (c === undefined ? false : c)),
"form.date": /^\d{2}\.\d{2}\.\d{4}$/,
"form.year": type(/^\d{4}$/, "=>", (c) => Number.parseInt(c, 10)),
"form.yearmonth": /^\d{2}\/\d{4}$/ ,
"form.yesno.yes": "'yes'",
"form.yesno.no": "'no'",
"form.yesno": ["form.yesno.yes | form.yesno.no", "=>", (c) => c === "yes"],
});

const arkScope = type.scope({
...formScope.export(),
});

const ark = arkScope;

const Thing = ark.type
.or(
ark.type
.or(
{
hasWorkPermit: "form.yesno.yes",
workPermit: {
validUntil: "form.yearmonth",
},
},
{
hasWorkPermit: "form.yesno.no",
},
)
.and({
hasTemporaryResidence: "form.yesno.yes",
temporaryResidence: {
validUntil: "form.yearmonth",
},
}),
{
hasTemporaryResidence: "form.yesno.no",
},
)
.and({
inGermanySince: "form.yearmonth",
});

const out = Thing({})
import { type } from "arktype";

const formScope = type.scope({
"form.number": "string.numeric.parse",
"form.integer": "string.integer.parse",
"form.string": "string > 0",
"form.checkbox": type("true|false|undefined", "=>", (c) => (c === undefined ? false : c)),
"form.date": /^\d{2}\.\d{2}\.\d{4}$/,
"form.year": type(/^\d{4}$/, "=>", (c) => Number.parseInt(c, 10)),
"form.yearmonth": /^\d{2}\/\d{4}$/ ,
"form.yesno.yes": "'yes'",
"form.yesno.no": "'no'",
"form.yesno": ["form.yesno.yes | form.yesno.no", "=>", (c) => c === "yes"],
});

const arkScope = type.scope({
...formScope.export(),
});

const ark = arkScope;

const Thing = ark.type
.or(
ark.type
.or(
{
hasWorkPermit: "form.yesno.yes",
workPermit: {
validUntil: "form.yearmonth",
},
},
{
hasWorkPermit: "form.yesno.no",
},
)
.and({
hasTemporaryResidence: "form.yesno.yes",
temporaryResidence: {
validUntil: "form.yearmonth",
},
}),
{
hasTemporaryResidence: "form.yesno.no",
},
)
.and({
inGermanySince: "form.yearmonth",
});

const out = Thing({})
14 Replies
Flosi21
Flosi21OP7d ago
I did some digging and the problem seems to be that in the NaryUnionParser the scope is not passed to type.infer When i change (nary.ts: line 22 )
<const a, const b, r = Type<type.infer<a> | type.infer<b>, $>>(
a: type.validate<a, $>,
b: type.validate<b, $>
): r extends infer _ ? _ : never
<const a, const b, r = Type<type.infer<a> | type.infer<b>, $>>(
a: type.validate<a, $>,
b: type.validate<b, $>
): r extends infer _ ? _ : never
to
<const a, const b, r = Type<type.infer<a, $> | type.infer<b, $>, $>>(
a: type.validate<a, $>,
b: type.validate<b, $>
): r extends infer _ ? _ : never
<const a, const b, r = Type<type.infer<a, $> | type.infer<b, $>, $>>(
a: type.validate<a, $>,
b: type.validate<b, $>
): r extends infer _ ? _ : never
it works. Is there are reason why the scope is not passed to type.infer here?
ssalbdivad
ssalbdivad7d ago
You're right, that is a mistake! Would be happy to accept a PR (double check other n-ary operations as well to make sure scope is passed + add test) otherwise will fix in next release Good digging 👍
ssalbdivad
ssalbdivad7d ago
One warning, if you name keywords like this with ., people might expect it to be a submodule, which is a built-in version of that syntax: https://arktype.io/docs/scopes#submodules
ArkType
ArkType Docs
TypeScript's 1:1 validator, optimized from editor to runtime
Flosi21
Flosi21OP7d ago
thanks a lot for the quick response, I will open a pull request for this issue 🙂 https://github.com/arktypeio/arktype/issues/1422
GitHub
type inference resolves custom types from scopes to be "never" when...
Report a bug 🔎 Search Terms never union discriminated union scope 🧩 Context ArkType version: 2.1.19 TypeScript version (5.1+): 5.7.3 Other context you think may be relevant (JS flavor, OS, etc.): 🧑...
Flosi21
Flosi21OP7d ago
another quick question: is there a reason why we do not reuse the inferredX properties when defining r?
inferredA = type.infer<a, $>,
inferredB = type.infer<b, $>,
inferredC = type.infer<c, $>,
r = Type<inferNaryMerge<[type.infer<a>, type.infer<b>, type.infer<c>]>, $>
inferredA = type.infer<a, $>,
inferredB = type.infer<b, $>,
inferredC = type.infer<c, $>,
r = Type<inferNaryMerge<[type.infer<a>, type.infer<b>, type.infer<c>]>, $>
ssalbdivad
ssalbdivad7d ago
I want to say no, as long as it doesn't cause any types to break its certainly cleaner (though probably negligible in terms of perf due to caching) Actually this change overall could be quite good for perf if you're not using the default scope because the missing $ arg could cause the def to be parsed twice
Flosi21
Flosi21OP7d ago
nice 😌
Flosi21
Flosi21OP6d ago
the PR is now ready for review :) https://github.com/arktypeio/arktype/pull/1423
GitHub
#1422: fix nary type inference for scopes by Flosi23 · Pull Reques...
fixes: #1422 Added scope $ to all nary type inference. TODO: add tests
Flosi21
Flosi21OP6d ago
addressed and pushed your requested changes 🙂
ssalbdivad
ssalbdivad6d ago
Awesome thanks so much- very much appreciate your thoroughness here!
Flosi21
Flosi21OP6d ago
thanks to you for you blazingly fast response time and reviews!
ssalbdivad
ssalbdivad6d ago
First time I've heard blazingly fast in that context 😅
Flosi21
Flosi21OP6d ago
btw do you know when these changes will be released?
ssalbdivad
ssalbdivad6d ago
Should be in the next day or two hopefully with the JSON schema change I'm working on!

Did you find this page helpful?