Null is usually not added to a variable's list of types
Hello.
I have tried to make some fields of class nullable, but can't get any information about allowed "nullable" behavior (same works with types, functions and interfaces too).
After a little investigation of the behavior, it turned out that the null type is added to field types in some cases. Here is an example code (ts) and "compiled" code (js):
import { NullLiteral } from "typescript";
// ...
class Test3 {
testString1: string | null = null;
testString2: null | string = null;
testNull: null = null;
testNumber: number | null = null;
testUndefined: undefined | null = null;
testDate: Date | null = null;
testNullLiteral: NullLiteral | null = null;
testBoolean: boolean | null = null;
testAny: any | null = null;
testInterface: Test2 | null = null;
testPromise: Promise<string | number | null> | null = null;
testMethod(arg: string | null): string | null {
return arg;
}
}
_ßr.Type.store.set(31, { n: "test3", k: 3, types: [
_ßr.Type.store.wrap({ n: "string", k: 2, ctor: function () {return Promise.resolve(String);} }),
_ßr.Type.store.wrap({ n: "number", k: 2, ctor: function () {return Promise.resolve(Number);} })
], union: true, inter: false });
_ßr.Type.store.set(99, { n: "testDate", k: 3, types: [_ßr.Type.store.getLazy(99), _ßr.Type.store.wrap({ n: "null", k: 2 })], union: true, inter: false });
_ßr.Type.store.set(100, { n: "testNullLiteral", k: 3, types: [_ßr.Type.store.getLazy(100), _ßr.Type.store.wrap({ n: "null", k: 2 })], union: true, inter: false });
_ßr.Type.store.set(112, { n: "Promise", k: 2, args: [_ßr.Type.store.get(31)] });
_ßr.Type.store.set(95, { k: 1, n: "Test3", fn: "ts-api-test/test.ts:Test3#95", props: [
{ n: "testString1", t: _ßr.Type.store.wrap({ n: "string", k: 2, ctor: function () {return Promise.resolve(String);} }), am: 2, acs: 0, ro: false, o: false },
{ n: "testString2", t: _ßr.Type.store.wrap({ n: "string", k: 2, ctor: function () {return Promise.resolve(String);} }), am: 2, acs: 0, ro: false, o: false },
{ n: "testNull", t: _ßr.Type.store.wrap({ n: "null", k: 2 }), am: 2, acs: 0, ro: false, o: false },
{ n: "testNumber", t: _ßr.Type.store.wrap({ n: "number", k: 2, ctor: function () {return Promise.resolve(Number);} }), am: 2, acs: 0, ro: false, o: false },
{ n: "testUndefined", t: _ßr.Type.store.wrap({ n: "null", k: 2 }), am: 2, acs: 0, ro: false, o: false },
{ n: "testDate", t: _ßr.Type.store.get(99), am: 2, acs: 0, ro: false, o: false },
{ n: "testNullLiteral", t: _ßr.Type.store.get(100), am: 2, acs: 0, ro: false, o: false },
{ n: "testBoolean", t: _ßr.Type.store.wrap({ n: "boolean", k: 2, ctor: function () {return Promise.resolve(Boolean);} }), am: 2, acs: 0, ro: false, o: false },
{ n: "testAny", t: _ßr.Type.store.wrap({ n: "any", k: 2 }), am: 2, acs: 0, ro: false, o: false },
{ n: "testInterface", t: _ßr.Type.store.get(88), am: 2, acs: 0, ro: false, o: false },
{ n: "testPromise", t: _ßr.Type.store.get(112), am: 2, acs: 0, ro: false, o: false }
], meths: [
{ n: "testMethod", params: [
{ n: "arg", t: _ßr.Type.store.wrap({ n: "string", k: 2, ctor: function () {return Promise.resolve(String);} }), o: false }
], rt: _ßr.Type.store.wrap({ n: "string", k: 2, ctor: function () {return Promise.resolve(String);} }), tp: [], o: false, am: 2 }
], ctors: [{ params: [] }], ctor: function () {return Promise.resolve(Test3);} });
As you can see, only types for testNull, testDate, testNullLiteral are shown as they should.
Also, is the behavior of testDate correct?
In testDate recursive reference specified to type testDate (_ßr.Type.store.getLazy(99))
tst-reflect: 0.7.5 tst-reflect-transformer: 0.9.10
TY @Irelynx for the issue. I'll look into it.
Yes, testDate is ttly wrong.
This is caused by the strictNullChecks tsconfig option. It is disabled by default and when it is disabled, you can assign null into many types.
Such as:
class Foo {
testString1: string = null;
testNumber: number = null;
testBoolean: boolean = null;
}
const x = new Foo();
x.testString1 = null;
x.testNumber = null;
x.testBoolean = null;
That is valid TS code with default tsconfig (with strictNullChecks disabled).
So when you have
class Foo {
testString1: string | null = null;
testNumber: number | null = null;
testBoolean: boolean | null = null;
}
it is the same type, cuz those nulls are stripped off. Compiler throws it away with disabled strictNullChecks.
Solution is to enable the strictNullChecks option.
There is a way how to get that information anyway (that there is union with null), but it is quite complicated and not reliable.
This must be handled so the reflection keeps standard behavior, no matter what the strictNullChecks option is.
So with strictNullChecks: false (default) every type will be union with null.
So not just fooProp: string | null; will be string | null but fooProp: string; will be string | null too.
But this has big impact and it is quite complicated to implement this into currect version so it will be in the next version.
Thanks a lot for your investigation!
I will try change the tsconfig and test my code as soon as I have time for it.