diff --git a/CHANGELOG.md b/CHANGELOG.md index cd0dda54d..3a8dacf23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Improve file compaction performance on platforms with page sizes greater than 4k (for example arm64 Apple platforms) for files less than 256 pages in size (Core 14.4.0). ### Fixed +* Using prefix expressions such as negation of numbers as an initializer would fail. (Issue [#1606](https://github.com/realm/realm-dart/issues/1606)) ### Compatibility * Realm Studio: 13.0.0 or later. diff --git a/packages/realm_generator/lib/src/field_element_ex.dart b/packages/realm_generator/lib/src/field_element_ex.dart index d7f001262..47bf95e3b 100644 --- a/packages/realm_generator/lib/src/field_element_ex.dart +++ b/packages/realm_generator/lib/src/field_element_ex.dart @@ -347,7 +347,7 @@ extension FieldElementEx on FieldElement { } final initExpression = initializerExpression; - if (initExpression != null && initExpression is! Literal) { + if (initExpression != null && !_isValidFieldInitializer(initExpression)) { throw RealmInvalidGenerationSourceError( 'Field initializers must be constant', primarySpan: initializerExpressionSpan(file, initExpression), @@ -390,4 +390,15 @@ extension FieldElementEx on FieldElement { } return false; } + + bool _isValidFieldInitializer(Expression initExpression) { + return switch (initExpression) { + Literal _ => true, + InstanceCreationExpression i => i.isConst, + ParenthesizedExpression i => _isValidFieldInitializer(i.expression), + PrefixExpression e => _isValidFieldInitializer(e.operand), + BinaryExpression b => _isValidFieldInitializer(b.leftOperand) && _isValidFieldInitializer(b.rightOperand), + _ => false, + }; + } } diff --git a/packages/realm_generator/test/good_test_data/const_initializer.dart b/packages/realm_generator/test/good_test_data/const_initializer.dart new file mode 100644 index 000000000..2ce626738 --- /dev/null +++ b/packages/realm_generator/test/good_test_data/const_initializer.dart @@ -0,0 +1,26 @@ +import 'package:realm_common/realm_common.dart'; + +part 'const_initializer.realm.dart'; + +@RealmModel() +class _ConstInitializer { + int zero = 0; + int minusOne = -1; + int fooOrOne = const int.fromEnvironment('FOO', defaultValue: 1); + int parenthesis = (1); + int minusMinusOne = -(-1); + int add = 1 + 1; + + String fooEnv = const String.fromEnvironment('FOO'); + String fooLit = 'foo'; + + // const collections allowed, but must be empty + var constEmptyList = const []; // list + var constEmptyMap = const {}; // map + var constEmptySet = const {}; // set + + // const not needed on collections + var emptyList = []; + var emptyMao = {}; + var emptySet = {}; +} diff --git a/packages/realm_generator/test/good_test_data/const_initializer.expected b/packages/realm_generator/test/good_test_data/const_initializer.expected new file mode 100644 index 000000000..9267acfd2 --- /dev/null +++ b/packages/realm_generator/test/good_test_data/const_initializer.expected @@ -0,0 +1,246 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'const_initializer.dart'; + +// ************************************************************************** +// RealmObjectGenerator +// ************************************************************************** + +// ignore_for_file: type=lint +class ConstInitializer extends _ConstInitializer + with RealmEntity, RealmObjectBase, RealmObject { + static var _defaultsSet = false; + + ConstInitializer({ + int zero = 0, + int minusOne = -1, + int fooOrOne = const int.fromEnvironment('FOO', defaultValue: 1), + int parenthesis = (1), + int minusMinusOne = -(-1), + int add = 1 + 1, + String fooEnv = const String.fromEnvironment('FOO'), + String fooLit = 'foo', + Iterable constEmptyList = const [], + Map constEmptyMap = const {}, + Set constEmptySet = const {}, + Iterable emptyList = const [], + Map emptyMao = const {}, + Set emptySet = const {}, + }) { + if (!_defaultsSet) { + _defaultsSet = RealmObjectBase.setDefaults({ + 'zero': 0, + 'minusOne': -1, + 'fooOrOne': const int.fromEnvironment('FOO', defaultValue: 1), + 'parenthesis': (1), + 'minusMinusOne': -(-1), + 'add': 1 + 1, + 'fooEnv': const String.fromEnvironment('FOO'), + 'fooLit': 'foo', + }); + } + RealmObjectBase.set(this, 'zero', zero); + RealmObjectBase.set(this, 'minusOne', minusOne); + RealmObjectBase.set(this, 'fooOrOne', fooOrOne); + RealmObjectBase.set(this, 'parenthesis', parenthesis); + RealmObjectBase.set(this, 'minusMinusOne', minusMinusOne); + RealmObjectBase.set(this, 'add', add); + RealmObjectBase.set(this, 'fooEnv', fooEnv); + RealmObjectBase.set(this, 'fooLit', fooLit); + RealmObjectBase.set>( + this, 'constEmptyList', RealmList(constEmptyList)); + RealmObjectBase.set>( + this, 'constEmptyMap', RealmMap(constEmptyMap)); + RealmObjectBase.set>( + this, 'constEmptySet', RealmSet(constEmptySet)); + RealmObjectBase.set>( + this, 'emptyList', RealmList(emptyList)); + RealmObjectBase.set>( + this, 'emptyMao', RealmMap(emptyMao)); + RealmObjectBase.set>( + this, 'emptySet', RealmSet(emptySet)); + } + + ConstInitializer._(); + + @override + int get zero => RealmObjectBase.get(this, 'zero') as int; + @override + set zero(int value) => RealmObjectBase.set(this, 'zero', value); + + @override + int get minusOne => RealmObjectBase.get(this, 'minusOne') as int; + @override + set minusOne(int value) => RealmObjectBase.set(this, 'minusOne', value); + + @override + int get fooOrOne => RealmObjectBase.get(this, 'fooOrOne') as int; + @override + set fooOrOne(int value) => RealmObjectBase.set(this, 'fooOrOne', value); + + @override + int get parenthesis => RealmObjectBase.get(this, 'parenthesis') as int; + @override + set parenthesis(int value) => RealmObjectBase.set(this, 'parenthesis', value); + + @override + int get minusMinusOne => + RealmObjectBase.get(this, 'minusMinusOne') as int; + @override + set minusMinusOne(int value) => + RealmObjectBase.set(this, 'minusMinusOne', value); + + @override + int get add => RealmObjectBase.get(this, 'add') as int; + @override + set add(int value) => RealmObjectBase.set(this, 'add', value); + + @override + String get fooEnv => RealmObjectBase.get(this, 'fooEnv') as String; + @override + set fooEnv(String value) => RealmObjectBase.set(this, 'fooEnv', value); + + @override + String get fooLit => RealmObjectBase.get(this, 'fooLit') as String; + @override + set fooLit(String value) => RealmObjectBase.set(this, 'fooLit', value); + + @override + RealmList get constEmptyList => + RealmObjectBase.get(this, 'constEmptyList') as RealmList; + @override + set constEmptyList(covariant RealmList value) => + throw RealmUnsupportedSetError(); + + @override + RealmMap get constEmptyMap => + RealmObjectBase.get(this, 'constEmptyMap') as RealmMap; + @override + set constEmptyMap(covariant RealmMap value) => + throw RealmUnsupportedSetError(); + + @override + RealmSet get constEmptySet => + RealmObjectBase.get(this, 'constEmptySet') as RealmSet; + @override + set constEmptySet(covariant RealmSet value) => + throw RealmUnsupportedSetError(); + + @override + RealmList get emptyList => + RealmObjectBase.get(this, 'emptyList') as RealmList; + @override + set emptyList(covariant RealmList value) => + throw RealmUnsupportedSetError(); + + @override + RealmMap get emptyMao => + RealmObjectBase.get(this, 'emptyMao') as RealmMap; + @override + set emptyMao(covariant RealmMap value) => + throw RealmUnsupportedSetError(); + + @override + RealmSet get emptySet => + RealmObjectBase.get(this, 'emptySet') as RealmSet; + @override + set emptySet(covariant RealmSet value) => + throw RealmUnsupportedSetError(); + + @override + Stream> get changes => + RealmObjectBase.getChanges(this); + + @override + ConstInitializer freeze() => + RealmObjectBase.freezeObject(this); + + EJsonValue toEJson() { + return { + 'zero': zero.toEJson(), + 'minusOne': minusOne.toEJson(), + 'fooOrOne': fooOrOne.toEJson(), + 'parenthesis': parenthesis.toEJson(), + 'minusMinusOne': minusMinusOne.toEJson(), + 'add': add.toEJson(), + 'fooEnv': fooEnv.toEJson(), + 'fooLit': fooLit.toEJson(), + 'constEmptyList': constEmptyList.toEJson(), + 'constEmptyMap': constEmptyMap.toEJson(), + 'constEmptySet': constEmptySet.toEJson(), + 'emptyList': emptyList.toEJson(), + 'emptyMao': emptyMao.toEJson(), + 'emptySet': emptySet.toEJson(), + }; + } + + static EJsonValue _toEJson(ConstInitializer value) => value.toEJson(); + static ConstInitializer _fromEJson(EJsonValue ejson) { + return switch (ejson) { + { + 'zero': EJsonValue zero, + 'minusOne': EJsonValue minusOne, + 'fooOrOne': EJsonValue fooOrOne, + 'parenthesis': EJsonValue parenthesis, + 'minusMinusOne': EJsonValue minusMinusOne, + 'add': EJsonValue add, + 'fooEnv': EJsonValue fooEnv, + 'fooLit': EJsonValue fooLit, + 'constEmptyList': EJsonValue constEmptyList, + 'constEmptyMap': EJsonValue constEmptyMap, + 'constEmptySet': EJsonValue constEmptySet, + 'emptyList': EJsonValue emptyList, + 'emptyMao': EJsonValue emptyMao, + 'emptySet': EJsonValue emptySet, + } => + ConstInitializer( + zero: fromEJson(zero), + minusOne: fromEJson(minusOne), + fooOrOne: fromEJson(fooOrOne), + parenthesis: fromEJson(parenthesis), + minusMinusOne: fromEJson(minusMinusOne), + add: fromEJson(add), + fooEnv: fromEJson(fooEnv), + fooLit: fromEJson(fooLit), + constEmptyList: fromEJson(constEmptyList), + constEmptyMap: fromEJson(constEmptyMap), + constEmptySet: fromEJson(constEmptySet), + emptyList: fromEJson(emptyList), + emptyMao: fromEJson(emptyMao), + emptySet: fromEJson(emptySet), + ), + _ => raiseInvalidEJson(ejson), + }; + } + + static final schema = () { + RealmObjectBase.registerFactory(ConstInitializer._); + register(_toEJson, _fromEJson); + return SchemaObject( + ObjectType.realmObject, ConstInitializer, 'ConstInitializer', [ + SchemaProperty('zero', RealmPropertyType.int), + SchemaProperty('minusOne', RealmPropertyType.int), + SchemaProperty('fooOrOne', RealmPropertyType.int), + SchemaProperty('parenthesis', RealmPropertyType.int), + SchemaProperty('minusMinusOne', RealmPropertyType.int), + SchemaProperty('add', RealmPropertyType.int), + SchemaProperty('fooEnv', RealmPropertyType.string), + SchemaProperty('fooLit', RealmPropertyType.string), + SchemaProperty('constEmptyList', RealmPropertyType.int, + collectionType: RealmCollectionType.list), + SchemaProperty('constEmptyMap', RealmPropertyType.int, + collectionType: RealmCollectionType.map), + SchemaProperty('constEmptySet', RealmPropertyType.int, + collectionType: RealmCollectionType.set), + SchemaProperty('emptyList', RealmPropertyType.int, + collectionType: RealmCollectionType.list), + SchemaProperty('emptyMao', RealmPropertyType.int, + collectionType: RealmCollectionType.map), + SchemaProperty('emptySet', RealmPropertyType.int, + collectionType: RealmCollectionType.set), + ]); + }(); + + @override + SchemaObject get objectSchema => RealmObjectBase.getSchema(this) ?? schema; +} diff --git a/packages/realm_generator/test/good_test_data/const_initializer.realm.dart b/packages/realm_generator/test/good_test_data/const_initializer.realm.dart new file mode 100644 index 000000000..f52e5e45a --- /dev/null +++ b/packages/realm_generator/test/good_test_data/const_initializer.realm.dart @@ -0,0 +1,4 @@ +// MOCK FILE! This file exists to ensure the parent file is valid Dart. +// The parent will be used as input to the realm_generator in a test, and the +// output compared to the .expected file. +part of 'const_initializer.dart';