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 checks for type declarations #2448
Add checks for type declarations #2448
Conversation
Thanks @samestep for this contribution. We would definitely like to get the TypeScript typing of math.js as precise as possible. I agree it's worth taking a moment to see if we can make a more general improvement to the type of issue you raise before going ahead with just these two specific fixes. So, when you say you were looking for something that would fix this sort of issue "across the board," did you mean understanding that the result of matrix operations were matrices for a larger collection of operations, or meaning that encoding the fact that say, basic arithmetic operations preserve types for a wider collection of types, or really both of these things? Another item that could really help get your changes in is that currently it does not seem that math.js has any testing for the type annotations in types/index.d.ts -- for example, making sure that typescript can make appropriate type inferences based on mathjs computations. If you have any suggestions as to how to perform such testing, or better yet can add a commit to your PR with just an initial pass at adding tests that check type inference in the cases of interest to you (that could in the future be extended to more cases), it would be extraordinarily helpful -- and it would assist in pursuing more general solutions, since having tests in place would ensure the more general solutions don't break any of the existing type declarations. Thanks so much for your thoughts/assistance and rest assured we will get at least some solution to your situation into mathjs soon. Looking forward to your further feedback. |
@gwhitney Thanks so much for the quick and detailed response!
Yes, both of those things. Initially I was thinking of something like this: add<T extends MathType>(x: T, y: T): T; But obviously that doesn't work because it doesn't allow you to add, for instance, a real number and a complex number (which I assume math.js allows?). So maybe it should actually be these two overloads: add<T extends MathType>(x: T, y: T): T;
add(x: MathType, y: MathType): MathType; That doesn't feel "optimal", but maybe that doesn't matter too much? But I didn't do that in this PR because I didn't see this pattern for any of the existing functions. For instance: multiply<T extends Matrix | MathArray>(x: T, y: MathType): T;
multiply(x: Unit, y: Unit): Unit;
multiply(x: number, y: number): number;
multiply(x: MathType, y: MathType): MathType; Part of my difficulty is that I don't know exactly what are the boundaries of math.js's flexibility (for instance, the return types of
This sounds awesome! I would be more than happy to help with this, whether that means autogenerating I'll come back to this over the weekend; from your expertise on the project, what do you think is the best goal to shoot for here? |
Actually, thinking about it more, I realize that we'd probably want to test the typedefs regardless of whether we autogenerate them, so in that case autogenerating them would probably be overkill. Anyways, I'll come back to this soon. |
One difficulty here is that although I do work on some projects that use TypeScript as the primary language, I am by no means an expert, especially not on TypeScript-JavaScript interoperation. So for example, I think one no-brainer test to add would be to ensure that types/index.ts runs without error and produces the expected output. But I am not even clear how to run that file in the context of a clone of the repo:
And then even if I could run it, I don't understand how it could work, given that Anyhow, those items aside (and any pointers you might have are welcome!), I do think that getting some TypeScript interoperability and typing tests into the mathjs test suite is the key to moving forward in anything other than tiny incremental steps -- we definitely want to minimize the chances that we break any existing use of mathjs from TypeScript going on out there. So if you're willing to give initial tests a stab, great! Once some reasonable testing is in place, I am happy to try to go somewhere more ambitious with the typing itself. Auto-generation from the "typed" declaration is by no means out of the question -- it would be really nice to have one less large, complex piece to maintain by hand. Or if we can't get that to work, then we may just have to continue with a hand-maintained set of type declarations, but try to "optimize" the typing as you point out. And I agree that in any case, we want to capture that add(x:T, y:T) is of type T for a reasonable range of T, so we will definitely adopt a solution that does that, and then if we can manage to also get such inferences as add(x: number, y:complex) is complex, as is add(x: complex, y: number), that would be great -- not to mention the fact that it is perfectly legal to call add on any number of arguments! Looking forward to your thoughts/initial commits on TypeScript testing. |
(One final comment at the moment: one tricky bit concerning more exact TypeScript typing, whether or not we try to autogenerate or hand-optimize, is capturing the |
@gwhitney I pushed a commit to check
Yeah, I ran into the same thing. I ended up following the advice from this SO answer, but I'm not sure whether that's actually the right thing to do in this case; I'm guessing you had Also: should I be doing the typechecking-infrastructure stuff and the |
Sam, thank you so much, this is a great start! At this point I have to say we will have to wait for @josdejong to review this PR before it can go in, if changing the In the meantime, here's my feedback, which of course Jos may want to amend:
that you mention above, in place of the current typing and your addition? It seems to me that it might work for adding a number and a Complex, since doesn't Thank you so much for working together with us on this! |
Yep, I totally agree that change to diff --git a/package-lock.json b/package-lock.json
index 92fd19501..ffb86f238 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5,7 +5,6 @@
"requires": true,
"packages": {
"": {
- "name": "mathjs",
"version": "10.1.1",
"license": "Apache-2.0",
"dependencies": {
It's possible; the answers and comments on that SO page I linked seemed all over the place, so I really don't know. You mentioned that you're not an expert on TypeScript-JavaScript interoperation; unfortunately neither am I 😅
Makes sense! Done.
Also a good idea! Done.
Yep, sounds good! Done.
Fair enough! And no, I hadn't tried that before, but I have now changed this PR to do that and it seems to work well.
Unfortunately that doesn't quite give what we'd like: math.add(42, math.complex(4, 2)) // error: Argument of type 'Complex' is not assignable to parameter of type '42'.
math.add<number | math.Complex>(42, math.complex(4, 2)) // works! but kind of verbose Since I'm guessing people might not want to have to explicitly write these generic parameters in this case, I also left in the plain
Thanks for being so welcoming! |
Nice! I think we are getting close on this. As far as running
Maybe you can check out the ramifications of this and/or see if there's a way to simplify it? My main concern with the above exactly as described is that it would be really problematic and non-DRY if we had to have two versions of package.json in two different directories that had to be kept in sync except for that one line being present or not. So I wouldn't really find that to be an acceptable final solution. But maybe the package.json in the subdirectory can be very much of a stub that wouldn't have to change at all typically and doesn't reiterate much if any of the info in the "real" one. Or something along those lines. Or maybe there's a way of including the main one and just overriding that one setting. Etc. If you can work something along those lines out, it's likely better than attempting to change a major global parameter, especially with the odd behavior you were seeing. Of course, Jos may have other ideas.
It looks kind of weird because it seems as though So I think as soon as we get the last kinks about running |
It appears that another option for running Specifically, in our case that would consist of If that also seems to work for you, then maybe we should try going with that? Having read a bit more about its meaning, I don't like the look of removing "type": "module" from package.json. Let me know your thoughts or if you have another alternative to suggest. Sorry this bit of interoperation between TypeScript and JavaScript is kind of mysterious. |
Thanks @samestep for working on the type definitions (and @gwhitney for reviewing). In the long run it would be great to have mathjs implemented in TypeScript (I think), but that will be a huge undertaking. Having the type definitions unit tested will be of great help to keep it all in sync. In general, mathjs is very flexible with types, I think it will be hard to capture the current behavior for 100% in strict types. So I guess for the time being we'll have to be happy with like 95% accurate types and accept some limitations. About the |
Sorry for dropping the ball on this! @gwhitney I modified this PR to use your last suggestion and it seems to work. I had already tried this previously, but didn't use it initially because it prints this warning:
This is better than removing @josdejong Are there any further changes you'd like me to make to this PR? |
All the tests pass and I think this is an important step forward. I've now also looked over the code changes in detail and all looks reasonable to me, except I am not clear what was going on with the |
Glad to hear you managed to solve it without changing |
* Add overloads for Matrix add and subtract * Add check for types/index.ts * Fix type errors in types/index.ts * Fix a couple execution errors * Run test:types as part of test:all * Fix remaining errors * Replace types/index.ts comments with asserts * Add tests for narrowed type inference * Add dual-purpose comment at top of types/index.ts * Update AUTHORS * Use Glen's alternate test:types suggestion
Published in |
This PR adds a
test:types
script which usests-node
to check thattypes/index.ts
works (it didn't prior to this PR), and modifiestest:all
to calltest:types
so that it gets run in CI. It also makes a couple modifications totypes/index.d.ts
, partially to fix errors intypes/index.ts
, and partially to improve usability of theadd
andsubtract
functions (see below).(original PR description)
Currently the return types for
add
andsubtract
are justMathType
when they're passed aMatrix
, necessitating type assertions or runtime checks if one wants to give TypeScript aMatrix
back. This PR adds overloads to those functions to avoid this issue.(At first I wanted to try to submit a PR with a generic solution to this issue across the board, but I wasn't sure the right way to do it since I still don't quite understand how the internal implementation of math.js works, so I'm just submitting a PR for these couple simple cases; perhaps the maintainers of this repo have some idea of how to solve this more generally?)