-
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
Cc/discriminated unions #2
Cc/discriminated unions #2
Conversation
files: ['types/*.ts'], | ||
files: ['types/*.ts', 'test/**/*.ts'], |
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 is unrelated, but I noticed that the the TS tests weren't being linted since they moved directories.
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.
Nice find. We probably want to move that to a separate PR though?
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.
It's a tiny enough change that I for one don't mind throwing it in here but of course if Jos objects...
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.
Mostly added it in this PR because the lack of linting annoyed me 😄 I went ahead and opened a separate PR for this change just to (possibly) get it in sooner. Should have done that last week 🤷
expectTypeOf(instance1).toMatchTypeOf<MathNode>() | ||
expectTypeOf(instance1).toMatchTypeOf<MathNodeCommon>() |
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.
If we're trying for a discriminated union on MathNode, then I don't think this test should pass: instance1
is a BaseNode
, and I don't think a BaseNode should be assignable to a MathNode: BaseNodes. The type
property on BaseNode is type: string
, whereas on all other nodes it's a constant.
If BaseNode
is assignmable to MathNode
, then
let node: MathNode
if (node.type === 'AssignmentNode) {
// now node has type BaseNode | AssignmentNode
}
types/index.d.ts
Outdated
TOp extends OperatorNodeMap[TFn] = never, | ||
TFn extends OperatorNodeFn = never, | ||
TFn extends OperatorNodeFn = OperatorNodeFn, |
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 should have isolated this issue more, but I am pretty sure that...
In the old setup, something like `new OperatorNode("+", "addition", ...)" was never going to be assignable to be assignable to a MathNode, because:
- MathNode union looked like
NodeType1 | NodeType2 ... | OperatorNode | ...
, i.e., OperatorNode was using its defaults in MathNode union... - and OperatorNode default included
op: never
, so nothing was ever going to be assignable to OperatorNode.
To remedy this, I originally wanted:
interface OperatorNode<
TOp extends OperatorNodeMap[TFn] = OperatorNodeMap[TFn],
TFn extends OperatorNodeFn = OperatorNodeFn,
...>{...}
But TS complained later parameters TFn can't be used as defaults for earlier parameters. So originally I was going to swap the order of the two, but then I just got rid of TOp.
NOTE: Fixing this issue may not have been entirely necessary to get the discriminated union stuff to work, I'm not 100% sure, and I need to run soon so can't try to isolate it more right now.
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.
In the old setup, something like `new OperatorNode("+", "addition", ...)" was never going to be assignable to be assignable to a MathNode, because:
Do you have a reproducible example of this? Maybe I'm misunderstanding but assigning new OperatorNode("+", "addition", ...)
to MathNode
seems to work for me (at least in test/typescript-tests/testTypes.ts):
But TS complained later parameters TFn can't be used as defaults for earlier parameters. So originally I was going to swap the order of the two, but then I just got rid of TOp.
I think it's going to be a fairly big breaking change to change the generics for OperatorNode
so I'd suggest we try and instead find a way around whatever issue there is here.
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.
tldr: I agree we should not change the OperatorNode signature in this PR. Instead we should use OperatorNode<OperatorNodeOp, OperatorNodeFn>
in the MathNodes interface. I do think there's an issue with the type definition for OperatorNode, but best not to change it here.
The test you screenshotted above will fail on your branch; I agree it passes on master. Simpler version:
// passes on MathJs 11.2.1, (last version that used discriminated union)
// passes on MathJs ^11.3.0 (when discriminated union was removed)
// fails on goodproblems/types/revert-to-discr-union-for-nodes
const node1: MathNode = new OperatorNode("+, "add", [new ConstantNode(1), new ConstantNode(1)])
Looking back at the old typedefs, the old discriminated union was:
type MathNode = ... | ... | OperatorNode<OperatorNodeOp, OperatorNodeFn> | ... ;
So I think we should go back to using OperatorNode<OperatorNodeOp, OperatorNodeFn>
not OperatorNode
(with its default arguments). That's definitely a more focused change than changing the type parameters.
But.. I do think that there is an issue with OperatorNode's type definition (a separate issue that needs not be handled here). Namely, its default arguments seem useless to me. No OperatorNode instance can ever be assignable OperatorNode
type (with its default args): Currently on master:
Better to change the type signature or remove the defaults, imo. But, again, I agree: best to avoid that here.
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 test you screenshotted above will fail on your branch; I agree it passes on master. Simpler version:
I was on that wrong branch, you're correct. 🙄
So I think we should go back to using OperatorNode<OperatorNodeOp, OperatorNodeFn> not OperatorNode (with its default arguments). That's definitely a more focused change than changing the type parameters.
Awesome, looks good 👍🏻
But.. I do think that there is an issue with OperatorNode's type definition (a separate issue that needs not be handled here). Namely, its default arguments seem useless to me. No OperatorNode instance can ever be assignable OperatorNode type (with its default args): Currently on master:
This was the reason for using never
: josdejong#2576 (comment), not sure it's still a good reason. Happy to check out a PR which changes this.
interface MyNode extends BaseNode { | ||
type: 'MyNode' | ||
a: MathNode | ||
} | ||
declare module 'mathjs' { | ||
interface MathNodeTypes { | ||
MyNode: MyNode | ||
} | ||
} |
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 didn't see this declaration in your branch...We would not expect some of the tests below to pass without it, right?
TS told me that ambient declarations need to be at the top level of nesting within a file, and I didn't want this to affect the other tests, so I moved this test to a separate file.
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 didn't see this declaration in your branch...We would not expect some of the tests below to pass without it, right?
Yep, think you're right.
TS told me that ambient declarations need to be at the top level of nesting within a file, and I didn't want this to affect the other tests, so I moved this test to a separate file.
Yeah separate file seems fair. We are aiming to start splitting this up anyways.
Nice work and thank you @ChristopherChudzicki! Sorry it took a while to respond. Left a few comments |
Whenever you can get a consensus PR filed against mathjs itself that will be great :) |
@mattvague Good call about |
@ChristopherChudzicki Nice work, thank you! Will try and get this merged ASAP |
I took a look at your work in regards to josdejong#2810. (Thanks for looking at that, by the way.)
See comments (which I am about to add) for more.