Created
December 18, 2019 17:47
-
-
Save jrheling/149ef2db274ce0f9722fc7322b972cd9 to your computer and use it in GitHub Desktop.
typescript type guards around closure definitions are ignored by the inner function
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Typescript handles type guards fundamentally differently than it | |
// handles data in the context of a closure. | |
// | |
// This is not new, and was documented at | |
// https://github.com/microsoft/TypeScript/issues/7662 | |
// and | |
// https://github.com/microsoft/TypeScript/issues/8552 | |
// | |
// Demonstration follows. | |
interface namedThing { | |
name: string; | |
val: number; | |
} | |
interface myType { | |
p1: namedThing | null; | |
p2: number; | |
} | |
// Given a list of namedThings and an object of myType, return | |
// a function that will itself return true if the object is in the | |
// list. | |
// | |
// Note: the inner function is gratuitous here, and is present only | |
// to demonstrate the behavior of data and type guards across the | |
// closure boundary. | |
function funcFactory(s: namedThing[], o: myType): () => boolean { | |
// this doesn't work because o.p1 could be null | |
//let thingWeAreLookingFor: namedThing = o.p1; | |
// with a type guard, we're good | |
let thingWeAreLookingFor: namedThing; | |
if (o.p1) { | |
thingWeAreLookingFor = o.p1; | |
} | |
// but that same type guarding approach does *not* work | |
// when we're trying to guard the variable within | |
// a closure -- the following fails because the compiler | |
// says that o.p1 might be null, even though it's wrapped | |
// in the type guard that guarantees otherwise. | |
// if (o.p1) { | |
// const innerF = function() { | |
// let foundIt = false; | |
// s.forEach( e => { | |
// if (e.name == o.p1.name) { | |
// foundIt = true; | |
// } | |
// } | |
// ) | |
// return(true); | |
// } | |
// } | |
// if the type guard is moved inside of the inner function, | |
// all is well | |
const innerF = function() { | |
let foundIt = false; | |
s.forEach( e => { | |
if (o.p1) { | |
// console.log(`comparing ${e.name} to ${o.p1.name}`); | |
if (e.name == o.p1.name) { | |
foundIt = true; | |
} | |
} | |
}) | |
return(foundIt); | |
} | |
return(innerF); | |
} | |
let obj = { | |
p1: { name: "foo", val: 2 }, | |
p2: 2 | |
} | |
let objList = [ | |
{ name: "bar", val: 2 }, | |
{ name: "foo", val: 3 }, | |
{ name: "obazof", val: 4 }, | |
] | |
let myFunc = funcFactory(objList, obj); | |
console.log(`1) The item ${myFunc() ? 'was' : 'was not'} found in the list`); | |
// changing what we're looking for now won't change myFunc, | |
// since it's bound to the values present when the closure was created | |
obj = { | |
p1: { name: "food", val: 2 }, | |
p2: 2 | |
} | |
console.log(`2) The item ${myFunc() ? 'was' : 'was not'} found in the list`); | |
// Obviously the fact that changing obj's value after creating myFunc has no | |
// impact on the value of myFunc is not surprising - this is the core of | |
// makes the closure a closure. | |
// | |
// But the point of the more drawn out example is to demonstrate that data and | |
// type guards behave differently across the closure's scope boundary. IOW, | |
// tsc logically *could* let the type guard defined in the outer scope "work" | |
// in the inner scope, it just chooses not to. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
See https://gist.github.com/madsimian/765ddfdbf32aeeaca05007cdd185da5b for a good outline of why this is appropriate