-
Notifications
You must be signed in to change notification settings - Fork 420
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
Collapse functions subtree transform #721
Collapse functions subtree transform #721
Conversation
Codecov Report
@@ Coverage Diff @@
## master #721 +/- ##
=========================================
+ Coverage 62.5% 62.9% +0.39%
=========================================
Files 121 121
Lines 7220 7313 +93
Branches 1633 1658 +25
=========================================
+ Hits 4513 4600 +87
- Misses 2378 2383 +5
- Partials 329 330 +1
Continue to review full report at Codecov.
|
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 think there's enough nits and comments to warrant a new review :)
* ↓ ↓ | ||
* A:1,0 X:1,1 | ||
*/ | ||
'focus-subtree': {| |
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 never use the keys in this type. Moreover they're duplicating the types. So why don't you just use an array instead of an object ? This would be Transform
already.
Then you'd simply use $TupleMap<...>
instead of $Values<$ObjMap<...>>
.
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.
As far as I know I can't convert a tuple into a union. The $TupleMap
returns another tuple. See here in flow.org/try.
I referenced a Flow issue in the comment above.
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.
ah it's so sad we can't get an union from an array's values... Thanks for trying.
// Do not throw an error, as we don't trust the data coming from a user. | ||
console.error('Unrecognized transform was passed to the URL.', type); | ||
break; | ||
throw assertExhaustiveCheck(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.
the function itself is throwing already, why calling "throw" 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.
same below of course
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.
Flow continues to go through the code, as it doesn't realize that assertExhaustiveCheck
throws inside of it. Some places I can take out the extraneous throw
without Flow complaining, but sometimes it is needed. I checked and this one is not needed, but tried to remove others and Flow complained. My preference would be to throw an additional time.
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 see; and after all this makes it quite clear when reading that we can throw here.
// @flow | ||
|
||
declare module 'memoize-immutable' { | ||
declare module.exports: <T: Function>(fn: T, config: Object) => T; |
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.
config
is optional and has 2 mutually exclusive parameters.
I took some time to do a libdef for flow-typed but it's not fully complete and I don't want to take the time to make it work...
So for now, maybe something like this would be good enough:
interface CacheInstance<K, V> {
has(K): boolean;
get(K): V;
set(K, V): void;
};
type CacheConfig<K, V> = {|
cache: CacheInstance<K, V>,
|};
type LimitConfig = {|
limit: number,
|};
declare module.exports: <F: Function, K, V>(fn: F, config ?: CacheConfig<K, V> | LimitConfig) => F;
And this will expose that this is incorrect: https://github.com/devtools-html/perf.html/blob/e4b3388a1f3dc742a925804b18a1041e28f1a372/src/reducers/profile-view.js#L561-L564
Indeed memoize-immutable passes "limit" only if "cache" isn't specified: https://github.com/memoize-immutable/memoize-immutable/blob/f4447e041ce7956455c9276191f5314d2bea08ea/index.js#L11
But anyway WeakTupleMap doesn't handle a "limit" as parameter: https://github.com/memoize-immutable/WeakTupleMap/blob/5a794788b942ac148d30cbfc0e2b8b313a0b3f35/index.js#L1-L3
data={{ type: 'collapse-function-subtree' }} | ||
> | ||
<span className="profileCallTreeContextMenuIcon profileCallTreeContextMenuIconCollapse" /> | ||
{"Collapse function's subtree"} |
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.
nit: I believe you used {" ... "}
because of the eslint rule, but the real fix is rather to use the nice quote ’
:) (shamelessly copied from wikipedia: https://en.wikipedia.org/wiki/Apostrophe)
=> Collapse function’s subtree
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.
Actually I think we need to make it clearer that this happens accross the whole tree => Collapse this function’s subnodes accross the tree
I changed "subtree" so that I can use the word "tree" without a repetition. You could also use "descendants" but I wasn't sure how accurate this term would be.
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 have no way to type the nice quote directly and have never used it in my English writing, not sure why we should make it more complicated than it needs to be.
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 updated the label to be more explicit.
type: 'collapse-function-subtree', | ||
funcIndex, | ||
}); | ||
break; |
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.
nit: please use the same order for the case blocks here and the case lines above.
'collapse-function-subtree', | ||
].forEach((transform: TransformType) => { | ||
// This is kind of an awkward switch, but it ensures we've exhaustively checked that | ||
// we have a mapping for every transform. |
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.
Are you sure this works as you think? I believe you can add or remove something from the array you're iterating with without flow warning you...
type TransformTypes = // comes from types/transforms.js
| 'focus-subtree'
| 'focus-function'
| 'merge-call-node'
| 'merge-function'
| 'drop-function'
| 'collapse-resource'
| 'collapse-direct-recursion';
const TRANSFORM_TO_SHORT_KEY: {[key: TransformTypes]: $Keys<typeof SHORT_KEY_TO_TRANSFORM>} = {
'focus-subtree': 'f',
'focus-function': 'ff',
'merge-call-node': 'mcn',
'merge-function': 'mf',
'drop-function': 'df',
'collapse-resource': 'cr',
'collapse-direct-recursion': 'rec',
};
const SHORT_KEY_TO_TRANSFORM: {[key: $Values<typeof TRANSFORM_TO_SHORT_KEY>]: TransformTypes} = {
f: 'focus-subtree',
ff: 'focus-function',
mcn: 'merge-call-node',
mf: 'merge-function',
df: 'drop-function',
cr: 'collapse-resource',
rec: 'collapse-direct-recursion',
};
Adding a new transform type wouldn't force you add it to these structures (I don't know how to do it, I think we'd need to check that $Keys<typeof TRANSFORM_TO_SHORT_KEY> === TransformTypes
but I don't know if that's possible), but your current code doesn't either. But this keeps the 2 structures synced in a type-safe way, with a much simpler code.
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.
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 see, this is because you added the TransformType
type to the argument (it wasn't there in the first commit), and Flow doesn't understand that not all types will be passed.
OK then.
src/profile-logic/transforms.js
Outdated
|
||
/** | ||
* This file contains the functions and logic for working with and applying transforms | ||
* to profile data. | ||
*/ | ||
|
||
// Create mappings from a transform name, to a url-friendly short name. | ||
export const TRANSFORM_TO_SHORT_KEY: { [TransformType]: string } = {}; | ||
export const SHORT_KEY_TO_TRANSFORM: { [string]: TransformType } = {}; |
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.
Why exporting them?
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.
Not sure why, I removed export
.
src/utils/flow.js
Outdated
default: { | ||
// The coerced type SHOULD be empty here. If in reality we get | ||
// here, then it's not a valid transform type, so return null. | ||
const unhandledCase: empty = coercedTabSlug; // eslint-disable-line no-unused-vars |
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.
nit: you can just do (coercedTabSlug: empty)
and get rid of the unused var.
(see https://flow.org/en/docs/types/casting/#toc-type-assertions)
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 this eslint error or no-unused-expressions
error
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.
Adding it again so that it's embedded in a review:
Ah, I see. Then I'd suggest to go by the no-unused-expressions because it could be made to work with Flow assertions. I filed eslint/eslint#9877 for this, maybe you can also reference the issue here.
src/utils/flow.js
Outdated
default: { | ||
// The coerced type SHOULD be empty here. If in reality we get | ||
// here, then it's not a valid transform type, so return null. | ||
const unhandledCase: empty = coercedType; // eslint-disable-line no-unused-vars |
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.
as above, you can just write (coercedType: empty)
and get rid of the unused var.
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.
See above.
src/utils/flow.js
Outdated
* This function takes a string and returns either a valid TabSlug or null, this doesn't | ||
* throw an error so that any arbitrary string can be converted, e.g. from a URL. | ||
*/ | ||
export function toValidTabSlug(tabSlug: any): TabSlug | null { |
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.
nit: please take a string as parameter and do just like in convertToTransformType
:)
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.
There's a toValidTabSlug
in url-handling, do you think you can make it use this new function?
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'd prefer any
, as then I can take null types and whatever I throw at it. I originally took a string to be more strict, but I believe I pass potentially null values in here, and it always does the right thing, not matter the type, so this solution is more general.
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 catching the toValidTabSlug
duplication.
bce2123
to
858f77d
Compare
Ok @julienw, review should be addressed. I rebased, and added on two new commits which I will squash when it's ready to merge. |
data={{ type: 'collapse-function-subtree' }} | ||
> | ||
<span className="profileCallTreeContextMenuIcon profileCallTreeContextMenuIconCollapse" /> | ||
{"Collapse function's subtree across the entire tree"} |
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 have no way to type the nice quote directly
Wikipedia tells me, on a Mac US Layout: Option + Shift + ]
Or just '
as a HTML entity.
and have never used it in my English writing
From Wikipedia: Both simplifications carried over to computer keyboards and the ASCII character set. However, although these are widely used due to their ubiquity and convenience, they are deprecated in contexts where proper typography is important.
not sure why we should make it more complicated than it needs to be.
complicated? Why complicated?
It's nicer, and maybe more important, it's proper typography. The '
is just a convenience from a time when we were using typewriters or using 7-bit ASCII.
And here I took the time to go to Wikipedia to be able to give you the character to use anyway (’
), so you just can copy paste it :)
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.
Embedding this in a review so that you don't miss it ^
// Do not throw an error, as we don't trust the data coming from a user. | ||
console.error('Unrecognized transform was passed to the URL.', type); | ||
break; | ||
throw assertExhaustiveCheck(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.
I see; and after all this makes it quite clear when reading that we can throw here.
src/profile-logic/transforms.js
Outdated
@@ -237,6 +237,11 @@ export function stringifyTransforms(transforms: TransformStack = []): string { | |||
'Expected to be able to convert a transform into its short key.' | |||
); | |||
} | |||
// This switch breaks down each transform down into shared groups of what data |
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.
nit: "down" repeated, I believe the first should be removed ?
src/profile-logic/transforms.js
Outdated
// they need as defined in src/types/transforms.js. For instance some transforms | ||
// need only a funcIndex, while some care about the current implemention, or | ||
// other pieces of data. The only reason why they are grouped together is to | ||
// not repeat the same line of code. |
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.
Can you please also add a similar comment in parseTransforms ?
Also I believe the last sentence is not useful, but up to you.
console.error( | ||
'Unknown tab found, maybe a URL upgrader needs to be written.', | ||
slug | ||
); |
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 think we miss this error now. But I think the Flow check brings a better protection.
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'll need to rebase and get a green build before landing.
Thanks for this new transform !
Some nits that I'd like to see fixed:
- the apostrophe
- adding one more comment and fixing one
- trading eslint error "no-unused-vars" for "no-unused-expressions"
@@ -36,14 +36,61 @@ export function immutableUpdate<T: Object>(object: T, ...rest: Object[]): T { | |||
* This function takes a string and returns either a valid TabSlug or null, this doesn't | |||
* throw an error so that any arbitrary string can be converted, e.g. from a URL. | |||
*/ | |||
export function toValidTabSlug(tabSlug: string): TabSlug | null { | |||
switch (tabSlug) { | |||
export function toValidTabSlug(tabSlug: any): TabSlug | null { |
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.
With any
Flow won't tell you if you try to pass, I don't know, an object, or a number, which could indicate a bug.
Currently it's string | void (not null).
I won't fight for this but I still think this would be better.
src/utils/flow.js
Outdated
default: { | ||
// The coerced type SHOULD be empty here. If in reality we get | ||
// here, then it's not a valid transform type, so return null. | ||
const unhandledCase: empty = coercedTabSlug; // eslint-disable-line no-unused-vars |
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.
Adding it again so that it's embedded in a review:
Ah, I see. Then I'd suggest to go by the no-unused-expressions because it could be made to work with Flow assertions. I filed eslint/eslint#9877 for this, maybe you can also reference the issue here.
data={{ type: 'collapse-function-subtree' }} | ||
> | ||
<span className="profileCallTreeContextMenuIcon profileCallTreeContextMenuIconCollapse" /> | ||
{"Collapse function's subtree across the entire tree"} |
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.
Embedding this in a review so that you don't miss it ^
} | ||
} | ||
const newSamples = Object.assign({}, samples, { | ||
stack: samples.stack.map(oldStack => { |
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.
Ah yes, thanks, got it
src/profile-logic/transforms.js
Outdated
'Expected to be able to convert a transform into its short key.' | ||
); | ||
} | ||
// This switch breaks down each transform down into shared groups of what data |
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.
(sorry if this is a dupe, but I don't see it so writing it again)
nit: down
is repeated, I believe the first one should be removed.
858f77d
to
ec85fb7
Compare
Please only review the last 3 commits, this is based off of the flow upgrade PR #709. I will rebase before merging.
9e7af51 Make transforms exhaustively checked
a36ed92 Add memoize-immutable type definition
bce2123 Collapse function subtree transform; resolves #545