You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Confusing difference between types and interfaces when intersected with string indexes #24970
Search Terms: intersection types and interfaces seem to behave differently. Inconsistent 'property 'x' of type 'y' is not assignable to string index type
Code
interfaceIJSONObject{[key: string]: string|number|boolean|IJSONObject}// we want to enforce that some objects have an `id` field// with a `type`. typeTIdElement={id: string}// here's an object with an `id` and a `day`.// we've enforced the `id` member with a `type`.typeWithType=TIdElement&{day: Date,}// we could use an interface to enforce the same idea that// things have an `id`interfaceIIdElement{id: string}// here's another object with an `id` and a `day`,// this time enforced with an `interface` instead.typeWithInterface=IIdElement&{day: Date,}// only the version where `id` was added via an intersection// with an `interface` causes a type error.//// expected behavior: a type error should happen with both,// because `Date` is not in `IJSONObject`'s index signatureinterfaceITestextendsIJSONObject{baz: WithType// no error, why?foo: WithInterface// error, as expected}
Expected behavior:
a type error should happen with both,
because Date is not in IJSONObject's index signature
Actual behavior: WithType doesn't cause an error, even though Date shouldn't be allowed.
Ryan Cavanaugh (@RyanCavanaugh) I'm not sure I completely understand you. Both baz and foo here are added via extends, and both WithType and WithInterface are constructed with intersections (&).
One of the type rules is that T & U is assignable to X if TorUis assignable toX. {id: string}, one of the constituents of WithType, is assignable to IJSonObject`.
ok, so - given that {id: string} is one of the constituents of both WithType and WithInterface, I guess what you're implying is that there is some rule that allows types to be assignable to interfaces that have string index operators, but interfaces can't be assigned to other interfaces that have string index operators, without themselves also defining a matching string index.
the above behavior is something I've noticed, I believe, but not seen described anywhere with some convincing reasoning.
is my description accurate?
if so, is there a place where I can read more about this, or see it explained in any more detail?
(at the core of these questions is a deep confusion about the minute differences between type and interface, and it seems to be a topic of little in-depth discussion)
I am confused like Monroe Ekilah (@ekilah) on this one. The interface in that situation has one of the constituents assignable to IJSONObject, why is it rejected? Your (Ryan) first reply is also confusing since both (type and interface) are using the intersection, and both are a member of a class that extends an interface.
There is not a single place that explains all emergent behavior. Sometimes we define that X causes Y, and B causes C, and P causes Q, and M causes N, then someone notices that X + B - P + Q isn't identical to X + B - P + M even though M and Q are supposed to be pretty similar - it isn't so much that we specifically designed the X + B - P + M rule and have written down a convincing explanation of why that should be the case, but rather than the initial five propositions are all individually sensible and cannot be changed without making a lot of other things worse.
Ford04 that's an interesting angle, and I really appreciate you sharing it. Your explanation goes above and beyond any of Ryan Cavanaugh (@RyanCavanaugh) 's three "attempts" to half-answer this question or provide some insight after already closing it as if it were obvious. I haven't seen mention of this "implicit index signature" before, but that issue you linked seems to describe a similar issue. Unfortunately it seems like people there have mostly been left to figure it out themselves as well.
For what it's worth, to this day I've never found a good resource that actually describes how types and interfaces differ internally or how we, as external clients of the language, should use them differently, and after using TypeScript for 1.5 years professionally now I think that is simply ridiculous. Here we are, trying to use the language as intended, and when we run across weird behaviors that don't make a lot of sense, the answer is not much more than a "working as intended" label.
Ryan Cavanaugh (@RyanCavanaugh) I invite you and your team back to read this thread all the way through to see if you can give us a little better of an answer, all this time later. Individually, none of your answers above make much sense, and together, they sound fairly incoherent. With all the advances of the type system lately, it surprises me that these types of questions are still left mostly unanswered.
Activity
RyanCavanaugh commentedon Jun 14, 2018
Intersection is a higher-order process that can occur during e.g. generic instantiation; intentionally this process can never create errors.
extendshappens immediately and we do check that you fulfill the base type's contract.ekilah commentedon Jun 14, 2018
Ryan Cavanaugh (@RyanCavanaugh) I'm not sure I completely understand you. Both
bazandfoohere are added viaextends, and bothWithTypeandWithInterfaceare constructed with intersections (&).RyanCavanaugh commentedon Jun 14, 2018
One of the type rules is that
T & Uis assignable toXifTorUis assignable toX.{id: string}, one of the constituents ofWithType, is assignable toIJSonObject`.ekilah commentedon Jun 14, 2018
ok, so - given that
{id: string}is one of the constituents of bothWithTypeandWithInterface, I guess what you're implying is that there is some rule that allowstypes to be assignable tointerfaces that have string index operators, butinterfaces can't be assigned to otherinterfaces that have string index operators, without themselves also defining a matching string index.the above behavior is something I've noticed, I believe, but not seen described anywhere with some convincing reasoning.
(at the core of these questions is a deep confusion about the minute differences between
typeandinterface, and it seems to be a topic of little in-depth discussion)MrDesjardins commentedon Jun 16, 2018
I am confused like Monroe Ekilah (@ekilah) on this one. The interface in that situation has one of the constituents assignable to
IJSONObject, why is it rejected? Your (Ryan) first reply is also confusing since both (type and interface) are using the intersection, and both are a member of a class that extends an interface.RyanCavanaugh commentedon Jun 18, 2018
The spec defines the rules: https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md
There is not a single place that explains all emergent behavior. Sometimes we define that X causes Y, and B causes C, and P causes Q, and M causes N, then someone notices that X + B - P + Q isn't identical to X + B - P + M even though M and Q are supposed to be pretty similar - it isn't so much that we specifically designed the X + B - P + M rule and have written down a convincing explanation of why that should be the case, but rather than the initial five propositions are all individually sensible and cannot be changed without making a lot of other things worse.
ekilah commentedon Oct 30, 2019
Ford04 that's an interesting angle, and I really appreciate you sharing it. Your explanation goes above and beyond any of Ryan Cavanaugh (@RyanCavanaugh) 's three "attempts" to half-answer this question or provide some insight after already closing it as if it were obvious. I haven't seen mention of this "implicit index signature" before, but that issue you linked seems to describe a similar issue. Unfortunately it seems like people there have mostly been left to figure it out themselves as well.
For what it's worth, to this day I've never found a good resource that actually describes how types and interfaces differ internally or how we, as external clients of the language, should use them differently, and after using TypeScript for 1.5 years professionally now I think that is simply ridiculous. Here we are, trying to use the language as intended, and when we run across weird behaviors that don't make a lot of sense, the answer is not much more than a "working as intended" label.
Ryan Cavanaugh (@RyanCavanaugh) I invite you and your team back to read this thread all the way through to see if you can give us a little better of an answer, all this time later. Individually, none of your answers above make much sense, and together, they sound fairly incoherent. With all the advances of the type system lately, it surprises me that these types of questions are still left mostly unanswered.