update: updating files with detailed comments
This commit is contained in:
parent
5fd57e1ebd
commit
ea26e024ce
12 changed files with 448 additions and 40 deletions
|
@ -7,10 +7,15 @@
|
|||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
/// The main library for the Protevus runtime package.
|
||||
///
|
||||
/// This library provides core functionality for the Protevus Platform,
|
||||
/// including compilation, analysis, and runtime management.
|
||||
library runtime;
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
// Export statements for various components of the runtime package
|
||||
export 'package:protevus_runtime/src/analyzer.dart';
|
||||
export 'package:protevus_runtime/src/build.dart';
|
||||
export 'package:protevus_runtime/src/build_context.dart';
|
||||
|
@ -21,19 +26,36 @@ export 'package:protevus_runtime/src/exceptions.dart';
|
|||
export 'package:protevus_runtime/src/generator.dart';
|
||||
export 'package:protevus_runtime/src/mirror_coerce.dart';
|
||||
export 'package:protevus_runtime/src/mirror_context.dart';
|
||||
|
||||
import 'package:protevus_runtime/runtime.dart';
|
||||
|
||||
/// Compiler for the runtime package itself.
|
||||
/// A specialized compiler for the runtime package itself.
|
||||
///
|
||||
/// Removes dart:mirror from a replica of this package, and adds
|
||||
/// a generated runtime to the replica's pubspec.
|
||||
/// This compiler is responsible for creating a mirror-free version of the
|
||||
/// runtime package. It removes dart:mirror dependencies and adds a generated
|
||||
/// runtime to the package's pubspec.
|
||||
class RuntimePackageCompiler extends Compiler {
|
||||
/// Compiles the runtime package.
|
||||
///
|
||||
/// This method is currently a no-op, returning an empty map. It may be
|
||||
/// extended in the future to perform actual compilation tasks.
|
||||
///
|
||||
/// [context] - The mirror context for compilation (unused in this implementation).
|
||||
///
|
||||
/// Returns an empty map, as no compilation is currently performed.
|
||||
@override
|
||||
Map<String, Object> compile(MirrorContext context) => {};
|
||||
|
||||
/// Modifies the package structure to remove mirror dependencies.
|
||||
///
|
||||
/// This method performs the following tasks:
|
||||
/// 1. Rewrites the main library file to remove mirror-related exports.
|
||||
/// 2. Updates the context file to use the generated runtime instead of mirror context.
|
||||
/// 3. Modifies the pubspec.yaml to include the generated runtime as a dependency.
|
||||
///
|
||||
/// [destinationDirectory] - The directory where the modified package will be created.
|
||||
@override
|
||||
void deflectPackage(Directory destinationDirectory) {
|
||||
// Rewrite the main library file
|
||||
final libraryFile = File.fromUri(
|
||||
destinationDirectory.uri.resolve("lib/").resolve("runtime.dart"),
|
||||
);
|
||||
|
@ -41,6 +63,7 @@ class RuntimePackageCompiler extends Compiler {
|
|||
"library runtime;\nexport 'src/context.dart';\nexport 'src/exceptions.dart';",
|
||||
);
|
||||
|
||||
// Update the context file
|
||||
final contextFile = File.fromUri(
|
||||
destinationDirectory.uri
|
||||
.resolve("lib/")
|
||||
|
@ -53,6 +76,7 @@ class RuntimePackageCompiler extends Compiler {
|
|||
);
|
||||
contextFile.writeAsStringSync(contextFileContents);
|
||||
|
||||
// Modify the pubspec.yaml
|
||||
final pubspecFile =
|
||||
File.fromUri(destinationDirectory.uri.resolve("pubspec.yaml"));
|
||||
final pubspecContents = pubspecFile.readAsStringSync().replaceFirst(
|
||||
|
|
|
@ -9,12 +9,30 @@
|
|||
|
||||
import 'package:protevus_runtime/runtime.dart';
|
||||
|
||||
/// Prefix string for List types in type checking.
|
||||
const String _listPrefix = "List<";
|
||||
|
||||
/// Prefix string for Map types in type checking.
|
||||
const String _mapPrefix = "Map<String,";
|
||||
|
||||
/// Casts a dynamic input to a specified type T.
|
||||
///
|
||||
/// This function attempts to cast the input to the specified type T.
|
||||
/// It handles nullable types, Lists, and Maps with various element types.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - input: The dynamic value to be cast.
|
||||
///
|
||||
/// Returns:
|
||||
/// The input cast to type T.
|
||||
///
|
||||
/// Throws:
|
||||
/// - [TypeCoercionException] if the casting fails.
|
||||
T cast<T>(dynamic input) {
|
||||
try {
|
||||
var typeString = T.toString();
|
||||
|
||||
// Handle nullable types
|
||||
if (typeString.endsWith('?')) {
|
||||
if (input == null) {
|
||||
return null as T;
|
||||
|
@ -22,11 +40,14 @@ T cast<T>(dynamic input) {
|
|||
typeString = typeString.substring(0, typeString.length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle List types
|
||||
if (typeString.startsWith(_listPrefix)) {
|
||||
if (input is! List) {
|
||||
throw TypeError();
|
||||
}
|
||||
|
||||
// Cast List to various element types
|
||||
if (typeString.startsWith("List<int>")) {
|
||||
return List<int>.from(input) as T;
|
||||
} else if (typeString.startsWith("List<num>")) {
|
||||
|
@ -50,12 +71,16 @@ T cast<T>(dynamic input) {
|
|||
} else if (typeString.startsWith("List<Map<String, dynamic>>")) {
|
||||
return List<Map<String, dynamic>>.from(input) as T;
|
||||
}
|
||||
} else if (typeString.startsWith(_mapPrefix)) {
|
||||
}
|
||||
// Handle Map types
|
||||
else if (typeString.startsWith(_mapPrefix)) {
|
||||
if (input is! Map) {
|
||||
throw TypeError();
|
||||
}
|
||||
|
||||
final inputMap = input as Map<String, dynamic>;
|
||||
|
||||
// Cast Map to various value types
|
||||
if (typeString.startsWith("Map<String, int>")) {
|
||||
return Map<String, int>.from(inputMap) as T;
|
||||
} else if (typeString.startsWith("Map<String, num>")) {
|
||||
|
@ -79,8 +104,10 @@ T cast<T>(dynamic input) {
|
|||
}
|
||||
}
|
||||
|
||||
// If no specific casting is needed, return the input as T
|
||||
return input as T;
|
||||
} on TypeError {
|
||||
// If a TypeError occurs during casting, throw a TypeCoercionException
|
||||
throw TypeCoercionException(T, input.runtimeType);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,11 @@ import 'package:analyzer/dart/ast/ast.dart';
|
|||
import 'package:analyzer/file_system/physical_file_system.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
||||
/// A class for analyzing Dart code.
|
||||
class CodeAnalyzer {
|
||||
/// Constructs a CodeAnalyzer with the given URI.
|
||||
///
|
||||
/// Throws an [ArgumentError] if the URI is not absolute or if no analysis context is found.
|
||||
CodeAnalyzer(this.uri) {
|
||||
if (!uri.isAbsolute) {
|
||||
throw ArgumentError("'uri' must be absolute for CodeAnalyzer");
|
||||
|
@ -27,16 +31,23 @@ class CodeAnalyzer {
|
|||
}
|
||||
}
|
||||
|
||||
/// Gets the path from the URI.
|
||||
String get path {
|
||||
return getPath(uri);
|
||||
}
|
||||
|
||||
/// The URI of the code to analyze.
|
||||
late final Uri uri;
|
||||
|
||||
/// The collection of analysis contexts.
|
||||
late AnalysisContextCollection contexts;
|
||||
|
||||
/// A cache of resolved ASTs.
|
||||
final _resolvedAsts = <String, AnalysisResult>{};
|
||||
|
||||
/// Resolves the unit or library at the given URI.
|
||||
///
|
||||
/// Returns a [Future] that completes with an [AnalysisResult].
|
||||
Future<AnalysisResult> resolveUnitOrLibraryAt(Uri uri) async {
|
||||
if (FileSystemEntity.isFileSync(
|
||||
uri.toFilePath(windows: Platform.isWindows),
|
||||
|
@ -47,6 +58,10 @@ class CodeAnalyzer {
|
|||
}
|
||||
}
|
||||
|
||||
/// Resolves the library at the given URI.
|
||||
///
|
||||
/// Returns a [Future] that completes with a [ResolvedLibraryResult].
|
||||
/// Throws an [ArgumentError] if the URI could not be resolved.
|
||||
Future<ResolvedLibraryResult> resolveLibraryAt(Uri uri) async {
|
||||
assert(
|
||||
FileSystemEntity.isDirectorySync(
|
||||
|
@ -68,6 +83,10 @@ class CodeAnalyzer {
|
|||
"${contexts.contexts.map((c) => c.contextRoot.root.toUri()).join(", ")})");
|
||||
}
|
||||
|
||||
/// Resolves the unit at the given URI.
|
||||
///
|
||||
/// Returns a [Future] that completes with a [ResolvedUnitResult].
|
||||
/// Throws an [ArgumentError] if the URI could not be resolved.
|
||||
Future<ResolvedUnitResult> resolveUnitAt(Uri uri) async {
|
||||
assert(
|
||||
FileSystemEntity.isFileSync(
|
||||
|
@ -89,6 +108,9 @@ class CodeAnalyzer {
|
|||
"${contexts.contexts.map((c) => c.contextRoot.root.toUri()).join(", ")})");
|
||||
}
|
||||
|
||||
/// Gets the class declaration from the file at the given URI.
|
||||
///
|
||||
/// Returns null if the class is not found or if there's an error.
|
||||
ClassDeclaration? getClassFromFile(String className, Uri fileUri) {
|
||||
try {
|
||||
return _getFileAstRoot(fileUri)
|
||||
|
@ -103,6 +125,9 @@ class CodeAnalyzer {
|
|||
}
|
||||
}
|
||||
|
||||
/// Gets all subclasses of the given superclass from the file at the given URI.
|
||||
///
|
||||
/// Returns a list of [ClassDeclaration]s.
|
||||
List<ClassDeclaration> getSubclassesFromFile(
|
||||
String superclassName,
|
||||
Uri fileUri,
|
||||
|
@ -115,6 +140,9 @@ class CodeAnalyzer {
|
|||
.toList();
|
||||
}
|
||||
|
||||
/// Gets the AST root of the file at the given URI.
|
||||
///
|
||||
/// Returns a [CompilationUnit].
|
||||
CompilationUnit _getFileAstRoot(Uri fileUri) {
|
||||
assert(
|
||||
FileSystemEntity.isFileSync(
|
||||
|
@ -135,6 +163,9 @@ class CodeAnalyzer {
|
|||
return unit.unit;
|
||||
}
|
||||
|
||||
/// Converts the input URI to a normalized path string.
|
||||
///
|
||||
/// This is a static utility method.
|
||||
static String getPath(dynamic inputUri) {
|
||||
return PhysicalResourceProvider.INSTANCE.pathContext.normalize(
|
||||
PhysicalResourceProvider.INSTANCE.pathContext.fromUri(inputUri),
|
||||
|
|
|
@ -11,16 +11,34 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:mirrors';
|
||||
|
||||
import 'package:protevus_runtime/runtime.dart';
|
||||
import 'package:io/io.dart';
|
||||
import 'package:package_config/package_config.dart';
|
||||
|
||||
/// A class responsible for building and compiling the application.
|
||||
///
|
||||
/// This class handles the entire build process, including resolving ASTs,
|
||||
/// generating runtime, compiling packages, and creating the final executable.
|
||||
class Build {
|
||||
/// Creates a new [Build] instance with the given [BuildContext].
|
||||
///
|
||||
/// [context] is the build context containing necessary information for the build process.
|
||||
Build(this.context);
|
||||
|
||||
/// The build context for this build operation.
|
||||
final BuildContext context;
|
||||
|
||||
/// Executes the build process.
|
||||
///
|
||||
/// This method performs the following steps:
|
||||
/// 1. Resolves ASTs
|
||||
/// 2. Generates runtime
|
||||
/// 3. Compiles packages
|
||||
/// 4. Prepares the build directory
|
||||
/// 5. Fetches dependencies
|
||||
/// 6. Compiles the final executable (if not for tests)
|
||||
///
|
||||
/// Throws a [StateError] if any step fails.
|
||||
Future execute() async {
|
||||
final compilers = context.context.compilers;
|
||||
|
||||
|
@ -129,6 +147,10 @@ class Build {
|
|||
}
|
||||
}
|
||||
|
||||
/// Fetches dependencies for the project.
|
||||
///
|
||||
/// This method runs 'dart pub get' with the '--offline' and '--no-precompile' flags.
|
||||
/// It throws a [StateError] if the command fails.
|
||||
Future getDependencies() async {
|
||||
const String cmd = "dart";
|
||||
|
||||
|
@ -147,6 +169,13 @@ class Build {
|
|||
}
|
||||
}
|
||||
|
||||
/// Compiles the source file to an executable.
|
||||
///
|
||||
/// [srcUri] is the URI of the source file to compile.
|
||||
/// [dstUri] is the URI where the compiled executable will be saved.
|
||||
///
|
||||
/// This method runs 'dart compile exe' with verbose output.
|
||||
/// It throws a [StateError] if the compilation fails.
|
||||
Future compile(Uri srcUri, Uri dstUri) async {
|
||||
final res = await Process.run(
|
||||
"dart",
|
||||
|
@ -173,6 +202,13 @@ class Build {
|
|||
print("${res.stdout}");
|
||||
}
|
||||
|
||||
/// Copies a package from source to destination.
|
||||
///
|
||||
/// [srcUri] is the URI of the source package.
|
||||
/// [dstUri] is the URI where the package will be copied.
|
||||
///
|
||||
/// This method creates the destination directory if it doesn't exist,
|
||||
/// copies the package contents, and handles Windows-specific file system issues.
|
||||
Future copyPackage(Uri srcUri, Uri dstUri) async {
|
||||
final dstDir = Directory.fromUri(dstUri);
|
||||
if (!dstDir.existsSync()) {
|
||||
|
@ -206,6 +242,12 @@ class Build {
|
|||
);
|
||||
}
|
||||
|
||||
/// Retrieves package information for a given compiler.
|
||||
///
|
||||
/// [compiler] is the compiler instance to get package information for.
|
||||
///
|
||||
/// This method uses reflection to find the source URI of the compiler
|
||||
/// and then retrieves the corresponding package information.
|
||||
Future<Package> _getPackageInfoForCompiler(Compiler compiler) async {
|
||||
final compilerUri = reflect(compiler).type.location!.sourceUri;
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
|
||||
import 'dart:io';
|
||||
import 'dart:mirrors';
|
||||
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:protevus_runtime/runtime.dart';
|
||||
import 'package:package_config/package_config.dart';
|
||||
|
@ -17,8 +16,18 @@ import 'package:path/path.dart';
|
|||
import 'package:pubspec_parse/pubspec_parse.dart';
|
||||
import 'package:yaml/yaml.dart';
|
||||
|
||||
/// Configuration and context values used during [Build.execute].
|
||||
/// BuildContext provides configuration and context values used during the build process.
|
||||
/// It encapsulates information about the application being compiled, build artifacts,
|
||||
/// and provides utility methods for file and package management.
|
||||
class BuildContext {
|
||||
/// Constructs a new BuildContext instance.
|
||||
///
|
||||
/// [rootLibraryFileUri]: The URI of the root library file of the application being compiled.
|
||||
/// [buildDirectoryUri]: The URI of the directory where build artifacts will be stored.
|
||||
/// [executableUri]: The URI of the executable build product file.
|
||||
/// [source]: The source script for the executable.
|
||||
/// [environment]: Optional map of environment variables.
|
||||
/// [forTests]: Boolean flag indicating whether this context is for tests (includes dev dependencies).
|
||||
BuildContext(
|
||||
this.rootLibraryFileUri,
|
||||
this.buildDirectoryUri,
|
||||
|
@ -30,6 +39,12 @@ class BuildContext {
|
|||
analyzer = CodeAnalyzer(sourceApplicationDirectory.uri);
|
||||
}
|
||||
|
||||
/// Creates a BuildContext instance from a map of values.
|
||||
///
|
||||
/// This factory constructor allows for easy serialization and deserialization of BuildContext objects.
|
||||
///
|
||||
/// [map]: A map containing the necessary values to construct a BuildContext.
|
||||
/// Returns: A new BuildContext instance.
|
||||
factory BuildContext.fromMap(Map<String, dynamic> map) {
|
||||
return BuildContext(
|
||||
Uri.parse(map['rootLibraryFileUri']),
|
||||
|
@ -41,6 +56,10 @@ class BuildContext {
|
|||
);
|
||||
}
|
||||
|
||||
/// Returns a map representation of the BuildContext with safe-to-serialize values.
|
||||
///
|
||||
/// This getter is useful for serialization purposes, ensuring that all values are
|
||||
/// in a format that can be easily serialized (e.g., converting URIs to strings).
|
||||
Map<String, dynamic> get safeMap => {
|
||||
'rootLibraryFileUri': sourceLibraryFile.uri.toString(),
|
||||
'buildDirectoryUri': buildDirectoryUri.toString(),
|
||||
|
@ -50,41 +69,56 @@ class BuildContext {
|
|||
'forTests': forTests
|
||||
};
|
||||
|
||||
/// The CodeAnalyzer instance used for analyzing Dart code.
|
||||
late final CodeAnalyzer analyzer;
|
||||
|
||||
/// A [Uri] to the library file of the application to be compiled.
|
||||
/// The URI of the root library file of the application being compiled.
|
||||
final Uri rootLibraryFileUri;
|
||||
|
||||
/// A [Uri] to the executable build product file.
|
||||
/// The URI of the executable build product file.
|
||||
final Uri executableUri;
|
||||
|
||||
/// A [Uri] to directory where build artifacts are stored during the build process.
|
||||
/// The URI of the directory where build artifacts are stored during the build process.
|
||||
final Uri buildDirectoryUri;
|
||||
|
||||
/// The source script for the executable.
|
||||
final String source;
|
||||
|
||||
/// Whether dev dependencies of the application package are included in the dependencies of the compiled executable.
|
||||
/// Indicates whether dev dependencies of the application package should be included
|
||||
/// in the dependencies of the compiled executable.
|
||||
final bool forTests;
|
||||
|
||||
/// Cached PackageConfig instance.
|
||||
PackageConfig? _packageConfig;
|
||||
|
||||
/// Optional map of environment variables.
|
||||
final Map<String, String>? environment;
|
||||
|
||||
/// The [RuntimeContext] available during the build process.
|
||||
/// The current RuntimeContext, cast as a MirrorContext.
|
||||
///
|
||||
/// This getter provides access to the runtime context available during the build process.
|
||||
MirrorContext get context => RuntimeContext.current as MirrorContext;
|
||||
|
||||
/// The URI of the target script file.
|
||||
///
|
||||
/// If [forTests] is true, this will be a test file, otherwise it will be the main application file.
|
||||
Uri get targetScriptFileUri => forTests
|
||||
? getDirectory(buildDirectoryUri.resolve("test/"))
|
||||
.uri
|
||||
.resolve("main_test.dart")
|
||||
: buildDirectoryUri.resolve("main.dart");
|
||||
|
||||
/// The Pubspec of the source application.
|
||||
///
|
||||
/// This getter parses and returns the pubspec.yaml file of the application being compiled.
|
||||
Pubspec get sourceApplicationPubspec => Pubspec.parse(
|
||||
File.fromUri(sourceApplicationDirectory.uri.resolve("pubspec.yaml"))
|
||||
.readAsStringSync(),
|
||||
);
|
||||
|
||||
/// The pubspec of the source application as a map.
|
||||
///
|
||||
/// This getter loads and returns the pubspec.yaml file as a YAML map.
|
||||
Map<dynamic, dynamic> get sourceApplicationPubspecMap => loadYaml(
|
||||
File.fromUri(
|
||||
sourceApplicationDirectory.uri.resolve("pubspec.yaml"),
|
||||
|
@ -101,26 +135,32 @@ class BuildContext {
|
|||
/// The directory where build artifacts are stored.
|
||||
Directory get buildDirectory => getDirectory(buildDirectoryUri);
|
||||
|
||||
/// The generated runtime directory
|
||||
/// The generated runtime directory.
|
||||
Directory get buildRuntimeDirectory =>
|
||||
getDirectory(buildDirectoryUri.resolve("generated_runtime/"));
|
||||
|
||||
/// Directory for compiled packages
|
||||
/// Directory for compiled packages.
|
||||
Directory get buildPackagesDirectory =>
|
||||
getDirectory(buildDirectoryUri.resolve("packages/"));
|
||||
|
||||
/// Directory for compiled application
|
||||
/// Directory for the compiled application.
|
||||
Directory get buildApplicationDirectory => getDirectory(
|
||||
buildPackagesDirectory.uri.resolve("${sourceApplicationPubspec.name}/"),
|
||||
);
|
||||
|
||||
/// Gets dependency package location relative to [sourceApplicationDirectory].
|
||||
/// Gets the dependency package configuration relative to [sourceApplicationDirectory].
|
||||
///
|
||||
/// This getter lazily loads and caches the package configuration.
|
||||
/// Returns: A Future that resolves to a PackageConfig instance.
|
||||
Future<PackageConfig> get packageConfig async {
|
||||
return _packageConfig ??=
|
||||
(await findPackageConfig(sourceApplicationDirectory))!;
|
||||
}
|
||||
|
||||
/// Returns a [Directory] at [uri], creates it recursively if it doesn't exist.
|
||||
/// Returns a [Directory] at the specified [uri], creating it recursively if it doesn't exist.
|
||||
///
|
||||
/// [uri]: The URI of the directory.
|
||||
/// Returns: A Directory instance.
|
||||
Directory getDirectory(Uri uri) {
|
||||
final dir = Directory.fromUri(uri);
|
||||
if (!dir.existsSync()) {
|
||||
|
@ -129,7 +169,10 @@ class BuildContext {
|
|||
return dir;
|
||||
}
|
||||
|
||||
/// Returns a [File] at [uri], creates all parent directories recursively if necessary.
|
||||
/// Returns a [File] at the specified [uri], creating all parent directories recursively if necessary.
|
||||
///
|
||||
/// [uri]: The URI of the file.
|
||||
/// Returns: A File instance.
|
||||
File getFile(Uri uri) {
|
||||
final file = File.fromUri(uri);
|
||||
if (!file.parent.existsSync()) {
|
||||
|
@ -139,6 +182,11 @@ class BuildContext {
|
|||
return file;
|
||||
}
|
||||
|
||||
/// Retrieves a Package instance from a given URI.
|
||||
///
|
||||
/// [uri]: The URI to resolve to a package.
|
||||
/// Returns: A Future that resolves to a Package instance, or null if not found.
|
||||
/// Throws: ArgumentError if the URI is not absolute or a package URI.
|
||||
Future<Package?> getPackageFromUri(Uri? uri) async {
|
||||
if (uri == null) {
|
||||
return null;
|
||||
|
@ -152,6 +200,13 @@ class BuildContext {
|
|||
return null;
|
||||
}
|
||||
|
||||
/// Retrieves import directives from a URI or source code.
|
||||
///
|
||||
/// [uri]: The URI of the file to analyze.
|
||||
/// [source]: The source code to analyze.
|
||||
/// [alsoImportOriginalFile]: Whether to include an import for the original file.
|
||||
/// Returns: A Future that resolves to a list of import directive strings.
|
||||
/// Throws: ArgumentError for invalid combinations of parameters.
|
||||
Future<List<String>> getImportDirectives({
|
||||
Uri? uri,
|
||||
String? source,
|
||||
|
@ -201,6 +256,10 @@ class BuildContext {
|
|||
return imports;
|
||||
}
|
||||
|
||||
/// Retrieves a ClassDeclaration from a given Type.
|
||||
///
|
||||
/// [type]: The Type to analyze.
|
||||
/// Returns: A Future that resolves to a ClassDeclaration, or null if not found.
|
||||
Future<ClassDeclaration?> getClassDeclarationFromType(Type type) async {
|
||||
final classMirror = reflectType(type);
|
||||
Uri uri = classMirror.location!.sourceUri;
|
||||
|
@ -214,6 +273,11 @@ class BuildContext {
|
|||
);
|
||||
}
|
||||
|
||||
/// Retrieves a FieldDeclaration from a ClassMirror and property name.
|
||||
///
|
||||
/// [type]: The ClassMirror to analyze.
|
||||
/// [propertyName]: The name of the property to find.
|
||||
/// Returns: A Future that resolves to a FieldDeclaration, or null if not found.
|
||||
Future<FieldDeclaration?> _getField(ClassMirror type, String propertyName) {
|
||||
return getClassDeclarationFromType(type.reflectedType).then((cd) {
|
||||
try {
|
||||
|
@ -229,6 +293,13 @@ class BuildContext {
|
|||
});
|
||||
}
|
||||
|
||||
/// Retrieves annotations from a field of a given Type.
|
||||
///
|
||||
/// This method searches for the field in the given type and its superclasses.
|
||||
///
|
||||
/// [type1]: The Type to analyze.
|
||||
/// [propertyName]: The name of the property to find.
|
||||
/// Returns: A Future that resolves to a list of Annotations.
|
||||
Future<List<Annotation>> getAnnotationsFromField(
|
||||
Type type1,
|
||||
String propertyName,
|
||||
|
|
|
@ -8,20 +8,31 @@
|
|||
*/
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:analyzer/dart/analysis/results.dart';
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:protevus_isolate/isolate.dart';
|
||||
import 'package:protevus_runtime/runtime.dart';
|
||||
import 'package:io/io.dart';
|
||||
|
||||
/// A class that represents an executable build process.
|
||||
///
|
||||
/// This class extends the [Executable] class and is responsible for
|
||||
/// executing a build process based on the provided build context.
|
||||
class BuildExecutable extends Executable {
|
||||
/// Constructs a new [BuildExecutable] instance.
|
||||
///
|
||||
/// [message] is a map containing the build context information.
|
||||
BuildExecutable(Map<String, dynamic> message) : super(message) {
|
||||
context = BuildContext.fromMap(message);
|
||||
}
|
||||
|
||||
/// The build context for this executable.
|
||||
late final BuildContext context;
|
||||
|
||||
/// Executes the build process.
|
||||
///
|
||||
/// This method creates a new [Build] instance with the current context
|
||||
/// and calls its execute method.
|
||||
@override
|
||||
Future execute() async {
|
||||
final build = Build(context);
|
||||
|
@ -29,21 +40,33 @@ class BuildExecutable extends Executable {
|
|||
}
|
||||
}
|
||||
|
||||
/// A class that manages the build process for non-mirrored builds.
|
||||
class BuildManager {
|
||||
/// Creates a new build manager to compile a non-mirrored build.
|
||||
/// Creates a new [BuildManager] instance.
|
||||
///
|
||||
/// [context] is the [BuildContext] for this build manager.
|
||||
BuildManager(this.context);
|
||||
|
||||
/// The build context for this manager.
|
||||
final BuildContext context;
|
||||
|
||||
/// Gets the URI of the source directory.
|
||||
Uri get sourceDirectoryUri => context.sourceApplicationDirectory.uri;
|
||||
|
||||
/// Performs the build process.
|
||||
///
|
||||
/// This method handles the following steps:
|
||||
/// 1. Creates the build directory if it doesn't exist.
|
||||
/// 2. Creates a temporary copy of the script file with the main function stripped.
|
||||
/// 3. Analyzes the script file and removes all main functions.
|
||||
/// 4. Copies the 'not_tests' directory if it exists.
|
||||
/// 5. Runs the build executable in an isolate.
|
||||
Future build() async {
|
||||
if (!context.buildDirectory.existsSync()) {
|
||||
context.buildDirectory.createSync();
|
||||
}
|
||||
|
||||
// Here is where we need to provide a temporary copy of the script file with the main function stripped;
|
||||
// this is because when the RuntimeGenerator loads, it needs Mirror access to any declarations in this file
|
||||
// Create a temporary copy of the script file with the main function stripped
|
||||
var scriptSource = context.source;
|
||||
final strippedScriptFile = File.fromUri(context.targetScriptFileUri)
|
||||
..writeAsStringSync(scriptSource);
|
||||
|
@ -52,6 +75,7 @@ class BuildManager {
|
|||
final parsedUnit = analyzerContext.currentSession
|
||||
.getParsedUnit(analyzer.path) as ParsedUnitResult;
|
||||
|
||||
// Find and remove all main functions
|
||||
final mainFunctions = parsedUnit.unit.declarations
|
||||
.whereType<FunctionDeclaration>()
|
||||
.where((f) => f.name.value() == "main")
|
||||
|
@ -63,12 +87,14 @@ class BuildManager {
|
|||
|
||||
strippedScriptFile.writeAsStringSync(scriptSource);
|
||||
|
||||
// Copy the 'not_tests' directory if it exists
|
||||
try {
|
||||
await copyPath(
|
||||
context.sourceApplicationDirectory.uri.resolve('test/not_tests').path,
|
||||
context.buildDirectoryUri.resolve('not_tests').path);
|
||||
} catch (_) {}
|
||||
|
||||
// Run the build executable in an isolate
|
||||
await IsolateExecutor.run(
|
||||
BuildExecutable(context.safeMap),
|
||||
packageConfigURI:
|
||||
|
@ -81,6 +107,9 @@ class BuildManager {
|
|||
);
|
||||
}
|
||||
|
||||
/// Cleans up the build directory.
|
||||
///
|
||||
/// This method deletes the build directory and its contents if it exists.
|
||||
Future clean() async {
|
||||
if (context.buildDirectory.existsSync()) {
|
||||
context.buildDirectory.deleteSync(recursive: true);
|
||||
|
|
|
@ -8,32 +8,70 @@
|
|||
*/
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:protevus_runtime/runtime.dart';
|
||||
|
||||
abstract class Compiler {
|
||||
/// Modifies a package on the filesystem in order to remove dart:mirrors from the package.
|
||||
/// An abstract class that defines the interface for compilers in the Protevus Platform.
|
||||
///
|
||||
/// A copy of this compiler's package will be written to [destinationDirectory].
|
||||
/// This method is overridden to modify the contents of that directory
|
||||
/// to remove all uses of dart:mirrors.
|
||||
/// This class provides methods for modifying packages, compiling runtime objects,
|
||||
/// and handling post-generation tasks. Implementations of this class are used to
|
||||
/// remove dart:mirrors usage from packages and prepare them for runtime execution.
|
||||
abstract class Compiler {
|
||||
/// Modifies a package on the filesystem to remove dart:mirrors usage.
|
||||
///
|
||||
/// This method creates a copy of the compiler's package in the [destinationDirectory]
|
||||
/// and modifies its contents to remove all uses of dart:mirrors. This is crucial for
|
||||
/// preparing packages for environments where reflection is not available or desired.
|
||||
///
|
||||
/// Packages should export their [Compiler] in their main library file and only
|
||||
/// import mirrors in files directly or transitively imported by the Compiler file.
|
||||
/// This method should remove that export statement and therefore remove all transitive mirror imports.
|
||||
///
|
||||
/// [destinationDirectory] The directory where the modified package will be written.
|
||||
void deflectPackage(Directory destinationDirectory);
|
||||
|
||||
/// Returns a map of runtime objects that can be used at runtime while running in mirrored mode.
|
||||
/// Compiles and returns a map of runtime objects for use in mirrored mode.
|
||||
///
|
||||
/// This method is responsible for creating runtime representations of objects
|
||||
/// that can be used when the application is running in mirrored mode.
|
||||
///
|
||||
/// [context] The [MirrorContext] providing reflection capabilities for compilation.
|
||||
///
|
||||
/// Returns a [Map] where keys are String identifiers and values are compiled Objects.
|
||||
Map<String, Object> compile(MirrorContext context);
|
||||
|
||||
/// A hook method called after package generation is complete.
|
||||
///
|
||||
/// This method can be overridden to perform any necessary cleanup or additional
|
||||
/// tasks after the package has been generated.
|
||||
///
|
||||
/// [context] The [BuildContext] containing information about the build process.
|
||||
void didFinishPackageGeneration(BuildContext context) {}
|
||||
|
||||
/// Returns a list of URIs that need to be resolved during the build process.
|
||||
///
|
||||
/// This method can be overridden to specify additional URIs that should be
|
||||
/// resolved as part of the compilation process.
|
||||
///
|
||||
/// [context] The [BuildContext] containing information about the build process.
|
||||
///
|
||||
/// Returns a [List] of [Uri] objects to be resolved.
|
||||
List<Uri> getUrisToResolve(BuildContext context) => [];
|
||||
}
|
||||
|
||||
/// Runtimes that generate source code implement this method.
|
||||
/// An abstract class for compilers that generate source code.
|
||||
///
|
||||
/// This class extends the functionality of [Compiler] to include
|
||||
/// the ability to generate source code that represents the runtime behavior.
|
||||
abstract class SourceCompiler {
|
||||
/// The source code, including directives, that declare a class that is equivalent in behavior to this runtime.
|
||||
/// Generates source code that declares a class equivalent in behavior to this runtime.
|
||||
///
|
||||
/// This method should be implemented to produce a string of Dart source code
|
||||
/// that includes all necessary directives and class declarations to replicate
|
||||
/// the behavior of the runtime in a static context.
|
||||
///
|
||||
/// [ctx] The [BuildContext] containing information about the build process.
|
||||
///
|
||||
/// Returns a [Future] that resolves to a [String] containing the generated source code.
|
||||
Future<String> compile(BuildContext ctx) async {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
|
|
@ -10,20 +10,26 @@
|
|||
import 'package:protevus_runtime/runtime.dart';
|
||||
|
||||
/// Contextual values used during runtime.
|
||||
///
|
||||
/// This abstract class defines the structure for runtime contexts in the Protevus Platform.
|
||||
/// It provides access to runtime objects and coercion functionality.
|
||||
abstract class RuntimeContext {
|
||||
/// The current [RuntimeContext] available to the executing application.
|
||||
///
|
||||
/// Is either a `MirrorContext` or a `GeneratedContext`,
|
||||
/// This static property holds either a `MirrorContext` or a `GeneratedContext`,
|
||||
/// depending on the execution type.
|
||||
static final RuntimeContext current = instance;
|
||||
|
||||
/// The runtimes available to the executing application.
|
||||
///
|
||||
/// This property stores a collection of runtime objects that can be accessed
|
||||
/// during the application's execution.
|
||||
late RuntimeCollection runtimes;
|
||||
|
||||
/// Gets a runtime object for [type].
|
||||
/// Gets a runtime object for the specified [type].
|
||||
///
|
||||
/// Callers typically invoke this method, passing their [runtimeType]
|
||||
/// in order to retrieve their runtime object.
|
||||
/// Callers typically invoke this operator, passing their [runtimeType]
|
||||
/// to retrieve their runtime object.
|
||||
///
|
||||
/// It is important to note that a runtime object must exist for every
|
||||
/// class that extends a class that has a runtime. Use `MirrorContext.getSubclassesOf` when compiling.
|
||||
|
@ -31,18 +37,44 @@ abstract class RuntimeContext {
|
|||
/// In other words, if the type `Base` has a runtime and the type `Subclass` extends `Base`,
|
||||
/// `Subclass` must also have a runtime. The runtime objects for both `Subclass` and `Base`
|
||||
/// must be the same type.
|
||||
///
|
||||
/// @param type The Type to retrieve the runtime object for.
|
||||
/// @return The runtime object associated with the specified type.
|
||||
dynamic operator [](Type type) => runtimes[type];
|
||||
|
||||
/// Coerces the given [input] to the specified type [T].
|
||||
///
|
||||
/// This method is used to convert or cast the input to the desired type.
|
||||
///
|
||||
/// @param input The input value to be coerced.
|
||||
/// @return The coerced value of type [T].
|
||||
T coerce<T>(dynamic input);
|
||||
}
|
||||
|
||||
/// A collection of runtime objects indexed by type names.
|
||||
///
|
||||
/// This class provides a way to store and retrieve runtime objects
|
||||
/// associated with specific types.
|
||||
class RuntimeCollection {
|
||||
/// Creates a new [RuntimeCollection] with the given [map].
|
||||
///
|
||||
/// @param map A map where keys are type names and values are runtime objects.
|
||||
RuntimeCollection(this.map);
|
||||
|
||||
/// The underlying map storing runtime objects.
|
||||
final Map<String, Object> map;
|
||||
|
||||
/// Returns an iterable of all runtime objects in the collection.
|
||||
Iterable<Object> get iterable => map.values;
|
||||
|
||||
/// Retrieves the runtime object for the specified type [t].
|
||||
///
|
||||
/// This operator first attempts to find an exact match for the type name.
|
||||
/// If not found, it tries to match a generic type by removing type parameters.
|
||||
///
|
||||
/// @param t The Type to retrieve the runtime object for.
|
||||
/// @return The runtime object associated with the specified type.
|
||||
/// @throws ArgumentError if no runtime object is found for the given type.
|
||||
Object operator [](Type t) {
|
||||
//todo: optimize by keeping a cache where keys are of type [Type] to avoid the
|
||||
// expensive indexOf and substring calls in this method
|
||||
|
@ -67,10 +99,10 @@ class RuntimeCollection {
|
|||
}
|
||||
}
|
||||
|
||||
/// Prevents a type from being compiled when it otherwise would be.
|
||||
/// An annotation to prevent a type from being compiled when it otherwise would be.
|
||||
///
|
||||
/// Annotate a type with the const instance of this type to prevent its
|
||||
/// compilation.
|
||||
/// Use this annotation on a type to exclude it from compilation.
|
||||
class PreventCompilation {
|
||||
/// Creates a constant instance of [PreventCompilation].
|
||||
const PreventCompilation();
|
||||
}
|
||||
|
|
|
@ -7,13 +7,38 @@
|
|||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
/// Exception thrown when type coercion fails.
|
||||
///
|
||||
/// This exception is used to indicate that an attempt to convert a value
|
||||
/// from one type to another has failed. It provides information about
|
||||
/// the expected type and the actual type of the value.
|
||||
class TypeCoercionException implements Exception {
|
||||
/// Creates a new [TypeCoercionException].
|
||||
///
|
||||
/// [expectedType] is the type that was expected for the conversion.
|
||||
/// [actualType] is the actual type of the value that couldn't be converted.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// throw TypeCoercionException(String, int);
|
||||
/// ```
|
||||
TypeCoercionException(this.expectedType, this.actualType);
|
||||
|
||||
/// The type that was expected for the conversion.
|
||||
final Type expectedType;
|
||||
|
||||
/// The actual type of the value that couldn't be converted.
|
||||
final Type actualType;
|
||||
|
||||
@override
|
||||
|
||||
/// Returns a string representation of the exception.
|
||||
///
|
||||
/// The returned string includes both the expected type and the actual type
|
||||
/// to provide clear information about the nature of the coercion failure.
|
||||
///
|
||||
/// Returns:
|
||||
/// A string describing the type coercion exception.
|
||||
String toString() {
|
||||
return "input is not expected type '$expectedType' (input is '$actualType')";
|
||||
}
|
||||
|
|
|
@ -10,16 +10,32 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
/// Token used to replace directives in the loader shell.
|
||||
const String _directiveToken = "___DIRECTIVES___";
|
||||
|
||||
/// Token used to replace assignments in the loader shell.
|
||||
const String _assignmentToken = "___ASSIGNMENTS___";
|
||||
|
||||
/// A class responsible for generating runtime code.
|
||||
///
|
||||
/// This class allows adding runtime elements and writing them to a specified directory.
|
||||
class RuntimeGenerator {
|
||||
/// List of runtime elements to be generated.
|
||||
final _elements = <_RuntimeElement>[];
|
||||
|
||||
/// Adds a new runtime element to the generator.
|
||||
///
|
||||
/// [name] is the name of the runtime element.
|
||||
/// [source] is the source code of the runtime element.
|
||||
void addRuntime({required String name, required String source}) {
|
||||
_elements.add(_RuntimeElement(name, source));
|
||||
}
|
||||
|
||||
/// Writes the generated runtime code to the specified directory.
|
||||
///
|
||||
/// [directoryUri] is the URI of the directory where the code will be written.
|
||||
/// This method creates necessary directories, writes the library file,
|
||||
/// pubspec file, and individual runtime element files.
|
||||
Future<void> writeTo(Uri directoryUri) async {
|
||||
final dir = Directory.fromUri(directoryUri);
|
||||
final libDir = Directory.fromUri(dir.uri.resolve("lib/"));
|
||||
|
@ -48,6 +64,7 @@ class RuntimeGenerator {
|
|||
});
|
||||
}
|
||||
|
||||
/// Returns the content of the pubspec.yaml file as a string.
|
||||
String get pubspecSource => """
|
||||
name: generated_runtime
|
||||
description: A runtime generated by package:conduit_runtime
|
||||
|
@ -57,6 +74,9 @@ environment:
|
|||
sdk: '>=3.4.0 <4.0.0'
|
||||
""";
|
||||
|
||||
/// Returns the shell of the loader file as a string.
|
||||
///
|
||||
/// This shell contains placeholders for directives and assignments.
|
||||
String get _loaderShell => """
|
||||
import 'package:conduit_runtime/runtime.dart';
|
||||
import 'package:conduit_runtime/slow_coerce.dart' as runtime_cast;
|
||||
|
@ -80,12 +100,17 @@ class GeneratedContext extends RuntimeContext {
|
|||
}
|
||||
""";
|
||||
|
||||
/// Returns the complete source of the loader file.
|
||||
///
|
||||
/// This method replaces the directive and assignment tokens in the loader shell
|
||||
/// with the actual directives and assignments.
|
||||
String get loaderSource {
|
||||
return _loaderShell
|
||||
.replaceFirst(_directiveToken, _directives)
|
||||
.replaceFirst(_assignmentToken, _assignments);
|
||||
}
|
||||
|
||||
/// Generates the import directives for all runtime elements.
|
||||
String get _directives {
|
||||
final buf = StringBuffer();
|
||||
|
||||
|
@ -98,6 +123,7 @@ class GeneratedContext extends RuntimeContext {
|
|||
return buf.toString();
|
||||
}
|
||||
|
||||
/// Generates the assignments for all runtime elements.
|
||||
String get _assignments {
|
||||
final buf = StringBuffer();
|
||||
|
||||
|
@ -109,14 +135,24 @@ class GeneratedContext extends RuntimeContext {
|
|||
}
|
||||
}
|
||||
|
||||
/// A class representing a single runtime element.
|
||||
class _RuntimeElement {
|
||||
/// Creates a new runtime element.
|
||||
///
|
||||
/// [typeName] is the name of the type for this runtime element.
|
||||
/// [source] is the source code of this runtime element.
|
||||
_RuntimeElement(this.typeName, this.source);
|
||||
|
||||
/// The name of the type for this runtime element.
|
||||
final String typeName;
|
||||
|
||||
/// The source code of this runtime element.
|
||||
final String source;
|
||||
|
||||
/// Returns the relative URI for this runtime element's file.
|
||||
Uri get relativeUri => Uri.file("${typeName.toLowerCase()}.dart");
|
||||
|
||||
/// Returns the import alias for this runtime element.
|
||||
String get importAlias {
|
||||
return "g_${typeName.toLowerCase()}";
|
||||
}
|
||||
|
|
|
@ -8,9 +8,23 @@
|
|||
*/
|
||||
|
||||
import 'dart:mirrors';
|
||||
|
||||
import 'package:protevus_runtime/runtime.dart';
|
||||
|
||||
/// Attempts to cast an object to a specified type at runtime.
|
||||
///
|
||||
/// This function uses Dart's mirror system to perform runtime type checking
|
||||
/// and casting. It handles casting to List and Map types, including their
|
||||
/// generic type arguments.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - [object]: The object to be cast.
|
||||
/// - [intoType]: A [TypeMirror] representing the type to cast into.
|
||||
///
|
||||
/// Returns:
|
||||
/// The object cast to the specified type.
|
||||
///
|
||||
/// Throws:
|
||||
/// - [TypeCoercionException] if the casting fails.
|
||||
Object runtimeCast(Object object, TypeMirror intoType) {
|
||||
final exceptionToThrow =
|
||||
TypeCoercionException(intoType.reflectedType, object.runtimeType);
|
||||
|
@ -51,6 +65,16 @@ Object runtimeCast(Object object, TypeMirror intoType) {
|
|||
throw exceptionToThrow;
|
||||
}
|
||||
|
||||
/// Determines if a given type is fully primitive.
|
||||
///
|
||||
/// A type is considered fully primitive if it's a basic type (num, String, bool),
|
||||
/// dynamic, or a collection (List, Map) where all nested types are also primitive.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - [type]: A [TypeMirror] representing the type to check.
|
||||
///
|
||||
/// Returns:
|
||||
/// true if the type is fully primitive, false otherwise.
|
||||
bool isTypeFullyPrimitive(TypeMirror type) {
|
||||
if (type == reflectType(dynamic)) {
|
||||
return true;
|
||||
|
|
|
@ -8,12 +8,19 @@
|
|||
*/
|
||||
|
||||
import 'dart:mirrors';
|
||||
|
||||
import 'package:protevus_runtime/runtime.dart';
|
||||
|
||||
/// Global instance of the MirrorContext.
|
||||
RuntimeContext instance = MirrorContext._();
|
||||
|
||||
/// A runtime context implementation using Dart's mirror system.
|
||||
///
|
||||
/// This class provides runtime type information and compilation capabilities
|
||||
/// using reflection.
|
||||
class MirrorContext extends RuntimeContext {
|
||||
/// Private constructor to ensure singleton instance.
|
||||
///
|
||||
/// Initializes the context by compiling all available runtimes.
|
||||
MirrorContext._() {
|
||||
final m = <String, Object>{};
|
||||
|
||||
|
@ -31,6 +38,9 @@ class MirrorContext extends RuntimeContext {
|
|||
runtimes = RuntimeCollection(m);
|
||||
}
|
||||
|
||||
/// List of all class mirrors in the current mirror system.
|
||||
///
|
||||
/// Excludes classes marked with @PreventCompilation.
|
||||
final List<ClassMirror> types = currentMirrorSystem()
|
||||
.libraries
|
||||
.values
|
||||
|
@ -40,6 +50,9 @@ class MirrorContext extends RuntimeContext {
|
|||
.where((cm) => firstMetadataOfType<PreventCompilation>(cm) == null)
|
||||
.toList();
|
||||
|
||||
/// List of all available compilers.
|
||||
///
|
||||
/// Returns instances of non-abstract classes that are subclasses of Compiler.
|
||||
List<Compiler> get compilers {
|
||||
return types
|
||||
.where((b) => b.isSubclassOf(reflectClass(Compiler)) && !b.isAbstract)
|
||||
|
@ -47,6 +60,10 @@ class MirrorContext extends RuntimeContext {
|
|||
.toList();
|
||||
}
|
||||
|
||||
/// Retrieves all non-abstract subclasses of a given type.
|
||||
///
|
||||
/// [type] The base type to find subclasses of.
|
||||
/// Returns a list of ClassMirror objects representing the subclasses.
|
||||
List<ClassMirror> getSubclassesOf(Type type) {
|
||||
final mirror = reflectClass(type);
|
||||
return types.where((decl) {
|
||||
|
@ -68,6 +85,13 @@ class MirrorContext extends RuntimeContext {
|
|||
}).toList();
|
||||
}
|
||||
|
||||
/// Coerces an input to a specified type T.
|
||||
///
|
||||
/// Attempts to cast the input directly, and if that fails,
|
||||
/// uses runtime casting.
|
||||
///
|
||||
/// [input] The object to be coerced.
|
||||
/// Returns the coerced object of type T.
|
||||
@override
|
||||
T coerce<T>(dynamic input) {
|
||||
try {
|
||||
|
@ -78,6 +102,11 @@ class MirrorContext extends RuntimeContext {
|
|||
}
|
||||
}
|
||||
|
||||
/// Retrieves the first metadata annotation of a specific type from a declaration.
|
||||
///
|
||||
/// [dm] The DeclarationMirror to search for metadata.
|
||||
/// [dynamicType] Optional TypeMirror to use instead of T.
|
||||
/// Returns the first metadata of type T, or null if not found.
|
||||
T? firstMetadataOfType<T>(DeclarationMirror dm, {TypeMirror? dynamicType}) {
|
||||
final tMirror = dynamicType ?? reflectType(T);
|
||||
try {
|
||||
|
|
Loading…
Reference in a new issue