-
Notifications
You must be signed in to change notification settings - Fork 13k
Not planned
Not planned
Copy link
Labels
In DiscussionNot yet reached consensusNot yet reached consensusSuggestionAn idea for TypeScriptAn idea for TypeScript
Description
TypeScript Version: 2.2.2
Code
interface IndexType {
[key: string]: string;
}
interface doesNotWork {
hola: string;
}
type doWorks = { hola: string };
let y: IndexType;
const correctA = { hola: "hello" };
const correctB: doWorks = { hola: "hello" };
//error should be assignable to y
const error: doesNotWork = { hola: "hello " };
y = correctA;
y = correctB;
y = error; //Index signature is missing in type 'doesNotWork'
y = {... error}; //workaround but not equivalent since the instance is not the same
Expected behavior:
The code should not result on a compiler error since the interface doesNotWork
is equivalent to the type { hola: string }
Actual behavior:
Variable error
of type doesNotWork
can't be assigned to y
GuillaumeNury, metabacalhau, roddolf, martinsik, cainlevy and 362 moresteve-taylor0x0-mico and FrameMuse
Metadata
Metadata
Assignees
Labels
In DiscussionNot yet reached consensusNot yet reached consensusSuggestionAn idea for TypeScriptAn idea for TypeScript
Type
Projects
Milestone
Relationships
Development
Select code repository
Activity
syabro commentedon Apr 25, 2017
Same here for 2.3
cainlevy commentedon Jun 28, 2017
Seeing this still in 2.4. Here's a playground repro.
This appears to be a bug considering implicit index signatures is listed as a feature of v2.0.
rafaelkallis commentedon Aug 2, 2017
Any update regarding the issue? Still not fixed as of
2.4.2
.Playground
RyanCavanaugh commentedon Sep 26, 2017
Just to fill people in, this behavior is currently by design. Because interfaces can be augmented by additional declarations but type aliases can't, it's "safer" (heavy quotes on that one) to infer an implicit index signature for type aliases than for interfaces. But we'll consider doing it for interfaces as well if that seems to make sense
NN--- commentedon Sep 27, 2017
@RyanCavanaugh Thanx for reasoning. Can I say as a rule of thumb to use 'type' for internal interfaces since I don't need augmentation and 'interface' for external to be able to augment ?
RyanCavanaugh commentedon Sep 27, 2017
There are some other differences between type aliases and interface that might affect your choice - namely you can
extend
an interface but not a type alias. So it's not quite that simple, but as rules of thumb go it's not a bad one.NN--- commentedon Sep 27, 2017
You can extend types through intersection types.
Is there anything I miss here?
Codesleuth commentedon Feb 8, 2018
I have an example where this rule is inconsistent when casting to the expected indexed type:
Playground link
What is it that is considered different about these that is preventing the first case? Note that this is occurring in 2.7 (see the playground link)
patrickneugebauer commentedon Mar 8, 2018
I was working on a somewhat related example today and I was pretty confused by the results. I tried to create some related situations so I could understand the rules.
As I understand:
I tested this in typescript playground:
Codesleuth commentedon Mar 9, 2018
Thanks @patrickneugebauer - I had noticed those rules too, and have resorted to using
type
overinterface
in the common case, to allow building layers of type interactions without hitting walls of errors like these.In my example, changing the interface to a type does indeed get around the error.
210 remaining items
ref(node): Small `RequestData` integration tweaks (#5979)
JakeTompkins commentedon Nov 4, 2022
This issue is old enough to be in kindergarten by now. Has no consensus been reached or has it just been abandoned?
hanzhangyu commentedon Nov 4, 2022
Webbrother commentedon Nov 16, 2022
I believe that the current behavior of Interfaces is correct. Because I can not assign a type to a much wider type.
I can not assign Animal to Dog, but I can assign Dog to Animal.
See "L" letter meaning of SOLID principles.
BUT behavior of Types is wrong.
Let's say I have an Interface A.
I can not assign A to
Record<string, string>
because the Record can have any other property.Example:
So if I'll pass A to fn it is obvious that an error must occur.
Unfortunately at the moment, I can assign Type to Record without error. And I believe this is wrong behavior.
Playground
jcalz commentedon Nov 17, 2022
^ Like it or not, implicit index signatures are an intended feature of TypeScript.
Webbrother commentedon Nov 17, 2022
@jcalz Thank you for your comment. Yes, I see that this is intended feature but I bealeve it has wrong implementation.
I bealive the root of problem here is how
Record<string, string>
works.then It means that
Record
is "wider" than just object. We can call any string property of Record. So we can say that record has all possible string keys (in fact of course it has just a few props and it is impossible to have all possible keys).Therefore,
according to Barbara Liskov principle we can not assign the object to the Record! But such a restriction makes a lot of people confusing, because assignment of the Object to Record intuitively looks good.
So if we expect that such a assignment have to be correct then we have to agree that the Record is not "wider" type tyan
IObj
, therefore it should be impossible to call any string property on Record type.So work with record should looks like:
It is impossible to have an object with all possible string keys that's why code from
example 3
looks more correct than code fromexample 1
To finalize:
Abviously that current types logic implementation has a problems and leads people to confusing. So I see two possible ways to fix it.
To accept that Record is "wider" than object. Then current behavior of Interfaces is correct. But behavior of Types is wrong and should be fixed.
To accept that Record is not "wider" than object. Then current behavior of Record and Interfaces are wrong and should be fixed.
After long thinking I personally prefer the 2nd option.
matgr1 commentedon Nov 18, 2022
in case it helps anyone, I just found a workaround (essentially converting the
interface
to atype
viaPick
) for a slightly different situation where I hit the same underlying issue:and depending on the use case, I think it can be applied to the original issue:
lobsterkatie commentedon Nov 18, 2022
Nice, @matgr1.
I've extracted it into a helper type:
type IndexOptional<T> = Pick<T, keyof T>
and can confirm that, in the above example, it works to do:Webbrother commentedon Nov 18, 2022
@matgr1 @lobsterkatie
This is just a rough workaround.
Pick<SomeInterface, keyof SomeInterface>
is just converting anInterface
to the sameType
and nothing more.IndexOptional<Interface>
- absolutely the same.Those examples work because in Typescript
Type
can be assigned toRecord<..., ...>
. There are no other reasons.As I explained above this is wrong behavior, because it contradicts to Liskov substitution principle.
It is still unsafe (demo):
jcalz commentedon Nov 18, 2022
@Webbrother Yes, TypeScript is intentionally unsound in places; implicit index signatures and unchecked indexed accesses are two of these places, and not really the subject of this GitHub issue. This issue is just about whether or not interfaces should be granted implicit index signatures, with all the accompanying baggage, for better or worse.
lobsterkatie commentedon Nov 18, 2022
@Webbrother - yes, I understand it's a workaround (and what it's doing). It's just now a slightly nicer-looking workaround. 🙂
And yes, exactly what @jcalz said (though he said it better and more concisely than I could have, so thanks for that, @jcalz!).
RyanCavanaugh commentedon Nov 18, 2022
As I said above, before my preschooler was born 😅:
This has not changed.
Changing the behavior at this point would honestly be an incredibly disruptive breaking change without much concrete upside. For clarity, I'm going to close and lock this - interfaces should declare an explicit index signature if they intend to be indexed.