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
Record key type mismatch is not flagged as error #51680
functionget_value(): Record<number,string>{return{}}functionmain(){constghi : Record<string,string>=get_value()// should be an errorconsole.log(ghi)}main()
🙁 Actual behavior
Assignment to ghi from get_value is accepted as valid.
🙂 Expected behavior
Assigned to ghi should be an error as the key types do not match.
Record takes a Key type, implying it is significant. But what you are saying is that the Key type is effectively irrelevant. If TypeScript is meant to treat these types as equivalent, then why would Key be specified in Record<Key, Value>. Are you suggesting that Key is not part of the structural type?
This also conflicts with the ability to produce compatible types where assigned is rejected. For example, in a tagged type:
export interface nominal<T> {
"nominal structural brand": T
}
namespace refType {
export enum rtFieldRefIdStr {}
}
export type FieldRefIdStr = nominal<refType.rtFieldRefIdStr> & string
const ref : FieldRefIdStr = "example" // this is rejected
The assignment to ref is rejected as it isn't the same type, but it is definitely a compatible type if we're thinking about runtime structure.
The type of the constant data-structure is compatible with the Record type, thus the assignment should be allowed, but it is not. In this case it knows the types are not compatible.
That's the excess property check, which is more of a linter feature than part of the type system. It only happens for object literals, and it's a common case of confusion. When the object is returned by a function it's assignable again.
But you can see how that check is a valuable check and would be desired for the assignment in my original example? It does not imply anything about the runtime structure; detecting it does not lie about any types.
The assignment to ref is rejected as it isn't the same type, but it is definitely a compatible type if we're thinking about runtime structure.
The runtime structure doesn't matter, because TypeScripts types are compile time only. The string literal is not compatible, because it's missing the property that the target type requires.
But you can see how that check is a valuable check and would be desired for the assignment in my original example?
Sure. That's why the issue #12936 exists. But until that feature is implemented (if ever) you have to deal with what's supported right now. The behavior you describe in your issue is not a bug.
I suppose I'm objecting to the fact that the Key in record is not part of its structural type. The language is effectively lying that it supports a Key parameter at all in that case.
Again: TypeScript is structurally typed. If the structure matches, it's compatible. The language is not lying to you. If the type is not compatible anymore you get an error. For example Record<"a" | "b", number> is not assignable to Record<string, string>, because the first type says "the values of the keys a and b are of type number", whereas the latter type says "all my values no matter the key are of the type string".
In your initial example you have a type that says "all my number-like properties must be typed string", which is compatible with a type saying "all my properties are typed string".
This is purely a misunderstanding on your side on how the language works. It frequently happens with people more used to languages that are nominally typed.
Bit of a tangent here, but this is why you should never write generics that don't use their type parameter.
typeFoo<T>={foo: string};
Any Foo<T> will be assignable to any other Foo<T> because it produces the same type no matter what T is. Under structural typing what matters for assignability isn't that you provided a different type parameter, but how that type parameter is used. This is why I prefer to think of generics in TypeScript as "type functions" rather than types.
Activity
MartinJohns commentedon Nov 29, 2022
TypeScript is structurally typed. These two types are compatible, so the assignment is fine.
You can't represent a type that only had number-like properties in TypeScript. This would require "exact types".
edaqa-uncountable commentedon Nov 29, 2022
Record takes a
Keytype, implying it is significant. But what you are saying is that theKeytype is effectively irrelevant. If TypeScript is meant to treat these types as equivalent, then why wouldKeybe specified inRecord<Key, Value>. Are you suggesting thatKeyis not part of the structural type?This also conflicts with the ability to produce compatible types where assigned is rejected. For example, in a tagged type:
The assignment to
refis rejected as it isn't the same type, but it is definitely a compatible type if we're thinking about runtime structure.edaqa-uncountable commentedon Nov 29, 2022
This modified example from the documentation also counteracts the claim that Key is not part of the structure.
The type of the constant data-structure is compatible with the Record type, thus the assignment should be allowed, but it is not. In this case it knows the types are not compatible.
MartinJohns commentedon Nov 29, 2022
That's the excess property check, which is more of a linter feature than part of the type system. It only happens for object literals, and it's a common case of confusion. When the object is returned by a function it's assignable again.
edaqa-uncountable commentedon Nov 29, 2022
But you can see how that check is a valuable check and would be desired for the assignment in my original example? It does not imply anything about the runtime structure; detecting it does not lie about any types.
MartinJohns commentedon Nov 29, 2022
The runtime structure doesn't matter, because TypeScripts types are compile time only. The string literal is not compatible, because it's missing the property that the target type requires.
MartinJohns commentedon Nov 29, 2022
Sure. That's why the issue #12936 exists. But until that feature is implemented (if ever) you have to deal with what's supported right now. The behavior you describe in your issue is not a bug.
edaqa-uncountable commentedon Nov 29, 2022
I suppose I'm objecting to the fact that the
Keyin record is not part of its structural type. The language is effectively lying that it supports a Key parameter at all in that case.MartinJohns commentedon Nov 29, 2022
Again: TypeScript is structurally typed. If the structure matches, it's compatible. The language is not lying to you. If the type is not compatible anymore you get an error. For example
Record<"a" | "b", number>is not assignable toRecord<string, string>, because the first type says "the values of the keys a and b are of type number", whereas the latter type says "all my values no matter the key are of the type string".In your initial example you have a type that says "all my number-like properties must be typed string", which is compatible with a type saying "all my properties are typed string".
This is purely a misunderstanding on your side on how the language works. It frequently happens with people more used to languages that are nominally typed.
See also the discussion in #48906.
edaqa-uncountable commentedon Nov 29, 2022
In that sense, is it valid to define a strict type as follows, since it would be structurally incompatible with any other StrictRecord:
Used in place of Record in the example it produces the desired error.
This should be structurally exclusive, since any other KeyT would produce an incompatible "__keyType" declaration.
MartinJohns commentedon Nov 29, 2022
That's one of the common approaches to enforce more nominal typing, yes. See also #202.
fatcerberus commentedon Nov 29, 2022
Bit of a tangent here, but this is why you should never write generics that don't use their type parameter.
Any
Foo<T>will be assignable to any otherFoo<T>because it produces the same type no matter whatTis. Under structural typing what matters for assignability isn't that you provided a different type parameter, but how that type parameter is used. This is why I prefer to think of generics in TypeScript as "type functions" rather than types.typescript-bot commentedon Dec 2, 2022
This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.