private TypeSymbol NarrowType()

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