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;
}
}