private IEnumerable <Type> Run(TargetTypeSelectorParams search)
        {
            // for IFoo<IEnumerable<IBar<in T>>>, this should return something like
            // IFoo<IEnumerable<IBar<T>>>,
            // IFoo<IEnumerable<IBar<T[Base0..n]>>>,    //foreach base and interface of T if contravariant
            // IFoo<IEnumerable<IBar<>>,
            // IFoo<IEnumerable<>
            // IFoo<>

            List <Type> toReturn = new List <Type>(20)
            {
                search.Type
            };

            if (!VariantMatches.TryGetValue(search.Type, out HashSet <Type> variantMatches))
            {
                variantMatches = VariantMatches[search.Type] = new HashSet <Type>();
            }

            // these get added after all other bases and interfaces have been added.
            List <Type> explicitlyAddedBases = new List <Type>();

            Type[] temp;

            if (search.Type.IsGenericType && !search.Type.IsGenericTypeDefinition)
            {
                // now return any covariant matches if applicable
                // NOTE - if the search is for a type parameter, then we don't perform this operation
                // as we're only interested in materialising covariant types which we know *should* materialise
                // at least one target match.
                if (search.TypeParameter == null &&
                    !search.Type.IsValueType &&
                    RootTargets != null)
                {
                    temp = RootTargets.GetKnownCovariantTypes(search.Type).ToArray();
                    variantMatches.UnionWith(temp);
                    toReturn.AddRange(temp);
                }

                // for every generic type, there is at least two versions - the closed and the open
                // when you consider, also, that a generic parameter might also be a generic, with multiple
                // versions - you can see that things can get icky.
                var argSearches = search.Type.GetGenericArguments()
                                  .Zip(search.Type.GetGenericTypeDefinition().GetGenericArguments(),
                                       (arg, param) => new TargetTypeSelectorParams(
                                           arg,
                                           param,
                                           search)
                                       ).ToArray();

                var    typeParamSearchLists = argSearches.Select(t => Run(t).ToArray()).ToArray();
                var    genericType          = search.Type.GetGenericTypeDefinition();
                Type[] combinationArray     = null;
                Type   compatibleType       = null;

                // Note: the first result will be equal to search.Type, hence Skip(1)
                foreach (var combination in typeParamSearchLists.Permutate().Skip(1))
                {
                    combinationArray = combination.ToArray();
                    try
                    {
                        compatibleType = genericType.MakeGenericType(combinationArray);
                    }
                    catch (Exception) {
                        // ignore things like constraints etc because it could easily happen and
                        // it's actually very hard to check up front.
                        continue;
                    }
                    // check if this combination counts as a variant match
                    if (!compatibleType.ContainsGenericParameters)
                    {
                        for (var f = 0; f < combinationArray.Length; f++)
                        {
                            if (VariantMatches.TryGetValue(argSearches[f].Type, out var typeArgVariants) && typeArgVariants.Contains(combinationArray[f]))
                            {
                                variantMatches.Add(compatibleType);
                                break;
                            }
                        }
                    }

                    toReturn.Add(compatibleType);
                    if (search.TypeParameter == null &&
                        !search.Type.IsValueType &&
                        RootTargets != null)
                    {
                        temp = RootTargets.GetKnownCovariantTypes(compatibleType).ToArray();
                        variantMatches.UnionWith(temp);
                        toReturn.AddRange(temp);
                    }
                }

                toReturn.Add(genericType);
            }

            bool        isArray               = search.Type.IsArray;
            Type        arrayElemType         = null;
            bool        isValueTypeArray      = false;
            bool        addElemTypeInterfaces = false;
            bool        addArrayBases         = false;
            List <Type> allArrayTypes         = new List <Type>();

            if (isArray)
            {
                allArrayTypes.Add(search.Type);
                arrayElemType    = search.Type.GetElementType();
                isValueTypeArray = arrayElemType.IsValueType;
                if (!isValueTypeArray)
                {
                    addElemTypeInterfaces = arrayElemType.IsInterface;
                    addArrayBases         = !addElemTypeInterfaces;
                }
            }

            if (search.EnableVariance)
            {
                if (search.TypeParameterIsContravariant &&
                    (search.Contravariance & Contravariance.BasesAndInterfaces) != Contravariance.None)
                {
                    // note - when spawning child searches, this search's parent is passed as the parent
                    // to the newly created search.  This ensures that the contravariant search direction
                    // is calculated correctly

                    if ((search.Contravariance & Contravariance.Bases) == Contravariance.Bases)
                    {
                        if (isArray)
                        {
                            explicitlyAddedBases.Add(typeof(Array));
                            explicitlyAddedBases.Add(typeof(object));
                            // Arrays are weird.  Given an instance i of type A[], where A is a reference or interface type,
                            // then i can be implicitly converted to an array of any base or interface ('supertype') of A; and
                            // can also be implicitly converted to any interface or base of an array of that supertype.
                            // So, given Foo : IFoo
                            // We can have IFoo[] a = new Foo[10];
                            // We can also have IList<IFoo> a = new Foo[10];
                            // Because IList<T> is an interface of IFoo[].
                            // The same also holds true for any contravariant type argument, so:
                            // Action<IList<IFoo>> a = new Action<Foo[]>(foos => <<blah>>) is allowed.
                            if (addArrayBases)
                            {
                                allArrayTypes.AddRange(search.Type.GetBaseArrayTypes());
                            }
                            else if (addElemTypeInterfaces)
                            {
                                allArrayTypes.AddRange(search.Type.GetInterfaceArrayTypes());
                            }

                            // loop through the bases, constructing array types for each and adding them
                            // note we skip the first because it'll be the search type.
                            temp = allArrayTypes.Skip(1)
                                   .SelectMany(tt => Run(new TargetTypeSelectorParams(tt, search.TypeParameter, search.Parent, Contravariance.Bases)))
                                   .Where(tt => !explicitlyAddedBases.Contains(tt)).ToArray();
                            variantMatches.UnionWith(temp);
                            toReturn.AddRange(temp);
                        }

                        // if it's a class then iterate the bases
                        if (!search.Type.IsInterface &&
                            !search.Type.IsValueType &&
                            search.Type != typeof(object))
                        {
                            if (!explicitlyAddedBases.Contains(typeof(object)))
                            {
                                explicitlyAddedBases.Add(typeof(object));
                            }

                            // note - disable interfaces when recursing into the base
                            // note also - ignore 'object'
                            temp = Run(new TargetTypeSelectorParams(search.Type.BaseType, search.TypeParameter, search.Parent, Contravariance.Bases))
                                   .Where(tt => !explicitlyAddedBases.Contains(tt)).ToArray();
                            variantMatches.UnionWith(temp);
                            toReturn.AddRange(temp);
                        }
                    }

                    if ((search.Contravariance & Contravariance.Interfaces) == Contravariance.Interfaces)
                    {
                        // don't check interfaces when type is a value type
                        if (!search.Type.IsValueType)
                        {
                            if (isArray)
                            {
                                // have to include all the interfaces of all the array types that are compatible per array covariance
                                temp = allArrayTypes
                                       .SelectMany(t => t.GetInterfaces()
                                                   .SelectMany(tt => Run(new TargetTypeSelectorParams(tt, search.TypeParameter, search.Parent, Contravariance.Bases))))
                                       .OrderBy(t => t, DescendingTypeOrder.Instance)
                                       .Where(tt => !explicitlyAddedBases.Contains(tt)).ToArray();
                                variantMatches.UnionWith(temp);
                                toReturn.AddRange(temp);
                            }
                            else
                            {
                                temp = search.Type.GetInterfaces()
                                       .SelectMany(t => Run(new TargetTypeSelectorParams(t, search.TypeParameter, search.Parent, Contravariance.Bases)))
                                       .Where(tt => !explicitlyAddedBases.Contains(tt)).ToArray();
                                variantMatches.UnionWith(temp);
                                toReturn.AddRange(temp);
                            }
                        }
                    }
                    variantMatches.UnionWith(explicitlyAddedBases);
                    toReturn.AddRange(explicitlyAddedBases);
                }
            }

            return(toReturn);
        }
Example #2
0
        public TargetTypeSelectorParams(Type type,
                                        Type typeParameter = null,
                                        TargetTypeSelectorParams parent        = null,
                                        Contravariance?contravariantSearchType = null)
            : this(type)
        {
            Parent        = parent;
            RootTargets   = parent?.RootTargets;
            TypeParameter = typeParameter;
            // We enable variance if we have no parent
            // Or if we have a variant type parameter and
            // the parent hasn't disabled variance.
            EnableVariance = parent == null ||
                             (TypeParameterIsVariant && Parent.EnableVariance);

            if (EnableVariance)
            {
                bool enableContravariance;
                if (ForceContravariance == null)
                {
                    // start off always enabled
                    enableContravariance = true;
                    var overridenContravariance = GetInternalContravarianceOverride(Type);

                    if (overridenContravariance != null)
                    {
                        // once it's forced, all child searches will avoid testing the EnableContravariance option
                        this._forceContravariance = overridenContravariance;
                        enableContravariance      = overridenContravariance.Value;
                    }
                    else if (RootTargets != null)
                    {
                        enableContravariance = RootTargets.GetOption(
                            Type, Options.EnableContravariance.Default);
                    }
                }
                else
                {
                    enableContravariance = ForceContravariance.Value;
                }

                if (contravariantSearchType != null)
                {
                    Contravariance = contravariantSearchType.Value;
                }
                else
                {
                    if (!enableContravariance)
                    {
                        Contravariance = Contravariance.None;
                    }
                    else
                    {
                        // if the parent has its contravariance search set to None, we inherit that
                        // and move on.
                        if (Parent?.Contravariance == Contravariance.None)
                        {
                            Contravariance = Contravariance.None;
                        }
                        else
                        {
                            var numContras = TypeParameterChain.Count(t => t.IsContravariantTypeParameter());
                            if (numContras <= 1 || (numContras % 2) == 1)
                            {
                                Contravariance = Contravariance.BasesAndInterfaces;
                            }
                            else
                            {
                                Contravariance = Contravariance.Derived;
                            }
                        }
                    }
                }
            }
        }