-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Object is possibly null inside a closure defined within a const constraint #36193
Comments
Actually now I think this issue should be closed because the behavior is correct. Consider this: let foo: string | null = 'bar'
const delegate: { baz: ()=>void }: { baz: ()=>{} }
if (foo) {
function baz() {
console.log(foo.length) // Object is possibly 'null'
}
delegate.baz = baz
}
foo = null
delegate.baz() // runtime failure edit April 24: this is a bad example, |
In principle, TypeScript can check whether the function is allocated. (Not sure should I open a feature request.) if (foo) {
function baz() {
console.log(foo.length) // Object is 'string' | 'null'
}
delegate.baz = baz
} if (foo) {
function baz() {
console.log(foo.length) // Object is 'string'
}
// no assign for baz
} Or design a keyword to prevent the function allocated. if (foo) {
static function baz() {
console.log(foo.length) // Object is 'string'
}
delegate.baz = baz // error: function can't be allocated because function is static
} |
Hi @johnsoncodehk I think Typescript behaves correctly already. Consider this: if(foo) {
function baz() {
console.log(foo.length) // Object is possibly null
}
setTimeout(baz,100)
} Since there is no way to guarantee that |
@benallfree in this case But I just found a other case so we can't do that: if(foo) {
baz(); // foo is not null here
foo = null
baz(); // foo is null here but no type error
function baz() {
console.log(foo.length)
}
} |
@johnsoncodehk Right. Then do you agree also that this issue should be closed? |
Yes, I do. :) |
Closing per discussion. |
Sorry, reopening, I think I might be confusing myself. @johnsoncodehk this issue is about a
So maybe the real question is why |
This issue seem to have 2 problems now. In you comment, it seem not a closure type narrowing problem, but is a problem of type picking priority in function. const foo: string | null = 'bar'
function baz() {
console.log(foo.length) // Object is possibly 'null'
} In this case function preference to (remove this part because updated before comment) |
@johnsoncodehk |
So our consensus is under this 2 conditions, variables type narrowing in closure function is feasible?
|
@johnsoncodehk Can you provide an example please?
Yes this does seem to be an issue. Try this playground sample: const foo: string | null = 'bar'
const baz1 = () => {
console.log(foo.length) // OK (narrows to string)
}
const baz2 = function () {
console.log(foo.length) // OK (narrows to string)
}
function baz3() {
console.log(foo.length) // Error (possibly null)
}
console.log(foo.length) // OK (narrows to string) |
Never mind just let we focus on your problem to avoid confusion.
Because const baz1 = () => {
console.log(foo.length) // Error (possibly null)
}
const baz2 = function () {
console.log(foo.length) // Error (possibly null)
}
function baz3() {
console.log(foo.length) // Error (possibly null)
}
const foo: string | null = 'bar' |
Ah, function hoisting! I did not know this. From SO:
In TS we can still know that the |
I believe typescript could handle this better. I encountered the problem with a real-world example like this, which I have simplified so it can live in a playground ... type UrlString = `http${string}`;
interface Item {
message: string;
}
interface Config {
endpointUrl?: UrlString;
}
async function doSave(item: Item, endpointUrl: UrlString) {
const response = await fetch(`${endpointUrl}/item`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(item),
});
if (response.status !== 200) {
throw new Error("Save unsuccessful");
}
return item;
}
function createHandle(config: Config) {
const { endpointUrl } = config;
if (!endpointUrl) {
throw new Error(`Cannot create handle without endpointUrl config`);
}
// FUNCTION BASED APPROACH
async function save(item: Item) {
// compilation error Type 'undefined' is not assignable to type '`http${string}`'
return await doSave(item, endpointUrl);
}
return {
save,
};
} I have no choice but for the config object to have It is interesting that if I replace the later lines from // METHOD BASED APPROACH
return {
async save(item:Item){
// no compilation error
return await doSave(item, endpointUrl);
}
};
} However, this also suggests to me that typescript type narrowing inference is not doing its job correctly for the case that a function is defined within a closure over a |
Search: Object is possibly null, narrowed type, type constraint, closure
Code
Expected behavior:
Compiles
Actual behavior:
Playground Link:
Playground
Related Issues:
#12113, #33319, #9998
Discussion:
By design, type narrowings are reset inside closures. This makes sense, but there is one case where better assumptions can be made: when the closure is defined inside a type constraint block AND the type narrowing is based on a
const
value. Even if called from async, the closure is still referencing a const and was created after a suitable type constraint occurred.Thoughts?
The text was updated successfully, but these errors were encountered: