According to typescript definition, using the non-null assertion operator never should impact execution, but I have a case where it, in fact, does.
My problem has been in replicating this with a simpler project. In my project, I have a fairly simple function:
const getNpcCharacterData = (npcMapConfig: IMapEntity | undefined): any => {
return {
position: {
x: npcMapConfig?.position!.x ?? 0,
y: npcMapConfig?.position!.y ?? 0,
},
};
};
Using the !
causes an error if npcMapConfig
is undefined. If I remove the !
, there is no error. Looking at the generated javascript code, it is, in fact, different in these cases.
Without !
:
return {
position: {
x: (_npcMapConfig_position_x = (_npcMapConfig = npcMapConfig) === null || _npcMapConfig === void 0 ? void 0 : _npcMapConfig.position.x) !== null && _npcMapConfig_position_x !== void 0 ? _npcMapConfig_position_x : 0,
...
Versus, with the !
return {
position: {
x: (_npcMapConfig_position_x = ((_npcMapConfig = npcMapConfig) === null || _npcMapConfig === void 0 ? void 0 : _npcMapConfig.position).x) !== null && _npcMapConfig_position_x !== void 0 ? _npcMapConfig_position_x : 0,
...
The difference is placement of parens, resulting in difference of when it is evaluating the .x
. This seems unusual, new and unexpected.
This did not happen until recently when we updated from version 4.9.5 to 5.1.16. Generating for ES5 in both cases, identical tsconfig.json.
Unfortunately, I cannot get a small reproducible case. Every small case I create, the generated javascript is identical with or without !
. It must be something else in my project, but something that impacts the conversion from ts -> js, which seems limited. It isn't a local issue, as it occurs in our CICD builds as well as local debug builds.
What I'm looking for an answer to is what else could impact this. Webpack
is my biggest suspicion, but my understanding is that runs after the conversion to javascript, so while it might change the js, how would the !
change it since that is removed prior? We are also using next-js, and I'm trying to reproduce without that to get a simple case. There is es-lint, but I don't think that impacts the transpilation.
Also, in my smaller cases, it only uses 2 temporary variables, one for _x and one for _y, but in the problematic code, it is also using a temp variable for the parameter itself (_npcMapConfig
). I am unsure why it would use a temporary for that, so any tsconfig settings that might lead to more aggressive use of temp variables or optimizations could be key, although I've been trying with identical tsconfig.
Turns out, by using next-js
, the compiler used is not tsc
but swc
.
The behavior is easily reproduced when using swc
as the transpiler.
I am changing the title of this question so this answer may help others in future.