Typescript error with required properties
stevekrouse opened this issue · 9 comments
It seems like AllowImplicit
is confusing Typescript into thinking that required properties are optional.
interface A {
a: string;
}
const aDecoder: Decoder<A> = object({
a: string
});
Output:
Type 'Decoder<{ a?: string; }, unknown>' is not assignable to type 'Decoder<A, unknown>'.
Type '{ a?: string; }' is not assignable to type 'A'.
Property 'a' is optional in type '{ a?: string; }' but required in type 'A'.ts(2322)
Live example: https://codesandbox.io/s/nostalgic-sinoussi-lkrq1?file=/src/index.ts
I ran into the same trouble in one of my projects (TS 4.4.3, decoders 1.25.4). Oddly enough, in a different project (TS 4.3.5, decoders 1.25.1), all is working as expected. Installing the same versions of TS and decoders into the project with the problems didn't solve them.
I would love to get this fixed. Do you have any clues about what might be causing this behavior of the AllowImplicit
type here? Can you perhaps put together a list of reproduction steps to go from an empty directory to getting this error? Would be much appreciated! 🙏
I'll try to come up with a minimal reproduction.
I narrowed it down to the strict
setting in tsconfig.json
.
- when
"strict": false
(the default, when not set), the problem exists - when
"strict": true
the problem is gone
Steps to reproduce
Step 1: Create npm project
cd /tmp
mkdir repro-decoders-750
cd repro-decoders-750
npm init -y
npm i -D typescript@4.4.4
npm i decoders@1.25.4
Step 2: Create source file
Create file repro.ts
:
import { object, string } from "decoders";
import type { Decoder } from "decoders";
export interface Person {
name: string;
}
export const personDecoder: Decoder<Person> = object({
name: string,
});
Step 3: Create tsconfig.json files
Create tsconfig.bad.json
:
{
"compilerOptions": {
"moduleResolution": "node",
"module": "es2020",
"lib": ["es2020", "DOM"],
"target": "es2020",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"strict": false
}
}
Create tsconfig.ok.json
:
{
"compilerOptions": {
"moduleResolution": "node",
"module": "es2020",
"lib": ["es2020", "DOM"],
"target": "es2020",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"strict": true
}
}
Step 4: Demonstrate behaviour
Demostrate different behaviour:
npx tsc --noemit -p tsconfig.ok.json
# no output = no compiler errors = all ok
npx tsc --noemit -p tsconfig.bad.json
repro.ts:8:14 - error TS2322: Type 'Decoder<{ name?: string; }, unknown>' is not assignable to type 'Decoder<Person, unknown>'.
Type '{ name?: string; }' is not assignable to type 'Person'.
Property 'name' is optional in type '{ name?: string; }' but required in type 'Person'.
8 export const personDecoder: Decoder<Person> = object({
~~~~~~~~~~~~~
Found 1 error.
See difference in config:
diff -u tsconfig.*
--- tsconfig.bad.json 2021-10-29 15:37:58.000000000 +0200
+++ tsconfig.ok.json 2021-10-29 15:38:00.000000000 +0200
@@ -8,6 +8,6 @@
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
- "strict": false
+ "strict": true
}
}
Awesome, thx! Will try to take a look somewhere next week.
Thank you for this bug report @stevekrouse and thanks for these amazing reproduction steps @djlauk 🙏 !
I've narrowed the source of the bug down further and found that the strictNullChecks
flag is the real culprit (which is part of the family that strict
will enable).
https://www.typescriptlang.org/tsconfig#strictNullChecks
"When
strictNullChecks
is false,null
andundefined
are effectively ignored by the language. This can lead to unexpected errors at runtime."
This sounds really scary and I'm looking to understand when you would ever want to use this, especially if you are already in the center of the Venn diagram of users interested in using TypeScript and decoders 😄
I'm currently trying to figure out if I want to dive in and see what's causing this and see if I can somehow make it compatible with that setting (it would be nice it it would Just Work™ no matter how your TypeScript project is configured of course!), or… if I should just recommend to always use strictNullChecks: true
when using decoders.
Thoughts? Are you deliberately not using strictNullChecks
for some reason?
@nvie Thanks for looking into the matter!
To me, it would be sufficient to add it to the documentation, I guess. I can try and come up with a PR for that.
I encountered this problem when adding decoders to a toy side project whilst trying out SvelteKit. Turns out their default tsconfig.json
doesn't enable strict mode.
My best guess about people not wanting TS's strict mode, is to ease migration from JS. In the case of Svelte / SvelteKit the destructuring shorthand is conveniently used for function parameters. In JS that looks nicely terse (e.g. have a look at the get
function in this example here); in TS that terseness only works without strict mode. (If you enable strict mode, TS complains about params
implicitly being type any
.)
Thanks!!