public static bool AreTypesAssignable()

in src/Bicep.Core/TypeSystem/TypeValidator.cs [76:200]


        public static bool AreTypesAssignable(TypeSymbol sourceType, TypeSymbol targetType)
        {
            if (sourceType.ValidationFlags.HasFlag(TypeSymbolValidationFlags.PreventAssignment) || targetType.ValidationFlags.HasFlag(TypeSymbolValidationFlags.PreventAssignment))
            {
                return false;
            }

            if (sourceType is AnyType)
            {
                // "any" type is assignable to all types
                return true;
            }

            switch (sourceType, targetType)
            {
                case (_, AnyType):
                    // values of all types can be assigned to the "any" type
                    return true;

                case (LambdaType sourceLambda, LambdaType targetLambda):
                    return AreLambdaTypesAssignable(sourceLambda, targetLambda);

                case (IScopeReference, IScopeReference):
                    // checking for valid combinations of scopes happens after type checking. this allows us to provide a richer & more intuitive error message.
                    return true;

                case (_, UnionType targetUnion) when ReferenceEquals(targetUnion, LanguageConstants.ResourceOrResourceCollectionRefItem):
                    return sourceType is IScopeReference || sourceType is ArrayType { Item: IScopeReference };

                case (ResourceType sourceResourceType, ResourceParentType targetResourceParentType):
                    // Assigning a resource to a parent property.
                    return sourceResourceType.TypeReference.IsParentOf(targetResourceParentType.ChildTypeReference);

                case (ResourceType sourceResourceType, ResourceParameterType resourceParameterType):
                    // Assigning a resource to a parameter ignores the API Version
                    return sourceResourceType.TypeReference.FormatType().Equals(resourceParameterType.TypeReference.FormatType(), StringComparison.OrdinalIgnoreCase);

                case (ResourceType sourceResourceType, _):
                    // When assigning a resource, we're really assigning the value of the resource body.
                    return AreTypesAssignable(sourceResourceType.Body.Type, targetType);

                case (ModuleType sourceModuleType, _):
                    // When assigning a module, we're really assigning the value of the module body.
                    return AreTypesAssignable(sourceModuleType.Body.Type, targetType);

                case (TestType sourceTestType, _):
                    // When assigning a module, we're really assigning the value of the test body.
                    return AreTypesAssignable(sourceTestType.Body.Type, targetType);

                case (StringLiteralType, StringLiteralType):
                    // The name *is* the escaped string value, so we must have an exact match.
                    return targetType.Name == sourceType.Name;

                case (IntegerLiteralType sourceInt, IntegerLiteralType targetInt):
                    return targetInt.Value == sourceInt.Value;

                case (BooleanLiteralType sourceBool, BooleanLiteralType targetBool):
                    return sourceBool.Value == targetBool.Value;

                case (StringType, StringLiteralType):
                    // We allow primitive to like-typed literal assignment only in the case where the "AllowLooseAssignment" validation flag has been set.
                    // This is to allow parameters without 'allowed' values to be assigned to fields expecting enums.
                    // At some point we may want to consider flowing the enum type backwards to solve this more elegantly.
                    return sourceType.ValidationFlags.HasFlag(TypeSymbolValidationFlags.AllowLooseAssignment);

                case (IntegerType, IntegerLiteralType):
                    return sourceType.ValidationFlags.HasFlag(TypeSymbolValidationFlags.AllowLooseAssignment);

                case (BooleanType, BooleanLiteralType):
                    return sourceType.ValidationFlags.HasFlag(TypeSymbolValidationFlags.AllowLooseAssignment);

                case (StringLiteralType, StringType):
                    return true;

                case (IntegerLiteralType, IntegerType):
                    // integer literals can be assigned to ints
                    return true;

                case (BooleanLiteralType, BooleanType):
                    // boolean literals can be assigned to bools
                    return true;

                case (IntegerType, IntegerType):
                    return true;

                case (StringType, StringType):
                    return true;

                case (BooleanType, BooleanType):
                    return true;

                case (NullType, NullType):
                    return true;

                case (ArrayType sourceArray, ArrayType targetArray):
                    // both types are arrays
                    // this function does not validate item types
                    return true;

                case (ObjectType, ObjectType):
                case (DiscriminatedObjectType, DiscriminatedObjectType):
                case (ObjectType, DiscriminatedObjectType):
                case (DiscriminatedObjectType, ObjectType):
                    // validation left for later
                    return true;

                case (UnionType sourceUnion, _):
                    // union types are guaranteed to be flat

                    // TODO: Replace with some sort of set intersection
                    // are all source type members assignable to the target type?
                    return sourceUnion.Members.All(sourceMember => AreTypesAssignable(sourceMember.Type, targetType) == true);

                case (_, UnionType targetUnion):
                    // the source type should be a singleton type
                    Debug.Assert(!(sourceType is UnionType), "!(sourceType is UnionType)");

                    // can source type be assigned to any union member types
                    return targetUnion.Members.Any(targetMember => AreTypesAssignable(sourceType, targetMember.Type) == true);

                default:
                    // expression cannot be assigned to the type
                    return false;
            }
        }