NullableWalker.cs 261.4 KB
Newer Older
1 2 3
// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

#if DEBUG
A
Andy Gocke 已提交
4
// See comment in DefiniteAssignment.
5 6 7 8
#define REFERENCE_STATE
#endif

using System;
9
using System.Collections.Generic;
10 11
using System.Collections.Immutable;
using System.Diagnostics;
12
using System.Linq;
13
using Microsoft.CodeAnalysis.CSharp.Symbols;
14
using Microsoft.CodeAnalysis.CSharp.Syntax;
15 16 17 18 19 20
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp
{
    /// <summary>
21
    /// Nullability flow analysis.
22
    /// </summary>
A
Andy Gocke 已提交
23
    internal sealed partial class NullableWalker : LocalDataFlowPass<NullableWalker.LocalState>
24 25
    {
        /// <summary>
26 27
        /// Used to copy variable slots and types from the NullableWalker for the containing method
        /// or lambda to the NullableWalker created for a nested lambda or local function.
28
        /// </summary>
29 30
        internal sealed class VariableState
        {
31 32
            // Consider referencing the collections directly from the original NullableWalker
            // rather than coping the collections. (Items are added to the collections
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
            // but never replaced so the collections are lazily populated but otherwise immutable.)
            internal readonly ImmutableDictionary<VariableIdentifier, int> VariableSlot;
            internal readonly ImmutableArray<VariableIdentifier> VariableBySlot;
            internal readonly ImmutableDictionary<Symbol, TypeSymbolWithAnnotations> VariableTypes;

            internal VariableState(
                ImmutableDictionary<VariableIdentifier, int> variableSlot,
                ImmutableArray<VariableIdentifier> variableBySlot,
                ImmutableDictionary<Symbol, TypeSymbolWithAnnotations> variableTypes)
            {
                VariableSlot = variableSlot;
                VariableBySlot = variableBySlot;
                VariableTypes = variableTypes;
            }
        }

        /// <summary>
        /// The inferred type at the point of declaration of var locals and parameters.
        /// </summary>
        private readonly PooledDictionary<Symbol, TypeSymbolWithAnnotations> _variableTypes = PooledDictionary<Symbol, TypeSymbolWithAnnotations>.GetInstance();
53

54
        private readonly Binder _binder;
55 56 57 58

        /// <summary>
        /// Conversions with nullability and unknown matching any.
        /// </summary>
59 60
        private readonly Conversions _conversions;

61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
        /// <summary>
        /// Use the return type and nullability from _methodSignatureOpt to calculate return
        /// expression conversions. If false, the signature of _member is used instead.
        /// </summary>
        private readonly bool _useMethodSignatureReturnType;

        /// <summary>
        /// Use the the parameter types and nullability from _methodSignatureOpt for initial
        /// parameter state. If false, the signature of _member is used instead.
        /// </summary>
        private readonly bool _useMethodSignatureParameterTypes;

        /// <summary>
        /// Method signature used for return type or parameter types. Distinct from _member
        /// signature when _member is a lambda and type is inferred from MethodTypeInferrer.
        /// </summary>
        private readonly MethodSymbol _methodSignatureOpt;

        /// <summary>
80
        /// Return statements and the result types from analyzing the returned expressions. Used when inferring lambda return type in MethodTypeInferrer.
81
        /// </summary>
82
        private readonly ArrayBuilder<(BoundReturnStatement, TypeSymbolWithAnnotations)> _returnTypesOpt;
83 84 85 86 87 88 89

        /// <summary>
        /// An optional callback for callers to receive notification of the inferred type and nullability
        /// of each expression in the method. Since the walker may require multiple passes, the callback
        /// may be invoked multiple times for a single expression, potentially with different nullability
        /// each time. The last call for each expression will include the final inferred type and nullability.
        /// </summary>
90 91
        private readonly Action<BoundExpression, TypeSymbolWithAnnotations> _callbackOpt;

92 93
        /// <summary>
        /// Invalid type, used only to catch Visit methods that do not set
94
        /// _result.Type. See VisitExpressionWithoutStackGuard.
95 96 97
        /// </summary>
        private static readonly TypeSymbolWithAnnotations _invalidType = TypeSymbolWithAnnotations.Create(ErrorTypeSymbol.UnknownResultType);

98
        private TypeSymbolWithAnnotations _resultType;
99

100 101 102
        /// <summary>
        /// Instances being constructed.
        /// </summary>
103
        private PooledDictionary<BoundExpression, ObjectCreationPlaceholderLocal> _placeholderLocalsOpt;
104

105 106 107 108 109 110
        /// <summary>
        /// For methods with annotations, we'll need to visit the arguments twice.
        /// Once for diagnostics and once for result state (but disabling diagnostics).
        /// </summary>
        private bool _disableDiagnostics = false;

111 112 113 114 115 116
        /// <summary>
        /// Used to allow <see cref="MakeSlot(BoundExpression)"/> to substitute the correct slot for a <see cref="BoundConditionalReceiver"/> when
        /// it's encountered.
        /// </summary>
        private int _lastConditionalAccessSlot = -1;

117 118
        protected override void Free()
        {
119
            _variableTypes.Free();
120
            _placeholderLocalsOpt?.Free();
121 122 123
            base.Free();
        }

124
        private NullableWalker(
125
            CSharpCompilation compilation,
126 127 128 129
            MethodSymbol method,
            bool useMethodSignatureReturnType,
            bool useMethodSignatureParameterTypes,
            MethodSymbol methodSignatureOpt,
130
            BoundNode node,
131
            ArrayBuilder<(BoundReturnStatement, TypeSymbolWithAnnotations)> returnTypesOpt,
132
            VariableState initialState,
133
            Action<BoundExpression, TypeSymbolWithAnnotations> callbackOpt)
134
            : base(compilation, method, node, new EmptyStructTypeCache(compilation, dev12CompilerCompatibility: false), trackUnassignments: true)
135
        {
136
            _callbackOpt = callbackOpt;
137
            _binder = compilation.GetBinderFactory(node.SyntaxTree).GetBinder(node.Syntax);
138
            Debug.Assert(!_binder.Conversions.IncludeNullability);
139
            _conversions = (Conversions)_binder.Conversions.WithNullability(true);
140 141 142
            _useMethodSignatureReturnType = (object)methodSignatureOpt != null && useMethodSignatureReturnType;
            _useMethodSignatureParameterTypes = (object)methodSignatureOpt != null && useMethodSignatureParameterTypes;
            _methodSignatureOpt = methodSignatureOpt;
143
            _returnTypesOpt = returnTypesOpt;
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
            if (initialState != null)
            {
                var variableBySlot = initialState.VariableBySlot;
                nextVariableSlot = variableBySlot.Length;
                foreach (var (variable, slot) in initialState.VariableSlot)
                {
                    Debug.Assert(slot < nextVariableSlot);
                    _variableSlot.Add(variable, slot);
                }
                this.variableBySlot = variableBySlot.ToArray();
                foreach (var pair in initialState.VariableTypes)
                {
                    _variableTypes.Add(pair.Key, pair.Value);
                }
            }
159 160
        }

161 162
        // For purpose of nullability analysis, awaits create pending branches, so async usings and foreachs do too
        public sealed override bool AwaitUsingAndForeachAddsPendingBranch => true;
163

164 165 166 167 168 169 170
        protected override bool ConvertInsufficientExecutionStackExceptionToCancelledByStackGuardException()
        {
            return true;
        }

        protected override ImmutableArray<PendingBranch> Scan(ref bool badRegion)
        {
171
            if (_returnTypesOpt != null)
172
            {
173
                _returnTypesOpt.Clear();
174
            }
175 176
            this.Diagnostics.Clear();
            ParameterSymbol methodThisParameter = MethodThisParameter;
A
Andy Gocke 已提交
177
            this.State = TopState();                   // entry point is reachable
178
            this.regionPlace = RegionPlace.Before;
179
            EnterParameters();                               // with parameters assigned
180 181
            if ((object)methodThisParameter != null)
            {
182
                EnterParameter(methodThisParameter, methodThisParameter.Type);
183 184 185 186 187 188
            }

            ImmutableArray<PendingBranch> pendingReturns = base.Scan(ref badRegion);
            return pendingReturns;
        }

189 190 191 192 193 194
        internal static void Analyze(
            CSharpCompilation compilation,
            MethodSymbol method,
            BoundNode node,
            DiagnosticBag diagnostics,
            Action<BoundExpression, TypeSymbolWithAnnotations> callbackOpt = null)
195
        {
196
            if (method.IsImplicitlyDeclared && (!method.IsImplicitConstructor || method.ContainingType.IsImplicitlyDeclared))
197 198 199
            {
                return;
            }
200 201
            Analyze(compilation, method, node, diagnostics, useMethodSignatureReturnType: false, useMethodSignatureParameterTypes: false, methodSignatureOpt: null, returnTypes: null, initialState: null, callbackOpt);
        }
202

203 204 205 206 207
        internal static void Analyze(
            CSharpCompilation compilation,
            BoundLambda lambda,
            DiagnosticBag diagnostics,
            MethodSymbol delegateInvokeMethod,
208
            ArrayBuilder<(BoundReturnStatement, TypeSymbolWithAnnotations)> returnTypes,
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
            VariableState initialState)
        {
            Analyze(
                compilation,
                lambda.Symbol,
                lambda.Body,
                diagnostics,
                useMethodSignatureReturnType: true,
                useMethodSignatureParameterTypes: !lambda.UnboundLambda.HasExplicitlyTypedParameterList,
                methodSignatureOpt: delegateInvokeMethod,
                returnTypes, initialState,
                callbackOpt: null);
        }

        private static void Analyze(
            CSharpCompilation compilation,
            MethodSymbol method,
            BoundNode node,
            DiagnosticBag diagnostics,
            bool useMethodSignatureReturnType,
            bool useMethodSignatureParameterTypes,
            MethodSymbol methodSignatureOpt,
231
            ArrayBuilder<(BoundReturnStatement, TypeSymbolWithAnnotations)> returnTypes,
232 233 234 235 236
            VariableState initialState,
            Action<BoundExpression, TypeSymbolWithAnnotations> callbackOpt)
        {
            Debug.Assert(diagnostics != null);
            var walker = new NullableWalker(compilation, method, useMethodSignatureReturnType, useMethodSignatureParameterTypes, methodSignatureOpt, node, returnTypes, initialState, callbackOpt);
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
            try
            {
                bool badRegion = false;
                ImmutableArray<PendingBranch> returns = walker.Analyze(ref badRegion);
                diagnostics.AddRange(walker.Diagnostics);
                Debug.Assert(!badRegion);
            }
            catch (BoundTreeVisitor.CancelledByStackGuardException ex) when (diagnostics != null)
            {
                ex.AddAnError(diagnostics);
            }
            finally
            {
                walker.Free();
            }
        }

        protected override void Normalize(ref LocalState state)
        {
256
            int oldNext = state.Capacity;
257
            state.EnsureCapacity(nextVariableSlot);
258 259 260 261 262 263 264
            Populate(ref state, oldNext);
        }

        private void Populate(ref LocalState state, int start)
        {
            int capacity = state.Capacity;
            for (int slot = start; slot < capacity; slot++)
265
            {
266
                (NullableAnnotation value, bool assigned) = GetDefaultState(ref state, slot);
267
                state[slot] = value;
268
                state.SetAssigned(slot, assigned);
269 270 271
            }
        }

272
        private (NullableAnnotation annotation, bool assigned) GetDefaultState(ref LocalState state, int slot)
273 274 275
        {
            if (slot == 0)
            {
276
                return (NullableAnnotation.Unknown, false);
277 278 279 280 281 282 283 284
            }

            var variable = variableBySlot[slot];
            var symbol = variable.Symbol;

            switch (symbol.Kind)
            {
                case SymbolKind.Local:
285
                    return (NullableAnnotation.Unknown, false);
286 287 288
                case SymbolKind.Parameter:
                    {
                        var parameter = (ParameterSymbol)symbol;
289 290
                        if (parameter.RefKind == RefKind.Out)
                        {
291
                            return (NullableAnnotation.Unknown, false);
292 293 294 295 296 297
                        }
                        TypeSymbolWithAnnotations parameterType;
                        if (!_variableTypes.TryGetValue(parameter, out parameterType))
                        {
                            parameterType = parameter.Type;
                        }
298 299

                        return (parameterType.NullableAnnotation, true);
300 301 302
                    }
                case SymbolKind.Field:
                case SymbolKind.Property:
303
                case SymbolKind.Event:
304 305
                    {
                        int containingSlot = variable.ContainingSlot;
306 307
                        if (containingSlot > 0 &&
                            variableBySlot[containingSlot].Symbol.GetTypeOrReturnType().TypeKind == TypeKind.Struct &&
308
                            !state.IsAssigned(containingSlot))
309
                        {
310
                            return (NullableAnnotation.Unknown, false);
311
                        }
312 313

                        return (symbol.GetTypeOrReturnType().NullableAnnotation, true);
314 315 316 317
                    }
                default:
                    throw ExceptionUtilities.UnexpectedValue(symbol.Kind);
            }
318 319 320 321
        }

        protected override bool TryGetReceiverAndMember(BoundExpression expr, out BoundExpression receiver, out Symbol member)
        {
322 323 324 325
            receiver = null;
            member = null;

            switch (expr.Kind)
326
            {
327
                case BoundKind.FieldAccess:
328
                    {
329 330
                        var fieldAccess = (BoundFieldAccess)expr;
                        var fieldSymbol = fieldAccess.FieldSymbol;
331 332
                        member = fieldSymbol;
                        if (fieldSymbol.IsFixedSizeBuffer)
333 334 335
                        {
                            return false;
                        }
336 337 338 339
                        if (fieldSymbol.IsStatic)
                        {
                            return true;
                        }
340 341 342 343 344 345 346
                        receiver = fieldAccess.ReceiverOpt;
                        break;
                    }
                case BoundKind.EventAccess:
                    {
                        var eventAccess = (BoundEventAccess)expr;
                        var eventSymbol = eventAccess.EventSymbol;
347 348
                        // https://github.com/dotnet/roslyn/issues/29901 Use AssociatedField for field-like events?
                        member = eventSymbol;
349 350
                        if (eventSymbol.IsStatic)
                        {
351
                            return true;
352 353 354 355 356 357 358 359
                        }
                        receiver = eventAccess.ReceiverOpt;
                        break;
                    }
                case BoundKind.PropertyAccess:
                    {
                        var propAccess = (BoundPropertyAccess)expr;
                        var propSymbol = propAccess.PropertySymbol;
360 361
                        member = GetBackingFieldIfStructProperty(propSymbol);
                        if (member is null)
362 363 364
                        {
                            return false;
                        }
365 366 367 368
                        if (propSymbol.IsStatic)
                        {
                            return true;
                        }
369 370
                        receiver = propAccess.ReceiverOpt;
                        break;
371 372
                    }
            }
373

374 375
            Debug.Assert(member?.IsStatic != true);

376 377 378 379
            return (object)member != null &&
                (object)receiver != null &&
                receiver.Kind != BoundKind.TypeExpression &&
                (object)receiver.Type != null;
380 381
        }

382
        // https://github.com/dotnet/roslyn/issues/29619 Use backing field for struct property
383 384 385 386
        // for now, to avoid cycles if the struct type contains a property of the struct type.
        // Remove this and populate struct members lazily to match classes.
        private Symbol GetBackingFieldIfStructProperty(Symbol symbol)
        {
387
            if (symbol.Kind == SymbolKind.Property && !symbol.ContainingType.IsNullableType())
388 389 390 391 392
            {
                var property = (PropertySymbol)symbol;
                var containingType = property.ContainingType;
                if (containingType.TypeKind == TypeKind.Struct)
                {
393
                    // https://github.com/dotnet/roslyn/issues/29619 Relying on field name
394 395
                    // will not work for properties declared in other languages.
                    var fieldName = GeneratedNames.MakeBackingFieldName(property.Name);
396
                    return _emptyStructTypeCache.GetStructFields(containingType, includeStatic: symbol.IsStatic).FirstOrDefault(f => f.Name == fieldName);
397 398 399 400 401
                }
            }
            return symbol;
        }

402
        // https://github.com/dotnet/roslyn/issues/29619 Temporary, until we're using
403
        // properties on structs directly.
404
        protected override int GetOrCreateSlot(Symbol symbol, int containingSlot = 0)
405 406 407 408 409 410 411 412 413
        {
            symbol = GetBackingFieldIfStructProperty(symbol);
            if ((object)symbol == null)
            {
                return -1;
            }
            return base.GetOrCreateSlot(symbol, containingSlot);
        }

414 415 416 417
        protected override int MakeSlot(BoundExpression node)
        {
            switch (node.Kind)
            {
418 419 420 421 422 423 424
                case BoundKind.ThisReference:
                case BoundKind.BaseReference:
                    {
                        var method = getTopLevelMethod(_symbol as MethodSymbol);
                        var thisParameter = method?.ThisParameter;
                        return (object)thisParameter != null ? GetOrCreateSlot(thisParameter) : -1;
                    }
425 426 427
                case BoundKind.Conversion:
                    {
                        var conv = (BoundConversion)node;
428
                        switch (conv.Conversion.Kind)
429
                        {
430 431 432 433 434
                            case ConversionKind.ExplicitNullable:
                                {
                                    var operand = conv.Operand;
                                    var operandType = operand.Type;
                                    var convertedType = conv.Type;
435
                                    if (AreNullableAndUnderlyingTypes(operandType, convertedType, out _))
436 437 438 439 440 441 442 443
                                    {
                                        // Explicit conversion of Nullable<T> to T is equivalent to Nullable<T>.Value.
                                        // For instance, in the following, when evaluating `((A)a).B` we need to recognize
                                        // the nullability of `(A)a` (not nullable) and the slot (the slot for `a.Value`).
                                        //   struct A { B? B; }
                                        //   struct B { }
                                        //   if (a?.B != null) _ = ((A)a).B.Value; // no warning
                                        int containingSlot = MakeSlot(operand);
444
                                        return containingSlot < 0 ? -1 : GetNullableOfTValueSlot(operandType, containingSlot, out _);
445
                                    }
446 447 448 449 450 451 452 453 454 455 456 457
                                    else if (AreNullableAndUnderlyingTypes(convertedType, operandType, out _))
                                    {
                                        // Explicit conversion of T to Nullable<T> is equivalent to new Nullable<T>(t).
                                        return getPlaceholderSlot(node);
                                    }
                                }
                                break;
                            case ConversionKind.ImplicitNullable:
                                // Implicit conversion of T to Nullable<T> is equivalent to new Nullable<T>(t).
                                if (AreNullableAndUnderlyingTypes(conv.Type, conv.Operand.Type, out _))
                                {
                                    return getPlaceholderSlot(node);
458 459 460 461 462 463 464 465 466 467
                                }
                                break;
                            case ConversionKind.Identity:
                            case ConversionKind.ImplicitTupleLiteral:
                            case ConversionKind.ImplicitTuple:
                                if (isSupportedConversion(conv.Conversion, conv.Operand))
                                {
                                    return MakeSlot(conv.Operand);
                                }
                                break;
468 469 470
                        }
                    }
                    break;
471
                case BoundKind.DefaultExpression:
472
                case BoundKind.ObjectCreationExpression:
473
                case BoundKind.DynamicObjectCreationExpression:
474
                case BoundKind.AnonymousObjectCreationExpression:
475 476
                case BoundKind.TupleLiteral:
                case BoundKind.ConvertedTupleLiteral:
477
                    return getPlaceholderSlot(node);
478 479 480 481 482 483
                case BoundKind.ConditionalReceiver:
                    {
                        int slot = _lastConditionalAccessSlot;
                        _lastConditionalAccessSlot = -1;
                        return slot;
                    }
484
                default:
485 486 487
                    // If there was a placeholder local for this node, we should
                    // use the placeholder for the slot. See other cases above.
                    Debug.Assert(_placeholderLocalsOpt?.TryGetValue(node, out _) != true);
488
                    return base.MakeSlot(node);
489
            }
490 491

            return -1;
492

493 494 495 496 497 498 499 500 501
            int getPlaceholderSlot(BoundExpression expr)
            {
                if (_placeholderLocalsOpt != null && _placeholderLocalsOpt.TryGetValue(expr, out ObjectCreationPlaceholderLocal placeholder))
                {
                    return GetOrCreateSlot(placeholder);
                }
                return -1;
            }

502 503 504 505 506 507 508 509 510 511 512 513 514
            MethodSymbol getTopLevelMethod(MethodSymbol method)
            {
                while ((object)method != null)
                {
                    var container = method.ContainingSymbol;
                    if (container.Kind == SymbolKind.NamedType)
                    {
                        return method;
                    }
                    method = container as MethodSymbol;
                }
                return null;
            }
515 516 517 518 519 520 521 522 523 524 525 526

            // Returns true if the nullable state from the operand of the conversion
            // can be used as is, and we can create a slot from the conversion.
            bool isSupportedConversion(Conversion conversion, BoundExpression operandOpt)
            {
                // https://github.com/dotnet/roslyn/issues/32599: Allow implicit and explicit
                // conversions where the nullable state of the operand remains valid.
                switch (conversion.Kind)
                {
                    case ConversionKind.Identity:
                    case ConversionKind.DefaultOrNullLiteral:
                    case ConversionKind.ImplicitReference:
527
                        return true;
528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550
                    case ConversionKind.ImplicitTupleLiteral:
                        {
                            var arguments = ((BoundConvertedTupleLiteral)operandOpt).Arguments;
                            var conversions = conversion.UnderlyingConversions;
                            for (int i = 0; i < arguments.Length; i++)
                            {
                                // https://github.com/dotnet/roslyn/issues/32600: Copy nullable
                                // state of tuple elements independently.
                                if (!isSupportedConversion(conversions[i], (arguments[i] as BoundConversion)?.Operand))
                                {
                                    return false;
                                }
                            }
                            return true;
                        }
                    case ConversionKind.ImplicitTuple:
                        // https://github.com/dotnet/roslyn/issues/32600: Copy nullable
                        // state of tuple elements independently.
                        return conversion.UnderlyingConversions.All(c => isSupportedConversion(c, null));
                    default:
                        return false;
                }
            }
551 552
        }

553
        private new void VisitLvalue(BoundExpression node)
554 555 556 557
        {
            switch (node.Kind)
            {
                case BoundKind.Local:
558
                    _resultType = GetDeclaredLocalResult(((BoundLocal)node).LocalSymbol);
559
                    break;
560
                case BoundKind.Parameter:
561
                    _resultType = GetDeclaredParameterResult(((BoundParameter)node).ParameterSymbol);
562
                    break;
563 564 565
                case BoundKind.FieldAccess:
                    {
                        var fieldAccess = (BoundFieldAccess)node;
566
                        VisitMemberAccess(node, fieldAccess.ReceiverOpt, fieldAccess.FieldSymbol, asLvalue: true);
567
                    }
568
                    break;
569 570 571
                case BoundKind.PropertyAccess:
                    {
                        var propertyAccess = (BoundPropertyAccess)node;
572
                        VisitMemberAccess(node, propertyAccess.ReceiverOpt, propertyAccess.PropertySymbol, asLvalue: true);
573 574
                    }
                    break;
575
                case BoundKind.EventAccess:
576
                    {
577
                        var eventAccess = (BoundEventAccess)node;
578
                        VisitMemberAccess(node, eventAccess.ReceiverOpt, eventAccess.EventSymbol, asLvalue: true);
579 580
                    }
                    break;
581 582
                case BoundKind.ObjectInitializerMember:
                    throw ExceptionUtilities.UnexpectedValue(node.Kind); // Should have been handled in VisitObjectCreationExpression().
583
                default:
584
                    VisitRvalue(node);
585 586
                    break;
            }
587 588 589

            if (_callbackOpt != null)
            {
590
                _callbackOpt(node, _resultType);
591
            }
592 593
        }

594
        private TypeSymbolWithAnnotations VisitRvalueWithResult(BoundExpression node)
595 596
        {
            base.VisitRvalue(node);
597
            return _resultType;
598 599 600 601 602 603 604
        }

        private static object GetTypeAsDiagnosticArgument(TypeSymbol typeOpt)
        {
            return typeOpt ?? (object)"<null>";
        }

605 606 607 608 609 610 611
        private enum AssignmentKind
        {
            Assignment,
            Return,
            Argument
        }

612 613 614
        /// <summary>
        /// Reports top-level nullability problem in assignment.
        /// </summary>
615
        private bool ReportNullableAssignmentIfNecessary(BoundExpression value, TypeSymbolWithAnnotations targetType, TypeSymbolWithAnnotations valueType, bool useLegacyWarnings, AssignmentKind assignmentKind = AssignmentKind.Assignment, Symbol target = null)
616
        {
617 618
            Debug.Assert((object)target != null || assignmentKind != AssignmentKind.Argument);

619 620 621 622 623 624 625
            if (value == null)
            {
                return false;
            }

            if (targetType.IsNull ||
                targetType.IsValueType ||
626
                !targetType.NullableAnnotation.IsAnyNotNullable() ||
627
                valueType.IsNull ||
628
                !valueType.NullableAnnotation.IsAnyNullable())
629 630 631 632 633
            {
                return false;
            }

            var unwrappedValue = SkipReferenceConversions(value);
634
            if (unwrappedValue.IsSuppressed)
635 636 637
            {
                return false;
            }
638
            if (reportNullLiteralAssignmentIfNecessary(value))
639 640 641
            {
                return true;
            }
642

643
            if (valueType.IsNull)
644 645 646 647
            {
                return false;
            }

648 649
            if (assignmentKind == AssignmentKind.Argument)
            {
650
                ReportSafetyDiagnostic(ErrorCode.WRN_NullReferenceArgument, value.Syntax,
651 652 653 654 655
                    new FormattedSymbol(target, SymbolDisplayFormat.ShortFormat),
                    new FormattedSymbol(target.ContainingSymbol, SymbolDisplayFormat.MinimallyQualifiedFormat));
            }
            else if (useLegacyWarnings)
            {
656
                ReportNonSafetyDiagnostic(value.Syntax);
657 658 659
            }
            else
            {
660
                ReportSafetyDiagnostic(assignmentKind == AssignmentKind.Return ? ErrorCode.WRN_NullReferenceReturn : ErrorCode.WRN_NullReferenceAssignment, value.Syntax);
661 662 663 664 665 666
            }

            return true;

            // Report warning converting null literal to non-nullable reference type.
            // target (e.g.: `object x = null;` or calling `void F(object y)` with `F(null)`).
667
            bool reportNullLiteralAssignmentIfNecessary(BoundExpression expr)
668
            {
669
                if (expr.ConstantValue?.IsNull != true && !IsDefaultOfUnconstrainedTypeParameter(expr))
670 671 672 673
                {
                    return false;
                }

674 675
                if (useLegacyWarnings)
                {
676
                    ReportNonSafetyDiagnostic(expr.Syntax);
677
                }
678
                else
679
                {
680
                    ReportSafetyDiagnostic(assignmentKind == AssignmentKind.Return ? ErrorCode.WRN_NullReferenceReturn : ErrorCode.WRN_NullAsNonNullable, expr.Syntax);
681 682 683
                }
                return true;
            }
684
        }
685

686 687 688
        private static bool IsDefaultOfUnconstrainedTypeParameter(BoundExpression expr)
        {
            switch (expr.Kind)
689
            {
690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716
                case BoundKind.Conversion:
                    {
                        var conversion = (BoundConversion)expr;
                        return conversion.Conversion.Kind == ConversionKind.DefaultOrNullLiteral &&
                            IsDefaultOfUnconstrainedTypeParameter(conversion.Operand);
                    }
                case BoundKind.DefaultExpression:
                    return IsTypeParameterDisallowingAnnotation(expr.Type);
                default:
                    return false;
            }
        }

        private static bool IsDefaultValue(BoundExpression expr)
        {
            switch (expr.Kind)
            {
                case BoundKind.Conversion:
                    {
                        var conversion = (BoundConversion)expr;
                        return conversion.Conversion.Kind == ConversionKind.DefaultOrNullLiteral &&
                            IsDefaultValue(conversion.Operand);
                    }
                case BoundKind.DefaultExpression:
                    return true;
                default:
                    return false;
717
            }
718 719
        }

720
        // Maybe this method can be replaced by VisitOptionalImplicitConversion or ApplyConversion
C
Charles Stoner 已提交
721 722 723 724 725 726
        private void ReportAssignmentWarnings(BoundExpression value, TypeSymbolWithAnnotations targetType, TypeSymbolWithAnnotations valueType, bool useLegacyWarnings)
        {
            Debug.Assert(value != null);

            if (this.State.Reachable)
            {
727
                if (targetType.IsNull || valueType.IsNull)
C
Charles Stoner 已提交
728 729 730 731
                {
                    return;
                }

732 733 734 735 736 737 738 739
                // Report top-level nullability issues
                ReportNullableAssignmentIfNecessary(value, targetType, valueType, useLegacyWarnings, assignmentKind: AssignmentKind.Assignment);

                // Report nested nullability issues
                var sourceType = valueType.TypeSymbol;
                var destinationType = targetType.TypeSymbol;
                if ((object)sourceType != null && IsNullabilityMismatch(destinationType, sourceType))
                {
740
                    ReportNullabilityMismatchInAssignment(value.Syntax, sourceType, destinationType);
741
                }
C
Charles Stoner 已提交
742 743 744
            }
        }

745 746 747 748 749
        private void ReportNullabilityMismatchInAssignment(SyntaxNode syntaxNode, object sourceType, object destinationType)
        {
            ReportSafetyDiagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, syntaxNode, sourceType, destinationType);
        }

750
        /// <summary>
C
Charles Stoner 已提交
751
        /// Update tracked value on assignment.
752
        /// </summary>
C
Charles Stoner 已提交
753
        private void TrackNullableStateForAssignment(BoundExpression value, TypeSymbolWithAnnotations targetType, int targetSlot, TypeSymbolWithAnnotations valueType, int valueSlot = -1)
754
        {
C
Charles Stoner 已提交
755
            Debug.Assert(value != null);
756
            Debug.Assert(!IsConditionalState);
C
Charles Stoner 已提交
757

758 759
            if (this.State.Reachable)
            {
760
                if (targetType.IsNull)
761 762 763
                {
                    return;
                }
764

765
                if (targetSlot <= 0 || targetSlot == valueSlot)
766
                {
C
Charles Stoner 已提交
767 768
                    return;
                }
769

C
Charles Stoner 已提交
770
                if (targetSlot >= this.State.Capacity) Normalize(ref this.State);
771

772
                var newState = valueType.NullableAnnotation;
773 774 775 776
                this.State[targetSlot] = newState;
                if (newState.IsAnyNullable() && _tryState.HasValue)
                {
                    var state = _tryState.Value;
777
                    state[targetSlot] = NullableAnnotation.Nullable;
778 779
                    _tryState = state;
                }
C
Charles Stoner 已提交
780 781

                InheritDefaultState(targetSlot);
782

783 784 785 786 787
                // https://github.com/dotnet/roslyn/issues/33428: Can the areEquivalentTypes check be removed
                // if InheritNullableStateOfMember asserts the member is valid for target and value?
                if (areEquivalentTypes(targetType, valueType)) // https://github.com/dotnet/roslyn/issues/29968 Allow assignment to base type.
                {
                    if (targetType.IsReferenceType || targetType.IsNullableType())
788
                    {
789 790 791
                        // Nullable<T> is handled here rather than in InheritNullableStateOfTrackableStruct since that
                        // method only clones auto-properties (see https://github.com/dotnet/roslyn/issues/29619).
                        // When that issue is fixed, Nullable<T> should be handled there instead.
C
Charles Stoner 已提交
792
                        if (valueSlot > 0)
793
                        {
C
Charles Stoner 已提交
794
                            InheritNullableStateOfTrackableType(targetSlot, valueSlot, skipSlot: targetSlot);
795
                        }
796
                    }
797
                    else if (EmptyStructTypeCache.IsTrackableStructType(targetType.TypeSymbol))
798
                    {
C
Charles Stoner 已提交
799
                        InheritNullableStateOfTrackableStruct(targetType.TypeSymbol, targetSlot, valueSlot, isDefaultValue: IsDefaultValue(value), skipSlot: targetSlot);
800 801
                    }
                }
802
            }
803 804 805

            bool areEquivalentTypes(TypeSymbolWithAnnotations t1, TypeSymbolWithAnnotations t2) =>
                t1.TypeSymbol.Equals(t2.TypeSymbol, TypeCompareKind.AllIgnoreOptions);
806 807
        }

808
        private void ReportNonSafetyDiagnostic(SyntaxNode syntax)
809
        {
810
            ReportNonSafetyDiagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, syntax);
811 812
        }

813 814 815 816 817 818 819 820 821 822 823
        private void ReportNonSafetyDiagnostic(ErrorCode errorCode, SyntaxNode syntax)
        {
            // All warnings should be in the `#pragma warning ... nullable` set.
            Debug.Assert(!ErrorFacts.NullableFlowAnalysisSafetyWarnings.Contains(MessageProvider.Instance.GetIdForErrorCode((int)errorCode)));
            Debug.Assert(ErrorFacts.NullableFlowAnalysisNonSafetyWarnings.Contains(MessageProvider.Instance.GetIdForErrorCode((int)errorCode)));
#pragma warning disable CS0618
            ReportDiagnostic(errorCode, syntax);
#pragma warning restore CS0618
        }

        private void ReportSafetyDiagnostic(ErrorCode errorCode, SyntaxNode syntaxNode, params object[] arguments)
824
        {
825
            // All warnings should be in the `#pragma warning ... nullable` set.
826 827 828 829 830 831
            Debug.Assert(ErrorFacts.NullableFlowAnalysisSafetyWarnings.Contains(MessageProvider.Instance.GetIdForErrorCode((int)errorCode)));
            Debug.Assert(!ErrorFacts.NullableFlowAnalysisNonSafetyWarnings.Contains(MessageProvider.Instance.GetIdForErrorCode((int)errorCode)));
#pragma warning disable CS0618
            ReportDiagnostic(errorCode, syntaxNode, arguments);
#pragma warning restore CS0618
        }
832

833 834 835
        [Obsolete("Use ReportSafetyDiagnostic/ReportNonSafetyDiagnostic instead", error: false)]
        private void ReportDiagnostic(ErrorCode errorCode, SyntaxNode syntaxNode, params object[] arguments)
        {
836 837 838 839
            if (!_disableDiagnostics)
            {
                Diagnostics.Add(errorCode, syntaxNode.GetLocation(), arguments);
            }
840 841
        }

C
Charles Stoner 已提交
842
        private void InheritNullableStateOfTrackableStruct(TypeSymbol targetType, int targetSlot, int valueSlot, bool isDefaultValue, int skipSlot = -1)
843 844 845 846
        {
            Debug.Assert(targetSlot > 0);
            Debug.Assert(EmptyStructTypeCache.IsTrackableStructType(targetType));

C
Charles Stoner 已提交
847 848 849 850 851
            if (skipSlot < 0)
            {
                skipSlot = targetSlot;
            }

852
            // https://github.com/dotnet/roslyn/issues/29619 Handle properties not backed by fields.
853
            // See ModifyMembers_StructPropertyNoBackingField and PropertyCycle_Struct tests.
854 855
            foreach (var field in _emptyStructTypeCache.GetStructInstanceFields(targetType))
            {
C
Charles Stoner 已提交
856
                InheritNullableStateOfMember(targetSlot, valueSlot, field, isDefaultValue: isDefaultValue, skipSlot);
857 858 859
            }
        }

C
Charles Stoner 已提交
860 861
        // 'skipSlot' is the original target slot that should be skipped in case of cycles.
        private void InheritNullableStateOfMember(int targetContainerSlot, int valueContainerSlot, Symbol member, bool isDefaultValue, int skipSlot)
862
        {
863
            Debug.Assert(targetContainerSlot > 0);
C
Charles Stoner 已提交
864
            Debug.Assert(skipSlot > 0);
865
            // https://github.com/dotnet/roslyn/issues/33428: Ensure member is valid for target and value.
866

C
Misc.  
Charles Stoner 已提交
867
            TypeSymbolWithAnnotations fieldOrPropertyType = member.GetTypeOrReturnType();
868

869 870 871
            // Nullable<T> is handled here rather than in InheritNullableStateOfTrackableStruct since that
            // method only clones auto-properties (see https://github.com/dotnet/roslyn/issues/29619).
            // When that issue is fixed, Nullable<T> should be handled there instead.
872
            if (fieldOrPropertyType.IsReferenceType || fieldOrPropertyType.IsPossiblyNullableReferenceTypeTypeParameter() || fieldOrPropertyType.IsNullableType())
873
            {
874
                int targetMemberSlot = GetOrCreateSlot(member, targetContainerSlot);
C
Charles Stoner 已提交
875 876
                Debug.Assert(targetMemberSlot > 0);

877 878 879
                NullableAnnotation value = (isDefaultValue && fieldOrPropertyType.IsReferenceType) ?
                    NullableAnnotation.Nullable :
                    fieldOrPropertyType.NullableAnnotation;
C
Charles Stoner 已提交
880 881
                int valueMemberSlot = -1;

882
                if (valueContainerSlot > 0)
883
                {
C
Charles Stoner 已提交
884 885 886 887 888
                    valueMemberSlot = VariableSlot(member, valueContainerSlot);
                    if (valueMemberSlot == skipSlot)
                    {
                        return;
                    }
889 890 891 892 893 894
                    value = valueMemberSlot > 0 && valueMemberSlot < this.State.Capacity ?
                        this.State[valueMemberSlot] :
                        NullableAnnotation.Unknown;
                }

                this.State[targetMemberSlot] = value;
895

C
Charles Stoner 已提交
896
                if (valueMemberSlot > 0)
897
                {
C
Charles Stoner 已提交
898
                    InheritNullableStateOfTrackableType(targetMemberSlot, valueMemberSlot, skipSlot);
899 900 901 902
                }
            }
            else if (EmptyStructTypeCache.IsTrackableStructType(fieldOrPropertyType.TypeSymbol))
            {
C
Misc.  
Charles Stoner 已提交
903
                int targetMemberSlot = GetOrCreateSlot(member, targetContainerSlot);
904
                if (targetMemberSlot > 0)
905
                {
C
Charles Stoner 已提交
906 907
                    int valueMemberSlot = (valueContainerSlot > 0) ? GetOrCreateSlot(member, valueContainerSlot) : -1;
                    if (valueMemberSlot == skipSlot)
908
                    {
C
Charles Stoner 已提交
909
                        return;
910
                    }
C
Charles Stoner 已提交
911
                    InheritNullableStateOfTrackableStruct(fieldOrPropertyType.TypeSymbol, targetMemberSlot, valueMemberSlot, isDefaultValue: isDefaultValue, skipSlot);
912 913 914 915
                }
            }
        }

916
        private void InheritDefaultState(int targetSlot)
917 918 919
        {
            Debug.Assert(targetSlot > 0);

920 921
            // Reset the state of any members of the target.
            for (int slot = targetSlot + 1; slot < nextVariableSlot; slot++)
922
            {
923 924
                var variable = variableBySlot[slot];
                if (variable.ContainingSlot != targetSlot)
925 926 927
                {
                    continue;
                }
928
                this.State[slot] = variable.Symbol.GetTypeOrReturnType().NullableAnnotation;
929 930 931
                InheritDefaultState(slot);
            }
        }
932

C
Charles Stoner 已提交
933
        private void InheritNullableStateOfTrackableType(int targetSlot, int valueSlot, int skipSlot)
934 935 936
        {
            Debug.Assert(targetSlot > 0);
            Debug.Assert(valueSlot > 0);
937

938
            // Clone the state for members that have been set on the value.
C
Charles Stoner 已提交
939
            for (int slot = valueSlot + 1; slot < nextVariableSlot; slot++)
940 941 942
            {
                var variable = variableBySlot[slot];
                if (variable.ContainingSlot != valueSlot)
943 944 945
                {
                    continue;
                }
946
                var member = variable.Symbol;
C
Misc.  
Charles Stoner 已提交
947
                Debug.Assert(member.Kind == SymbolKind.Field || member.Kind == SymbolKind.Property || member.Kind == SymbolKind.Event);
C
Charles Stoner 已提交
948
                InheritNullableStateOfMember(targetSlot, valueSlot, member, isDefaultValue: false, skipSlot);
949 950 951
            }
        }

952 953 954 955 956
        private TypeSymbol GetSlotType(int slot)
        {
            return VariableType(variableBySlot[slot].Symbol).TypeSymbol;
        }

A
Andy Gocke 已提交
957
        protected override LocalState TopState()
958
        {
959
            var state = new LocalState(reachable: true, BitVector.Create(nextVariableSlot), new ArrayBuilder<NullableAnnotation>(nextVariableSlot));
960 961
            Populate(ref state, start: 0);
            return state;
962 963 964 965
        }

        protected override LocalState UnreachableState()
        {
966
            return new LocalState(reachable: false, BitVector.Empty, null);
967 968
        }

A
Andy Gocke 已提交
969
        protected override LocalState ReachableBottomState()
970
        {
971 972
            // Create a reachable state in which all variables are known to be non-null.
            var builder = new ArrayBuilder<NullableAnnotation>(nextVariableSlot);
973
            builder.AddMany(NullableAnnotation.NotNullable, nextVariableSlot);
974
            return new LocalState(reachable: true, BitVector.AllSet(nextVariableSlot), builder);
975 976
        }

977
        private void EnterParameters()
978
        {
979
            var methodParameters = ((MethodSymbol)_symbol).Parameters;
980 981 982 983
            var signatureParameters = _useMethodSignatureParameterTypes ? _methodSignatureOpt.Parameters : methodParameters;
            Debug.Assert(signatureParameters.Length == methodParameters.Length);
            int n = methodParameters.Length;
            for (int i = 0; i < n; i++)
984
            {
985
                var parameter = methodParameters[i];
986
                var parameterType = signatureParameters[i].Type;
987
                EnterParameter(parameter, parameterType);
988 989 990
            }
        }

991
        private void EnterParameter(ParameterSymbol parameter, TypeSymbolWithAnnotations parameterType)
992
        {
F
Fredric Silberberg 已提交
993
            _variableTypes[parameter] = parameterType;
994
            int slot = GetOrCreateSlot(parameter);
F
Fredric Silberberg 已提交
995

996 997
            Debug.Assert(!IsConditionalState);
            if (slot > 0 && parameter.RefKind != RefKind.Out)
998
            {
999
                if (EmptyStructTypeCache.IsTrackableStructType(parameterType.TypeSymbol))
1000
                {
1001 1002 1003 1004
                    InheritNullableStateOfTrackableStruct(
                        parameterType.TypeSymbol,
                        slot,
                        valueSlot: -1,
C
Charles Stoner 已提交
1005
                        isDefaultValue: parameter.ExplicitDefaultConstantValue?.IsNull == true);
1006 1007 1008 1009 1010 1011
                }
            }
        }

        public override BoundNode VisitIsPatternExpression(BoundIsPatternExpression node)
        {
1012 1013
            VisitRvalue(node.Expression);
            var expressionResultType = this._resultType;
1014

1015
            VisitPattern(node.Expression, expressionResultType, node.Pattern);
1016

1017
            SetResult(node);
1018
            return node;
1019 1020
        }

1021 1022 1023
        /// <summary>
        /// Examples:
        /// `x is Point p`
1024
        /// `switch (x) ... case Point p:` // https://github.com/dotnet/roslyn/issues/29873 not yet handled
1025 1026 1027 1028 1029 1030
        ///
        /// If the expression is trackable, we'll return with different null-states for that expression in the two conditional states.
        /// If the pattern is a `var` pattern, we'll also have re-inferred the `var` type with nullability and
        /// updated the state for that declared local.
        /// </summary>
        private void VisitPattern(BoundExpression expression, TypeSymbolWithAnnotations expressionResultType, BoundPattern pattern)
1031
        {
1032 1033
            NullableAnnotation whenTrue = NullableAnnotation.Unknown; // the pattern tells us the expression (1) is null, (2) isn't null, or (3) we don't know.
            NullableAnnotation whenFalse = NullableAnnotation.Unknown;
1034 1035 1036
            switch (pattern.Kind)
            {
                case BoundKind.ConstantPattern:
1037 1038 1039
                    // If the constant is null, the pattern tells us the expression is null.
                    // If the constant is not null, the pattern tells us the expression is not null.
                    // If there is no constant, we don't know.
1040 1041 1042
                    switch (((BoundConstantPattern)pattern).ConstantValue?.IsNull)
                    {
                        case true:
1043 1044
                            whenTrue = NullableAnnotation.Nullable;
                            whenFalse = NullableAnnotation.NotNullable;
1045 1046
                            break;
                        case false:
1047
                            whenTrue = NullableAnnotation.NotNullable;
1048 1049 1050
                            whenFalse = expressionResultType.NullableAnnotation;
                            break;
                    }
1051 1052 1053 1054
                    break;
                case BoundKind.DeclarationPattern:
                    var declarationPattern = (BoundDeclarationPattern)pattern;
                    if (declarationPattern.IsVar)
1055
                    {
1056 1057 1058 1059 1060 1061 1062 1063
                        // The result type and state of the expression carry into the variable declared by var pattern
                        Symbol variable = declarationPattern.Variable;
                        // No variable declared for discard (`i is var _`)
                        if ((object)variable != null)
                        {
                            _variableTypes[variable] = expressionResultType;
                            TrackNullableStateForAssignment(expression, expressionResultType, GetOrCreateSlot(variable), expressionResultType);
                        }
N
Neal Gafter 已提交
1064 1065

                        whenFalse = NullableAnnotation.NotNullable; // whenFalse is unreachable
1066 1067 1068
                    }
                    else
                    {
1069
                        whenTrue = NullableAnnotation.NotNullable; // the pattern tells us the expression is not null
1070
                        whenFalse = expressionResultType.NullableAnnotation;
1071 1072 1073
                    }
                    break;
            }
1074 1075

            Debug.Assert(!IsConditionalState);
1076 1077

            int mainSlot = -1;
1078
            if (whenTrue != NullableAnnotation.Unknown)
1079 1080 1081
            {
                // Create slot when the state is unconditional since EnsureCapacity should be
                // called on all fields and that is simpler if state is limited to this.State.
1082
                mainSlot = MakeSlot(expression);
1083 1084
            }

1085
            base.VisitPattern(pattern);
1086 1087
            Debug.Assert(IsConditionalState);

1088
            // https://github.com/dotnet/roslyn/issues/29873 We should only report such
1089
            // diagnostics for locals that are set or checked explicitly within this method.
1090
            if (!expressionResultType.IsPointerType() && !expressionResultType.IsNull && expressionResultType.ValueCanBeNull() == false && whenTrue == NullableAnnotation.Nullable)
1091
            {
1092
                ReportNonSafetyDiagnostic(ErrorCode.HDN_NullCheckIsProbablyAlwaysFalse, pattern.Syntax);
1093 1094
            }

1095
            if (mainSlot > 0)
1096
            {
1097
                Debug.Assert(whenTrue != NullableAnnotation.Unknown);
1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117
                this.StateWhenTrue[mainSlot] = whenTrue;
                this.StateWhenFalse[mainSlot] = whenFalse;
            }

            if (whenTrue == NullableAnnotation.NotNullable || whenFalse == NullableAnnotation.NotNullable)
            {
                var slotBuilder = ArrayBuilder<int>.GetInstance();
                GetSlotsToMarkAsNotNullable(expression, slotBuilder);

                // Set all nested conditional slots. For example in a?.b?.c we'll set a, b, and c.
                if (whenTrue == NullableAnnotation.NotNullable)
                {
                    MarkSlotsAsNotNullable(slotBuilder, ref StateWhenTrue);
                }
                else if (whenFalse == NullableAnnotation.NotNullable)
                {
                    MarkSlotsAsNotNullable(slotBuilder, ref StateWhenFalse);
                }

                slotBuilder.Free();
1118
            }
1119 1120 1121 1122 1123
        }

        protected override BoundNode VisitReturnStatementNoAdjust(BoundReturnStatement node)
        {
            Debug.Assert(!IsConditionalState);
1124 1125 1126 1127 1128 1129 1130

            BoundExpression expr = node.ExpressionOpt;
            if (expr == null)
            {
                return null;
            }

1131
            if (_returnTypesOpt != null)
1132
            {
1133
                // Inferring return type. Should not convert to method return type.
1134
                TypeSymbolWithAnnotations result = VisitRvalueWithResult(expr);
1135
                _returnTypesOpt.Add((node, result));
1136 1137
                return null;
            }
1138

1139 1140
            TypeSymbolWithAnnotations returnType = GetReturnType();
            VisitOptionalImplicitConversion(expr, returnType, useLegacyWarnings: false, AssignmentKind.Return);
1141 1142 1143
            return null;
        }

1144
        private TypeSymbolWithAnnotations GetReturnType()
1145
        {
1146
            var method = (MethodSymbol)_symbol;
1147 1148
            var returnType = (_useMethodSignatureReturnType ? _methodSignatureOpt : method).ReturnType;
            Debug.Assert((object)returnType != LambdaSymbol.ReturnTypeIsBeingInferred);
1149 1150 1151 1152 1153 1154
            if (method.IsGenericTaskReturningAsync(compilation))
            {
                returnType = ((NamedTypeSymbol)returnType.TypeSymbol).TypeArgumentsNoUseSiteDiagnostics.Single();
            }

            return returnType;
1155 1156
        }

1157
        private static bool IsTypeParameterDisallowingAnnotation(TypeSymbol typeOpt)
1158
        {
1159
            return typeOpt?.IsTypeParameterDisallowingAnnotation() == true;
1160 1161
        }

1162 1163
        public override BoundNode VisitLocal(BoundLocal node)
        {
1164 1165 1166 1167
            var local = node.LocalSymbol;
            int slot = GetOrCreateSlot(local);
            var type = GetDeclaredLocalResult(local);
            _resultType = GetAdjustedResult(type, slot);
1168 1169 1170 1171 1172
            return null;
        }

        public override BoundNode VisitLocalDeclaration(BoundLocalDeclaration node)
        {
1173 1174 1175 1176
            var local = node.LocalSymbol;
            int slot = GetOrCreateSlot(local);

            var initializer = node.InitializerOpt;
1177
            if (initializer is null)
1178
            {
1179 1180
                return null;
            }
1181

1182
            bool inferredType = node.DeclaredType.InferredType;
1183
            TypeSymbolWithAnnotations type = local.Type;
1184
            TypeSymbolWithAnnotations valueType = VisitOptionalImplicitConversion(initializer, targetTypeOpt: inferredType ? default : type, useLegacyWarnings: true, AssignmentKind.Assignment);
1185

1186
            if (inferredType)
1187
            {
1188
                if (valueType.IsNull)
1189
                {
1190 1191 1192 1193 1194 1195
                    Debug.Assert(type.IsErrorType());
                    valueType = type;
                }
                _variableTypes[local] = valueType;
                type = valueType;
            }
1196

1197
            TrackNullableStateForAssignment(initializer, type, slot, valueType, MakeSlot(initializer));
1198
            return null;
1199 1200 1201 1202 1203
        }

        protected override BoundExpression VisitExpressionWithoutStackGuard(BoundExpression node)
        {
            Debug.Assert(!IsConditionalState);
1204
            _resultType = _invalidType;
1205
            var result = base.VisitExpressionWithoutStackGuard(node);
1206

1207
#if DEBUG
1208
            // Verify Visit method set _result.
1209
            TypeSymbolWithAnnotations resultType = _resultType;
1210 1211
            Debug.Assert((object)resultType.TypeSymbol != _invalidType.TypeSymbol);
            Debug.Assert(AreCloseEnough(resultType.TypeSymbol, node.Type));
1212
#endif
1213 1214
            if (_callbackOpt != null)
            {
1215
                _callbackOpt(node, _resultType);
1216
            }
1217 1218 1219 1220 1221

            if (node.IsSuppressed && !_resultType.IsNull)
            {
                _resultType = _resultType.WithTopLevelNonNullability();
            }
1222
            return result;
1223 1224
        }

1225 1226 1227 1228
#if DEBUG
        // For asserts only.
        private static bool AreCloseEnough(TypeSymbol typeA, TypeSymbol typeB)
        {
1229 1230 1231 1232 1233 1234 1235 1236
            if ((object)typeA == typeB)
            {
                return true;
            }
            if (typeA is null || typeB is null)
            {
                return false;
            }
1237 1238 1239
            bool canIgnoreType(TypeSymbol type) => (object)type.VisitType((t, unused1, unused2) => t.IsErrorType() || t.IsDynamic() || t.HasUseSiteError, (object)null) != null;
            return canIgnoreType(typeA) ||
                canIgnoreType(typeB) ||
1240
                typeA.Equals(typeB, TypeCompareKind.IgnoreCustomModifiersAndArraySizesAndLowerBounds | TypeCompareKind.IgnoreNullableModifiersForReferenceTypes | TypeCompareKind.IgnoreDynamicAndTupleNames); // Ignore TupleElementNames (see https://github.com/dotnet/roslyn/issues/23651).
1241 1242 1243 1244
        }
#endif

        protected override void VisitStatement(BoundStatement statement)
1245
        {
1246
            _resultType = _invalidType;
1247
            base.VisitStatement(statement);
1248
            _resultType = _invalidType;
1249
        }
1250

1251 1252
        public override BoundNode VisitObjectCreationExpression(BoundObjectCreationExpression node)
        {
1253
            Debug.Assert(!IsConditionalState);
1254 1255 1256
            var arguments = node.Arguments;
            var argumentTypes = VisitArguments(node, arguments, node.ArgumentRefKindsOpt, node.Constructor, node.ArgsToParamsOpt, node.Expanded);
            VisitObjectOrDynamicObjectCreation(node, arguments, argumentTypes, node.InitializerExpressionOpt);
1257 1258 1259
            return null;
        }

1260 1261 1262 1263 1264
        private void VisitObjectOrDynamicObjectCreation(
            BoundExpression node,
            ImmutableArray<BoundExpression> arguments,
            ImmutableArray<TypeSymbolWithAnnotations> argumentTypes,
            BoundExpression initializerOpt)
1265 1266 1267 1268 1269 1270
        {
            Debug.Assert(node.Kind == BoundKind.ObjectCreationExpression || node.Kind == BoundKind.DynamicObjectCreationExpression);

            int slot = -1;
            TypeSymbol type = node.Type;
            if ((object)type != null)
1271
            {
1272
                bool isTrackableStructType = EmptyStructTypeCache.IsTrackableStructType(type);
F
Fredric Silberberg 已提交
1273
                if (!type.IsValueType || isTrackableStructType)
1274
                {
1275
                    slot = GetOrCreateObjectCreationPlaceholderSlot(node);
1276
                    if (slot > 0 && isTrackableStructType)
1277
                    {
1278
                        this.State[slot] = NullableAnnotation.NotNullable;
1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292
                        var constructor = (node as BoundObjectCreationExpression)?.Constructor;
                        bool isDefaultValueTypeConstructor = constructor?.IsDefaultValueTypeConstructor() == true;
                        var tupleType = constructor?.ContainingType as TupleTypeSymbol;
                        if ((object)tupleType != null && !isDefaultValueTypeConstructor)
                        {
                            // new System.ValueTuple<T1, ..., TN>(e1, ..., eN)
                            TrackNullableStateOfTupleElements(slot, tupleType, arguments, argumentTypes, useRestField: true);
                        }
                        else
                        {
                            InheritNullableStateOfTrackableStruct(
                                type,
                                slot,
                                valueSlot: -1,
C
Charles Stoner 已提交
1293
                                isDefaultValue: isDefaultValueTypeConstructor);
1294
                        }
1295 1296 1297 1298 1299 1300
                    }
                }
            }

            if (initializerOpt != null)
            {
1301
                VisitObjectCreationInitializer(null, slot, initializerOpt);
1302 1303
            }

1304
            _resultType = TypeSymbolWithAnnotations.Create(type, NullableAnnotation.NotNullable);
1305 1306 1307 1308 1309 1310 1311 1312 1313 1314
        }

        private void VisitObjectCreationInitializer(Symbol containingSymbol, int containingSlot, BoundExpression node)
        {
            switch (node.Kind)
            {
                case BoundKind.ObjectInitializerExpression:
                    foreach (var initializer in ((BoundObjectInitializerExpression)node).Initializers)
                    {
                        switch (initializer.Kind)
1315
                        {
1316
                            case BoundKind.AssignmentOperator:
1317
                                VisitObjectElementInitializer(containingSlot, (BoundAssignmentOperator)initializer);
1318 1319 1320 1321
                                break;
                            default:
                                VisitRvalue(initializer);
                                break;
1322 1323
                        }
                    }
1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339
                    break;
                case BoundKind.CollectionInitializerExpression:
                    foreach (var initializer in ((BoundCollectionInitializerExpression)node).Initializers)
                    {
                        switch (initializer.Kind)
                        {
                            case BoundKind.CollectionElementInitializer:
                                VisitCollectionElementInitializer((BoundCollectionElementInitializer)initializer);
                                break;
                            default:
                                VisitRvalue(initializer);
                                break;
                        }
                    }
                    break;
                default:
1340
                    TypeSymbolWithAnnotations resultType = VisitRvalueWithResult(node);
1341
                    Debug.Assert((object)containingSymbol != null);
1342 1343
                    if ((object)containingSymbol != null)
                    {
1344
                        var type = containingSymbol.GetTypeOrReturnType();
1345 1346
                        ReportAssignmentWarnings(node, type, resultType, useLegacyWarnings: false);
                        TrackNullableStateForAssignment(node, type, containingSlot, resultType, MakeSlot(node));
1347
                    }
1348
                    break;
1349
            }
1350
        }
1351

1352
        private void VisitObjectElementInitializer(int containingSlot, BoundAssignmentOperator node)
1353 1354 1355 1356 1357 1358 1359 1360 1361 1362
        {
            var left = node.Left;
            switch (left.Kind)
            {
                case BoundKind.ObjectInitializerMember:
                    {
                        var objectInitializer = (BoundObjectInitializerMember)left;
                        var symbol = objectInitializer.MemberSymbol;
                        if (!objectInitializer.Arguments.IsDefaultOrEmpty)
                        {
1363
                            VisitArguments(objectInitializer, objectInitializer.Arguments, objectInitializer.ArgumentRefKindsOpt, (PropertySymbol)symbol, objectInitializer.ArgsToParamsOpt, objectInitializer.Expanded);
1364
                        }
1365 1366 1367 1368 1369
                        if ((object)symbol != null)
                        {
                            int slot = (containingSlot < 0) ? -1 : GetOrCreateSlot(symbol, containingSlot);
                            VisitObjectCreationInitializer(symbol, slot, node.Right);
                        }
1370 1371 1372 1373 1374 1375 1376
                    }
                    break;
                default:
                    VisitLvalue(node);
                    break;
            }
        }
1377

1378 1379
        private new void VisitCollectionElementInitializer(BoundCollectionElementInitializer node)
        {
1380 1381
            // Note: we analyze even omitted calls
            VisitArguments(node, node.Arguments, refKindsOpt: default, node.AddMethod, node.ArgsToParamsOpt, node.Expanded);
1382
            SetUnknownResultNullability();
1383 1384
        }

1385
        private void SetResult(BoundExpression node)
1386
        {
1387
            _resultType = TypeSymbolWithAnnotations.Create(node.Type);
1388 1389
        }

1390
        private int GetOrCreateObjectCreationPlaceholderSlot(BoundExpression node)
1391 1392
        {
            ObjectCreationPlaceholderLocal placeholder;
1393
            if (_placeholderLocalsOpt == null)
1394
            {
1395
                _placeholderLocalsOpt = PooledDictionary<BoundExpression, ObjectCreationPlaceholderLocal>.GetInstance();
1396 1397 1398 1399
                placeholder = null;
            }
            else
            {
1400
                _placeholderLocalsOpt.TryGetValue(node, out placeholder);
1401 1402 1403 1404
            }

            if ((object)placeholder == null)
            {
1405
                placeholder = new ObjectCreationPlaceholderLocal(_symbol, node);
1406
                _placeholderLocalsOpt.Add(node, placeholder);
1407 1408
            }

1409
            return GetOrCreateSlot(placeholder);
1410 1411 1412 1413 1414
        }

        public override BoundNode VisitAnonymousObjectCreationExpression(BoundAnonymousObjectCreationExpression node)
        {
            Debug.Assert(!IsConditionalState);
1415

1416
            int receiverSlot = -1;
1417 1418 1419 1420 1421
            var arguments = node.Arguments;
            var constructor = node.Constructor;
            for (int i = 0; i < arguments.Length; i++)
            {
                var argument = arguments[i];
1422
                TypeSymbolWithAnnotations argumentType = VisitRvalueWithResult(argument);
1423
                var parameter = constructor.Parameters[i];
1424
                ReportArgumentWarnings(argument, argumentType, parameter);
1425

1426
                // https://github.com/dotnet/roslyn/issues/24018 node.Declarations includes
1427
                // explicitly-named properties only. For now, skip expressions
1428
                // with implicit names. See NullableReferenceTypesTests.AnonymousTypes_05.
1429 1430 1431 1432
                if (node.Declarations.Length < arguments.Length)
                {
                    continue;
                }
1433

1434 1435 1436
                PropertySymbol property = node.Declarations[i].Property;
                if (receiverSlot <= 0)
                {
1437
                    receiverSlot = GetOrCreateObjectCreationPlaceholderSlot(node);
1438
                }
1439

1440 1441 1442
                TypeSymbolWithAnnotations propertyType = property.Type;
                ReportAssignmentWarnings(argument, propertyType, argumentType, useLegacyWarnings: false);
                TrackNullableStateForAssignment(argument, propertyType, GetOrCreateSlot(property, receiverSlot), argumentType, MakeSlot(argument));
1443
            }
1444

1445
            // https://github.com/dotnet/roslyn/issues/24018 _result may need to be a new anonymous
1446
            // type since the properties may have distinct nullability from original.
1447
            // (See NullableReferenceTypesTests.AnonymousObjectCreation_02.)
1448
            _resultType = TypeSymbolWithAnnotations.Create(node.Type, NullableAnnotation.NotNullable);
1449
            return null;
1450 1451 1452 1453
        }

        public override BoundNode VisitArrayCreation(BoundArrayCreation node)
        {
1454 1455 1456 1457 1458
            foreach (var expr in node.Bounds)
            {
                VisitRvalue(expr);
            }
            TypeSymbol resultType = (node.InitializerOpt == null) ? node.Type : VisitArrayInitializer(node);
1459
            _resultType = TypeSymbolWithAnnotations.Create(resultType, NullableAnnotation.NotNullable);
1460
            return null;
1461 1462
        }

1463
        private ArrayTypeSymbol VisitArrayInitializer(BoundArrayCreation node)
1464
        {
1465 1466 1467 1468 1469 1470 1471 1472
            BoundArrayInitialization initialization = node.InitializerOpt;
            var expressions = ArrayBuilder<BoundExpression>.GetInstance(initialization.Initializers.Length);
            GetArrayElements(initialization, expressions);
            int n = expressions.Count;

            // Consider recording in the BoundArrayCreation
            // whether the array was implicitly typed, rather than relying on syntax.
            bool isInferred = node.Syntax.Kind() == SyntaxKind.ImplicitArrayCreationExpression;
1473 1474
            var arrayType = (ArrayTypeSymbol)node.Type;
            var elementType = arrayType.ElementType;
1475 1476 1477 1478 1479 1480
            if (!isInferred)
            {
                for (int i = 0; i < n; i++)
                {
                    _ = VisitOptionalImplicitConversion(expressions[i], elementType, useLegacyWarnings: false, AssignmentKind.Assignment);
                }
1481

1482 1483 1484 1485 1486 1487
                _resultType = _invalidType;
                return arrayType;
            }

            var conversions = ArrayBuilder<Conversion>.GetInstance(n);
            var resultTypes = ArrayBuilder<TypeSymbolWithAnnotations>.GetInstance(n);
1488
            for (int i = 0; i < n; i++)
1489
            {
1490 1491 1492 1493 1494 1495
                // collect expressions, conversions and result types
                (BoundExpression expression, Conversion conversion) = RemoveConversion(expressions[i], includeExplicitConversions: false);
                expressions[i] = expression;
                conversions.Add(conversion);
                var resultType = VisitRvalueWithResult(expression);
                resultTypes.Add(resultType);
1496 1497
            }

1498 1499
            var placeholderBuilder = ArrayBuilder<BoundExpression>.GetInstance(n);
            for (int i = 0; i < n; i++)
1500
            {
1501 1502 1503 1504 1505 1506 1507 1508
                placeholderBuilder.Add(CreatePlaceholderIfNecessary(expressions[i], resultTypes[i]));
            }
            var placeholders = placeholderBuilder.ToImmutableAndFree();

            TypeSymbol bestType = null;
            if (!node.HasErrors)
            {
                HashSet<DiagnosticInfo> useSiteDiagnostics = null;
1509
                bestType = BestTypeInferrer.InferBestType(placeholders, _conversions, ref useSiteDiagnostics);
1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521
            }

            TypeSymbolWithAnnotations inferredType;
            if (bestType is null)
            {
                inferredType = elementType.SetUnknownNullabilityForReferenceTypes();
            }
            else
            {
                inferredType = TypeSymbolWithAnnotations.Create(bestType);
            }

1522
            if ((object)bestType != null)
1523 1524 1525
            {
                // Convert elements to best type to determine element top-level nullability and to report nested nullability warnings
                for (int i = 0; i < n; i++)
1526
                {
1527 1528
                    var placeholder = placeholders[i];
                    resultTypes[i] = ApplyConversion(placeholder, placeholder, conversions[i], inferredType, resultTypes[i], checkConversion: true,
1529
                        fromExplicitCast: false, useLegacyWarnings: false, AssignmentKind.Assignment, reportRemainingWarnings: true, reportTopLevelWarnings: false);
1530
                }
1531 1532 1533 1534 1535

                // Set top-level nullability on inferred element type
                inferredType = TypeSymbolWithAnnotations.Create(inferredType.TypeSymbol, BestTypeInferrer.GetNullableAnnotation(resultTypes));

                for (int i = 0; i < n; i++)
1536
                {
1537 1538 1539
                    var nodeForSyntax = expressions[i];
                    // Report top-level warnings
                    _ = ApplyConversion(nodeForSyntax, operandOpt: null, Conversion.Identity, targetTypeWithNullability: inferredType, operandType: resultTypes[i],
1540
                        checkConversion: true, fromExplicitCast: false, useLegacyWarnings: false, AssignmentKind.Assignment, reportRemainingWarnings: false);
1541 1542
                }
            }
1543

1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579
            resultTypes.Free();
            expressions.Free();

            _resultType = _invalidType;
            arrayType = arrayType.WithElementType(inferredType);
            return arrayType;
        }

        /// <summary>
        /// Applies a method similar to <see cref="VisitArrayInitializer(BoundArrayCreation)"/>
        /// The expressions returned from a lambda are not converted though, so we'll have to classify fresh conversions.
        /// Note: even if some conversions fail, we'll proceed to infer top-level nullability. That is reasonable in common cases.
        /// </summary>
        internal static TypeSymbolWithAnnotations BestTypeForLambdaReturns(
            ArrayBuilder<(BoundExpression, TypeSymbolWithAnnotations)> returns,
            CSharpCompilation compilation,
            BoundNode node)
        {
            var walker = new NullableWalker(compilation, method: null,
                useMethodSignatureReturnType: false, useMethodSignatureParameterTypes: false, methodSignatureOpt: null,
                node, returnTypesOpt: null, initialState: null, callbackOpt: null);

            int n = returns.Count;
            var expressions = ArrayBuilder<BoundExpression>.GetInstance();
            var resultTypes = ArrayBuilder<TypeSymbolWithAnnotations>.GetInstance(n);
            var placeholdersBuilder = ArrayBuilder<BoundExpression>.GetInstance(n);
            for (int i = 0; i < n; i++)
            {
                var (returnExpr, resultType) = returns[i];
                expressions.Add(returnExpr);
                resultTypes.Add(resultType);
                placeholdersBuilder.Add(CreatePlaceholderIfNecessary(returnExpr, resultType));
            }

            HashSet<DiagnosticInfo> useSiteDiagnostics = null;
            var placeholders = placeholdersBuilder.ToImmutableAndFree();
1580
            TypeSymbol bestType = BestTypeInferrer.InferBestType(placeholders, walker._conversions, ref useSiteDiagnostics);
1581 1582 1583

            TypeSymbolWithAnnotations inferredType;
            if ((object)bestType != null)
1584
            {
1585 1586 1587
                // Note: so long as we have a best type, we can proceed.
                var bestTypeWithObliviousAnnotation = TypeSymbolWithAnnotations.Create(bestType);
                ConversionsBase conversionsWithoutNullability = walker._conversions.WithNullability(false);
1588
                for (int i = 0; i < n; i++)
1589
                {
1590 1591 1592 1593
                    BoundExpression placeholder = placeholders[i];
                    Conversion conversion = conversionsWithoutNullability.ClassifyConversionFromExpression(placeholder, bestType, ref useSiteDiagnostics);
                    resultTypes[i] = walker.ApplyConversion(placeholder, placeholder, conversion, bestTypeWithObliviousAnnotation, resultTypes[i],
                        checkConversion: false, fromExplicitCast: false, useLegacyWarnings: false, AssignmentKind.Return,
1594
                        reportRemainingWarnings: false, reportTopLevelWarnings: false);
1595
                }
1596 1597 1598 1599 1600 1601 1602

                // Set top-level nullability on inferred type
                inferredType = TypeSymbolWithAnnotations.Create(bestType, BestTypeInferrer.GetNullableAnnotation(resultTypes));
            }
            else
            {
                inferredType = default;
1603 1604
            }

1605 1606 1607 1608 1609
            resultTypes.Free();
            expressions.Free();
            walker.Free();

            return inferredType;
1610 1611
        }

1612
        private static void GetArrayElements(BoundArrayInitialization node, ArrayBuilder<BoundExpression> builder)
1613
        {
1614
            foreach (var child in node.Initializers)
1615
            {
1616
                if (child.Kind == BoundKind.ArrayInitialization)
1617
                {
1618 1619 1620 1621 1622
                    GetArrayElements((BoundArrayInitialization)child, builder);
                }
                else
                {
                    builder.Add(child);
1623 1624 1625 1626
                }
            }
        }

1627
        public override BoundNode VisitArrayAccess(BoundArrayAccess node)
1628
        {
1629 1630 1631
            Debug.Assert(!IsConditionalState);

            VisitRvalue(node.Expression);
1632 1633

            Debug.Assert(!IsConditionalState);
F
Fredric Silberberg 已提交
1634
            Debug.Assert(!node.Expression.Type.IsValueType);
1635 1636
            // https://github.com/dotnet/roslyn/issues/30598: Mark receiver as not null
            // after indices have been visited, and only if the receiver has not changed.
1637
            CheckPossibleNullReceiver(node.Expression);
1638

1639
            var type = _resultType.TypeSymbol as ArrayTypeSymbol;
1640

1641 1642 1643 1644 1645
            foreach (var i in node.Indices)
            {
                VisitRvalue(i);
            }

A
Andy Gocke 已提交
1646
            if (node.Indices.Length == 1 &&
1647
                TypeSymbol.Equals(node.Indices[0].Type, compilation.GetWellKnownType(WellKnownType.System_Range), TypeCompareKind.ConsiderEverything2))
A
Andy Gocke 已提交
1648 1649 1650 1651 1652 1653 1654 1655
            {
                _resultType = TypeSymbolWithAnnotations.Create(type);
            }
            else
            {
                _resultType = type?.ElementType ?? default;
            }

1656 1657 1658 1659 1660 1661 1662 1663 1664 1665
            return null;
        }

        private TypeSymbolWithAnnotations InferResultNullability(BoundBinaryOperator node, TypeSymbolWithAnnotations leftType, TypeSymbolWithAnnotations rightType)
        {
            return InferResultNullability(node.OperatorKind, node.MethodOpt, node.Type, leftType, rightType);
        }

        private TypeSymbolWithAnnotations InferResultNullability(BinaryOperatorKind operatorKind, MethodSymbol methodOpt, TypeSymbol resultType, TypeSymbolWithAnnotations leftType, TypeSymbolWithAnnotations rightType)
        {
1666
            NullableAnnotation nullableAnnotation = NullableAnnotation.Unknown;
1667 1668
            if (operatorKind.IsUserDefined())
            {
1669
                if (operatorKind.IsLifted())
1670
                {
1671
                    // https://github.com/dotnet/roslyn/issues/29953 Conversions: Lifted operator
1672
                    return TypeSymbolWithAnnotations.Create(resultType);
1673
                }
1674
                // Update method based on operand types: see https://github.com/dotnet/roslyn/issues/29605.
1675
                if ((object)methodOpt != null && methodOpt.ParameterCount == 2)
1676
                {
1677
                    return methodOpt.ReturnType;
1678 1679
                }
            }
F
Fredric Silberberg 已提交
1680
            else if (!operatorKind.IsDynamic() && !resultType.IsValueType)
1681 1682 1683 1684 1685
            {
                switch (operatorKind.Operator() | operatorKind.OperandTypes())
                {
                    case BinaryOperatorKind.DelegateCombination:
                        {
1686 1687 1688
                            NullableAnnotation left = leftType.GetValueNullableAnnotation();
                            NullableAnnotation right = rightType.GetValueNullableAnnotation();
                            if (left.IsAnyNotNullable() || right.IsAnyNotNullable())
1689
                            {
1690
                                nullableAnnotation = NullableAnnotation.NotNullable;
1691
                            }
1692
                            else if (left.IsAnyNullable() && right.IsAnyNullable())
1693
                            {
1694
                                nullableAnnotation = NullableAnnotation.Nullable;
1695 1696 1697
                            }
                            else
                            {
1698
                                Debug.Assert(left == NullableAnnotation.Unknown || right == NullableAnnotation.Unknown);
1699
                            }
1700
                        }
1701
                        break;
1702
                    case BinaryOperatorKind.DelegateRemoval:
1703
                        nullableAnnotation = NullableAnnotation.Nullable; // Delegate removal can produce null.
1704 1705
                        break;
                    default:
1706
                        nullableAnnotation = NullableAnnotation.NotNullable;
1707
                        break;
1708 1709
                }
            }
1710 1711

            return TypeSymbolWithAnnotations.Create(resultType, nullableAnnotation);
1712 1713 1714 1715 1716
        }

        protected override void AfterLeftChildHasBeenVisited(BoundBinaryOperator binary)
        {
            Debug.Assert(!IsConditionalState);
1717
            //if (this.State.Reachable) // Consider reachability: see https://github.com/dotnet/roslyn/issues/28798
1718
            {
1719
                TypeSymbolWithAnnotations leftType = _resultType;
1720 1721 1722 1723
                bool warnOnNullReferenceArgument = (binary.OperatorKind.IsUserDefined() && (object)binary.MethodOpt != null && binary.MethodOpt.ParameterCount == 2);

                if (warnOnNullReferenceArgument)
                {
1724
                    ReportArgumentWarnings(binary.Left, leftType, binary.MethodOpt.Parameters[0]);
1725 1726 1727 1728
                }

                VisitRvalue(binary.Right);
                Debug.Assert(!IsConditionalState);
1729

1730 1731
                // At this point, State.Reachable may be false for
                // invalid code such as `s + throw new Exception()`.
1732
                TypeSymbolWithAnnotations rightType = _resultType;
1733 1734 1735

                if (warnOnNullReferenceArgument)
                {
1736
                    ReportArgumentWarnings(binary.Right, rightType, binary.MethodOpt.Parameters[1]);
1737 1738 1739
                }

                Debug.Assert(!IsConditionalState);
1740
                _resultType = InferResultNullability(binary, leftType, rightType);
1741 1742 1743 1744 1745

                BinaryOperatorKind op = binary.OperatorKind.Operator();
                if (op == BinaryOperatorKind.Equal || op == BinaryOperatorKind.NotEqual)
                {
                    BoundExpression operandComparedToNull = null;
1746
                    TypeSymbolWithAnnotations operandComparedToNullType = default;
1747 1748 1749 1750

                    if (binary.Right.ConstantValue?.IsNull == true)
                    {
                        operandComparedToNull = binary.Left;
1751
                        operandComparedToNullType = leftType;
1752 1753 1754 1755
                    }
                    else if (binary.Left.ConstantValue?.IsNull == true)
                    {
                        operandComparedToNull = binary.Right;
1756
                        operandComparedToNullType = rightType;
1757 1758 1759 1760
                    }

                    if (operandComparedToNull != null)
                    {
1761
                        // https://github.com/dotnet/roslyn/issues/29953: We should only report such
1762
                        // diagnostics for locals that are set or checked explicitly within this method.
1763
                        if (!operandComparedToNullType.IsNull && operandComparedToNullType.NullableAnnotation.IsAnyNotNullable())
1764
                        {
1765
                            ReportNonSafetyDiagnostic(op == BinaryOperatorKind.Equal ?
1766 1767 1768 1769 1770 1771 1772 1773
                                                                    ErrorCode.HDN_NullCheckIsProbablyAlwaysFalse :
                                                                    ErrorCode.HDN_NullCheckIsProbablyAlwaysTrue,
                                                                binary.Syntax);
                        }

                        // Skip reference conversions
                        operandComparedToNull = SkipReferenceConversions(operandComparedToNull);

1774
                        // Set all nested conditional slots. For example in a?.b?.c we'll set a, b, and c.
1775 1776
                        var slotBuilder = ArrayBuilder<int>.GetInstance();
                        GetSlotsToMarkAsNotNullable(operandComparedToNull, slotBuilder);
1777 1778 1779
                        if (slotBuilder.Count != 0)
                        {
                            Split();
1780 1781
                            ref LocalState stateToUpdate = ref (op == BinaryOperatorKind.Equal) ? ref this.StateWhenFalse : ref this.StateWhenTrue;
                            MarkSlotsAsNotNullable(slotBuilder, ref stateToUpdate);
1782
                        }
1783
                        slotBuilder.Free();
1784 1785 1786
                    }
                }
            }
1787
        }
1788

1789 1790 1791 1792 1793 1794 1795 1796 1797 1798
        /// <summary>
        /// If we learn that the operand is non-null, we can infer that certain
        /// sub-expressions were also non-null.
        /// Get all nested conditional slots for those sub-expressions. For example in a?.b?.c we'll set a, b, and c.
        /// Only returns slots for tracked expressions.
        /// </summary>
        private void GetSlotsToMarkAsNotNullable(BoundExpression operand, ArrayBuilder<int> slotBuilder)
        {
            Debug.Assert(operand != null);
            Debug.Assert(_lastConditionalAccessSlot == -1);
1799

1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814
            while (true)
            {
                // Due to the nature of binding, if there are conditional access they will be at the top of the bound tree,
                // potentially with a conversion on top of it. We go through any conditional accesses, adding slots for the
                // conditional receivers if they have them. If we ever get to a receiver that MakeSlot doesn't return a slot
                // for, nothing underneath is trackable and we bail at that point. Example:
                //
                //     a?.GetB()?.C // a is a field, GetB is a method, and C is a property
                //
                // The top of the tree is the a?.GetB() conditional call. We'll ask for a slot for a, and we'll get one because
                // fields have slots. The AccessExpression of the BoundConditionalAccess is another BoundConditionalAccess, this time
                // with a receiver of the GetB() BoundCall. Attempting to get a slot for this receiver will fail, and we'll
                // return an array with just the slot for a.
                int slot;
                switch (operand.Kind)
1815
                {
1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827
                    case BoundKind.Conversion:
                        // https://github.com/dotnet/roslyn/issues/29953 Detect when conversion has a nullable operand
                        operand = ((BoundConversion)operand).Operand;
                        continue;
                    case BoundKind.ConditionalAccess:
                        var conditional = (BoundConditionalAccess)operand;

                        slot = MakeSlot(conditional.Receiver);
                        if (slot > 0)
                        {
                            // If we got a slot we must have processed the previous conditional receiver.
                            Debug.Assert(_lastConditionalAccessSlot == -1);
1828

1829 1830
                            // We need to continue the walk regardless of whether the receiver should be updated.
                            var receiverType = conditional.Receiver.Type;
1831
                            if (PossiblyNullableType(receiverType))
1832
                            {
1833
                                slotBuilder.Add(slot);
1834 1835
                            }

1836
                            if (receiverType.IsNullableType())
1837
                            {
1838
                                slot = GetNullableOfTValueSlot(receiverType, slot, out _);
1839
                            }
1840
                        }
1841

1842 1843 1844 1845 1846 1847 1848 1849 1850 1851
                        if (slot > 0)
                        {
                            // When MakeSlot is called on the nested AccessExpression, it will recurse through receivers
                            // until it gets to the BoundConditionalReceiver associated with this node. In our override,
                            // we substitute this slot when we encounter a BoundConditionalReceiver, and reset the
                            // _lastConditionalAccess field.
                            _lastConditionalAccessSlot = slot;
                            operand = conditional.AccessExpression;
                            continue;
                        }
1852

1853 1854 1855 1856 1857 1858
                        // If there's no slot for this receiver, there cannot be another slot for any of the remaining
                        // access expressions.
                        break;
                    default:
                        // Attempt to create a slot for the current thing. If there were any more conditional accesses,
                        // they would have been on top, so this is the last thing we need to specially handle.
1859

1860 1861 1862
                        // https://github.com/dotnet/roslyn/issues/29953 When we handle unconditional access survival (ie after
                        // c.D has been invoked, c must be nonnull or we've thrown a NullRef), revisit whether
                        // we need more special handling here
F
Fredric Silberberg 已提交
1863

1864
                        slot = MakeSlot(operand);
1865
                        if (slot > 0 && PossiblyNullableType(operand.Type))
1866 1867 1868
                        {
                            // If we got a slot then all previous BoundCondtionalReceivers must have been handled.
                            Debug.Assert(_lastConditionalAccessSlot == -1);
F
Fredric Silberberg 已提交
1869

1870 1871
                            slotBuilder.Add(slot);
                        }
F
Fredric Silberberg 已提交
1872

1873 1874 1875 1876 1877 1878 1879 1880 1881 1882
                        break;
                }

                // If we didn't get a slot, it's possible that the current _lastConditionalSlot was never processed,
                // so we reset before leaving the function.
                _lastConditionalAccessSlot = -1;

                return;
            }
        }
1883

1884 1885 1886
        private static bool PossiblyNullableType(TypeSymbol operandType)
            => !(operandType is null) && (!operandType.IsValueType || operandType.IsNullableType());

1887 1888 1889 1890 1891 1892 1893 1894 1895 1896
        private static void MarkSlotsAsNotNullable(ArrayBuilder<int> slots, ref LocalState stateToUpdate)
        {
            if (slots is null)
            {
                return;
            }

            foreach (int slot in slots)
            {
                stateToUpdate[slot] = NullableAnnotation.NotNullable;
1897 1898 1899
            }
        }

1900 1901 1902 1903 1904 1905 1906 1907
        private void LearnFromNonNullTest(BoundExpression expression, ref LocalState state)
        {
            var slotBuilder = ArrayBuilder<int>.GetInstance();
            GetSlotsToMarkAsNotNullable(expression, slotBuilder);
            MarkSlotsAsNotNullable(slotBuilder, ref state);
            slotBuilder.Free();
        }

1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926
        private static BoundExpression SkipReferenceConversions(BoundExpression possiblyConversion)
        {
            while (possiblyConversion.Kind == BoundKind.Conversion)
            {
                var conversion = (BoundConversion)possiblyConversion;
                switch (conversion.ConversionKind)
                {
                    case ConversionKind.ImplicitReference:
                    case ConversionKind.ExplicitReference:
                        possiblyConversion = conversion.Operand;
                        break;
                    default:
                        return possiblyConversion;
                }
            }

            return possiblyConversion;
        }

C
Misc.  
Charles Stoner 已提交
1927 1928
        public override BoundNode VisitNullCoalescingAssignmentOperator(BoundNullCoalescingAssignmentOperator node)
        {
1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958
            BoundExpression leftOperand = node.LeftOperand;
            BoundExpression rightOperand = node.RightOperand;
            int leftSlot = MakeSlot(leftOperand);

            // The assignment to the left below needs the declared type from VisitLvalue, but the hidden
            // unnecessary check diagnostic needs the current adjusted type of the slot
            VisitLvalue(leftOperand);
            TypeSymbolWithAnnotations targetType = _resultType;
            TypeSymbolWithAnnotations currentLeftType = GetAdjustedResult(targetType, leftSlot);

            if (currentLeftType.ValueCanBeNull() == false)
            {
                ReportNonSafetyDiagnostic(ErrorCode.HDN_ExpressionIsProbablyNeverNull, leftOperand.Syntax);
            }

            var leftState = this.State.Clone();

            TypeSymbolWithAnnotations rightResult = VisitOptionalImplicitConversion(rightOperand, targetType, UseLegacyWarnings(leftOperand), AssignmentKind.Assignment);
            TrackNullableStateForAssignment(rightOperand, targetType, leftSlot, rightResult, MakeSlot(rightOperand));
            Join(ref this.State, ref leftState);

            TypeSymbolWithAnnotations resultType = GetNullCoalescingResultType(leftOperand, currentLeftType, rightOperand, rightResult, targetType.TypeSymbol);
            _resultType = resultType;

            if (leftSlot > 0)
            {
                this.State[leftSlot] = resultType.NullableAnnotation;
            }

            return null;
C
Misc.  
Charles Stoner 已提交
1959 1960
        }

1961 1962 1963 1964
        public override BoundNode VisitNullCoalescingOperator(BoundNullCoalescingOperator node)
        {
            Debug.Assert(!IsConditionalState);

1965 1966 1967
            BoundExpression leftOperand = node.LeftOperand;
            BoundExpression rightOperand = node.RightOperand;

1968 1969
            TypeSymbolWithAnnotations leftResult = VisitRvalueWithResult(leftOperand);
            TypeSymbolWithAnnotations rightResult;
1970

1971
            if (IsConstantNull(leftOperand))
1972
            {
1973 1974 1975
                rightResult = VisitRvalueWithResult(rightOperand);
                // Should be able to use rightResult for the result of the operator but
                // binding may have generated a different result type in the case of errors.
1976
                _resultType = TypeSymbolWithAnnotations.Create(node.Type, getNullableAnnotation(rightOperand, rightResult));
1977
                return null;
1978 1979
            }

1980 1981 1982 1983 1984 1985
            var whenNotNull = this.State.Clone();
            LearnFromNonNullTest(leftOperand, ref whenNotNull);

            // Consider learning in whenNull branch as well
            // https://github.com/dotnet/roslyn/issues/30297

1986
            if (leftResult.ValueCanBeNull() == false)
1987
            {
1988
                ReportNonSafetyDiagnostic(ErrorCode.HDN_ExpressionIsProbablyNeverNull, leftOperand.Syntax);
1989 1990
            }

1991
            bool leftIsConstant = leftOperand.ConstantValue != null;
1992 1993 1994 1995 1996
            if (leftIsConstant)
            {
                SetUnreachable();
            }

1997
            // https://github.com/dotnet/roslyn/issues/29955 For cases where the left operand determines
1998
            // the type, we should unwrap the right conversion and re-apply.
1999
            rightResult = VisitRvalueWithResult(rightOperand);
2000
            Join(ref this.State, ref whenNotNull);
2001
            TypeSymbol resultType;
2002 2003
            var leftResultType = leftResult.TypeSymbol;
            var rightResultType = rightResult.TypeSymbol;
2004
            switch (node.OperatorResultKind)
2005
            {
2006 2007 2008 2009
                case BoundNullCoalescingOperatorResultKind.NoCommonType:
                    resultType = node.Type;
                    break;
                case BoundNullCoalescingOperatorResultKind.LeftType:
2010
                    resultType = getLeftResultType(leftResultType, rightResultType);
2011 2012
                    break;
                case BoundNullCoalescingOperatorResultKind.LeftUnwrappedType:
2013
                    resultType = getLeftResultType(leftResultType.StrippedType(), rightResultType);
2014 2015
                    break;
                case BoundNullCoalescingOperatorResultKind.RightType:
2016
                    resultType = getRightResultType(leftResultType, rightResultType);
2017 2018
                    break;
                case BoundNullCoalescingOperatorResultKind.LeftUnwrappedRightType:
2019
                    resultType = getRightResultType(leftResultType.StrippedType(), rightResultType);
2020 2021
                    break;
                case BoundNullCoalescingOperatorResultKind.RightDynamicType:
2022
                    resultType = rightResultType;
2023 2024 2025
                    break;
                default:
                    throw ExceptionUtilities.UnexpectedValue(node.OperatorResultKind);
2026 2027
            }

2028
            _resultType = GetNullCoalescingResultType(leftOperand, leftResult, rightOperand, rightResult, resultType);
2029
            return null;
2030

2031 2032 2033 2034 2035 2036 2037 2038 2039 2040
            NullableAnnotation getNullableAnnotation(BoundExpression e, TypeSymbolWithAnnotations t)
            {
                if (t.IsNull)
                {
                    return GetNullableAnnotation(e);
                }

                return t.NullableAnnotation;
            }

2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051
            TypeSymbol getLeftResultType(TypeSymbol leftType, TypeSymbol rightType)
            {
                // If there was an identity conversion between the two operands (in short, if there
                // is no implicit conversion on the right operand), then check nullable conversions
                // in both directions since it's possible the right operand is the better result type.
                if ((object)rightType != null &&
                    (node.RightOperand as BoundConversion)?.ExplicitCastInCode != false &&
                    GenerateConversionForConditionalOperator(node.LeftOperand, leftType, rightType, reportMismatch: false).Exists)
                {
                    return rightType;
                }
2052

2053 2054 2055 2056 2057 2058 2059
                GenerateConversionForConditionalOperator(node.RightOperand, rightType, leftType, reportMismatch: true);
                return leftType;
            }
            TypeSymbol getRightResultType(TypeSymbol leftType, TypeSymbol rightType)
            {
                GenerateConversionForConditionalOperator(node.LeftOperand, leftType, rightType, reportMismatch: true);
                return rightType;
2060
            }
2061 2062
        }

2063 2064 2065 2066 2067 2068
        /// <summary>
        /// Return top-level nullability for the expression. This method should be called on a limited
        /// set of expressions only. It should not be called on expressions tracked by flow analysis
        /// other than <see cref="BoundKind.ExpressionWithNullability"/> which is an expression
        /// specifically created in NullableWalker to represent the flow analysis state.
        /// </summary>
2069
        private static NullableAnnotation GetNullableAnnotation(BoundExpression expr)
2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080
        {
            switch (expr.Kind)
            {
                case BoundKind.DefaultExpression:
                case BoundKind.Literal:
                    {
                        var constant = expr.ConstantValue;
                        if (constant != null)
                        {
                            if (constant.IsNull)
                            {
2081
                                return NullableAnnotation.Nullable;
2082 2083 2084
                            }
                            if (expr.Type?.IsReferenceType == true)
                            {
2085
                                return NullableAnnotation.NotNullable;
2086 2087
                            }
                        }
2088
                        return NullableAnnotation.Unknown;
2089 2090
                    }
                case BoundKind.ExpressionWithNullability:
2091
                    return ((BoundExpressionWithNullability)expr).NullableAnnotation;
2092 2093
                case BoundKind.MethodGroup:
                case BoundKind.UnboundLambda:
2094
                    return NullableAnnotation.Unknown;
2095 2096
                default:
                    Debug.Assert(false); // unexpected value
2097
                    return NullableAnnotation.Unknown;
2098 2099 2100
            }
        }

2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135
        private static TypeSymbolWithAnnotations GetNullCoalescingResultType(BoundExpression leftOperand, TypeSymbolWithAnnotations leftResult, BoundExpression rightOperand, TypeSymbolWithAnnotations rightResult, TypeSymbol resultType)
        {
            NullableAnnotation resultNullableAnnotation;
            if (getValueNullableAnnotation(leftOperand, leftResult).IsAnyNotNullable())
            {
                resultNullableAnnotation = getNullableAnnotation(leftOperand, leftResult);
            }
            else
            {
                resultNullableAnnotation = getNullableAnnotation(rightOperand, rightResult);
            }

            return TypeSymbolWithAnnotations.Create(resultType, resultNullableAnnotation);

            NullableAnnotation getNullableAnnotation(BoundExpression e, TypeSymbolWithAnnotations t)
            {
                if (t.IsNull)
                {
                    return GetNullableAnnotation(e);
                }

                return t.NullableAnnotation;
            }

            NullableAnnotation getValueNullableAnnotation(BoundExpression e, TypeSymbolWithAnnotations t)
            {
                if (t.IsNull)
                {
                    return GetNullableAnnotation(e);
                }

                return t.GetValueNullableAnnotation();
            }
        }

2136 2137 2138 2139
        public override BoundNode VisitConditionalAccess(BoundConditionalAccess node)
        {
            Debug.Assert(!IsConditionalState);

2140
            var receiver = node.Receiver;
2141
            var receiverType = VisitRvalueWithResult(receiver);
2142

2143
            var receiverState = this.State.Clone();
2144

2145
            if (receiver.Type?.IsValueType == false)
2146
            {
2147
                if (receiverType.ValueCanBeNull() == false)
2148
                {
2149
                    ReportNonSafetyDiagnostic(ErrorCode.HDN_ExpressionIsProbablyNeverNull, receiver.Syntax);
2150
                }
2151

2152 2153 2154 2155
                int slot = MakeSlot(SkipReferenceConversions(receiver));
                if (slot > 0)
                {
                    if (slot >= this.State.Capacity) Normalize(ref this.State);
2156
                    this.State[slot] = NullableAnnotation.NotNullable;
2157 2158 2159 2160
                }
            }

            if (IsConstantNull(node.Receiver))
2161
            {
2162
                SetUnreachable();
2163 2164
            }

2165
            VisitRvalue(node.AccessExpression);
A
Andy Gocke 已提交
2166
            Join(ref this.State, ref receiverState);
2167

2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179
            TypeSymbol type = node.Type;
            NullableAnnotation resultAnnotation;

            // If receiver or the access can produce nullable value, the result can be nullable.
            // Otherwise, result is not nullable.

            if (type.SpecialType == SpecialType.System_Void || type.IsErrorType() || _resultType.IsNull)
            {
                resultAnnotation = NullableAnnotation.Unknown;
            }
            else if (_resultType.IsPossiblyNullableReferenceTypeTypeParameter())
            {
2180
                Debug.Assert(TypeSymbol.Equals(_resultType.TypeSymbol, type, TypeCompareKind.ConsiderEverything2));
2181 2182 2183 2184 2185
                Conversion conversion;
                HashSet<DiagnosticInfo> useSiteDiagnostics = null;

                if (!receiverType.GetValueNullableAnnotation().IsAnyNullable())
                {
2186
                    resultAnnotation = NullableAnnotation.NotAnnotated; // Inherit nullability of the access
2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199
                }
                else if (receiverType.IsPossiblyNullableReferenceTypeTypeParameter() &&
                    (conversion = _conversions.ClassifyConversionFromType(receiverType.TypeSymbol, _resultType.TypeSymbol, ref useSiteDiagnostics)).Exists &&
                    !conversion.IsUserDefined)
                {
                    // where T : U
                    // T?.U or U?.T

                    // T?.U
                    // If T is nullable, U is also nullable - result is nullable
                    // If T is not nullable - result is nullable if U is nullable
                    // If U is nullable - result is nullable
                    // If U is not nullable, T is also not nullable - result is not nullable
2200
                    // So, nullability of the result can be inferred from nullability of U, and the type of the result is U.
2201 2202 2203 2204 2205 2206

                    // U ?. T
                    // If U is nullable - result is nullable
                    // If U is not nullable, T is also not nullable - result is not nullable
                    // If T is nullable, U is also nullable - result is nullable
                    // If T is not nullable - result is nullable if U is nullable
2207
                    // So, nullability of the result can be inferred from nullability of U, but the type of the result is T.
2208
                    // At the moment we don't have a way to represent this correlation, result type is one type parameter, but
2209
                    // nullability comes from another.
2210 2211 2212 2213
                    // Ideally, we would want to have the following behavior:
                    //     U x = U?.T - no warning
                    //     T y = U?.T - a warning
                    // But we can track the state only in the way when either both produce a warning, or none.
2214
                    // It feels like it is reasonable to prefer the latter approach, i.e. produce no warnings
2215
                    // for both scenarios - no false diagnostics.
2216
                    resultAnnotation = NullableAnnotation.NotAnnotated; // Inherit nullability of U
2217 2218 2219
                }
                else
                {
2220
                    resultAnnotation = NullableAnnotation.Nullable;
2221 2222 2223 2224 2225 2226 2227 2228
                }
            }
            else
            {
                NullableAnnotation receiverAnnotation = receiverType.GetValueNullableAnnotation();
                NullableAnnotation accessAnnotation = _resultType.GetValueNullableAnnotation();
                if (receiverAnnotation.IsAnyNullable() || accessAnnotation.IsAnyNullable())
                {
2229
                    resultAnnotation = NullableAnnotation.Nullable;
2230 2231 2232 2233 2234 2235 2236
                }
                else if (receiverAnnotation == NullableAnnotation.Unknown || accessAnnotation == NullableAnnotation.Unknown)
                {
                    resultAnnotation = NullableAnnotation.Unknown;
                }
                else
                {
2237
                    resultAnnotation = NullableAnnotation.NotNullable;
2238 2239 2240
                }
            }

2241
            // https://github.com/dotnet/roslyn/issues/29956 Use flow analysis type rather than node.Type
2242
            // so that nested nullability is inferred from flow analysis. See VisitConditionalOperator.
2243
            _resultType = TypeSymbolWithAnnotations.Create(type, resultAnnotation);
2244
            // https://github.com/dotnet/roslyn/issues/29956 Report conversion warnings.
2245 2246 2247 2248 2249
            return null;
        }

        public override BoundNode VisitConditionalOperator(BoundConditionalOperator node)
        {
C
Charles Stoner 已提交
2250
            var isByRef = node.IsRef;
2251 2252 2253 2254

            VisitCondition(node.Condition);
            var consequenceState = this.StateWhenTrue;
            var alternativeState = this.StateWhenFalse;
2255 2256 2257

            BoundExpression consequence;
            BoundExpression alternative;
2258 2259
            Conversion consequenceConversion;
            Conversion alternativeConversion;
2260 2261
            TypeSymbolWithAnnotations consequenceResult;
            TypeSymbolWithAnnotations alternativeResult;
2262

2263 2264 2265
            bool isConstantTrue = IsConstantTrue(node.Condition);
            bool isConstantFalse = IsConstantFalse(node.Condition);
            if (isConstantTrue)
2266
            {
2267 2268
                (alternative, alternativeConversion, alternativeResult) = visitConditionalOperand(alternativeState, node.Alternative);
                (consequence, consequenceConversion, consequenceResult) = visitConditionalOperand(consequenceState, node.Consequence);
2269
            }
2270
            else if (isConstantFalse)
2271
            {
2272 2273
                (consequence, consequenceConversion, consequenceResult) = visitConditionalOperand(consequenceState, node.Consequence);
                (alternative, alternativeConversion, alternativeResult) = visitConditionalOperand(alternativeState, node.Alternative);
2274 2275
            }
            else
2276
            {
2277
                (consequence, consequenceConversion, consequenceResult) = visitConditionalOperand(consequenceState, node.Consequence);
2278
                Unsplit();
2279
                (alternative, alternativeConversion, alternativeResult) = visitConditionalOperand(alternativeState, node.Alternative);
2280
                Unsplit();
A
Andy Gocke 已提交
2281
                Join(ref this.State, ref consequenceState);
2282 2283
            }

2284
            TypeSymbol resultType;
2285 2286
            if (node.HasErrors)
            {
2287
                resultType = null;
2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298
            }
            else
            {
                // Determine nested nullability using BestTypeInferrer.
                // For constant conditions, we could use the nested nullability of the particular
                // branch, but that requires using the nullability of the branch as it applies to the
                // target type. For instance, the result of the conditional in the following should
                // be `IEnumerable<object>` not `object[]`:
                //   object[] a = ...;
                //   IEnumerable<object?> b = ...;
                //   var c = true ? a : b;
2299 2300
                BoundExpression consequencePlaceholder = CreatePlaceholderIfNecessary(consequence, consequenceResult);
                BoundExpression alternativePlaceholder = CreatePlaceholderIfNecessary(alternative, alternativeResult);
2301
                HashSet<DiagnosticInfo> useSiteDiagnostics = null;
2302
                // https://github.com/dotnet/roslyn/issues/30432: InferBestTypeForConditionalOperator should use node.IsRef.
2303
                resultType = BestTypeInferrer.InferBestTypeForConditionalOperator(consequencePlaceholder, alternativePlaceholder, _conversions, out _, ref useSiteDiagnostics);
2304 2305
            }

2306 2307
            if ((object)resultType != null)
            {
2308
                var resultTypeWithAnnotations = TypeSymbolWithAnnotations.Create(resultType);
2309 2310 2311
                TypeSymbolWithAnnotations convertedConsequenceResult = default;
                TypeSymbolWithAnnotations convertedAlternativeResult = default;

2312 2313
                if (!isConstantFalse)
                {
2314
                    convertedConsequenceResult = ApplyConversion(
2315 2316 2317 2318 2319 2320 2321 2322
                        node.Consequence,
                        consequence,
                        consequenceConversion,
                        resultTypeWithAnnotations,
                        consequenceResult,
                        checkConversion: true,
                        fromExplicitCast: false,
                        useLegacyWarnings: false,
2323 2324
                        AssignmentKind.Assignment,
                        reportTopLevelWarnings: false);
2325 2326 2327 2328
                }

                if (!isConstantTrue)
                {
2329
                    convertedAlternativeResult = ApplyConversion(
2330 2331 2332 2333 2334 2335 2336 2337
                        node.Alternative,
                        alternative,
                        alternativeConversion,
                        resultTypeWithAnnotations,
                        alternativeResult,
                        checkConversion: true,
                        fromExplicitCast: false,
                        useLegacyWarnings: false,
2338 2339
                        AssignmentKind.Assignment,
                        reportTopLevelWarnings: false);
2340
                }
2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379

                if (convertedAlternativeResult.IsNull)
                {
                    Debug.Assert(!convertedConsequenceResult.IsNull);
                    _resultType = convertedConsequenceResult;
                }
                else if (convertedConsequenceResult.IsNull)
                {
                    Debug.Assert(!convertedAlternativeResult.IsNull);
                    _resultType = convertedAlternativeResult;
                }
                else
                {
                    _resultType = TypeSymbolWithAnnotations.Create(resultType,
                                                                   convertedConsequenceResult.NullableAnnotation.JoinForFlowAnalysisBranches(convertedAlternativeResult.NullableAnnotation,
                                                                                                                                             resultType,
                                                                                                                                             type => type.IsPossiblyNullableReferenceTypeTypeParameter()));
                }
            }
            else
            {
                NullableAnnotation resultNullableAnnotation;

                if (isConstantTrue)
                {
                    resultNullableAnnotation = getNullableAnnotation(consequence, consequenceResult);
                }
                else if (isConstantFalse)
                {
                    resultNullableAnnotation = getNullableAnnotation(alternative, alternativeResult);
                }
                else
                {
                    resultNullableAnnotation = getNullableAnnotation(consequence, consequenceResult).JoinForFlowAnalysisBranches(getNullableAnnotation(alternative, alternativeResult),
                                                                                                                                 node.Type,
                                                                                                                                 type => type.IsPossiblyNullableReferenceTypeTypeParameter());
                }

                _resultType = TypeSymbolWithAnnotations.Create(node.Type.SetUnknownNullabilityForReferenceTypes(), resultNullableAnnotation);
2380 2381
            }

2382 2383
            return null;

2384
            NullableAnnotation getNullableAnnotation(BoundExpression expr, TypeSymbolWithAnnotations type)
2385
            {
2386
                if (!type.IsNull)
2387
                {
2388
                    return type.GetValueNullableAnnotation();
2389
                }
2390 2391
                if (expr.IsLiteralNullOrDefault())
                {
2392
                    return NullableAnnotation.Nullable;
2393
                }
2394
                return NullableAnnotation.Unknown;
2395 2396
            }

2397
            (BoundExpression, Conversion, TypeSymbolWithAnnotations) visitConditionalOperand(LocalState state, BoundExpression operand)
2398
            {
2399
                Conversion conversion;
2400 2401 2402 2403
                SetState(state);
                if (isByRef)
                {
                    VisitLvalue(operand);
2404
                    conversion = Conversion.Identity;
2405 2406 2407
                }
                else
                {
2408
                    (operand, conversion) = RemoveConversion(operand, includeExplicitConversions: false);
2409 2410
                    Visit(operand);
                }
2411
                return (operand, conversion, _resultType);
2412
            }
2413 2414
        }

2415 2416 2417 2418 2419
        /// <summary>
        /// Placeholders are bound expressions with type and annotation, when available.
        /// But for typeless expressions (such as `null` or `(null, null)` we hold onto the original bound expression,
        /// as it will be useful for conversions from expression.
        /// </summary>
2420 2421 2422 2423
        private static BoundExpression CreatePlaceholderIfNecessary(BoundExpression expr, TypeSymbolWithAnnotations type)
        {
            return type.IsNull ?
                expr :
2424
                new BoundExpressionWithNullability(expr.Syntax, expr, type.NullableAnnotation, type.TypeSymbol);
2425 2426
        }

2427 2428 2429
        public override BoundNode VisitConditionalReceiver(BoundConditionalReceiver node)
        {
            var result = base.VisitConditionalReceiver(node);
2430
            // https://github.com/dotnet/roslyn/issues/29956 ConditionalReceiver does not
2431
            // have a result type. Should this be moved to ConditionalAccess?
2432
            _resultType = TypeSymbolWithAnnotations.Create(node.Type, NullableAnnotation.NotNullable);
2433 2434 2435 2436 2437
            return result;
        }

        public override BoundNode VisitCall(BoundCall node)
        {
2438
            // Note: we analyze even omitted calls
2439 2440
            var method = node.Method;
            var receiverOpt = node.ReceiverOpt;
2441 2442
            TypeSymbolWithAnnotations receiverType = default;

2443 2444
            if (receiverOpt != null && method.MethodKind != MethodKind.Constructor)
            {
2445
                receiverType = VisitRvalueWithResult(receiverOpt);
2446 2447
                // https://github.com/dotnet/roslyn/issues/30598: Mark receiver as not null
                // after arguments have been visited, and only if the receiver has not changed.
2448 2449
                CheckPossibleNullReceiver(receiverOpt);
            }
2450

2451
            // https://github.com/dotnet/roslyn/issues/29605 Can we handle some error cases?
2452
            // (Compare with CSharpOperationFactory.CreateBoundCallOperation.)
2453 2454 2455
            ImmutableArray<RefKind> refKindsOpt = node.ArgumentRefKindsOpt;
            (ImmutableArray<BoundExpression> arguments, ImmutableArray<Conversion> conversions) = RemoveArgumentConversions(node.Arguments, refKindsOpt);
            if (!receiverType.IsNull)
2456
            {
2457 2458
                // Update method based on inferred receiver type.
                method = (MethodSymbol)AsMemberOfResultType(receiverType, method);
2459 2460
            }

2461
            method = VisitArguments(node, arguments, refKindsOpt, method.Parameters, node.ArgsToParamsOpt,
2462
                node.Expanded, node.InvokedAsExtensionMethod, conversions, method).method;
2463

2464 2465 2466
            if (method.MethodKind == MethodKind.LocalFunction)
            {
                var localFunc = (LocalFunctionSymbol)method.OriginalDefinition;
2467 2468 2469
                ReplayReadsAndWrites(localFunc, node.Syntax, writes: true);
            }

2470
            //if (this.State.Reachable) // Consider reachability: see https://github.com/dotnet/roslyn/issues/28798
2471
            {
2472
                _resultType = method.ReturnType;
2473 2474
            }

2475
            return null;
2476 2477
        }

2478 2479 2480 2481
        /// <summary>
        /// For each argument, figure out if its corresponding parameter is annotated with NotNullWhenFalse or
        /// EnsuresNotNull.
        /// </summary>
2482
        private static ImmutableArray<FlowAnalysisAnnotations> GetAnnotations(int numArguments,
2483 2484
            bool expanded, ImmutableArray<ParameterSymbol> parameters, ImmutableArray<int> argsToParamsOpt)
        {
2485
            ArrayBuilder<FlowAnalysisAnnotations> builder = null;
2486 2487 2488 2489

            for (int i = 0; i < numArguments; i++)
            {
                (ParameterSymbol parameter, _) = GetCorrespondingParameter(i, parameters, argsToParamsOpt, expanded);
2490
                FlowAnalysisAnnotations annotations = parameter?.FlowAnalysisAnnotations ?? FlowAnalysisAnnotations.None;
2491

2492
                annotations = removeInapplicableAnnotations(parameter, annotations);
2493

2494
                if (annotations != FlowAnalysisAnnotations.None && builder == null)
2495
                {
2496 2497
                    builder = ArrayBuilder<FlowAnalysisAnnotations>.GetInstance(numArguments);
                    builder.AddMany(FlowAnalysisAnnotations.None, i);
2498 2499 2500 2501 2502 2503 2504 2505 2506
                }

                if (builder != null)
                {
                    builder.Add(annotations);
                }
            }

            return builder == null ? default : builder.ToImmutableAndFree();
2507

2508
            FlowAnalysisAnnotations removeInapplicableAnnotations(ParameterSymbol parameter, FlowAnalysisAnnotations annotations)
2509
            {
2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539
                // Ignore NotNullWhenTrue that is inapplicable
                annotations = removeInapplicableNotNullWhenSense(parameter, annotations, sense: true);

                // Ignore NotNullWhenFalse that is inapplicable
                annotations = removeInapplicableNotNullWhenSense(parameter, annotations, sense: false);

                const FlowAnalysisAnnotations both = FlowAnalysisAnnotations.AssertsTrue | FlowAnalysisAnnotations.AssertsFalse;
                if (parameter?.Type.SpecialType != SpecialType.System_Boolean)
                {
                    // AssertsTrue and AssertsFalse must be applied to a bool parameter
                    annotations &= ~both;
                }
                else if ((annotations & both) == both)
                {
                    // We'll ignore AssertsTrue and AssertsFalse if both set
                    annotations &= ~both;
                }

                return annotations;
            }

            FlowAnalysisAnnotations removeInapplicableNotNullWhenSense(ParameterSymbol parameter, FlowAnalysisAnnotations annotations, bool sense)
            {
                if (parameter is null)
                {
                    return annotations;
                }

                var whenSense = sense ? FlowAnalysisAnnotations.NotNullWhenTrue : FlowAnalysisAnnotations.NotNullWhenFalse;
                var whenNotSense = sense ? FlowAnalysisAnnotations.NotNullWhenFalse : FlowAnalysisAnnotations.NotNullWhenTrue;
2540 2541 2542 2543 2544 2545 2546 2547 2548

                // NotNullWhenSense (without NotNullWhenNotSense) must be applied on a bool-returning member
                if ((annotations & whenSense) != 0 &&
                    (annotations & whenNotSense) == 0 &&
                    parameter.ContainingSymbol.GetTypeOrReturnType().SpecialType != SpecialType.System_Boolean)
                {
                    annotations &= ~whenSense;
                }

2549 2550
                // NotNullWhenSense must be applied to a reference type, a nullable value type, or an unconstrained generic type
                if ((annotations & whenSense) != 0 && parameter.Type.IsValueType && !parameter.Type.IsNullableType())
2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562
                {
                    annotations &= ~whenSense;
                }

                // NotNullWhenSense is inapplicable when argument corresponds to params parameter and we're in expanded form
                if ((annotations & whenSense) != 0 && expanded && ReferenceEquals(parameter, parameters.Last()))
                {
                    annotations &= ~whenSense;
                }

                return annotations;
            }
2563 2564
        }

2565
        // https://github.com/dotnet/roslyn/issues/29863 Record in the node whether type
2566 2567
        // arguments were implicit, to allow for cases where the syntax is not an
        // invocation (such as a synthesized call from a query interpretation).
2568
        private static bool HasImplicitTypeArguments(BoundExpression node)
2569
        {
2570 2571 2572
            var syntax = node.Syntax;
            if (syntax.Kind() != SyntaxKind.InvocationExpression)
            {
2573
                // Unexpected syntax kind.
2574 2575
                return false;
            }
2576
            var nameSyntax = Binder.GetNameSyntax(((InvocationExpressionSyntax)syntax).Expression, out var _);
2577
            if (nameSyntax == null)
2578
            {
2579 2580
                // Unexpected syntax kind.
                return false;
2581
            }
2582 2583
            nameSyntax = nameSyntax.GetUnqualifiedName();
            return nameSyntax.Kind() != SyntaxKind.GenericName;
2584 2585
        }

2586
        protected override void VisitArguments(ImmutableArray<BoundExpression> arguments, ImmutableArray<RefKind> refKindsOpt, MethodSymbol method)
2587
        {
2588 2589 2590
            // Callers should be using VisitArguments overload below.
            throw ExceptionUtilities.Unreachable;
        }
2591

2592
        private ImmutableArray<TypeSymbolWithAnnotations> VisitArguments(
2593 2594 2595 2596 2597 2598
            BoundExpression node,
            ImmutableArray<BoundExpression> arguments,
            ImmutableArray<RefKind> refKindsOpt,
            MethodSymbol method,
            ImmutableArray<int> argsToParamsOpt,
            bool expanded)
2599
        {
2600 2601
            ImmutableArray<Conversion> conversions;
            (arguments, conversions) = RemoveArgumentConversions(arguments, refKindsOpt);
2602
            return VisitArguments(node, arguments, refKindsOpt, method is null ? default : method.Parameters, argsToParamsOpt, expanded, invokedAsExtensionMethod: false, conversions).resultTypes;
2603 2604
        }

2605
        private ImmutableArray<TypeSymbolWithAnnotations> VisitArguments(
2606
            BoundExpression node,
2607 2608
            ImmutableArray<BoundExpression> arguments,
            ImmutableArray<RefKind> refKindsOpt,
2609
            PropertySymbol property,
2610 2611
            ImmutableArray<int> argsToParamsOpt,
            bool expanded)
2612
        {
2613 2614
            ImmutableArray<Conversion> conversions;
            (arguments, conversions) = RemoveArgumentConversions(arguments, refKindsOpt);
2615
            return VisitArguments(node, arguments, refKindsOpt, property is null ? default : property.Parameters, argsToParamsOpt, expanded, invokedAsExtensionMethod: false, conversions).resultTypes;
2616 2617
        }

2618
        /// <summary>
2619
        /// If you pass in a method symbol, its type arguments will be re-inferred and the re-inferred method will be returned.
2620
        /// </summary>
2621
        private (MethodSymbol method, ImmutableArray<TypeSymbolWithAnnotations> resultTypes) VisitArguments(
2622 2623 2624 2625 2626
            BoundExpression node,
            ImmutableArray<BoundExpression> arguments,
            ImmutableArray<RefKind> refKindsOpt,
            ImmutableArray<ParameterSymbol> parameters,
            ImmutableArray<int> argsToParamsOpt,
2627
            bool expanded,
2628
            bool invokedAsExtensionMethod,
2629 2630
            ImmutableArray<Conversion> conversions,
            MethodSymbol method = null)
2631 2632
        {
            Debug.Assert(!arguments.IsDefault);
2633 2634 2635
            var savedState = this.State.Clone();

            // We do a first pass to work through the arguments without making any assumptions
2636
            ImmutableArray<TypeSymbolWithAnnotations> results = VisitArgumentsEvaluate(arguments, refKindsOpt);
2637

2638
            if ((object)method != null && method.IsGenericMethod)
2639
            {
2640 2641 2642 2643 2644
                if (HasImplicitTypeArguments(node))
                {
                    method = InferMethodTypeArguments((BoundCall)node, method, GetArgumentsForMethodTypeInference(arguments, results));
                    parameters = method.Parameters;
                }
2645
                if (ConstraintsHelper.RequiresChecking(method))
2646 2647 2648 2649
                {
                    var syntax = node.Syntax;
                    CheckMethodConstraints((syntax as InvocationExpressionSyntax)?.Expression ?? syntax, method);
                }
2650 2651
            }

2652 2653
            if (!node.HasErrors && !parameters.IsDefault)
            {
2654
                VisitArgumentConversions(arguments, conversions, refKindsOpt, parameters, argsToParamsOpt, expanded, invokedAsExtensionMethod, results);
2655
            }
2656 2657 2658

            // We do a second pass through the arguments, ignoring any diagnostics produced, but honoring the annotations,
            // to get the proper result state.
2659
            ImmutableArray<FlowAnalysisAnnotations> annotations = GetAnnotations(arguments.Length, expanded, parameters, argsToParamsOpt);
2660 2661 2662 2663

            if (!annotations.IsDefault)
            {
                this.SetState(savedState);
2664 2665 2666 2667 2668

                bool saveDisableDiagnostics = _disableDiagnostics;
                _disableDiagnostics = true;
                if (!node.HasErrors && !parameters.IsDefault)
                {
2669
                    VisitArgumentConversions(arguments, conversions, refKindsOpt, parameters, argsToParamsOpt, expanded, invokedAsExtensionMethod, results); // recompute out vars after state was reset
2670
                }
2671
                VisitArgumentsEvaluateHonoringAnnotations(arguments, refKindsOpt, annotations);
2672 2673

                _disableDiagnostics = saveDisableDiagnostics;
2674 2675
            }

2676
            return (method, results);
2677 2678
        }

2679
        private ImmutableArray<TypeSymbolWithAnnotations> VisitArgumentsEvaluate(
2680 2681
            ImmutableArray<BoundExpression> arguments,
            ImmutableArray<RefKind> refKindsOpt)
2682 2683 2684 2685
        {
            Debug.Assert(!IsConditionalState);
            int n = arguments.Length;
            if (n == 0)
2686
            {
2687
                return ImmutableArray<TypeSymbolWithAnnotations>.Empty;
2688
            }
2689
            var builder = ArrayBuilder<TypeSymbolWithAnnotations>.GetInstance(n);
2690
            for (int i = 0; i < n; i++)
2691
            {
2692
                VisitArgumentEvaluate(arguments, refKindsOpt, i, preserveConditionalState: false);
2693
                builder.Add(_resultType);
2694 2695
            }

2696
            _resultType = _invalidType;
2697 2698 2699
            return builder.ToImmutableAndFree();
        }

2700
        private void VisitArgumentEvaluate(ImmutableArray<BoundExpression> arguments, ImmutableArray<RefKind> refKindsOpt, int i, bool preserveConditionalState)
2701
        {
2702
            Debug.Assert(!IsConditionalState);
2703 2704 2705 2706
            RefKind refKind = GetRefKind(refKindsOpt, i);
            var argument = arguments[i];
            if (refKind != RefKind.Out)
            {
2707
                // https://github.com/dotnet/roslyn/issues/29958 `ref` arguments should be treated as l-values
2708
                // for assignment. See `ref x3` in NullableReferenceTypesTests.PassingParameters_01.
2709 2710 2711 2712 2713 2714 2715 2716 2717
                if (preserveConditionalState)
                {
                    Visit(argument);
                    // No Unsplit
                }
                else
                {
                    VisitRvalue(argument);
                }
2718 2719 2720
            }
            else
            {
2721
                // As far as we can tell, there is no scenario relevant to nullability analysis
2722
                // where splitting an L-value (for instance with a ref conditional) would affect the result.
2723 2724 2725 2726 2727 2728
                VisitLvalue(argument);
            }
        }

        /// <summary>
        /// Visit all the arguments for the purpose of computing the exit state of the method,
2729
        /// given the annotations.
2730
        /// If there is any [NotNullWhenTrue/False] annotation, then we'll return in a conditional state for the invocation.
2731 2732 2733 2734
        /// </summary>
        private void VisitArgumentsEvaluateHonoringAnnotations(
            ImmutableArray<BoundExpression> arguments,
            ImmutableArray<RefKind> refKindsOpt,
2735
            ImmutableArray<FlowAnalysisAnnotations> annotations)
2736 2737 2738
        {
            Debug.Assert(!IsConditionalState);
            Debug.Assert(annotations.Length == arguments.Length);
2739
            Debug.Assert(_disableDiagnostics);
2740 2741 2742

            for (int i = 0; i < arguments.Length; i++)
            {
2743 2744 2745 2746
                FlowAnalysisAnnotations annotation = annotations[i];
                bool assertsTrue = (annotation & FlowAnalysisAnnotations.AssertsTrue) != 0;
                bool assertsFalse = (annotation & FlowAnalysisAnnotations.AssertsFalse) != 0;

2747
                if (this.IsConditionalState)
2748
                {
2749
                    // We could be in a conditional state because of a conditional annotation (like NotNullWhenFalse)
2750 2751
                    // Then WhenTrue/False states correspond to the invocation returning true/false

2752
                    // We'll first assume that we're in the unconditional state where the method returns true,
2753 2754 2755 2756 2757 2758
                    // then we'll repeat assuming the method returns false.

                    LocalState whenTrue = this.StateWhenTrue.Clone();
                    LocalState whenFalse = this.StateWhenFalse.Clone();

                    this.SetState(whenTrue);
2759
                    visitArgumentEvaluateAndUnsplit(i, assertsTrue, assertsFalse);
2760 2761 2762 2763
                    Debug.Assert(!IsConditionalState);
                    whenTrue = this.State; // LocalState may be a struct

                    this.SetState(whenFalse);
2764
                    visitArgumentEvaluateAndUnsplit(i, assertsTrue, assertsFalse);
2765 2766 2767 2768
                    Debug.Assert(!IsConditionalState);
                    whenFalse = this.State; // LocalState may be a struct

                    this.SetConditionalState(whenTrue, whenFalse);
2769 2770 2771
                }
                else
                {
2772
                    visitArgumentEvaluateAndUnsplit(i, assertsTrue, assertsFalse);
2773 2774 2775
                }

                var argument = arguments[i];
2776
                var argumentType = argument.Type;
2777
                if (!PossiblyNullableType(argumentType))
2778 2779 2780 2781
                {
                    continue;
                }

2782 2783
                bool notNullWhenTrue = (annotation & FlowAnalysisAnnotations.NotNullWhenTrue) != 0;
                bool notNullWhenFalse = (annotation & FlowAnalysisAnnotations.NotNullWhenFalse) != 0;
2784
                if (notNullWhenTrue || notNullWhenFalse)
2785
                {
2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802
                    // The WhenTrue/False states correspond to the invocation returning true/false
                    bool wasPreviouslySplit = this.IsConditionalState;
                    Split();

                    var slotBuilder = ArrayBuilder<int>.GetInstance();
                    GetSlotsToMarkAsNotNullable(arguments[i], slotBuilder);

                    if (notNullWhenTrue)
                    {
                        MarkSlotsAsNotNullable(slotBuilder, ref StateWhenTrue);
                    }
                    if (notNullWhenFalse)
                    {
                        MarkSlotsAsNotNullable(slotBuilder, ref StateWhenFalse);
                        if (notNullWhenTrue && !wasPreviouslySplit) Unsplit();
                    }
                    slotBuilder.Free();
2803
                }
2804
            }
2805

2806
            _resultType = _invalidType;
2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831

            // Evaluate an argument, potentially producing a split state.
            // Then unsplit it based on [AssertsTrue] or [AssertsFalse] attributes, or default Unsplit otherwise.
            void visitArgumentEvaluateAndUnsplit(int argumentIndex, bool assertsTrue, bool assertsFalse)
            {
                Debug.Assert(!IsConditionalState);
                VisitArgumentEvaluate(arguments, refKindsOpt, argumentIndex, preserveConditionalState: true);

                if (!this.IsConditionalState)
                {
                    return;
                }
                else if (assertsTrue)
                {
                    this.SetState(this.StateWhenTrue);
                }
                else if (assertsFalse)
                {
                    this.SetState(this.StateWhenFalse);
                }
                else
                {
                    this.Unsplit();
                }
            }
2832 2833
        }

2834
        private void VisitArgumentConversions(
2835
            ImmutableArray<BoundExpression> arguments,
2836
            ImmutableArray<Conversion> conversions,
2837
            ImmutableArray<RefKind> refKindsOpt,
2838
            ImmutableArray<ParameterSymbol> parameters,
2839 2840
            ImmutableArray<int> argsToParamsOpt,
            bool expanded,
2841
            bool invokedAsExtensionMethod,
2842
            ImmutableArray<TypeSymbolWithAnnotations> results)
2843
        {
2844
            for (int i = 0; i < arguments.Length; i++)
2845
            {
2846 2847
                (ParameterSymbol parameter, TypeSymbolWithAnnotations parameterType) = GetCorrespondingParameter(i, parameters, argsToParamsOpt, expanded);
                if (parameter is null)
2848
                {
2849
                    continue;
2850
                }
2851
                VisitArgumentConversion(
2852
                    arguments[i],
2853
                    conversions.IsDefault ? Conversion.Identity : conversions[i],
2854 2855 2856
                    GetRefKind(refKindsOpt, i),
                    parameter,
                    parameterType,
2857 2858
                    results[i],
                    invokedAsExtensionMethod && i == 0);
2859 2860
            }
        }
2861

2862 2863 2864
        /// <summary>
        /// Report warnings for an argument corresponding to a specific parameter.
        /// </summary>
2865
        private void VisitArgumentConversion(
2866
            BoundExpression argument,
2867
            Conversion conversion,
2868 2869 2870
            RefKind refKind,
            ParameterSymbol parameter,
            TypeSymbolWithAnnotations parameterType,
2871 2872
            TypeSymbolWithAnnotations resultType,
            bool extensionMethodThisArgument)
2873
        {
2874
            var argumentType = resultType.TypeSymbol;
2875 2876 2877 2878 2879
            switch (refKind)
            {
                case RefKind.None:
                case RefKind.In:
                    {
2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891
                        ApplyConversion(
                            argument,
                            argument,
                            conversion,
                            parameterType,
                            resultType,
                            checkConversion: true,
                            fromExplicitCast: false,
                            useLegacyWarnings: false,
                            AssignmentKind.Argument,
                            target: parameter,
                            extensionMethodThisArgument: extensionMethodThisArgument);
2892 2893 2894
                    }
                    break;
                case RefKind.Out:
2895
                    {
2896
                        if (argument is BoundLocal local && local.DeclarationKind == BoundLocalDeclarationKind.WithInferredType)
2897
                        {
2898 2899 2900
                            _variableTypes[local.LocalSymbol] = parameterType;
                            resultType = parameterType;
                        }
2901

2902 2903 2904
                        TypeSymbolWithAnnotations adjustedParameterType = adjustNullableAnnotationForNullabilityCheck(parameterType, resultType);

                        if (!ReportNullableAssignmentIfNecessary(argument, resultType, adjustedParameterType, useLegacyWarnings: UseLegacyWarnings(argument)))
2905 2906 2907 2908 2909 2910
                        {
                            HashSet<DiagnosticInfo> useSiteDiagnostics = null;
                            if (!_conversions.HasIdentityOrImplicitReferenceConversion(parameterType.TypeSymbol, argumentType, ref useSiteDiagnostics))
                            {
                                ReportNullabilityMismatchInArgument(argument, argumentType, parameter, parameterType.TypeSymbol);
                            }
2911
                        }
2912
                        // Set nullable state of argument to parameter type.
2913
                        TrackNullableStateForAssignment(argument, resultType, MakeSlot(argument), parameterType);
2914
                        break;
2915 2916 2917
                    }
                case RefKind.Ref:
                    {
2918
                        bool reportedWarning = false;
2919
                        if (!argument.IsSuppressed)
2920
                        {
2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932
                            // Effect of this call is likely not observable due to https://github.com/dotnet/roslyn/issues/30946.
                            // See unit test NullabilityOfTypeParameters_080 for an attempt to see the effect.
                            TypeSymbolWithAnnotations adjustedResultType = adjustNullableAnnotationForNullabilityCheck(resultType, parameterType);

                            reportedWarning = ReportNullableAssignmentIfNecessary(argument, parameterType, adjustedResultType, useLegacyWarnings: false, assignmentKind: AssignmentKind.Argument, target: parameter);

                            if (!reportedWarning)
                            {
                                TypeSymbolWithAnnotations adjustedParameterType = adjustNullableAnnotationForNullabilityCheck(parameterType, resultType);

                                reportedWarning = ReportNullableAssignmentIfNecessary(argument, resultType, adjustedParameterType, useLegacyWarnings: UseLegacyWarnings(argument));
                            }
2933
                        }
2934

2935 2936 2937 2938 2939 2940 2941 2942 2943
                        if (!reportedWarning)
                        {
                            if ((object)argumentType != null &&
                                IsNullabilityMismatch(argumentType, parameterType.TypeSymbol))
                            {
                                ReportNullabilityMismatchInArgument(argument, argumentType, parameter, parameterType.TypeSymbol);
                            }
                        }
                        // Set nullable state of argument to parameter type.
2944
                        TrackNullableStateForAssignment(argument, resultType, MakeSlot(argument), parameterType);
2945
                        break;
2946 2947 2948 2949
                    }
                default:
                    throw ExceptionUtilities.UnexpectedValue(refKind);
            }
2950 2951 2952 2953 2954

            TypeSymbolWithAnnotations adjustNullableAnnotationForNullabilityCheck(TypeSymbolWithAnnotations sourceType, TypeSymbolWithAnnotations destinationType)
            {
                if (sourceType.IsPossiblyNullableReferenceTypeTypeParameter() && !destinationType.IsPossiblyNullableReferenceTypeTypeParameter())
                {
2955
                    return TypeSymbolWithAnnotations.Create(sourceType.TypeSymbol, NullableAnnotation.Nullable);
2956 2957 2958 2959
                }

                return sourceType;
            }
2960 2961
        }

2962 2963 2964
        private static (ImmutableArray<BoundExpression> Arguments, ImmutableArray<Conversion> Conversions) RemoveArgumentConversions(
            ImmutableArray<BoundExpression> arguments,
            ImmutableArray<RefKind> refKindsOpt)
2965
        {
2966
            int n = arguments.Length;
2967
            var conversions = default(ImmutableArray<Conversion>);
2968
            if (n > 0)
2969
            {
2970
                var argumentsBuilder = ArrayBuilder<BoundExpression>.GetInstance(n);
2971
                var conversionsBuilder = ArrayBuilder<Conversion>.GetInstance(n);
2972 2973 2974 2975 2976
                bool includedConversion = false;
                for (int i = 0; i < n; i++)
                {
                    RefKind refKind = GetRefKind(refKindsOpt, i);
                    var argument = arguments[i];
2977
                    var conversion = Conversion.Identity;
2978
                    // https://github.com/dotnet/roslyn/issues/29958 Should `RefKind.In` be treated similarly to `RefKind.None`?
2979 2980
                    if (refKind == RefKind.None)
                    {
2981 2982 2983
                        var before = argument;
                        (argument, conversion) = RemoveConversion(argument, includeExplicitConversions: false);
                        if (argument != before)
2984 2985 2986 2987 2988
                        {
                            includedConversion = true;
                        }
                    }
                    argumentsBuilder.Add(argument);
2989
                    conversionsBuilder.Add(conversion);
2990 2991 2992 2993
                }
                if (includedConversion)
                {
                    arguments = argumentsBuilder.ToImmutable();
2994
                    conversions = conversionsBuilder.ToImmutable();
2995 2996
                }
                argumentsBuilder.Free();
2997
                conversionsBuilder.Free();
2998
            }
2999
            return (arguments, conversions);
3000 3001
        }

3002 3003
        private VariableState GetVariableState()
        {
3004
            // https://github.com/dotnet/roslyn/issues/29617 To track nullability of captured variables inside and
3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017
            // outside a lambda, the lambda should be considered executed at the location the lambda
            // is converted to a delegate.
            return new VariableState(
                _variableSlot.ToImmutableDictionary(),
                ImmutableArray.Create(variableBySlot, start: 0, length: nextVariableSlot),
                _variableTypes.ToImmutableDictionary());
        }

        private UnboundLambda GetUnboundLambda(BoundLambda expr, VariableState variableState)
        {
            return expr.UnboundLambda.WithNullableState(_binder, variableState);
        }

3018 3019 3020 3021 3022
        private static (ParameterSymbol Parameter, TypeSymbolWithAnnotations Type) GetCorrespondingParameter(
            int argumentOrdinal,
            ImmutableArray<ParameterSymbol> parameters,
            ImmutableArray<int> argsToParamsOpt,
            bool expanded)
3023
        {
3024 3025
            if (parameters.IsDefault)
            {
3026
                return (default, default);
3027
            }
3028

3029
            int n = parameters.Length;
3030 3031 3032 3033
            ParameterSymbol parameter;

            if (argsToParamsOpt.IsDefault)
            {
3034
                if (argumentOrdinal < n)
3035
                {
3036
                    parameter = parameters[argumentOrdinal];
3037 3038 3039
                }
                else if (expanded)
                {
3040
                    parameter = parameters[n - 1];
3041 3042 3043 3044
                }
                else
                {
                    parameter = null;
3045 3046 3047 3048
                }
            }
            else
            {
3049 3050
                int parameterOrdinal = argsToParamsOpt[argumentOrdinal];

3051
                if (parameterOrdinal < n)
3052
                {
3053
                    parameter = parameters[parameterOrdinal];
3054 3055 3056 3057 3058 3059 3060 3061
                }
                else
                {
                    parameter = null;
                    expanded = false;
                }
            }

3062
            if (parameter is null)
3063
            {
3064
                Debug.Assert(!expanded);
3065
                return (default, default);
3066
            }
3067

3068
            var type = parameter.Type;
3069
            if (expanded && parameter.Ordinal == n - 1 && type.IsSZArray())
3070 3071 3072 3073 3074
            {
                type = ((ArrayTypeSymbol)type.TypeSymbol).ElementType;
            }

            return (parameter, type);
3075 3076
        }

3077
        private MethodSymbol InferMethodTypeArguments(BoundCall node, MethodSymbol method, ImmutableArray<BoundExpression> arguments)
3078
        {
3079
            Debug.Assert(method.IsGenericMethod);
3080

3081
            // https://github.com/dotnet/roslyn/issues/27961 OverloadResolution.IsMemberApplicableInNormalForm and
3082 3083 3084 3085 3086 3087 3088
            // IsMemberApplicableInExpandedForm use the least overridden method. We need to do the same here.
            var definition = method.ConstructedFrom;
            var refKinds = ArrayBuilder<RefKind>.GetInstance();
            if (node.ArgumentRefKindsOpt != null)
            {
                refKinds.AddRange(node.ArgumentRefKindsOpt);
            }
3089

3090
            // https://github.com/dotnet/roslyn/issues/27961 Do we really need OverloadResolution.GetEffectiveParameterTypes?
3091
            // Aren't we doing roughly the same calculations in GetCorrespondingParameter?
3092 3093
            OverloadResolution.GetEffectiveParameterTypes(
                definition,
3094
                arguments.Length,
3095 3096
                node.ArgsToParamsOpt,
                refKinds,
C
Charles Stoner 已提交
3097
                isMethodGroupConversion: false,
3098
                // https://github.com/dotnet/roslyn/issues/27961 `allowRefOmittedArguments` should be
3099 3100 3101 3102 3103 3104 3105
                // false for constructors and several other cases (see Binder use). Should we
                // capture the original value in the BoundCall?
                allowRefOmittedArguments: true,
                binder: _binder,
                expanded: node.Expanded,
                parameterTypes: out ImmutableArray<TypeSymbolWithAnnotations> parameterTypes,
                parameterRefKinds: out ImmutableArray<RefKind> parameterRefKinds);
3106

3107 3108 3109
            refKinds.Free();
            HashSet<DiagnosticInfo> useSiteDiagnostics = null;
            var result = MethodTypeInferrer.Infer(
3110 3111
                _binder,
                _conversions,
3112 3113 3114 3115 3116
                definition.TypeParameters,
                definition.ContainingType,
                parameterTypes,
                parameterRefKinds,
                arguments,
3117
                ref useSiteDiagnostics,
3118 3119
                getTypeWithAnnotationOpt: s_getTypeWithSpeakableAnnotations);

3120
            if (!result.Success)
3121
            {
3122
                return method;
3123
            }
3124
            return definition.Construct(result.InferredTypeArguments);
3125
        }
3126

3127 3128 3129 3130
        // Note: although only tuple types can have nested types that are unspeakable, we make all nested types speakable to be sure
        private readonly static Func<BoundExpression, TypeSymbolWithAnnotations> s_getTypeWithSpeakableAnnotations =
            (expr) => TypeSymbolWithAnnotations.Create(expr.Type, GetNullableAnnotation(expr)).SetSpeakableNullabilityForReferenceTypes();

3131
        private ImmutableArray<BoundExpression> GetArgumentsForMethodTypeInference(ImmutableArray<BoundExpression> arguments, ImmutableArray<TypeSymbolWithAnnotations> argumentResults)
3132
        {
3133
            // https://github.com/dotnet/roslyn/issues/27961 MethodTypeInferrer.Infer relies
3134 3135
            // on the BoundExpressions for tuple element types and method groups.
            // By using a generic BoundValuePlaceholder, we're losing inference in those cases.
3136
            // https://github.com/dotnet/roslyn/issues/27961 Inference should be based on
3137 3138 3139 3140 3141 3142 3143 3144 3145
            // unconverted arguments. Consider cases such as `default`, lambdas, tuples.
            int n = arguments.Length;
            var builder = ArrayBuilder<BoundExpression>.GetInstance(n);
            for (int i = 0; i < n; i++)
            {
                builder.Add(getArgumentForMethodTypeInference(arguments[i], argumentResults[i]));
            }
            return builder.ToImmutableAndFree();

3146
            BoundExpression getArgumentForMethodTypeInference(BoundExpression argument, TypeSymbolWithAnnotations argumentType)
3147
            {
3148 3149 3150 3151 3152 3153 3154
                if (argument.Kind == BoundKind.Lambda)
                {
                    // MethodTypeInferrer must infer nullability for lambdas based on the nullability
                    // from flow analysis rather than the declared nullability. To allow that, we need
                    // to re-bind lambdas in MethodTypeInferrer.
                    return GetUnboundLambda((BoundLambda)argument, GetVariableState());
                }
3155
                if (argumentType.IsNull)
3156 3157 3158 3159 3160 3161
                {
                    return argument;
                }
                if (argument is BoundLocal local && local.DeclarationKind == BoundLocalDeclarationKind.WithInferredType)
                {
                    // 'out var' doesn't contribute to inference
3162
                    return new BoundExpressionWithNullability(argument.Syntax, argument, NullableAnnotation.Unknown, type: null);
3163
                }
3164
                return new BoundExpressionWithNullability(argument.Syntax, argument, argumentType.NullableAnnotation, argumentType.TypeSymbol);
3165 3166
            }
        }
3167

3168 3169 3170
        private void CheckMethodConstraints(SyntaxNode syntax, MethodSymbol method)
        {
            var diagnosticsBuilder = ArrayBuilder<TypeParameterDiagnosticInfo>.GetInstance();
3171
            var nullabilityBuilder = ArrayBuilder<TypeParameterDiagnosticInfo>.GetInstance();
3172 3173 3174 3175
            ArrayBuilder<TypeParameterDiagnosticInfo> useSiteDiagnosticsBuilder = null;
            ConstraintsHelper.CheckMethodConstraints(
                method,
                _conversions,
J
Jared Parsons 已提交
3176
                includeNullability: true,
3177 3178
                compilation,
                diagnosticsBuilder,
3179
                nullabilityBuilder,
3180
                ref useSiteDiagnosticsBuilder);
3181
            foreach (var pair in nullabilityBuilder)
3182 3183 3184 3185
            {
                Diagnostics.Add(pair.DiagnosticInfo, syntax.Location);
            }
            useSiteDiagnosticsBuilder?.Free();
3186
            nullabilityBuilder.Free();
3187 3188 3189
            diagnosticsBuilder.Free();
        }

3190 3191 3192 3193
        private void ReplayReadsAndWrites(LocalFunctionSymbol localFunc,
                                  SyntaxNode syntax,
                                  bool writes)
        {
3194
            // https://github.com/dotnet/roslyn/issues/27233 Support field initializers in local functions.
3195 3196
        }

3197 3198 3199 3200 3201 3202 3203 3204 3205 3206
        /// <summary>
        /// Returns the expression without the top-most conversion plus the conversion.
        /// If the expression is not a conversion, returns the original expression plus
        /// the Identity conversion. If `includeExplicitConversions` is true, implicit and
        /// explicit conversions are considered. If `includeExplicitConversions` is false
        /// only implicit conversions are considered and if the expression is an explicit
        /// conversion, the expression is returned as is, with the Identity conversion.
        /// (Currently, the only visit method that passes `includeExplicitConversions: true`
        /// is VisitConversion. All other callers are handling implicit conversions only.)
        /// </summary>
3207
        private static (BoundExpression expression, Conversion conversion) RemoveConversion(BoundExpression expr, bool includeExplicitConversions)
3208
        {
3209
            ConversionGroup group = null;
3210 3211 3212 3213 3214 3215 3216
            while (true)
            {
                if (expr.Kind != BoundKind.Conversion)
                {
                    break;
                }
                var conversion = (BoundConversion)expr;
3217
                if (group != conversion.ConversionGroupOpt && group != null)
3218
                {
3219
                    // E.g.: (C)(B)a
3220 3221
                    break;
                }
3222 3223 3224 3225 3226 3227
                group = conversion.ConversionGroupOpt;
                Debug.Assert(group != null || !conversion.ExplicitCastInCode); // Explicit conversions should include a group.
                if (!includeExplicitConversions && group?.IsExplicitConversion == true)
                {
                    return (expr, Conversion.Identity);
                }
3228
                expr = conversion.Operand;
3229 3230 3231 3232 3233 3234 3235 3236 3237
                if (group == null)
                {
                    // Ungrouped conversion should not be followed by another ungrouped
                    // conversion. Otherwise, the conversions should have been grouped.
                    Debug.Assert(expr.Kind != BoundKind.Conversion ||
                        ((BoundConversion)expr).ConversionGroupOpt != null ||
                        ((BoundConversion)expr).ConversionKind == ConversionKind.NoConversion);
                    return (expr, conversion.Conversion);
                }
3238
            }
3239
            return (expr, group?.Conversion ?? Conversion.Identity);
3240
        }
3241

3242 3243
        // See Binder.BindNullCoalescingOperator for initial binding.
        private Conversion GenerateConversionForConditionalOperator(BoundExpression sourceExpression, TypeSymbol sourceType, TypeSymbol destinationType, bool reportMismatch)
3244
        {
3245
            var conversion = GenerateConversion(_conversions, sourceExpression, sourceType, destinationType, fromExplicitCast: false, extensionMethodThisArgument: false);
3246
            bool canConvertNestedNullability = conversion.Exists;
3247
            if (!canConvertNestedNullability && reportMismatch && !sourceExpression.IsSuppressed)
3248
            {
3249
                ReportNullabilityMismatchInAssignment(sourceExpression.Syntax, GetTypeAsDiagnosticArgument(sourceType), destinationType);
3250
            }
3251 3252
            return conversion;
        }
3253

3254
        private static Conversion GenerateConversion(Conversions conversions, BoundExpression sourceExpression, TypeSymbol sourceType, TypeSymbol destinationType, bool fromExplicitCast, bool extensionMethodThisArgument)
3255
        {
3256
            HashSet<DiagnosticInfo> useSiteDiagnostics = null;
3257 3258 3259 3260 3261 3262 3263 3264 3265 3266
            bool useExpression = UseExpressionForConversion(sourceExpression);
            if (extensionMethodThisArgument)
            {
                return conversions.ClassifyImplicitExtensionMethodThisArgConversion(
                    useExpression ? sourceExpression : null,
                    sourceType,
                    destinationType,
                    ref useSiteDiagnostics);
            }
            return useExpression ?
3267 3268 3269 3270 3271 3272 3273
                (fromExplicitCast ?
                    conversions.ClassifyConversionFromExpression(sourceExpression, destinationType, ref useSiteDiagnostics, forCast: true) :
                    conversions.ClassifyImplicitConversionFromExpression(sourceExpression, destinationType, ref useSiteDiagnostics)) :
                (fromExplicitCast ?
                    conversions.ClassifyConversionFromType(sourceType, destinationType, ref useSiteDiagnostics, forCast: true) :
                    conversions.ClassifyImplicitConversionFromType(sourceType, destinationType, ref useSiteDiagnostics));
        }
3274

3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298
        /// <summary>
        /// Returns true if the expression should be used as the source when calculating
        /// a conversion from this expression, rather than using the type (with nullability)
        /// calculated by visiting this expression. Typically, that means expressions that
        /// do not have an explicit type but there are several other cases as well.
        /// (See expressions handled in ClassifyImplicitBuiltInConversionFromExpression.)
        /// </summary>
        private static bool UseExpressionForConversion(BoundExpression value)
        {
            if (value is null)
            {
                return false;
            }
            if (value.Type is null || value.Type.IsDynamic() || value.ConstantValue != null)
            {
                return true;
            }
            switch (value.Kind)
            {
                case BoundKind.InterpolatedString:
                    return true;
                default:
                    return false;
            }
3299 3300
        }

3301 3302 3303
        /// <summary>
        /// Adjust declared type based on inferred nullability at the point of reference.
        /// </summary>
3304
        private TypeSymbolWithAnnotations GetAdjustedResult(TypeSymbolWithAnnotations type, int slot)
3305
        {
3306
            if (slot > 0 && slot < this.State.Capacity)
3307
            {
3308 3309
                NullableAnnotation annotation = this.State[slot];
                if (annotation != type.NullableAnnotation)
3310
                {
3311
                    return TypeSymbolWithAnnotations.Create(type.TypeSymbol, annotation);
3312 3313
                }
            }
3314
            return type;
3315
        }
3316

3317
        private static Symbol AsMemberOfResultType(TypeSymbolWithAnnotations resultType, Symbol symbol)
3318
        {
3319
            var containingType = resultType.TypeSymbol as NamedTypeSymbol;
3320
            if ((object)containingType == null || containingType.IsErrorType())
3321
            {
3322 3323
                return symbol;
            }
3324 3325 3326 3327 3328
            return AsMemberOfType(containingType, symbol);
        }

        private static Symbol AsMemberOfType(NamedTypeSymbol containingType, Symbol symbol)
        {
3329
            Debug.Assert((object)symbol != null);
3330 3331 3332 3333
            if (symbol.Kind == SymbolKind.Method)
            {
                if (((MethodSymbol)symbol).MethodKind == MethodKind.LocalFunction)
                {
3334
                    // https://github.com/dotnet/roslyn/issues/27233 Handle type substitution for local functions.
3335 3336
                    return symbol;
                }
3337
            }
3338 3339 3340 3341
            var symbolDef = symbol.OriginalDefinition;
            var symbolDefContainer = symbolDef.ContainingType;
            while (true)
            {
3342
                if (containingType.OriginalDefinition.Equals(symbolDefContainer, TypeCompareKind.AllIgnoreOptions))
3343
                {
3344 3345 3346 3347
                    if (symbolDefContainer.IsTupleType)
                    {
                        return AsMemberOfTupleType((TupleTypeSymbol)containingType, symbol);
                    }
3348 3349 3350 3351 3352 3353
                    var result = symbolDef.SymbolAsMember(containingType);
                    if (result is MethodSymbol resultMethod && resultMethod.IsGenericMethod)
                    {
                        return resultMethod.Construct(((MethodSymbol)symbol).TypeArguments);
                    }
                    return result;
3354 3355 3356 3357 3358 3359 3360
                }
                containingType = containingType.BaseTypeNoUseSiteDiagnostics;
                if ((object)containingType == null)
                {
                    break;
                }
            }
3361
            // https://github.com/dotnet/roslyn/issues/29967 Handle other cases such as interfaces.
3362
            return symbol;
3363 3364
        }

3365 3366
        private static Symbol AsMemberOfTupleType(TupleTypeSymbol tupleType, Symbol symbol)
        {
3367
            if (symbol.ContainingType.Equals(tupleType))
3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385
            {
                return symbol;
            }
            switch (symbol.Kind)
            {
                case SymbolKind.Field:
                    {
                        var index = ((FieldSymbol)symbol).TupleElementIndex;
                        if (index >= 0)
                        {
                            return tupleType.TupleElements[index];
                        }
                        return tupleType.GetTupleMemberSymbolForUnderlyingMember(((TupleFieldSymbol)symbol).UnderlyingField);
                    }
                case SymbolKind.Property:
                    return tupleType.GetTupleMemberSymbolForUnderlyingMember(((TuplePropertySymbol)symbol).UnderlyingProperty);
                case SymbolKind.Event:
                    return tupleType.GetTupleMemberSymbolForUnderlyingMember(((TupleEventSymbol)symbol).UnderlyingEvent);
3386 3387
                case SymbolKind.Method:
                    return tupleType.GetTupleMemberSymbolForUnderlyingMember(((TupleMethodSymbol)symbol).UnderlyingMethod);
3388 3389 3390 3391 3392
                default:
                    throw ExceptionUtilities.UnexpectedValue(symbol.Kind);
            }
        }

3393 3394
        public override BoundNode VisitConversion(BoundConversion node)
        {
3395
            // https://github.com/dotnet/roslyn/issues/29959 Assert VisitConversion is only used for explicit conversions.
3396 3397 3398
            //Debug.Assert(node.ExplicitCastInCode);
            //Debug.Assert(node.ConversionGroupOpt != null);
            //Debug.Assert(!node.ConversionGroupOpt.ExplicitType.IsNull);
3399

3400
            TypeSymbolWithAnnotations explicitType = node.ConversionGroupOpt?.ExplicitType ?? default;
3401
            bool fromExplicitCast = !explicitType.IsNull;
3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412
            TypeSymbolWithAnnotations targetType = fromExplicitCast ? explicitType : TypeSymbolWithAnnotations.Create(node.Type);
            Debug.Assert(!targetType.IsNull);

            (BoundExpression operand, Conversion conversion) = RemoveConversion(node, includeExplicitConversions: true);
            TypeSymbolWithAnnotations operandType = VisitRvalueWithResult(operand);
            _resultType = ApplyConversion(
                node,
                operand,
                conversion,
                targetType,
                operandType,
3413
                checkConversion: true,
3414 3415 3416 3417
                fromExplicitCast: fromExplicitCast,
                useLegacyWarnings: fromExplicitCast,
                AssignmentKind.Assignment,
                reportTopLevelWarnings: fromExplicitCast,
3418
                reportRemainingWarnings: true);
3419 3420 3421 3422 3423 3424 3425 3426 3427

            switch (node.ConversionKind)
            {
                case ConversionKind.ImplicitNullable:
                case ConversionKind.ExplicitNullable:
                    TrackNullableStateIfNullableConversion(node);
                    break;
            }

3428 3429
            return null;
        }
3430

3431 3432 3433 3434 3435 3436 3437 3438 3439
        /// <summary>
        /// Visit an expression. If an explicit target type is provided, the expression is converted
        /// to that type. This method should be called whenever an expression may contain
        /// an implicit conversion, even if that conversion was omitted from the bound tree,
        /// so the conversion can be re-classified with nullability.
        /// </summary>
        private TypeSymbolWithAnnotations VisitOptionalImplicitConversion(BoundExpression expr, TypeSymbolWithAnnotations targetTypeOpt, bool useLegacyWarnings, AssignmentKind assignmentKind)
        {
            if (targetTypeOpt.IsNull)
3440
            {
3441
                return VisitRvalueWithResult(expr);
3442
            }
3443

3444 3445
            (BoundExpression operand, Conversion conversion) = RemoveConversion(expr, includeExplicitConversions: false);
            var operandType = VisitRvalueWithResult(operand);
3446 3447 3448 3449 3450
            // If an explicit conversion was used in place of an implicit conversion, the explicit
            // conversion was created by initial binding after reporting "error CS0266:
            // Cannot implicitly convert type '...' to '...'. An explicit conversion exists ...".
            // Since an error was reported, we don't need to report nested warnings as well.
            bool reportNestedWarnings = !conversion.IsExplicit;
3451
            var resultType = ApplyConversion(
3452 3453 3454 3455 3456 3457 3458 3459
                expr,
                operand,
                conversion,
                targetTypeOpt,
                operandType,
                checkConversion: true,
                fromExplicitCast: false,
                useLegacyWarnings: useLegacyWarnings,
3460 3461
                assignmentKind,
                reportTopLevelWarnings: true,
3462
                reportRemainingWarnings: reportNestedWarnings);
3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486

            var conv = expr as BoundConversion;
            if (conv != null && conv.ConversionKind == ConversionKind.ImplicitNullable)
            {
                TrackNullableStateIfNullableConversion(conv);
            }

            return resultType;
        }

        private static bool AreNullableAndUnderlyingTypes(TypeSymbol nullableTypeOpt, TypeSymbol underlyingTypeOpt, out TypeSymbolWithAnnotations underlyingTypeWithAnnotations)
        {
            if (nullableTypeOpt?.IsNullableType() == true &&
                underlyingTypeOpt?.IsNullableType() == false)
            {
                var typeArg = nullableTypeOpt.GetNullableUnderlyingTypeWithAnnotations();
                if (typeArg.TypeSymbol.Equals(underlyingTypeOpt, TypeCompareKind.AllIgnoreOptions))
                {
                    underlyingTypeWithAnnotations = typeArg;
                    return true;
                }
            }
            underlyingTypeWithAnnotations = default;
            return false;
3487 3488
        }

3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503
        public override BoundNode VisitTupleLiteral(BoundTupleLiteral node)
        {
            VisitTupleExpression(node);
            return null;
        }

        public override BoundNode VisitConvertedTupleLiteral(BoundConvertedTupleLiteral node)
        {
            VisitTupleExpression(node);
            return null;
        }

        private void VisitTupleExpression(BoundTupleExpression node)
        {
            var arguments = node.Arguments;
3504
            ImmutableArray<TypeSymbolWithAnnotations> elementTypes = arguments.SelectAsArray((a, w) => w.VisitRvalueWithResult(a), this);
3505
            var tupleOpt = (TupleTypeSymbol)node.Type;
3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517
            if (tupleOpt is null)
            {
                _resultType = default;
            }
            else
            {
                int slot = GetOrCreateObjectCreationPlaceholderSlot(node);
                if (slot > 0)
                {
                    this.State[slot] = NullableAnnotation.NotNullable;
                    TrackNullableStateOfTupleElements(slot, tupleOpt, arguments, elementTypes, useRestField: false);
                }
3518 3519

                tupleOpt = tupleOpt.WithElementTypes(elementTypes);
J
Jared Parsons 已提交
3520
                var locations = tupleOpt.TupleElements.SelectAsArray((element, location) => element.Locations.FirstOrDefault() ?? location, node.Syntax.Location);
3521
                tupleOpt.CheckConstraints(_conversions, includeNullability: true, node.Syntax, locations, compilation, diagnosticsOpt: null, nullabilityDiagnosticsOpt: Diagnostics);
3522
                _resultType = TypeSymbolWithAnnotations.Create(tupleOpt, NullableAnnotation.NotNullable);
3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564
            }
        }

        /// <summary>
        /// Set the nullability of tuple elements for tuples at the point of construction.
        /// If <paramref name="useRestField"/> is true, the tuple was constructed with an explicit
        /// 'new ValueTuple' call, in which case the 8-th element, if any, represents the 'Rest' field.
        /// </summary>
        private void TrackNullableStateOfTupleElements(
            int slot,
            TupleTypeSymbol tupleType,
            ImmutableArray<BoundExpression> values,
            ImmutableArray<TypeSymbolWithAnnotations> types,
            bool useRestField)
        {
            Debug.Assert(values.Length == types.Length);
            Debug.Assert(values.Length == (useRestField ? Math.Min(tupleType.TupleElements.Length, TupleTypeSymbol.RestPosition) : tupleType.TupleElements.Length));

            if (slot > 0)
            {
                var tupleElements = tupleType.TupleElements;
                int n = values.Length;
                if (useRestField)
                {
                    n = Math.Min(n, TupleTypeSymbol.RestPosition - 1);
                }
                for (int i = 0; i < n; i++)
                {
                    trackState(values[i], tupleElements[i], types[i]);
                }
                if (useRestField && values.Length == TupleTypeSymbol.RestPosition)
                {
                    var restField = tupleType.GetMembers(TupleTypeSymbol.RestFieldName).FirstOrDefault() as FieldSymbol;
                    if ((object)restField != null)
                    {
                        trackState(values.Last(), restField, types.Last());
                    }
                }
            }

            void trackState(BoundExpression value, FieldSymbol field, TypeSymbolWithAnnotations valueType) =>
                TrackNullableStateForAssignment(value, field.Type, GetOrCreateSlot(field, slot), valueType, MakeSlot(value));
3565 3566
        }

3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600
        private void TrackNullableStateOfNullableValue(int containingSlot, TypeSymbol containingType, BoundExpression value, TypeSymbolWithAnnotations valueType, int valueSlot)
        {
            Debug.Assert(containingType.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T);
            Debug.Assert(containingSlot > 0);
            Debug.Assert(valueSlot > 0);

            int targetSlot = GetNullableOfTValueSlot(containingType, containingSlot, out Symbol symbol);
            Debug.Assert(targetSlot > 0);
            if (targetSlot > 0)
            {
                TrackNullableStateForAssignment(value, symbol.GetTypeOrReturnType(), targetSlot, valueType, valueSlot);
            }
        }

        private void TrackNullableStateIfNullableConversion(BoundConversion node)
        {
            Debug.Assert(node.ConversionKind == ConversionKind.ImplicitNullable || node.ConversionKind == ConversionKind.ExplicitNullable);

            var operand = node.Operand;
            var operandType = operand.Type;
            var convertedType = node.Type;
            if (AreNullableAndUnderlyingTypes(convertedType, operandType, out TypeSymbolWithAnnotations underlyingType))
            {
                // Conversion of T to Nullable<T> is equivalent to new Nullable<T>(t).
                int valueSlot = MakeSlot(operand);
                if (valueSlot > 0)
                {
                    int containingSlot = GetOrCreateObjectCreationPlaceholderSlot(node);
                    Debug.Assert(containingSlot > 0);
                    TrackNullableStateOfNullableValue(containingSlot, convertedType, operand, underlyingType, valueSlot);
                }
            }
        }

C
Charles Stoner 已提交
3601 3602 3603 3604 3605 3606 3607
        public override BoundNode VisitTupleBinaryOperator(BoundTupleBinaryOperator node)
        {
            base.VisitTupleBinaryOperator(node);
            SetResult(node);
            return null;
        }

3608 3609
        private void ReportNullabilityMismatchWithTargetDelegate(SyntaxNode syntax, NamedTypeSymbol delegateType, MethodSymbol method)
        {
3610 3611
            Debug.Assert((object)method != null);
            Debug.Assert(method.MethodKind != MethodKind.LambdaMethod);
3612

3613 3614
            MethodSymbol invoke = delegateType?.DelegateInvokeMethod;
            if (invoke is null)
3615 3616 3617 3618
            {
                return;
            }

3619
            if (IsNullabilityMismatch(method.ReturnType, invoke.ReturnType, requireIdentity: false))
3620
            {
3621
                ReportSafetyDiagnostic(ErrorCode.WRN_NullabilityMismatchInReturnTypeOfTargetDelegate, syntax,
3622
                    new FormattedSymbol(method, SymbolDisplayFormat.MinimallyQualifiedFormat),
3623 3624 3625 3626 3627 3628
                    delegateType);
            }

            int count = Math.Min(invoke.ParameterCount, method.ParameterCount);
            for (int i = 0; i < count; i++)
            {
3629 3630 3631
                var invokeParameter = invoke.Parameters[i];
                var methodParameter = method.Parameters[i];
                if (IsNullabilityMismatch(invokeParameter.Type, methodParameter.Type, requireIdentity: invokeParameter.RefKind != RefKind.None))
3632
                {
3633
                    ReportSafetyDiagnostic(ErrorCode.WRN_NullabilityMismatchInParameterTypeOfTargetDelegate, syntax,
3634
                        new FormattedSymbol(methodParameter, SymbolDisplayFormat.ShortFormat),
3635
                        new FormattedSymbol(method, SymbolDisplayFormat.MinimallyQualifiedFormat),
3636 3637 3638 3639 3640
                        delegateType);
                }
            }
        }

3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 3657 3658 3659 3660
        private void ReportNullabilityMismatchWithTargetDelegate(SyntaxNode syntax, NamedTypeSymbol delegateType, UnboundLambda unboundLambda)
        {
            if (!unboundLambda.HasExplicitlyTypedParameterList)
            {
                return;
            }

            MethodSymbol invoke = delegateType?.DelegateInvokeMethod;
            if (invoke is null)
            {
                return;
            }

            int count = Math.Min(invoke.ParameterCount, unboundLambda.ParameterCount);
            for (int i = 0; i < count; i++)
            {
                var invokeParameter = invoke.Parameters[i];
                // Parameter nullability is expected to match exactly. This corresponds to the behavior of initial binding.
                //    Action<string> x = (object o) => { }; // error CS1661: Cannot convert lambda expression to delegate type 'Action<string>' because the parameter types do not match the delegate parameter types
                //    Action<object> y = (object? o) => { }; // warning CS8622: Nullability of reference types in type of parameter 'o' of 'lambda expression' doesn't match the target delegate 'Action<object>'.
3661
                // https://github.com/dotnet/roslyn/issues/29959 Consider relaxing and allow implicit conversions of nullability.
3662 3663 3664
                // (Compare with method group conversions which pass `requireIdentity: false`.)
                if (IsNullabilityMismatch(invokeParameter.Type, unboundLambda.ParameterType(i), requireIdentity: true))
                {
3665
                    // https://github.com/dotnet/roslyn/issues/29959 Consider using location of specific lambda parameter.
3666
                    ReportSafetyDiagnostic(ErrorCode.WRN_NullabilityMismatchInParameterTypeOfTargetDelegate, syntax,
3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689
                        unboundLambda.ParameterName(i),
                        unboundLambda.MessageID.Localize(),
                        delegateType);
                }
            }
        }

        private bool IsNullabilityMismatch(TypeSymbolWithAnnotations source, TypeSymbolWithAnnotations destination, bool requireIdentity)
        {
            if (!HasTopLevelNullabilityConversion(source, destination, requireIdentity))
            {
                return true;
            }
            if (requireIdentity)
            {
                return IsNullabilityMismatch(source, destination);
            }
            var sourceType = source.TypeSymbol;
            var destinationType = destination.TypeSymbol;
            HashSet<DiagnosticInfo> useSiteDiagnostics = null;
            return !_conversions.ClassifyImplicitConversionFromType(sourceType, destinationType, ref useSiteDiagnostics).Exists;
        }

3690
        private bool HasTopLevelNullabilityConversion(TypeSymbolWithAnnotations source, TypeSymbolWithAnnotations destination, bool requireIdentity)
3691 3692
        {
            return requireIdentity ?
3693 3694
                _conversions.HasTopLevelNullabilityIdentityConversion(source, destination) :
                _conversions.HasTopLevelNullabilityImplicitConversion(source, destination);
3695 3696
        }

3697 3698 3699 3700 3701 3702 3703 3704 3705 3706
        /// <summary>
        /// Apply the conversion to the type of the operand and return the resulting type. (If the
        /// operand does not have an explicit type, the operand expression is used for the type.)
        /// If `checkConversion` is set, the incoming conversion is assumed to be from binding and will be
        /// re-calculated, this time considering nullability. (Note that the conversion calculation considers
        /// nested nullability only. The caller is responsible for checking the top-level nullability of
        /// the type returned by this method.) `canConvertNestedNullability` is set if the conversion
        /// considering nested nullability succeeded. `node` is used only for the location of diagnostics.
        /// </summary>
        private TypeSymbolWithAnnotations ApplyConversion(
3707
            BoundExpression node,
3708 3709
            BoundExpression operandOpt,
            Conversion conversion,
3710
            TypeSymbolWithAnnotations targetTypeWithNullability,
3711 3712 3713
            TypeSymbolWithAnnotations operandType,
            bool checkConversion,
            bool fromExplicitCast,
3714 3715 3716 3717
            bool useLegacyWarnings,
            AssignmentKind assignmentKind,
            ParameterSymbol target = null,
            bool reportTopLevelWarnings = true,
3718
            bool reportRemainingWarnings = true,
3719
            bool extensionMethodThisArgument = false)
3720 3721
        {
            Debug.Assert(node != null);
3722
            Debug.Assert(operandOpt != null || !operandType.IsNull);
3723
            Debug.Assert(!targetTypeWithNullability.IsNull);
3724
            Debug.Assert((object)target != null || assignmentKind != AssignmentKind.Argument);
3725

3726 3727
            NullableAnnotation resultAnnotation = NullableAnnotation.Unknown;
            bool forceOperandAnnotationForResult = false;
3728
            bool canConvertNestedNullability = true;
3729
            bool isSuppressed = false;
3730

3731
            if (operandOpt?.IsSuppressed == true)
3732 3733 3734
            {
                reportTopLevelWarnings = false;
                reportRemainingWarnings = false;
3735
                isSuppressed = true;
3736 3737
            }

3738
            TypeSymbol targetType = targetTypeWithNullability.TypeSymbol;
3739
            switch (conversion.Kind)
3740
            {
3741
                case ConversionKind.MethodGroup:
3742
                    if (reportRemainingWarnings)
3743
                    {
3744
                        ReportNullabilityMismatchWithTargetDelegate(node.Syntax, targetType.GetDelegateType(), conversion.Method);
3745
                    }
3746
                    resultAnnotation = NullableAnnotation.NotNullable;
3747 3748
                    break;

3749
                case ConversionKind.AnonymousFunction:
3750 3751 3752 3753 3754 3755 3756 3757 3758
                    if (operandOpt.Kind == BoundKind.Lambda)
                    {
                        var lambda = (BoundLambda)operandOpt;
                        var delegateType = targetType.GetDelegateType();
                        var methodSignatureOpt = lambda.UnboundLambda.HasExplicitlyTypedParameterList ? null : delegateType?.DelegateInvokeMethod;
                        var variableState = GetVariableState();
                        Analyze(compilation, lambda, Diagnostics, delegateInvokeMethod: delegateType?.DelegateInvokeMethod, returnTypes: null, initialState: variableState);
                        var unboundLambda = GetUnboundLambda(lambda, variableState);
                        var boundLambda = unboundLambda.Bind(delegateType);
3759
                        if (reportRemainingWarnings)
3760 3761 3762
                        {
                            ReportNullabilityMismatchWithTargetDelegate(node.Syntax, delegateType, unboundLambda);
                        }
3763
                        return TypeSymbolWithAnnotations.Create(targetType, NullableAnnotation.NotNullable);
3764 3765 3766
                    }
                    break;

3767
                case ConversionKind.InterpolatedString:
3768
                    resultAnnotation = NullableAnnotation.NotNullable;
3769
                    break;
3770

3771 3772
                case ConversionKind.ExplicitUserDefined:
                case ConversionKind.ImplicitUserDefined:
3773
                    // cf. Binder.CreateUserDefinedConversion
3774
                    {
3775
                        if (!conversion.IsValid)
3776 3777 3778
                        {
                            break;
                        }
3779 3780

                        // operand -> conversion "from" type
3781
                        // May be distinct from method parameter type for Nullable<T>.
3782 3783 3784 3785
                        operandType = ApplyConversion(
                            node,
                            operandOpt,
                            conversion.UserDefinedFromConversion,
3786
                            TypeSymbolWithAnnotations.Create(conversion.BestUserDefinedConversionAnalysis.FromType),
3787
                            operandType,
3788
                            checkConversion: true,
3789
                            fromExplicitCast: false,
3790
                            useLegacyWarnings,
3791 3792
                            assignmentKind,
                            target);
3793

3794
                        // Update method based on operandType: see https://github.com/dotnet/roslyn/issues/29605.
3795
                        // (see NullableReferenceTypesTests.ImplicitConversions_07).
3796 3797 3798 3799
                        var methodOpt = conversion.Method;
                        Debug.Assert((object)methodOpt != null);
                        Debug.Assert(methodOpt.ParameterCount == 1);
                        var parameter = methodOpt.Parameters[0];
3800 3801 3802 3803 3804 3805
                        var parameterType = parameter.Type;
                        TypeSymbolWithAnnotations underlyingOperandType = default;
                        bool isLiftedConversion =
                            operandType.IsNullableType() &&
                            !parameterType.IsNullableType() &&
                            parameterType.Equals(underlyingOperandType = operandType.GetNullableUnderlyingType(), TypeCompareKind.AllIgnoreOptions);
3806 3807

                        // conversion "from" type -> method parameter type
3808
                        NullableAnnotation operandAnnotation = operandType.NullableAnnotation;
3809
                        _ = ClassifyAndApplyConversion(operandOpt ?? node, parameterType, isLiftedConversion ? underlyingOperandType : operandType,
3810
                            useLegacyWarnings, AssignmentKind.Argument, target: parameter, reportWarnings: reportRemainingWarnings);
3811 3812

                        // method parameter type -> method return type
3813 3814 3815 3816
                        var methodReturnType = methodOpt.ReturnType;
                        if (isLiftedConversion)
                        {
                            operandType = TypeSymbolWithAnnotations.Create(
D
dotnet-bot 已提交
3817
                                methodReturnType.IsValueType && !methodReturnType.IsNullableType() ?
3818 3819 3820 3821 3822 3823 3824 3825 3826 3827
                                    compilation.GetSpecialType(SpecialType.System_Nullable_T).Construct(ImmutableArray.Create(methodReturnType)) :
                                    methodReturnType.TypeSymbol,
                                methodReturnType.NullableAnnotation.IsAnyNullable() || operandAnnotation.IsAnyNullable() ?
                                    NullableAnnotation.Nullable :
                                    (methodReturnType.IsPossiblyNullableReferenceTypeTypeParameter() ? methodReturnType.NullableAnnotation : NullableAnnotation.NotNullable));
                        }
                        else
                        {
                            operandType = methodReturnType;
                        }
3828 3829

                        // method return type -> conversion "to" type
3830
                        // May be distinct from method return type for Nullable<T>.
3831 3832
                        operandType = ClassifyAndApplyConversion(operandOpt ?? node, TypeSymbolWithAnnotations.Create(conversion.BestUserDefinedConversionAnalysis.ToType), operandType,
                            useLegacyWarnings, assignmentKind, target, reportWarnings: reportRemainingWarnings);
3833 3834

                        // conversion "to" type -> final type
3835
                        // https://github.com/dotnet/roslyn/issues/29959 If the original conversion was
3836
                        // explicit, this conversion should not report nested nullability mismatches.
3837
                        // (see NullableReferenceTypesTests.ExplicitCast_UserDefined_02).
3838 3839
                        operandType = ClassifyAndApplyConversion(node, targetTypeWithNullability, operandType,
                            useLegacyWarnings, assignmentKind, target, reportWarnings: reportRemainingWarnings);
3840
                        return operandType;
3841
                    }
3842

3843 3844
                case ConversionKind.ExplicitDynamic:
                case ConversionKind.ImplicitDynamic:
3845
                    resultAnnotation = operandType.IsNull ? NullableAnnotation.Unknown : operandType.NullableAnnotation;
3846
                    if (resultAnnotation == NullableAnnotation.NotAnnotated && targetType.IsTypeParameter())
3847
                    {
3848
                        resultAnnotation = NullableAnnotation.NotNullable;
3849
                    }
3850 3851 3852 3853 3854 3855 3856 3857
                    else if (targetType.IsValueType)
                    {
                        Debug.Assert(!operandType.IsNull); // If assert fails, add a test that verifies resulting type is nullable.
                        resultAnnotation = (targetType.IsNullableType() && (operandType.IsNull || operandType.NullableAnnotation.IsAnyNullable())) ? NullableAnnotation.Nullable : NullableAnnotation.NotNullable;
                    }
                    break;

                case ConversionKind.ImplicitThrow:
3858 3859 3860
                    break;

                case ConversionKind.Unboxing:
3861 3862 3863 3864 3865 3866
                    if (targetType.IsValueType)
                    {
                        Debug.Assert(!operandType.IsNull); // If assert fails, add a test that verifies resulting type is nullable.
                        resultAnnotation = (targetType.IsNullableTypeOrTypeParameter() && (operandType.IsNull || operandType.NullableAnnotation.IsAnyNullable())) ? NullableAnnotation.Nullable : NullableAnnotation.NotNullable;
                    }
                    else if (!operandType.IsNull && targetType.IsTypeParameter())
3867 3868
                    {
                        resultAnnotation = operandType.GetValueNullableAnnotation();
3869

3870
                        if (resultAnnotation == NullableAnnotation.NotAnnotated)
3871
                        {
3872
                            resultAnnotation = NullableAnnotation.NotNullable;
3873
                        }
3874 3875 3876
                    }
                    break;

3877
                case ConversionKind.Boxing:
3878
                    if (!operandType.IsNull)
3879
                    {
3880
                        if (operandType.IsValueType)
3881
                        {
3882 3883
                            resultAnnotation = (operandType.IsNullableTypeOrTypeParameter() && operandType.GetValueNullableAnnotation().IsAnyNullable()) ? NullableAnnotation.Nullable : NullableAnnotation.NotNullable;
                            break;
3884
                        }
3885
                        else if (IsTypeParameterDisallowingAnnotation(operandType.TypeSymbol))
3886
                        {
3887 3888 3889 3890 3891 3892 3893 3894 3895 3896
                            if (operandType.IsPossiblyNullableReferenceTypeTypeParameter() && !targetTypeWithNullability.IsPossiblyNullableReferenceTypeTypeParameter())
                            {
                                resultAnnotation = NullableAnnotation.Nullable;
                                forceOperandAnnotationForResult = targetType.IsPossiblyNullableReferenceTypeTypeParameter();
                            }
                            else
                            {
                                resultAnnotation = operandType.NullableAnnotation;
                            }
                            break;
3897
                        }
3898
                    }
3899 3900 3901 3902 3903
                    Debug.Assert(operandType.IsNull ||
                        !operandType.IsReferenceType ||
                        operandType.SpecialType == SpecialType.System_ValueType ||
                        operandType.TypeKind == TypeKind.Interface ||
                        operandType.TypeKind == TypeKind.Dynamic);
3904
                    break;
3905

3906
                case ConversionKind.NoConversion:
3907
                case ConversionKind.DefaultOrNullLiteral:
3908 3909 3910 3911
                    checkConversion = false;
                    goto case ConversionKind.Identity;

                case ConversionKind.Identity:
3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924
                    // If the operand is an explicit conversion, and this identity conversion
                    // is converting to the same type including nullability, skip the conversion
                    // to avoid reporting redundant warnings. Also check useLegacyWarnings
                    // since that value was used when reporting warnings for the explicit cast.
                    if (useLegacyWarnings && operandOpt?.Kind == BoundKind.Conversion)
                    {
                        var operandConversion = (BoundConversion)operandOpt;
                        var explicitType = operandConversion.ConversionGroupOpt.ExplicitType;
                        if (!explicitType.IsNull && explicitType.Equals(targetTypeWithNullability, TypeCompareKind.IgnoreInsignificantNullableModifiersDifference))
                        {
                            return operandType;
                        }
                    }
3925 3926 3927 3928
                    if (operandType.TypeSymbol?.IsTupleType == true)
                    {
                        goto case ConversionKind.ImplicitTuple;
                    }
3929 3930
                    goto case ConversionKind.ImplicitReference;

3931 3932
                case ConversionKind.ImplicitReference:
                case ConversionKind.ExplicitReference:
3933
                    if (operandType.IsNull && operandOpt.IsLiteralNullOrDefault())
3934
                    {
3935
                        resultAnnotation = NullableAnnotation.Nullable;
F
Fredric Silberberg 已提交
3936
                    }
3937 3938 3939 3940 3941
                    else
                    {
                        // Inherit state from the operand.
                        if (checkConversion)
                        {
3942
                            // https://github.com/dotnet/roslyn/issues/29959 Assert conversion is similar to original.
3943
                            conversion = GenerateConversion(_conversions, operandOpt, operandType.TypeSymbol, targetType, fromExplicitCast, extensionMethodThisArgument);
3944 3945
                            canConvertNestedNullability = conversion.Exists;
                        }
3946 3947 3948 3949 3950

                        if (operandType.IsNull)
                        {
                            resultAnnotation = NullableAnnotation.Unknown;
                        }
3951
                        else if (operandType.IsPossiblyNullableReferenceTypeTypeParameter())
3952
                        {
3953 3954
                            if (conversion.Kind == ConversionKind.ExplicitReference)
                            {
3955
                                resultAnnotation = NullableAnnotation.Nullable;
3956 3957 3958
                            }
                            else if (!targetTypeWithNullability.IsPossiblyNullableReferenceTypeTypeParameter())
                            {
3959
                                resultAnnotation = NullableAnnotation.Nullable;
3960 3961 3962 3963 3964 3965
                                forceOperandAnnotationForResult = targetType.IsPossiblyNullableReferenceTypeTypeParameter();
                            }
                            else
                            {
                                resultAnnotation = operandType.NullableAnnotation;
                            }
3966 3967 3968 3969
                        }
                        else
                        {
                            resultAnnotation = operandType.NullableAnnotation;
3970
                            if (resultAnnotation == NullableAnnotation.NotAnnotated && targetType.IsTypeParameter())
3971
                            {
3972
                                resultAnnotation = NullableAnnotation.NotNullable;
3973
                            }
3974
                        }
3975
                    }
3976
                    break;
3977

3978
                case ConversionKind.ImplicitNullable:
3979 3980 3981 3982 3983 3984 3985 3986 3987 3988 3989 3990 3991 3992 3993 3994
                    if (checkConversion)
                    {
                        conversion = GenerateConversion(_conversions, operandOpt, operandType.TypeSymbol, targetType, fromExplicitCast, extensionMethodThisArgument);
                        canConvertNestedNullability = conversion.Exists;
                    }
                    if ((targetType.IsValueType && !targetType.IsNullableType()) ||
                        (operandType.IsValueType && !operandType.IsNullableType()))
                    {
                        resultAnnotation = NullableAnnotation.NotNullable;
                    }
                    else
                    {
                        resultAnnotation = operandType.NullableAnnotation.IsAnyNullable() ? NullableAnnotation.Nullable : NullableAnnotation.NotNullable;
                    }
                    break;

3995
                case ConversionKind.ExplicitNullable:
3996 3997 3998 3999 4000 4001
                    if (operandType.TypeSymbol?.IsNullableType() == true &&
                        !targetType.IsNullableType())
                    {
                        // Explicit conversion of Nullable<T> to T is equivalent to Nullable<T>.Value.
                        if (reportTopLevelWarnings && operandType.NullableAnnotation.IsAnyNullable())
                        {
4002
                            ReportSafetyDiagnostic(ErrorCode.WRN_NullableValueTypeMayBeNull, node.Syntax);
4003 4004 4005 4006 4007 4008 4009 4010 4011 4012 4013 4014 4015 4016
                        }
                        // Mark the value as not nullable, regardless of whether it was known to be nullable,
                        // because the implied call to `.Value` will only succeed if not null.
                        if (operandOpt != null)
                        {
                            int slot = MakeSlot(operandOpt);
                            if (slot > 0)
                            {
                                this.State[slot] = NullableAnnotation.NotNullable;
                            }
                        }
                    }
                    goto case ConversionKind.ImplicitNullable;

4017 4018 4019 4020 4021 4022 4023 4024 4025 4026
                case ConversionKind.ImplicitTupleLiteral:
                case ConversionKind.ImplicitTuple:
                case ConversionKind.ExplicitTupleLiteral:
                case ConversionKind.ExplicitTuple:
                    if (checkConversion)
                    {
                        // https://github.com/dotnet/roslyn/issues/29699: Report warnings for user-defined conversions on tuple elements.
                        conversion = GenerateConversion(_conversions, operandOpt, operandType.TypeSymbol, targetType, fromExplicitCast, extensionMethodThisArgument);
                        canConvertNestedNullability = conversion.Exists;
                    }
4027
                    resultAnnotation = NullableAnnotation.NotNullable;
4028 4029
                    break;

4030 4031 4032 4033
                case ConversionKind.Deconstruction:
                    // Can reach here, with an error type, when the
                    // Deconstruct method is missing or inaccessible.
                    break;
4034

4035 4036 4037
                case ConversionKind.ExplicitEnumeration:
                    // Can reach here, with an error type.
                    break;
4038

4039
                default:
F
Fredric Silberberg 已提交
4040
                    Debug.Assert(targetType.IsValueType);
4041
                    break;
4042
            }
4043

4044 4045 4046 4047 4048
            if (isSuppressed)
            {
                resultAnnotation = NullableAnnotation.NotNullable;
            }

4049
            var resultType = TypeSymbolWithAnnotations.Create(targetType, resultAnnotation);
4050

4051
            if (operandType.TypeSymbol?.IsErrorType() != true && !targetType.IsErrorType())
4052
            {
4053 4054
                // Need to report all warnings that apply since the warnings can be suppressed individually.
                if (reportTopLevelWarnings)
4055
                {
4056
                    ReportNullableAssignmentIfNecessary(node, targetTypeWithNullability, resultType, useLegacyWarnings: useLegacyWarnings, assignmentKind, target);
4057
                }
4058
                if (reportRemainingWarnings && !canConvertNestedNullability)
4059
                {
4060 4061 4062 4063 4064 4065
                    if (assignmentKind == AssignmentKind.Argument)
                    {
                        ReportNullabilityMismatchInArgument(node, operandType.TypeSymbol, target, targetType);
                    }
                    else
                    {
4066
                        ReportNullabilityMismatchInAssignment(node.Syntax, GetTypeAsDiagnosticArgument(operandType.TypeSymbol), targetType);
4067
                    }
4068 4069 4070
                }
            }

4071 4072 4073 4074 4075
            if (forceOperandAnnotationForResult)
            {
                resultType = TypeSymbolWithAnnotations.Create(targetType, operandType.NullableAnnotation);
            }

4076
            return resultType;
4077 4078
        }

4079
        private TypeSymbolWithAnnotations ClassifyAndApplyConversion(BoundExpression node, TypeSymbolWithAnnotations targetType, TypeSymbolWithAnnotations operandType,
4080
            bool useLegacyWarnings, AssignmentKind assignmentKind, ParameterSymbol target, bool reportWarnings)
4081
        {
4082
            Debug.Assert((object)target != null || assignmentKind != AssignmentKind.Argument);
4083
            HashSet<DiagnosticInfo> useSiteDiagnostics = null;
4084
            var conversion = _conversions.ClassifyStandardConversion(null, operandType.TypeSymbol, targetType.TypeSymbol, ref useSiteDiagnostics);
4085
            if (reportWarnings && !conversion.Exists)
4086
            {
4087 4088 4089 4090 4091 4092
                if (assignmentKind == AssignmentKind.Argument)
                {
                    ReportNullabilityMismatchInArgument(node, operandType.TypeSymbol, target, targetType.TypeSymbol);
                }
                else
                {
4093
                    ReportNullabilityMismatchInAssignment(node.Syntax, operandType.TypeSymbol, targetType.TypeSymbol);
4094
                }
4095
            }
4096

4097 4098 4099 4100 4101 4102 4103 4104 4105 4106
            return ApplyConversion(
                node,
                operandOpt: null,
                conversion,
                targetType,
                operandType,
                checkConversion: false,
                fromExplicitCast: false,
                useLegacyWarnings: useLegacyWarnings,
                assignmentKind,
4107 4108 4109
                target,
                reportTopLevelWarnings: reportWarnings,
                reportRemainingWarnings: reportWarnings);
4110 4111
        }

4112 4113 4114 4115 4116 4117 4118 4119 4120
        public override BoundNode VisitDelegateCreationExpression(BoundDelegateCreationExpression node)
        {
            if (node.MethodOpt?.MethodKind == MethodKind.LocalFunction)
            {
                var syntax = node.Syntax;
                var localFunc = (LocalFunctionSymbol)node.MethodOpt.OriginalDefinition;
                ReplayReadsAndWrites(localFunc, syntax, writes: false);
            }

4121
            base.VisitDelegateCreationExpression(node);
4122
            _resultType = TypeSymbolWithAnnotations.Create(node.Type, NullableAnnotation.NotNullable);
4123
            return null;
4124 4125 4126 4127 4128 4129 4130 4131 4132 4133
        }

        public override BoundNode VisitMethodGroup(BoundMethodGroup node)
        {
            Debug.Assert(!IsConditionalState);

            BoundExpression receiverOpt = node.ReceiverOpt;
            if (receiverOpt != null)
            {
                VisitRvalue(receiverOpt);
4134 4135 4136 4137
                // https://github.com/dotnet/roslyn/issues/30563: Should not check receiver here.
                // That check should be handled when applying the method group conversion,
                // when we have a specific method, to avoid reporting null receiver warnings
                // for extension method delegates.
4138 4139 4140
                CheckPossibleNullReceiver(receiverOpt);
            }

4141
            //if (this.State.Reachable) // Consider reachability: see https://github.com/dotnet/roslyn/issues/28798
4142
            {
4143
                _resultType = default;
4144 4145 4146 4147 4148 4149 4150
            }

            return null;
        }

        public override BoundNode VisitLambda(BoundLambda node)
        {
4151 4152
            SetResult(node);
            return null;
4153 4154 4155 4156
        }

        public override BoundNode VisitUnboundLambda(UnboundLambda node)
        {
4157 4158 4159 4160
            // The presence of this node suggests an error was detected in an earlier phase.
            // Analyze the body to report any additional warnings.
            var lambda = node.BindForErrorRecovery();
            Analyze(compilation, lambda, Diagnostics, delegateInvokeMethod: null, returnTypes: null, initialState: GetVariableState());
4161
            SetResult(node);
4162 4163 4164
            return null;
        }

4165 4166 4167 4168 4169 4170 4171 4172 4173 4174 4175 4176 4177 4178 4179 4180 4181
        public override BoundNode VisitLocalFunctionStatement(BoundLocalFunctionStatement node)
        {
            var body = node.Body;
            if (body != null)
            {
                Analyze(
                    compilation,
                    node.Symbol,
                    body,
                    Diagnostics,
                    useMethodSignatureReturnType: false,
                    useMethodSignatureParameterTypes: false,
                    methodSignatureOpt: null,
                    returnTypes: null,
                    initialState: GetVariableState(),
                    callbackOpt: _callbackOpt);
            }
4182
            _resultType = _invalidType;
4183 4184 4185
            return null;
        }

4186 4187
        public override BoundNode VisitThisReference(BoundThisReference node)
        {
4188
            VisitThisOrBaseReference(node);
4189 4190 4191
            return null;
        }

4192 4193
        private void VisitThisOrBaseReference(BoundExpression node)
        {
4194
            _resultType = TypeSymbolWithAnnotations.Create(node.Type, NullableAnnotation.NotNullable);
4195 4196
        }

4197 4198
        public override BoundNode VisitParameter(BoundParameter node)
        {
4199 4200 4201 4202
            var parameter = node.ParameterSymbol;
            int slot = GetOrCreateSlot(parameter);
            var type = GetDeclaredParameterResult(parameter);
            _resultType = GetAdjustedResult(type, slot);
4203 4204 4205 4206 4207 4208 4209
            return null;
        }

        public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node)
        {
            Debug.Assert(!IsConditionalState);

4210
            var left = node.Left;
4211
            var right = node.Right;
4212
            VisitLvalue(left);
4213
            TypeSymbolWithAnnotations leftType = _resultType;
4214

4215
            if (left.Kind == BoundKind.EventAccess && ((BoundEventAccess)left).EventSymbol.IsWindowsRuntimeEvent)
4216 4217 4218 4219
            {
                // Event assignment is a call to an Add method. (Note that assignment
                // of non-field-like events uses BoundEventAssignmentOperator
                // rather than BoundAssignmentOperator.)
4220
                VisitRvalue(right);
4221 4222 4223
                SetResult(node);
            }
            else
4224
            {
4225
                TypeSymbolWithAnnotations rightType = VisitOptionalImplicitConversion(right, leftType, UseLegacyWarnings(left), AssignmentKind.Assignment);
C
Charles Stoner 已提交
4226
                TrackNullableStateForAssignment(right, leftType, MakeSlot(left), rightType, MakeSlot(right));
4227
                // https://github.com/dotnet/roslyn/issues/30066 Check node.Type.IsErrorType() instead?
4228
                _resultType = node.HasErrors ? TypeSymbolWithAnnotations.Create(node.Type) : rightType;
4229 4230 4231 4232 4233
            }

            return null;
        }

4234 4235 4236 4237 4238 4239
        private static bool UseLegacyWarnings(BoundExpression expr)
        {
            switch (expr.Kind)
            {
                case BoundKind.Local:
                    return true;
4240 4241 4242
                case BoundKind.Parameter:
                    RefKind kind = ((BoundParameter)expr).ParameterSymbol.RefKind;
                    return kind == RefKind.None;
4243 4244 4245 4246 4247
                default:
                    return false;
            }
        }

4248 4249
        public override BoundNode VisitDeconstructionAssignmentOperator(BoundDeconstructionAssignmentOperator node)
        {
4250 4251 4252 4253 4254 4255 4256 4257 4258 4259 4260 4261 4262 4263 4264 4265 4266
            var left = node.Left;
            var right = node.Right;
            var variables = GetDeconstructionAssignmentVariables(left);

            if (node.HasErrors)
            {
                // In the case of errors, simply visit the right as an r-value to update
                // any nullability state even though deconstruction is skipped.
                VisitRvalue(right.Operand);
            }
            else
            {
                VisitDeconstructionArguments(variables, right.Conversion, right.Operand);
            }

            variables.FreeAll(v => v.NestedVariables);

4267 4268 4269
            // https://github.com/dotnet/roslyn/issues/33011: Result type should be inferred and the constraints should
            // be re-verified. Even though the standard tuple type has no constraints we support that scenario. Constraints_78
            // has a test for this case that should start failing when this is fixed.
4270
            SetResult(node);
4271 4272 4273
            return null;
        }

4274 4275 4276 4277 4278 4279 4280 4281 4282 4283 4284 4285 4286 4287 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297 4298 4299 4300 4301 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319 4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330
        private void VisitDeconstructionArguments(ArrayBuilder<DeconstructionVariable> variables, Conversion conversion, BoundExpression right)
        {
            Debug.Assert(conversion.Kind == ConversionKind.Deconstruction);

            int n = variables.Count;

            if (!conversion.DeconstructionInfo.IsDefault)
            {
                VisitRvalue(right);

                var invocation = conversion.DeconstructionInfo.Invocation as BoundCall;
                var deconstructMethod = invocation?.Method;

                if ((object)deconstructMethod != null)
                {
                    if (!invocation.InvokedAsExtensionMethod)
                    {
                        CheckPossibleNullReceiver(right);
                    }
                    else
                    {
                        // https://github.com/dotnet/roslyn/issues/33006: Check nullability of `this` argument.
                    }

                    // https://github.com/dotnet/roslyn/issues/33006: Update `Deconstruct` method
                    // based on inferred receiver type, and check constraints.

                    var parameters = deconstructMethod.Parameters;
                    int offset = invocation.InvokedAsExtensionMethod ? 1 : 0;
                    Debug.Assert(parameters.Length - offset == n);

                    for (int i = 0; i < n; i++)
                    {
                        var variable = variables[i];
                        var underlyingConversion = conversion.UnderlyingConversions[i];
                        var nestedVariables = variable.NestedVariables;
                        if (nestedVariables != null)
                        {
                            // https://github.com/dotnet/roslyn/issues/33005: Not handling deconstructing argument of Deconstruct.
                            //VisitDeconstructionArguments(nestedVariables, underlyingConversion, arg);
                        }
                        else
                        {
                            var parameter = parameters[i + offset];
                            VisitArgumentConversion(variable.Expression, underlyingConversion, parameter.RefKind, parameter, parameter.Type, variable.Type, extensionMethodThisArgument: false);
                        }
                    }
                }
            }
            else
            {
                var rightParts = GetDeconstructionRightParts(right);
                Debug.Assert(rightParts.Length == n);

                for (int i = 0; i < n; i++)
                {
                    var variable = variables[i];
C
Charles Stoner 已提交
4331
                    var underlyingConversion = conversion.UnderlyingConversions[i];
4332 4333 4334 4335
                    var rightPart = rightParts[i];
                    var nestedVariables = variable.NestedVariables;
                    if (nestedVariables != null)
                    {
C
Charles Stoner 已提交
4336
                        VisitDeconstructionArguments(nestedVariables, underlyingConversion, rightPart);
4337 4338 4339 4340
                    }
                    else
                    {
                        var targetType = variable.Type;
C
Charles Stoner 已提交
4341 4342 4343 4344 4345 4346 4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 4357 4358 4359 4360 4361 4362 4363 4364 4365 4366 4367
                        TypeSymbolWithAnnotations operandType;
                        TypeSymbolWithAnnotations valueType;
                        int valueSlot;
                        if (underlyingConversion.IsIdentity)
                        {
                            operandType = default;
                            valueType = VisitOptionalImplicitConversion(rightPart, targetType, useLegacyWarnings: true, AssignmentKind.Assignment);
                            valueSlot = MakeSlot(rightPart);
                        }
                        else
                        {
                            operandType = VisitRvalueWithResult(rightPart);
                            valueType = ApplyConversion(
                                rightPart,
                                rightPart,
                                underlyingConversion,
                                targetType,
                                operandType,
                                checkConversion: true,
                                fromExplicitCast: false,
                                useLegacyWarnings: true,
                                AssignmentKind.Assignment,
                                reportTopLevelWarnings: true,
                                reportRemainingWarnings: true);
                            valueSlot = -1;
                        }

4368
                        int targetSlot = MakeSlot(variable.Expression);
C
Charles Stoner 已提交
4369 4370 4371 4372 4373 4374 4375 4376 4377 4378 4379 4380 4381 4382
                        TrackNullableStateForAssignment(rightPart, targetType, targetSlot, valueType, valueSlot);

                        // Conversion of T to Nullable<T> is equivalent to new Nullable<T>(t).
                        // (Should this check be moved to VisitOptionalImplicitConversion or TrackNullableStateForAssignment?)
                        if (targetSlot > 0 &&
                            underlyingConversion.Kind == ConversionKind.ImplicitNullable &&
                            AreNullableAndUnderlyingTypes(targetType.TypeSymbol, operandType.TypeSymbol, out TypeSymbolWithAnnotations underlyingType))
                        {
                            valueSlot = MakeSlot(rightPart);
                            if (valueSlot > 0)
                            {
                                TrackNullableStateOfNullableValue(targetSlot, targetType.TypeSymbol, rightPart, underlyingType, valueSlot);
                            }
                        }
4383 4384 4385 4386 4387 4388 4389 4390 4391 4392 4393 4394 4395 4396 4397 4398 4399 4400 4401 4402 4403 4404 4405 4406 4407 4408 4409 4410 4411 4412 4413 4414 4415 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 4433 4434 4435 4436 4437 4438 4439 4440 4441 4442 4443 4444 4445 4446 4447 4448 4449 4450 4451 4452 4453 4454 4455 4456 4457 4458 4459 4460 4461 4462 4463 4464 4465 4466 4467 4468 4469
                    }
                }
            }
        }

        private readonly struct DeconstructionVariable
        {
            internal readonly BoundExpression Expression;
            internal readonly TypeSymbolWithAnnotations Type;
            internal readonly ArrayBuilder<DeconstructionVariable> NestedVariables;

            internal DeconstructionVariable(BoundExpression expression, TypeSymbolWithAnnotations type)
            {
                Expression = expression;
                Type = type;
                NestedVariables = null;
            }

            internal DeconstructionVariable(ArrayBuilder<DeconstructionVariable> nestedVariables)
            {
                Expression = null;
                Type = default;
                NestedVariables = nestedVariables;
            }
        }

        private ArrayBuilder<DeconstructionVariable> GetDeconstructionAssignmentVariables(BoundTupleExpression tuple)
        {
            var arguments = tuple.Arguments;
            var builder = ArrayBuilder<DeconstructionVariable>.GetInstance(arguments.Length);
            foreach (var argument in arguments)
            {
                builder.Add(getDeconstructionAssignmentVariable(argument));
            }
            return builder;

            DeconstructionVariable getDeconstructionAssignmentVariable(BoundExpression expr)
            {
                switch (expr.Kind)
                {
                    case BoundKind.TupleLiteral:
                    case BoundKind.ConvertedTupleLiteral:
                        return new DeconstructionVariable(GetDeconstructionAssignmentVariables((BoundTupleExpression)expr));
                    default:
                        VisitLvalue(expr);
                        return new DeconstructionVariable(expr, _resultType);
                }
            }
        }

        /// <summary>
        /// Return the sub-expressions for the righthand side of a deconstruction
        /// assignment. cf. LocalRewriter.GetRightParts.
        /// </summary>
        private ImmutableArray<BoundExpression> GetDeconstructionRightParts(BoundExpression expr)
        {
            switch (expr.Kind)
            {
                case BoundKind.TupleLiteral:
                case BoundKind.ConvertedTupleLiteral:
                    return ((BoundTupleExpression)expr).Arguments;
                case BoundKind.Conversion:
                    {
                        var conv = (BoundConversion)expr;
                        switch (conv.ConversionKind)
                        {
                            case ConversionKind.Identity:
                            case ConversionKind.ImplicitTupleLiteral:
                                return GetDeconstructionRightParts(conv.Operand);
                        }
                    }
                    break;
            }

            if (expr.Type is TupleTypeSymbol tupleType)
            {
                // https://github.com/dotnet/roslyn/issues/33011: Should include conversion.UnderlyingConversions[i].
                // For instance, Boxing conversions (see Deconstruction_ImplicitBoxingConversion_02) and
                // ImplicitNullable conversions (see Deconstruction_ImplicitNullableConversion_02).
                VisitRvalue(expr);
                var fields = tupleType.TupleElements;
                return fields.SelectAsArray((f, e) => (BoundExpression)new BoundFieldAccess(e.Syntax, e, f, constantValueOpt: null), expr);
            }

            throw ExceptionUtilities.Unreachable;
        }

4470 4471 4472 4473
        public override BoundNode VisitIncrementOperator(BoundIncrementOperator node)
        {
            Debug.Assert(!IsConditionalState);

4474
            VisitRvalue(node.Operand);
4475
            var operandType = _resultType;
4476 4477
            bool setResult = false;

4478 4479
            if (this.State.Reachable)
            {
4480
                // https://github.com/dotnet/roslyn/issues/29961 Update increment method based on operand type.
4481
                MethodSymbol incrementOperator = (node.OperatorKind.IsUserDefined() && (object)node.MethodOpt != null && node.MethodOpt.ParameterCount == 1) ? node.MethodOpt : null;
4482 4483 4484
                TypeSymbolWithAnnotations targetTypeOfOperandConversion;
                AssignmentKind assignmentKind = AssignmentKind.Assignment;
                ParameterSymbol target = null;
4485

4486
                // https://github.com/dotnet/roslyn/issues/29961 Update conversion method based on operand type.
4487 4488
                if (node.OperandConversion.IsUserDefined && (object)node.OperandConversion.Method != null && node.OperandConversion.Method.ParameterCount == 1)
                {
4489
                    targetTypeOfOperandConversion = node.OperandConversion.Method.ReturnType;
4490 4491 4492
                }
                else if ((object)incrementOperator != null)
                {
4493 4494 4495
                    targetTypeOfOperandConversion = incrementOperator.Parameters[0].Type;
                    assignmentKind = AssignmentKind.Argument;
                    target = incrementOperator.Parameters[0];
4496 4497 4498 4499
                }
                else
                {
                    // Either a built-in increment, or an error case.
4500
                    targetTypeOfOperandConversion = default;
4501 4502
                }

4503
                TypeSymbolWithAnnotations resultOfOperandConversionType;
4504

4505
                if (!targetTypeOfOperandConversion.IsNull)
4506
                {
4507
                    // https://github.com/dotnet/roslyn/issues/29961 Should something special be done for targetTypeOfOperandConversion for lifted case?
4508 4509 4510 4511 4512 4513 4514 4515 4516 4517 4518 4519
                    resultOfOperandConversionType = ApplyConversion(
                        node.Operand,
                        node.Operand,
                        node.OperandConversion,
                        targetTypeOfOperandConversion,
                        operandType,
                        checkConversion: true,
                        fromExplicitCast: false,
                        useLegacyWarnings: false,
                        assignmentKind,
                        target,
                        reportTopLevelWarnings: true,
4520
                        reportRemainingWarnings: true);
4521 4522 4523
                }
                else
                {
4524
                    resultOfOperandConversionType = operandType;
4525 4526
                }

4527
                TypeSymbolWithAnnotations resultOfIncrementType;
4528 4529
                if ((object)incrementOperator == null)
                {
4530
                    resultOfIncrementType = resultOfOperandConversionType;
4531
                }
4532
                else
4533
                {
4534
                    resultOfIncrementType = incrementOperator.ReturnType;
4535 4536
                }

4537 4538 4539 4540 4541 4542 4543 4544 4545 4546
                resultOfIncrementType = ApplyConversion(
                    node,
                    node,
                    node.ResultConversion,
                    operandType,
                    resultOfIncrementType,
                    checkConversion: true,
                    fromExplicitCast: false,
                    useLegacyWarnings: false,
                    AssignmentKind.Assignment);
4547

4548
                // https://github.com/dotnet/roslyn/issues/29961 Check node.Type.IsErrorType() instead?
4549
                if (!node.HasErrors)
4550
                {
4551
                    var op = node.OperatorKind.Operator();
4552
                    _resultType = (op == UnaryOperatorKind.PrefixIncrement || op == UnaryOperatorKind.PrefixDecrement) ? resultOfIncrementType : operandType;
4553 4554
                    setResult = true;

4555
                    TrackNullableStateForAssignment(node, operandType, MakeSlot(node.Operand), valueType: resultOfIncrementType);
4556 4557
                }
            }
4558 4559

            if (!setResult)
4560
            {
4561
                this.SetResult(node);
4562 4563 4564 4565 4566 4567 4568
            }

            return null;
        }

        public override BoundNode VisitCompoundAssignmentOperator(BoundCompoundAssignmentOperator node)
        {
4569
            VisitLvalue(node.Left); // https://github.com/dotnet/roslyn/issues/29962 Method should be called VisitValue rather than VisitLvalue.
4570
            TypeSymbolWithAnnotations leftType = _resultType;
4571

4572
            TypeSymbolWithAnnotations resultType;
4573 4574
            Debug.Assert(!IsConditionalState);

4575
            //if (this.State.Reachable) // Consider reachability: see https://github.com/dotnet/roslyn/issues/28798
4576
            {
4577
                TypeSymbolWithAnnotations leftOnRightType = GetAdjustedResult(leftType, MakeSlot(node.Left));
4578

4579
                // https://github.com/dotnet/roslyn/issues/29962 Update operator based on inferred argument types.
4580 4581
                if ((object)node.Operator.LeftType != null)
                {
4582
                    // https://github.com/dotnet/roslyn/issues/29962 Ignoring top-level nullability of operator left parameter.
4583 4584 4585 4586 4587 4588 4589 4590 4591 4592 4593
                    leftOnRightType = ApplyConversion(
                        node.Left,
                        node.Left,
                        node.LeftConversion,
                        TypeSymbolWithAnnotations.Create(node.Operator.LeftType),
                        leftOnRightType,
                        checkConversion: true,
                        fromExplicitCast: false,
                        useLegacyWarnings: false,
                        AssignmentKind.Assignment,
                        reportTopLevelWarnings: false,
4594
                        reportRemainingWarnings: true);
4595 4596 4597
                }
                else
                {
4598
                    leftOnRightType = default;
4599 4600 4601
                }

                VisitRvalue(node.Right);
4602
                TypeSymbolWithAnnotations rightType = _resultType;
4603 4604 4605 4606

                if ((object)node.Operator.ReturnType != null)
                {
                    if (node.Operator.Kind.IsUserDefined() && (object)node.Operator.Method != null && node.Operator.Method.ParameterCount == 2)
4607
                    {
4608 4609
                        ReportArgumentWarnings(node.Left, leftOnRightType, node.Operator.Method.Parameters[0]);
                        ReportArgumentWarnings(node.Right, rightType, node.Operator.Method.Parameters[1]);
4610 4611
                    }

4612
                    resultType = InferResultNullability(node.Operator.Kind, node.Operator.Method, node.Operator.ReturnType, leftOnRightType, rightType);
4613 4614 4615 4616 4617 4618 4619 4620 4621 4622
                    resultType = ApplyConversion(
                        node,
                        node,
                        node.FinalConversion,
                        leftType,
                        resultType,
                        checkConversion: true,
                        fromExplicitCast: false,
                        useLegacyWarnings: false,
                        AssignmentKind.Assignment);
4623 4624 4625
                }
                else
                {
4626
                    resultType = TypeSymbolWithAnnotations.Create(node.Type);
4627 4628
                }

4629 4630
                TrackNullableStateForAssignment(node, leftType, MakeSlot(node.Left), resultType);
                _resultType = resultType;
4631
            }
4632
            //else
4633
            //{   // https://github.com/dotnet/roslyn/issues/29962 code should be restored?
4634 4635 4636 4637
            //    VisitRvalue(node.Right);
            //    AfterRightHasBeenVisited(node);
            //    resultType = null;
            //}
4638 4639 4640 4641 4642 4643 4644 4645 4646 4647 4648 4649

            return null;
        }

        public override BoundNode VisitFixedLocalCollectionInitializer(BoundFixedLocalCollectionInitializer node)
        {
            var initializer = node.Expression;
            if (initializer.Kind == BoundKind.AddressOfOperator)
            {
                initializer = ((BoundAddressOfOperator)initializer).Operand;
            }

4650 4651
            this.VisitRvalue(initializer);
            SetResult(node);
4652 4653 4654 4655 4656
            return null;
        }

        public override BoundNode VisitAddressOfOperator(BoundAddressOfOperator node)
        {
4657
            SetResult(node);
4658 4659 4660
            return null;
        }

4661 4662 4663
        private void ReportArgumentWarnings(BoundExpression argument, TypeSymbolWithAnnotations argumentType, ParameterSymbol parameter)
        {
            var paramType = parameter.Type;
4664
            ReportNullableAssignmentIfNecessary(argument, paramType, argumentType, useLegacyWarnings: false, assignmentKind: AssignmentKind.Argument, target: parameter);
4665

4666
            if (!argumentType.IsNull && IsNullabilityMismatch(paramType.TypeSymbol, argumentType.TypeSymbol))
4667
            {
4668
                ReportNullabilityMismatchInArgument(argument, argumentType.TypeSymbol, parameter, paramType.TypeSymbol);
4669 4670 4671
            }
        }

4672 4673 4674 4675 4676 4677
        /// <summary>
        /// Report warning passing argument where nested nullability does not match
        /// parameter (e.g.: calling `void F(object[] o)` with `F(new[] { maybeNull })`).
        /// </summary>
        private void ReportNullabilityMismatchInArgument(BoundExpression argument, TypeSymbol argumentType, ParameterSymbol parameter, TypeSymbol parameterType)
        {
4678
            ReportSafetyDiagnostic(ErrorCode.WRN_NullabilityMismatchInArgument, argument.Syntax, argumentType, parameterType,
4679 4680 4681 4682
                new FormattedSymbol(parameter, SymbolDisplayFormat.ShortFormat),
                new FormattedSymbol(parameter.ContainingSymbol, SymbolDisplayFormat.MinimallyQualifiedFormat));
        }

4683
        private TypeSymbolWithAnnotations GetDeclaredLocalResult(LocalSymbol local)
4684
        {
4685 4686 4687
            return _variableTypes.TryGetValue(local, out TypeSymbolWithAnnotations type) ?
                type :
                local.Type;
4688 4689
        }

4690
        private TypeSymbolWithAnnotations GetDeclaredParameterResult(ParameterSymbol parameter)
4691
        {
4692 4693
            return _variableTypes.TryGetValue(parameter, out TypeSymbolWithAnnotations type) ?
                type :
4694
                parameter.Type;
4695 4696
        }

4697
        public override BoundNode VisitBaseReference(BoundBaseReference node)
4698
        {
4699 4700
            VisitThisOrBaseReference(node);
            return null;
4701 4702 4703 4704
        }

        public override BoundNode VisitFieldAccess(BoundFieldAccess node)
        {
4705
            VisitMemberAccess(node, node.ReceiverOpt, node.FieldSymbol, asLvalue: false);
4706
            return null;
4707 4708 4709 4710
        }

        public override BoundNode VisitPropertyAccess(BoundPropertyAccess node)
        {
4711
            VisitMemberAccess(node, node.ReceiverOpt, node.PropertySymbol, asLvalue: false);
4712
            return null;
4713 4714 4715 4716
        }

        public override BoundNode VisitIndexerAccess(BoundIndexerAccess node)
        {
4717 4718
            var receiverOpt = node.ReceiverOpt;
            VisitRvalue(receiverOpt);
4719 4720
            // https://github.com/dotnet/roslyn/issues/30598: Mark receiver as not null
            // after indices have been visited, and only if the receiver has not changed.
4721 4722
            CheckPossibleNullReceiver(receiverOpt);

4723
            // https://github.com/dotnet/roslyn/issues/29964 Update indexer based on inferred receiver type.
4724
            VisitArguments(node, node.Arguments, node.ArgumentRefKindsOpt, node.Indexer, node.ArgsToParamsOpt, node.Expanded);
4725

A
Andy Gocke 已提交
4726
            // https://github.com/dotnet/roslyn/issues/30620 remove before shipping dev16
4727
            if (node.Arguments.Length == 1 &&
4728
                TypeSymbol.Equals(node.Arguments[0].Type, compilation.GetWellKnownType(WellKnownType.System_Range), TypeCompareKind.ConsiderEverything2))
4729 4730 4731 4732 4733 4734 4735
            {
                _resultType = TypeSymbolWithAnnotations.Create(node.Type);
            }
            else
            {
                _resultType = node.Indexer.Type;
            }
4736
            return null;
4737 4738 4739 4740
        }

        public override BoundNode VisitEventAccess(BoundEventAccess node)
        {
4741
            VisitMemberAccess(node, node.ReceiverOpt, node.EventSymbol, asLvalue: false);
4742 4743
            return null;
        }
4744

4745
        private void VisitMemberAccess(BoundExpression node, BoundExpression receiverOpt, Symbol member, bool asLvalue)
4746
        {
4747
            Debug.Assert(!IsConditionalState);
4748

4749
            //if (this.State.Reachable) // Consider reachability: see https://github.com/dotnet/roslyn/issues/28798
4750
            {
4751
                VisitRvalue(receiverOpt);
4752

4753
                SpecialMember? nullableOfTMember = null;
4754 4755
                if (!member.IsStatic)
                {
4756
                    member = AsMemberOfResultType(_resultType, member);
4757
                    nullableOfTMember = GetNullableOfTMember(compilation, member);
4758 4759
                    // https://github.com/dotnet/roslyn/issues/30598: For l-values, mark receiver as not null
                    // after RHS has been visited, and only if the receiver has not changed.
4760 4761
                    bool allowValueType = nullableOfTMember == SpecialMember.System_Nullable_T_get_Value;
                    CheckPossibleNullReceiver(receiverOpt, allowValueType, (allowValueType ? node : (receiverOpt ?? node)).Syntax);
4762 4763 4764 4765 4766 4767
                }

                var resultType = member.GetTypeOrReturnType();

                if (!asLvalue)
                {
4768 4769
                    // We are supposed to track information for the node. Use whatever we managed to
                    // accumulate so far.
4770
                    if (PossiblyNullableType(resultType.TypeSymbol))
4771
                    {
4772
                        int slot = MakeMemberSlot(receiverOpt, member);
4773
                        if (slot > 0 && slot < this.State.Capacity)
4774
                        {
4775 4776
                            var annotation = this.State[slot];
                            if (annotation != resultType.NullableAnnotation)
4777
                            {
4778
                                resultType = TypeSymbolWithAnnotations.Create(resultType.TypeSymbol, annotation);
4779
                            }
4780 4781
                        }
                    }
4782 4783 4784 4785

                    Debug.Assert(!IsConditionalState);
                    if (nullableOfTMember == SpecialMember.System_Nullable_T_get_HasValue)
                    {
4786
                        int containingSlot = (receiverOpt is null) ? -1 : MakeSlot(receiverOpt);
4787 4788 4789 4790 4791 4792 4793 4794
                        if (containingSlot > 0)
                        {
                            // https://github.com/dotnet/roslyn/issues/31516: Report HDN_NullCheckIsProbablyAlwaysTrue/False
                            // when HasValue check is unnecessary.
                            Split();
                            this.StateWhenTrue[containingSlot] = NullableAnnotation.NotNullable;
                        }
                    }
4795 4796
                }

4797
                _resultType = resultType;
4798
            }
4799 4800 4801 4802 4803 4804 4805 4806 4807 4808 4809 4810 4811 4812 4813 4814 4815 4816 4817 4818 4819 4820
        }

        private static SpecialMember? GetNullableOfTMember(CSharpCompilation compilation, Symbol member)
        {
            if (member.Kind == SymbolKind.Property)
            {
                var getMethod = ((PropertySymbol)member.OriginalDefinition).GetMethod;
                if ((object)getMethod != null && getMethod.ContainingType.SpecialType == SpecialType.System_Nullable_T)
                {
                    if (getMethod == compilation.GetSpecialTypeMember(SpecialMember.System_Nullable_T_get_Value))
                    {
                        return SpecialMember.System_Nullable_T_get_Value;
                    }
                    if (getMethod == compilation.GetSpecialTypeMember(SpecialMember.System_Nullable_T_get_HasValue))
                    {
                        return SpecialMember.System_Nullable_T_get_HasValue;
                    }
                }
            }
            return null;
        }

4821
        private int GetNullableOfTValueSlot(TypeSymbol containingType, int containingSlot, out Symbol valueProperty)
4822 4823
        {
            Debug.Assert(containingType.IsNullableType());
4824
            Debug.Assert(TypeSymbol.Equals(GetSlotType(containingSlot), containingType, TypeCompareKind.ConsiderEverything2));
4825 4826

            var getValue = (MethodSymbol)compilation.GetSpecialTypeMember(SpecialMember.System_Nullable_T_get_Value);
4827 4828
            valueProperty = getValue?.AsMember((NamedTypeSymbol)containingType)?.AssociatedSymbol;
            return (valueProperty is null) ? -1 : GetOrCreateSlot(valueProperty, containingSlot);
4829 4830
        }

4831 4832 4833 4834 4835 4836 4837
        protected override void VisitForEachExpression(BoundForEachStatement node)
        {
            var expr = node.Expression;
            VisitRvalue(expr);
            CheckPossibleNullReceiver(expr);
        }

4838 4839 4840 4841 4842
        public override void VisitForEachIterationVariables(BoundForEachStatement node)
        {
            // declare and assign all iteration variables
            foreach (var iterationVariable in node.IterationVariables)
            {
4843
                TypeSymbolWithAnnotations sourceType = node.EnumeratorInfoOpt?.ElementType ?? default;
4844
                NullableAnnotation annotation = NullableAnnotation.Unknown;
4845
                if (!sourceType.IsNull)
4846 4847 4848 4849
                {
                    TypeSymbolWithAnnotations destinationType = iterationVariable.Type;
                    HashSet<DiagnosticInfo> useSiteDiagnostics = null;
                    Conversion conversion = _conversions.ClassifyImplicitConversionFromType(sourceType.TypeSymbol, destinationType.TypeSymbol, ref useSiteDiagnostics);
4850 4851 4852 4853 4854 4855 4856 4857 4858 4859 4860
                    TypeSymbolWithAnnotations result = ApplyConversion(
                        node.IterationVariableType,
                        operandOpt: null,
                        conversion,
                        destinationType,
                        sourceType,
                        checkConversion: false,
                        fromExplicitCast: true,
                        useLegacyWarnings: false,
                        AssignmentKind.Assignment,
                        reportTopLevelWarnings: false,
4861 4862
                        reportRemainingWarnings: false);
                    if (destinationType.IsReferenceType && destinationType.NullableAnnotation.IsAnyNotNullable() && result.NullableAnnotation.IsAnyNullable())
4863
                    {
4864
                        ReportNonSafetyDiagnostic(node.IterationVariableType.Syntax);
4865
                    }
4866 4867 4868 4869 4870 4871 4872
                    annotation = result.NullableAnnotation;
                }

                int slot = GetOrCreateSlot(iterationVariable);
                if (slot > 0)
                {
                    this.State[slot] = annotation;
4873
                }
4874 4875 4876
            }
        }

4877 4878 4879 4880 4881 4882 4883
        public override BoundNode VisitFromEndIndexExpression(BoundFromEndIndexExpression node)
        {
            var result = base.VisitFromEndIndexExpression(node);
            SetResult(node);
            return result;
        }

4884 4885
        public override BoundNode VisitObjectInitializerMember(BoundObjectInitializerMember node)
        {
4886 4887
            // Should be handled by VisitObjectCreationExpression.
            throw ExceptionUtilities.Unreachable;
4888 4889 4890 4891
        }

        public override BoundNode VisitDynamicObjectInitializerMember(BoundDynamicObjectInitializerMember node)
        {
4892
            SetResult(node);
4893 4894 4895 4896 4897 4898
            return null;
        }

        public override BoundNode VisitBadExpression(BoundBadExpression node)
        {
            var result = base.VisitBadExpression(node);
4899
            _resultType = TypeSymbolWithAnnotations.Create(node.Type);
4900 4901 4902 4903 4904 4905
            return result;
        }

        public override BoundNode VisitTypeExpression(BoundTypeExpression node)
        {
            var result = base.VisitTypeExpression(node);
4906
            SetResult(node);
4907 4908 4909 4910 4911 4912
            return result;
        }

        public override BoundNode VisitTypeOrValueExpression(BoundTypeOrValueExpression node)
        {
            var result = base.VisitTypeOrValueExpression(node);
4913
            SetResult(node);
4914 4915 4916 4917 4918 4919 4920 4921
            return result;
        }

        public override BoundNode VisitUnaryOperator(BoundUnaryOperator node)
        {
            Debug.Assert(!IsConditionalState);

            var result = base.VisitUnaryOperator(node);
4922
            TypeSymbolWithAnnotations resultType = default;
4923

4924
            // Update method based on inferred operand type: see https://github.com/dotnet/roslyn/issues/29605.
4925 4926
            if (node.OperatorKind.IsUserDefined())
            {
4927 4928
                if (node.OperatorKind.IsLifted())
                {
4929
                    // https://github.com/dotnet/roslyn/issues/29953 Conversions: Lifted operator
4930
                }
4931
                else if ((object)node.MethodOpt != null && node.MethodOpt.ParameterCount == 1)
4932
                {
4933
                    ReportArgumentWarnings(node.Operand, _resultType, node.MethodOpt.Parameters[0]);
4934
                    resultType = node.MethodOpt.ReturnType;
4935 4936
                }
            }
4937

4938
            _resultType = resultType.IsNull ? TypeSymbolWithAnnotations.Create(node.Type) : resultType;
4939
            return null;
4940 4941 4942 4943 4944
        }

        public override BoundNode VisitPointerIndirectionOperator(BoundPointerIndirectionOperator node)
        {
            var result = base.VisitPointerIndirectionOperator(node);
4945
            SetResult(node);
4946 4947 4948 4949 4950 4951
            return result;
        }

        public override BoundNode VisitPointerElementAccess(BoundPointerElementAccess node)
        {
            var result = base.VisitPointerElementAccess(node);
4952
            SetResult(node);
4953 4954 4955 4956 4957
            return result;
        }

        public override BoundNode VisitRefTypeOperator(BoundRefTypeOperator node)
        {
4958 4959 4960
            VisitRvalue(node.Operand);
            SetResult(node);
            return null;
4961 4962 4963 4964 4965
        }

        public override BoundNode VisitMakeRefOperator(BoundMakeRefOperator node)
        {
            var result = base.VisitMakeRefOperator(node);
4966
            SetResult(node);
4967 4968 4969 4970 4971 4972
            return result;
        }

        public override BoundNode VisitRefValueOperator(BoundRefValueOperator node)
        {
            var result = base.VisitRefValueOperator(node);
4973
            SetResult(node);
4974 4975 4976
            return result;
        }

4977
        private TypeSymbolWithAnnotations InferResultNullability(BoundUserDefinedConditionalLogicalOperator node)
4978
        {
4979 4980
            if (node.OperatorKind.IsLifted())
            {
4981
                // https://github.com/dotnet/roslyn/issues/29953 Conversions: Lifted operator
4982
                return TypeSymbolWithAnnotations.Create(node.Type);
4983
            }
4984
            // Update method based on inferred operand types: see https://github.com/dotnet/roslyn/issues/29605.
4985 4986
            if ((object)node.LogicalOperator != null && node.LogicalOperator.ParameterCount == 2)
            {
4987
                return node.LogicalOperator.ReturnType;
4988 4989 4990
            }
            else
            {
4991
                return default;
4992 4993 4994 4995 4996 4997
            }
        }

        protected override void AfterLeftChildOfBinaryLogicalOperatorHasBeenVisited(BoundExpression node, BoundExpression right, bool isAnd, bool isBool, ref LocalState leftTrue, ref LocalState leftFalse)
        {
            Debug.Assert(!IsConditionalState);
4998
            //if (this.State.Reachable) // Consider reachability: see https://github.com/dotnet/roslyn/issues/28798
4999
            {
5000
                TypeSymbolWithAnnotations leftType = _resultType;
5001
                // https://github.com/dotnet/roslyn/issues/29605 Update operator methods based on inferred operand types.
5002 5003 5004 5005 5006 5007 5008 5009 5010 5011 5012 5013 5014 5015 5016 5017 5018 5019 5020 5021 5022 5023 5024 5025
                MethodSymbol logicalOperator = null;
                MethodSymbol trueFalseOperator = null;
                BoundExpression left = null;

                switch (node.Kind)
                {
                    case BoundKind.BinaryOperator:
                        Debug.Assert(!((BoundBinaryOperator)node).OperatorKind.IsUserDefined());
                        break;
                    case BoundKind.UserDefinedConditionalLogicalOperator:
                        var binary = (BoundUserDefinedConditionalLogicalOperator)node;
                        if (binary.LogicalOperator != null && binary.LogicalOperator.ParameterCount == 2)
                        {
                            logicalOperator = binary.LogicalOperator;
                            left = binary.Left;
                            trueFalseOperator = isAnd ? binary.FalseOperator : binary.TrueOperator;

                            if ((object)trueFalseOperator != null && trueFalseOperator.ParameterCount != 1)
                            {
                                trueFalseOperator = null;
                            }
                        }
                        break;
                    default:
5026
                        throw ExceptionUtilities.UnexpectedValue(node.Kind);
5027 5028 5029 5030 5031 5032
                }

                Debug.Assert((object)trueFalseOperator == null || ((object)logicalOperator != null && left != null));

                if ((object)trueFalseOperator != null)
                {
5033
                    ReportArgumentWarnings(left, leftType, trueFalseOperator.Parameters[0]);
5034 5035 5036 5037
                }

                if ((object)logicalOperator != null)
                {
5038
                    ReportArgumentWarnings(left, leftType, logicalOperator.Parameters[0]);
5039 5040 5041
                }

                Visit(right);
5042
                TypeSymbolWithAnnotations rightType = _resultType;
5043

5044
                _resultType = InferResultNullabilityOfBinaryLogicalOperator(node, leftType, rightType);
5045 5046 5047

                if ((object)logicalOperator != null)
                {
5048
                    ReportArgumentWarnings(right, rightType, logicalOperator.Parameters[1]);
5049 5050
                }
            }
5051 5052

            AfterRightChildOfBinaryLogicalOperatorHasBeenVisited(node, right, isAnd, isBool, ref leftTrue, ref leftFalse);
5053 5054
        }

5055
        private TypeSymbolWithAnnotations InferResultNullabilityOfBinaryLogicalOperator(BoundExpression node, TypeSymbolWithAnnotations leftType, TypeSymbolWithAnnotations rightType)
5056 5057 5058 5059
        {
            switch (node.Kind)
            {
                case BoundKind.BinaryOperator:
5060
                    return InferResultNullability((BoundBinaryOperator)node, leftType, rightType);
5061 5062 5063
                case BoundKind.UserDefinedConditionalLogicalOperator:
                    return InferResultNullability((BoundUserDefinedConditionalLogicalOperator)node);
                default:
5064
                    throw ExceptionUtilities.UnexpectedValue(node.Kind);
5065 5066 5067 5068 5069 5070
            }
        }

        public override BoundNode VisitAwaitExpression(BoundAwaitExpression node)
        {
            var result = base.VisitAwaitExpression(node);
5071
            CheckPossibleNullReceiver(node.Expression);
5072
            if (node.Type.IsValueType || node.HasErrors || (object)node.AwaitableInfo.GetResult == null)
5073
            {
5074 5075 5076 5077
                SetResult(node);
            }
            else
            {
5078
                // Update method based on inferred receiver type: see https://github.com/dotnet/roslyn/issues/29605.
5079
                _resultType = node.AwaitableInfo.GetResult.ReturnType;
5080 5081 5082 5083 5084 5085 5086
            }
            return result;
        }

        public override BoundNode VisitTypeOfOperator(BoundTypeOfOperator node)
        {
            var result = base.VisitTypeOfOperator(node);
5087
            _resultType = TypeSymbolWithAnnotations.Create(node.Type, NullableAnnotation.NotNullable);
5088 5089 5090 5091 5092 5093
            return result;
        }

        public override BoundNode VisitMethodInfo(BoundMethodInfo node)
        {
            var result = base.VisitMethodInfo(node);
5094
            SetResult(node);
5095 5096 5097 5098 5099 5100
            return result;
        }

        public override BoundNode VisitFieldInfo(BoundFieldInfo node)
        {
            var result = base.VisitFieldInfo(node);
5101
            SetResult(node);
5102 5103 5104 5105 5106
            return result;
        }

        public override BoundNode VisitDefaultExpression(BoundDefaultExpression node)
        {
5107 5108
            Debug.Assert(!this.IsConditionalState);

5109
            var result = base.VisitDefaultExpression(node);
5110
            TypeSymbol type = node.Type;
5111 5112 5113 5114 5115 5116
            if (EmptyStructTypeCache.IsTrackableStructType(type))
            {
                int slot = GetOrCreateObjectCreationPlaceholderSlot(node);
                if (slot > 0)
                {
                    this.State[slot] = NullableAnnotation.NotNullable;
C
Charles Stoner 已提交
5117
                    InheritNullableStateOfTrackableStruct(type, slot, valueSlot: -1, isDefaultValue: true);
5118 5119
                }
            }
5120
            _resultType = TypeSymbolWithAnnotations.Create(type, (type is null || type.IsNullableType() || !type.IsValueType) ? NullableAnnotation.Nullable : NullableAnnotation.Unknown);
5121 5122 5123 5124 5125
            return result;
        }

        public override BoundNode VisitIsOperator(BoundIsOperator node)
        {
5126 5127 5128
            Debug.Assert(!this.IsConditionalState);

            var operand = node.Operand;
5129 5130
            var result = base.VisitIsOperator(node);
            Debug.Assert(node.Type.SpecialType == SpecialType.System_Boolean);
5131

5132
            if (operand.Type?.IsValueType == false)
5133
            {
5134 5135 5136 5137 5138 5139 5140 5141
                var slotBuilder = ArrayBuilder<int>.GetInstance();
                GetSlotsToMarkAsNotNullable(operand, slotBuilder);
                if (slotBuilder.Count > 0)
                {
                    Split();
                    MarkSlotsAsNotNullable(slotBuilder, ref StateWhenTrue);
                }
                slotBuilder.Free();
5142 5143
            }

5144
            SetResult(node);
5145 5146 5147 5148 5149
            return result;
        }

        public override BoundNode VisitAsOperator(BoundAsOperator node)
        {
5150
            VisitRvalue(node.Operand);
5151

5152
            //if (this.State.Reachable) // Consider reachability: see https://github.com/dotnet/roslyn/issues/28798
5153
            {
5154
                NullableAnnotation nullableAnnotation = NullableAnnotation.Unknown;
5155 5156
                var type = node.Type;

5157
                if (PossiblyNullableType(type))
5158
                {
5159
                    var operandType = _resultType;
5160 5161 5162
                    switch (node.Conversion.Kind)
                    {
                        case ConversionKind.Identity:
5163
                            // Inherit nullability from the operand
5164
                            nullableAnnotation = operandType.NullableAnnotation;
5165 5166
                            break;

5167 5168
                        case ConversionKind.ImplicitReference:
                            // Inherit nullability from the operand
5169
                            if (!operandType.IsNull && operandType.IsPossiblyNullableReferenceTypeTypeParameter())
5170
                            {
5171
                                if (!type.IsPossiblyNullableReferenceTypeTypeParameter())
5172
                                {
5173
                                    nullableAnnotation = NullableAnnotation.Nullable;
5174 5175 5176
                                }
                                else
                                {
5177
                                    nullableAnnotation = NullableAnnotation.NotAnnotated;
5178 5179 5180 5181
                                }
                            }
                            else
                            {
5182 5183
                                nullableAnnotation = operandType.NullableAnnotation;
                                if (nullableAnnotation == NullableAnnotation.NotAnnotated && type.IsTypeParameter())
5184
                                {
5185
                                    nullableAnnotation = NullableAnnotation.NotNullable;
5186 5187
                                }
                            }
5188 5189 5190
                            break;

                        case ConversionKind.Boxing:
5191
                            if (operandType.TypeSymbol?.IsValueType == true)
5192
                            {
5193
                                nullableAnnotation = (operandType.TypeSymbol.IsNullableType() && operandType.NullableAnnotation.IsAnyNullable()) ? NullableAnnotation.Nullable : NullableAnnotation.NotNullable;
5194 5195 5196
                            }
                            else
                            {
5197
                                Debug.Assert(operandType.TypeSymbol?.IsReferenceType != true);
5198

5199
                                if (!operandType.IsNull)
5200
                                {
5201
                                    if (operandType.IsPossiblyNullableReferenceTypeTypeParameter() && type.IsPossiblyNullableReferenceTypeTypeParameter())
5202
                                    {
5203
                                        nullableAnnotation = NullableAnnotation.NotAnnotated;
5204 5205 5206
                                    }
                                    else
                                    {
5207
                                        nullableAnnotation = operandType.GetValueNullableAnnotation();
5208 5209 5210 5211
                                    }
                                }
                                else
                                {
5212
                                    nullableAnnotation = NullableAnnotation.Nullable;
5213
                                }
5214 5215 5216
                            }
                            break;

5217 5218 5219 5220
                        case ConversionKind.ImplicitNullable:
                            nullableAnnotation = (operandType.IsNullableType() && operandType.NullableAnnotation.IsAnyNullable()) ? NullableAnnotation.Nullable : NullableAnnotation.NotNullable;
                            break;

5221
                        default:
5222
                            nullableAnnotation = NullableAnnotation.Nullable;
5223 5224 5225
                            break;
                    }
                }
5226

5227
                _resultType = TypeSymbolWithAnnotations.Create(type, nullableAnnotation);
5228 5229
            }

5230
            return null;
5231 5232 5233 5234 5235
        }

        public override BoundNode VisitSizeOfOperator(BoundSizeOfOperator node)
        {
            var result = base.VisitSizeOfOperator(node);
5236
            SetResult(node);
5237 5238 5239 5240 5241 5242 5243
            return result;
        }

        public override BoundNode VisitArgList(BoundArgList node)
        {
            var result = base.VisitArgList(node);
            Debug.Assert(node.Type.SpecialType == SpecialType.System_RuntimeArgumentHandle);
5244
            SetResult(node);
5245 5246 5247 5248 5249
            return result;
        }

        public override BoundNode VisitArgListOperator(BoundArgListOperator node)
        {
5250
            VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt);
5251
            Debug.Assert((object)node.Type == null);
5252
            SetResult(node);
5253
            return null;
5254 5255 5256 5257 5258 5259 5260
        }

        public override BoundNode VisitLiteral(BoundLiteral node)
        {
            var result = base.VisitLiteral(node);

            Debug.Assert(!IsConditionalState);
5261
            //if (this.State.Reachable) // Consider reachability: see https://github.com/dotnet/roslyn/issues/28798
5262 5263 5264 5265 5266 5267
            {
                var constant = node.ConstantValue;

                if (constant != null &&
                    ((object)node.Type != null ? node.Type.IsReferenceType : constant.IsNull))
                {
5268
                    _resultType = TypeSymbolWithAnnotations.Create(node.Type, constant.IsNull ? NullableAnnotation.Nullable : NullableAnnotation.NotNullable);
5269 5270 5271
                }
                else
                {
5272
                    SetResult(node);
5273 5274 5275 5276 5277 5278 5279 5280 5281 5282
                }
            }

            return result;
        }

        public override BoundNode VisitPreviousSubmissionReference(BoundPreviousSubmissionReference node)
        {
            var result = base.VisitPreviousSubmissionReference(node);
            Debug.Assert(node.WasCompilerGenerated);
5283
            SetResult(node);
5284 5285 5286 5287 5288 5289 5290
            return result;
        }

        public override BoundNode VisitHostObjectMemberReference(BoundHostObjectMemberReference node)
        {
            var result = base.VisitHostObjectMemberReference(node);
            Debug.Assert(node.WasCompilerGenerated);
5291
            SetResult(node);
5292 5293 5294 5295 5296 5297
            return result;
        }

        public override BoundNode VisitPseudoVariable(BoundPseudoVariable node)
        {
            var result = base.VisitPseudoVariable(node);
5298
            SetResult(node);
5299 5300 5301
            return result;
        }

5302 5303 5304 5305 5306 5307 5308
        public override BoundNode VisitRangeExpression(BoundRangeExpression node)
        {
            var result = base.VisitRangeExpression(node);
            SetResult(node);
            return result;
        }

5309 5310 5311
        public override BoundNode VisitRangeVariable(BoundRangeVariable node)
        {
            var result = base.VisitRangeVariable(node);
5312
            SetResult(node); // https://github.com/dotnet/roslyn/issues/29863 Need to review this
5313 5314 5315 5316 5317 5318 5319 5320 5321 5322 5323 5324
            return result;
        }

        public override BoundNode VisitLabel(BoundLabel node)
        {
            var result = base.VisitLabel(node);
            SetUnknownResultNullability();
            return result;
        }

        public override BoundNode VisitDynamicMemberAccess(BoundDynamicMemberAccess node)
        {
5325 5326 5327
            var receiver = node.Receiver;
            VisitRvalue(receiver);
            CheckPossibleNullReceiver(receiver);
5328 5329

            Debug.Assert(node.Type.IsDynamic());
5330
            _resultType = TypeSymbolWithAnnotations.Create(node.Type);
5331
            return null;
5332 5333 5334 5335
        }

        public override BoundNode VisitDynamicInvocation(BoundDynamicInvocation node)
        {
5336
            VisitRvalue(node.Expression);
5337
            VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt);
5338 5339

            Debug.Assert(node.Type.IsDynamic());
5340
            Debug.Assert(node.Type.IsReferenceType);
5341

5342
            // https://github.com/dotnet/roslyn/issues/29893 Update applicable members based on inferred argument types.
5343 5344
            NullableAnnotation nullableAnnotation = InferResultNullabilityFromApplicableCandidates(StaticCast<Symbol>.From(node.ApplicableMethods));
            _resultType = TypeSymbolWithAnnotations.Create(node.Type, nullableAnnotation);
5345
            return null;
5346 5347 5348 5349
        }

        public override BoundNode VisitEventAssignmentOperator(BoundEventAssignmentOperator node)
        {
5350
            VisitRvalue(node.ReceiverOpt);
5351 5352
            Debug.Assert(!IsConditionalState);
            var receiverOpt = node.ReceiverOpt;
5353
            var @event = node.Event;
5354
            if (!@event.IsStatic)
5355
            {
5356
                @event = (EventSymbol)AsMemberOfResultType(_resultType, @event);
5357 5358
                // https://github.com/dotnet/roslyn/issues/30598: Mark receiver as not null
                // after arguments have been visited, and only if the receiver has not changed.
5359 5360
                CheckPossibleNullReceiver(receiverOpt);
            }
5361
            VisitRvalue(node.Argument);
5362
            // https://github.com/dotnet/roslyn/issues/31018: Check for delegate mismatch.
5363
            SetResult(node); // https://github.com/dotnet/roslyn/issues/29969 Review whether this is the correct result
5364
            return null;
5365 5366 5367 5368
        }

        public override BoundNode VisitDynamicObjectCreationExpression(BoundDynamicObjectCreationExpression node)
        {
5369
            Debug.Assert(!IsConditionalState);
5370 5371 5372
            var arguments = node.Arguments;
            var argumentTypes = VisitArgumentsEvaluate(arguments, node.ArgumentRefKindsOpt);
            VisitObjectOrDynamicObjectCreation(node, arguments, argumentTypes, node.InitializerExpressionOpt);
5373
            return null;
5374 5375 5376 5377
        }

        public override BoundNode VisitObjectInitializerExpression(BoundObjectInitializerExpression node)
        {
5378
            // Only reachable from bad expression. Otherwise handled in VisitObjectCreationExpression().
5379
            SetResult(node);
5380
            return null;
5381 5382 5383 5384
        }

        public override BoundNode VisitCollectionInitializerExpression(BoundCollectionInitializerExpression node)
        {
5385
            // Only reachable from bad expression. Otherwise handled in VisitObjectCreationExpression().
5386
            SetResult(node);
5387
            return null;
5388 5389 5390 5391
        }

        public override BoundNode VisitDynamicCollectionElementInitializer(BoundDynamicCollectionElementInitializer node)
        {
5392
            // Only reachable from bad expression. Otherwise handled in VisitObjectCreationExpression().
5393
            SetResult(node);
5394
            return null;
5395 5396 5397 5398 5399
        }

        public override BoundNode VisitImplicitReceiver(BoundImplicitReceiver node)
        {
            var result = base.VisitImplicitReceiver(node);
5400
            SetResult(node);
5401 5402 5403 5404 5405 5406
            return result;
        }

        public override BoundNode VisitAnonymousPropertyDeclaration(BoundAnonymousPropertyDeclaration node)
        {
            var result = base.VisitAnonymousPropertyDeclaration(node);
5407
            SetResult(node);
5408 5409 5410 5411 5412 5413
            return result;
        }

        public override BoundNode VisitNoPiaObjectCreationExpression(BoundNoPiaObjectCreationExpression node)
        {
            var result = base.VisitNoPiaObjectCreationExpression(node);
5414
            _resultType = TypeSymbolWithAnnotations.Create(node.Type, NullableAnnotation.NotNullable);
5415 5416 5417 5418 5419 5420
            return result;
        }

        public override BoundNode VisitNewT(BoundNewT node)
        {
            var result = base.VisitNewT(node);
5421
            _resultType = TypeSymbolWithAnnotations.Create(node.Type, NullableAnnotation.NotNullable);
5422 5423 5424 5425 5426 5427
            return result;
        }

        public override BoundNode VisitArrayInitialization(BoundArrayInitialization node)
        {
            var result = base.VisitArrayInitialization(node);
5428
            SetResult(node);
5429 5430 5431 5432 5433
            return result;
        }

        private void SetUnknownResultNullability()
        {
5434
            _resultType = default;
5435 5436 5437 5438 5439
        }

        public override BoundNode VisitStackAllocArrayCreation(BoundStackAllocArrayCreation node)
        {
            var result = base.VisitStackAllocArrayCreation(node);
5440
            Debug.Assert((object)node.Type == null || node.Type.IsPointerType() || node.Type.IsRefLikeType);
5441
            SetResult(node);
5442 5443 5444 5445 5446
            return result;
        }

        public override BoundNode VisitDynamicIndexerAccess(BoundDynamicIndexerAccess node)
        {
5447 5448
            var receiver = node.ReceiverOpt;
            VisitRvalue(receiver);
5449 5450
            // https://github.com/dotnet/roslyn/issues/30598: Mark receiver as not null
            // after indices have been visited, and only if the receiver has not changed.
5451
            CheckPossibleNullReceiver(receiver);
5452
            VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt);
5453 5454 5455

            Debug.Assert(node.Type.IsDynamic());

5456
            // https://github.com/dotnet/roslyn/issues/29893 Update applicable members based on inferred argument types.
5457
            NullableAnnotation nullableAnnotation = (object)node.Type != null && !node.Type.IsValueType ?
5458
                InferResultNullabilityFromApplicableCandidates(StaticCast<Symbol>.From(node.ApplicableIndexers)) :
5459 5460
                NullableAnnotation.Unknown;
            _resultType = TypeSymbolWithAnnotations.Create(node.Type, nullableAnnotation);
5461
            return null;
5462 5463
        }

5464
        private void CheckPossibleNullReceiver(BoundExpression receiverOpt, bool checkNullableValueType = false, SyntaxNode syntaxOpt = null)
5465
        {
5466
            Debug.Assert(!this.IsConditionalState);
5467
            if (receiverOpt != null && this.State.Reachable)
5468
            {
5469
#if DEBUG
5470
                Debug.Assert(receiverOpt.Type is null || _resultType.TypeSymbol is null || AreCloseEnough(receiverOpt.Type, _resultType.TypeSymbol));
5471
#endif
5472
                var resultType = _resultType.TypeSymbol;
5473 5474 5475 5476 5477 5478
                if (resultType is null)
                {
                    return;
                }

                if (_resultType.GetValueNullableAnnotation().IsAnyNullable())
5479
                {
5480 5481 5482 5483 5484
                    bool isValueType = resultType.IsValueType;
                    if (isValueType && (!checkNullableValueType || !resultType.IsNullableType()))
                    {
                        return;
                    }
5485

5486
                    ReportSafetyDiagnostic(isValueType ? ErrorCode.WRN_NullableValueTypeMayBeNull : ErrorCode.WRN_NullReferenceReceiver, syntaxOpt ?? receiverOpt.Syntax);
5487
                }
5488

5489
                LearnFromNonNullTest(receiverOpt, ref this.State);
5490 5491 5492 5493 5494
            }
        }

        private static bool IsNullabilityMismatch(TypeSymbolWithAnnotations type1, TypeSymbolWithAnnotations type2)
        {
5495
            // Note, when we are paying attention to nullability, we ignore insignificant differences and oblivious mismatch.
5496
            // See TypeCompareKind.UnknownNullableModifierMatchesAny and TypeCompareKind.IgnoreInsignificantNullableModifiersDifference
5497
            return type1.Equals(type2, TypeCompareKind.AllIgnoreOptions) &&
5498
                !type1.Equals(type2, TypeCompareKind.AllIgnoreOptions & ~TypeCompareKind.IgnoreNullableModifiersForReferenceTypes);
5499 5500 5501 5502
        }

        private static bool IsNullabilityMismatch(TypeSymbol type1, TypeSymbol type2)
        {
5503
            // Note, when we are paying attention to nullability, we ignore insignificant differences and oblivious mismatch.
5504
            // See TypeCompareKind.UnknownNullableModifierMatchesAny and TypeCompareKind.IgnoreInsignificantNullableModifiersDifference
5505
            return type1.Equals(type2, TypeCompareKind.AllIgnoreOptions) &&
5506
                !type1.Equals(type2, TypeCompareKind.AllIgnoreOptions & ~TypeCompareKind.IgnoreNullableModifiersForReferenceTypes);
5507 5508
        }

5509
        private NullableAnnotation InferResultNullabilityFromApplicableCandidates(ImmutableArray<Symbol> applicableMembers)
5510 5511 5512
        {
            if (applicableMembers.IsDefaultOrEmpty)
            {
5513
                return NullableAnnotation.Unknown;
5514 5515
            }

5516
            NullableAnnotation result = NullableAnnotation.NotNullable;
5517 5518 5519 5520 5521 5522 5523

            foreach (Symbol member in applicableMembers)
            {
                TypeSymbolWithAnnotations type = member.GetTypeOrReturnType();

                if (type.IsReferenceType)
                {
5524 5525
                    NullableAnnotation memberResult = type.GetValueNullableAnnotation();
                    if (memberResult.IsAnyNullable())
5526 5527
                    {
                        // At least one candidate can produce null, assume dynamic access can produce null as well
5528
                        result = NullableAnnotation.Nullable;
5529 5530
                        break;
                    }
5531
                    else if (memberResult == NullableAnnotation.Unknown)
5532
                    {
5533
                        // At least one candidate can produce result of an unknown nullability.
5534
                        // At best, dynamic access can produce result of an unknown nullability as well.
5535
                        result = NullableAnnotation.Unknown;
5536 5537 5538 5539
                    }
                }
                else if (!type.IsValueType)
                {
5540
                    result = NullableAnnotation.Unknown;
5541 5542 5543
                }
            }

5544
            return result;
5545 5546 5547 5548 5549
        }

        public override BoundNode VisitQueryClause(BoundQueryClause node)
        {
            var result = base.VisitQueryClause(node);
5550
            SetResult(node); // https://github.com/dotnet/roslyn/issues/29863 Implement nullability analysis in LINQ queries
5551 5552 5553 5554 5555 5556
            return result;
        }

        public override BoundNode VisitNameOfOperator(BoundNameOfOperator node)
        {
            var result = base.VisitNameOfOperator(node);
5557
            _resultType = TypeSymbolWithAnnotations.Create(node.Type, NullableAnnotation.NotNullable);
5558 5559 5560 5561 5562 5563 5564 5565 5566 5567 5568 5569 5570
            return result;
        }

        public override BoundNode VisitNamespaceExpression(BoundNamespaceExpression node)
        {
            var result = base.VisitNamespaceExpression(node);
            SetUnknownResultNullability();
            return result;
        }

        public override BoundNode VisitInterpolatedString(BoundInterpolatedString node)
        {
            var result = base.VisitInterpolatedString(node);
5571
            _resultType = TypeSymbolWithAnnotations.Create(node.Type, NullableAnnotation.NotNullable);
5572 5573 5574 5575 5576 5577 5578 5579 5580 5581
            return result;
        }

        public override BoundNode VisitStringInsert(BoundStringInsert node)
        {
            var result = base.VisitStringInsert(node);
            SetUnknownResultNullability();
            return result;
        }

5582 5583 5584 5585 5586 5587 5588 5589 5590
        public override BoundNode VisitConvertedStackAllocExpression(BoundConvertedStackAllocExpression node)
        {
            var result = base.VisitConvertedStackAllocExpression(node);
            SetResult(node);
            return result;
        }

        public override BoundNode VisitDiscardExpression(BoundDiscardExpression node)
        {
5591
            SetResult(node);
5592 5593 5594
            return null;
        }

5595 5596
        public override BoundNode VisitThrowExpression(BoundThrowExpression node)
        {
5597 5598 5599 5600 5601 5602 5603 5604 5605 5606 5607 5608 5609 5610 5611 5612 5613 5614 5615 5616 5617 5618 5619 5620 5621 5622 5623 5624 5625 5626
            VisitThrow(node.Expression);
            _resultType = default;
            return null;
        }

        public override BoundNode VisitThrowStatement(BoundThrowStatement node)
        {
            VisitThrow(node.ExpressionOpt);
            return null;
        }

        private void VisitThrow(BoundExpression expr)
        {
            if (expr != null)
            {
                var result = VisitRvalueWithResult(expr);
                // Cases:
                // null
                // null!
                // Other (typed) expression, including suppressed ones
                if (expr.ConstantValue?.IsNull == true ||
                    result.GetValueNullableAnnotation().IsAnyNullable())
                {
                    if (!expr.IsSuppressed)
                    {
                        ReportSafetyDiagnostic(ErrorCode.WRN_PossibleNull, expr.Syntax);
                    }
                }
            }
            SetUnreachable();
5627 5628
        }

5629 5630 5631 5632 5633 5634 5635
        public override BoundNode VisitYieldReturnStatement(BoundYieldReturnStatement node)
        {
            BoundExpression expr = node.Expression;
            if (expr == null)
            {
                return null;
            }
5636
            var method = (MethodSymbol)_symbol;
5637 5638 5639
            TypeSymbolWithAnnotations elementType = InMethodBinder.GetIteratorElementTypeFromReturnType(compilation, RefKind.None,
                method.ReturnType.TypeSymbol, errorLocationNode: null, diagnostics: null).elementType;

5640 5641 5642 5643
            VisitOptionalImplicitConversion(expr, elementType, useLegacyWarnings: false, AssignmentKind.Return);
            return null;
        }

5644 5645 5646 5647 5648
        protected override string Dump(LocalState state)
        {
            return string.Empty;
        }

A
Andy Gocke 已提交
5649
        protected override void Meet(ref LocalState self, ref LocalState other)
5650 5651 5652 5653 5654 5655 5656 5657 5658
        {
            if (self.Capacity != other.Capacity)
            {
                Normalize(ref self);
                Normalize(ref other);
            }

            for (int slot = 1; slot < self.Capacity; slot++)
            {
5659 5660
                NullableAnnotation selfAnnotation = self[slot];
                NullableAnnotation otherAnnotation = other[slot];
5661
                NullableAnnotation union = selfAnnotation.MeetForFlowAnalysisFinally(otherAnnotation);
5662 5663

                if (selfAnnotation != union)
5664 5665 5666
                {
                    self[slot] = union;
                }
5667 5668 5669 5670 5671 5672 5673

                bool selfIsAssigned = self.IsAssigned(slot);
                bool isAssigned = selfIsAssigned || other.IsAssigned(slot);
                if (selfIsAssigned != isAssigned)
                {
                    self.SetAssigned(slot, isAssigned);
                }
5674 5675 5676
            }
        }

A
Andy Gocke 已提交
5677
        protected override bool Join(ref LocalState self, ref LocalState other)
5678 5679 5680 5681 5682 5683 5684 5685 5686 5687 5688 5689 5690
        {
            if (self.Reachable == other.Reachable)
            {
                bool result = false;

                if (self.Capacity != other.Capacity)
                {
                    Normalize(ref self);
                    Normalize(ref other);
                }

                for (int slot = 1; slot < self.Capacity; slot++)
                {
5691
                    NullableAnnotation selfAnnotation = self[slot];
5692
                    NullableAnnotation intersection = selfAnnotation.JoinForFlowAnalysisBranches(other[slot], (slot, this), IsPossiblyNullableReferenceTypeTypeParameterDelegate);
5693
                    if (selfAnnotation != intersection)
5694 5695 5696 5697
                    {
                        self[slot] = intersection;
                        result = true;
                    }
5698 5699 5700 5701 5702 5703 5704 5705

                    bool selfIsAssigned = self.IsAssigned(slot);
                    bool isAssigned = selfIsAssigned && other.IsAssigned(slot);
                    if (selfIsAssigned != isAssigned)
                    {
                        self.SetAssigned(slot, isAssigned);
                        result = true;
                    }
5706 5707 5708 5709 5710 5711
                }

                return result;
            }
            else if (!self.Reachable)
            {
5712
                self = other.Clone();
5713 5714 5715 5716 5717 5718 5719 5720 5721
                return true;
            }
            else
            {
                Debug.Assert(!other.Reachable);
                return false;
            }
        }

5722
        private readonly static Func<(int slot, NullableWalker self), bool> IsPossiblyNullableReferenceTypeTypeParameterDelegate = args =>
5723 5724 5725 5726 5727
        {
            Symbol symbol = args.self.variableBySlot[args.slot].Symbol;
            return (object)symbol != null && VariableType(symbol).TypeSymbol?.IsPossiblyNullableReferenceTypeTypeParameter() == true;
        };

5728
        [DebuggerDisplay("{GetDebuggerDisplay(), nq}")]
5729
#if REFERENCE_STATE
A
Andy Gocke 已提交
5730
        internal class LocalState : ILocalState
5731
#else
A
Andy Gocke 已提交
5732
        internal struct LocalState : ILocalState
5733 5734
#endif
        {
5735
            private BitVector _assigned;
5736 5737
            private ArrayBuilder<NullableAnnotation> _state;
            public bool Reachable { get; }
5738

5739
            internal LocalState(bool reachable, BitVector assigned, ArrayBuilder<NullableAnnotation> state)
5740
            {
5741
                Debug.Assert(!assigned.IsNull);
5742
                this.Reachable = reachable;
5743
                this._assigned = assigned;
5744
                this._state = state;
5745 5746
            }

5747
            internal int Capacity => _state?.Count ?? 0;
5748 5749 5750

            internal void EnsureCapacity(int capacity)
            {
5751 5752
                _assigned.EnsureCapacity(capacity);

5753 5754 5755 5756 5757 5758 5759 5760 5761
                if (_state == null)
                {
                    _state = new ArrayBuilder<NullableAnnotation>(capacity);
                }

                if (_state.Count < capacity)
                {
                    _state.Count = capacity;
                }
5762 5763
            }

5764
            internal NullableAnnotation this[int slot]
5765 5766 5767
            {
                get
                {
5768 5769 5770 5771 5772 5773
                    if (slot < Capacity)
                    {
                        return _state[slot];
                    }

                    return NullableAnnotation.Unknown;
5774 5775 5776
                }
                set
                {
5777 5778
                    EnsureCapacity(slot + 1);
                    _state[slot] = value;
5779
                    SetAssigned(slot, true);
5780 5781 5782
                }
            }

5783 5784 5785 5786 5787 5788 5789 5790 5791 5792
            internal void SetAssigned(int slot, bool value)
            {
                _assigned[slot] = value;
            }

            internal bool IsAssigned(int slot)
            {
                return _assigned[slot];
            }

5793 5794 5795 5796 5797 5798
            /// <summary>
            /// Produce a duplicate of this flow analysis state.
            /// </summary>
            /// <returns></returns>
            public LocalState Clone()
            {
5799
                ArrayBuilder<NullableAnnotation> clone;
5800

5801 5802 5803 5804 5805
                if (_state == null)
                {
                    clone = null;
                }
                else
5806
                {
5807 5808 5809
                    clone = new ArrayBuilder<NullableAnnotation>(_state.Count);
                    clone.Count = 0;
                    clone.AddRange(_state);
5810
                }
5811

5812
                return new LocalState(Reachable, _assigned.Clone(), clone);
5813
            }
5814

5815
            internal string GetDebuggerDisplay()
5816 5817 5818 5819 5820 5821
            {
                var pooledBuilder = PooledStringBuilder.GetInstance();
                var builder = pooledBuilder.Builder;
                builder.Append(" ");
                for (int i = this.Capacity - 1; i >= 0; i--)
                {
5822 5823 5824 5825 5826 5827 5828 5829
                    string append;

                    switch (_state[i])
                    {
                        case NullableAnnotation.Unknown:
                            append = "__";
                            break;

5830
                        case NullableAnnotation.Annotated:
5831 5832 5833
                            append = "?-";
                            break;

5834
                        case NullableAnnotation.Nullable:
5835 5836 5837
                            append = "?+";
                            break;

5838
                        case NullableAnnotation.NotNullable:
5839 5840 5841
                            append = "!+";
                            break;

5842
                        case NullableAnnotation.NotAnnotated:
5843 5844 5845 5846 5847 5848 5849 5850
                            append = "!-";
                            break;

                        default:
                            throw ExceptionUtilities.UnexpectedValue(_state[i]);
                    }

                    builder.Append(append);
5851 5852 5853 5854
                }

                return pooledBuilder.ToStringAndFree();
            }
5855 5856 5857
        }
    }
}