-
Notifications
You must be signed in to change notification settings - Fork 662
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
Extra strict method arguments #496
Comments
This is quite the ambitious proposal, I like it! In spirit, I'm totally aligned with the vision of providing useful static analysis checks that save our users the round-trip to the docs in order to understand which combinations of arguments are valid, and which aren't. The intention to make the argument types as self-describing as possible is why I left comments like the following in the code: Line 200 in c49ba78
I think the ideas outlined here serve that vision very well. I have a few concerns, but i think they are addressable:
Happy to continue this discussion! When we have a specification for the work we actually want to merge, lets start a new |
|
some thoughts:
This is an interesting proposal. I like it, but I have a competing idea. Here's the challenge:
I think these could be all the use cases we want. for an ordinary user, you'll just use the first form. If you're an expert you'll either use the second or third form. The second form works if you have some knowledge of a hidden argument but you still want the safety of the type system to do some work for you. The third form works if you have some knowledge of a hidden method so basically all bets are off.
Even if we utilize the What I am pretty sure of, is that we are highly unlikely to get that kind of detailed specification out of Slack's official OpenAPI spec. It's just too intricate and it would be a nightmare to maintain (given the knowledge I have about how that schema is built). So I think the logical next step is to explore/prototype a sort of 2-pass process where we could start with the OpenAPI spec and decorate/modify the types it gives us with as much information as we can about those object shapes.
Wow, the MessageAttachment is probably one of the most complicated structures in the entire API. Kudos to you for taking it on to describe it. I still think the way you've implemented the type is hard to read, and therefore error prone. Let's take this part for example:
I don't think this is accurate. The docs say
simplify, which requires memorizing the identity rules for the type system. i believe
well now these two types completely overlap and it reduces further
I think we could fix this by instead using:
I can't even be sure this is right until i evaluate it with the rest of the expression. The concern is that it took that many mental steps for a reader to figure just this part out, with quite a bit of set theory and type system knowledge memorized (or looked up). And I'm not even done. On top of that concern, do we even know what the Intellisense completion offers when the structure is that complicated? If the real-time type hints show me that entire blob on top of my cursor, I'm likely to be more confused about what is valid than the structure we currently have. Maybe this can be mitigated by giving the sub-expressions (things inside the |
GenericsI actually originally wanted a way to express these constraints as generics originally (possibly different than what @aoberoi meant by "generics") but I couldn't remember a way to use the keys of a generic type. Well, I just remembered how (there's this lovely operator called // Makes all keys of T of type `never`
type Not<T> = { [K in keyof T]: never; };
// Requires matching exactly one of A or B
type OneOf<A, B> = (A & Partial<Not<B>>) | (B & Partial<Not<A>>);
// Requires matching at most one of A or B
type MaxOneOf<A, B> = Partial<OneOf<A, B>>;
// Requires matching at least one of A or B
type MinOneOf<A, B> = (A & Partial<B>) | (B & Partial<A>);
// Requires all properties of Required to be present before any properties of Dependent can be present.
type Requires<Required, Dependent> = (Required & Partial<Dependent>) | Partial<Required & Not<Dependent>>; Here's a rewrite of my previous export type MessageAttachment = {
color?: string;
pretext?: string;
actions?: MessageAttachmentAction[];
fields?: (
{ short?: boolean; } &
MinOneOf<{ title: string; }, { value: string; }>
)[];
ts?: string | number;
} &
MinOneOf<{ fallback: string; }, { text: string; }> &
Requires<{ author_name: string; }, { author_link: string; author_icon: string; }> &
Requires<{ title: string; }, { title_link: string; }> &
MaxOneOf<{ image_url: string; }, { thumb_url: string; }> &
Requires<{ footer: string; }, { footer_icon: string; }>;
I think these generics (which I wrote yesterday, funny enough) should help the point of readability, so long as the names of these generics make sense. Names are hard; feel free to change the ones I chose if there's something you feel makes more sense (especially IntellisenseI'm at a bit of a crossroads on this one. When hovering over the On the other hand, the auto-complete intellisense essentially shows the real type of all arguments: I feel like this outcome would be my choice as a user as I'd like to be able to see all arguments and their types here without having to check the API docs.
I'd also like to draw some attention to the TypeScript Playground. This tool is super useful for testing out these types and making sure they do/don't throw errors. Further, this playground is built on Monaco Editor, the web-based editor that powers VS Code (complete with TypeScript intellisense already set up), which is where I expect most (obviously not all) TypeScript development to happen. |
Wow, the generics example is very interesting, and does go a long way to improve the readability. They seem so useful that I'd almost suggest publishing them in a dedicated package, with tests to prove their properties, and then we may take that package as a dependency. Also, thanks for investigating the Intellisense capabilities. I agree that the hover-over hints are not ideal, so I think we should provide some feedback to Microsoft about that. ( |
We are exploring the possibility of solving this by generating types from a common schema or specification. |
👋 It looks like this issue has been open for 30 days with no activity. We'll mark this as stale for now, and wait 10 days for an update or for further comment before closing this issue out. |
web-api v7 should have addressed this issue. |
Description
On the topic of method arguments interfaces, thoughts on possibly making those interfaces extra strong by attempting to ensure that certain combinations of arguments are valid?
For example, one could say the following call to
chat.postMessage
is technically incorrect:Because passing
as_user
astrue
meansicon_url
is ignored. With the right type signature, the above example can throw an error at static analysis.This can be demonstrated in a smaller case. Say you have an object named
shape
and it either has the propertysize
orradius
, but not both. This this "exclusive or" operation comes at the cost of verbosity in TypeScript:Going back to the example from before, the implementation would look something like this:
The solution is mighty verbose, but the result is that method arguments can be statically checked to conform to the right shape:
Obviously, this is a lot of work to do, but I feel like it might just be worth it (or, at least, a lot of fun).
Even more obvious, doing this would end up with bugs where correct combinations turn out to throw errors.
To mitigate this, the definition of
Method
at the top ofmethod.ts
could be altered so an optional type argument could be supplied to methods to be automatically unioned for method arguments, allowing callers to assert that their arguments are right by supplyingany
as the argument:Then a consumer could just do:
to assert that their arguments are correct.
And finally, this doesn't affect JavaScript users at all 👏 🙌 ✨
What type of issue is this? (place an
x
in one of the[ ]
)Requirements (place an
x
in each of the[ ]
)The text was updated successfully, but these errors were encountered: