/// <summary>
        /// Attempts to drill into the supplied type and remove the next found wrapper (given the provided filters)
        /// to distill it to its core type. (i.e. convert IEnumerable[T] to 'T').
        /// </summary>
        /// <param name="type">The type to inspect.</param>
        /// <param name="eliminateEnumerables">if set to <c>true</c> this method will attempt to eliminate any wrapper type that declares <see cref="IEnumerable{T}" />.</param>
        /// <param name="eliminateTask">if set to <c>true</c> this method will attempt to remove a <see cref="Task{T}" /> declaration from the type.</param>
        /// <param name="eliminateNullableT">if set to <c>true</c> the Nullable{T} wrapper that can exist on value types will be stripped (leaving just T).</param>
        /// <returns>Type.</returns>
        public static Type EliminateNextWrapperFromCoreType(
            Type type,
            bool eliminateEnumerables = true,
            bool eliminateTask        = true,
            bool eliminateNullableT   = true)
        {
            if (type == null)
            {
                return(null);
            }

            // eliminate Task<T>
            if (eliminateTask && type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Task <>))
            {
                return(type.GetGenericArguments()[0]);
            }

            // drill into the concrete type if a list was supplied List<T>, IEnumerable<T>, ICollection<T> etc.
            if (eliminateEnumerables && GraphValidation.IsValidListType(type))
            {
                return(type.GetEnumerableUnderlyingType(false));
            }

            if (eliminateNullableT && Validation.IsNullableOfT(type))
            {
                return(Nullable.GetUnderlyingType(type));
            }

            return(type);
        }
        /// <summary>
        /// Inspects the provided type and generates a type expression to represent it in the object grpah.
        /// </summary>
        /// <param name="typeToCheck">The complete type specification to check.</param>
        /// <param name="typeDefinition">An optional expression declaration to use as a set of overrides on the type provided.</param>
        /// <returns>GraphFieldOptions.</returns>
        public static GraphTypeExpression GenerateTypeExpression(Type typeToCheck, IGraphTypeExpressionDeclaration typeDefinition = null)
        {
            Validation.ThrowIfNull(typeToCheck, nameof(typeToCheck));

            if (typeDefinition?.TypeWrappers != null)
            {
                typeToCheck = GraphValidation.EliminateWrappersFromCoreType(
                    typeToCheck,
                    eliminateEnumerables: true,
                    eliminateTask: true,
                    eliminateNullableT: true);

                return(new GraphTypeExpression(typeToCheck.FriendlyName(), typeDefinition.TypeWrappers));
            }

            // strip out Task{T} before doin any type inspections
            typeToCheck = GraphValidation.EliminateWrappersFromCoreType(
                typeToCheck,
                eliminateEnumerables: false,
                eliminateTask: true,
                eliminateNullableT: false);

            var hasDefinedDefaultValue = typeDefinition?.HasDefaultValue ?? false;

            List <MetaGraphTypes> wrappers = new List <MetaGraphTypes>();

            if (GraphValidation.IsValidListType(typeToCheck))
            {
                // auto generated type expressions will always allow for a nullable list (since class references can be null)
                // unwrap any nested lists as necessary:  e.g.  IEnumerable<IEnumerable<IEnumerable<T>>>
                while (true)
                {
                    var unwrappedType = EliminateNextWrapperFromCoreType(
                        typeToCheck,
                        eliminateEnumerables: true,
                        eliminateTask: false,
                        eliminateNullableT: false);

                    if (unwrappedType == typeToCheck)
                    {
                        break;
                    }

                    wrappers.Add(MetaGraphTypes.IsList);
                    typeToCheck = unwrappedType;
                }

                if (!hasDefinedDefaultValue && GraphValidation.IsNotNullable(typeToCheck))
                {
                    wrappers.Add(MetaGraphTypes.IsNotNull);
                }
            }
            else if (!hasDefinedDefaultValue && GraphValidation.IsNotNullable(typeToCheck))
            {
                wrappers.Add(MetaGraphTypes.IsNotNull);
            }

            typeToCheck = EliminateWrappersFromCoreType(
                typeToCheck,
                eliminateEnumerables: false,
                eliminateTask: false,
                eliminateNullableT: true);

            return(new GraphTypeExpression(typeToCheck.FriendlyName(), wrappers));
        }