LearnFromDecisionDag(
            SyntaxNode node,
            BoundDecisionDag decisionDag,
            BoundExpression expression,
            TypeWithState expressionType,
            ref PossiblyConditionalState initialState)
        {
            // We reuse the slot at the beginning of a switch (or is-pattern expression), pretending that we are
            // not copying the input to evaluate the patterns.  In this way we infer non-nullability of the original
            // variable's parts based on matched pattern parts.  Mutations in `when` clauses can show the inaccuracy
            // of analysis based on this choice.
            var rootTemp          = BoundDagTemp.ForOriginalInput(expression);
            int originalInputSlot = MakeSlot(expression);

            if (originalInputSlot <= 0)
            {
                originalInputSlot = makeDagTempSlot(expressionType.ToTypeWithAnnotations(compilation), rootTemp);
            }
            Debug.Assert(originalInputSlot > 0);

            // If the input of the switch (or is-pattern expression) is a tuple literal, we reuse the slots of
            // those expressions (when possible), pretending that we are not copying them into a temporary ValueTuple instance
            // to evaluate the patterns.  In this way we infer non-nullability of the original element's parts.
            // We do not extend such courtesy to nested tuple literals.
            var originalInputElementSlots = expression is BoundTupleExpression tuple
                ? tuple.Arguments.SelectAsArray(static (a, w) => w.MakeSlot(a), this)
Esempio n. 2
0
#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters

        private static ImmutableArray <SymbolDisplayPart> ToDisplayParts(
            ITypeSymbol symbol,
            CodeAnalysis.NullableFlowState nullableFlowState,
            SemanticModel semanticModelOpt,
            int positionOpt,
            SymbolDisplayFormat format,
            bool minimal)
        {
            // https://github.com/dotnet/roslyn/issues/35035: Refactor this. We need to be able to handle non-TypeSymbol inputs
            var annotation = (CodeAnalysis.NullableAnnotation?)TypeWithState.Create((TypeSymbol)symbol, nullableFlowState.ToInternalFlowState()).ToTypeWithAnnotations().NullableAnnotation.ToPublicAnnotation();

            return(ToDisplayParts(symbol, annotation, semanticModelOpt, positionOpt, format, minimal));
        }
        LearnFromDecisionDag(
            SyntaxNode node,
            BoundDecisionDag decisionDag,
            BoundExpression expression,
            TypeWithState expressionType,
            ref LocalState initialState)
        {
            // We reuse the slot at the beginning of a switch (or is-pattern expression), pretending that we are
            // not copying the input to evaluate the patterns.  In this way we infer non-nullability of the original
            // variable's parts based on matched pattern parts.  Mutations in `when` clauses can show the inaccuracy
            // of analysis based on this choice.
            var rootTemp          = BoundDagTemp.ForOriginalInput(expression);
            int originalInputSlot = MakeSlot(expression);

            if (originalInputSlot <= 0)
            {
                originalInputSlot = makeDagTempSlot(expressionType.ToTypeWithAnnotations(), rootTemp);
                initialState[originalInputSlot] = expressionType.State;
            }

            var tempMap = PooledDictionary <BoundDagTemp, (int slot, TypeSymbol type)> .GetInstance();

            Debug.Assert(originalInputSlot > 0);
            tempMap.Add(rootTemp, (originalInputSlot, expressionType.Type));

            var nodeStateMap = PooledDictionary <BoundDecisionDagNode, (LocalState state, bool believedReachable)> .GetInstance();

            nodeStateMap.Add(decisionDag.RootNode, (state: initialState.Clone(), believedReachable: true));

            var labelStateMap = PooledDictionary <LabelSymbol, (LocalState state, bool believedReachable)> .GetInstance();

            foreach (var dagNode in decisionDag.TopologicallySortedNodes)
            {
                bool found = nodeStateMap.TryGetValue(dagNode, out var nodeStateAndBelievedReachable);
                Debug.Assert(found); // the topologically sorted nodes should contain only reachable nodes
                (LocalState nodeState, bool nodeBelievedReachable) = nodeStateAndBelievedReachable;
                SetState(nodeState);

                switch (dagNode)
                {
                case BoundEvaluationDecisionDagNode p:
                {
                    var evaluation = p.Evaluation;
                    (int inputSlot, TypeSymbol inputType) = tempMap.TryGetValue(evaluation.Input, out var slotAndType) ? slotAndType : throw ExceptionUtilities.Unreachable;
                    Debug.Assert(inputSlot > 0);
                    var inputState = this.State[inputSlot];

                    switch (evaluation)
                    {
                    case BoundDagDeconstructEvaluation e:
                    {
                        // https://github.com/dotnet/roslyn/issues/34232
                        // We may need to recompute the Deconstruct method for a deconstruction if
                        // the receiver type has changed (e.g. its nested nullability).
                        var method         = e.DeconstructMethod;
                        int extensionExtra = method.IsStatic ? 1 : 0;
                        for (int i = 0; i < method.ParameterCount - extensionExtra; i++)
                        {
                            var parameterType = method.Parameters[i + extensionExtra].TypeWithAnnotations;
                            var output        = new BoundDagTemp(e.Syntax, parameterType.Type, e, i);
                            int outputSlot    = makeDagTempSlot(parameterType, output);
                            Debug.Assert(outputSlot > 0);
                            addToTempMap(output, outputSlot, parameterType.Type);
                        }
                        break;
                    }

                    case BoundDagTypeEvaluation e:
                    {
                        var output = new BoundDagTemp(e.Syntax, e.Type, e);
                        HashSet <DiagnosticInfo> discardedDiagnostics = null;
                        int outputSlot;
                        switch (_conversions.WithNullability(false).ClassifyConversionFromType(inputType, e.Type, ref discardedDiagnostics).Kind)
                        {
                        case ConversionKind.Identity:
                        case ConversionKind.ImplicitReference:
                        case ConversionKind.NoConversion:
                        case ConversionKind.ExplicitReference:
                            outputSlot = inputSlot;
                            break;

                        case ConversionKind.ExplicitNullable when AreNullableAndUnderlyingTypes(inputType, e.Type, out _):
                            outputSlot = GetNullableOfTValueSlot(inputType, inputSlot, out _, forceSlotEvenIfEmpty: true);

                            if (outputSlot < 0)
                            {
                                goto default;
                            }
                            break;

                        default:
                            outputSlot = makeDagTempSlot(TypeWithAnnotations.Create(e.Type, NullableAnnotation.NotAnnotated), output);
                            break;
                        }
                        State[outputSlot] = NullableFlowState.NotNull;
                        var outputType = TypeWithState.Create(e.Type, inputState);
                        addToTempMap(output, outputSlot, outputType.Type);
                        break;
                    }

                    case BoundDagFieldEvaluation e:
                    {
                        Debug.Assert(inputSlot > 0);
                        var field      = (FieldSymbol)AsMemberOfType(inputType, e.Field);
                        int outputSlot = GetOrCreateSlot(field, inputSlot, forceSlotEvenIfEmpty: true);
                        Debug.Assert(outputSlot > 0);
                        var type   = field.Type;
                        var output = new BoundDagTemp(e.Syntax, type, e);
                        addToTempMap(output, outputSlot, type);
                        break;
                    }

                    case BoundDagPropertyEvaluation e:
                    {
                        Debug.Assert(inputSlot > 0);
                        var property   = (PropertySymbol)AsMemberOfType(inputType, e.Property);
                        var type       = property.TypeWithAnnotations;
                        var output     = new BoundDagTemp(e.Syntax, type.Type, e);
                        int outputSlot = GetOrCreateSlot(property, inputSlot, forceSlotEvenIfEmpty: true);
                        if (outputSlot <= 0)
                        {
                            // This is needed due to https://github.com/dotnet/roslyn/issues/29619
                            outputSlot = makeDagTempSlot(type, output);
                        }
                        Debug.Assert(outputSlot > 0);
                        addToTempMap(output, outputSlot, type.Type);
                        break;
                    }

                    case BoundDagIndexEvaluation e:
                    {
                        var type       = TypeWithAnnotations.Create(e.Property.Type, NullableAnnotation.Annotated);
                        var output     = new BoundDagTemp(e.Syntax, type.Type, e);
                        int outputSlot = makeDagTempSlot(type, output);
                        Debug.Assert(outputSlot > 0);
                        addToTempMap(output, outputSlot, type.Type);
                        break;
                    }

                    default:
                        throw ExceptionUtilities.UnexpectedValue(p.Evaluation.Kind);
                    }
                    gotoNode(p.Next, this.State, nodeBelievedReachable);
                    break;
                }

                case BoundTestDecisionDagNode p:
                {
                    var  test      = p.Test;
                    bool foundTemp = tempMap.TryGetValue(test.Input, out var slotAndType);
                    Debug.Assert(foundTemp);

                    (int inputSlot, TypeSymbol inputType) = slotAndType;
                    var inputState = this.State[inputSlot];
                    Split();
                    switch (test)
                    {
                    case BoundDagTypeTest t:
                        if (inputSlot > 0)
                        {
                            learnFromNonNullTest(inputSlot, ref this.StateWhenTrue);
                        }
                        gotoNode(p.WhenTrue, this.StateWhenTrue, nodeBelievedReachable);
                        gotoNode(p.WhenFalse, this.StateWhenFalse, nodeBelievedReachable);
                        break;

                    case BoundDagNonNullTest t:
                        if (inputSlot > 0)
                        {
                            learnFromNonNullTest(inputSlot, ref this.StateWhenTrue);
                        }
                        gotoNode(p.WhenTrue, this.StateWhenTrue, nodeBelievedReachable);
                        gotoNode(p.WhenFalse, this.StateWhenFalse, nodeBelievedReachable & inputState.MayBeNull());
                        break;

                    case BoundDagExplicitNullTest t:
                        if (inputSlot > 0)
                        {
                            LearnFromNullTest(inputSlot, inputType, ref this.StateWhenTrue);
                            learnFromNonNullTest(inputSlot, ref this.StateWhenFalse);
                        }
                        gotoNode(p.WhenTrue, this.StateWhenTrue, nodeBelievedReachable);
                        gotoNode(p.WhenFalse, this.StateWhenFalse, nodeBelievedReachable);
                        break;

                    case BoundDagValueTest t:
                        Debug.Assert(t.Value != ConstantValue.Null);
                        if (inputSlot > 0)
                        {
                            learnFromNonNullTest(inputSlot, ref this.StateWhenTrue);
                        }
                        gotoNode(p.WhenTrue, this.StateWhenTrue, nodeBelievedReachable);
                        gotoNode(p.WhenFalse, this.StateWhenFalse, nodeBelievedReachable);
                        break;

                    default:
                        throw ExceptionUtilities.UnexpectedValue(test.Kind);
                    }
                    break;
                }

                case BoundLeafDecisionDagNode d:
                    // We have one leaf decision dag node per reachable label
                    labelStateMap.Add(d.Label, (this.State, nodeBelievedReachable));
                    break;

                case BoundWhenDecisionDagNode w:
                    // bind the pattern variables, inferring their types as well
                    foreach (var binding in w.Bindings)
                    {
                        var variableAccess = binding.VariableAccess;
                        var tempSource     = binding.TempContainingValue;
                        var foundTemp      = tempMap.TryGetValue(tempSource, out var tempSlotAndType);
                        Debug.Assert(foundTemp);
                        var(tempSlot, tempType) = tempSlotAndType;
                        var tempState = this.State[tempSlot];
                        if (variableAccess is BoundLocal {
                            LocalSymbol: SourceLocalSymbol {
                                IsVar: true
                            } local
                        })
                        {
                            var inferredType = TypeWithState.Create(tempType, tempState).ToTypeWithAnnotations();
                            if (_variableTypes.TryGetValue(local, out var existingType))
                            {
                                // merge inferred nullable annotation from different branches of the decision tree
                                _variableTypes[local] = TypeWithAnnotations.Create(existingType.Type, existingType.NullableAnnotation.Join(inferredType.NullableAnnotation));
                            }
                            else
                            {
                                _variableTypes[local] = inferredType;
                            }

                            int localSlot = GetOrCreateSlot(local, forceSlotEvenIfEmpty: true);
                            this.State[localSlot] = tempState;
                        }
Esempio n. 4
0
        /// <summary>
        /// Verify the type and nullability inferred by NullabilityWalker of all expressions in the source
        /// that are followed by specific annotations. Annotations are of the form /*T:type*/.
        /// </summary>
        internal static void VerifyTypes(this CSharpCompilation compilation, SyntaxTree tree = null)
        {
            Assert.True(compilation.NullableSemanticAnalysisEnabled);

            if (tree == null)
            {
                foreach (var syntaxTree in compilation.SyntaxTrees)
                {
                    VerifyTypes(compilation, syntaxTree);
                }

                return;
            }

            var root           = tree.GetRoot();
            var allAnnotations = getAnnotations();

            if (allAnnotations.IsEmpty)
            {
                return;
            }

            var model = compilation.GetSemanticModel(tree);
            var annotationsByMethod = allAnnotations.GroupBy(annotation => annotation.Expression.Ancestors().OfType <BaseMethodDeclarationSyntax>().First()).ToArray();

            foreach (var annotations in annotationsByMethod)
            {
                var methodSyntax = annotations.Key;
                var method       = model.GetDeclaredSymbol(methodSyntax);

                var expectedTypes = annotations.SelectAsArray(annotation => annotation.Text);
                var actualTypes   = annotations.SelectAsArray(annotation =>
                {
                    var typeInfo = model.GetTypeInfo(annotation.Expression);
                    Assert.NotEqual(CodeAnalysis.NullableFlowState.None, typeInfo.Nullability.FlowState);
                    // https://github.com/dotnet/roslyn/issues/35035: After refactoring symboldisplay, we should be able to just call something like typeInfo.Type.ToDisplayString(typeInfo.Nullability.FlowState, TypeWithState.TestDisplayFormat)
                    var type = TypeWithState.Create(
                        (annotation.IsConverted ? typeInfo.ConvertedType : typeInfo.Type).GetSymbol(),
                        (annotation.IsConverted ? typeInfo.ConvertedNullability : typeInfo.Nullability).FlowState.ToInternalFlowState()).ToTypeWithAnnotations();
                    return(type.ToDisplayString(TypeWithAnnotations.TestDisplayFormat));
                });
                // Consider reporting the correct source with annotations on mismatch.
                AssertEx.Equal(expectedTypes, actualTypes, message: method.ToTestDisplayString());
            }

            ImmutableArray <(ExpressionSyntax Expression, string Text, bool IsConverted)> getAnnotations()
            {
                var builder = ArrayBuilder <(ExpressionSyntax, string, bool)> .GetInstance();

                foreach (var token in root.DescendantTokens())
                {
                    foreach (var trivia in token.TrailingTrivia)
                    {
                        if (trivia.Kind() == SyntaxKind.MultiLineCommentTrivia)
                        {
                            var          text                 = trivia.ToFullString();
                            const string typePrefix           = "/*T:";
                            const string convertedPrefix      = "/*CT:";
                            const string suffix               = "*/";
                            bool         startsWithTypePrefix = text.StartsWith(typePrefix);
                            if (text.EndsWith(suffix) && (startsWithTypePrefix || text.StartsWith(convertedPrefix)))
                            {
                                var prefix = startsWithTypePrefix ? typePrefix : convertedPrefix;
                                var expr   = getEnclosingExpression(token);
                                Assert.True(expr != null, $"VerifyTypes could not find a matching expression for annotation '{text}'.");

                                var content = text.Substring(prefix.Length, text.Length - prefix.Length - suffix.Length);
                                builder.Add((expr, content, !startsWithTypePrefix));
                            }
                        }
                    }
                }
                return(builder.ToImmutableAndFree());
            }

            ExpressionSyntax getEnclosingExpression(SyntaxToken token)
            {
                var node = token.Parent;

                while (true)
                {
                    var expr = asExpression(node);
                    if (expr != null)
                    {
                        return(expr);
                    }
                    if (node == root)
                    {
                        break;
                    }
                    node = node.Parent;
                }
                return(null);
            }

            ExpressionSyntax asExpression(SyntaxNode node)
            {
                while (true)
                {
                    switch (node)
                    {
                    case null:
                        return(null);

                    case ParenthesizedExpressionSyntax paren:
                        return(paren.Expression);

                    case IdentifierNameSyntax id when id.Parent is MemberAccessExpressionSyntax memberAccess && memberAccess.Name == node:
                        node = memberAccess;
                        continue;

                    case ExpressionSyntax expr when expr.Parent is ConditionalAccessExpressionSyntax cond && cond.WhenNotNull == node:
                        node = cond;
                        continue;

                    case ExpressionSyntax expr:
                        return(expr);

                    case { Parent: var parent } :
                        node = parent;
                        continue;
                    }
Esempio n. 5
0
 public void VisitWithNullability(TypeSymbol symbol, NullableFlowState topLevelNullability)
 {
     VisitWithAnnotation(TypeWithState.Create(symbol, topLevelNullability).ToTypeWithAnnotations());
 }
Esempio n. 6
0
        /// <summary>
        /// Verify the type and nullability inferred by NullabilityWalker of all expressions in the source
        /// that are followed by specific annotations. Annotations are of the form /*T:type*/.
        /// </summary>
        internal static void VerifyTypes(this CSharpCompilation compilation, SyntaxTree tree = null)
        {
            // When nullable analysis does not require a feature flag, this can be removed so that we
            // don't need to create an extra compilation
            if (compilation.Feature("run-nullable-analysis") != "true")
            {
                compilation = compilation.WithAdditionalFeatures(("run-nullable-analysis", "true"));
            }

            if (tree == null)
            {
                foreach (var syntaxTree in compilation.SyntaxTrees)
                {
                    VerifyTypes(compilation, syntaxTree);
                }
                return;
            }

            var root           = tree.GetRoot();
            var allAnnotations = getAnnotations();

            if (allAnnotations.IsEmpty)
            {
                return;
            }

            var model = compilation.GetSemanticModel(tree);
            var annotationsByMethod = allAnnotations.GroupBy(annotation => annotation.Expression.Ancestors().OfType <BaseMethodDeclarationSyntax>().First()).ToArray();

            foreach (var annotations in annotationsByMethod)
            {
                var methodSyntax = annotations.Key;
                var method       = model.GetDeclaredSymbol(methodSyntax);

                var expectedTypes = annotations.SelectAsArray(annotation => annotation.Text);
                var actualTypes   = annotations.SelectAsArray(annotation =>
                {
                    var typeInfo = model.GetTypeInfo(annotation.Expression);
                    Assert.NotEqual(CodeAnalysis.NullableAnnotation.NotApplicable, typeInfo.Nullability.Annotation);
                    Assert.NotEqual(CodeAnalysis.NullableFlowState.NotApplicable, typeInfo.Nullability.FlowState);
                    // https://github.com/dotnet/roslyn/issues/35035: After refactoring symboldisplay, we should be able to just call something like typeInfo.Type.ToDisplayString(typeInfo.Nullability.FlowState, TypeWithState.TestDisplayFormat)
                    return(TypeWithState.Create((TypeSymbol)typeInfo.Type, typeInfo.Nullability.FlowState.ToInternalFlowState()).ToTypeWithAnnotations().ToDisplayString(TypeWithAnnotations.TestDisplayFormat));
                });
                // Consider reporting the correct source with annotations on mismatch.
                AssertEx.Equal(expectedTypes, actualTypes, message: method.ToTestDisplayString());
            }

            ImmutableArray <(ExpressionSyntax Expression, string Text)> getAnnotations()
            {
                var builder = ArrayBuilder <(ExpressionSyntax, string)> .GetInstance();

                foreach (var token in root.DescendantTokens())
                {
                    foreach (var trivia in token.TrailingTrivia)
                    {
                        if (trivia.Kind() == SyntaxKind.MultiLineCommentTrivia)
                        {
                            var          text   = trivia.ToFullString();
                            const string prefix = "/*T:";
                            const string suffix = "*/";
                            if (text.StartsWith(prefix) && text.EndsWith(suffix))
                            {
                                var expr = getEnclosingExpression(token);
                                Assert.True(expr != null, $"VerifyTypes could not find a matching expression for annotation '{text}'.");

                                var content = text.Substring(prefix.Length, text.Length - prefix.Length - suffix.Length);
                                builder.Add((expr, content));
                            }
                        }
                    }
                }
                return(builder.ToImmutableAndFree());
            }

            ExpressionSyntax getEnclosingExpression(SyntaxToken token)
            {
                var node = token.Parent;

                while (true)
                {
                    var expr = asExpression(node);
                    if (expr != null)
                    {
                        return(expr);
                    }
                    if (node == root)
                    {
                        break;
                    }
                    node = node.Parent;
                }
                return(null);
            }

            ExpressionSyntax asExpression(SyntaxNode node)
            {
                var expr = node as ExpressionSyntax;

                if (expr == null)
                {
                    return(null);
                }
                switch (expr.Kind())
                {
                case SyntaxKind.ParenthesizedExpression:
                    return(((ParenthesizedExpressionSyntax)expr).Expression);

                case SyntaxKind.IdentifierName:
                    if (expr.Parent is MemberAccessExpressionSyntax memberAccess && memberAccess.Name == expr)
                    {
                        return(memberAccess);
                    }
                    break;
                }
                return(expr);
            }
        }