/// <summary> /// Infer return type. If `nullableState` is non-null, nullability is also inferred and `NullableWalker.Analyze` /// uses that state to set the inferred nullability of variables in the enclosing scope. `conversions` is /// only needed when nullability is inferred. /// </summary> public TypeWithAnnotations GetInferredReturnType(ConversionsBase conversions, NullableWalker.VariableState nullableState, ref HashSet <DiagnosticInfo> useSiteDiagnostics) { if (!InferredReturnType.UseSiteDiagnostics.IsEmpty) { if (useSiteDiagnostics == null) { useSiteDiagnostics = new HashSet <DiagnosticInfo>(); } foreach (var info in InferredReturnType.UseSiteDiagnostics) { useSiteDiagnostics.Add(info); } } if (nullableState == null) { return(InferredReturnType.TypeWithAnnotations); } else { Debug.Assert(conversions != null); // Diagnostics from NullableWalker.Analyze can be dropped here since Analyze // will be called again from NullableWalker.ApplyConversion when the // BoundLambda is converted to an anonymous function. // https://github.com/dotnet/roslyn/issues/31752: Can we avoid generating extra // diagnostics? And is this exponential when there are nested lambdas? var returnTypes = ArrayBuilder <(BoundReturnStatement, TypeWithAnnotations)> .GetInstance(); var diagnostics = DiagnosticBag.GetInstance(); var delegateType = Type.GetDelegateType(); var compilation = Binder.Compilation; NullableWalker.Analyze(compilation, lambda: this, (Conversions)conversions, diagnostics, delegateInvokeMethod: delegateType?.DelegateInvokeMethod, initialState: nullableState, analyzedNullabilityMapOpt: null, updatedMethodSymbolMapOpt: null, snapshotBuilderOpt: null, returnTypes); diagnostics.Free(); var inferredReturnType = InferReturnType(returnTypes, node: this, compilation, conversions, delegateType, Symbol.IsAsync); returnTypes.Free(); return(inferredReturnType.TypeWithAnnotations); } }
public TypeSymbolWithAnnotations GetInferredReturnType(ref HashSet <DiagnosticInfo> useSiteDiagnostics, NullableWalker.VariableState nullableState = null) { if (!InferredReturnType.UseSiteDiagnostics.IsEmpty) { if (useSiteDiagnostics == null) { useSiteDiagnostics = new HashSet <DiagnosticInfo>(); } foreach (var info in InferredReturnType.UseSiteDiagnostics) { useSiteDiagnostics.Add(info); } } if (nullableState == null) { return(InferredReturnType.Type); } else { var returnTypes = ArrayBuilder <(RefKind, TypeSymbolWithAnnotations)> .GetInstance(); // Diagnostics from NullableWalker.Analyze can be dropped here since Analyze // will be called again from NullableWalker.ApplyConversion when the // BoundLambda is converted to an anonymous function. // https://github.com/dotnet/roslyn/issues/29617 Can we avoid generating extra // diagnostics? And is this exponential when there are nested lambdas? var diagnostics = DiagnosticBag.GetInstance(); var delegateType = Type.GetDelegateType(); var compilation = Binder.Compilation; var conversions = (Conversions)Binder.Conversions.WithNullability(includeNullability: true); NullableWalker.Analyze(compilation, lambda: this, diagnostics, delegateInvokeMethod: delegateType?.DelegateInvokeMethod, returnTypes: returnTypes, initialState: nullableState); diagnostics.Free(); var inferredReturnType = InferReturnType(returnTypes, compilation, conversions, delegateType, Symbol.IsAsync); returnTypes.Free(); return(inferredReturnType.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*/. /// </summary> internal static void VerifyTypes(this CSharpCompilation compilation, SyntaxTree tree = null) { 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 method = (MethodSymbol)model.GetDeclaredSymbol(annotations.Key); var diagnostics = DiagnosticBag.GetInstance(); var block = MethodCompiler.BindMethodBody(method, new TypeCompilationState(method.ContainingType, compilation, null), diagnostics); var dictionary = new Dictionary <SyntaxNode, TypeSymbolWithAnnotations>(); NullableWalker.Analyze( compilation, method, block, diagnostics, callbackOpt: (BoundExpression expr, TypeSymbolWithAnnotations exprType) => dictionary[expr.Syntax] = exprType); diagnostics.Free(); var expectedTypes = annotations.SelectAsArray(annotation => annotation.Text); var actualTypes = annotations.SelectAsArray(annotation => toDisplayString(annotation.Expression)); // Consider reporting the correct source with annotations on mismatch. AssertEx.Equal(expectedTypes, actualTypes, message: method.ToTestDisplayString()); foreach (var entry in dictionary.Values.Where(v => !v.IsNull)) { // Result types cannot have nested types that are unspeakables Assert.Null(entry.VisitType(typeOpt: null, typeWithAnnotationsPredicateOpt: (tswa, a, b) => !tswa.Equals(entry, TypeCompareKind.ConsiderEverything) && !tswa.NullableAnnotation.IsSpeakable(), typePredicateOpt: (ts, _, b) => false, arg: (object)null, canDigThroughNullable: true)); } string toDisplayString(SyntaxNode syntaxOpt) { return((syntaxOpt != null) && dictionary.TryGetValue(syntaxOpt, out var type) ? (type.IsNull ? "<null>" : type.ToDisplayString(TypeSymbolWithAnnotations.TestDisplayFormat)) : null); } } 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); } }