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
Exact Types #12936
Comments
I would suggest the syntax is arguable here. Since TypeScript now allows leading pipe for union type. class B {}
type A = | number |
B Compiles now and is equivalent to I think this might not I expect if exact type is introduced. |
Not sure if realted but FYI #7481 |
If the type Exact<T> = {|
[P in keyof T]: P[T]
|} and then you could write |
This is probably the last thing I miss from Flow, compared to TypeScript. The |
@HerringtonDarkholme Thanks. My initial issue has mentioned that, but I omitted it in the end as someone would have a better syntax anyway, turns out they do 😄 @DanielRosenwasser That looks a lot more reasonable, thanks! @wallverb I don't think so, though I'd also like to see that feature exist 😄 |
What if I want to express a union of types, where some of them are exact, and some of them are not? The suggested syntax would make it error-prone and difficult to read, even If extra attention is given for spacing: |Type1| | |Type2| | Type3 | |Type4| | Type5 | |Type6| Can you quickly tell which members of the union are not exact? And without the careful spacing? |Type1|||Type2||Type3||Type4||Type5||Type6| (answer: |
@rotemdan See the above answer, there's the generic type |
There's also the concern of how it would look in editor hints, preview popups and compiler messages. Type aliases currently just "flatten" to raw type expressions. The alias is not preserved so the incomperhensible expressions would still appear in the editor, unless some special measures are applied to counteract that. I find it hard to believe this syntax was accepted into a programming language like Flow, which does have unions with the same syntax as Typescript. To me it doesn't seem wise to introduce a flawed syntax that is fundamentally in conflict with existing syntax and then try very hard to "cover" it. One interesting (amusing?) alternative is to use a modifier like function test(a: only string, b: only User) {}; That was the best syntax I could find back then. Edit: function test(a: just string, b: just User) {}; (Edit: now that I recall that syntax was originally for a modifier for nominal types, but I guess it doesn't really matter.. The two concepts are close enough so these keywords might also work here) |
I was wondering, maybe both keywords could be introduced to describe two slightly different types of matching:
Nominal matching could be seen as an even "stricter" version of exact structural matching. It would mean that not only the type has to be structurally identical, the value itself must be associated with the exact same type identifier as specified. This may or may not support type aliases, in addition to interfaces and classes. I personally don't believe the subtle difference would create that much confusion, though I feel it is up to the Typescript team to decide if the concept of a nominal modifier like (Edit: just a note about |
@ethanresnick Why do you believe that? |
This would be exceedingly useful in the codebase I'm working on right now. If this was already part of the language then I wouldn't have spent today tracking down an error. (Perhaps other errors but not this particular error 😉) |
I don't like the pipe syntax inspired by Flow. Something like exact interface Foo {} |
@mohsen1 I'm sure most people would use the |
With I think interface Foo {}
type Bar = exact Foo |
Exceedingly helpful for things that work over databases or network calls to databases or SDKs like AWS SDK which take objects with all optional properties as additional data gets silently ignored and can lead to hard to very hard to find bugs 🌹 |
@mohsen1 That question seems irrelevant to the syntax, since the same question still exists using the keyword approach. Personally, I don't have a preferred answer and would have to play with existing expectations to answer it - but my initial reaction is that it shouldn't matter whether The usage of an |
We talked about this for quite a while. I'll try to summarize the discussion. Excess Property CheckingExact types are just a way to detect extra properties. The demand for exact types dropped off a lot when we initially implemented excess property checking (EPC). EPC was probably the biggest breaking change we've taken but it has paid off; almost immediately we got bugs when EPC didn't detect an excess property. For the most part where people want exact types, we'd prefer to fix that by making EPC smarter. A key area here is when the target type is a union type - we want to just take this as a bug fix (EPC should work here but it's just not implemented yet). All-optional typesRelated to EPC is the problem of all-optional types (which I call "weak" types). Most likely, all weak types would want to be exact. We should just implement weak type detection (#7485 / #3842); the only blocker here is intersection types which require some extra complexity in implementation. Whose type is exact?The first major problem we see with exact types is that it's really unclear which types should be marked exact. At one end of the spectrum, you have functions which will literally throw an exception (or otherwise do bad things) if given an object with an own-key outside of some fixed domain. These are few and far between (I can't name an example from memory). In the middle, there are functions which silently ignore Clearly the "will throw if given extra data" functions should be marked as accepting exact types. But what about the middle? People will likely disagree. Violations of Assumptions / Instantiation ProblemsWe have some basic tenets that exact types would invalidate. For example, it's assumed that a type It's also assumed that MiscellanyWhat is the meaning of Often exact types are desired where what you really want is an "auto-disjointed" union. In other words, you might have an API that can accept Summary: Use Cases NeededOur hopeful diagnosis is that this is, outside of the relatively few truly-closed APIs, an XY Problem solution. Wherever possible we should use EPC to detect "bad" properties. So if you have a problem and you think exact types are the right solution, please describe the original problem here so we can compose a catalog of patterns and see if there are other solutions which would be less invasive/confusing. |
A tweet from @sebmarkbage (React core contributor)
|
@lazytype FYI, that TS Playground link is broken. It's news to me, but apparently without the |
Adding my use-case: TypeScript should be lighting up type Filter = { id: string }
type MakeFilter = () => Filter
function create() {
return (fn: MakeFilter) => () => {};
}
const make = create()
// const make: (fn: MakeFilter) => () => void
const voidFn = make(() => ({ id: "asdf", foo: "bar" })) Moot, but |
@TylerR909 That would be an excess property checking bug. Exact types wouldn't (and shouldn't) fix that. You'd be better off filing a new issue with that. |
Вынес свойства которые не используются в NativeSelect Не уверен что можно что-то сейчас сделать на уровне типизации, microsoft/TypeScript#12936 - Fixes #4378
Вынес свойства которые не используются в NativeSelect Не уверен что можно что-то сейчас сделать на уровне типизации, microsoft/TypeScript#12936 - Fixes #4378
Вынес свойства которые не используются в NativeSelect Не уверен что можно что-то сейчас сделать на уровне типизации, microsoft/TypeScript#12936 - Fixes #4378
Label Any news, or comments, in 2023? |
I want to add my use case here, it deals with leaning on the TS type checker for safely transforming complex data types. I'll illustrate the bug I had with a simpler example of the type of transformation that's tricky: type Type1 = {
A: string
B: string
}
type Type2 = {
a: number
b: number
}
const transform = (obj: Type1): Type2 => {
const renamed = renameKeys(obj)
const transformed = convertValues(renamed)
return transformed
}
const renameKeys = (obj:Type1) => {
return {
a: obj.A,
B: obj.B,
}
}
const convertValues = (obj: ReturnType<typeof renameKeys>) => {
return {
...obj,
a: parseInt(obj.a,10),
b: parseInt(obj.B,10),
}
}
console.log(transform({ A: '1', B: '2'})) The transform function has a simple input type and return type, and this code has no type errors but prints, surprisingly:
As you can see, I accidentally mis-typed the The only way to prevent this is to remember to do a stricter runtime check with something like zod which actually allows me to opt-in to exact checking with the type Type1 = {
A: string
B: string
}
const Type2Schema = z.object({
a: z.number(),
b: z.number(),
}).strict()
type Type2 = z.infer<typeof Type2Schema>
const transform = (obj: Type1): Type2 => {
const renamed = renameKeys(obj)
const transformed = convertValues(renamed)
return Type2Schema.parse(transformed)
}
const renameKeys = (obj:Type1) => {
return {
a: obj.A,
B: obj.B,
}
}
const convertValues = (obj: ReturnType<typeof renameKeys>) => {
return {
...obj,
a: parseInt(obj.a,10),
b: parseInt(obj.B,10),
}
}
console.log(transform({ A: '1', B: '2'})) // <-- now throws an ZodError due to existence of `B` key. It works, but hard to remember exactly when I need to deploy this pattern. I know I could deploy intermediary types here in the transform flow, but that's something that's really cumbersome when the data gets complex and we want to decompose our transformations. I also don't think EPC can help me here, unless I'm missing something. |
The way I've managed tracking which objects have been validated as exact is to just tag them. type Exact<T> = T & {readonly _exact: unique symbol }
function toExact<T extends {}>(obj: T, allowedKeys: (keyof T)[]): Exact<T> {
const r = {} as Exact<T>
allowedKeys.forEach(key => {
(r[key] as any) = obj[key]
})
return r
} Then a function can just take a parameter of type |
This is a proposal to enable a syntax for exact types. A similar feature can be seen in Flow (https://flowtype.org/docs/objects.html#exact-object-types), but I would like to propose it as a feature used for type literals and not interfaces. The specific syntax I'd propose using is the pipe (which almost mirrors the Flow implementation, but it should surround the type statement), as it's familiar as the mathematical absolute syntax.
This syntax change would be a new feature and affect new definition files being written if used as a parameter or exposed type. This syntax could be combined with other more complex types.
Apologies in advance if this is a duplicate, I could not seem to find the right keywords to find any duplicates of this feature.
Edit: This post was updated to use the preferred syntax proposal mentioned at #12936 (comment), which encompasses using a simpler syntax with a generic type to enable usage in expressions.
The text was updated successfully, but these errors were encountered: