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
Add Exact
type
#259
Add Exact
type
#259
Conversation
Sounds like a useful type. Please follow: https://github.com/sindresorhus/type-fest/blob/main/.github/contributing.md#submitting-a-new-type |
I think |
The type description and docs text needs more work. |
readme.md
Outdated
@@ -124,6 +124,7 @@ Click the type names for complete docs. | |||
- [`Includes`](source/includes.d.ts) - Returns a boolean for whether the given array includes the given item. | |||
- [`Simplify`](source/simplify.d.ts) - Flatten the type output to improve type hints shown in editors. | |||
- [`Jsonify`](source/jsonify.d.ts) - Transform a type to one that is assignable to the `JsonValue` type. | |||
- [`Exactly`](source/exactly.d.ts) - Create a guard for function to reject excess properties. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The type is not just about guarding functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can only think of guarding a function because it requires input type from a function input. Could you suggest other usages?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The descriptions describes one of the uses of the type, but the description should describe what it does.
source/exactly.d.ts
Outdated
@@ -0,0 +1,45 @@ | |||
import {Primitive} from './primitive'; | |||
|
|||
type KeysOfUnion<T> = T extends T ? keyof T : never; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs a short description.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
description added.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This type could be moved into internal.d.ts
as it could be useful for other types here too.
Looks like this partly fixes: #156 |
Thanks @sindresorhus I'll review the other implementation in the SO link u suggested. |
ProgressReviewing another |
Hi @sindresorhus sorry I finally have some time to pick up the work on this again. I have reviewed the solution in this SO post and surprisingly the concept is exactly the same as my solution in this PR. So the solution is basically "Find all excess properties from the input type and mark them as never." Are you suggesting you prefer to use the name from the SO post above? |
I have tried to rewrite tests with I have also verified that the tests are executing correctly when running Do you help review and see what else should be improved? Much appreciated. |
No, |
|
readme.md
Outdated
@@ -124,6 +124,7 @@ Click the type names for complete docs. | |||
- [`Includes`](source/includes.d.ts) - Returns a boolean for whether the given array includes the given item. | |||
- [`Simplify`](source/simplify.d.ts) - Flatten the type output to improve type hints shown in editors. | |||
- [`Jsonify`](source/jsonify.d.ts) - Transform a type to one that is assignable to the `JsonValue` type. | |||
- [`Exact`](source/exact.d.ts) - Create a type from type A and B and convert all keys exclusive to type B as `never`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the code review @sindresorhus
I have updated the description to describe what it does instead of what it can do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are now describing the actual logic of the code. The "what it does" needs to be more high level. Look at the other descriptions for inspiration.
I'll try my best. Currently trying to review #156 |
source/exactly.d.ts
Outdated
@@ -0,0 +1,45 @@ | |||
import {Primitive} from './primitive'; | |||
|
|||
type KeysOfUnion<T> = T extends T ? keyof T : never; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This type could be moved into internal.d.ts
as it could be useful for other types here too.
source/exact.d.ts
Outdated
Create a type from type A and B and convert all keys exclusive to type B as `never`. | ||
|
||
This is useful when function wants to guard the arguments to only accept the | ||
exact defined keys and reject any excess properties. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't hard-wrap.
readme.md
Outdated
@@ -124,6 +124,7 @@ Click the type names for complete docs. | |||
- [`Includes`](source/includes.d.ts) - Returns a boolean for whether the given array includes the given item. | |||
- [`Simplify`](source/simplify.d.ts) - Flatten the type output to improve type hints shown in editors. | |||
- [`Jsonify`](source/jsonify.d.ts) - Transform a type to one that is assignable to the `JsonValue` type. | |||
- [`Exact`](source/exact.d.ts) - Create a type from type A and B and convert all keys exclusive to type B as `never`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are now describing the actual logic of the code. The "what it does" needs to be more high level. Look at the other descriptions for inspiration.
Friendly bump |
Exact creates a type from type A and B and changes keys exclusive to type B to `never`. This is useful for function type-guarding to reject arguments with excess
@sindresorhus can you give an idea when this will be merged/released? |
No, but if you want to see it released, you can help review. |
I do intend to merge this, but it still requires many docs improvements, and I don't have to do them right now. |
Hi @sindresorhus do you mind help point out which doc i can improve on, i will do it asap. e.g.
the following comments have been addressed but if it still requires more improvement, pls let me know
|
You are still describing the code. I think the Stack Overflow answers describes it more clearly:
|
source/exact.d.ts
Outdated
- take both the preferred type and actual provided type as input. | ||
- generates the list of keys that exist in the provided type but not in the | ||
defined type. | ||
- mark these excess keys as `never` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be an internal code comment, not in the doc comment.
source/internal.d.ts
Outdated
@@ -42,3 +42,12 @@ export type Subtract<A extends number, B extends number> = BuildTuple<A> extends | |||
Matches any primitive, `Date`, or `RegExp` value. | |||
*/ | |||
export type BuiltIns = Primitive | Date | RegExp; | |||
|
|||
/** | |||
Returns the accessible keys that also works for union type. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No idea what this is trying to say. But from reading the SO answer, I guess you meant something like this:
Get all the keys from types in a union type.
source/exact.d.ts
Outdated
|
||
function onlyAcceptName(args: OnlyAcceptName) {} | ||
|
||
// TypeScript complains this because it's an object literal. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure which word has typo but I rewrite this sentence and hopefully this makes more sense.
I have updated the other comments as suggested.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would recommend using a grammar checker like Grammarly. It would have caught this.
29f3df0
to
08de69b
Compare
I finally had time to look at this. Thanks ;) |
I think theres a gap in this implementation where it wont search for the exactness of types in arrays. Something like this might work
But I don't know if this is 100% correct |
@adam-thomas-privitar Do you mind create a PR for this? I think it can be verified by a test. |
hey @adam-thomas-privitar and @zorji. Sorry first time here I was playing around with the suggested update but it seems like there's a circular constraint issue. Updated change:
The only significant change that I can see is I'm matching on also testing by passing in a variable: Testing with matching on Also checking the exactness of array types wouldn't work in the above scenario |
Hi @JasonShin @adam-thomas-privitar Do you have a case where the type does not work? I am trying it with array and it looks like it's working for me. import { Exact } from 'type-fest'
type Type = Array<{ code: string; name: string }>
const fn = <T extends Exact<Type, T>>(args: T) => args
// correct input
fn([{
code: 'test',
name: '1'
}])
// missing field
fn([{
code: 'test'
}])
// incorrect type
fn([{
code: 'test',
name: 1
}])
// excess field
fn([{
code: 'test',
name: '1',
excessField: '',
}]) Also, I would suggest open a new issue and link to this PR and provide the minimal reproduce code. |
@zorji Thanks, I was playing with the type more I can only reproduce in the following weird intersection type type Type = Array<{ test: string }> & Array<{ z: number }>
// Can't construct Exact type with errors saying it's trying to also match on array method 'pop' and etc etc
const fn = <T extends Exact<Type, T>>(args: T) => args;
// correct input
fn([{
code: 'test',
name: '1'
}])
// missing field
fn([{
code: 'test'
}])
// incorrect type
fn([{
code: 'test',
name: 1
}])
// excess field
fn([{
code: 'test',
name: '1',
excessField: '',
}]) But as per https://www.reddit.com/r/typescript/comments/m5wvrx/how_to_extract_proper_intersection_type_from_an/ and microsoft/TypeScript#43267 this is a very weird type. Workaround for those who have control over their typing would be writing the intersection type type Type = ({ code: string } & { name: string })[]
const fn = <T extends Exact<Type, T>>(args: T) => args;
// correct input
fn([{
code: 'test',
name: '1'
}])
// missing field
fn([{
code: 'test'
}])
// incorrect type
fn([{
code: 'test',
name: 1
}])
// excess field
fn([{
code: 'test',
name: '1',
excessField: '',
}]) The only problem would be if the user doesn't have control over the underlying module's type declaration but they are providing the weird intersection type that goes I don't think @adam-thomas-privitar's suggestion fixes this issue as well mmm I will still raise an issue to see if anyone thinks this is important Issue link: #419 |
Exact
is a type to allow a function to reject excess properties.TypeScript only performs excess property check on object literal only. (see explanation https://stackoverflow.com/a/62358260)
i.e. it does not complain when you assign an object that has more fields than the required type except when it's an object literal.
Here is the demo code
To have a better type check, we can apply this
Exact<T>
to the above demo (inspired from https://stackoverflow.com/a/59230299)My implementation is a deep
Exactly
and also useKeysOfUnion
from https://stackoverflow.com/a/49402091The idea is to find all excess properties and mark them as
never
.