Editor/TypeExtensions.cs
using System;
using System.Linq;
using System.Reflection;

namespace ExtendedEditor;

public static class TypeExtensions
{
    private static readonly Type _unmanagedGenericType = typeof(UnmanagedGeneric<>);
    private class UnmanagedGeneric<T> where T : unmanaged { }

    public static ConstructorInfo? GetEffectivelyDefaultConstructor(this Type type, BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance)
    {
        var result = type.GetConstructor(bindingFlags, Type.EmptyTypes);
        if(result is not null)
            return result;

        var constructors = type.GetConstructors(bindingFlags);
        foreach(var constructor in constructors)
        {
            var parameters = constructor.GetParameters();
            if(parameters.All(p => p.HasDefaultValue))
                return constructor;
        }

        return null;
    }

    public static object? CreateByEffectivelyDefaultConstructor(this Type type, BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance)
    {
        var constructor = type.GetEffectivelyDefaultConstructor(bindingFlags);
        if(constructor is null)
            return false;

        var parameters = constructor.GetParameters();
        if(parameters.Length == 0)
            return constructor.Invoke(null);

        object?[] args = [.. parameters.Select(p => p.DefaultValue)];
        return constructor.Invoke(args);
    }


    public static bool IsNullableValueType(this Type type)
    {
        return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
    }

    public static bool IsUnmanaged(this Type type)
    {
        if(!type.IsValueType)
            return false;

        try
        {
            _unmanagedGenericType.MakeGenericType(type);
            return true;
        }
        catch
        {
            return false;
        }
    }

    public static bool CanBeGenericParameter(this Type chosenType, Type genericParameter, Type?[]? selectedArgs)
    {
        if(!genericParameter.IsGenericParameter)
            throw new ArgumentException("Not generic parameter.", nameof(genericParameter));

        if(chosenType.IsAbstract && chosenType.IsSealed) // static classes can't be generic parameters
            return false;

        if(chosenType == typeof(void))
            return false;

        var attributes = genericParameter.GenericParameterAttributes;

        if(attributes.HasFlag(GenericParameterAttributes.NotNullableValueTypeConstraint))
        {
            // where T : struct
            if(!chosenType.IsValueType || chosenType.IsNullableValueType())
                return false;

            // where T : unmanaged
            bool isUnmanagedConstraint = genericParameter.CustomAttributes.Any(a => a.AttributeType.FullName == "System.Runtime.CompilerServices.UnmanagedTypeAttribute");
            if(isUnmanagedConstraint && !chosenType.IsUnmanaged())
                return false;
        }

        // where T : class
        if(attributes.HasFlag(GenericParameterAttributes.ReferenceTypeConstraint) &&
            chosenType.IsValueType)
            return false;

        // where T : new()
        if(attributes.HasFlag(GenericParameterAttributes.DefaultConstructorConstraint))
        {
            if(chosenType.IsAbstract || chosenType.IsInterface)
                return false;

            if(!chosenType.IsValueType && chosenType.GetConstructor(BindingFlags.Public | BindingFlags.Instance, Type.EmptyTypes) == null)
                return false;
        }

        // where T : allows ref struct
        if(!attributes.HasFlag(GenericParameterAttributes.AllowByRefLike) && chosenType.IsByRefLike)
            return false;

        // where T : Type1, Type2, ...
        Type? underlyingType = Nullable.GetUnderlyingType(chosenType);
        var constraints = genericParameter.GetGenericParameterConstraints();
        foreach(var constraint in constraints)
        {
            if(!TryMakeEffective(constraint, selectedArgs, out var effectiveConstraint))
                return false;

            var typeToTest = effectiveConstraint.IsInterface ? (underlyingType ?? chosenType) : chosenType;
            if(!typeToTest.IsAssignableTo(effectiveConstraint))
                return false;
        }

        return true;
    }

    private static bool TryMakeEffective(Type constraint, Type?[]? selectedArgs, out Type effectiveConstraint)
    {
        effectiveConstraint = null!;

        // where T : U
        if(constraint.IsGenericParameter)
        {
            int position = constraint.GenericParameterPosition;
            if(selectedArgs is null ||
                position >= selectedArgs.Length ||
                selectedArgs[position] is null)
            {
                return false;
            }

            effectiveConstraint = selectedArgs[position]!;
            return true;
        }

        // where T : IComparable<U>
        if(constraint.IsGenericType && constraint.ContainsGenericParameters)
        {
            var genericArguments = constraint.GetGenericArguments();
            for(int i = 0; i < genericArguments.Length; i++)
            {
                if(!TryMakeEffective(genericArguments[i], selectedArgs, out var effectiveArgument))
                    return false;

                genericArguments[i] = effectiveArgument;
            }

            effectiveConstraint = constraint.GetGenericTypeDefinition().MakeGenericType(genericArguments);
            return true;
        }

        effectiveConstraint = constraint;
        return true;
    }
}