private WeaveResult WeaveProperty(PropertyDefinition prop, TypeDefinition type, Dictionary<string, Tuple<MethodReference, MethodReference>> methodTable) { var columnName = prop.Name; var mapToAttribute = prop.CustomAttributes.FirstOrDefault(a => a.AttributeType.Name == "MapToAttribute"); if (mapToAttribute != null) { columnName = (string)mapToAttribute.ConstructorArguments[0].Value; } var backingField = prop.GetBackingField(); var isIndexed = prop.CustomAttributes.Any(a => a.AttributeType.Name == "IndexedAttribute"); if (isIndexed && (!_indexableTypes.Contains(prop.PropertyType.FullName))) { return WeaveResult.Error($"{type.Name}.{prop.Name} is marked as [Indexed] which is only allowed on integral types as well as string, bool and DateTimeOffset, not on {prop.PropertyType.FullName}."); } var isPrimaryKey = prop.IsPrimaryKey(); if (isPrimaryKey && (!_primaryKeyTypes.Contains(prop.PropertyType.FullName))) { return WeaveResult.Error($"{type.Name}.{prop.Name} is marked as [PrimaryKey] which is only allowed on integral and string types, not on {prop.PropertyType.FullName}."); } var isRequired = prop.IsRequired(); if (isRequired && !prop.IsNullable() && prop.PropertyType.FullName != StringTypeName && prop.PropertyType.FullName != ByteArrayTypeName) { return WeaveResult.Error($"{type.Name}.{prop.Name} is marked as [Required] which is only allowed on strings or nullable scalar types, not on {prop.PropertyType.FullName}."); } if (!prop.IsAutomatic()) { if (prop.PropertyType.Resolve().BaseType.IsSameAs(_references.RealmObject)) { return WeaveResult.Warning($"{type.Name}.{columnName} is not an automatic property but its type is a RealmObject which normally indicates a relationship."); } return WeaveResult.Skipped(); } if (_typeTable.ContainsKey(prop.PropertyType.FullName)) { // If the property is automatic but doesn't have a setter, we should still ignore it. if (prop.SetMethod == null) { return WeaveResult.Skipped(); } var typeId = prop.PropertyType.FullName + (isPrimaryKey ? " unique" : string.Empty); Tuple<MethodReference, MethodReference> realmAccessors; if (!methodTable.TryGetValue(typeId, out realmAccessors)) { var getter = new MethodReference("Get" + _typeTable[prop.PropertyType.FullName] + "Value", prop.PropertyType, _references.RealmObject) { HasThis = true, Parameters = { new ParameterDefinition(ModuleDefinition.TypeSystem.String) } }; var setter = new MethodReference("Set" + _typeTable[prop.PropertyType.FullName] + "Value" + (isPrimaryKey ? "Unique" : string.Empty), ModuleDefinition.TypeSystem.Void, _references.RealmObject) { HasThis = true, Parameters = { new ParameterDefinition(ModuleDefinition.TypeSystem.String), new ParameterDefinition(prop.PropertyType) } }; methodTable[typeId] = realmAccessors = Tuple.Create(getter, setter); } ReplaceGetter(prop, columnName, realmAccessors.Item1); ReplaceSetter(prop, backingField, columnName, realmAccessors.Item2); } else if (prop.IsIList()) { var elementType = ((GenericInstanceType)prop.PropertyType).GenericArguments.Single(); if (!elementType.Resolve().BaseType.IsSameAs(_references.RealmObject)) { return WeaveResult.Warning($"SKIPPING {type.Name}.{columnName} because it is an IList but its generic type is not a RealmObject subclass, so will not persist."); } if (prop.SetMethod != null) { return WeaveResult.Error($"{type.Name}.{columnName} has a setter but its type is a IList which only supports getters."); } var concreteListConstructor = _references.System_Collections_Generic_ListOfT_Constructor.MakeHostInstanceGeneric(elementType); // weaves list getter which also sets backing to List<T>, forcing it to accept us setting it post-init var backingDef = backingField as FieldDefinition; if (backingDef != null) { backingDef.Attributes &= ~FieldAttributes.InitOnly; // without a set; auto property has this flag we must clear } ReplaceListGetter(prop, backingField, columnName, new GenericInstanceMethod(_references.RealmObject_GetListValue) { GenericArguments = { elementType } }, concreteListConstructor); } else if (prop.PropertyType.Resolve().BaseType.IsSameAs(_references.RealmObject)) { // with casting in the _realmObject methods, should just work ReplaceGetter(prop, columnName, new GenericInstanceMethod(_references.RealmObject_GetObjectValue) { GenericArguments = { prop.PropertyType } }); ReplaceSetter(prop, backingField, columnName, new GenericInstanceMethod(_references.RealmObject_SetObjectValue) { GenericArguments = { prop.PropertyType } }); } else if (prop.IsIQueryable()) { var backlinkAttribute = prop.CustomAttributes.FirstOrDefault(a => a.AttributeType.Name == "BacklinkAttribute"); if (backlinkAttribute == null) { return WeaveResult.Skipped(); } if (prop.SetMethod != null) { return WeaveResult.Error("Backlink properties must be read-only."); } var elementType = ((GenericInstanceType)prop.PropertyType).GenericArguments.Single(); var inversePropertyName = (string)backlinkAttribute.ConstructorArguments[0].Value; var inverseProperty = elementType.Resolve().Properties.SingleOrDefault(p => p.Name == inversePropertyName); if (inverseProperty == null || (!inverseProperty.PropertyType.IsSameAs(type) && !inverseProperty.IsIList(type))) { return WeaveResult.Error($"The property '{elementType.Name}.{inversePropertyName}' does not constitute a link to '{type.Name}' as described by '{type.Name}.{prop.Name}'."); } var backingDef = backingField as FieldDefinition; if (backingDef != null) { backingDef.Attributes &= ~FieldAttributes.InitOnly; // without a set; auto property has this flag we must clear } ReplaceBacklinksGetter(prop, backingField, columnName, elementType); } else if (prop.SetMethod == null) { return WeaveResult.Skipped(); } else if (prop.PropertyType.FullName == "System.DateTime") { return WeaveResult.Error($"Class '{type.Name}' field '{prop.Name}' is a DateTime which is not supported - use DateTimeOffset instead."); } else if (prop.PropertyType.FullName == "System.Nullable`1<System.DateTime>") { return WeaveResult.Error($"Class '{type.Name}' field '{prop.Name}' is a DateTime? which is not supported - use DateTimeOffset? instead."); } else { return WeaveResult.Error($"Class '{type.Name}' field '{columnName}' is a '{prop.PropertyType}' which is not yet supported."); } var preserveAttribute = new CustomAttribute(_references.PreserveAttribute_Constructor); prop.CustomAttributes.Add(preserveAttribute); var wovenPropertyAttribute = new CustomAttribute(_references.WovenPropertyAttribute_Constructor); prop.CustomAttributes.Add(wovenPropertyAttribute); Debug.WriteLine(string.Empty); var primaryKeyMsg = isPrimaryKey ? "[PrimaryKey]" : string.Empty; var indexedMsg = isIndexed ? "[Indexed]" : string.Empty; LogDebug($"Woven {type.Name}.{prop.Name} as a {prop.PropertyType.FullName} {primaryKeyMsg} {indexedMsg}."); return WeaveResult.Success(prop, backingField, isPrimaryKey); }