Generic Reader
Introduction
The premise of source code generation is that we can specify
(hopefully few) details and flesh out the rest of the classes, and methods during the build process.
Dart's static analyzer
provides access to libraries, classes,
class fields, class methods, functions, variables, etc in the form of Elements
.
Source code generation relies heavily on constants known at compile time.
Compile-time constant expressions are represented by a DartObject
and
can be accessed by using the method computeConstantValue()
(available for elements representing a variable).
For built-in types, DartObject
has methods that allow reading the underlying constant object.
It is a more laborious task to read constant values of user defined data-types.
The package generic_reader
includes extentions on
ConstantReader
that simplify reading constants of type List
, Set
, Map
, Enum
and provides a systematic way of reading arbitrary constants of known data-type.
Usage
To use the package generic_reader
the following steps are required:
-
Include
generic_reader
andsource_gen
as dependencies in your pubspec.yaml file. -
Register a Decoder function for each user defined data-type
T
that is going to be read. A decoder function has the signatureT Function(ConstantReader constantReader)
. It reads the constant expression represented byconstantReader
and returns a instance ofT
. Note: The built-in typesbool
,double
,int
,String
,Type
,Symbol
, and Dart enums do not require a decoder function. -
Retrieve the compile-time constant values using the methods
get<T>()
,getList<T>()
,getSet<T>()
,getMap<T>()
. -
Process the retrieved compile-time constants and generate the required source code.
Decoder Functions
The extension GenericReader
provides a systematic method of retrieving constants of
arbitrary data-types by allowing users to register Decoder
functions (for lack of a better a name).
Decoder functions can make use of other registered decoder functions enabling the retrieval of
complex generic data-structures.
Decoders functions know how to decode a specific data-type and have the following signature:
typedef T Decoder<T>(ConstantReader constantReader);
The input argument is of type ConstantReader
, a wrapper around
DartObject
,
and the function returns an object of type T
.
It is required that the input argument constantReader
represents an object of type T
.
User defined types are often a composition of other types, as illustrated in the example below.
Click to show source-code.
enum Title{Mr, Mrs, Dr}
class Age {
const Age(this.age);
final int age;
bool get isAdult => age > 21;
@override
String toString() {
return 'age: $age';
}
}
class Name {
const Name({
required this.firstName,
required this.lastName,
this.middleName = '',
});
final String firstName;
final String lastName;
final String middleName;
@override
String toString() {
return '$firstName ${middleName == '' ? '' : middleName + ' ' }$lastName';
}
}
class User {
const User({
required this.name,
required this.id,
required this.age,
required this.title,
});
final Name name;
final Age age;
final int id;
final Title title;
@override
String toString() {
return 'user: $name\n'
' title: ${title}\n'
' id: $id\n'
' $age\n';
}
}
In order to retrieve a constant value of type User
one has
to retrieve the constructor parameters of type int
, Name
, Title
, and Age
first.
The following shows how to define decoder functions for the types Age
, Name
, and User
.
Note that each decoder knows the constructor parameter-names and parameter-types
of the class it handles. For example, the decoder for User
knows that age
has type Age
and that the field-name is age.
import 'package:generic_reader/generic_reader.dart';
import 'package:source_gen/source_gen.dart' show ConstantReader;
import 'package:test_types/test_types.dart';
/// Defining decoder functions.
Age ageDecoder(ConstantReader constantReader) => Age(constantReader.read('age').intValue);
Name nameDecoder(ConstantReader constantReader) {
final firstName = constantReader.read('firstName').stringValue;
final lastName = constantReader.read('lastName').stringValue;
final middleName = constantReader.read('middleName').stringValue;
return Name(firstName: firstName, lastName: lastName, middleName: middleName);
};
User userDecoder(ConstantReader constantReader){
final id = constantReader.read('id').intValue;
final age = constantReader.read('age').get<Age>();
final name = constantReader.read('name').get<Name>();
final tile = constantReader.read('title').get<Title>();
return User(name: name, age: age, id: id, title: title);
};
// Registering decoders.
GenericReader.addDecoder<Age>(ageDecoder)
GenericReader.addDecoder<Name>(nameDecoder)
GenericReader.addDecoder<User>(userDecoder);
// Reading the library where an object of type User is defined.
// Retrieving the ConstantReader object representing an instance of User:
// constantReaderOfUser.
// Retrieving a constant value of type User:
final User user = reader.get<User>(constantReaderOfUser);
A short program demonstrating how to retrieve a constant of type User
is located at examples/bin/user_example.dart
.
Limitations
-
Constants retrievable with
GenericReader
must have a built-in Dart type, a type made available by depending on a package, or a type defined in the file being read. The functions matching the static type of an analyzer element with the type of a runtime object do not work with relative imports.E.g. the demos in folder
example/bin
read types that are provided by the packagetest_types
located in the subfolder with the same name. -
Defining decoder functions for each data-type has its obvious limitiations when it comes to generic types. In practice, however, generic classes are often designed in such a manner that only few type parameters are valid or likely to be useful. Constants that need to be retrieved during the source-generation process are most likely annotations and simple data-types that convey information to source code generators. A demonstration on how to retrieve constant values with generic type is presented in example.
Examples
For further information on how to use GenericReader to retrieve constants of arbitrary type see example.
Features and bugs
Please file feature requests and bugs at the issue tracker.
Libraries
- generic_reader
- Library providing a customizable generic reader aimed at creating
runtime constant objects from a static representation
of a compile-time constant expression such as
ConstantReader
orDartObject
.