google / reflectable.dart

Reflectable is a Dart library that allows programmers to eliminate certain usages of dynamic reflection by specialization of reflective code to an equivalent implementation using only static techniques. The use of dynamic reflection is constrained in order to ensure that the specialized code can be generated and will have a reasonable size.
https://pub.dev/packages/reflectable
BSD 3-Clause "New" or "Revised" License
374 stars 56 forks source link

Reflectable don't find "main" but im using in my library not an application #312

Closed paulocoutinhox closed 5 months ago

paulocoutinhox commented 1 year ago

Hi,

Im trying add support for reflectable into my library XPLPC (https://github.com/xplpc/xplpc), but when i add the annotation of my reflectable constant and run the builder it always create a message with contents // No output from reflectable, there is no "main".

import 'package:xplpc/reflectable/reflector.dart';

@reflector
abstract class Codable<T> {
  Map<String, dynamic> encode();
  T decode(Map<String, dynamic> data);
}

import 'dart:ffi' as ffi;
import 'dart:typed_data';

import "package:ffi/ffi.dart";
import 'package:xplpc/reflectable/reflector.dart';
import 'package:xplpc/type/codable.dart';

@reflector
class DataView implements Codable {
  late int ptr;
  late int size;

  DataView(this.ptr, this.size);

  static DataView createFromByteBuffer(ffi.Pointer<ffi.Uint8> data, int size) {
    return DataView(data.address, size);
  }

  static DataView createFromByteArray(Uint8List data) {
    final blob = calloc<ffi.Uint8>(data.length);
    final blobBytes = blob.asTypedList(data.length);
    blobBytes.setAll(0, data);
    return DataView(blob.address, data.length);
  }

  Map<String, dynamic> toJson() {
    return {
      'ptr': ptr,
      'size': size,
    };
  }

  DataView.fromJson(Map<String, dynamic> json) {
    ptr = json['ptr'];
    size = json['size'];
  }

  @override
  decode(Map<String, dynamic> data) {
    return DataView.fromJson(data);
  }

  @override
  Map<String, dynamic> encode() {
    return toJson();
  }
}

My build.xml:

targets:
  $default:
    builders:
      reflectable:
        generate_for:
          - lib/core/xplpc.dart
        options:
          formatted: true

I set the tag "generate_for" to lib/core/xplpc.dart because the file xplpc.dart is when i want initialize it:

import 'dart:ffi' as ffi;
import 'dart:io';

import 'package:xplpc/core/config.dart';
import 'package:xplpc/proxy/platform_proxy.dart';
import 'package:xplpc/reflectable/reflector.dart';

class XPLPC {
  // singleton
  static XPLPC? _instance;
  XPLPC._();
  static XPLPC get instance => _instance ??= XPLPC._();

  // native library
  late ffi.DynamicLibrary library;

  // properties
  bool initialized = false;
  late Config config;
  var reflector = const Reflector();

  // methods
  void initialize(Config config) {
    if (initialized) {
      return;
    }

    initialized = true;

    this.config = config;

    loadLibrary();
    loadPlatformProxy();
    loadReflectable();
  }

  void loadLibrary() {
    var openDirect = false;
    late String openPath;

    if (Platform.isAndroid) {
      openPath = "libxplpc.so";
    } else {
      openDirect = true;
    }

    if (Platform.environment.containsKey('FLUTTER_TEST')) {
      openDirect = false;
      openPath =
          "../../build/flutter-macos-xcframework/xplpc.xcframework/macos-arm64_x86_64/xplpc.framework/Versions/A/xplpc";
    }

    if (openDirect) {
      library = ffi.DynamicLibrary.process();
    } else {
      library = ffi.DynamicLibrary.open(openPath);
    }
  }

  void loadPlatformProxy() {
    PlatformProxy.initialize();
  }

  void loadReflectable() {
    initializeReflectable(); // this method need be generated
  }
}

I don't have a main.dart because it is a library.

What im doing wrong?

Thanks.

eernstg commented 1 year ago

The reflectable code generator is intended to run on an application, because it supports including reflective data about declarations that are not imported (directly or indirectly) by any particular library other than the entry point (that is, the library in your application that exports the function main).

For example, you could have a reflector reflector with a subtypeQuantifyCapability, and then in a library L you could annotate a given class C with @reflector.

This means that the code generator must generate code such that an instance of any subtype (in the entire application, not just the ones that are in scope in L, or even the ones that are accessible via one or more import steps from L) gets the reflection support which is specified by reflector.

This is only possible if the code generator is executed on a library from which every library of the application is reachable via zero or more import steps; and that is in general only true for the library that contains main, that is, your 'main.dart', or whatever the name is. (OK, 'main.dart' could also re-export a main function which is declared in some other library, but that special case is probably not used often in practice.)

So if you want to be able to reflect on anything that clients of your package declare then you'd need to ask your clients to run the code generator on their applications. See https://pub.dev/packages?q=dependency%3Areflectable for some examples.

If you only want to use reflection on declarations that are declared in your library (let's call it L0) or in libraries imported (directly or indirectly) from L0 then you can just create a dummy 'main.dart' that imports L0 and use that 'main.dart' as the argument to the reflectable code generator. This will generate a library with data that supports reflective access to declarations (and/or instances thereof) which are reachable from L0. But that won't cover any code outside the libraries reachable from L0, so you need to know that this is sufficient for your purpose.

paulocoutinhox commented 1 year ago

Is possible have the two ways in my case?

Because i need make unit tests on that class that is reflectable and, at the same time, i can explain to users of my lib that they need use reflectable (like in my example i will need do this).

eernstg commented 1 year ago

I would expect that you can just consider your unit tests to be applications (similarly to https://github.com/google/reflectable.dart/tree/master/test_reflectable/test).

paulocoutinhox commented 1 year ago

Ok,

Im starting from tests to check if everything will work.

import 'package:reflectable/reflectable.dart';

class Reflector extends Reflectable {
  const Reflector()
      : super(
          declarationsCapability,
          invokingCapability,
          metadataCapability,
          newInstanceCapability,
          typeRelationsCapability,
          typeCapability,
        );
}

const reflector = Reflector();

import 'dart:ffi' as ffi;
import 'dart:typed_data';

import "package:ffi/ffi.dart";
import 'package:xplpc/reflectable/reflector.dart';

@reflector
class DataView {
  late int ptr;
  late int size;

  DataView(this.ptr, this.size);

  static DataView createFromByteBuffer(ffi.Pointer<ffi.Uint8> data, int size) {
    return DataView(data.address, size);
  }

  static DataView createFromByteArray(Uint8List data) {
    final blob = calloc<ffi.Uint8>(data.length);
    final blobBytes = blob.asTypedList(data.length);
    blobBytes.setAll(0, data);
    return DataView(blob.address, data.length);
  }

  Map<String, dynamic> toJson() {
    return {
      'ptr': ptr,
      'size': size,
    };
  }

  DataView.fromJson(Map<String, dynamic> json) {
    ptr = json['ptr'];
    size = json['size'];
  }
}

But im getting this error:

"Attempt to evaluate `isAssignableTo` for `.DataView` without capability."
image
paulocoutinhox commented 1 year ago

Hi,

I solve the problem changing from typeMirror.isAssignableTo(codableType) to typeMirror == codableType and it works.

I will try apply to my example now.

Thanks.