-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Forbid function assignment to index signatures:any #31102
Conversation
@typescript-bot run dt |
This was my thought at first too, but only if you take the index signature to mean "it has all the properties in the world". But that's logistically impossible so I prefer to think of it as "it can have any possible property, but doesn't have to" (effectively it's a type in which every property is optional). TS just avoids bugging you about the potential |
I'm not sure of any examples in the wild, but I can see how a function might be used as a property bag as well. Maybe the assignment needs to be allowed if the target type also has a call signature? It's also possible that that is allowed in the code and I didn't notice, but could be good to add a test for it. |
+1 to this change. I'm curious if we could take it even further. It's confusing for I'm curious what the original context and motivation was for adding the special behvaior, and whether it's too late to revisit it. |
@russelldavis Wait - I’m confused: you want the code here: #31808 to be made to work again despite the free-for-all it allows, but are okay with the change proposed in this PR which is about further restricting the semantics of index signatures? I don’t mean to sound combative but - if the case here is made to be an error, then why shouldn’t the one in #31808 continue to be an error as well? |
@fatcerberus the change here only applies to index signatures of the form |
To be a bit more explicit, you're right that I think typescript should be more restrictive here and less restrictive in #31808 (and somewhere in the middle where the two overlap). Indexers in general are safe, and using them in generic constraints doesn't change anything about their safety (which is why I'm questioning the breaking change that led to #31808). What is actually unsafe is the special rule that allows everything (except for primitives) to be assigned to If you look at the "crazy stuff" example given at #31661 (comment), the craziness has everything to do with the special rule being discussed here, and nothing to do with generics (it applies equally to an equivalent example without generics). |
Right, but In that example an assignment was allowed through an alias that otherwise would have been prevented. The only difference with So for example: function foo<T extends Record<string, unknown>>(obj: T) {
obj["s"] = 123;
obj["n"] = "hello";
}
let z = { n: 1, s: "abc" };
foo(z);
foo([1, 2, 3]);
foo(new Error("wat"));
|
It really is special. More below.
This is not correct. The assignment Also, just look at this PR. The code literally has a special case for
Your example above that statement actually shows the opposite. The bad stuff does not happen with I think you may have gotten confused because, prior to 3.5, there was also a special rule for (Note that https://www.typescriptlang.org/play/ isn't updated to 3.5 yet, so you have to be careful when using it to test things -- I've been bitten by that as well.) |
Thanks, so you're right - that is indeed an error in 3.5. However, this isn't (I tested in VSCode with 3.5.1 selected): type X = { [key: string]: unknown };
type Y = { pig: number, cow: number, ape: number };
let y: Y = { pig: 812, cow: 1208, ape: 128 };
let x: X = y; // shouldn't be allowed but is
x.whale = "it's fat";
x.pig = "type safety is now well and truly broken"; So the |
I'm not sure I agree that that example should be disallowed, but it's nuanced and I'm currently out of time. Either way, it's completely unaffected by this PR and by my proposal at #31661 (comment). Thanks for the discussion @fatcerberus. |
Mostly I’m just trying to understand what mechanism disallows the assignment from |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should sync this, rerun the tests, and go over the DT failures.
if (!targetInfo || targetInfo.type.flags & TypeFlags.Any && !sourceIsPrimitive) { | ||
if (!targetInfo || | ||
targetInfo.type.flags & TypeFlags.Any && !sourceIsPrimitive && | ||
getSignaturesOfStructuredType(source, SignatureKind.Call).length === 0 && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indexTypesRelatedTo
just takes Type
s as arguments, so this should just be getSignaturesOfType
:
getSignaturesOfStructuredType(source, SignatureKind.Call).length === 0 && | |
getSignaturesOfType(source, SignatureKind.Call).length === 0 && |
if (!targetInfo || | ||
targetInfo.type.flags & TypeFlags.Any && !sourceIsPrimitive && | ||
getSignaturesOfStructuredType(source, SignatureKind.Call).length === 0 && | ||
getSignaturesOfStructuredType(source, SignatureKind.Construct).length === 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likewise:
getSignaturesOfStructuredType(source, SignatureKind.Construct).length === 0) { | |
getSignaturesOfType(source, SignatureKind.Construct).length === 0) { |
This PR hasn't seen any activity for quite a while, so I'm going to close it to keep the number of open PRs manageable. Feel free to open a fresh PR or continue the discussion here. |
I'll open a bug for this shortly, but in the meantime:
It doesn't make sense for functions or constructors to be assignable to types with an index signature; it doesn't even make sense for anything to be assignable to a target with an index signature if the source doesn't have one (h/t @weswigham). This PR makes a small change of exempting types with call/construct signatures from the rule that makes any non-primitive source type assignable to string/number index signature of
any
.This has the concrete advantage of preventing functions from being assigned to
ArrayLike<any>
.