Consider the code below (running in TypeScript 3.2.2):
const status: 'successful' | 'failed' = 'successful';
function test(): typeof status {
const status = 'hello';
return 'successful';
}
This doesn't compile since the return type of test
and its signature do not match:
error TS2322: Type '"successful"' is not assignable to type 'IResult<"hello">'.
For some reason the status
definition inside the function is being used to determine the return type.
This doesn't happen when using var
; this code:
function test(): typeof status {
var status = 'hello'; // notice the var here
return 'successful';
}
produces the expected return type of 'successful' | 'failed'
.
Using let
:
function test(): typeof status {
let status = 'hello'; // notice the let here
return 'successful';
}
This compiles but with the effect that the return type is string
, the inner definition is being used again.
I expected tsc
to use the status
which is defined most high up in scopes to evaluate its return type in both cases, regardless of what declarations exist inside test
. Why is the behaviour as observed above? This should be related to how tsc
decides which variables to use for type inference.
There are 12 cases here:
(const, let, var) * (global, local) * (explicit string union, inferred string)
TL;DR
"ok" | "no"
.let
and const
ARE available for typeof
at return position, they shadow outer declaration ..var
is not available for typeof
at return type position (for whatever reason)let
and var
will consider type to be string
, and const
will type to exact [immutable original] value.// Infer, Inner
function test_inner_let(): typeof status01 { // type is string
let status01 = 'ok'; // let is mutable, so type is only string
return 'lol';
}
function test_inner_const(): typeof status02 { // type is 'ok'
const status02 = 'ok'; // const allows to specify type to exact 'ok'
return 'lol'; // error, 'lol' is not assignable to 'ok'
}
function test_inner_var(): typeof status03 { // type is any, TS warning: status03 not found
var status03 = 'ok'; // var is mutable, so type is string
return 'lol';
}
// Explicit, Inner
function test_inner_let_t(): typeof status11 { // type is union 'ok'|'no'
let status11: 'ok' | 'no' = 'ok';
return 'lol'; // error, 'lol' is not assignable to 'ok'|'no'
}
function test_inner_const_t(): typeof status12 { // type is union 'ok'|'no'
const status12: 'ok' | 'no' = 'ok';
return 'lol'; // error, 'lol' is not assignable to 'ok'|'no'
}
function test_inner_var_t(): typeof status13 { // type is any, TS warning: status13 not found
var status13: 'ok' | 'no' = 'ok';
return 'lol';
}
// Explicit, Outer - everything works the same
let status21: 'ok' | 'no' = 'ok';
function test_outer_let_t(): typeof status21 { // type is union 'ok'|'no'
return 'lol'; // error, 'lol' is not assignable to 'ok'|'no'
}
const status22: 'ok' | 'no' = 'ok';
function test_outer_const_t(): typeof status22 { // type is union 'ok'|'no'
return 'lol'; // error, 'lol' is not assignable to 'ok'|'no'
}
var status23: 'ok' | 'no' = 'ok';
function test_outer_var_t(): typeof status23 { // type is union 'ok'|'no'
return 'lol'; // error, 'lol' is not assignable to 'ok'|'no'
}
// Infer, Outer
let status31 = 'ok'; // type is string
function test_outer_let(): typeof status31 { // type is string
return 'lol';
}
const status32 = 'ok'; // const allows to specify type to exact 'ok'
function test_outer_const(): typeof status32 { // type is 'ok'
return 'lol'; // error, 'lol' is not assignable to 'ok'
}
var status33 = 'ok'; // var is mutable, so type is string
function test_outer_var(): typeof status33 { // type is string
return 'lol';
}
// (Explicit, Outer const) + (Implicit, Inner)
const status41: 'ok' | 'no' = 'ok';
function test_combo_let(): typeof status41 { // type is string, inner let took preference
let status41 = 'ok';
return 'lol';
}
const status42: 'ok' | 'no' = 'ok';
function test_combo_const(): typeof status42 { // type is 'sorry', inner const took preference
const status42 = 'sorry';
return 'lol'; // error, 'lol' is not assignable to 'sorry'
}
const status43: 'ok' | 'no' = 'ok';
function test_combo_var(): typeof status43 { // type is union 'ok'|'no', var is not bubling up
var status = 'whatever';
return 'lol'; // error, 'lol' is not assignable to 'ok'|'no'
}