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);
}