Beispiel #1
0
        static bool InitializeDefaultValue(PocoPropertyInfo p, IActivityMonitor monitor)
        {
            bool success = true;
            var  aDefs   = p.DeclaredProperties.Select(x => (Prop: x, x.GetCustomAttribute <DefaultValueAttribute>()))
                           .Where(a => a.Item2 != null)
                           .Select(a => (a.Prop, a.Item2 !.Value));

            var first = aDefs.FirstOrDefault();

            if (first.Prop != null)
            {
                p.HasDefaultValue = true;
                p.DefaultValue    = first.Value;
                var    w             = new StringCodeWriter();
                string defaultSource = p.DefaultValueSource = w.Append(first.Value).ToString();
                foreach (var other in aDefs.Skip(1))
                {
                    w.StringBuilder.Clear();
                    var o = w.Append(other.Value).ToString();
                    if (defaultSource != o)
                    {
                        monitor.Error($"Default values difference between {first.Prop.DeclaringType}.{first.Prop.Name} = {defaultSource} and {other.Prop.DeclaringType}.{other.Prop.Name} = {o}.");
                        success = false;
                    }
                }
            }
            return(success);
        }
Beispiel #2
0
        static bool HandleUnionTypesIfAny(IActivityMonitor monitor, ref PropertyInfo[]?unionTypesDef, Type interfaceType, PropertyInfo p, PocoPropertyInfo implP)
        {
            var uAttr = p.GetCustomAttributes <UnionTypeAttribute>().FirstOrDefault();

            if (uAttr != null)
            {
                // A union type, it cannot be readonly.
                if (implP.IsReadOnly)
                {
                    monitor.Error($"Invalid readonly [UnionType] '{interfaceType.FullName}.{p.Name}' property: a readonly union is forbidden. Allowed readonly property types are non nullable IPoco or List<>, Dictionary<,> or Set<>.");
                    return(false);
                }
                // A union type is not a basic property (fix the fact that typeof(object) is a basic property).
                implP.IsBasicPropertyType = false;
                bool          isPropertyNullable   = implP.PropertyNullableTypeTree.Kind.IsNullable();
                List <string>?typeDeviants         = null;
                List <string>?nullableDef          = null;
                List <string>?interfaceCollections = null;

                if (unionTypesDef == null)
                {
                    Type?u = interfaceType.GetNestedType("UnionTypes", BindingFlags.Public | BindingFlags.NonPublic);
                    if (u == null)
                    {
                        monitor.Error($"[UnionType] attribute on '{interfaceType.FullName}.{p.Name}' requires a nested 'class UnionTypes {{ public (int?,string) {p.Name} {{ get; }} }}' with the types (here, (int?,string) is just an example of course).");
                        return(false);
                    }
                    unionTypesDef = u.GetProperties();
                }
                var f = unionTypesDef.FirstOrDefault(f => f.Name == p.Name);
                if (f == null)
                {
                    monitor.Error($"The nested class UnionTypes requires a public value tuple '{p.Name}' property.");
                    return(false);
                }
                var tree = f.GetNullableTypeTree();
                if ((tree.Kind & NullabilityTypeKind.IsTupleType) == 0)
                {
                    monitor.Error($"The '{p.Name}' property of the nested class UnionTypes must be a value tuple (current type is {tree}).");
                    return(false);
                }
                if (tree.Kind.IsNullable() != isPropertyNullable)
                {
                    monitor.Error($"The '{p.Name}' property of the nested class UnionTypes must{(isPropertyNullable ? "" : "NOT")} BE nullable since the property itself is nullable.");
                    return(false);
                }
                foreach (var sub in tree.SubTypes)
                {
                    if (!p.PropertyType.IsAssignableFrom(sub.Type))
                    {
                        if (typeDeviants == null)
                        {
                            typeDeviants = new List <string>();
                        }
                        typeDeviants.Add(sub.ToString());
                    }
                    else if (sub.Type.IsGenericType)
                    {
                        var tGen = sub.Type.GetGenericTypeDefinition();
                        if (tGen == typeof(IList <>))
                        {
                            if (interfaceCollections == null)
                            {
                                interfaceCollections = new List <string>();
                            }
                            interfaceCollections.Add($"{sub} should be a List<{sub.RawSubTypes[0]}>");
                        }
                        else if (tGen == typeof(IDictionary <,>))
                        {
                            if (interfaceCollections == null)
                            {
                                interfaceCollections = new List <string>();
                            }
                            interfaceCollections.Add($"{sub} should be a Dictionary<{sub.RawSubTypes[0]},{sub.RawSubTypes[1]}>");
                        }
                        else if (tGen == typeof(ISet <>))
                        {
                            if (interfaceCollections == null)
                            {
                                interfaceCollections = new List <string>();
                            }
                            interfaceCollections.Add($"{sub} should be a HashSet<{sub.RawSubTypes[0]}>");
                        }
                    }
                    if (sub.Kind.IsNullable())
                    {
                        if (nullableDef == null)
                        {
                            nullableDef = new List <string>();
                        }
                        nullableDef.Add(sub.ToString());
                    }
                }
                if (typeDeviants != null)
                {
                    monitor.Error($"Invalid [UnionType] attribute on '{interfaceType.FullName}.{p.Name}'. Union type{(typeDeviants.Count > 1 ? "s" : "")} '{typeDeviants.Concatenate( "' ,'" )}' {(typeDeviants.Count > 1 ? "are" : "is")} incompatible with the property type '{p.PropertyType.Name}'.");
                }
                if (interfaceCollections != null)
                {
                    monitor.Error($"Invalid [UnionType] attribute on '{interfaceType.FullName}.{p.Name}'. Collection types must be concrete: {interfaceCollections.Concatenate()}.");
                }
                if (nullableDef != null)
                {
                    monitor.Error($"Invalid [UnionType] attribute on '{interfaceType.FullName}.{p.Name}'. Union type definitions must not be nullable: please change '{nullableDef.Concatenate( "' ,'" )}' to be not nullable.");
                    return(false);
                }
                if (typeDeviants != null || interfaceCollections != null)
                {
                    return(false);
                }

                // Type definitions are non-nullable.
                var types = tree.SubTypes.ToList();
                if (types.Any(t => t.Type == typeof(object)))
                {
                    monitor.Error($"{implP}': UnionTypes cannot define the type 'object' since this would erase all possible types.");
                    return(false);
                }
                if (!implP.AddUnionPropertyTypes(monitor, types, uAttr.CanBeExtended))
                {
                    monitor.Error($"{implP}': [UnionType( CanBeExtended = true )] should be used on all interfaces or all unioned type definitions across IPoco family must be the same.");
                    return(false);
                }
            }
            return(true);
        }
Beispiel #3
0
        static bool HandlePocoProperty(IActivityMonitor monitor,
                                       HashSet <Type> expanded,
                                       Dictionary <string, PocoPropertyInfo> properties,
                                       List <PocoPropertyInfo> propertyList,
                                       ref PropertyInfo[]?unionTypesDef,
                                       Type interfaceType,
                                       PropertyInfo p)
        {
            // We cannot check the equality of property type here because we need to consider IPoco families: we
            // have to wait that all of them have been registered.
            // Same as the Poco-like: it's easier to consider them once IPoco analysis is done.
            // ClassInfo.CheckPropertiesVarianceAndUnionTypes checks the type and the nullability.
            bool success    = true;
            var  isReadOnly = !p.CanWrite;

            if (properties.TryGetValue(p.Name, out var implP))
            {
                // Already defined on a previously analyzed interface.
                implP.DeclaredProperties.Add(p);
                if (implP.IsReadOnly != isReadOnly)
                {
                    Type iW = implP.DeclaredProperties[0].DeclaringType !;
                    Type iR = interfaceType;
                    if (isReadOnly)
                    {
                        (iR, iW) = (iW, iR);
                    }
                    monitor.Error($"Interface '{iR.ToCSharpName()}' and '{iW.ToCSharpName()}' both declare property '{p.Name}' but the first is readonly and the latter is read/write. The same property must be readonly or read/write for all the interfaces that define it.");
                    success = false;
                }
            }
            else
            {
                // New property.

                // We must always create it, even if an error is detected to let the dynamic generation
                // of the runtime Poco class (with fake getters/setters) succeed.
                if (!p.CanRead)
                {
                    monitor.Error($"Poco property '{interfaceType.ToCSharpName()}.{p.Name}' cannot be read.");
                    success = false;
                }
                var  nullabilityInfo = p.GetNullabilityInfo();
                var  nullTree        = p.PropertyType.GetNullableTypeTree(nullabilityInfo, NullableTypeTree.ObliviousDefaultBuilder);
                bool isBasicProperty = PocoSupportResultExtension.IsBasicPropertyType(nullTree.Type);
                Type?genRefType      = nullTree.Kind.IsReferenceType() && nullTree.Type.IsGenericType ? nullTree.Type.GetGenericTypeDefinition() : null;
                bool isReadonlyCompliantCollection = genRefType != null && (genRefType == typeof(List <>) ||
                                                                            genRefType == typeof(Dictionary <,>) ||
                                                                            genRefType == typeof(HashSet <>));
                bool isStandardCollection = isReadonlyCompliantCollection || p.PropertyType.IsArray;
                if (isReadOnly && success)
                {
                    // Basic checks that don't require all IPoco to be discovered (kind of fail fast).
                    if (nullTree.Kind.IsNullable())
                    {
                        monitor.Error($"Poco property '{interfaceType.ToCSharpName()}.{p.Name}' type is nullable and readonly (it has no setter). This is forbidden since value will always be null.");
                        success = false;
                    }
                    else if (isBasicProperty)
                    {
                        monitor.Error($"Poco property '{interfaceType.ToCSharpName()}.{p.Name}' type is a readonly basic type (it has no setter). This is forbidden since totally useless.");
                        success = false;
                    }
                    else if (isStandardCollection && !isReadonlyCompliantCollection)
                    {
                        monitor.Error($"Poco property '{interfaceType.ToCSharpName()}.{p.Name}' type is a readonly array (it has no setter). This is forbidden since we could only generate an empty array.");
                        success = false;
                    }
                    else if (typeof(IPoco).IsAssignableFrom(p.PropertyType) && expanded.Contains(p.PropertyType))
                    {
                        monitor.Error($"Poco Cyclic dependency error: readonly property '{interfaceType.FullName}.{p.Name}' references its own Poco type.");
                        success = false;
                        // Testing whether they are actual IPoco (ie. not excluded from Setup) and don't create
                        // instantiation cycles is deferred when the global result is built.
                    }
                }
                implP = new PocoPropertyInfo(p, propertyList.Count, isReadOnly, nullabilityInfo, nullTree, isStandardCollection);
                properties.Add(p.Name, implP);
                propertyList.Add(implP);
                implP.IsBasicPropertyType = isBasicProperty;
            }
            return(success && HandleUnionTypesIfAny(monitor, ref unionTypesDef, interfaceType, p, implP));
        }