Skip to content

Commit

Permalink
Merge a0f55e1 into 81ed943
Browse files Browse the repository at this point in the history
  • Loading branch information
nielsenko committed Mar 7, 2022
2 parents 81ed943 + a0f55e1 commit a08b58b
Show file tree
Hide file tree
Showing 23 changed files with 373 additions and 229 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Expand Up @@ -99,6 +99,7 @@
},
"dart.lineLength": 160,
"cSpell.words": [
"Finalizable"
"Finalizable",
"Unmanaged"
]
}
4 changes: 2 additions & 2 deletions example/bin/myapp.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion generator/lib/src/class_element_ex.dart
Expand Up @@ -169,7 +169,6 @@ extension ClassElementEx on ClassElement {
rethrow;
} catch (e, s) {
// Fallback. Not perfect, but better than just forwarding original error.
print(s);
throw RealmInvalidGenerationSourceError(
'$e',
todo: //
Expand Down
55 changes: 22 additions & 33 deletions generator/lib/src/dart_type_ex.dart
Expand Up @@ -20,8 +20,10 @@ import 'dart:typed_data';

import 'package:analyzer/dart/element/type.dart';
import 'package:realm_common/realm_common.dart';
import 'package:realm_generator/src/pseudo_type.dart';
import 'package:source_gen/source_gen.dart';

import 'element.dart';
import 'error.dart';
import 'session.dart';
import 'type_checkers.dart';
Expand All @@ -32,7 +34,7 @@ extension DartTypeEx on DartType {
bool get isRealmAny => const TypeChecker.fromRuntime(RealmAny).isAssignableFromType(this);
bool get isRealmBacklink => false; // TODO
bool get isRealmCollection => realmCollectionType != RealmCollectionType.none;
bool get isRealmModel => realmModelChecker.annotationsOfExact(element!).isNotEmpty;
bool get isRealmModel => element != null ? realmModelChecker.annotationsOfExact(element!).isNotEmpty : false;

bool get isNullable => session.typeSystem.isNullable(this);
DartType get asNonNullable => session.typeSystem.promoteToNonNull(this);
Expand All @@ -46,26 +48,16 @@ extension DartTypeEx on DartType {
return RealmCollectionType.none;
}

DartType? get nullIfDynamic => isDynamic ? null : this;

DartType get basicType {
if (isDynamic) return this;
if (isNullable) return asNonNullable.basicType;
if (isRealmCollection) {
return (this as ParameterizedType).typeArguments.last;
}
if (isRealmModel) {
// TODO: convert _T to T using a ClassTypeMacro.
// This awaits the addition of the static meta programming feature to Dart.
// Until then we get by with a few wellplaced string operations.
}
return this;
}

// TODO: Using replaceAll is a hack.
// It is needed for now, since we cannot construct a DartType for the yet to
// be generated classes, ie. for A given _A. Once the new static meta
// programming feature is added to dart, we should be able to resolve this
// using a ClassTypeMacro.
String get basicName => basicType.toString().replaceAll(session.prefix, '');
String get basicMappedName => basicType.asNonNullable.mappedName;

DartType get mappedType {
final self = this;
Expand All @@ -74,37 +66,34 @@ extension DartTypeEx on DartType {
final provider = session.typeProvider;
final mapped = self.typeArguments.last.mappedType;
if (self != mapped) {
if (self.isDartCoreList) return provider.listType(mapped);
if (self.isDartCoreSet) return provider.setType(mapped);
if (self.isDartCoreList) {
final mappedList = provider.listType(mapped);
return PseudoType('Realm${mappedList.getDisplayString(withNullability: false)}', nullabilitySuffix: mappedList.nullabilitySuffix);
}
if (self.isDartCoreSet) {
final mappedSet = provider.setType(mapped);
return PseudoType('Realm${mappedSet.getDisplayString(withNullability: false)}', nullabilitySuffix: mappedSet.nullabilitySuffix);
}
if (self.isDartCoreMap) {
return provider.mapType(self.typeArguments.first, mapped);
final mappedMap = provider.mapType(self.typeArguments.first, mapped);
return PseudoType('Realm${mappedMap.getDisplayString(withNullability: false)}', nullabilitySuffix: mappedMap.nullabilitySuffix);
}
}
}
} else if (isRealmModel) {
// TODO: convert _T to T using a ClassTypeMacro.
// This awaits the addition of the static meta programming feature to Dart.
// Until then we get by with a few wellplaced string operations.
return PseudoType(
getDisplayString(withNullability: false).replaceAll(session.prefix, ''),
nullabilitySuffix: nullabilitySuffix,
);
}
return self;
}

String get mappedName => mappedType.getDisplayString(withNullability: true);

RealmPropertyType? get realmType => _realmType(true);

RealmPropertyType? _realmType(bool recurse) {
// Check for as-of-yet unsupported type
if (isDartCoreSet || //
isDartCoreMap ||
isRealmAny ||
isExactly<Decimal128>() ||
isExactly<ObjectId>() ||
isExactly<Uuid>()) {
throw RealmInvalidGenerationSourceError(
'Not supported yet',
todo: 'Avoid using this type for now',
element: element!,
);
}
if (isRealmCollection && recurse) {
return (this as ParameterizedType).typeArguments.last._realmType(false); // only recurse once! (for now)
}
Expand Down
4 changes: 2 additions & 2 deletions generator/lib/src/error.dart
Expand Up @@ -25,7 +25,7 @@ import 'session.dart';
import 'utils.dart';

class RealmInvalidGenerationSourceError extends InvalidGenerationSourceError {
final FileSpan primarySpan;
final FileSpan? primarySpan;
final String? primaryLabel;
final Map<FileSpan, String> secondarySpans;
bool color;
Expand All @@ -38,7 +38,7 @@ class RealmInvalidGenerationSourceError extends InvalidGenerationSourceError {
bool? color,
this.primaryLabel,
Map<FileSpan, String> secondarySpans = const {},
}) : primarySpan = primarySpan ?? element.span!,
}) : primarySpan = primarySpan ?? element.span,
secondarySpans = {...secondarySpans},
color = color ?? session.color,
super(message, todo: todo, element: element) {
Expand Down
51 changes: 31 additions & 20 deletions generator/lib/src/field_element_ex.dart
Expand Up @@ -17,9 +17,11 @@
////////////////////////////////////////////////////////////////////////////////
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:build/build.dart';
import 'package:realm_common/realm_common.dart';
import 'package:realm_generator/src/expanded_context_span.dart';
import 'package:realm_generator/src/pseudo_type.dart';
import 'package:source_gen/source_gen.dart';
import 'package:source_span/source_span.dart';

Expand Down Expand Up @@ -55,17 +57,11 @@ extension FieldElementEx on FieldElement {
[span!],
);

// Works even if type of field is unresolved
String get typeText => (typeAnnotation ?? initializerExpression?.staticType ?? type).toString();
DartType get modelType => typeAnnotation?.type?.nullIfDynamic ?? initializerExpression?.staticType ?? PseudoType(typeAnnotation.toString());

String get typeModelName => type.isDynamic ? typeText : type.getDisplayString(withNullability: true);
String get modelTypeName => modelType.getDisplayString(withNullability: true);

// TODO: using replaceAll is a temporary hack.
// It is needed for now, since we cannot construct a DartType for the yet to
// be generated classes, ie. for _A given A. Once the new static meta
// programming feature is added to dart, we should be able to resolve this
// using a ClassTypeMacro.
String get typeName => typeModelName.replaceAll(session.prefix, '');
String get mappedTypeName => modelType.mappedName;

RealmFieldInfo? get realmInfo {
try {
Expand All @@ -82,6 +78,22 @@ extension FieldElementEx on FieldElement {
final primaryKey = primaryKeyInfo;
final indexed = indexedInfo;

// Check for as-of-yet unsupported type
if (type.isDartCoreSet || //
type.isDartCoreMap ||
type.isRealmAny ||
type.isExactly<Decimal128>() ||
type.isExactly<ObjectId>() ||
type.isExactly<Uuid>()) {
throw RealmInvalidGenerationSourceError(
'Field type not supported yet',
element: this,
primarySpan: typeSpan(span!.file),
primaryLabel: 'not yet supported',
todo: 'Avoid using $modelTypeName for now',
);
}

// Validate primary key
if (primaryKey != null) {
if (type.isNullable) {
Expand All @@ -94,7 +106,7 @@ extension FieldElementEx on FieldElement {
primaryLabel: 'is nullable',
todo: //
'Consider using the @Indexed() annotation instead, '
"or make '$displayName' ${anOrA(typeText)} ${type.asNonNullable}.",
"or make '$displayName' ${anOrA(modelTypeName)} ${type.asNonNullable}.",
);
}
if (indexed != null) {
Expand All @@ -106,16 +118,16 @@ extension FieldElementEx on FieldElement {
));
}
// Since the setter of a dart late final public field without initializer is public,
// the error of setting a primary key after construction will be a runtime error no matter
// what we do. See:
// the error of setting a primary key after construction will be a runtime error no matter
// what we do. See:
//
// https://github.com/dart-lang/language/issues/1239
// https://github.com/dart-lang/language/issues/2068
//
// Hence we may as well lift the restriction that primary keys must be declared final.
//
// However, this may change in the future. Either as the dart language team change this
// blemish. Or perhaps we can avoid the late modifier, once static meta programming lands
// blemish. Or perhaps we can avoid the late modifier, once static meta programming lands
// in dart. Therefor we keep the code outcommented for later.
/*
if (!isFinal) {
Expand Down Expand Up @@ -146,7 +158,7 @@ extension FieldElementEx on FieldElement {
'Realm only support indexes on String, int, and bool fields',
element: this,
primarySpan: typeSpan(file),
primaryLabel: "$typeText is not an indexable type",
primaryLabel: "$modelTypeName is not an indexable type",
todo: //
"Change the type of '$displayName', "
"or remove the $annotation annotation",
Expand All @@ -162,10 +174,10 @@ extension FieldElementEx on FieldElement {
String todo;
if (notARealmTypeSpan != null) {
todo = //
"Add a @RealmModel annotation on '$typeName', "
"Add a @RealmModel annotation on '$mappedTypeName', "
"or an @Ignored annotation on '$displayName'.";
} else if (type.isDynamic && typeName != 'dynamic' && !typeName.startsWith(session.prefix)) {
todo = "Did you intend to use _$typeName as type for '$displayName'?";
} else if (type.isDynamic && mappedTypeName != 'dynamic' && !mappedTypeName.startsWith(session.prefix)) {
todo = "Did you intend to use _$mappedTypeName as type for '$displayName'?";
} else {
todo = "Remove the invalid field or add an @Ignored annotation on '$displayName'.";
}
Expand All @@ -174,7 +186,7 @@ extension FieldElementEx on FieldElement {
'Not a realm type',
element: this,
primarySpan: typeSpan(file),
primaryLabel: '$typeText is not a realm model type',
primaryLabel: '$modelTypeName is not a realm model type',
secondarySpans: {
modelSpan: "in realm model '${enclosingElement.displayName}'",
// may go both above and below, or stem from another file
Expand Down Expand Up @@ -211,7 +223,7 @@ extension FieldElementEx on FieldElement {
'Realm object references must be nullable',
primarySpan: typeSpan(file),
primaryLabel: 'is not nullable',
todo: 'Change type to $typeText?',
todo: 'Change type to $modelTypeName?',
element: this,
);
}
Expand All @@ -229,7 +241,6 @@ extension FieldElementEx on FieldElement {
rethrow;
} catch (e, s) {
// Fallback. Not perfect, but better than just forwarding original error.
print(s);
throw RealmInvalidGenerationSourceError(
'$e',
todo: //
Expand Down
26 changes: 14 additions & 12 deletions generator/lib/src/format_spans.dart
Expand Up @@ -22,25 +22,27 @@ String formatSpans(
String message, {
required Element element,
required String todo,
required SourceSpan primarySpan,
SourceSpan? primarySpan,
String? primaryLabel,
Map<SourceSpan, String> secondarySpans = const {},
bool color = false,
}) {
final buffer = StringBuffer(message);
try {
final span = primarySpan;
final formated = secondarySpans.isEmpty && primaryLabel == null
? span.highlight(color: color)
: span.highlightMultiple(
primaryLabel ?? '!',
secondarySpans,
color: color,
);
buffer
..write('\n' * 2 + 'in: ')
..writeln(span.start.toolString)
..write(formated);
if (span != null) {
final formatted = secondarySpans.isEmpty && primaryLabel == null
? span.highlight(color: color)
: span.highlightMultiple(
primaryLabel ?? '!',
secondarySpans,
color: color,
);
buffer
..write('\n' * 2 + 'in: ')
..writeln(span.start.toolString)
..write(formatted);
}
} catch (e) {
// Source for `element` wasn't found, it must be in a summary with no
// associated source. We can still give the name.
Expand Down
Expand Up @@ -41,7 +41,7 @@ String humanReadable(Duration duration) {
return '${hours}h ${remaining.inMinutes}m';
}

FutureOr<T> meassure<T>(FutureOr<T> Function() action, {String tag = '', int repetitions = 1}) async {
FutureOr<T> measure<T>(FutureOr<T> Function() action, {String tag = '', int repetitions = 1}) async {
return [
for (int i = 0; i < repetitions; ++i)
await (() async {
Expand Down
52 changes: 52 additions & 0 deletions generator/lib/src/pseudo_type.dart
@@ -0,0 +1,52 @@
// ignore_for_file: implementation_imports

import 'dart:mirrors';

import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type_visitor.dart';
import 'package:analyzer/src/dart/element/display_string_builder.dart';
import 'package:analyzer/src/dart/element/type.dart';

// Used to represent a type that is not yet defined, such as the mapped type of a realm model (ie. A for _A)
// Hopefully we can get rid of this when static meta programming lands
class PseudoType extends TypeImpl {
@override
final NullabilitySuffix nullabilitySuffix;
final String _name;

PseudoType(this._name, {this.nullabilitySuffix = NullabilitySuffix.none}) : super(null);

Never get _never => throw UnimplementedError();

@override
R accept<R>(TypeVisitor<R> visitor) => _never;

@override
R acceptWithArgument<R, A>(TypeVisitorWithArgument<R, A> visitor, A argument) => _never;

@override
void appendTo(ElementDisplayStringBuilder builder) {
// Use reflection to call private methods:
//
// builder._write(_name);
// builder._writeNullability(nullabilitySuffix);

final im = reflect(builder);

// Private Symbols are suffixed with a secret '@<some int>'
// .. hence this ugly trick ヽ(ಠ_ಠ)ノ
final _writeSymbol = im.type.instanceMembers.keys.firstWhere((m) => '$m'.contains('"_write"'));
im.invoke(_writeSymbol, <dynamic>[_name]); // #_write won't work

final _writeNullability = im.type.instanceMembers.keys.firstWhere((m) => '$m'.contains('"_writeNullability"'));
im.invoke(_writeNullability, <dynamic>[nullabilitySuffix]); // #_writeNullability won't work
}

@override
String? get name => _name;

@override
PseudoType withNullability(NullabilitySuffix nullabilitySuffix) {
return PseudoType(_name, nullabilitySuffix: nullabilitySuffix);
}
}

0 comments on commit a08b58b

Please sign in to comment.