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
Unexpected error when generic extends recursive type (alias vs interface). #49751
You'd expect alias vs interface to behave indifferently when used as a generic argument. Moreover, when defining a shape, the use of interface should be preferred as, to my understanding, it is more semantically correct and has better error reporting.
However it particularly fails in the following piece of code when passed to a generic parameter extending a recursive type.
Is this error intended? If so, what is the intended difference to be represented here?
Thank you.
🔎 Search Terms
alias interface error generic extends recursive json type
The error has nothing to do with the type being recursive or not. It happens because plain { ... } object types have an implied index signature (this is what allows you to assign object literals to Record types), while interfaces are only assignable to index-signature types if the interface has a compatible index signature itself. The error message tells you exactly what's wrong:
Type 'MyLayout2' does not satisfy the constraint 'Layout'.
Index signature for type 'string' is missing in type 'MyLayout2'.
This is in fact part of the "better error reporting" for interfaces that you allude to. 😉
However, the center of the issue is the ability to have a generic parameter extend a recursive type.
Does the actual reason means that interfaces can't be used as arguments to these types of generic?
Or does it means that having a generic extend a recursive type is not supported, is not a thing?
Is the consequence for this error intended or is it a bug? If so, what is the intended difference? Does it have sense to have this error here?
Like I said, it has nothing to do with the recursion and everything to do with the index signature in Layout. If Layout were instead { foo: Primitive | Array<Primitive | Layout> | Layout } with corresponding types for MyLayout1 and MyLayout2 then the interface/POJO distinction wouldn't matter.
But yes, if your generic is constrained to a type with an index signature, then you can't use interfaces with it unless they also have a compatible index signature.
It's ok. I understand what you're saying, and I'm sorry if my example feels like I'm asking about that particular error and nothing more. It just happened to be the one that I could use because of the semantics.
But as I just mentioned, that is not the real issue I'm trying to highlight. It's what's behind, the consequence. I don't know how else to put it. The developer intention (because it seems to work as would be the intention using the type alias).
My questions remain unanswered.
Then I have absolutely no idea what you're asking, because the error you're pointing out is due to the lack of index signature in an interface and has nothing at all to do with your generic type being recursive.
The problem you're running into boils down to this...
You sound a little bit annoyed by not being able to understand and now implying I'm misusing this place as a support forum.
So let me break it down and try to explain it the simplest I can in steps.
I'm putting a generic argument to guard for (extends) a recursive type, which itself is "generic" in the sense that their keys are whatever string (not symbols, not numbers).
The (only) way to declare it is by having to use the "index signature" (for better or for worst, that's not the point).
I then have a type MyLayout2 that semantically complies with this recursive type (in other words, it is substitutable).
But as you already know, it doesn't work. However the functionally and semantically equivalent MyLayout1 (also substitutable) does work.
Question: Is the fact that, the types substitutable to Layout, one works and the other does not a design? If so, what's the reasoning? or is it a bug? or is it because Typescript lacking features to better declare the recursive type?, etc.
I understood you the first time. It is not the nature of that particular error I'm reporting about. Please stop going there like in your previous examples with fooey, otherwise the comments on this issue will get very confusing as to what is tried to be discussed. No need to explain it again. Not a support forum after all.
I understood you the first time. It is not the nature of that particular error I'm reporting about.
Then I have no clue what you want me to say. This particular error is the only thing preventing your code as written from working, so if you already understand why that error happens, there is no further question to answer. There is no bug here; your code will need to be refactored.
well.. this seems like the answer is the question itself and we'll just keep going around.
nevermind then, maybe it's just that Typescript lacks more advanced support for doing something like this and I mistook it for a fault in design (what I tried to report).
because, bottom line, from certain point of view it doesn't make sense, it's like there's no way to guard an interface from being something like a JSON-like object (not containing functions or classes).
and it certainly feels like the fact that it works declaring it as a type alias is just a fluke. It's not consistent.
but with you guys just focused around the technicality of the index signature, this is going nowhere. I'm just closing then because there's no deeper insight being took.
but with you guys just focused around the technicality of the index signature
Because that’s literally the source of the behavior you’re questioning. You are correct that it is not consistent, but the inconsistency is by design: the type alias working there is convenient, but not safe in general, so you can use interface to get stricter behavior.
You will find many inconsistencies like this in TypeScript, because soundness is not a design goal. The design of the type system is largely guided by pragmatism in practice.
Great. I accept the response of convenience making the inconsistencies by design. That was what my report was about.
also for posterity:
functionfn(x: Layout){x.foo=123;// shouldn't happen if x type not strictly equal to Layout}constx: MyLayout1={outer: [{inner: ''}]};fn(x);// should not be possible. a generic <T extends Layout> should be required.
The reason: convenience and practicality. Instead of making generics more difficult, let's just have it work with type alias but not with interfaces through the feature of index signatures.
Activity
fatcerberus commentedon Jul 1, 2022
The error has nothing to do with the type being recursive or not. It happens because plain
{ ... }object types have an implied index signature (this is what allows you to assign object literals toRecordtypes), while interfaces are only assignable to index-signature types if the interface has a compatible index signature itself. The error message tells you exactly what's wrong:This is in fact part of the "better error reporting" for interfaces that you allude to. 😉
miguel-leon commentedon Jul 1, 2022
Alright, thank you for the explanation Bruce Pascoe (@fatcerberus).
However, the center of the issue is the ability to have a generic parameter extend a recursive type.
Does the actual reason means that interfaces can't be used as arguments to these types of generic?
Or does it means that having a generic extend a recursive type is not supported, is not a thing?
Is the consequence for this error intended or is it a bug? If so, what is the intended difference? Does it have sense to have this error here?
You understand what I mean?
fatcerberus commentedon Jul 1, 2022
Like I said, it has nothing to do with the recursion and everything to do with the index signature in
Layout. IfLayoutwere instead{ foo: Primitive | Array<Primitive | Layout> | Layout }with corresponding types forMyLayout1andMyLayout2then the interface/POJO distinction wouldn't matter.But yes, if your generic is constrained to a type with an index signature, then you can't use interfaces with it unless they also have a compatible index signature.
miguel-leon commentedon Jul 1, 2022
It's ok. I understand what you're saying, and I'm sorry if my example feels like I'm asking about that particular error and nothing more. It just happened to be the one that I could use because of the semantics.
But as I just mentioned, that is not the real issue I'm trying to highlight. It's what's behind, the consequence. I don't know how else to put it. The developer intention (because it seems to work as would be the intention using the type alias).
My questions remain unanswered.
Thanks anyway.
fatcerberus commentedon Jul 1, 2022
Then I have absolutely no idea what you're asking, because the error you're pointing out is due to the lack of index signature in an interface and has nothing at all to do with your generic type being recursive.
The problem you're running into boils down to this...
...which, yes, is by design.
fatcerberus commentedon Jul 1, 2022
For the record: If your real question here wasn't about the specific error and was more of "how do I do this?" - this is not a support forum.
somebody1234 commentedon Jul 1, 2022
dropping these here in case they are useful:
Playground with recursive interface
Playground with error fixed by simply making interface extend Layout
miguel-leon commentedon Jul 1, 2022
You sound a little bit annoyed by not being able to understand and now implying I'm misusing this place as a support forum.
So let me break it down and try to explain it the simplest I can in steps.
I'm putting a generic argument to guard for (extends) a recursive type, which itself is "generic" in the sense that their keys are whatever string (not symbols, not numbers).
The (only) way to declare it is by having to use the "index signature" (for better or for worst, that's not the point).
I then have a type
MyLayout2that semantically complies with this recursive type (in other words, it is substitutable).But as you already know, it doesn't work. However the functionally and semantically equivalent
MyLayout1(also substitutable) does work.Layout, one works and the other does not a design? If so, what's the reasoning? or is it a bug? or is it because Typescript lacking features to better declare the recursive type?, etc.I understood you the first time. It is not the nature of that particular error I'm reporting about. Please stop going there like in your previous examples with fooey, otherwise the comments on this issue will get very confusing as to what is tried to be discussed. No need to explain it again. Not a support forum after all.
Thank you.
somebody1234 commentedon Jul 1, 2022
except it doesnt semantically comply.
Layoutrequires arbitrary keys to be valid,MyLayout2does not, henceMyLayout2is not substitutable.fatcerberus commentedon Jul 1, 2022
Then I have no clue what you want me to say. This particular error is the only thing preventing your code as written from working, so if you already understand why that error happens, there is no further question to answer. There is no bug here; your code will need to be refactored.
miguel-leon commentedon Jul 1, 2022
well.. this seems like the answer is the question itself and we'll just keep going around.
nevermind then, maybe it's just that Typescript lacks more advanced support for doing something like this and I mistook it for a fault in design (what I tried to report).
because, bottom line, from certain point of view it doesn't make sense, it's like there's no way to guard an interface from being something like a JSON-like object (not containing functions or classes).
and it certainly feels like the fact that it works declaring it as a type alias is just a fluke. It's not consistent.
but with you guys just focused around the technicality of the index signature, this is going nowhere. I'm just closing then because there's no deeper insight being took.
thank you.
fatcerberus commentedon Jul 1, 2022
Because that’s literally the source of the behavior you’re questioning. You are correct that it is not consistent, but the inconsistency is by design: the type alias working there is convenient, but not safe in general, so you can use
interfaceto get stricter behavior.You will find many inconsistencies like this in TypeScript, because soundness is not a design goal. The design of the type system is largely guided by pragmatism in practice.
somebody1234 commentedon Jul 1, 2022
for posterity: the index signature is part of the type, not a technicality. and so this is 100% equivalent to saying this:
miguel-leon commentedon Jul 1, 2022
Great. I accept the response of convenience making the inconsistencies by design. That was what my report was about.
also for posterity:
The reason: convenience and practicality. Instead of making generics more difficult, let's just have it work with type alias but not with interfaces through the feature of index signatures.