src/Compilers/Test/Utilities/CSharp/CompilationTestUtils.cs
/// <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>
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*/.
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 * / .
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);
                        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);
                    });
                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;
                    }
                }
            }
        }
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 ) ; 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 ) ; } ) ; 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 = " " ; const string convertedPrefix = " " ; 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 ; } } } }
Verify the type and nullability inferred by NullabilityWalker of all expressions in the source
that are followed by specific annotations.
Verify the type and nullability inferred by NullabilityWalker of all expressions in the source that are followed by specific annotations .
// 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) // Consider reporting the correct source with annotations on mismatch.
CSharpCompilation
this
tree
SyntaxTree