abstract class PluginOptions {} abstract class Plugin<Options extends PluginOptions> { Options get options; } class MyPluginOptions extends PluginOptions {} class MyPlugin extends Plugin<MyPluginOptions> { static const pluginKey = PluginKey<MyPluginOptions, MyPlugin>(); @override MyPluginOptions get options => MyPluginOptions(); } O getOptionsGenerically<O extends PluginOptions, P extends Plugin<O>>(P plugin) => plugin.options; class PluginKey<O extends PluginOptions, P extends Plugin<O>> { const PluginKey(); } O getOptionsWithPluginKey<O extends PluginOptions, P extends Plugin<O>>(PluginKey<O, P> pluginKey, P plugin) => plugin.options; void main() { final plugin = MyPlugin(); // Problem: The static type = `PluginOptions` but we want `MyPluginOptions`. // // This is due to limitations in Dart's ability to infer generics when referemce each other. final /* PluginOptions */ options = getOptionsGenerically(plugin); // Option 1: pass all generic types explicitly. // // But you have to know which parameters to pass, and for anything more than 2 parameters, it's very verbose. final /* MyPluginOptions */ myPluginOptions = getOptionsGenerically<MyPluginOptions, MyPlugin>(plugin); // Option 2: plugin key // // The plugin key encodes the generic parameters so that we don't have to remember them/pass them specifically. // The plugin key is also easier to locate when it has a dedicated location per plugin. final /* MyPluginOptions */ myPluginKeyOptions = getOptionsWithPluginKey(MyPlugin.pluginKey, plugin); }