File: Microsoft\CSharp\RuntimeBinder\Semantics\TypeBind.cs
Web Access
Project: src\src\libraries\Microsoft.CSharp\src\Microsoft.CSharp.csproj (Microsoft.CSharp)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CSharp.RuntimeBinder.Errors;
using Microsoft.CSharp.RuntimeBinder.Syntax;
 
namespace Microsoft.CSharp.RuntimeBinder.Semantics
{
    /////////////////////////////////////////////////////////////////////////////////
    // Defines the structure used when binding types
 
    // CheckConstraints options.
    [Flags]
    internal enum CheckConstraintsFlags
    {
        None = 0x00,
        Outer = 0x01,
        NoErrors = 0x04,
    }
 
    /////////////////////////////////////////////////////////////////////////////////
    // TypeBind has static methods to accomplish most tasks.
    //
    // For some of these tasks there are also instance methods. The instance
    // method versions don't report not found errors (they may report others) but
    // instead record error information in the TypeBind instance. Call the
    // ReportErrors method to report recorded errors.
 
    internal static class TypeBind
    {
        // Check the constraints of any type arguments in the given Type.
        [RequiresUnreferencedCode(Binder.TrimmerWarning)]
        public static bool CheckConstraints(CType type, CheckConstraintsFlags flags)
        {
            type = type.GetNakedType(false);
 
            if (!(type is AggregateType ats))
            {
                if (type is NullableType nub)
                {
                    ats = nub.GetAts();
                }
                else
                {
                    return true;
                }
            }
 
            if (ats.TypeArgsAll.Count == 0)
            {
                // Common case: there are no type vars, so there are no constraints.
                ats.ConstraintError = false;
                return true;
            }
 
            // Already checked.
            if (ats.ConstraintError.HasValue)
            {
                // No errors
                if (!ats.ConstraintError.GetValueOrDefault())
                {
                    return true;
                }
 
                // We want the result, not an exception.
                if ((flags & CheckConstraintsFlags.NoErrors) != 0)
                {
                    return false;
                }
            }
 
            TypeArray typeVars = ats.OwningAggregate.GetTypeVars();
            TypeArray typeArgsThis = ats.TypeArgsThis;
            TypeArray typeArgsAll = ats.TypeArgsAll;
 
            Debug.Assert(typeVars.Count == typeArgsThis.Count);
 
            // Check the outer type first. If CheckConstraintsFlags.Outer is not specified and the
            // outer type has already been checked then don't bother checking it.
            if (ats.OuterType != null && ((flags & CheckConstraintsFlags.Outer) != 0 || !ats.OuterType.ConstraintError.HasValue))
            {
                if (!CheckConstraints(ats.OuterType, flags))
                {
                    ats.ConstraintError = true;
                    return false;
                }
            }
 
            if (typeVars.Count > 0)
            {
                if (!CheckConstraintsCore(ats.OwningAggregate, typeVars, typeArgsThis, typeArgsAll, null, flags & CheckConstraintsFlags.NoErrors))
                {
                    ats.ConstraintError = true;
                    return false;
                }
            }
 
            // Now check type args themselves.
            for (int i = 0; i < typeArgsThis.Count; i++)
            {
                CType arg = typeArgsThis[i].GetNakedType(true);
                if (arg is AggregateType atArg && !atArg.ConstraintError.HasValue)
                {
                    CheckConstraints(atArg, flags | CheckConstraintsFlags.Outer);
                    if (atArg.ConstraintError.GetValueOrDefault())
                    {
                        ats.ConstraintError = true;
                        return false;
                    }
                }
            }
 
            ats.ConstraintError = false;
            return true;
        }
 
        ////////////////////////////////////////////////////////////////////////////////
        // Check the constraints on the method instantiation.
        [RequiresUnreferencedCode(Binder.TrimmerWarning)]
        public static void CheckMethConstraints(MethWithInst mwi)
        {
            Debug.Assert(mwi.Meth() != null && mwi.GetType() != null && mwi.TypeArgs != null);
            Debug.Assert(mwi.Meth().typeVars.Count == mwi.TypeArgs.Count);
            Debug.Assert(mwi.GetType().OwningAggregate == mwi.Meth().getClass());
 
            if (mwi.TypeArgs.Count > 0)
            {
                CheckConstraintsCore(mwi.Meth(), mwi.Meth().typeVars, mwi.TypeArgs, mwi.GetType().TypeArgsAll, mwi.TypeArgs, CheckConstraintsFlags.None);
            }
        }
 
        ////////////////////////////////////////////////////////////////////////////////
        // Check whether typeArgs satisfies the constraints of typeVars. The
        // typeArgsCls and typeArgsMeth are used for substitution on the bounds. The
        // tree and symErr are used for error reporting.
        [RequiresUnreferencedCode(Binder.TrimmerWarning)]
        private static bool CheckConstraintsCore(Symbol symErr, TypeArray typeVars, TypeArray typeArgs, TypeArray typeArgsCls, TypeArray typeArgsMeth, CheckConstraintsFlags flags)
        {
            Debug.Assert(typeVars.Count == typeArgs.Count);
            Debug.Assert(typeVars.Count > 0);
            Debug.Assert(flags == CheckConstraintsFlags.None || flags == CheckConstraintsFlags.NoErrors);
 
            for (int i = 0; i < typeVars.Count; i++)
            {
                // Empty bounds should be set to object.
                TypeParameterType var = (TypeParameterType)typeVars[i];
                CType arg = typeArgs[i];
 
                if (!CheckSingleConstraint(symErr, var, arg, typeArgsCls, typeArgsMeth, flags))
                {
                    return false;
                }
            }
 
            return true;
        }
 
        [RequiresUnreferencedCode(Binder.TrimmerWarning)]
        private static bool CheckSingleConstraint(Symbol symErr, TypeParameterType var, CType arg, TypeArray typeArgsCls, TypeArray typeArgsMeth, CheckConstraintsFlags flags)
        {
            Debug.Assert(!(arg is PointerType));
            Debug.Assert(!arg.IsStaticClass);
 
            bool fReportErrors = 0 == (flags & CheckConstraintsFlags.NoErrors);
 
            if (var.HasRefConstraint && !arg.IsReferenceType)
            {
                if (fReportErrors)
                {
                    throw ErrorHandling.Error(ErrorCode.ERR_RefConstraintNotSatisfied, symErr, new ErrArgNoRef(var), arg);
                }
 
                return false;
            }
 
            TypeArray bnds = TypeManager.SubstTypeArray(var.Bounds, typeArgsCls, typeArgsMeth);
            int itypeMin = 0;
 
            if (var.HasValConstraint)
            {
                // If we have a type variable that is constrained to a value type, then we
                // want to check if its a nullable type, so that we can report the
                // constraint error below. In order to do this however, we need to check
                // that either the type arg is not a value type, or it is a nullable type.
                //
                // To check whether or not its a nullable type, we need to get the resolved
                // bound from the type argument and check against that.
 
                if (!arg.IsNonNullableValueType)
                {
                    if (fReportErrors)
                    {
                        throw ErrorHandling.Error(ErrorCode.ERR_ValConstraintNotSatisfied, symErr, new ErrArgNoRef(var), arg);
                    }
 
                    return false;
                }
 
                // Since FValCon() is set it is redundant to check System.ValueType as well.
                if (bnds.Count != 0 && bnds[0].IsPredefType(PredefinedType.PT_VALUE))
                {
                    itypeMin = 1;
                }
            }
 
            for (int j = itypeMin; j < bnds.Count; j++)
            {
                CType typeBnd = bnds[j];
                if (!SatisfiesBound(arg, typeBnd))
                {
                    if (fReportErrors)
                    {
                        // The bound isn't satisfied because of a constraint type. Explain to the user why not.
                        // There are 4 main cases, based on the type of the supplied type argument:
                        //  - reference type, or type parameter known to be a reference type
                        //  - nullable type, from which there is a boxing conversion to the constraint type(see below for details)
                        //  - type variable
                        //  - value type
                        // These cases are broken out because: a) The sets of conversions which can be used
                        // for constraint satisfaction is different based on the type argument supplied,
                        // and b) Nullable is one funky type, and user's can use all the help they can get
                        // when using it.
                        ErrorCode error;
                        if (arg.IsReferenceType)
                        {
                            // A reference type can only satisfy bounds to types
                            // to which they have an implicit reference conversion
                            error = ErrorCode.ERR_GenericConstraintNotSatisfiedRefType;
                        }
                        else if (arg is NullableType nubArg && SymbolLoader.HasBaseConversion(nubArg.UnderlyingType, typeBnd))    // This is inlining FBoxingConv
                        {
                            // nullable types do not satisfy bounds to every type that they are boxable to
                            // They only satisfy bounds of object and ValueType
                            if (typeBnd.IsPredefType(PredefinedType.PT_ENUM) || nubArg.UnderlyingType == typeBnd)
                            {
                                // Nullable types don't satisfy bounds of EnumType, or the underlying type of the enum
                                // even though the conversion from Nullable to these types is a boxing conversion
                                // This is a rare case, because these bounds can never be directly stated ...
                                // These bounds can only occur when one type parameter is constrained to a second type parameter
                                // and the second type parameter is instantiated with Enum or the underlying type of the first type
                                // parameter
                                error = ErrorCode.ERR_GenericConstraintNotSatisfiedNullableEnum;
                            }
                            else
                            {
                                // Nullable types don't satisfy the bounds of any interface type
                                // even when there is a boxing conversion from the Nullable type to
                                // the interface type. This will be a relatively common scenario
                                // so we cal it out separately from the previous case.
                                Debug.Assert(typeBnd.IsInterfaceType);
                                error = ErrorCode.ERR_GenericConstraintNotSatisfiedNullableInterface;
                            }
                        }
                        else
                        {
                            // Value types can only satisfy bounds through boxing conversions.
                            // Note that the exceptional case of Nullable types and boxing is handled above.
                            error = ErrorCode.ERR_GenericConstraintNotSatisfiedValType;
                        }
 
                        throw ErrorHandling.Error(error, new ErrArg(symErr), new ErrArg(typeBnd, ErrArgFlags.Unique), var, new ErrArg(arg, ErrArgFlags.Unique));
                    }
 
                    return false;
                }
            }
 
            // Check the newable constraint.
            if (!var.HasNewConstraint || arg.IsValueType)
            {
                return true;
            }
 
            if (arg.IsClassType)
            {
                AggregateSymbol agg = ((AggregateType)arg).OwningAggregate;
 
                // Due to late binding nature of IDE created symbols, the AggregateSymbol might not
                // have all the information necessary yet, if it is not fully bound.
                // by calling LookupAggMember, it will ensure that we will update all the
                // information necessary at least for the given method.
                SymbolLoader.LookupAggMember(NameManager.GetPredefinedName(PredefinedName.PN_CTOR), agg, symbmask_t.MASK_ALL);
 
                if (agg.HasPubNoArgCtor() && !agg.IsAbstract())
                {
                    return true;
                }
            }
 
            if (fReportErrors)
            {
                throw ErrorHandling.Error(ErrorCode.ERR_NewConstraintNotSatisfied, symErr, new ErrArgNoRef(var), arg);
            }
 
            return false;
        }
 
        ////////////////////////////////////////////////////////////////////////////////
        // Determine whether the arg type satisfies the typeBnd constraint. Note that
        // typeBnd could be just about any type (since we added naked type parameter
        // constraints).
 
        [RequiresUnreferencedCode(Binder.TrimmerWarning)]
        private static bool SatisfiesBound(CType arg, CType typeBnd)
        {
            if (typeBnd == arg)
                return true;
 
            switch (typeBnd.TypeKind)
            {
                default:
                    Debug.Fail("Unexpected type.");
                    return false;
 
                case TypeKind.TK_VoidType:
                case TypeKind.TK_PointerType:
                    return false;
 
                case TypeKind.TK_ArrayType:
                case TypeKind.TK_TypeParameterType:
                    break;
 
                case TypeKind.TK_NullableType:
                    typeBnd = ((NullableType)typeBnd).GetAts();
                    break;
 
                case TypeKind.TK_AggregateType:
                    break;
            }
 
            Debug.Assert(typeBnd is AggregateType || typeBnd is TypeParameterType || typeBnd is ArrayType);
 
            switch (arg.TypeKind)
            {
                default:
                    return false;
                case TypeKind.TK_PointerType:
                    return false;
                case TypeKind.TK_NullableType:
                    arg = ((NullableType)arg).GetAts();
                    // Fall through.
                    goto case TypeKind.TK_TypeParameterType;
                case TypeKind.TK_TypeParameterType:
                case TypeKind.TK_ArrayType:
                case TypeKind.TK_AggregateType:
                    return SymbolLoader.HasBaseConversion(arg, typeBnd);
            }
        }
    }
}