-
-
Notifications
You must be signed in to change notification settings - Fork 214
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Runtime behavior of optional
/undefinedable
doesn’t match TS with exactOptionalPropertyTypes
#983
Comments
import * as v from "valibot";
const unknown_schema = v.object({ field: v.unknown() });
type UnknownOutput = v.InferOutput<typeof unknown_schema>;
// { field: unknown }
const b = v.parse(
v.union([unknown_schema, v.object({ bogus: v.string() })]),
{}
);
if (!("field" in b)) { // narrows b to { bogus: string }
const s: string = b.bogus; // oops, this is undefined
} In Zod, import { z } from "zod";
const unknown_schema = z.object({ field: z.unknown() });
type UnknownOutput = z.output<typeof unknown_schema>;
// { field?: unknown }
const b = z.union([unknown_schema, z.object({ bogus: z.string() })]).parse({});
if (!("field" in b)) { // safely avoids narrowing b
const s: string = b.bogus; // safely fails type checking
} |
Thank you for creating this PR. This is intentionally and documented. We do not distinguish between missing and |
If we do not distinguish between missing and
to reflect the fact that we can in fact output either missing or |
The current solution is a tradeoff where we intentionally do not distinguish between missing and undefined entries for a smaller bundle size, but still allow users to output the type they need while knowing that there is a mismatch in runtime behavior. I can understand why it sounds like a dump, but it seems to be the best trade at the moment. We could also add a question mark when using |
The whole job of a validation library is to establish that your runtime values match the expected types! Declaring fictitious types that don’t match the runtime behavior defeats the entire point. It does not help anyone, and is wildly unsafe, to make a compile time declaration that the value has the type you need, when at runtime the value does not have that type. I understand that the current runtime behavior has a bundle size advantage. So the only good solution is to correct the types. This is not a novel unproven proposal. Zod’s runtime has the same behavior as Valibot’s runtime, and Zod declares its types correctly, like I showed in the examples above. |
Are you aware of any issues that can actually cause problems due to this mismatch in a specific scenario? So far, the pros of the current implementation outweigh the cons (feel free to read #385), but I am happy to re-evaluate and possibly change it. |
I provided such scenarios in my original report. On both of the lines with a comment A bug like this can result in an arbitrary wrong type being assigned to any value: import * as v from "valibot";
function cast<T, U>(t: T): U {
const a = v.parse(v.object({ b: v.optional(v.never()) }), { b: undefined });
while (!("b" in a)) {}
return a.b ?? t;
}
const n: number = cast("twenty-too");
console.log(n); $ tsc --module Node16 --strict --exactOptionalPropertyTypes test.ts
$ node test.js
twenty-too Please appreciate that a type safety violation is an actual problem. In a project as large as Zulip, it is not possible for the core reviewers and hundreds of new open-source contributors to keep the entire codebase in our heads, and manually account for subtle discrepancies between the compiler’s reported types and the actual types, so it is very easy for those discrepancies to snowball into major bugs and security vulnerabilities. We need to be able to rely on TypeScript’s correct checking, and TypeScript needs to be able rely on libraries’ correct type definitions in order to provide that. Yes, I read #385; it’s the very first thing I linked in my original report. I’m pretty sure the reporter doesn’t realize how unsafe the situation has become (“I can now return the validation result directly and don't have to check for those |
Thank you very much for your time and feedback! I agree and will be reviewing and re-evaluating the situation over the next few weeks. I will share my findings with you so that we can discuss the final decision together. |
I have thought about this and there is a problem. We can't know at runtime if |
We don’t need to detect anything at runtime (or at all). When If you have any doubt about how this should work, look at how Zod handles this; as far as I can tell, it does this all correctly. |
As far as I know, Zod does not support |
Zod works fine with Zod’s behavior, as I explained in my original report above, is that (There are users who have requested that Zod provide a way to distinguish |
Thank you! I will discuss this with Colin from Zod and David from ArkType and report back here. |
#385 was resolved by adding
v.undefinedable
with different typing fromv.optional
:However, this typing is inconsistent with the runtime behavior: at runtime, both schemas accept and return both of
{}
and{ field: undefined }
. This means that both schemas break type safety withtsc --strict --exactOptionalPropertyTypes
.Either the types should be adjusted to match the runtime behavior, or the runtime behavior should be adjusted to match the types.
Zod does the former:
The text was updated successfully, but these errors were encountered: