CSharpControlFlowNullReferenceState GetExpressionNullReferenceState(
            [CanBeNull] CSharpCompilerNullableInspector nullabilityInspector,
            [CanBeNull] CSharpControlFlowGraphInspector inspector,
            [CanBeNull][ItemNotNull] HashSet <IAsExpression> alwaysSuccessTryCastExpressions,
            [NotNull] ICSharpExpression expression)
        {
            if (nullabilityInspector != null)
            {
                return(GetExpressionNullReferenceStateByNullableContext(nullabilityInspector, expression));
            }

            Debug.Assert(inspector != null);
            Debug.Assert(alwaysSuccessTryCastExpressions != null);

            while (true)
            {
                switch (expression)
                {
                case IReferenceExpression referenceExpression:
                    if (referenceExpression is IConditionalAccessExpression conditionalAccessExpression &&
                        conditionalAccessExpression.HasConditionalAccessSign)
                    {
                        var referenceState = GetExpressionNullReferenceStateByAnnotations(referenceExpression);
                        if (referenceState == CSharpControlFlowNullReferenceState.NOT_NULL)
                        {
                            expression = conditionalAccessExpression.ConditionalQualifier;
                            continue;
                        }
                    }

                    var nullReferenceState = inspector.GetExpressionNullReferenceState(referenceExpression, true);

                    if (nullReferenceState == CSharpControlFlowNullReferenceState.UNKNOWN)
                    {
                        nullReferenceState = GetExpressionNullReferenceStateByAnnotations(referenceExpression);
                    }

                    return(nullReferenceState);

                case IAsExpression asExpression when alwaysSuccessTryCastExpressions.Contains(asExpression):
                    return(CSharpControlFlowNullReferenceState.NOT_NULL);

                case IObjectCreationExpression _: return(CSharpControlFlowNullReferenceState.NOT_NULL);

                case IInvocationExpression invocationExpression:
                    if (invocationExpression.InvokedExpression is IReferenceExpression invokedExpression)
                    {
                        return(GetExpressionNullReferenceStateByAnnotations(invokedExpression));
                    }

                    goto default;

                default: return(CSharpControlFlowNullReferenceState.UNKNOWN);
                }
            }
        }
        static CSharpControlFlowNullReferenceState GetExpressionNullReferenceStateByNullableContext(
            [NotNull] CSharpCompilerNullableInspector nullabilityInspector,
            [NotNull] ICSharpExpression expression)
        {
            var type = expression.Type();

            if (expression.IsDefaultValueOf(type))
            {
                switch (type.Classify)
                {
                case TypeClassification.VALUE_TYPE:
                    return(type.IsNullable() ? CSharpControlFlowNullReferenceState.NULL : CSharpControlFlowNullReferenceState.NOT_NULL);

                case TypeClassification.REFERENCE_TYPE: return(CSharpControlFlowNullReferenceState.NULL);

                case TypeClassification.UNKNOWN: return(CSharpControlFlowNullReferenceState.UNKNOWN);    // unconstrained generic type

                default: goto case TypeClassification.UNKNOWN;
                }
            }

            var controlFlowGraph = nullabilityInspector.ControlFlowGraph;

            Debug.Assert(controlFlowGraph != null);

            var rootElement = controlFlowGraph.BodyElement.SourceElement;
            var inClosure   = null as bool?;

            for (var e = (ITreeNode)expression; e != null && e != rootElement; e = e.Parent)
            {
                if (e != expression)
                {
                    if (inClosure == null)
                    {
                        inClosure = expression.IsInsideClosure();
                    }
                    if (inClosure != e.IsInsideClosure())
                    {
                        break;
                    }
                }

                var edge = controlFlowGraph.GetLeafElementsFor(e).LastOrDefault()?.Exits.FirstOrDefault();
                if (edge != null)
                {
                    var nullableContext = nullabilityInspector.GetContext(edge);

                    switch (nullableContext?.ExpressionAnnotation)
                    {
                    case NullableAnnotation.NotAnnotated:
                    case NullableAnnotation.NotNullable:
                        return(CSharpControlFlowNullReferenceState.NOT_NULL);

                    case NullableAnnotation.Annotated:
                    case NullableAnnotation.Nullable:
                        return(CSharpControlFlowNullReferenceState.MAY_BE_NULL);    // todo: distinguish if the expression is "null" or just "may be null" here

                    default: return(CSharpControlFlowNullReferenceState.UNKNOWN);
                    }
                }
            }

            return(CSharpControlFlowNullReferenceState.UNKNOWN);
        }
        void AnalyzeWhenExpressionIsKnownToBeTrueOrFalse(
            [NotNull] IHighlightingConsumer context,
            [CanBeNull] CSharpCompilerNullableInspector nullabilityInspector,
            [CanBeNull] CSharpControlFlowGraphInspector inspector,
            [CanBeNull][ItemNotNull] HashSet <IAsExpression> alwaysSuccessTryCastExpressions,
            [NotNull] Assertion assertion,
            bool isKnownToBeTrue)
        {
            if (assertion is AssertionStatement assertionStatement)
            {
                // pattern: Assert(true); or Assert(false);
                Debug.Assert(CSharpTokenType.TRUE_KEYWORD != null);
                Debug.Assert(CSharpTokenType.FALSE_KEYWORD != null);
                if (IsLiteral(assertionStatement.Expression, isKnownToBeTrue ? CSharpTokenType.TRUE_KEYWORD : CSharpTokenType.FALSE_KEYWORD))
                {
                    context.AddHighlighting(
                        new RedundantAssertionStatementSuggestion(
                            $"Assertion is redundant because the expression is {(isKnownToBeTrue ? "true" : "false")} here.",
                            assertionStatement));
                }

                if (assertionStatement.Expression is IEqualityExpression equalityExpression)
                {
                    // pattern: Assert(x != null); when x is known to be null or not null
                    Debug.Assert(CSharpTokenType.NULL_KEYWORD != null);
                    var expression = TryGetOtherOperand(equalityExpression, EqualityExpressionType.NE, CSharpTokenType.NULL_KEYWORD);
                    if (expression != null)
                    {
                        switch (GetExpressionNullReferenceState(
                                    nullabilityInspector,
                                    inspector,
                                    alwaysSuccessTryCastExpressions,
                                    expression))
                        {
                        case CSharpControlFlowNullReferenceState.NOT_NULL:
                            if (isKnownToBeTrue)
                            {
                                context.AddHighlighting(
                                    new RedundantAssertionStatementSuggestion(
                                        "Assertion is redundant because the expression is true here.",
                                        assertionStatement));
                            }
                            break;

                        case CSharpControlFlowNullReferenceState.NULL:
                            if (!isKnownToBeTrue)
                            {
                                context.AddHighlighting(
                                    new RedundantAssertionStatementSuggestion(
                                        "Assertion is redundant because the expression is false here.",
                                        assertionStatement));
                            }
                            break;
                        }
                    }

                    // pattern: Assert(x == null); when x is known to be null or not null
                    expression = TryGetOtherOperand(equalityExpression, EqualityExpressionType.EQEQ, CSharpTokenType.NULL_KEYWORD);
                    if (expression != null)
                    {
                        switch (GetExpressionNullReferenceState(nullabilityInspector, inspector, alwaysSuccessTryCastExpressions, expression))
                        {
                        case CSharpControlFlowNullReferenceState.NOT_NULL:
                            if (!isKnownToBeTrue)
                            {
                                context.AddHighlighting(
                                    new RedundantAssertionStatementSuggestion(
                                        "Assertion is redundant because the expression is false here.",
                                        assertionStatement));
                            }
                            break;

                        case CSharpControlFlowNullReferenceState.NULL:
                            if (isKnownToBeTrue)
                            {
                                context.AddHighlighting(
                                    new RedundantAssertionStatementSuggestion(
                                        "Assertion is redundant because the expression is true here.",
                                        assertionStatement));
                            }
                            break;
                        }
                    }
                }
            }
        }
        void AnalyzeWhenExpressionIsKnownToBeNullOrNotNull(
            [NotNull] IHighlightingConsumer context,
            [CanBeNull] CSharpCompilerNullableInspector nullabilityInspector,
            [CanBeNull] CSharpControlFlowGraphInspector inspector,
            [CanBeNull][ItemNotNull] HashSet <IAsExpression> alwaysSuccessTryCastExpressions,
            [NotNull] Assertion assertion,
            bool isKnownToBeNull)
        {
            if (assertion is AssertionStatement assertionStatement)
            {
                // pattern: Assert(null);
                Debug.Assert(CSharpTokenType.NULL_KEYWORD != null);
                if (isKnownToBeNull && IsLiteral(assertionStatement.Expression, CSharpTokenType.NULL_KEYWORD))
                {
                    context.AddHighlighting(
                        new RedundantAssertionStatementSuggestion("Assertion is redundant because the expression is null here.", assertionStatement));
                }

                // pattern: Assert(x); when x is known to be null or not null
                switch (GetExpressionNullReferenceState(
                            nullabilityInspector,
                            inspector,
                            alwaysSuccessTryCastExpressions,
                            assertionStatement.Expression))
                {
                case CSharpControlFlowNullReferenceState.NOT_NULL:
                    if (!isKnownToBeNull)
                    {
                        context.AddHighlighting(
                            new RedundantAssertionStatementSuggestion(
                                "Assertion is redundant because the expression is not null here.",
                                assertionStatement));
                    }
                    break;

                case CSharpControlFlowNullReferenceState.NULL:
                    if (isKnownToBeNull)
                    {
                        context.AddHighlighting(
                            new RedundantAssertionStatementSuggestion(
                                "Assertion is redundant because the expression is null here.",
                                assertionStatement));
                    }
                    break;
                }
            }

            if (!isKnownToBeNull)
            {
                switch (assertion)
                {
                case InlineAssertion inlineAssertion:
                    if (GetExpressionNullReferenceState(
                            nullabilityInspector,
                            inspector,
                            alwaysSuccessTryCastExpressions,
                            inlineAssertion.QualifierExpression) ==
                        CSharpControlFlowNullReferenceState.NOT_NULL)
                    {
                        context.AddHighlighting(
                            new RedundantInlineAssertionSuggestion(
                                "Assertion is redundant because the expression is not null here.",
                                inlineAssertion));
                    }
                    break;

                case NullForgivingOperation nullForgivingOperation:
                    Debug.Assert(nullForgivingOperation.SuppressNullableWarningExpression.Operand != null);

                    if (GetExpressionNullReferenceState(
                            nullabilityInspector,
                            inspector,
                            alwaysSuccessTryCastExpressions,
                            nullForgivingOperation.SuppressNullableWarningExpression.Operand) ==
                        CSharpControlFlowNullReferenceState.NOT_NULL)
                    {
                        context.AddHighlighting(
                            new RedundantNullForgivingOperatorSuggestion(
                                "Null-forgiving operator is redundant because the expression is not null here.",
                                nullForgivingOperation));
                    }
                    break;
                }
            }
        }
        void AnalyzeAssertions(
            [NotNull] ElementProblemAnalyzerData data,
            [NotNull] IHighlightingConsumer consumer,
            [NotNull] ICSharpTreeNode rootNode,
            [NotNull] ICSharpControlFlowGraph controlFlowGraph)
        {
            var assertions = Assertion.CollectAssertions(assertionMethodAnnotationProvider, assertionConditionAnnotationProvider, rootNode);

            assertions.ExceptWith(
                from highlightingInfo in consumer.Highlightings
                where highlightingInfo != null
                let redundantAssertionHighlighting = highlightingInfo.Highlighting as RedundantAssertionSuggestion
                                                     where redundantAssertionHighlighting != null
                                                     select redundantAssertionHighlighting.Assertion);

            if (assertions.Count == 0)
            {
                return; // no (new) assertions found
            }

            CSharpCompilerNullableInspector nullabilityInspector;
            CSharpControlFlowGraphInspector inspector;
            HashSet <IAsExpression>         alwaysSuccessTryCastExpressions;

            if (rootNode.IsNullableWarningsContextEnabled())
            {
                nullabilityInspector = (CSharpCompilerNullableInspector)CSharpCompilerNullableInspector.Inspect(
                    controlFlowGraph,
                    null,
                    ValueAnalysisMode.OFF); // wrong [NotNull] annotation in R# code
                inspector = null;
                alwaysSuccessTryCastExpressions = null;
            }
            else
            {
                nullabilityInspector            = null;
                inspector                       = CSharpControlFlowGraphInspector.Inspect(controlFlowGraph, data.GetValueAnalysisMode());
                alwaysSuccessTryCastExpressions =
                    new HashSet <IAsExpression>(inspector.AlwaysSuccessTryCastExpressions ?? Array.Empty <IAsExpression>());
            }

            foreach (var assertion in assertions)
            {
                switch (assertion.AssertionConditionType)
                {
                case AssertionConditionType.IS_TRUE:
                    AnalyzeWhenExpressionIsKnownToBeTrueOrFalse(
                        consumer,
                        nullabilityInspector,
                        inspector,
                        alwaysSuccessTryCastExpressions,
                        assertion,
                        true);
                    break;

                case AssertionConditionType.IS_FALSE:
                    AnalyzeWhenExpressionIsKnownToBeTrueOrFalse(
                        consumer,
                        nullabilityInspector,
                        inspector,
                        alwaysSuccessTryCastExpressions,
                        assertion,
                        false);
                    break;

                case AssertionConditionType.IS_NOT_NULL:
                    AnalyzeWhenExpressionIsKnownToBeNullOrNotNull(
                        consumer,
                        nullabilityInspector,
                        inspector,
                        alwaysSuccessTryCastExpressions,
                        assertion,
                        false);
                    break;

                case AssertionConditionType.IS_NULL:
                    AnalyzeWhenExpressionIsKnownToBeNullOrNotNull(
                        consumer,
                        nullabilityInspector,
                        inspector,
                        alwaysSuccessTryCastExpressions,
                        assertion,
                        true);
                    break;
                }
            }
        }