Skip to content

dart:mirror is not cross-platform #59682

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

Closed
gmb15551 opened this issue Dec 7, 2024 · 3 comments
Closed

dart:mirror is not cross-platform #59682

gmb15551 opened this issue Dec 7, 2024 · 3 comments
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. area-web Use area-web for Dart web related issues, including the DDC and dart2js compilers and JS interop. library-mirrors type-question A question about expected behavior or functionality web-platform

Comments

@gmb15551
Copy link

gmb15551 commented Dec 7, 2024

When working with Dart, there are some difficulties in solving the following task.

The task is to write a Chain class that will store a set of functions and execute them when necessary. The class, when executing the function chain, should trigger rollback actions for all the completed tasks in case of an error. This can be useful when you need to either execute all the functions or execute none if an error occurs.

When starting the chain, arguments can be passed to it. The chain stores these arguments. To call the functions from storage, the required arguments are fetched. The list of required arguments is determined based on dart:mirror, which extracts parameter names and information about them from the function's description. However, the solution based on dart:mirror is not cross-platform. Extracting names using the toString method for function signatures may not be very convenient, fast, and also is not cross-platform.

Is it possible to add a limited version of dart:mirrors in the Dart SDK that would be cross-platform and allow extracting the following information from functions: whether an argument is named or not, and the name of the argument?

A simplified code for working with chains is presented below.

import 'dart:mirrors';

/// Type for storing the arguments of the function chain.
typedef ChainArgs = Map<Symbol, dynamic>;

/// Used to describe the parameters of a function.
class FunctionParameter {
  /// The identifier of the parameter as specified in the function description.
  Symbol simpleName;

  /// Whether the parameter is named or not.
  bool isNamed;

  FunctionParameter(this.simpleName, this.isNamed);
}

/// Used to store a list with information about all function parameters.
class FunctionParameters {
  final List<FunctionParameter> params = [];

  /// Constructor that uses a non-cross-platform solution
  /// `dart:mirrors` to get the function parameter information.
  FunctionParameters(Function function) {
    var mirror = reflect(function) as ClosureMirror;
    for (var p in mirror.function.parameters) {
      params.add(FunctionParameter(p.simpleName, p.isNamed));
    }
  }
}

/// Class representing a task in the task chain.
///
/// There are two functions. The first one [run] is used to execute the task.
/// The second one [cancel] is used to undo the task in case of an error during execution.
class Task {
  /// Run the task for execution. For example, create a file.
  Function run;

  /// Undo the task. For example, delete a file.
  Function? cancel;

  /// Information about the parameters of the [run] function.
  FunctionParameters runParameters;

  Task(this.run, [this.cancel]) : runParameters = FunctionParameters(run);
}

/// Task chain.
class Chain {
  /// List of tasks to execute.
  final List<Task> tasks = [];

  /// List of completed tasks. This is used to call cancel functions only for
  /// tasks that have been executed.
  final List<Task> completedTasks = [];

  /// Add a task to the chain.
  void addTask(Task task) => tasks.add(task);

  /// Cancel all completed tasks.
  void cancel() {
    for (var t in completedTasks) {
      try {
        t.cancel!();
      } catch (e) {
        throw AssertionError("The cancel function should not throw exceptions");
      }
    }
  }

  /// Start executing the chain.
  void run(ChainArgs args) {
    try {
      for (var task in tasks) {
        // args contains arguments for the chain. All task arguments are stored
        // in a single Map. When running a task, the required arguments for calling
        // the function are extracted from this Map. This Map contains non-fixed
        // arguments, executed tasks can generate data, store it in this Map, and 
        // the saved values can be used in other tasks.
        var params = task.runParameters;
        Map<Symbol, dynamic> namedParams = {};
        List<dynamic> posParams = [];
        for (var p in params.params) {
          // If the special parameter __context__ is specified, it is used
          // for accessing all arguments of the chain within the function.
          // It can change the state of the object with these arguments.
          if (p.simpleName == #$context) {
            if (p.isNamed) {
              namedParams[p.simpleName] = args;
            } else {
              posParams.add(args);
            }
          } else {
            if (p.isNamed) {
              namedParams[p.simpleName] = args[p.simpleName];
            } else {
              posParams.add(args[p.simpleName]);
            }
          }
        }

        // The function is called with the required arguments.
        Function.apply(task.run, posParams, namedParams);

        // The task is added to the completed list for rollback in case of error.
        // If the cancel function is not specified, it is considered that no
        // cancellation is needed for this task.
        if (task.cancel != null) {
          completedTasks.add(task);
        }
      }
    } catch (e) {
      // Cancel all tasks in case of an error.
      cancel();

      // Re-throw the exception.
      rethrow;
    }
  }
}

void main() {
  var c = Chain();
  c.addTask(Task((ChainArgs $context) {
    $context[#b] = 2;
  }));

  c.addTask(Task((int a, int b) {
    print('a = $a');
    print('b = $b');
    print('sum = $a + $b');
  }));
  c.run({#a: 1});
}
@dart-github-bot
Copy link
Collaborator

Summary: User needs cross-platform Dart functionality to extract parameter names and types from functions for a chain execution rollback mechanism. dart:mirrors is not cross-platform, limiting their solution.

@dart-github-bot dart-github-bot added area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. triage-automation See https://github.com/dart-lang/ecosystem/tree/main/pkgs/sdk_triage_bot. type-question A question about expected behavior or functionality labels Dec 7, 2024
@lrhn lrhn added library-mirrors area-web Use area-web for Dart web related issues, including the DDC and dart2js compilers and JS interop. and removed triage-automation See https://github.com/dart-lang/ecosystem/tree/main/pkgs/sdk_triage_bot. labels Dec 8, 2024
@biggs0125
Copy link

You can find extensive discussion on the long term plans for dart:mirrors support here: #44489

In summary, dart:mirrors is considered an unstable API and has too many inherit shortcomings (mostly around its affects on whole program tree-shaking) for it to be worth trying to add more support for it. The eventual goal is to replace it with something that addresses these issues and solves (most of) the same use-cases. But for now it's basically frozen as is.

The Dart team is currently working on implementing macros as a language-level metaprogramming solution that can be a replacement for dart:mirrors in many cases. Though it's not widely available yet, you can find more about that here:
https://dart.dev/language/macros#the-macros-language-feature

It's not as pretty but in the meantime if you need this code to work on all platforms you'll likely need the user to pass the list of FunctionParameters (or some similar representation that lets you derive the list) into the Task constructor for you.

@biggs0125
Copy link

Closing this out as there is no intended work here. @gmb15551 Feel free to comment on #44489 with any additional concerns if you feel macros won't solve your use-case and you need something else from dart:mirrors.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. area-web Use area-web for Dart web related issues, including the DDC and dart2js compilers and JS interop. library-mirrors type-question A question about expected behavior or functionality web-platform
Projects
None yet
Development

No branches or pull requests

4 participants