-
Notifications
You must be signed in to change notification settings - Fork 0
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
check
with explicit type signature gives error on is.object.matching
with a partial object
#17
Comments
The problem is rather complex and involves how types are inferred in typescript. I've been fighting with this quite a lot so I'll write here my current findings so that I can refer to them later when I'll fight against this again. The final goal would be something like this: let obj :DaInterface = {
a:1
b:2,
c:3,
d:4
};
check(obj, is.object.matching({
a: 1, // This is a number so it obviously matches
b: is.number, // This is a matcher to a number,
c: is.string, // This should give compile time error, in no case c can be a string
//d is missing and it's ok, we don't want to check it
xx :is.string, // This should give compile time error, in no case xx is allowed to exist
zz: is.undefined, // This should NOT give error, we want to explicitly check that something is not set
}); So, the requirements are:
Let's start from how
Given that, a naive implementation of objectMatching could be:
This immediately resolves requirements 1,2,3 and 4 above. We can support also requirement 5 by creating an "unwrapping" type, that unwraps the matchers into the corresponding base type, which is currently implemented as OrigObj and I'll ignore it in the following discussion. Other requirements are however mostly broken. Let's start with requirement 6, that not all keys must be specified, for in
However, the case above is not what usually happens (sometimes yer, sometimes no), what happens is slightly different. Typescript does not propagate the type from the For example:
Is interpreted as:
So, since (and in recent typescript, it will interpret it as Curiously, also requirement 7 is broken, cause unknown keys can be freely specified:
The matcher type argument can be casted into DaInterface, cause it has a,b,c etc.. and the fact that is also has To work around these two problems, I implemented a type called ObjMatch that is sort of a partial, and allows for other keys as long as they are Matcher. Since typescript type parameters are all bivariant in strange ways, Matcher is a valid Matcher, so any key can be specified with any value, so de-facto requirement 7 is not there. But at least requirement 6 is, sort-of. It is satisfied when the Matcher returned is of the right type, otherwise no matter what we do with the signature of the parameter of objectMatching, the returned value will still me something like Notice that when not using interfaces, the situations changes even more. As briefly mentioned above, types that typescript is creating automatically out of object literals are stricter than those declared using interfaces and types. So, as of today, there is no way to obtain all the above requirements.
It essentially satisfies requirement 6 (you can skip some properties) and 7 (no extraneous ones), until you don't check all the properties, in which case 6 is useless and 7 breaks for whatever reason. Notice that in the example above also intellisense in VSCode works correctly. However, in the wild, it does not work so smoothly. In more complex cases, the above syntax does not have the same properties. It usually complains about missing properties, while in the examples above the type being simpler and the properties being explicitly missing .. it still compiles properly. Instead using:
It at the moment the best option. It forces the Matcher type, and for some reason this "early cast" works most of the times. It even works with:
Which means the problem is somehow in the covariant/cotravariant nature of typescript type parameters. This means that it could be fixed once microsoft/TypeScript#48240 gets into the language correctly. |
The reason it works is that it propagates the type back!
It resolves to:
So, it then boils down on how much INSTEAD
resolves to:
So, instead of enforcing a cast "from check to the argument", it reinterprets the check signature in a way that works. And in this case it works correctly because:
So, to keep most of the requirements without having to specify the type explicitly, we need to find a way to invert the resolution of the type parameters, if it's possible, forcing the objectMatching to accept the "incoming" type parameter from the outer |
The The reason being that while in the single |
I've extended the use of RecursivePartial, now the situation is:
Does not give error for missing elements (before it did), so it satisfies requirement 6. However intellisense does not work, cause it changes everything to
Works correctly, has intellisense, but allows for everything to be written in, cause
Is still the best option, cause it has intellisense, allows partials (requirement 6), gives errors on unknown (requirement 7), except that it allows unknown with matchers cause |
No description provided.
The text was updated successfully, but these errors were encountered: