in src/Bicep.Core/TypeSystem/TypeValidator.cs [230:378]
private TypeSymbol NarrowType(TypeValidatorConfig config, SyntaxBase expression, TypeSymbol targetType)
=> NarrowType(config, expression, typeManager.GetTypeInfo(expression), targetType);
private TypeSymbol NarrowType(TypeValidatorConfig config, SyntaxBase expression, TypeSymbol expressionType, TypeSymbol targetType)
{
if (config.DisallowAny && expressionType is AnyType)
{
// certain properties such as scope, parent, dependsOn do not allow values of "any" type
diagnosticWriter.Write(config.OriginSyntax ?? expression, x => x.AnyTypeIsNotAllowed());
// if we let the type narrowing continue, we could get more diagnostics
// but it also leads to duplicate "disallow any" diagnostics caused by the union type narrowing
// (occurs with "dependsOn: [ any(true) ]")
return targetType;
}
if (config.SkipTypeErrors == false && expressionType is ErrorType)
{
// since we dynamically checked type, we need to collect the errors but only if the caller wants them
diagnosticWriter.WriteMultiple(expressionType.GetDiagnostics());
return targetType;
}
switch (targetType)
{
case ResourceType targetResourceType:
{
var narrowedBody = NarrowType(config, expression, targetResourceType.Body.Type);
return new ResourceType(
targetResourceType.DeclaringNamespace,
targetResourceType.TypeReference,
targetResourceType.ValidParentScopes,
targetResourceType.ReadOnlyScopes,
targetResourceType.Flags,
narrowedBody,
targetResourceType.UniqueIdentifierProperties);
}
case ModuleType targetModuleType:
{
var narrowedBody = NarrowType(config, expression, targetModuleType.Body.Type);
return new ModuleType(targetModuleType.Name, targetModuleType.ValidParentScopes, narrowedBody);
}
case TestType targetTestType:
{
var narrowedBody = NarrowType(config, expression, targetTestType.Body.Type);
return new TestType(targetTestType.Name, narrowedBody);
}
case ArrayType loopArrayType when expression is ForSyntax @for:
{
// for-expression assignability check
var narrowedBody = NarrowType(config, @for.Body, loopArrayType.Item.Type);
return new TypedArrayType(narrowedBody, TypeSymbolValidationFlags.Default);
}
case UnionType when TypeCollapser.TryCollapse(targetType) is TypeSymbol collapsed:
targetType = collapsed;
break;
}
// basic assignability check
if (AreTypesAssignable(expressionType, targetType) == false)
{
if (TypeHelper.WouldBeAssignableIfNonNullable(expressionType, targetType, out var nonNullableExpressionType))
{
diagnosticWriter.Write(DiagnosticBuilder.ForPosition(expression).PossibleNullReferenceAssignment(targetType, expressionType.Type, expression));
return NarrowType(config, expression, nonNullableExpressionType, targetType);
}
// fundamentally different types - cannot assign
if (config.OnTypeMismatch is not null)
{
config.OnTypeMismatch(targetType, expressionType, expression);
}
else
{
diagnosticWriter.Write(config.OriginSyntax ?? expression, x => x.ExpectedValueTypeMismatch(ShouldWarn(targetType), targetType, expressionType));
}
return targetType;
}
if (targetType is ResourceParameterType)
{
return targetType;
}
// integer assignability check
if (targetType is IntegerType targetInteger)
{
return NarrowIntegerAssignmentType(config, expression, expressionType, targetInteger);
}
if (targetType is IntegerLiteralType targetIntegerLiteral)
{
return NarrowIntegerLiteralAssignmentType(config, expression, expressionType, targetIntegerLiteral);
}
// string assignability check
if (targetType is StringType targetString)
{
return NarrowStringAssignmentType(config, expression, expressionType, targetString);
}
if (targetType is StringLiteralType targetStringLiteral)
{
return NarrowStringLiteralAssignmentType(config, expression, expressionType, targetStringLiteral);
}
// object assignability check
if (targetType is ObjectType targetObjectType &&
NarrowObjectAssignmentType(config, UnwrapIfConditionBody(expression), expressionType, targetObjectType) is TypeSymbol narrowedObject)
{
return narrowedObject;
}
if (targetType is DiscriminatedObjectType targetDiscriminatedObjectType &&
NarrowDiscriminatedObjectType(config, UnwrapIfConditionBody(expression), expressionType, targetDiscriminatedObjectType) is TypeSymbol narrowedDiscriminatedObject)
{
return narrowedDiscriminatedObject;
}
// array assignability check
if (targetType is ArrayType targetArrayType &&
NarrowArrayAssignmentType(config, expression, expressionType, targetArrayType) is TypeSymbol narrowedArray)
{
return narrowedArray;
}
if (expression is VariableAccessSyntax variableAccess &&
NarrowVariableAccessType(config, variableAccess, targetType) is TypeSymbol narrowedVariableAccess)
{
return narrowedVariableAccess;
}
if (targetType is UnionType targetUnionType)
{
return NarrowUnionType(config, expression, expressionType, targetUnionType);
}
if (expression is LambdaSyntax sourceLambda && targetType is LambdaType targetLambdaType)
{
return NarrowLambdaType(config, sourceLambda, targetLambdaType);
}
return expressionType;
}