-
Notifications
You must be signed in to change notification settings - Fork 129
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
Typescript conversion for src/observable/map
#793
Typescript conversion for src/observable/map
#793
Conversation
"no-unused-vars": "off", | ||
"@typescript-eslint/no-unused-vars": ["warn"], |
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 import-js/eslint-plugin-import#653 (comment) (I was getting the error in the OP of that thread when trying to lint)
"@typescript-eslint/no-misused-promises": 2, | ||
"no-unused-vars": "off", | ||
"@typescript-eslint/no-unused-vars": ["warn"], | ||
"no-undef": "off", |
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.
"@typescript-eslint/no-unused-vars": ["warn"], | ||
"no-undef": "off", | ||
"semi": ["error", "always"], | ||
"@typescript-eslint/explicit-function-return-type": ["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.
Added based on feedback in my first PR.
Also: I tried for the life of me to convert |
I wonder if we can use a mixin of some sort here. In const NewSortedMapList = mapCreator(SortedMapList);
export { NewSortedMapList as SortedMapList };
function mapCreator(mapClass) {
return class extends mapClass {
sortValues(comparator) {
return new SortedMapList(this, comparator);
}
mapValues(mapper, updater) {
return new MappedMap(this, mapper, updater);
}
filterValues(filter) {
return new FilteredMap(this, filter);
}
join(...otherMaps) {
return new JoinedMap([this].concat(otherMaps));
}
};
} Haven't tried it out but it's worth a try. |
@MidhunSureshR I wasn't able to figure out a good way to do it using mixins, however I did find a way to eliminate a good amount of the boilerplate: |
I just noticed that there's a constrained mixin pattern that might work for us, I'll play around with that. |
8902261
to
05622d9
Compare
Couldn't get mixin to workI did some messing around today, long story short is that I didn't find a good way use mixins, even when using constraints. A core issue is that mixins don't play nicely with type GConstructor<T = {}> = new (...args: any[]) => T;
type IsABaseObservableMap<K, V> = GConstructor<BaseObservableMap<K, V>>;
function makeTransformable<K, V, TBase extends IsABaseObservableMap<K, V>>(Base: TBase) {
return class extends Base {
join(...otherMaps: Array<BaseObservableMap<K, V>>): JoinedMap<K, V> {
return new JoinedMap([this].concat(otherMaps));
}
mapValues(mapper: Mapper<V>, updater?: Updater<V>): MappedMap<K, V> {
return new MappedMap(this, mapper, updater);
}
sortValues(comparator: Comparator<V>): SortedMapList {
return new SortedMapList(this, comparator);
}
filterValues(filter: Filter<K, V>): FilteredMap<K, V> {
return new FilteredMap(this, filter);
}
}
} The returned class complains I also ran into another typing error which I wasn't able to decipher, wherein the type system seems to get tripped up on It's as if it doesn't realize that What I did insteadI changed IMO it would be nicer if we could get mixins to work in a manner similar to what I tried above, but given the issues with the type system, this is good enough. (Naturally, feel free to show me how its done if you think you see a path I missed). I'm going to leave those merge conflicts unresolved until we figure out what's going on here. |
import { SortedMapList as _SortedMapList } from "./list/SortedMapList.js";
import { MappedMap as _MappedMap } from "./map/MappedMap";
import { FilteredMap as _FilteredMap } from "./map/FilteredMap";
import { JoinedMap as _JoinedMap } from "./map/JoinedMap";
import { BaseObservableMap as _BaseObservableMap } from "./map/BaseObservableMap.js";
// re-export "root" (of chain) collection
export { ObservableMap } from "./map/ObservableMap";
export { ObservableArray } from "./list/ObservableArray";
export { SortedArray } from "./list/SortedArray";
export { MappedList } from "./list/MappedList";
export { AsyncMappedList } from "./list/AsyncMappedList";
export { ConcatList } from "./list/ConcatList";
type GConstructor<T = {}> = abstract new (...args: any[]) => T;
type MapConstructor<K, V> = GConstructor<_BaseObservableMap<K, V>>;
function addTransformers<K, V, T extends MapConstructor<K, V>>(MyClass: T) {
abstract class ClassWithTransformers extends MyClass {
sortValues(comparator) {
return new _SortedMapList(this, comparator);
}
mapValues(mapper, updater) {
return new _MappedMap(this, mapper, updater);
}
filterValues(filter) {
return new _FilteredMap(this, filter);
}
join(...otherMaps) {
return new _JoinedMap([this].concat(otherMaps));
}
}
return ClassWithTransformers;
}
export const SortedMapList = addTransformers(_SortedMapList);
export const MappedMap = addTransformers(_MappedMap);
export const FilteredMap = addTransformers(_FilteredMap);
export const JoinedMap = addTransformers(_JoinedMap); @ibeckermayer would this work? |
… however we still run into a bug
@MidhunSureshR Clever, it appears to work: 7d27d46 The primary downsides I see to this approach are:
One way is that from a straightforward reading of the code, it seems like we are instantiating function addTransformers<K, V, T extends MapConstructor<K, V>>(MyClass: T): ((abstract new (...args: any[]) => ClassWithTransformers) & {
prototype: addTransformers<any, any, any>.ClassWithTransformers;
}) & T which is something, but I'm not exactly clear what. As an example const ObservableMap: ((abstract new (...args: any[]) => addTransformers<unknown, unknown, typeof _ObservableMap>.ClassWithTransformers) & {
prototype: addTransformers<any, any, any>.ClassWithTransformers;
}) & typeof _ObservableMap It's not exactly clear to me why the type system allows me to instantiate that, but the overarching point is that the types of these classes are now more or less inscrutable.
In other words, there's a semantic difference between this solution and the previous one -- previously we were saying that all In both cases the code around this part gets a bit "weird". I'm partial to my solution from before, because outside of |
@MidhunSureshR Any thoughts on my argument above? |
https://matrix-org.github.io/synapse/latest/development/contributing_guide.html#sign-off Signed-off-by: Isaiah Becker-Mayer [email protected] |
…y named BaseObservableMapDefaults class
…/BaseObservableMapTransformers.ts
Sorry, it took me a bit of time to come back to this. One issue with both the approaches is that we're making non trivial changes to the code to get static typing to work. Can we instead do some thing like this? Thank you for working on this. |
For reference, I found a bug in the
No worries, I will take a look in detail and see if we can get it to work. At first glance I'm skeptical, because this contains an It appears that the general problem we're trying to solve is that we have a |
The example is close to what we currently have and causes the same problem. Namely, classes that extend |
7d27d46
to
77f21f7
Compare
Alright, turns out we aren't the first to encounter this general problem. I was able to solve it by arranging the export order in There's a lot of commits in here now from me going down various paths, if it's not breaking a project policy then it might be best to squash and merge this one. If that's not an option let me know, and I can try and work some gitfu to clean the history up. |
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.
Taking an initial look at the PR:
@MidhunSureshR I think this one is very close to ready. |
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 🎉
The conversion of
domain/session/leftpanel
was being blocked by typescript's inability to pick up on the methods ofBaseObservableMap
when they were defined like the following in order to avoid circular dependencies:This PR fixes that problem by adding those functions as abstract functions in
BaseObservableMap
, and adding their default implementations in a new class calledBaseObservableMapDefaults
.Each class that inherits
BaseObservableMap<K, V>
is then initialized with aBaseObservableMapDefaults
field like:https://github.com/ibeckermayer/hydrogen-web/blob/f0ebcfcbfe4f2a007670bafae9489df413909d68/src/observable/map/ObservableMap.ts#L25-L26
and then some boilerplate is added to call the default implementations:
https://github.com/ibeckermayer/hydrogen-web/blob/f0ebcfcbfe4f2a007670bafae9489df413909d68/src/observable/map/ObservableMap.ts#L102-L116
and some boilerplate which calls the default implementation, which is now defined in
config.ts
.Update 1
Found a way to limit the boilerplate: c6efdc7