Repository navigation
Recursive definitions in mixins #29872
Description
Activity
- addedNeeds InvestigationThis issue needs a team member to investigate its status.This issue needs a team member to investigate its status.
on Feb 19, 2019 canonic-epicure commented
on May 9, 2019 AuthorMore actionsAny hope for a proper solution of this issue?
Reacted by El Pupi and Viktor Shchelochkov- addedAwaiting More FeedbackThis means we'd like to hear from more people who would be helped by this featureThis means we'd like to hear from more people who would be helped by this featureSuggestionAn idea for TypeScriptAn idea for TypeScriptand removedNeeds InvestigationThis issue needs a team member to investigate its status.This issue needs a team member to investigate its status.
on Aug 22, 2019 When using mixins via class expressions this doesn't seem to apply:
type Constructor = new (...args: any[]) => {}; function Scale<TBase extends Constructor>(Base: TBase) { return class Scaling extends Base { _scale = 1; setScale(scale: number) { this._scale = scale; } get scale(): number { return this._scale; } }; } function Logger<TBase extends Constructor>(Base: TBase) { return class Logger extends Base { }; } class Group { sprites: typeof EightBitSprite[] } class Sprite { group: typeof LoggableGroup } const EightBitSprite = Scale(Sprite); const LoggableGroup = Logger(Group);
canonic-epicure commented
on Jun 30, 2020 AuthorMore actionsOrta Therox (@orta) This is because you haven't created an "abstract" mixin type - a type that represents instance of any class that has consumed a mixin. You only work with specific classes. Abstract
LoggerTwould look like:// supporting declarations for mixin pattern export type AnyFunction<A = any> = (...input: any[]) => A export type AnyConstructor<A = any> = new (...input: any[]) => A export type Mixin<T extends AnyFunction> = InstanceType<ReturnType<T>> function Logger<TBase extends Constructor>(Base: TBase) { return class Logger extends Base { }; } type LoggerT = Mixin<typeof Logger> // "abstract" mixin type
Actually, I just checked and the originally submitted code seems to work starting from 3.8.2!! (The version picker in playground is awesome). It does not work in 3.7.5 and prior.
Hopefully this was an intentional improvement, properly covered with a test, not a random codebase fluctuation. Could you check may be?
Yes please, I hope this is tested for. Making robust mixins has been difficult, results have been brittle.
canonic-epicure commented
on Sep 20, 2020 AuthorMore actionsHello again,
As I mentioned in the previous comment, the issue of this ticket seems to be resolved in 3.8.2 release. However, this ticket did not receive any updates at that point, so, I'm suspecting the "fix" might be just a side effect of some other change in the codebase.
Can someone from the dev team please check the originally reported issue and confirm, that this behavior is indeed now "officially" supported (and "pinned" with the test)?
canonic-epicure commented
on Jun 25, 2021 AuthorMore actionsNo, actually, as I thought, the "fix" was just a random codebase fluctuation. First of all, if you enable
strictPropertyInitializationit will fail again with the same message. W/o that option it works:
Playground linkSecondly, it fails for 2nd level of mixins:
Playground linkThis is so frustrating. Probably a decent portion of the TS issues is related to the bad support of the mixin pattern. Why not finally implementing a proper solution/workaround? It does not have to be academically pure, it should just work. Can't believe its something impossible.
Although the original example still does not work, the errors go away if written like this:
// supporting declarations for mixin pattern export type AnyFunction<A = any> = (...input: any[]) => A export type AnyConstructor<T = object, A extends any[] = any[], Static = {}> = (new (...a: A) => T) & Static export function SampleMixin1<T extends AnyConstructor>(base: T) { return class Sample1 extends base { another!: InstanceType<ReturnType<typeof SampleMixin2>> } } export function SampleMixin2<T extends AnyConstructor>(base: T) { return class Sample2 extends base { another!: InstanceType<ReturnType<typeof SampleMixin1>> } }
However the result is very strange!!!! Sometimes hovering on
anotherwill show proper types. Most of the time, it will just showany. There definitely something flaky happening under the hood.Here is something I've been doing in practice that doesn't have any errors and seems to work (at least in my cases so far):
// supporting declarations for mixin pattern export type AnyFunction<A = any> = (...input: any[]) => A export type AnyConstructor<T = object, A extends any[] = any[], Static = {}> = (new (...a: A) => T) & Static export function SampleMixin1<T extends AnyConstructor>(base: T) { return class Sample1 extends base { another!: Sample2 } } export const Sample1 = SampleMixin1(class {}) export type Sample1 = typeof Sample1 export function SampleMixin2<T extends AnyConstructor>(base: T) { return class Sample2 extends base { another!: Sample1 } } export const Sample2 = SampleMixin2(class {}) export type Sample2 = typeof Sample2
canonic-epicure commented
on Jul 21, 2021 AuthorMore actionsJoe Pea (@trusktr) Can you try making it working as in my 2nd example here? Basically, create a
SampleMixin3which extendsSampleMixin1andSampleMixin4, which extendsSampleMixin2. Then make a cycle between them
Its currently possible to create a circular references in classes just fine:
However, the same thing fails when using mixin-based classes:
This makes things much more complicated, you need to introduce some dummy interfaces, etc, etc.
The workaround exists - to use different form of creating the type for the standalone mixin class - with interfaces:
But this notation seems to drive crazy the IDE (the language server under the hood?). It stop finding usages of properties, show many false type errors etc. Basically in such approach you are limited to old-good console
npx tsclaunch (which works well at least), but all the development aid facilities don't work.I think mixin pattern should be supported first class. I'd even create some language construct for it.