-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
deleting a prop doesn't invalidate the interface #13783
Comments
To track effects of |
I do use
Using Or am I wrong? |
this compiles too without const p: Props = {
foo: 'hello'
bar: undefined
} and will result in the same runtime behavior. |
I try to summarise: Given we have the following interface, function and using interface Props {
foo: string;
bar: string;
}
const say = (props: Props) => console.log(`${props.foo.length} ${props.bar.length}`); This is invalid: const p = {
foo: 'hello',
bar: undefined
};
say(p); This is invalid: const p = {
foo: 'hello'
};
say(p); This is invalid: const p = {
foo: 'hello',
bar: 'world'
};
p.bar = undefined;
say(p); But this is valid and intended?: const p = {
foo: 'hello',
bar: 'world'
};
delete p.bar;
say(p); That means I'd need to mark every property in every interface as optional, if I'd want nobody to accidentally delete a required property. Isn't that more of a bug instead of an intended solution? Note that this works, too. But it is probably also a bug. const p1 = {
foo: 'hello',
bar: 'world'
};
const p2 = {
...p1,
bar: undefined
};
say(p2); |
@mhegazy you closed the issue, so this works as intended? Looking at my examples from three months ago this still looks wrong to me. |
The last case is tracked by #13195. |
for this case, with const p = {
foo: 'hello',
bar: 'world'
};
delete p.bar;
say(p); I do not think control flow analysis is the right tool here, but we could disallow deleting a property whose type does not include |
Thank you! |
I think treating |
No, they have different semantics. var x = {}
'y' in x // false
x.y = undefined
'y' in x // true
delete x.y
'y' in x // false TypeScript currently lets you delete properties from objects even if they are non-optional, and even if you have every kind of option on like interface Foo {
y: number
}
const x: Foo = { y: 3 }
delete x.y
// Now x is no longer a 'Foo', with no warnings or errors. I'd class that as a bug. |
i agree. delete should no be allowed under |
The best way I found to remove a prop from the object and infer a new object type is to use es6 destruct and rest features: interface Props {
foo: string;
bar: string;
}
const p: Props = {
foo: 'hello',
bar: 'world'
}
const say = (props: Props) => console.log(`${props.foo.length} ${props.bar.length}`);
say(p);
const { bar, ...newP } = p;
say(newP); But I agree delete on a non optional property should be forbidden. |
This issue still appears at 3.2.4 version. Any chances to get it fixed atm? |
I ran into this today and was surprised: const myObject = {
myProperty: 'content',
myOtherProperty: 'other content',
};
delete myObject.myProperty;
myObject.myProperty.padStart(2); // => Runtime error |
This behavior surprised me, too. |
So sad that typescript gives you a false sense of security.
This code
should be treated the same as casting |
That rule would then fail with delete someObject.a;
someObject.a= 'a'; // someProperty does not exist on Omit<SomeTime, 'a'>; |
Nope, So if
If
If the type is not specified:
|
So,
Which would succeed if |
So, if this is an implementation of the delete type checking:
The proper delete validation should be something like this:
Where Well, we should probably try implementing some ESLint rule first using the discarded variable casting approach above. But I'm not sure whether typescript exposes required API. And where to even start. |
I am probably in the minority here but I think that i.e. it should throw on the next |
AFAIK, TypeScript doesn't have a notion transitionary state. I.e. there's no Schrodinger type. A variable value never changes type. Types are immutable. You can't assign a field a type that is not in the list of allowed types. You can only "remove" types via typeguards but even typeguards don't change the type of a variable. It's still the same type when you pass an object to a function. It makes sense that types are immutable and don't change in the course of the program. They're checked statically, during the build. That's the reason why we have unions: we need to specify all the types a give variable can be of. Because can't add them. We can only reduce (typeguard) them. But even then we can't assign an existing variable a new type. We can only create a new variable with new type. The Of course you can use typecasting, but that would be cheating. |
I disagree with your analogy. Until the object is used to do something that actually needs to re-evaluate the type of that object, nothing is actually invalid in my book. |
@Mouvedia ok, what would be a type of a deleted property in this case, when passing it to
|
If I was using an IDE Id expect a wavy line under the argument of Because this
should be allowed. |
If such property isn't optional I don't think you should be able to delete it, if you need to change the value just assign the new value. Delete is "the same" of assign undefined to the property. |
This could be easily fixed like this:
That is if you know what you're doing you're welcome to shoot yourself in the foot. But by default, TypeScript should aim to prevent runtime errors, not tolerate some temporary untyped state of a variable. |
Deleting a property from an object is not exactly the same as setting interface ExampleType {
foo: number,
bar: number | undefined
}
const obj: ExampleType = { foo: 1, bar: 2 };
console.log(Object.keys(obj)); // [ 'foo', 'bar' ]
obj.bar = undefined; // This is allowed
console.log(Object.keys(obj)); // [ 'foo', 'bar' ]
delete obj.bar; // This should not be allowed as `bar` won't be enumerable
console.log(Object.keys(obj)); // [ 'foo' ] |
🤔 It seems the implementation from #37921 is not considering JS prototypes. For instance: const el = document.createElement('div');
el.getBoundingClientRect = () => new DOMRect();
delete el.getBoundingClientRect; // TS complains here, but this doesn't invalidate the interface, as
// `getBoundingClientRect` is still available from `el` prototype chain. I wonder if there was a way to tell TS it's fine to delete own |
That would mean TypeScript needs a notion of "overridable" in addition to "optional". So that interface prop has a default value/type. Alternatively, TypeScript should only allow a subset of what's possible with JavaScript, which is kind of the point of TS. |
TypeScript Version: 2.1? (Playground)
Code
See here.
Expected behavior:
Don't compile, because we
delete
bar
fromp
.Actual behavior:
Compiles. Resulting in a runtime error:
Uncaught TypeError: Cannot read property 'length' of undefined
.The text was updated successfully, but these errors were encountered: