Home > Net >  Why can't we change const to let?
Why can't we change const to let?

Time:11-27

Consider the following trivial example:

namespace Calculus {
  type Func<T> = {
    (arg: T): T;
    delta: (arg: T) => T;
  };

  const f: Func<number> = (x) => x * x; // const cannot be changed to let, why?
  f.delta = (x) => 2 * x;
  console.log(`f(5) = ${f(5)} and f'(5) = ${f.delta(5)}`);
}

Why can't we change const to let? Here is the error message:

Property delta is missing in type (x: number) => number but required in type Func<number>.

CodePudding user response:

The support for property declarations on functions, where you can define a function and then later add properties to it, is only available in a few specific use cases. In microsoft/TypeScript#26368, the pull request that implemented this feature, this is allowed "only for function declarations and function/arrow expressions used as initialisers of const variables".

The feature is definitely sensitive to code style; even wrapping the initializer in parentheses will break it, as shown in microsoft/TypeScript#46284

So the reason you can't change const to let here is that doing so no longer initializes a const variable, and so the assignment is checked in the normal way, where all properties need to be present upon initialization:

const foo: {a: string} = {}; // error!
foo.a = "";

Of course this raises the question: why isn't let supported? It isn't stated explicitly in the pull request, but I strongly suspect it's because allowing re-assignable variables would require more careful checking to prevent invalid assignments from leaking though. For example:

let g: Func<number> = x => x   1; // imagine this is okay
g.delta = x => 1; // then this completes the initialization
g = x => x   7; // but this wrecks it; is this okay? 

The compiler would need to do something like control flow analysis to "reset" g back to partially uninitialized after a reassignment. That's not impossible to implement, but it requires more work and probably wasn't worth anyone's time or effort.

Besides, even the current feature doesn't do control flow analysis, leading to fun uncaught runtime errors like this:

const h: Func<number> = x => Math.exp(x); // okay?!
if (1 < 0) { // this never happens
  h.delta = h; // the compiler is fooled by this
}
h.delta(1); // no compiler error, but
// RUNTIME ERROR            
  • Related