diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 28095859578b..23f65dcb803d 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -1121,7 +1121,8 @@ private BoundExpression BindTypeOf(TypeOfExpressionSyntax node, DiagnosticBag di TypeofBinder typeofBinder = new TypeofBinder(typeSyntax, this); //has special handling for unbound types AliasSymbol alias; - TypeSymbol type = typeofBinder.BindType(typeSyntax, diagnostics, out alias).Type; + TypeWithAnnotations typeWithAnnotations = typeofBinder.BindType(typeSyntax, diagnostics, out alias); + TypeSymbol type = typeWithAnnotations.Type; bool hasError = false; @@ -1133,7 +1134,7 @@ private BoundExpression BindTypeOf(TypeOfExpressionSyntax node, DiagnosticBag di hasError = true; } - BoundTypeExpression boundType = new BoundTypeExpression(typeSyntax, alias, type, type.IsErrorType()); + BoundTypeExpression boundType = new BoundTypeExpression(typeSyntax, alias, typeWithAnnotations, type.IsErrorType()); return new BoundTypeOfOperator(node, boundType, null, this.GetWellKnownType(WellKnownType.System_Type, diagnostics, node), hasError); } @@ -1141,11 +1142,12 @@ private BoundExpression BindSizeOf(SizeOfExpressionSyntax node, DiagnosticBag di { ExpressionSyntax typeSyntax = node.Type; AliasSymbol alias; - TypeSymbol type = this.BindType(typeSyntax, diagnostics, out alias).Type; + TypeWithAnnotations typeWithAnnotations = this.BindType(typeSyntax, diagnostics, out alias); + TypeSymbol type = typeWithAnnotations.Type; bool typeHasErrors = type.IsErrorType() || CheckManagedAddr(type, node, diagnostics); - BoundTypeExpression boundType = new BoundTypeExpression(typeSyntax, alias, type, typeHasErrors); + BoundTypeExpression boundType = new BoundTypeExpression(typeSyntax, alias, typeWithAnnotations, typeHasErrors); ConstantValue constantValue = GetConstantSizeOf(type); bool hasErrors = ReferenceEquals(constantValue, null) && ReportUnsafeIfNotAllowed(node, diagnostics, type); return new BoundSizeOfOperator(node, boundType, constantValue, @@ -5716,7 +5718,7 @@ private BoundExpression BindMemberAccessWithBoundLeft( Error(diagnostics, lookupResult.Error, right); return new BoundTypeExpression(node, null, - new ExtendedErrorTypeSymbol(GetContainingNamespaceOrType(symbols[0]), symbols.ToImmutable(), lookupResult.Kind, lookupResult.Error, rightArity)); + new ExtendedErrorTypeSymbol(GetContainingNamespaceOrType(symbols[0]), symbols.ToImmutable(), lookupResult.Kind, lookupResult.Error, rightArity)); } else if (lookupResult.Kind == LookupResultKind.Empty) { diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs index 258b717842e4..8d1deb82f2d6 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs @@ -2783,7 +2783,7 @@ private BoundExpression BindIsOperator(BinaryExpressionSyntax node, DiagnosticBa operandHasErrors = true; } - var typeExpression = new BoundTypeExpression(node.Right, alias, targetType); + var typeExpression = new BoundTypeExpression(node.Right, alias, targetTypeWithAnnotations); var targetTypeKind = targetType.TypeKind; if (operandHasErrors || IsOperatorErrors(node, operand.Type, typeExpression, diagnostics)) { @@ -3151,7 +3151,7 @@ private BoundExpression BindAsOperator(BinaryExpressionSyntax node, DiagnosticBa AliasSymbol alias; TypeWithAnnotations targetTypeWithAnnotations = BindType(node.Right, diagnostics, out alias); TypeSymbol targetType = targetTypeWithAnnotations.Type; - var typeExpression = new BoundTypeExpression(node.Right, alias, targetType); + var typeExpression = new BoundTypeExpression(node.Right, alias, targetTypeWithAnnotations); var targetTypeKind = targetType.TypeKind; var resultType = targetType; diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs index 09467d79a56c..c420becf6606 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs @@ -363,7 +363,7 @@ private BoundPattern BindDeclarationPattern( TypeSymbol declType = boundDeclType.Type; inputValEscape = GetValEscape(declType, inputValEscape); BindPatternDesignation( - node.Designation, boundDeclType.Type, inputValEscape, typeSyntax, diagnostics, + node.Designation, boundDeclType.TypeWithAnnotations, inputValEscape, typeSyntax, diagnostics, ref hasErrors, out Symbol variableSymbol, out BoundExpression variableAccess); return new BoundDeclarationPattern(node, variableSymbol, variableAccess, boundDeclType, isVar: false, inputType, hasErrors); } @@ -379,14 +379,14 @@ private BoundTypeExpression BindPatternType( TypeWithAnnotations declType = BindType(typeSyntax, diagnostics, out AliasSymbol aliasOpt); Debug.Assert(declType.HasType); Debug.Assert(typeSyntax.Kind() != SyntaxKind.NullableType); // the syntax does not permit nullable annotations - BoundTypeExpression boundDeclType = new BoundTypeExpression(typeSyntax, aliasOpt, inferredType: false, type: declType.Type); + BoundTypeExpression boundDeclType = new BoundTypeExpression(typeSyntax, aliasOpt, inferredType: false, type: declType); hasErrors |= CheckValidPatternType(typeSyntax, inputType, declType.Type, patternTypeWasInSource: true, diagnostics: diagnostics); return boundDeclType; } private void BindPatternDesignation( VariableDesignationSyntax designation, - TypeSymbol declType, + TypeWithAnnotations declType, uint inputValEscape, TypeSyntax typeSyntax, DiagnosticBag diagnostics, @@ -405,18 +405,18 @@ private void BindPatternDesignation( if ((InConstructorInitializer || InFieldInitializer) && ContainingMemberOrLambda.ContainingSymbol.Kind == SymbolKind.NamedType) CheckFeatureAvailability(designation, MessageID.IDS_FeatureExpressionVariablesInQueriesAndInitializers, diagnostics); - localSymbol.SetTypeWithAnnotations(TypeWithAnnotations.Create(declType)); - localSymbol.SetValEscape(GetValEscape(declType, inputValEscape)); + localSymbol.SetTypeWithAnnotations(declType); + localSymbol.SetValEscape(GetValEscape(declType.Type, inputValEscape)); // Check for variable declaration errors. hasErrors |= localSymbol.ScopeBinder.ValidateDeclarationNameConflictsInScope(localSymbol, diagnostics); if (!hasErrors) - hasErrors = CheckRestrictedTypeInAsync(this.ContainingMemberOrLambda, declType, diagnostics, typeSyntax ?? (SyntaxNode)designation); + hasErrors = CheckRestrictedTypeInAsync(this.ContainingMemberOrLambda, declType.Type, diagnostics, typeSyntax ?? (SyntaxNode)designation); variableSymbol = localSymbol; variableAccess = new BoundLocal( - syntax: designation, localSymbol: localSymbol, constantValueOpt: null, type: declType); + syntax: designation, localSymbol: localSymbol, constantValueOpt: null, type: declType.Type); return; } else @@ -425,7 +425,7 @@ private void BindPatternDesignation( Debug.Assert(designation.SyntaxTree.Options.Kind != SourceCodeKind.Regular); GlobalExpressionVariable expressionVariableField = LookupDeclaredField(singleVariableDesignation); DiagnosticBag tempDiagnostics = DiagnosticBag.GetInstance(); - expressionVariableField.SetTypeWithAnnotations(TypeWithAnnotations.Create(declType), tempDiagnostics); + expressionVariableField.SetTypeWithAnnotations(declType, tempDiagnostics); tempDiagnostics.Free(); BoundExpression receiver = SynthesizeReceiver(designation, expressionVariableField, diagnostics); @@ -454,7 +454,7 @@ private static uint GetValEscape(TypeSymbol type, uint possibleValEscape) return type.IsRefLikeType ? possibleValEscape : Binder.ExternalScope; } - TypeSymbol BindRecursivePatternType( + TypeWithAnnotations BindRecursivePatternType( TypeSyntax typeSyntax, TypeSymbol inputType, DiagnosticBag diagnostics, @@ -464,12 +464,13 @@ TypeSymbol BindRecursivePatternType( if (typeSyntax != null) { boundDeclType = BindPatternType(typeSyntax, inputType, diagnostics, ref hasErrors); - return boundDeclType.Type; + return boundDeclType.TypeWithAnnotations; } else { boundDeclType = null; - return inputType.StrippedType(); // remove the nullable part of the input's type + // remove the nullable part of the input's type; e.g. a nullable int becomes an int in a recursive pattern + return new TypeWithState(inputType.StrippedType(), NullableFlowState.MaybeNull).ToTypeWithAnnotations(); } } @@ -493,7 +494,8 @@ private BoundPattern BindRecursivePattern(RecursivePatternSyntax node, TypeSymbo } TypeSyntax typeSyntax = node.Type; - TypeSymbol declType = BindRecursivePatternType(typeSyntax, inputType, diagnostics, ref hasErrors, out BoundTypeExpression boundDeclType); + TypeWithAnnotations declTypeWithAnnotations = BindRecursivePatternType(typeSyntax, inputType, diagnostics, ref hasErrors, out BoundTypeExpression boundDeclType); + TypeSymbol declType = declTypeWithAnnotations.Type; inputValEscape = GetValEscape(declType, inputValEscape); MethodSymbol deconstructMethod = null; @@ -553,7 +555,7 @@ private BoundPattern BindRecursivePattern(RecursivePatternSyntax node, TypeSymbo } BindPatternDesignation( - node.Designation, declType, inputValEscape, typeSyntax, diagnostics, + node.Designation, declTypeWithAnnotations, inputValEscape, typeSyntax, diagnostics, ref hasErrors, out Symbol variableSymbol, out BoundExpression variableAccess); return new BoundRecursivePattern( syntax: node, declaredType: boundDeclType, inputType: inputType, deconstructMethod: deconstructMethod, @@ -839,10 +841,11 @@ private BoundPattern BindVarDesignation( } case SyntaxKind.SingleVariableDesignation: { + var declType = new TypeWithState(inputType, NullableFlowState.MaybeNull).ToTypeWithAnnotations(); BindPatternDesignation( - designation: node, declType: inputType, inputValEscape: inputValEscape, typeSyntax: null, diagnostics: diagnostics, hasErrors: ref hasErrors, + designation: node, declType: declType, inputValEscape: inputValEscape, typeSyntax: null, diagnostics: diagnostics, hasErrors: ref hasErrors, variableSymbol: out Symbol variableSymbol, variableAccess: out BoundExpression variableAccess); - var boundOperandType = new BoundTypeExpression(syntax: node, aliasOpt: null, type: inputType); // fake a type expression for the variable's type + var boundOperandType = new BoundTypeExpression(syntax: node, aliasOpt: null, type: declType); // fake a type expression for the variable's type // We continue to use a BoundDeclarationPattern for the var pattern, as they have more in common. return new BoundDeclarationPattern( node.Parent.Kind() == SyntaxKind.VarPattern ? node.Parent : node, // for `var x` use whole pattern, otherwise use designation for the syntax diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index 32a13b4858cf..b602dbcc1610 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -1076,7 +1076,7 @@ protected BoundLocalDeclaration BindVariableDeclaration( } diagnostics.AddRangeAndFree(localDiagnostics); - var boundDeclType = new BoundTypeExpression(typeSyntax, aliasOpt, inferredType: isVar, type: declTypeOpt.Type); + var boundDeclType = new BoundTypeExpression(typeSyntax, aliasOpt, inferredType: isVar, type: declTypeOpt); return new BoundLocalDeclaration(associatedSyntaxNode, localSymbol, boundDeclType, initializerOpt, arguments, hasErrors); } diff --git a/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs b/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs index 8cf706319517..46d3524b71bf 100644 --- a/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs +++ b/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs @@ -24,7 +24,7 @@ namespace Microsoft.CodeAnalysis.CSharp /// represents the test performed by evaluating the expression of the /// when-clause of a switch case; and represents a leaf node when we /// have finally determined exactly which case matches. Each test processes a single input, and there are - /// four kinds: tests a value for null; + /// four kinds: tests a value for null; /// tests that a value is not null; checks if the value is of a given type; /// and checks if the value is equal to a given constant. Of the evaluations, /// there are which represents an invocation of a type's @@ -121,7 +121,7 @@ private BoundDecisionDag CreateDecisionDagForIsPattern( BoundPattern pattern, LabelSymbol whenTrueLabel) { - var rootIdentifier = new BoundDagTemp(inputExpression.Syntax, inputExpression.Type, source: null, index: 0); + var rootIdentifier = BoundDagTemp.ForOriginalInput(inputExpression); return MakeDecisionDag(syntax, ImmutableArray.Create(MakeTestsForPattern(index: 1, pattern.Syntax, rootIdentifier, pattern, whenClause: null, whenTrueLabel))); } @@ -130,7 +130,7 @@ private BoundDecisionDag CreateDecisionDagForSwitchStatement( BoundExpression switchGoverningExpression, ImmutableArray switchSections) { - var rootIdentifier = new BoundDagTemp(switchGoverningExpression.Syntax, switchGoverningExpression.Type, source: null, index: 0); + var rootIdentifier = BoundDagTemp.ForOriginalInput(switchGoverningExpression); int i = 0; var builder = ArrayBuilder.GetInstance(switchSections.Length); foreach (BoundSwitchSection section in switchSections) @@ -155,7 +155,7 @@ private BoundDecisionDag CreateDecisionDagForSwitchExpression( BoundExpression switchExpressionInput, ImmutableArray switchArms) { - var rootIdentifier = new BoundDagTemp(switchExpressionInput.Syntax, switchExpressionInput.Type, source: null, index: 0); + var rootIdentifier = BoundDagTemp.ForOriginalInput(switchExpressionInput); int i = 0; var builder = ArrayBuilder.GetInstance(switchArms.Length); foreach (BoundSwitchExpressionArm arm in switchArms) @@ -311,18 +311,18 @@ private void MakeTestsAndBindings( tests.Add(new BoundDagTypeTest(syntax, iTupleType, input)); var valueAsITupleEvaluation = new BoundDagTypeEvaluation(syntax, iTupleType, input); tests.Add(valueAsITupleEvaluation); - var valueAsITuple = new BoundDagTemp(syntax, iTupleType, valueAsITupleEvaluation, 0); + var valueAsITuple = new BoundDagTemp(syntax, iTupleType, valueAsITupleEvaluation); var lengthEvaluation = new BoundDagPropertyEvaluation(syntax, getLengthProperty, valueAsITuple); tests.Add(lengthEvaluation); - var lengthTemp = new BoundDagTemp(syntax, this._compilation.GetSpecialType(SpecialType.System_Int32), lengthEvaluation, 0); + var lengthTemp = new BoundDagTemp(syntax, this._compilation.GetSpecialType(SpecialType.System_Int32), lengthEvaluation); tests.Add(new BoundDagValueTest(syntax, ConstantValue.Create(patternLength), lengthTemp)); for (int i = 0; i < patternLength; i++) { var indexEvaluation = new BoundDagIndexEvaluation(syntax, getItemProperty, i, valueAsITuple); tests.Add(indexEvaluation); - var indexTemp = new BoundDagTemp(syntax, objectType, indexEvaluation, 0); + var indexTemp = new BoundDagTemp(syntax, objectType, indexEvaluation); MakeTestsAndBindings(indexTemp, pattern.Subpatterns[i].Pattern, tests, bindings); } } @@ -393,7 +393,7 @@ private BoundDagTemp MakeConvertToType( } var evaluation = new BoundDagTypeEvaluation(syntax, type, input); - input = new BoundDagTemp(syntax, type, evaluation, index: 0); + input = new BoundDagTemp(syntax, type, evaluation); tests.Add(evaluation); } @@ -408,7 +408,7 @@ private void MakeTestsAndBindings( { if (constant.ConstantValue == ConstantValue.Null) { - tests.Add(new BoundDagNullTest(constant.Syntax, input)); + tests.Add(new BoundDagExplicitNullTest(constant.Syntax, input)); } else { @@ -479,7 +479,7 @@ private void MakeTestsAndBindings( FieldSymbol field = elements[i]; var evaluation = new BoundDagFieldEvaluation(syntax, field, input); // fetch the ItemN field tests.Add(evaluation); - var output = new BoundDagTemp(syntax, field.Type, evaluation, index: 0); + var output = new BoundDagTemp(syntax, field.Type, evaluation); MakeTestsAndBindings(output, pattern, tests, bindings); } } @@ -516,7 +516,7 @@ private void MakeTestsAndBindings( } tests.Add(evaluation); - var output = new BoundDagTemp(pattern.Syntax, symbol.GetTypeOrReturnType().Type, evaluation, index: 0); + var output = new BoundDagTemp(pattern.Syntax, symbol.GetTypeOrReturnType().Type, evaluation); MakeTestsAndBindings(output, pattern, tests, bindings); } } @@ -620,9 +620,20 @@ DagState uniqifyState(ImmutableArray cases) // An evaluation is considered to always succeed, so there is no false branch break; case BoundDagTest d: - SplitCases(state.Cases, d, out ImmutableArray whenTrueDecisions, out ImmutableArray whenFalseDecisions); + bool foundExplicitNullTest = false; + SplitCases( + state.Cases, d, + out ImmutableArray whenTrueDecisions, + out ImmutableArray whenFalseDecisions, + ref foundExplicitNullTest); state.TrueBranch = uniqifyState(whenTrueDecisions); state.FalseBranch = uniqifyState(whenFalseDecisions); + if (foundExplicitNullTest && d is BoundDagNonNullTest t) + { + // Turn an "implicit" non-null test into an explicit null test to preserve its explicitness + state.SelectedTest = new BoundDagExplicitNullTest(t.Syntax, t.Input, t.HasErrors); + (state.TrueBranch, state.FalseBranch) = (state.FalseBranch, state.TrueBranch); + } break; case var n: throw ExceptionUtilities.UnexpectedValue(n.Kind); @@ -721,13 +732,14 @@ private void SplitCases( ImmutableArray cases, BoundDagTest d, out ImmutableArray whenTrue, - out ImmutableArray whenFalse) + out ImmutableArray whenFalse, + ref bool foundExplicitNullTest) { var whenTrueBuilder = ArrayBuilder.GetInstance(); var whenFalseBuilder = ArrayBuilder.GetInstance(); foreach (RemainingTestsForCase c in cases) { - FilterCase(c, d, whenTrueBuilder, whenFalseBuilder); + FilterCase(c, d, whenTrueBuilder, whenFalseBuilder, ref foundExplicitNullTest); } whenTrue = whenTrueBuilder.ToImmutableAndFree(); @@ -738,7 +750,8 @@ private void FilterCase( RemainingTestsForCase testsForCase, BoundDagTest test, ArrayBuilder whenTrueBuilder, - ArrayBuilder whenFalseBuilder) + ArrayBuilder whenFalseBuilder, + ref bool foundExplicitNullTest) { var trueBuilder = ArrayBuilder.GetInstance(); var falseBuilder = ArrayBuilder.GetInstance(); @@ -751,7 +764,8 @@ private void FilterCase( trueTestPermitsTrueOther: out bool trueDecisionPermitsTrueOther, falseTestPermitsTrueOther: out bool falseDecisionPermitsTrueOther, trueTestImpliesTrueOther: out bool trueDecisionImpliesTrueOther, - falseTestImpliesTrueOther: out bool falseDecisionImpliesTrueOther); + falseTestImpliesTrueOther: out bool falseDecisionImpliesTrueOther, + foundExplicitNullTest: ref foundExplicitNullTest); if (trueDecisionPermitsTrueOther) { if (!trueDecisionImpliesTrueOther) @@ -827,7 +841,8 @@ private void CheckConsistentDecision( out bool trueTestPermitsTrueOther, out bool falseTestPermitsTrueOther, out bool trueTestImpliesTrueOther, - out bool falseTestImpliesTrueOther) + out bool falseTestImpliesTrueOther, + ref bool foundExplicitNullTest) { // innocent until proven guilty trueTestPermitsTrueOther = true; @@ -858,7 +873,8 @@ private void CheckConsistentDecision( // !(v != null) --> !(v == K) falseTestPermitsTrueOther = false; break; - case BoundDagNullTest v2: + case BoundDagExplicitNullTest v2: + foundExplicitNullTest = true; // v != null --> !(v == null) trueTestPermitsTrueOther = false; // !(v != null) --> v == null @@ -914,7 +930,8 @@ private void CheckConsistentDecision( break; case BoundDagValueTest v2: break; - case BoundDagNullTest v2: + case BoundDagExplicitNullTest v2: + foundExplicitNullTest = true; // v is T --> !(v == null) trueTestPermitsTrueOther = false; break; @@ -929,7 +946,8 @@ private void CheckConsistentDecision( break; case BoundDagTypeTest t2: break; - case BoundDagNullTest v2: + case BoundDagExplicitNullTest v2: + foundExplicitNullTest = true; // v == K --> !(v == null) trueTestPermitsTrueOther = false; break; @@ -959,7 +977,8 @@ private void CheckConsistentDecision( break; } break; - case BoundDagNullTest v1: + case BoundDagExplicitNullTest v1: + foundExplicitNullTest = true; switch (other) { case BoundDagNonNullTest n2: @@ -972,7 +991,8 @@ private void CheckConsistentDecision( // v == null --> !(v is T) trueTestPermitsTrueOther = false; break; - case BoundDagNullTest v2: + case BoundDagExplicitNullTest v2: + foundExplicitNullTest = true; // v == null --> v == null trueTestImpliesTrueOther = true; // !(v == null) --> !(v == null) @@ -1067,7 +1087,7 @@ int tempIdentifier(BoundDagEvaluation e) string tempName(BoundDagTemp t) { - return $"t{tempIdentifier(t.Source)}{(t.Index != 0 ? $".{t.Index.ToString()}" : "")}"; + return $"t{tempIdentifier(t.Source)}"; } var resultBuilder = PooledStringBuilder.GetInstance(); @@ -1264,7 +1284,7 @@ private static bool SameTest(BoundDagTest x, BoundDagTest y) case BoundKind.DagValueTest: return ((BoundDagValueTest)x).Value == ((BoundDagValueTest)y).Value; - case BoundKind.DagNullTest: + case BoundKind.DagExplicitNullTest: case BoundKind.DagNonNullTest: return true; diff --git a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs index f804fcbda8cc..f88a7d13853d 100644 --- a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs @@ -219,7 +219,7 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics, } } - TypeSymbol iterationVariableType; + TypeWithAnnotations iterationVariableType; BoundTypeExpression boundIterationVariableType; bool hasNameConflicts = false; BoundForEachDeconstructStep deconstructStep = null; @@ -251,7 +251,7 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics, Debug.Assert(declType.HasType); } - iterationVariableType = declType.Type; + iterationVariableType = declType; boundIterationVariableType = new BoundTypeExpression(typeSyntax, alias, iterationVariableType); SourceLocalSymbol local = this.IterationVariable; @@ -309,12 +309,12 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics, case SyntaxKind.ForEachVariableStatement: { var node = (ForEachVariableStatementSyntax)_syntax; - iterationVariableType = inferredType.Type ?? CreateErrorType("var"); + iterationVariableType = inferredType.HasType ? inferredType : TypeWithAnnotations.Create(CreateErrorType("var")); var variables = node.Variable; if (variables.IsDeconstructionLeft()) { - var valuePlaceholder = new BoundDeconstructValuePlaceholder(_syntax.Expression, collectionEscape, iterationVariableType).MakeCompilerGenerated(); + var valuePlaceholder = new BoundDeconstructValuePlaceholder(_syntax.Expression, collectionEscape, iterationVariableType.Type).MakeCompilerGenerated(); DeclarationExpressionSyntax declaration = null; ExpressionSyntax expression = null; BoundDeconstructionAssignmentOperator deconstruction = BindDeconstruction( @@ -370,7 +370,7 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics, iterationVariables.All(local => local.DeclarationKind == LocalDeclarationKind.ForEachIterationVariable), "Should not have iteration variables that are not ForEachIterationVariable in valid code"); - hasErrors = hasErrors || boundIterationVariableType.HasErrors || iterationVariableType.IsErrorType(); + hasErrors = hasErrors || boundIterationVariableType.HasErrors || iterationVariableType.Type.IsErrorType(); // Skip the conversion checks and array/enumerator differentiation if we know we have an error (except local name conflicts). if (hasErrors) @@ -404,7 +404,7 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics, // but it turns out that these are equivalent (when both are available). HashSet useSiteDiagnostics = null; - Conversion elementConversion = this.Conversions.ClassifyConversionFromType(inferredType.Type, iterationVariableType, ref useSiteDiagnostics, forCast: true); + Conversion elementConversion = this.Conversions.ClassifyConversionFromType(inferredType.Type, iterationVariableType.Type, ref useSiteDiagnostics, forCast: true); if (!elementConversion.IsValid) { @@ -415,7 +415,7 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics, } else { - SymbolDistinguisher distinguisher = new SymbolDistinguisher(this.Compilation, inferredType.Type, iterationVariableType); + SymbolDistinguisher distinguisher = new SymbolDistinguisher(this.Compilation, inferredType.Type, iterationVariableType.Type); diagnostics.Add(ErrorCode.ERR_NoExplicitConv, foreachKeyword.GetLocation(), distinguisher.First, distinguisher.Second); } hasErrors = true; diff --git a/src/Compilers/CSharp/Portable/Binder/SwitchExpressionBinder.cs b/src/Compilers/CSharp/Portable/Binder/SwitchExpressionBinder.cs index 723358f899f4..0bd067ae4562 100644 --- a/src/Compilers/CSharp/Portable/Binder/SwitchExpressionBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/SwitchExpressionBinder.cs @@ -80,7 +80,7 @@ private bool CheckSwitchExpressionExhaustive( } // We only report exhaustive warnings when the default label is reachable through some series of - // tests that do not include a test in which the value is know to be null. Handling paths with + // tests that do not include a test in which the value is known to be null. Handling paths with // nulls is the job of the nullable walker. foreach (var n in TopologicalSort.IterativeSort(new[] { decisionDag.RootNode }, nonNullSuccessors)) { @@ -102,7 +102,7 @@ ImmutableArray nonNullSuccessors(BoundDecisionDagNode n) { case BoundDagNonNullTest t: // checks that the input is not null return ImmutableArray.Create(p.WhenTrue); - case BoundDagNullTest t: // checks that the input is null + case BoundDagExplicitNullTest t: // checks that the input is null return ImmutableArray.Create(p.WhenFalse); default: return BoundDecisionDag.Successors(n); diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundDecisionDag.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundDecisionDag.cs index e3f4c9c1c2e7..19a37fc28c84 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundDecisionDag.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundDecisionDag.cs @@ -170,7 +170,7 @@ BoundDecisionDagNode makeReplacement(BoundDecisionDagNode dag, Func - - - @@ -206,6 +203,9 @@ identify the scenarios in which this property is populated. --> + + + + - - + + diff --git a/src/Compilers/CSharp/Portable/BoundTree/Constructors.cs b/src/Compilers/CSharp/Portable/BoundTree/Constructors.cs index cbce0c9df1af..7b4821928e2a 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/Constructors.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/Constructors.cs @@ -424,20 +424,46 @@ public BoundParameter(SyntaxNode syntax, ParameterSymbol parameterSymbol) internal sealed partial class BoundTypeExpression { - public BoundTypeExpression(SyntaxNode syntax, AliasSymbol aliasOpt, TypeSymbol type, bool hasErrors = false) + public BoundTypeExpression(SyntaxNode syntax, AliasSymbol aliasOpt, bool inferredType, BoundTypeExpression boundContainingTypeOpt, TypeWithAnnotations typeWithAnnotations, bool hasErrors = false) + : this(syntax, aliasOpt, inferredType, boundContainingTypeOpt, typeWithAnnotations, typeWithAnnotations.Type, hasErrors) + { + Debug.Assert((object)typeWithAnnotations.Type != null, "Field 'type' cannot be null"); + } + + public BoundTypeExpression(SyntaxNode syntax, AliasSymbol aliasOpt, TypeWithAnnotations type, bool hasErrors = false) : this(syntax, aliasOpt, false, null, type, hasErrors) { } - public BoundTypeExpression(SyntaxNode syntax, AliasSymbol aliasOpt, TypeSymbol type) + public BoundTypeExpression(SyntaxNode syntax, AliasSymbol aliasOpt, TypeWithAnnotations type) : this(syntax, aliasOpt, false, null, type) { } - public BoundTypeExpression(SyntaxNode syntax, AliasSymbol aliasOpt, bool inferredType, TypeSymbol type, bool hasErrors = false) + public BoundTypeExpression(SyntaxNode syntax, AliasSymbol aliasOpt, bool inferredType, TypeWithAnnotations type, bool hasErrors = false) : this(syntax, aliasOpt, inferredType, null, type, hasErrors) { } + + public BoundTypeExpression(SyntaxNode syntax, AliasSymbol aliasOpt, bool inferredType, BoundTypeExpression boundContainingTypeOpt, TypeSymbol type, bool hasErrors = false) + : this(syntax, aliasOpt, inferredType, boundContainingTypeOpt, TypeWithAnnotations.Create(type), hasErrors) + { + } + + public BoundTypeExpression(SyntaxNode syntax, AliasSymbol aliasOpt, TypeSymbol type, bool hasErrors = false) + : this(syntax, aliasOpt, TypeWithAnnotations.Create(type), hasErrors) + { + } + + public BoundTypeExpression(SyntaxNode syntax, AliasSymbol aliasOpt, TypeSymbol type) + : this(syntax, aliasOpt, TypeWithAnnotations.Create(type)) + { + } + + public BoundTypeExpression(SyntaxNode syntax, AliasSymbol aliasOpt, bool inferredType, TypeSymbol type, bool hasErrors = false) + : this(syntax, aliasOpt, inferredType, TypeWithAnnotations.Create(type), hasErrors) + { + } } internal sealed partial class BoundNamespaceExpression @@ -569,4 +595,14 @@ public BoundAddressOfOperator(SyntaxNode syntax, BoundExpression operand, TypeSy { } } + + internal partial class BoundDagTemp + { + public BoundDagTemp(SyntaxNode syntax, TypeSymbol type, BoundDagEvaluation source) + : this(syntax, type, source, index: 0, hasErrors: false) + { + } + + public static BoundDagTemp ForOriginalInput(BoundExpression expr) => new BoundDagTemp(expr.Syntax, expr.Type, source: null); + } } diff --git a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs index dee162725469..ff650c03ebe6 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs +++ b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs @@ -19,7 +19,7 @@ namespace Microsoft.CodeAnalysis.CSharp { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class CSharpResources { @@ -15801,6 +15801,24 @@ internal static string WRN_SwitchExpressionNotExhaustive_Title { } } + /// + /// Looks up a localized string similar to The switch expression does not handle some null inputs (it is not exhaustive).. + /// + internal static string WRN_SwitchExpressionNotExhaustiveForNull { + get { + return ResourceManager.GetString("WRN_SwitchExpressionNotExhaustiveForNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The switch expression does not handle some null inputs.. + /// + internal static string WRN_SwitchExpressionNotExhaustiveForNull_Title { + get { + return ResourceManager.GetString("WRN_SwitchExpressionNotExhaustiveForNull_Title", resourceCulture); + } + } + /// /// Looks up a localized string similar to Source file has exceeded the limit of 16,707,565 lines representable in the PDB; debug information will be incorrect. /// diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 0962f512fb8f..b83ae1f359d6 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -5796,4 +5796,10 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ The feature '{0}' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + + The switch expression does not handle some null inputs (it is not exhaustive). + + + The switch expression does not handle some null inputs. + diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 14282f970663..14de10528650 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -1244,7 +1244,7 @@ internal enum ErrorCode ERR_InvalidDebugInfo = 7103, #endregion diagnostics introduced in C# 6 - // huge gap here; unused 7104-8000 + // unused 7104-8000 #region more diagnostics introduced in Roslyn (C# 6) WRN_UnimplementedCommandLineSwitch = 8001, @@ -1356,7 +1356,7 @@ internal enum ErrorCode ERR_LocalFunctionMissingBody = 8112, ERR_InvalidHashAlgorithmName = 8113, - // Available = 8113, 8114, 8115 + // Unused 8113, 8114, 8115 #region diagnostics for pattern-matching introduced in C# 7 ERR_ThrowMisplaced = 8115, @@ -1450,7 +1450,7 @@ internal enum ErrorCode ERR_BadLanguageVersion = 8192, #endregion - // Available = 8193-8195 + // Unused 8193-8195 #region diagnostics for out var ERR_ImplicitlyTypedOutVariableUsedInTheSameArgumentList = 8196, @@ -1633,9 +1633,7 @@ internal enum ErrorCode WRN_NullReferenceReceiver = 8602, WRN_NullReferenceReturn = 8603, WRN_NullReferenceArgument = 8604, - // Available = 8605, - // Available = 8606, - // Available = 8607, + // Unused 8605-8607 WRN_NullabilityMismatchInTypeOnOverride = 8608, WRN_NullabilityMismatchInReturnTypeOnOverride = 8609, WRN_NullabilityMismatchInParameterTypeOnOverride = 8610, @@ -1667,7 +1665,7 @@ internal enum ErrorCode ERR_BadNullableContextOption = 8636, ERR_NullableDirectiveQualifierExpected = 8637, WRN_ConditionalAccessMayReturnNull = 8638, - // Available = 8639, + // Unused 8639 ERR_ExpressionTreeCantContainRefStruct = 8640, ERR_ElseCannotStartStatement = 8641, ERR_ExpressionTreeCantContainNullCoalescingAssignment = 8642, @@ -1683,6 +1681,7 @@ internal enum ErrorCode ERR_FeatureInPreview = 8652, WRN_DefaultExpressionMayIntroduceNullT = 8653, WRN_NullLiteralMayIntroduceNullT = 8654, + WRN_SwitchExpressionNotExhaustiveForNull = 8655, #endregion diagnostics introduced for C# 8.0 diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs index 272aace97be3..b175cae43fe2 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs @@ -40,6 +40,7 @@ static ErrorFacts() builder.Add(getId(ErrorCode.WRN_NullLiteralMayIntroduceNullT)); builder.Add(getId(ErrorCode.WRN_ConditionalAccessMayReturnNull)); builder.Add(getId(ErrorCode.WRN_AsOperatorMayReturnNull)); + builder.Add(getId(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull)); NullableFlowAnalysisSafetyWarnings = builder.ToImmutable(); @@ -404,6 +405,7 @@ internal static int GetWarningLevel(ErrorCode code) case ErrorCode.WRN_NullLiteralMayIntroduceNullT: case ErrorCode.WRN_ConditionalAccessMayReturnNull: case ErrorCode.WRN_AsOperatorMayReturnNull: + case ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull: return 1; default: return 0; diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass_Switch.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass_Switch.cs index 6cef15b88541..33490ac1274c 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass_Switch.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass_Switch.cs @@ -13,18 +13,38 @@ internal abstract partial class AbstractFlowPass { public override BoundNode VisitSwitchStatement(BoundSwitchStatement node) { - // visit switch header - VisitRvalue(node.Expression); + // dispatch to the switch sections + var initialState = VisitSwitchStatementDispatch(node); + + // visit switch sections + var afterSwitchState = UnreachableState(); + var switchSections = node.SwitchSections; + var iLastSection = (switchSections.Length - 1); + for (var iSection = 0; iSection <= iLastSection; iSection++) + { + VisitSwitchSection(switchSections[iSection], iSection == iLastSection); + // Even though it is illegal for the end of a switch section to be reachable, in erroneous + // code it may be reachable. We treat that as an implicit break (branch to afterSwitchState). + Join(ref afterSwitchState, ref this.State); + } + + if (node.DecisionDag.ReachableLabels.Contains(node.BreakLabel) || node.DefaultLabel == null && IsTraditionalSwitch(node)) + { + Join(ref afterSwitchState, ref initialState); + } - // visit switch block - VisitSwitchBlock(node); + ResolveBreaks(afterSwitchState, node.BreakLabel); return null; } - private void VisitSwitchBlock(BoundSwitchStatement node) + protected virtual TLocalState VisitSwitchStatementDispatch(BoundSwitchStatement node) { - var initialState = State.Clone(); + // visit switch header + VisitRvalue(node.Expression); + + TLocalState initialState = this.State.Clone(); + var reachableLabels = node.DecisionDag.ReachableLabels; foreach (var section in node.SwitchSections) { @@ -52,24 +72,7 @@ private void VisitSwitchBlock(BoundSwitchStatement node) } } - // visit switch sections - var afterSwitchState = UnreachableState(); - var switchSections = node.SwitchSections; - var iLastSection = (switchSections.Length - 1); - for (var iSection = 0; iSection <= iLastSection; iSection++) - { - VisitSwitchSection(switchSections[iSection], iSection == iLastSection); - // Even though it is illegal for the end of a switch section to be reachable, in erroneous - // code it may be reachable. We treat that as an implicit break (branch to afterSwitchState). - Join(ref afterSwitchState, ref this.State); - } - - if (reachableLabels.Contains(node.BreakLabel) || node.DefaultLabel == null && IsTraditionalSwitch(node)) - { - Join(ref afterSwitchState, ref initialState); - } - - ResolveBreaks(afterSwitchState, node.BreakLabel); + return initialState; } /// diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.LocalFunctions.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.LocalFunctions.cs index 3295def19eee..fc0b730c9e93 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.LocalFunctions.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.LocalFunctions.cs @@ -222,7 +222,7 @@ private void RecordReadInLocalFunction(int slot) // fields we need to record each field assignment separately, // since some fields may be assigned when this read is replayed VariableIdentifier id = variableBySlot[slot]; - var type = VariableTypeWithAnnotations(id.Symbol).Type; + var type = id.Symbol.GetTypeOrReturnType().Type; Debug.Assert(!_emptyStructTypeCache.IsEmptyStructType(type)); diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.VariableIdentifier.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.VariableIdentifier.cs index 3aac75c3c78f..9e7d3727ea4e 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.VariableIdentifier.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.VariableIdentifier.cs @@ -16,9 +16,17 @@ internal partial class LocalDataFlowPass public VariableIdentifier(Symbol symbol, int containingSlot = 0) { - Debug.Assert(symbol.Kind == SymbolKind.Local || symbol.Kind == SymbolKind.Field || symbol.Kind == SymbolKind.Parameter || - (symbol as MethodSymbol)?.MethodKind == MethodKind.LocalFunction || - symbol.Kind == SymbolKind.Property || symbol.Kind == SymbolKind.Event); + Debug.Assert(symbol.Kind switch + { + SymbolKind.Local => true, + SymbolKind.Parameter => true, + SymbolKind.Field => true, + SymbolKind.Property => true, + SymbolKind.Event => true, + SymbolKind.ErrorType => true, + SymbolKind.Method when symbol is MethodSymbol m && m.MethodKind == MethodKind.LocalFunction => true, + _ => false + }); Symbol = symbol; ContainingSlot = containingSlot; } diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs index fa3e034cfc36..5fb7edf8d3f4 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs @@ -906,7 +906,7 @@ protected virtual void ReportUnassigned(Symbol symbol, SyntaxNode node, int slot // We've already reported the use of a local before its declaration. No need to emit // another diagnostic for the same issue. } - else if (!_alreadyReported[slot] && !VariableTypeWithAnnotations(symbol).Type.IsErrorType()) + else if (!_alreadyReported[slot] && !symbol.GetTypeOrReturnType().Type.IsErrorType()) { // CONSIDER: could suppress this diagnostic in cases where the local was declared in a using // or fixed statement because there's a special error code for not initializing those. @@ -1231,7 +1231,7 @@ private bool FieldsAllSet(int containingSlot, LocalState state) Debug.Assert(containingSlot != -1); Debug.Assert(!state.IsAssigned(containingSlot)); VariableIdentifier variable = variableBySlot[containingSlot]; - TypeSymbol structType = VariableTypeWithAnnotations(variable.Symbol).Type; + TypeSymbol structType = variable.Symbol.GetTypeOrReturnType().Type; foreach (var field in _emptyStructTypeCache.GetStructInstanceFields(structType)) { if (_emptyStructTypeCache.IsEmptyStructType(field.Type)) continue; @@ -1259,7 +1259,7 @@ private void SetSlotAssigned(int slot, ref LocalState state) { if (slot < 0) return; VariableIdentifier id = variableBySlot[slot]; - TypeSymbol type = VariableTypeWithAnnotations(id.Symbol).Type; + TypeSymbol type = id.Symbol.GetTypeOrReturnType().Type; Debug.Assert(!_emptyStructTypeCache.IsEmptyStructType(type)); if (slot >= state.Assigned.Capacity) Normalize(ref state); if (state.IsAssigned(slot)) return; // was already fully assigned. @@ -1295,7 +1295,7 @@ private void SetSlotUnassigned(int slot, ref LocalState state) { if (slot < 0) return; VariableIdentifier id = variableBySlot[slot]; - TypeSymbol type = VariableTypeWithAnnotations(id.Symbol).Type; + TypeSymbol type = id.Symbol.GetTypeOrReturnType().Type; Debug.Assert(!_emptyStructTypeCache.IsEmptyStructType(type)); if (!state.IsAssigned(slot)) return; // was already unassigned state.Unassign(slot); diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/LocalDataFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/LocalDataFlowPass.cs index f1b01a399cd3..9d0e1b5461e9 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/LocalDataFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/LocalDataFlowPass.cs @@ -103,7 +103,7 @@ protected virtual int GetOrCreateSlot(Symbol symbol, int containingSlot = 0) // Since analysis may proceed in multiple passes, it is possible the slot is already assigned. if (!_variableSlot.TryGetValue(identifier, out slot)) { - var variableType = VariableTypeWithAnnotations(symbol).Type; + var variableType = symbol.GetTypeOrReturnType().Type; if (_emptyStructTypeCache.IsEmptyStructType(variableType)) { return -1; @@ -244,27 +244,5 @@ protected int MakeMemberSlot(BoundExpression receiverOpt, Symbol member) } return GetOrCreateSlot(member, containingSlot); } - - protected static TypeWithAnnotations VariableTypeWithAnnotations(Symbol s) - { - switch (s.Kind) - { - case SymbolKind.Local: - return ((LocalSymbol)s).TypeWithAnnotations; - case SymbolKind.Field: - return ((FieldSymbol)s).TypeWithAnnotations; - case SymbolKind.Parameter: - return ((ParameterSymbol)s).TypeWithAnnotations; - case SymbolKind.Method: - Debug.Assert(((MethodSymbol)s).MethodKind == MethodKind.LocalFunction); - return default; - case SymbolKind.Property: - return ((PropertySymbol)s).TypeWithAnnotations; - case SymbolKind.Event: - return ((EventSymbol)s).TypeWithAnnotations; - default: - throw ExceptionUtilities.UnexpectedValue(s.Kind); - } - } } } diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.ObjectCreationPlaceholderLocal.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.ObjectCreationPlaceholderLocal.cs deleted file mode 100644 index 47f58c15490f..000000000000 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.ObjectCreationPlaceholderLocal.cs +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Immutable; -using Microsoft.CodeAnalysis.CSharp.Symbols; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.CSharp -{ - internal partial class NullableWalker - { - /// - /// A symbol to represent a placeholder for an instance being constructed by - /// . It is used to track the state - /// of members being initialized. - /// - private sealed class ObjectCreationPlaceholderLocal : LocalSymbol - { - private readonly Symbol _containingSymbol; - private readonly TypeWithAnnotations _type; - private readonly BoundExpression _objectCreationExpression; - - public ObjectCreationPlaceholderLocal(Symbol containingSymbol, BoundExpression objectCreationExpression) - { - _containingSymbol = containingSymbol; - _type = TypeWithAnnotations.Create(objectCreationExpression.Type, NullableAnnotation.NotAnnotated); - _objectCreationExpression = objectCreationExpression; - } - - public override bool Equals(object obj) - { - if ((object)this == obj) - { - return true; - } - - var other = obj as ObjectCreationPlaceholderLocal; - - return (object)other != null && (object)_objectCreationExpression == other._objectCreationExpression; - } - - public override int GetHashCode() - { - return _objectCreationExpression.GetHashCode(); - } - - internal override SyntaxNode ScopeDesignatorOpt - { - get - { - return null; - } - } - - public override Symbol ContainingSymbol - { - get - { - return _containingSymbol; - } - } - - public override ImmutableArray DeclaringSyntaxReferences - { - get - { - return ImmutableArray.Empty; - } - } - - public override ImmutableArray Locations - { - get - { - return ImmutableArray.Empty; - } - } - - public override TypeWithAnnotations TypeWithAnnotations - { - get - { - return _type; - } - } - - internal override LocalDeclarationKind DeclarationKind - { - get - { - return LocalDeclarationKind.None; - } - } - - internal override SyntaxToken IdentifierToken - { - get - { - throw ExceptionUtilities.Unreachable; - } - } - - internal override bool IsCompilerGenerated - { - get - { - return true; - } - } - - internal override bool IsImportedFromMetadata - { - get - { - return false; - } - } - - internal override bool IsPinned - { - get - { - return false; - } - } - - public override RefKind RefKind - { - get - { - return RefKind.None; - } - } - - internal override SynthesizedLocalKind SynthesizedKind - { - get - { - throw ExceptionUtilities.Unreachable; - } - } - - internal override ConstantValue GetConstantValue(SyntaxNode node, LocalSymbol inProgress, DiagnosticBag diagnostics = null) - { - return null; - } - - internal override ImmutableArray GetConstantValueDiagnostics(BoundExpression boundInitValue) - { - return ImmutableArray.Empty; - } - - internal override SyntaxNode GetDeclaratorSyntax() - { - throw ExceptionUtilities.Unreachable; - } - - internal override LocalSymbol WithSynthesizedLocalKindAndSyntax(SynthesizedLocalKind kind, SyntaxNode syntax) - { - throw ExceptionUtilities.Unreachable; - } - - internal override uint ValEscapeScope => throw ExceptionUtilities.Unreachable; - - internal override uint RefEscapeScope => throw ExceptionUtilities.Unreachable; - } - } -} diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.PlaceholderLocal.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.PlaceholderLocal.cs new file mode 100644 index 000000000000..08ebf088a1fa --- /dev/null +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.PlaceholderLocal.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Diagnostics; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp +{ + internal partial class NullableWalker + { + /// + /// A symbol to be used as a placeholder for an instance being constructed by + /// , or the input expression of a pattern-matching operation. + /// It is used to track the state of an expression, such as members being initialized. + /// + private sealed class PlaceholderLocal : LocalSymbol + { + private readonly Symbol _containingSymbol; + private readonly TypeWithAnnotations _type; + private readonly object _identifier; + + public PlaceholderLocal(Symbol containingSymbol, object identifier, TypeWithAnnotations type) + { + Debug.Assert(identifier != null); + _containingSymbol = containingSymbol; + _type = type; + _identifier = identifier; + } + + public override bool Equals(object obj) + { + if ((object)this == obj) + { + return true; + } + + var other = obj as PlaceholderLocal; + + return (object)other != null && _identifier.Equals(other._identifier); + } + + public override int GetHashCode() => _identifier.GetHashCode(); + internal override SyntaxNode ScopeDesignatorOpt => null; + public override Symbol ContainingSymbol => _containingSymbol; + public override ImmutableArray DeclaringSyntaxReferences => ImmutableArray.Empty; + public override ImmutableArray Locations => ImmutableArray.Empty; + public override TypeWithAnnotations TypeWithAnnotations => _type; + internal override LocalDeclarationKind DeclarationKind => LocalDeclarationKind.None; + internal override SyntaxToken IdentifierToken => throw ExceptionUtilities.Unreachable; + internal override bool IsCompilerGenerated => true; + internal override bool IsImportedFromMetadata => false; + internal override bool IsPinned => false; + public override RefKind RefKind => RefKind.None; + internal override SynthesizedLocalKind SynthesizedKind => throw ExceptionUtilities.Unreachable; + internal override ConstantValue GetConstantValue(SyntaxNode node, LocalSymbol inProgress, DiagnosticBag diagnostics = null) => null; + internal override ImmutableArray GetConstantValueDiagnostics(BoundExpression boundInitValue) => ImmutableArray.Empty; + internal override SyntaxNode GetDeclaratorSyntax() => throw ExceptionUtilities.Unreachable; + internal override LocalSymbol WithSynthesizedLocalKindAndSyntax(SynthesizedLocalKind kind, SyntaxNode syntax) => throw ExceptionUtilities.Unreachable; + internal override uint ValEscapeScope => throw ExceptionUtilities.Unreachable; + internal override uint RefEscapeScope => throw ExceptionUtilities.Unreachable; + } + } +} diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 7d8434efb9f7..ac6ee08e0273 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -174,9 +174,9 @@ private void SetResult(TypeWithState rvalueType, TypeWithAnnotations lvalueType) } /// - /// Instances being constructed. + /// Placeholder locals, e.g. for objects being constructed. /// - private PooledDictionary _placeholderLocalsOpt; + private PooledDictionary _placeholderLocalsOpt; /// /// For methods with annotations, we'll need to visit the arguments twice. @@ -207,7 +207,7 @@ private NullableWalker( ArrayBuilder<(BoundReturnStatement, TypeWithAnnotations)> returnTypesOpt, VariableState initialState, Action callbackOpt) - : base(compilation, method, node, new EmptyStructTypeCache(compilation, dev12CompilerCompatibility: false), trackUnassignments: true) + : base(compilation, method, node, new NeverEmptyStructTypeCache(), trackUnassignments: true) { _callbackOpt = callbackOpt; _binder = compilation.GetBinderFactory(node.SyntaxTree).GetBinder(node.Syntax); @@ -406,6 +406,8 @@ private NullableFlowState GetDefaultState(ref LocalState state, int slot) case SymbolKind.Property: case SymbolKind.Event: return symbol.GetTypeOrReturnType().ToTypeWithState().State; + case SymbolKind.ErrorType: + return NullableFlowState.NotNull; default: throw ExceptionUtilities.UnexpectedValue(symbol.Kind); } @@ -583,17 +585,15 @@ protected override int MakeSlot(BoundExpression node) return _lastConditionalAccessSlot; } default: - // 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); - return base.MakeSlot(node); + int slot = getPlaceholderSlot(node); + return (slot > 0) ? slot : base.MakeSlot(node); } return -1; int getPlaceholderSlot(BoundExpression expr) { - if (_placeholderLocalsOpt != null && _placeholderLocalsOpt.TryGetValue(expr, out ObjectCreationPlaceholderLocal placeholder)) + if (_placeholderLocalsOpt != null && _placeholderLocalsOpt.TryGetValue(expr, out PlaceholderLocal placeholder)) { return GetOrCreateSlot(placeholder); } @@ -943,27 +943,32 @@ private void ReportNonSafetyDiagnostic(ErrorCode errorCode, SyntaxNode syntax) 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); + ReportDiagnostic(errorCode, syntax.GetLocation()); #pragma warning restore CS0618 } private void ReportSafetyDiagnostic(ErrorCode errorCode, SyntaxNode syntaxNode, params object[] arguments) + { + ReportSafetyDiagnostic(errorCode, syntaxNode.GetLocation(), arguments); + } + + private void ReportSafetyDiagnostic(ErrorCode errorCode, Location location, params object[] arguments) { // 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, syntaxNode, arguments); + ReportDiagnostic(errorCode, location, arguments); #pragma warning restore CS0618 } [Obsolete("Use ReportSafetyDiagnostic/ReportNonSafetyDiagnostic instead", error: false)] - private void ReportDiagnostic(ErrorCode errorCode, SyntaxNode syntaxNode, params object[] arguments) + private void ReportDiagnostic(ErrorCode errorCode, Location location, params object[] arguments) { Debug.Assert(!IsConditionalState); if (this.State.Reachable && !_disableDiagnostics) { - Diagnostics.Add(errorCode, syntaxNode.GetLocation(), arguments); + Diagnostics.Add(errorCode, location, arguments); } } @@ -1094,7 +1099,7 @@ private void InheritNullableStateOfTrackableType(int targetSlot, int valueSlot, private TypeSymbol GetSlotType(int slot) { - return VariableTypeWithAnnotations(variableBySlot[slot].Symbol).Type; + return variableBySlot[slot].Symbol.GetTypeOrReturnType().Type; } protected override LocalState TopState() @@ -1156,106 +1161,6 @@ private void EnterParameter(ParameterSymbol parameter, TypeWithAnnotations param } } - public override BoundNode VisitIsPatternExpression(BoundIsPatternExpression node) - { - var resultType = VisitRvalueWithState(node.Expression); - VisitPattern(node.Expression, resultType, node.Pattern); - SetNotNullResult(node); - return node; - } - - /// - /// Examples: - /// `x is Point p` - /// `switch (x) ... case Point p:` // https://github.com/dotnet/roslyn/issues/29873 not yet handled - /// - /// 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. - /// - private void VisitPattern(BoundExpression expression, TypeWithState expressionResultType, BoundPattern pattern) - { - NullableFlowState whenTrue = expressionResultType.State; - NullableFlowState whenFalse = expressionResultType.State; - - switch (pattern.Kind) - { - case BoundKind.ConstantPattern: - // 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. - switch (((BoundConstantPattern)pattern).ConstantValue?.IsNull) - { - case true: - whenTrue = NullableFlowState.MaybeNull; - whenFalse = NullableFlowState.NotNull; - break; - case false: - whenTrue = NullableFlowState.NotNull; - whenFalse = NullableFlowState.MaybeNull; - break; - } - break; - case BoundKind.DeclarationPattern: - var declarationPattern = (BoundDeclarationPattern)pattern; - if (declarationPattern.IsVar) - { - // 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) - { - var variableType = expressionResultType.ToTypeWithAnnotations(); - _variableTypes[variable] = variableType; - TrackNullableStateForAssignment(expression, variableType, GetOrCreateSlot(variable), expressionResultType); - } - - whenFalse = NullableFlowState.NotNull; // whenFalse is unreachable - } - else - { - whenTrue = NullableFlowState.NotNull; // the pattern tells us the expression is not null - } - break; - default: - // https://github.com/dotnet/roslyn/issues/29909 : handle other kinds of patterns - break; - } - - Debug.Assert(!IsConditionalState); - - // Create slot since EnsureCapacity should be - // called on all fields and that is simpler if state is limited to this.State. - int mainSlot = MakeSlot(expression); - - base.VisitPattern(pattern); - Debug.Assert(IsConditionalState); - - if (mainSlot > 0) - { - SetStateAndTrackForFinally(ref this.StateWhenTrue, mainSlot, whenTrue); - SetStateAndTrackForFinally(ref this.StateWhenFalse, mainSlot, whenFalse); - } - - if (whenTrue.IsNotNull() || whenFalse.IsNotNull()) - { - var slotBuilder = ArrayBuilder.GetInstance(); - GetSlotsToMarkAsNotNullable(expression, slotBuilder); - - // Set all nested conditional slots. For example in a?.b?.c we'll set a, b, and c. - if (whenTrue.IsNotNull()) - { - MarkSlotsAsNotNull(slotBuilder, ref StateWhenTrue); - } - else if (whenFalse.IsNotNull()) - { - MarkSlotsAsNotNull(slotBuilder, ref StateWhenFalse); - } - - slotBuilder.Free(); - } - } - protected override BoundNode VisitReturnStatementNoAdjust(BoundReturnStatement node) { Debug.Assert(!IsConditionalState); @@ -1480,7 +1385,7 @@ private void VisitObjectOrDynamicObjectCreation( NullableFlowState resultState = NullableFlowState.NotNull; if ((object)type != null) { - slot = GetOrCreateObjectCreationPlaceholderSlot(node); + slot = GetOrCreatePlaceholderSlot(node); if (slot > 0) { var constructor = (node as BoundObjectCreationExpression)?.Constructor; @@ -1625,25 +1530,25 @@ private void SetNotNullResult(BoundExpression node) ResultType = new TypeWithState(node.Type, NullableFlowState.NotNull); } - private int GetOrCreateObjectCreationPlaceholderSlot(BoundExpression node) + private int GetOrCreatePlaceholderSlot(BoundExpression node) { - ObjectCreationPlaceholderLocal placeholder; - if (_placeholderLocalsOpt == null) - { - _placeholderLocalsOpt = PooledDictionary.GetInstance(); - placeholder = null; - } - else - { - _placeholderLocalsOpt.TryGetValue(node, out placeholder); - } + if (_emptyStructTypeCache.IsEmptyStructType(node.Type)) + return -1; + + return GetOrCreatePlaceholderSlot(node, TypeWithAnnotations.Create(node.Type, NullableAnnotation.NotAnnotated)); + } - if (placeholder is null) + private int GetOrCreatePlaceholderSlot(object identifier, TypeWithAnnotations type) + { + _placeholderLocalsOpt ??= PooledDictionary.GetInstance(); + + if (!_placeholderLocalsOpt.TryGetValue(identifier, out PlaceholderLocal placeholder)) { - placeholder = new ObjectCreationPlaceholderLocal(_symbol, node); - _placeholderLocalsOpt.Add(node, placeholder); + placeholder = new PlaceholderLocal(_symbol, identifier, type); + _placeholderLocalsOpt.Add(identifier, placeholder); } + Debug.Assert((object)placeholder != null); return GetOrCreateSlot(placeholder); } @@ -1663,7 +1568,7 @@ public override BoundNode VisitAnonymousObjectCreationExpression(BoundAnonymousO if (argumentsWithAnnotations.All(argType => argType.HasType)) { anonymousType = AnonymousTypeManager.ConstructAnonymousTypeSymbol(anonymousType, argumentsWithAnnotations); - int receiverSlot = GetOrCreateObjectCreationPlaceholderSlot(node); + int receiverSlot = GetOrCreatePlaceholderSlot(node); for (int i = 0; i < arguments.Length; i++) { var argument = arguments[i]; @@ -2148,14 +2053,25 @@ private void LearnFromNonNullTest(BoundExpression expression, ref LocalState sta slotBuilder.Free(); } + private void LearnFromNonNullTest(int slot, ref LocalState state) + { + state[slot] = NullableFlowState.NotNull; + } + private int LearnFromNullTest(BoundExpression expression, ref LocalState state) { var expressionWithoutConversion = RemoveConversion(expression, includeExplicitConversions: true).expression; var slot = MakeSlot(expressionWithoutConversion); - if (slot > 0 && PossiblyNullableType(expressionWithoutConversion.Type)) + return LearnFromNullTest(slot, expressionWithoutConversion.Type, ref state); + } + + private int LearnFromNullTest(int slot, TypeSymbol expressionType, ref LocalState state) + { + if (slot > 0 && PossiblyNullableType(expressionType)) { SetStateAndTrackForFinally(ref state, slot, NullableFlowState.MaybeNull); } + return slot; } @@ -3730,7 +3646,7 @@ private void VisitTupleExpression(BoundTupleExpression node) } else { - int slot = GetOrCreateObjectCreationPlaceholderSlot(node); + int slot = GetOrCreatePlaceholderSlot(node); if (slot > 0) { this.State[slot] = NullableFlowState.NotNull; @@ -3818,7 +3734,7 @@ private void TrackNullableStateOfNullableValue(BoundExpression node, BoundExpres int valueSlot = MakeSlot(operand); if (valueSlot > 0) { - int containingSlot = GetOrCreateObjectCreationPlaceholderSlot(node); + int containingSlot = GetOrCreatePlaceholderSlot(node); Debug.Assert(containingSlot > 0); TrackNullableStateOfNullableValue(containingSlot, convertedType, operand, underlyingType.ToTypeWithState(), valueSlot); } @@ -5307,7 +5223,7 @@ public override BoundNode VisitDefaultExpression(BoundDefaultExpression node) TypeSymbol type = node.Type; if (EmptyStructTypeCache.IsTrackableStructType(type)) { - int slot = GetOrCreateObjectCreationPlaceholderSlot(node); + int slot = GetOrCreatePlaceholderSlot(node); if (slot > 0) { this.State[slot] = NullableFlowState.NotNull; diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs new file mode 100644 index 000000000000..d896df31be5b --- /dev/null +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs @@ -0,0 +1,441 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp +{ + internal sealed partial class NullableWalker + { + /// + /// Learn something about the input from a test of a given expression against a given pattern. The given + /// state is updated to note that any slots that are tested against `null` may be null. + /// + /// true if there is a top-level explicit null check + private void LearnFromAnyNullPatterns( + BoundExpression expression, + BoundPattern pattern) + { + int slot = MakeSlot(expression); + LearnFromAnyNullPatterns(slot, expression.Type, pattern); + } + + /// + /// Learn from any constant null patterns appearing in the pattern. + /// + /// Tye type of the input expression (before nullable analysis). + /// Used to determine which types can contain null. + /// true if there is a top-level explicit null check + private void LearnFromAnyNullPatterns( + int inputSlot, + TypeSymbol inputType, + BoundPattern pattern) + { + if (inputSlot <= 0) + return; + + switch (pattern) + { + case BoundConstantPattern cp: + bool isExplicitNullCheck = cp.Value.ConstantValue == ConstantValue.Null; + if (isExplicitNullCheck) + { + LearnFromNullTest(inputSlot, inputType, ref this.State); + } + break; + case BoundDeclarationPattern _: + case BoundDiscardPattern _: + case BoundITuplePattern _: + break; // nothing to learn + case BoundRecursivePattern rp: + { + // for positional part: we only learn from tuples (not Deconstruct) + if (rp.DeconstructMethod is null && !rp.Deconstruction.IsDefault) + { + var elements = inputType.TupleElements; + for (int i = 0, n = Math.Min(rp.Deconstruction.Length, elements.IsDefault ? 0 : elements.Length); i < n; i++) + { + BoundSubpattern item = rp.Deconstruction[i]; + FieldSymbol element = elements[i]; + LearnFromAnyNullPatterns(GetOrCreateSlot(element, inputSlot), element.Type, item.Pattern); + } + } + + // for property part + if (!rp.Properties.IsDefault) + { + for (int i = 0, n = rp.Properties.Length; i < n; i++) + { + BoundSubpattern item = rp.Properties[i]; + Symbol symbol = item.Symbol; + if (symbol?.ContainingType.Equals(inputType, TypeCompareKind.AllIgnoreOptions) == true) + { + LearnFromAnyNullPatterns(GetOrCreateSlot(symbol, inputSlot), symbol.GetTypeOrReturnType().Type, item.Pattern); + } + } + } + } + break; + default: + throw ExceptionUtilities.UnexpectedValue(pattern); + } + } + + protected override LocalState VisitSwitchStatementDispatch(BoundSwitchStatement node) + { + // first, learn from any null tests in the patterns + int slot = MakeSlot(node.Expression); + if (slot > 0) + { + var originalInputType = node.Expression.Type; + foreach (var section in node.SwitchSections) + { + foreach (var label in section.SwitchLabels) + { + LearnFromAnyNullPatterns(slot, originalInputType, label.Pattern); + } + } + } + + // visit switch header + var expressionState = VisitRvalueWithState(node.Expression); + LocalState initialState = this.State.Clone(); + var labelStateMap = LearnFromDecisionDag(node.Syntax, node.DecisionDag, node.Expression, expressionState, ref initialState); + + foreach (var section in node.SwitchSections) + { + foreach (var label in section.SwitchLabels) + { + var labelResult = labelStateMap.TryGetValue(label.Label, out var s1) ? s1 : (state: UnreachableState(), believedReachable: false); + SetState(labelResult.state); + PendingBranches.Add(new PendingBranch(label, this.State, label.Label)); + } + } + + labelStateMap.Free(); + return initialState; + } + + private PooledDictionary + LearnFromDecisionDag( + SyntaxNode node, + BoundDecisionDag decisionDag, + BoundExpression expression, + TypeWithState expressionType, + ref LocalState initialState) + { + var tempMap = PooledDictionary.GetInstance(); + var rootTemp = BoundDagTemp.ForOriginalInput(expression); + + // We create a fresh slot to track the switch expression, as it is copied at the start of the switch. + // We use the syntax to identify the root slot to ensure we don't share the slots between possibly nested switches. + int originalInputSlot = makeDagTempSlot(expressionType.ToTypeWithAnnotations(), rootTemp); + Debug.Assert(originalInputSlot > 0); + tempMap.Add(rootTemp, (originalInputSlot, expressionType.Type)); + initialState[originalInputSlot] = expressionType.State; + + var nodeStateMap = PooledDictionary.GetInstance(); + nodeStateMap.Add(decisionDag.RootNode, (state: initialState.Clone(), believedReachable: true)); + + var labelStateMap = PooledDictionary.GetInstance(); + + foreach (var dagNode in decisionDag.TopologicallySortedNodes) + { + bool found = nodeStateMap.TryGetValue(dagNode, out var nodeStateAndBelievedReachable); + Debug.Assert(found); // the topologically sorted nodes should contain only reachable nodes + (LocalState nodeState, bool nodeBelievedReachable) = nodeStateAndBelievedReachable; + SetState(nodeState); + + switch (dagNode) + { + case BoundEvaluationDecisionDagNode p: + { + var evaluation = p.Evaluation; + (int inputSlot, TypeSymbol inputType) = tempMap.TryGetValue(evaluation.Input, out var slotAndType) ? slotAndType : throw ExceptionUtilities.Unreachable; + Debug.Assert(inputSlot > 0); + var inputState = this.State[inputSlot]; + + switch (evaluation) + { + case BoundDagDeconstructEvaluation e: + { + // https://github.com/dotnet/roslyn/issues/34232 + // We may need to recompute the Deconstruct method for a deconstruction if + // the receiver type has changed (e.g. its nested nullability). + var method = e.DeconstructMethod; + int extensionExtra = method.IsStatic ? 1 : 0; + for (int i = 0; i < method.ParameterCount - extensionExtra; i++) + { + var parameterType = method.Parameters[i + extensionExtra].TypeWithAnnotations; + var output = new BoundDagTemp(e.Syntax, parameterType.Type, e, i); + int outputSlot = makeDagTempSlot(parameterType, output); + Debug.Assert(outputSlot > 0); + addToTempMap(output, outputSlot, parameterType.Type); + } + break; + } + case BoundDagTypeEvaluation e: + { + var output = new BoundDagTemp(e.Syntax, e.Type, e); + int outputSlot = inputSlot; + var outputType = new TypeWithState(e.Type, inputState); + addToTempMap(output, outputSlot, outputType.Type); + break; + } + case BoundDagFieldEvaluation e: + { + Debug.Assert(inputSlot > 0); + var field = (FieldSymbol)AsMemberOfType(inputType, e.Field); + int outputSlot = GetOrCreateSlot(field, inputSlot); + Debug.Assert(outputSlot > 0); + var type = field.Type; + var output = new BoundDagTemp(e.Syntax, type, e); + addToTempMap(output, outputSlot, type); + break; + } + case BoundDagPropertyEvaluation e: + { + Debug.Assert(inputSlot > 0); + var property = (PropertySymbol)AsMemberOfType(inputType, e.Property); + var type = property.TypeWithAnnotations; + var output = new BoundDagTemp(e.Syntax, type.Type, e); + int outputSlot = GetOrCreateSlot(property, inputSlot); + if (outputSlot <= 0) + { + // This is needed due to https://github.com/dotnet/roslyn/issues/29619 + outputSlot = makeDagTempSlot(type, output); + } + Debug.Assert(outputSlot > 0); + addToTempMap(output, outputSlot, type.Type); + break; + } + case BoundDagIndexEvaluation e: + { + var type = TypeWithAnnotations.Create(e.Property.Type, NullableAnnotation.Annotated); + var output = new BoundDagTemp(e.Syntax, type.Type, e); + int outputSlot = makeDagTempSlot(type, output); + Debug.Assert(outputSlot > 0); + addToTempMap(output, outputSlot, type.Type); + break; + } + default: + throw ExceptionUtilities.UnexpectedValue(p.Evaluation.Kind); + } + gotoNode(p.Next, this.State, nodeBelievedReachable); + break; + } + case BoundTestDecisionDagNode p: + { + var test = p.Test; + bool foundTemp = tempMap.TryGetValue(test.Input, out var slotAndType); + Debug.Assert(foundTemp); + + (int inputSlot, TypeSymbol inputType) = slotAndType; + var inputState = this.State[inputSlot]; + Split(); + switch (test) + { + case BoundDagTypeTest t: + if (inputSlot > 0) + { + learnFromNonNullTest(inputSlot, ref this.StateWhenTrue); + } + gotoNode(p.WhenTrue, this.StateWhenTrue, nodeBelievedReachable); + gotoNode(p.WhenFalse, this.StateWhenFalse, nodeBelievedReachable); + break; + case BoundDagNonNullTest t: + if (inputSlot > 0) + { + learnFromNonNullTest(inputSlot, ref this.StateWhenTrue); + } + gotoNode(p.WhenTrue, this.StateWhenTrue, nodeBelievedReachable); + gotoNode(p.WhenFalse, this.StateWhenFalse, nodeBelievedReachable & inputState.MayBeNull()); + break; + case BoundDagExplicitNullTest t: + if (inputSlot > 0) + { + LearnFromNullTest(inputSlot, inputType, ref this.StateWhenTrue); + learnFromNonNullTest(inputSlot, ref this.StateWhenFalse); + } + gotoNode(p.WhenTrue, this.StateWhenTrue, nodeBelievedReachable); + gotoNode(p.WhenFalse, this.StateWhenFalse, nodeBelievedReachable); + break; + case BoundDagValueTest t: + Debug.Assert(t.Value != ConstantValue.Null); + if (inputSlot > 0) + { + learnFromNonNullTest(inputSlot, ref this.StateWhenTrue); + } + gotoNode(p.WhenTrue, this.StateWhenTrue, nodeBelievedReachable); + gotoNode(p.WhenFalse, this.StateWhenFalse, nodeBelievedReachable); + break; + default: + throw ExceptionUtilities.UnexpectedValue(test.Kind); + } + break; + } + case BoundLeafDecisionDagNode d: + // We have one leaf decision dag node per reachable label + labelStateMap.Add(d.Label, (this.State, nodeBelievedReachable)); + break; + case BoundWhenDecisionDagNode w: + // bind the pattern variables, inferring their types as well + foreach (var binding in w.Bindings) + { + var variableAccess = binding.VariableAccess; + var tempSource = binding.TempContainingValue; + var foundTemp = tempMap.TryGetValue(tempSource, out var tempSlotAndType); + Debug.Assert(foundTemp); + var (tempSlot, tempType) = tempSlotAndType; + var tempState = this.State[tempSlot]; + if (variableAccess is BoundLocal { LocalSymbol: SourceLocalSymbol { IsVar: true } local }) + { + var inferredType = new TypeWithState(tempType, tempState).ToTypeWithAnnotations(); + if (_variableTypes.TryGetValue(local, out var existingType)) + { + // merge inferred nullable annotation from different branches of the decision tree + _variableTypes[local] = TypeWithAnnotations.Create(existingType.Type, existingType.NullableAnnotation.Join(inferredType.NullableAnnotation)); + } + else + { + _variableTypes[local] = inferredType; + } + + int localSlot = GetOrCreateSlot(local); + this.State[localSlot] = tempState; + } + else + { + // https://github.com/dotnet/roslyn/issues/34144 perform inference for top-level var-declared fields in scripts + } + } + + if (w.WhenExpression != null && w.WhenExpression.ConstantValue != ConstantValue.True) + { + VisitCondition(w.WhenExpression); + Debug.Assert(this.IsConditionalState); + gotoNode(w.WhenTrue, this.StateWhenTrue, nodeBelievedReachable); + gotoNode(w.WhenFalse, this.StateWhenFalse, nodeBelievedReachable); + } + else + { + Debug.Assert(w.WhenFalse is null); + gotoNode(w.WhenTrue, this.State, nodeBelievedReachable); + } + break; + default: + throw ExceptionUtilities.UnexpectedValue(dagNode.Kind); + } + } + + SetUnreachable(); // the decision dag is always complete (no fall-through) + tempMap.Free(); + nodeStateMap.Free(); + return labelStateMap; + + void learnFromNonNullTest(int inputSlot, ref LocalState state) + { + LearnFromNonNullTest(inputSlot, ref state); + if (inputSlot == originalInputSlot) + LearnFromNonNullTest(expression, ref state); + } + + void addToTempMap(BoundDagTemp output, int slot, TypeSymbol type) + { + // We need to track all dag temps, so there should be a slot + Debug.Assert(slot > 0); + if (tempMap.TryGetValue(output, out var outputSlotAndType)) + { + // The dag temp has already been allocated on another branch of the dag + Debug.Assert(outputSlotAndType.slot == slot); + Debug.Assert(outputSlotAndType.type.Equals(type, TypeCompareKind.AllIgnoreOptions)); + } + else + { + tempMap.Add(output, (slot, type)); + } + } + + void gotoNode(BoundDecisionDagNode node, LocalState state, bool believedReachable) + { + if (nodeStateMap.TryGetValue(node, out var stateAndReachable)) + { + Join(ref state, ref stateAndReachable.state); + believedReachable |= stateAndReachable.believedReachable; + } + + nodeStateMap[node] = (state, believedReachable); + } + + int makeDagTempSlot(TypeWithAnnotations type, BoundDagTemp temp) + { + object slotKey = (node, temp); + return GetOrCreatePlaceholderSlot(slotKey, type); + } + } + + public override BoundNode VisitSwitchExpression(BoundSwitchExpression node) + { + // first, learn from any null tests in the patterns + int slot = MakeSlot(node.Expression); + if (slot > 0) + { + var originalInputType = node.Expression.Type; + foreach (var arm in node.SwitchArms) + { + LearnFromAnyNullPatterns(slot, originalInputType, arm.Pattern); + } + } + + var expressionState = VisitRvalueWithState(node.Expression); + var labelStateMap = LearnFromDecisionDag(node.Syntax, node.DecisionDag, node.Expression, expressionState, ref this.State); + var endState = UnreachableState(); + + if (!node.ReportedNotExhaustive && node.DefaultLabel != null && + labelStateMap.TryGetValue(node.DefaultLabel, out var defaultLabelState) && defaultLabelState.believedReachable) + { + SetState(defaultLabelState.state); + ReportSafetyDiagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, ((SwitchExpressionSyntax)node.Syntax).SwitchKeyword.GetLocation()); + } + + foreach (var arm in node.SwitchArms) + { + SetState(!arm.Pattern.HasErrors && labelStateMap.TryGetValue(arm.Label, out var labelState) ? labelState.state : UnreachableState()); + VisitRvalue(arm.Value); + Join(ref endState, ref this.State); + } + + labelStateMap.Free(); + SetState(endState); + + // https://github.com/dotnet/roslyn/issues/34233 + // We need to recompute the result type and state of the switch expression based on + // the result type and state of all of the arms. This can be done in a way similar + // to how it is done for an implicit array creation expression. + this.ResultType = TypeWithAnnotations.Create(node.Type).ToTypeWithState(); + return null; + } + + public override BoundNode VisitIsPatternExpression(BoundIsPatternExpression node) + { + Debug.Assert(!IsConditionalState); + LearnFromAnyNullPatterns(node.Expression, node.Pattern); + var expressionState = VisitRvalueWithState(node.Expression); + var labelStateMap = LearnFromDecisionDag(node.Syntax, node.DecisionDag, node.Expression, expressionState, ref this.State); + var trueState = labelStateMap.TryGetValue(node.WhenTrueLabel, out var s1) ? s1.state : UnreachableState(); + var falseState = labelStateMap.TryGetValue(node.WhenFalseLabel, out var s2) ? s2.state : UnreachableState(); + labelStateMap.Free(); + SetConditionalState(trueState, falseState); + SetNotNullResult(node); + return null; + } + } +} diff --git a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs index a38952999e67..a4a0d78bb3a4 100644 --- a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs @@ -130,7 +130,7 @@ internal enum BoundKind: byte DagTemp, DagTypeTest, DagNonNullTest, - DagNullTest, + DagExplicitNullTest, DagValueTest, DagDeconstructEvaluation, DagTypeEvaluation, @@ -780,15 +780,16 @@ public BoundExtractedFinallyBlock Update(BoundBlock finallyBlock) internal sealed partial class BoundTypeExpression : BoundExpression { - public BoundTypeExpression(SyntaxNode syntax, AliasSymbol aliasOpt, bool inferredType, BoundTypeExpression boundContainingTypeOpt, TypeSymbol type, bool hasErrors = false) + public BoundTypeExpression(SyntaxNode syntax, AliasSymbol aliasOpt, bool inferredType, BoundTypeExpression boundContainingTypeOpt, TypeWithAnnotations typeWithAnnotations, TypeSymbol type, bool hasErrors = false) : base(BoundKind.TypeExpression, syntax, type, hasErrors || boundContainingTypeOpt.HasErrors()) { - Debug.Assert((object)type != null, "Field 'type' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); + Debug.Assert((object)typeWithAnnotations != null, "Field 'typeWithAnnotations' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); this.AliasOpt = aliasOpt; this.InferredType = inferredType; this.BoundContainingTypeOpt = boundContainingTypeOpt; + this.TypeWithAnnotations = typeWithAnnotations; } @@ -798,16 +799,18 @@ public BoundTypeExpression(SyntaxNode syntax, AliasSymbol aliasOpt, bool inferre public BoundTypeExpression BoundContainingTypeOpt { get; } + public TypeWithAnnotations TypeWithAnnotations { get; } + public override BoundNode Accept(BoundTreeVisitor visitor) { return visitor.VisitTypeExpression(this); } - public BoundTypeExpression Update(AliasSymbol aliasOpt, bool inferredType, BoundTypeExpression boundContainingTypeOpt, TypeSymbol type) + public BoundTypeExpression Update(AliasSymbol aliasOpt, bool inferredType, BoundTypeExpression boundContainingTypeOpt, TypeWithAnnotations typeWithAnnotations, TypeSymbol type) { - if (aliasOpt != this.AliasOpt || inferredType != this.InferredType || boundContainingTypeOpt != this.BoundContainingTypeOpt || !TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) + if (aliasOpt != this.AliasOpt || inferredType != this.InferredType || boundContainingTypeOpt != this.BoundContainingTypeOpt || typeWithAnnotations != this.TypeWithAnnotations || !TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) { - var result = new BoundTypeExpression(this.Syntax, aliasOpt, inferredType, boundContainingTypeOpt, type, this.HasErrors); + var result = new BoundTypeExpression(this.Syntax, aliasOpt, inferredType, boundContainingTypeOpt, typeWithAnnotations, type, this.HasErrors); result.CopyAttributes(this); return result; } @@ -816,7 +819,7 @@ public BoundTypeExpression Update(AliasSymbol aliasOpt, bool inferredType, Bound protected override BoundExpression ShallowClone() { - var result = new BoundTypeExpression(this.Syntax, this.AliasOpt, this.InferredType, this.BoundContainingTypeOpt, this.Type, this.HasErrors); + var result = new BoundTypeExpression(this.Syntax, this.AliasOpt, this.InferredType, this.BoundContainingTypeOpt, this.TypeWithAnnotations, this.Type, this.HasErrors); result.CopyAttributes(this); return result; } @@ -5041,10 +5044,10 @@ public BoundDagNonNullTest Update(BoundDagTemp input) } } - internal sealed partial class BoundDagNullTest : BoundDagTest + internal sealed partial class BoundDagExplicitNullTest : BoundDagTest { - public BoundDagNullTest(SyntaxNode syntax, BoundDagTemp input, bool hasErrors = false) - : base(BoundKind.DagNullTest, syntax, input, hasErrors || input.HasErrors()) + public BoundDagExplicitNullTest(SyntaxNode syntax, BoundDagTemp input, bool hasErrors = false) + : base(BoundKind.DagExplicitNullTest, syntax, input, hasErrors || input.HasErrors()) { Debug.Assert((object)input != null, "Field 'input' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); @@ -5054,14 +5057,14 @@ public BoundDagNullTest(SyntaxNode syntax, BoundDagTemp input, bool hasErrors = public override BoundNode Accept(BoundTreeVisitor visitor) { - return visitor.VisitDagNullTest(this); + return visitor.VisitDagExplicitNullTest(this); } - public BoundDagNullTest Update(BoundDagTemp input) + public BoundDagExplicitNullTest Update(BoundDagTemp input) { if (input != this.Input) { - var result = new BoundDagNullTest(this.Syntax, input, this.HasErrors); + var result = new BoundDagExplicitNullTest(this.Syntax, input, this.HasErrors); result.CopyAttributes(this); return result; } @@ -8483,8 +8486,8 @@ internal R VisitInternal(BoundNode node, A arg) return VisitDagTypeTest(node as BoundDagTypeTest, arg); case BoundKind.DagNonNullTest: return VisitDagNonNullTest(node as BoundDagNonNullTest, arg); - case BoundKind.DagNullTest: - return VisitDagNullTest(node as BoundDagNullTest, arg); + case BoundKind.DagExplicitNullTest: + return VisitDagExplicitNullTest(node as BoundDagExplicitNullTest, arg); case BoundKind.DagValueTest: return VisitDagValueTest(node as BoundDagValueTest, arg); case BoundKind.DagDeconstructEvaluation: @@ -9073,7 +9076,7 @@ public virtual R VisitDagNonNullTest(BoundDagNonNullTest node, A arg) { return this.DefaultVisit(node, arg); } - public virtual R VisitDagNullTest(BoundDagNullTest node, A arg) + public virtual R VisitDagExplicitNullTest(BoundDagExplicitNullTest node, A arg) { return this.DefaultVisit(node, arg); } @@ -9801,7 +9804,7 @@ public virtual BoundNode VisitDagNonNullTest(BoundDagNonNullTest node) { return this.DefaultVisit(node); } - public virtual BoundNode VisitDagNullTest(BoundDagNullTest node) + public virtual BoundNode VisitDagExplicitNullTest(BoundDagExplicitNullTest node) { return this.DefaultVisit(node); } @@ -10655,7 +10658,7 @@ public override BoundNode VisitDagNonNullTest(BoundDagNonNullTest node) this.Visit(node.Input); return null; } - public override BoundNode VisitDagNullTest(BoundDagNullTest node) + public override BoundNode VisitDagExplicitNullTest(BoundDagExplicitNullTest node) { this.Visit(node.Input); return null; @@ -11108,7 +11111,7 @@ public override BoundNode VisitTypeExpression(BoundTypeExpression node) { BoundTypeExpression boundContainingTypeOpt = (BoundTypeExpression)this.Visit(node.BoundContainingTypeOpt); TypeSymbol type = this.VisitType(node.Type); - return node.Update(node.AliasOpt, node.InferredType, boundContainingTypeOpt, type); + return node.Update(node.AliasOpt, node.InferredType, boundContainingTypeOpt, node.TypeWithAnnotations, type); } public override BoundNode VisitTypeOrValueExpression(BoundTypeOrValueExpression node) { @@ -11671,7 +11674,7 @@ public override BoundNode VisitDagNonNullTest(BoundDagNonNullTest node) BoundDagTemp input = (BoundDagTemp)this.Visit(node.Input); return node.Update(input); } - public override BoundNode VisitDagNullTest(BoundDagNullTest node) + public override BoundNode VisitDagExplicitNullTest(BoundDagExplicitNullTest node) { BoundDagTemp input = (BoundDagTemp)this.Visit(node.Input); return node.Update(input); @@ -12256,6 +12259,7 @@ public override TreeDumperNode VisitTypeExpression(BoundTypeExpression node, obj new TreeDumperNode("aliasOpt", node.AliasOpt, null), new TreeDumperNode("inferredType", node.InferredType, null), new TreeDumperNode("boundContainingTypeOpt", null, new TreeDumperNode[] { Visit(node.BoundContainingTypeOpt, null) }), + new TreeDumperNode("typeWithAnnotations", node.TypeWithAnnotations, null), new TreeDumperNode("type", node.Type, null), new TreeDumperNode("isSuppressed", node.IsSuppressed, null) } @@ -13290,9 +13294,9 @@ public override TreeDumperNode VisitDagNonNullTest(BoundDagNonNullTest node, obj } ); } - public override TreeDumperNode VisitDagNullTest(BoundDagNullTest node, object arg) + public override TreeDumperNode VisitDagExplicitNullTest(BoundDagExplicitNullTest node, object arg) { - return new TreeDumperNode("dagNullTest", null, new TreeDumperNode[] + return new TreeDumperNode("dagExplicitNullTest", null, new TreeDumperNode[] { new TreeDumperNode("input", null, new TreeDumperNode[] { Visit(node.Input, null) }) } diff --git a/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs b/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs index 13ecfb21cbde..6fac9ab600eb 100644 --- a/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs @@ -222,6 +222,7 @@ public static bool IsWarning(ErrorCode code) case ErrorCode.WRN_DuplicateInterfaceWithNullabilityMismatchInBaseList: case ErrorCode.WRN_DefaultExpressionMayIntroduceNullT: case ErrorCode.WRN_NullLiteralMayIntroduceNullT: + case ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull: return true; default: return false; diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_BasePatternSwitchLocalRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_BasePatternSwitchLocalRewriter.cs index bf8ac1a62387..a261a0e3fe16 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_BasePatternSwitchLocalRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_BasePatternSwitchLocalRewriter.cs @@ -280,7 +280,7 @@ protected BoundDecisionDag ShareTempsIfPossibleAndEvaluateInput( else { // assign the input expression to its temp. - BoundExpression inputTemp = _tempAllocator.GetTemp(InputTemp(loweredSwitchGoverningExpression)); + BoundExpression inputTemp = _tempAllocator.GetTemp(BoundDagTemp.ForOriginalInput(loweredSwitchGoverningExpression)); Debug.Assert(inputTemp != loweredSwitchGoverningExpression); result.Add(_factory.Assignment(inputTemp, loweredSwitchGoverningExpression)); savedInputExpression = inputTemp; diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Patterns.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Patterns.cs index 0cb8a1358b7b..79ff9b671403 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Patterns.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Patterns.cs @@ -34,8 +34,6 @@ public void Free() _tempAllocator.Free(); } - protected static BoundDagTemp InputTemp(BoundExpression expr) => new BoundDagTemp(expr.Syntax, expr.Type, null, 0); - public class DagTempAllocator { private readonly SyntheticBoundNodeFactory _factory; @@ -122,7 +120,7 @@ protected BoundExpression LowerEvaluation(BoundDagEvaluation evaluation) case BoundDagFieldEvaluation f: { FieldSymbol field = f.Field; - var outputTemp = new BoundDagTemp(f.Syntax, field.Type, f, index: 0); + var outputTemp = new BoundDagTemp(f.Syntax, field.Type, f); BoundExpression output = _tempAllocator.GetTemp(outputTemp); BoundExpression access = _localRewriter.MakeFieldAccess(f.Syntax, input, field, null, LookupResultKind.Viable, field.Type); access.WasCompilerGenerated = true; @@ -132,7 +130,7 @@ protected BoundExpression LowerEvaluation(BoundDagEvaluation evaluation) case BoundDagPropertyEvaluation p: { PropertySymbol property = p.Property; - var outputTemp = new BoundDagTemp(p.Syntax, property.Type, p, index: 0); + var outputTemp = new BoundDagTemp(p.Syntax, property.Type, p); BoundExpression output = _tempAllocator.GetTemp(outputTemp); return _factory.AssignmentExpression(output, _factory.Property(input, property)); } @@ -184,7 +182,7 @@ void addArg(RefKind refKind, BoundExpression expression) } TypeSymbol type = t.Type; - var outputTemp = new BoundDagTemp(t.Syntax, type, t, index: 0); + var outputTemp = new BoundDagTemp(t.Syntax, type, t); BoundExpression output = _tempAllocator.GetTemp(outputTemp); HashSet useSiteDiagnostics = null; Conversion conversion = _factory.Compilation.Conversions.ClassifyBuiltInConversion(inputType, output.Type, ref useSiteDiagnostics); @@ -220,7 +218,7 @@ void addArg(RefKind refKind, BoundExpression expression) Debug.Assert(e.Property.GetMethod.ParameterCount == 1); Debug.Assert(e.Property.GetMethod.Parameters[0].Type.SpecialType == SpecialType.System_Int32); TypeSymbol type = e.Property.GetMethod.ReturnType; - var outputTemp = new BoundDagTemp(e.Syntax, type, e, index: 0); + var outputTemp = new BoundDagTemp(e.Syntax, type, e); BoundExpression output = _tempAllocator.GetTemp(outputTemp); return _factory.AssignmentExpression(output, _factory.Call(input, e.Property.GetMethod, _factory.Literal(e.Index))); } @@ -246,7 +244,7 @@ protected BoundExpression LowerTest(BoundDagTest test) // Note that this tests for non-null as a side-effect. We depend on that to sometimes avoid the null check. return _factory.Is(input, d.Type); - case BoundDagNullTest d: + case BoundDagExplicitNullTest d: return _localRewriter.MakeNullCheck(d.Syntax, input, input.Type.IsNullableType() ? BinaryOperatorKind.NullableNullEqual : BinaryOperatorKind.Equal); case BoundDagValueTest d: @@ -345,7 +343,7 @@ evaluation is BoundDagTypeEvaluation typeEvaluation && ) { BoundExpression input = _tempAllocator.GetTemp(test.Input); - BoundExpression output = _tempAllocator.GetTemp(new BoundDagTemp(evaluation.Syntax, typeEvaluation.Type, evaluation, index: 0)); + BoundExpression output = _tempAllocator.GetTemp(new BoundDagTemp(evaluation.Syntax, typeEvaluation.Type, evaluation)); sideEffect = _factory.AssignmentExpression(output, _factory.As(input, typeEvaluation.Type)); testExpression = _factory.ObjectNotEqual(output, _factory.Null(output.Type)); return true; @@ -365,7 +363,7 @@ protected BoundDecisionDag ShareTempsAndEvaluateInput( Action addCode, out BoundExpression savedInputExpression) { - var inputDagTemp = InputTemp(loweredInput); + var inputDagTemp = BoundDagTemp.ForOriginalInput(loweredInput); if (loweredInput.Kind == BoundKind.Local || loweredInput.Kind == BoundKind.Parameter) { // If we're switching on a local variable and there is no when clause (checked by the caller), @@ -472,7 +470,7 @@ private BoundDecisionDag RewriteTupleInput( Debug.Assert(field != null); var expr = loweredInput.Arguments[i]; var fieldFetchEvaluation = new BoundDagFieldEvaluation(expr.Syntax, field, originalInput); - var temp = new BoundDagTemp(expr.Syntax, expr.Type, fieldFetchEvaluation, 0); + var temp = new BoundDagTemp(expr.Syntax, expr.Type, fieldFetchEvaluation); if (!tupleElementEvaluated[i]) { storeToTemp(temp, expr); @@ -515,7 +513,7 @@ eval.Field is var field && if (!tupleElementEvaluated[i]) { // Store the value in the right temp - var temp = new BoundDagTemp(eval.Syntax, field.Type, eval, 0); + var temp = new BoundDagTemp(eval.Syntax, field.Type, eval); BoundExpression expr = loweredInput.Arguments[i]; storeToTemp(temp, expr); tupleElementEvaluated[i] = true; diff --git a/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs index ecf8197018a1..912dc3d07815 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs @@ -912,7 +912,7 @@ internal bool IsIterator /// /// If the method was written as an iterator method (i.e. with yield statements in its body) returns the - /// element type of the iterator. Otherwise returns default(TypeSymbolWithAnnotations). + /// element type of the iterator. Otherwise returns default(TypeWithAnnotations). /// internal virtual TypeWithAnnotations IteratorElementTypeWithAnnotations { diff --git a/src/Compilers/CSharp/Portable/Symbols/SymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/SymbolExtensions.cs index 0eebfe3dafd1..8fad2ea9342b 100644 --- a/src/Compilers/CSharp/Portable/Symbols/SymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/SymbolExtensions.cs @@ -414,6 +414,11 @@ internal static void GetTypeOrReturnType(this Symbol symbol, out RefKind refKind returnType = parameter.TypeWithAnnotations; refCustomModifiers = parameter.RefCustomModifiers; break; + case SymbolKind.ErrorType: + refKind = RefKind.None; + returnType = TypeWithAnnotations.Create((TypeSymbol)symbol); + refCustomModifiers = ImmutableArray.Empty; + break; default: throw ExceptionUtilities.UnexpectedValue(symbol.Kind); } diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeWithAnnotations.cs b/src/Compilers/CSharp/Portable/Symbols/TypeWithAnnotations.cs index e77e19dcccdb..7e20e30f7472 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeWithAnnotations.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeWithAnnotations.cs @@ -687,20 +687,20 @@ public override int GetHashCode() return Type.GetHashCode(); } -#pragma warning disable CS0809 - [Obsolete("Unsupported", error: true)] + /// + /// Used by the generated . + /// public static bool operator ==(TypeWithAnnotations? x, TypeWithAnnotations? y) -#pragma warning restore CS0809 { - throw ExceptionUtilities.Unreachable; + return x.HasValue == y.HasValue && (!x.HasValue || x.GetValueOrDefault().IsSameAs(y.GetValueOrDefault())); } -#pragma warning disable CS0809 - [Obsolete("Unsupported", error: true)] + /// + /// Used by the generated . + /// public static bool operator !=(TypeWithAnnotations? x, TypeWithAnnotations? y) -#pragma warning restore CS0809 { - throw ExceptionUtilities.Unreachable; + return !(x == y); } // Field-wise ReferenceEquals. diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeWithState.cs b/src/Compilers/CSharp/Portable/Symbols/TypeWithState.cs index 34d5ec5b30fa..65c01373b282 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeWithState.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeWithState.cs @@ -19,6 +19,7 @@ internal readonly struct TypeWithState public TypeWithState(TypeSymbol type, NullableFlowState state) => (Type, State) = (type, state); public void Deconstruct(out TypeSymbol type, out NullableFlowState state) => (type, state) = (Type, State); public string GetDebuggerDisplay() => $"{{Type:{Type?.GetDebuggerDisplay()}, State:{State}{"}"}"; + public override string ToString() => GetDebuggerDisplay(); public TypeWithState WithNotNullState() => new TypeWithState(Type, NullableFlowState.NotNull); public TypeWithAnnotations ToTypeWithAnnotations() { diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index 8a6174acd8f9..4f6f436b3fd8 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -1567,6 +1567,16 @@ Výraz switch nezpracovává všechny možné vstupy (není vyčerpávající). + + The switch expression does not handle some null inputs (it is not exhaustive). + The switch expression does not handle some null inputs (it is not exhaustive). + + + + The switch expression does not handle some null inputs. + The switch expression does not handle some null inputs. + + The switch expression does not handle all possible inputs (it is not exhaustive). Výraz switch nezpracovává všechny možné vstupy (není vyčerpávající). diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index d31f7ac0226b..3eb5fbad2c7c 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -1567,6 +1567,16 @@ Der switch-Ausdruck verarbeitet nicht alle möglichen Eingaben (nicht umfassend). + + The switch expression does not handle some null inputs (it is not exhaustive). + The switch expression does not handle some null inputs (it is not exhaustive). + + + + The switch expression does not handle some null inputs. + The switch expression does not handle some null inputs. + + The switch expression does not handle all possible inputs (it is not exhaustive). Der switch-Ausdruck verarbeitet nicht alle möglichen Eingaben (nicht umfassend). diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index c25089375097..3ec2a9231bf0 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -1569,6 +1569,16 @@ La expresión switch no controla todas las entradas posibles (no es exhaustiva). + + The switch expression does not handle some null inputs (it is not exhaustive). + The switch expression does not handle some null inputs (it is not exhaustive). + + + + The switch expression does not handle some null inputs. + The switch expression does not handle some null inputs. + + The switch expression does not handle all possible inputs (it is not exhaustive). La expresión switch no controla todas las entradas posibles (no es exhaustiva). diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index be69e544854b..b7cb8bed4aea 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -1568,6 +1568,16 @@ L'expression switch ne prend pas en charge toutes les entrées possibles (elle n'est pas exhaustive). + + The switch expression does not handle some null inputs (it is not exhaustive). + The switch expression does not handle some null inputs (it is not exhaustive). + + + + The switch expression does not handle some null inputs. + The switch expression does not handle some null inputs. + + The switch expression does not handle all possible inputs (it is not exhaustive). L'expression switch ne prend pas en charge toutes les entrées possibles (elle n'est pas exhaustive). diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 14067aa00e44..8e487145db7b 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -1567,6 +1567,16 @@ L'espressione switch non gestisce tutti gli input possibili (non è esaustiva). + + The switch expression does not handle some null inputs (it is not exhaustive). + The switch expression does not handle some null inputs (it is not exhaustive). + + + + The switch expression does not handle some null inputs. + The switch expression does not handle some null inputs. + + The switch expression does not handle all possible inputs (it is not exhaustive). L'espressione switch non gestisce tutti gli input possibili (non è esaustiva). diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index f5c7a11d0482..762668bed838 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -1567,6 +1567,16 @@ switch 式がすべての可能な入力を処理しません (すべてを網羅していません)。 + + The switch expression does not handle some null inputs (it is not exhaustive). + The switch expression does not handle some null inputs (it is not exhaustive). + + + + The switch expression does not handle some null inputs. + The switch expression does not handle some null inputs. + + The switch expression does not handle all possible inputs (it is not exhaustive). switch 式がすべての可能な入力を処理しません (すべてを網羅していません)。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index e66212320499..c15fceab0f07 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -1567,6 +1567,16 @@ switch 식은 가능한 입력을 모두 처리하지는 않습니다(전체 아님). + + The switch expression does not handle some null inputs (it is not exhaustive). + The switch expression does not handle some null inputs (it is not exhaustive). + + + + The switch expression does not handle some null inputs. + The switch expression does not handle some null inputs. + + The switch expression does not handle all possible inputs (it is not exhaustive). switch 식은 가능한 입력을 모두 처리하지는 않습니다(전체 아님). diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index fd0b96420bf6..abc258030538 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -1567,6 +1567,16 @@ Wyrażenie switch nie obsługuje wszystkich możliwych danych wejściowych (nie jest kompletne). + + The switch expression does not handle some null inputs (it is not exhaustive). + The switch expression does not handle some null inputs (it is not exhaustive). + + + + The switch expression does not handle some null inputs. + The switch expression does not handle some null inputs. + + The switch expression does not handle all possible inputs (it is not exhaustive). Wyrażenie switch nie obsługuje wszystkich możliwych danych wejściowych (nie jest kompletne). diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index b75f6042790c..98990a04123c 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -1567,6 +1567,16 @@ A expressão switch não manipula todas as entradas possíveis (não é finita). + + The switch expression does not handle some null inputs (it is not exhaustive). + The switch expression does not handle some null inputs (it is not exhaustive). + + + + The switch expression does not handle some null inputs. + The switch expression does not handle some null inputs. + + The switch expression does not handle all possible inputs (it is not exhaustive). A expressão switch não manipula todas as entradas possíveis (não é finita). diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 0a3732915a94..912512f02479 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -1567,6 +1567,16 @@ Выражение switch обрабатывает не все возможные входные данные (оно не полное). + + The switch expression does not handle some null inputs (it is not exhaustive). + The switch expression does not handle some null inputs (it is not exhaustive). + + + + The switch expression does not handle some null inputs. + The switch expression does not handle some null inputs. + + The switch expression does not handle all possible inputs (it is not exhaustive). Выражение switch обрабатывает не все возможные входные данные (оно не полное). diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index 6d905ac3f7fa..6eb0d042a673 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -1568,6 +1568,16 @@ Switch ifadesi tüm olası girişleri işlemiyor (tam kapsamlı değil). + + The switch expression does not handle some null inputs (it is not exhaustive). + The switch expression does not handle some null inputs (it is not exhaustive). + + + + The switch expression does not handle some null inputs. + The switch expression does not handle some null inputs. + + The switch expression does not handle all possible inputs (it is not exhaustive). Switch ifadesi tüm olası girişleri işlemiyor (tam kapsamlı değil). diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index 28a8ba7489d9..77911ef1f3ca 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -357,302 +357,6 @@ 文件名“{0}”为空、包含无效字符、未使用绝对路径指定驱动器或太长 - - - Visual C# Compiler Options - - - OUTPUT FILES - --out:<file> Specify output file name (default: base name of - file with main class or first file) --target:exe Build a console executable (default) (Short - form: -t:exe) --target:winexe Build a Windows executable (Short form: - -t:winexe) --target:library Build a library (Short form: -t:library) --target:module Build a module that can be added to another - assembly (Short form: -t:module) --target:appcontainerexe Build an Appcontainer executable (Short form: - -t:appcontainerexe) --target:winmdobj Build a Windows Runtime intermediate file that - is consumed by WinMDExp (Short form: -t:winmdobj) --doc:<file> XML Documentation file to generate --refout:<file> Reference assembly output to generate --platform:<string> Limit which platforms this code can run on: x86, - Itanium, x64, arm, arm64, anycpu32bitpreferred, or - anycpu. The default is anycpu. - - - INPUT FILES - --recurse:<wildcard> Include all files in the current directory and - subdirectories according to the wildcard - specifications --reference:<alias>=<file> Reference metadata from the specified assembly - file using the given alias (Short form: -r) --reference:<file list> Reference metadata from the specified assembly - files (Short form: -r) --addmodule:<file list> Link the specified modules into this assembly --link:<file list> Embed metadata from the specified interop - assembly files (Short form: -l) --analyzer:<file list> Run the analyzers from this assembly - (Short form: -a) --additionalfile:<file list> Additional files that don't directly affect code - generation but may be used by analyzers for producing - errors or warnings. --embed Embed all source files in the PDB. --embed:<file list> Embed specific files in the PDB. - - - RESOURCES - --win32res:<file> Specify a Win32 resource file (.res) --win32icon:<file> Use this icon for the output --win32manifest:<file> Specify a Win32 manifest file (.xml) --nowin32manifest Do not include the default Win32 manifest --resource:<resinfo> Embed the specified resource (Short form: -res) --linkresource:<resinfo> Link the specified resource to this assembly - (Short form: -linkres) Where the resinfo format - is <file>[,<string name>[,public|private]] - - - CODE GENERATION - --debug[+|-] Emit debugging information --debug:{full|pdbonly|portable|embedded} - Specify debugging type ('full' is default, - 'portable' is a cross-platform format, - 'embedded' is a cross-platform format embedded into - the target .dll or .exe) --optimize[+|-] Enable optimizations (Short form: -o) --deterministic Produce a deterministic assembly - (including module version GUID and timestamp) --refonly Produce a reference assembly in place of the main output --instrument:TestCoverage Produce an assembly instrumented to collect - coverage information --sourcelink:<file> Source link info to embed into PDB. - - - ERRORS AND WARNINGS - --warnaserror[+|-] Report all warnings as errors --warnaserror[+|-]:<warn list> Report specific warnings as errors --warn:<n> Set warning level (0-4) (Short form: -w) --nowarn:<warn list> Disable specific warning messages --ruleset:<file> Specify a ruleset file that disables specific - diagnostics. --errorlog:<file> Specify a file to log all compiler and analyzer - diagnostics. --reportanalyzer Report additional analyzer information, such as - execution time. - - - LANGUAGE - --checked[+|-] Generate overflow checks --unsafe[+|-] Allow 'unsafe' code --define:<symbol list> Define conditional compilation symbol(s) (Short - form: -d) --langversion:? Display the allowed values for language version --langversion:<string> Specify language version such as - `default` (latest major version), or - `latest` (latest version, including minor versions), - or specific versions like `6` or `7.1` --nullable[+|-] Specify nullable context option enable|disable. --nullable:{enable|disable|safeonly|warnings|safeonlywarnings} - Specify nullable context option enable|disable|safeonly|warnings|safeonlywarnings. - - - SECURITY - --delaysign[+|-] Delay-sign the assembly using only the public - portion of the strong name key --publicsign[+|-] Public-sign the assembly using only the public - portion of the strong name key --keyfile:<file> Specify a strong name key file --keycontainer:<string> Specify a strong name key container --highentropyva[+|-] Enable high-entropy ASLR - - - MISCELLANEOUS - -@<file> Read response file for more options --help Display this usage message (Short form: -?) --nologo Suppress compiler copyright message --noconfig Do not auto include CSC.RSP file --parallel[+|-] Concurrent build. --version Display the compiler version number and exit. - - - ADVANCED - --baseaddress:<address> Base address for the library to be built --checksumalgorithm:<alg> Specify algorithm for calculating source file - checksum stored in PDB. Supported values are: - SHA1 or SHA256 (default). --codepage:<n> Specify the codepage to use when opening source - files --utf8output Output compiler messages in UTF-8 encoding --main:<type> Specify the type that contains the entry point - (ignore all other possible entry points) (Short - form: -m) --fullpaths Compiler generates fully qualified paths --filealign:<n> Specify the alignment used for output file - sections --pathmap:<K1>=<V1>,<K2>=<V2>,... - Specify a mapping for source path names output by - the compiler. --pdb:<file> Specify debug information file name (default: - output file name with .pdb extension) --errorendlocation Output line and column of the end location of - each error --preferreduilang Specify the preferred output language name. --nosdkpath Disable searching the default SDK path for standard library assemblies. --nostdlib[+|-] Do not reference standard library (mscorlib.dll) --subsystemversion:<string> Specify subsystem version of this assembly --lib:<file list> Specify additional directories to search in for - references --errorreport:<string> Specify how to handle internal compiler errors: - prompt, send, queue, or none. The default is - queue. --appconfig:<file> Specify an application configuration file - containing assembly binding settings --moduleassemblyname:<string> Name of the assembly which this module will be - a part of --modulename:<string> Specify the name of the source module - - - Visual C# 编译器选项 - - - 输出文件 - --out:<file> 指定输出文件名称(默认: 具有主类的文件或 - 第一个文件的基名称) --target:exe 生成控制台可执行文件(默认)(缩 - 写: -t:exe) --target:winexe 生成 Windows 可执行文件(缩写: - -t:winexe) --target:library 生成库(缩写: -t:library) --target:module 生成可添加到另一个程序集的 - 模块(缩写: -t:module) --target:appcontainerexe 生成 Appcontainer 可执行文件(缩写: - -t:appcontainerexe) --target:winmdobj 生成 WinMDExp 使用的 - Windows 运行时中间文件(缩写: -t:winmdobj) --doc:<file> 要生成的 XML 文档文件 --refout:<file> 要生成的引用程序集输出 --platform:<string> 限制此代码可以在其上运行的平台: x86、 - Itanium、x64、arm、arm64、anycpu32bitpreferred 或 - anycpu。默认平台为 anycpu。 - - - 输入文件 - --recurse:<wildcard> 根据通配符规范包括当前目录和 - 子目录中的所有 - 文件 --reference:<alias >=<file> 使用给定别名从指定程序集 - 引用元数据(缩写: -r) --reference:<file list> 从指定程序集文件引用 - 元数据(缩写: -r) --addmodule:<file list> 将指定模块链接到此程序集中 --link:<file list> 嵌入指定互操作程序集文件中的 - 元数据(缩写: -l) --analyzer:<file list> 运行此程序集中的分析器 - (缩写: -a) --additionalfile:<file list> 不会直接影响代码生成 - 但可能被分析器用于生成 - 错误或警告的其他文件。 --embed 嵌入 PDB 中的所有源文件。 --embed:<file list> 嵌入 PDB 中的特定文件 - - - 资源 - --win32res:<file> 指定 Win32 资源文件(.res) --win32icon:<file> 使用此图标输出 --win32manifest:<file> 指定 Win32 清单文件(.xml) --nowin32manifest 不包括默认的 Win32 清单 --resource:<resinfo> 嵌入指定资源(缩写: -res) --linkresource:<resinfo> 将指定资源链接到此程序集 - (缩写: -linkres)其中 resinfo 的格式 - 是 <文件>[,<字符串名称>[,public|private]] - - - 代码生成 - --debug[+|-] 发出调试信息 --debug:{full|pdbonly|portable|embedded} - 指定调试类型(默认为 "full", - "portable" 为跨平台格式, - "embedded" 为嵌入目标 .dll 或 .exe 的 - 跨平台格式) --optimize[+|-] 启用优化(缩写: -o) --deterministic 生成确定性的程序集 - (包括模块版本 GUID 和时间戳) --refonly 生成引用程序集来替换主要输出 --instrument:TestCoverage 生成对其检测以收集覆盖率信息的 - 程序集 --sourcelink:<file> 要嵌入到 PDB 中的源链接信息。 - - - 错误和警告 - --warnaserror[+|-] 将所有警告报告为错误 --warnaserror[+|-]:<warn list> 将特定警告报告为错误 --warn:<n> 设置警告级别(0-4)(缩写: -w) --nowarn:<warn list> 禁用特定警告消息 --ruleset:<file> 指定禁用特定诊断的 - 规则集文件。 --errorlog:<file> 指定用于记录所有编译器和分析器诊断的 - 文件。 --reportanalyzer 报告其他分析器信息,如 - 执行时间。 - - - 语言 - --checked[+|-] 生成溢出检查 --unsafe[+|-] 允许 "unsafe" 代码 --define:<symbol list> 定义条件编译符号(缩 - 写: -d) --langversion:? 显示允许的语言版本值 --langversion:<string> 指定语言版本,如 - “default” (最新主要版本)、 - “latest” (最新版本,包括次要版本) - 或 “6”、”7.1”等特定版本 --nullable[+|-] 指定可为 null 的上下文选项 enable|disable。 --nullable:{enable|disable|safeonly|warnings|safeonlywarnings} - 指定可为 null 的上下文选项 enable|disable|safeonly|warnings|safeonlywarnings。 - - - - 安全 - --delaysign[+|-] 仅使用强名称密钥的公共部分对程序集 - 进行延迟签名 --publicsign[+|-] 仅使用强名称密钥的公共部分对程序集 - 进行公共签名 --keyfile:<file> 指定强名称密钥文件 --keycontainer:<string> 指定强名称密钥容器 --highentropyva[+|-] 启用高平均信息量 ASLR - - - 杂项 - - @<file> 读取响应文件以获取更多选项 --help 显示此用法消息(缩写: -?) --nologo 取消显示编译器版权消息 --noconfig 不自动包括 CSC.RSP 文件 --parallel[+|-] 并发生成。 --version 显示编译器版本号并退出。 - - - 高级 - --baseaddress:<address> 要生成的库的基址 --checksumalgorithm:<alg> 指定计算存储在 PDB 中的源文件 - 校验和的算法。支持的值是: - SHA1 或 SHA256 (默认)。 --codepage:<n> 指定打开源文件时要使用的 - 代码页 --utf8output 以 UTF-8 编码格式输出编译器消息 --main:<type> 指定包含入口点的类型 - (忽略所有其他可能的入口点)(缩 - 写: -m) --fullpaths 编译器生成完全限定路径 --filealign:<n> 指定用于输出文件节的 - 对齐方式 --pathmap:<K1>=<V1>,<K2>=<V2>,... - 通过编译器指定源路径名称输出的 - 映射。 --pdb:<file> 指定调试信息文件名称(默认: - 具有 .pdb 扩展名的输出文件名) --errorendlocation 输出每个错误的结束位置 - 行和列 --preferreduilang 指定首选输出语言名称。 --nosdkpath 禁用搜索标准库程序集的默认 SDK 路径 --nostdlib[+|-] 不引用标准库(mscorlib.dll) --subsystemversion:<string> 指定此程序集的子系统版本 --lib:<file list> 指定要在其中搜索引用的附加 - 目录 --errorreport:<string> 指定如何处理内部编译器错误: - prompt、send、queue 或 none。默认为 - queue。 --appconfig:<file> 指定包含程序集绑定设置的 - 应用程序配置文件 --moduleassemblyname:<string> 此模块所属程序集 - 的名称 --modulename:<string> 指定源模块的名称 - - Visual C# Compiler Options - disposable 可处置的 @@ -1568,6 +1272,16 @@ Switch 表达式不会处理所有可能的输入(它并非详尽无遗)。 + + The switch expression does not handle some null inputs (it is not exhaustive). + The switch expression does not handle some null inputs (it is not exhaustive). + + + + The switch expression does not handle some null inputs. + The switch expression does not handle some null inputs. + + The switch expression does not handle all possible inputs (it is not exhaustive). Switch 表达式不会处理所有可能的输入(它并非详尽无遗)。 @@ -8424,6 +8138,301 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ 支持的语言版本: + + + Visual C# Compiler Options + + - OUTPUT FILES - +-out:<file> Specify output file name (default: base name of + file with main class or first file) +-target:exe Build a console executable (default) (Short + form: -t:exe) +-target:winexe Build a Windows executable (Short form: + -t:winexe) +-target:library Build a library (Short form: -t:library) +-target:module Build a module that can be added to another + assembly (Short form: -t:module) +-target:appcontainerexe Build an Appcontainer executable (Short form: + -t:appcontainerexe) +-target:winmdobj Build a Windows Runtime intermediate file that + is consumed by WinMDExp (Short form: -t:winmdobj) +-doc:<file> XML Documentation file to generate +-refout:<file> Reference assembly output to generate +-platform:<string> Limit which platforms this code can run on: x86, + Itanium, x64, arm, arm64, anycpu32bitpreferred, or + anycpu. The default is anycpu. + + - INPUT FILES - +-recurse:<wildcard> Include all files in the current directory and + subdirectories according to the wildcard + specifications +-reference:<alias>=<file> Reference metadata from the specified assembly + file using the given alias (Short form: -r) +-reference:<file list> Reference metadata from the specified assembly + files (Short form: -r) +-addmodule:<file list> Link the specified modules into this assembly +-link:<file list> Embed metadata from the specified interop + assembly files (Short form: -l) +-analyzer:<file list> Run the analyzers from this assembly + (Short form: -a) +-additionalfile:<file list> Additional files that don't directly affect code + generation but may be used by analyzers for producing + errors or warnings. +-embed Embed all source files in the PDB. +-embed:<file list> Embed specific files in the PDB. + + - RESOURCES - +-win32res:<file> Specify a Win32 resource file (.res) +-win32icon:<file> Use this icon for the output +-win32manifest:<file> Specify a Win32 manifest file (.xml) +-nowin32manifest Do not include the default Win32 manifest +-resource:<resinfo> Embed the specified resource (Short form: -res) +-linkresource:<resinfo> Link the specified resource to this assembly + (Short form: -linkres) Where the resinfo format + is <file>[,<string name>[,public|private]] + + - CODE GENERATION - +-debug[+|-] Emit debugging information +-debug:{full|pdbonly|portable|embedded} + Specify debugging type ('full' is default, + 'portable' is a cross-platform format, + 'embedded' is a cross-platform format embedded into + the target .dll or .exe) +-optimize[+|-] Enable optimizations (Short form: -o) +-deterministic Produce a deterministic assembly + (including module version GUID and timestamp) +-refonly Produce a reference assembly in place of the main output +-instrument:TestCoverage Produce an assembly instrumented to collect + coverage information +-sourcelink:<file> Source link info to embed into PDB. + + - ERRORS AND WARNINGS - +-warnaserror[+|-] Report all warnings as errors +-warnaserror[+|-]:<warn list> Report specific warnings as errors +-warn:<n> Set warning level (0-4) (Short form: -w) +-nowarn:<warn list> Disable specific warning messages +-ruleset:<file> Specify a ruleset file that disables specific + diagnostics. +-errorlog:<file> Specify a file to log all compiler and analyzer + diagnostics. +-reportanalyzer Report additional analyzer information, such as + execution time. + + - LANGUAGE - +-checked[+|-] Generate overflow checks +-unsafe[+|-] Allow 'unsafe' code +-define:<symbol list> Define conditional compilation symbol(s) (Short + form: -d) +-langversion:? Display the allowed values for language version +-langversion:<string> Specify language version such as + `default` (latest major version), or + `latest` (latest version, including minor versions), + or specific versions like `6` or `7.1` +-nullable[+|-] Specify nullable context option enable|disable. +-nullable:{enable|disable|safeonly|warnings|safeonlywarnings} + Specify nullable context option enable|disable|safeonly|warnings|safeonlywarnings. + + - SECURITY - +-delaysign[+|-] Delay-sign the assembly using only the public + portion of the strong name key +-publicsign[+|-] Public-sign the assembly using only the public + portion of the strong name key +-keyfile:<file> Specify a strong name key file +-keycontainer:<string> Specify a strong name key container +-highentropyva[+|-] Enable high-entropy ASLR + + - MISCELLANEOUS - +@<file> Read response file for more options +-help Display this usage message (Short form: -?) +-nologo Suppress compiler copyright message +-noconfig Do not auto include CSC.RSP file +-parallel[+|-] Concurrent build. +-version Display the compiler version number and exit. + + - ADVANCED - +-baseaddress:<address> Base address for the library to be built +-checksumalgorithm:<alg> Specify algorithm for calculating source file + checksum stored in PDB. Supported values are: + SHA1 or SHA256 (default). +-codepage:<n> Specify the codepage to use when opening source + files +-utf8output Output compiler messages in UTF-8 encoding +-main:<type> Specify the type that contains the entry point + (ignore all other possible entry points) (Short + form: -m) +-fullpaths Compiler generates fully qualified paths +-filealign:<n> Specify the alignment used for output file + sections +-pathmap:<K1>=<V1>,<K2>=<V2>,... + Specify a mapping for source path names output by + the compiler. +-pdb:<file> Specify debug information file name (default: + output file name with .pdb extension) +-errorendlocation Output line and column of the end location of + each error +-preferreduilang Specify the preferred output language name. +-nosdkpath Disable searching the default SDK path for standard library assemblies. +-nostdlib[+|-] Do not reference standard library (mscorlib.dll) +-subsystemversion:<string> Specify subsystem version of this assembly +-lib:<file list> Specify additional directories to search in for + references +-errorreport:<string> Specify how to handle internal compiler errors: + prompt, send, queue, or none. The default is + queue. +-appconfig:<file> Specify an application configuration file + containing assembly binding settings +-moduleassemblyname:<string> Name of the assembly which this module will be + a part of +-modulename:<string> Specify the name of the source module + + + Visual C# 编译器选项 + + - 输出文件 - +-out:<file> 指定输出文件名称(默认: 具有主类的文件或 + 第一个文件的基名称) +-target:exe 生成控制台可执行文件(默认)(缩 + 写: -t:exe) +-target:winexe 生成 Windows 可执行文件(缩写: + -t:winexe) +-target:library 生成库(缩写: -t:library) +-target:module 生成可添加到另一个程序集的 + 模块(缩写: -t:module) +-target:appcontainerexe 生成 Appcontainer 可执行文件(缩写: + -t:appcontainerexe) +-target:winmdobj 生成 WinMDExp 使用的 + Windows 运行时中间文件(缩写: -t:winmdobj) +-doc:<file> 要生成的 XML 文档文件 +-refout:<file> 要生成的引用程序集输出 +-platform:<string> 限制此代码可以在其上运行的平台: x86、 + Itanium、x64、arm、arm64、anycpu32bitpreferred 或 + anycpu。默认平台为 anycpu。 + + - 输入文件 - +-recurse:<wildcard> 根据通配符规范包括当前目录和 + 子目录中的所有 + 文件 +-reference:<alias >=<file> 使用给定别名从指定程序集 + 引用元数据(缩写: -r) +-reference:<file list> 从指定程序集文件引用 + 元数据(缩写: -r) +-addmodule:<file list> 将指定模块链接到此程序集中 +-link:<file list> 嵌入指定互操作程序集文件中的 + 元数据(缩写: -l) +-analyzer:<file list> 运行此程序集中的分析器 + (缩写: -a) +-additionalfile:<file list> 不会直接影响代码生成 + 但可能被分析器用于生成 + 错误或警告的其他文件。 +-embed 嵌入 PDB 中的所有源文件。 +-embed:<file list> 嵌入 PDB 中的特定文件 + + - 资源 - +-win32res:<file> 指定 Win32 资源文件(.res) +-win32icon:<file> 使用此图标输出 +-win32manifest:<file> 指定 Win32 清单文件(.xml) +-nowin32manifest 不包括默认的 Win32 清单 +-resource:<resinfo> 嵌入指定资源(缩写: -res) +-linkresource:<resinfo> 将指定资源链接到此程序集 + (缩写: -linkres)其中 resinfo 的格式 + 是 <文件>[,<字符串名称>[,public|private]] + + - 代码生成 - +-debug[+|-] 发出调试信息 +-debug:{full|pdbonly|portable|embedded} + 指定调试类型(默认为 "full", + "portable" 为跨平台格式, + "embedded" 为嵌入目标 .dll 或 .exe 的 + 跨平台格式) +-optimize[+|-] 启用优化(缩写: -o) +-deterministic 生成确定性的程序集 + (包括模块版本 GUID 和时间戳) +-refonly 生成引用程序集来替换主要输出 +-instrument:TestCoverage 生成对其检测以收集覆盖率信息的 + 程序集 +-sourcelink:<file> 要嵌入到 PDB 中的源链接信息。 + + - 错误和警告 - +-warnaserror[+|-] 将所有警告报告为错误 +-warnaserror[+|-]:<warn list> 将特定警告报告为错误 +-warn:<n> 设置警告级别(0-4)(缩写: -w) +-nowarn:<warn list> 禁用特定警告消息 +-ruleset:<file> 指定禁用特定诊断的 + 规则集文件。 +-errorlog:<file> 指定用于记录所有编译器和分析器诊断的 + 文件。 +-reportanalyzer 报告其他分析器信息,如 + 执行时间。 + + - 语言 - +-checked[+|-] 生成溢出检查 +-unsafe[+|-] 允许 "unsafe" 代码 +-define:<symbol list> 定义条件编译符号(缩 + 写: -d) +-langversion:? 显示允许的语言版本值 +-langversion:<string> 指定语言版本,如 + “default” (最新主要版本)、 + “latest” (最新版本,包括次要版本) + 或 “6”、”7.1”等特定版本 +-nullable[+|-] 指定可为 null 的上下文选项 enable|disable。 +-nullable:{enable|disable|safeonly|warnings|safeonlywarnings} + 指定可为 null 的上下文选项 enable|disable|safeonly|warnings|safeonlywarnings。 + + - 安全 - +-delaysign[+|-] 仅使用强名称密钥的公共部分对程序集 + 进行延迟签名 +-publicsign[+|-] 仅使用强名称密钥的公共部分对程序集 + 进行公共签名 +-keyfile:<file> 指定强名称密钥文件 +-keycontainer:<string> 指定强名称密钥容器 +-highentropyva[+|-] 启用高平均信息量 ASLR + + - 杂项 - + @<file> 读取响应文件以获取更多选项 +-help 显示此用法消息(缩写: -?) +-nologo 取消显示编译器版权消息 +-noconfig 不自动包括 CSC.RSP 文件 +-parallel[+|-] 并发生成。 +-version 显示编译器版本号并退出。 + + - 高级 - +-baseaddress:<address> 要生成的库的基址 +-checksumalgorithm:<alg> 指定计算存储在 PDB 中的源文件 + 校验和的算法。支持的值是: + SHA1 (默认)或 SHA256。 +-codepage:<n> 指定打开源文件时要使用的 + 代码页 +-utf8output 以 UTF-8 编码格式输出编译器消息 +-main:<type> 指定包含入口点的类型 + (忽略所有其他可能的入口点)(缩 + 写: -m) +-fullpaths 编译器生成完全限定路径 +-filealign:<n> 指定用于输出文件节的 + 对齐方式 +-pathmap:<K1>=<V1>,<K2>=<V2>,... + 通过编译器指定源路径名称输出的 + 映射。 +-pdb:<file> 指定调试信息文件名称(默认: + 具有 .pdb 扩展名的输出文件名) +-errorendlocation 输出每个错误的结束位置 + 行和列 +-preferreduilang 指定首选输出语言名称。 +-nosdkpath 禁用搜索标准库程序集的默认 SDK 路径 +-nostdlib[+|-] 不引用标准库(mscorlib.dll) +-subsystemversion:<string> 指定此程序集的子系统版本 +-lib:<file list> 指定要在其中搜索引用的附加 + 目录 +-errorreport:<string> 指定如何处理内部编译器错误: + prompt、send、queue 或 none。默认为 + queue。 +-appconfig:<file> 指定包含程序集绑定设置的 + 应用程序配置文件 +-moduleassemblyname:<string> 此模块所属程序集 + 的名称 +-modulename:<string> 指定源模块的名称 + + Visual C# Compiler Options + '{0}': a class with the ComImport attribute cannot specify field initializers. '“{0}”: 具有 ComImport 特性的类不能指定字段初始值设定项。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index e03d85f48ed3..5771a67d03b1 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -1567,6 +1567,16 @@ switch 運算式未處理所有可能的輸入 (其並不徹底)。 + + The switch expression does not handle some null inputs (it is not exhaustive). + The switch expression does not handle some null inputs (it is not exhaustive). + + + + The switch expression does not handle some null inputs. + The switch expression does not handle some null inputs. + + The switch expression does not handle all possible inputs (it is not exhaustive). switch 運算式未處理所有可能的輸入 (其並不徹底)。 diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IIsPatternExpression.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IIsPatternExpression.cs index a58740119961..cf8debed3aaf 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IIsPatternExpression.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IIsPatternExpression.cs @@ -1125,7 +1125,7 @@ void M1(object o, bool b) IInvalidOperation (OperationKind.Invalid, Type: null, IsInvalid, IsImplicit) (Syntax: 'var x') Children(0) Pattern: - IDeclarationPatternOperation (OperationKind.DeclarationPattern, Type: null, IsInvalid) (Syntax: 'var x') (InputType: ?, DeclaredSymbol: ? x, MatchesNull: True) + IDeclarationPatternOperation (OperationKind.DeclarationPattern, Type: null, IsInvalid) (Syntax: 'var x') (InputType: ?, DeclaredSymbol: ?? x, MatchesNull: True) "; VerifyOperationTreeForTest(compilation, expectedOperationTree); @@ -1177,7 +1177,8 @@ void M1(object o, bool b) IInvalidOperation (OperationKind.Invalid, Type: null, IsInvalid) (Syntax: 'Prop') Children(0) Pattern: - IDeclarationPatternOperation (OperationKind.DeclarationPattern, Type: null) (Syntax: 'var x') (InputType: ?, DeclaredSymbol: ? x, MatchesNull: True)"; + IDeclarationPatternOperation (OperationKind.DeclarationPattern, Type: null) (Syntax: 'var x') (InputType: ?, DeclaredSymbol: ?? x, MatchesNull: True) +"; VerifyOperationTreeForTest(compilation, expectedOperationTree); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index 508d65cefb1f..0cc75dd11366 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -21712,324 +21712,6 @@ static void F(object? o) ); } - [Fact] - public void ConditionalBranching_IsConstantPattern_Null() - { - CSharpCompilation c = CreateCompilation(new[] { @" -class C -{ - void Test(object? x) - { - if (x is null) - { - x.ToString(); // warn - } - else - { - x.ToString(); - } - } -} -" }, options: WithNonNullTypesTrue()); - - c.VerifyDiagnostics( - // (8,13): warning CS8602: Possible dereference of a null reference. - // x.ToString(); // warn - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(8, 13) - ); - } - - [Fact] - public void ConditionalBranching_IsConstantPattern_NullInverted() - { - CSharpCompilation c = CreateCompilation(new[] { @" -class C -{ - void Test(object? x) - { - if (!(x is null)) - { - x.ToString(); - } - else - { - x.ToString(); // warn - } - } -} -" }, options: WithNonNullTypesTrue()); - - c.VerifyDiagnostics( - // (12,13): warning CS8602: Possible dereference of a null reference. - // x.ToString(); // warn - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(12, 13) - ); - } - - [Fact] - public void ConditionalBranching_IsConstantPattern_NonNull() - { - CSharpCompilation c = CreateCompilation(new[] { @" -class C -{ - void Test(object? x) - { - const string nonNullConstant = ""hello""; - if (x is nonNullConstant) - { - x.ToString(); - } - else - { - x.ToString(); // warn - } - } -} -" }, options: WithNonNullTypesTrue()); - - c.VerifyDiagnostics( - // (13,13): warning CS8602: Possible dereference of a null reference. - // x.ToString(); // warn - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(13, 13) - ); - } - - [Fact] - public void ConditionalBranching_IsConstantPattern_NullConstant() - { - CSharpCompilation c = CreateCompilation(new[] { @" -class C -{ - void Test(object? x) - { - const string? nullConstant = null; - if (x is nullConstant) - { - x.ToString(); // warn - } - else - { - x.ToString(); - } - } -} -" }, options: WithNonNullTypesTrue()); - - c.VerifyDiagnostics( - // (9,13): warning CS8602: Possible dereference of a null reference. - // x.ToString(); // warn - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(9, 13) - ); - } - - [Fact] - public void ConditionalBranching_IsConstantPattern_NonConstant() - { - CSharpCompilation c = CreateCompilation(new[] { @" -class C -{ - void Test(object? x) - { - string nonConstant = ""hello""; - if (x is nonConstant) - { - x.ToString(); // warn - } - } -} -" }, options: WithNonNullTypesTrue()); - - c.VerifyDiagnostics( - // (7,18): error CS0150: A constant value is expected - // if (x is nonConstant) - Diagnostic(ErrorCode.ERR_ConstantExpected, "nonConstant").WithLocation(7, 18), - // (9,13): warning CS8602: Possible dereference of a null reference. - // x.ToString(); // warn - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(9, 13) - ); - } - - [Fact] - [WorkItem(29868, "https://github.com/dotnet/roslyn/issues/29868")] - public void ConditionalBranching_IsConstantPattern_Null_AlreadyTestedAsNonNull() - { - // https://github.com/dotnet/roslyn/issues/29868: confirm that we want such hidden warnings - CSharpCompilation c = CreateCompilation(new[] { @" -class C -{ - void Test(object? x) - { - if (x != null) - { - if (x is null) // hidden - { - x.ToString(); // warn - } - else - { - x.ToString(); - } - } - } -} -" }, options: WithNonNullTypesTrue()); - - c.VerifyDiagnostics( - // (10,17): warning CS8602: Possible dereference of a null reference. - // x.ToString(); // warn - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(10, 17) - ); - } - - [Fact] - public void ConditionalBranching_IsConstantPattern_Null_AlreadyTestedAsNull() - { - CSharpCompilation c = CreateCompilation(new[] { @" -class C -{ - void Test(object? x) - { - if (x == null) - { - if (x is null) - { - x.ToString(); // warn - } - else - { - x.ToString(); - } - } - } -} -" }, options: WithNonNullTypesTrue()); - - c.VerifyDiagnostics( - // (10,17): warning CS8602: Possible dereference of a null reference. - // x.ToString(); // warn - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(10, 17) - ); - } - - [Fact] - public void ConditionalBranching_IsDeclarationPattern() - { - CSharpCompilation c = CreateCompilation(new[] { @" -class C -{ - void Test(object? x) - { - if (x is C c) - { - x.ToString(); - c.ToString(); - } - else - { - x.ToString(); // warn - } - } -} -" }, options: WithNonNullTypesTrue()); - - c.VerifyDiagnostics( - // (13,13): warning CS8602: Possible dereference of a null reference. - // x.ToString(); // warn - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(13, 13) - ); - } - - [Fact] - public void ConditionalBranching_IsVarDeclarationPattern() - { - CSharpCompilation c = CreateCompilation(new[] { @" -class C -{ - void Test(object? x) - { - if (x is var c) - { - x.ToString(); // warn 1 - c /*T:object?*/ .ToString(); // warn 2 - } - else - { - x.ToString(); - } - } -} -" }, options: WithNonNullTypesTrue()); - - c.VerifyTypes(); - c.VerifyDiagnostics( - // (8,13): warning CS8602: Possible dereference of a null reference. - // x.ToString(); // warn 1 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(8, 13), - // (9,13): warning CS8602: Possible dereference of a null reference. - // c /*T:object?*/ .ToString(); // warn 2 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c").WithLocation(9, 13) - ); - } - - [Fact] - public void ConditionalBranching_IsVarDeclarationPattern_Discard() - { - CSharpCompilation c = CreateCompilation(new[] { @" -class C -{ - void Test(object? x) - { - if (x is var _) - { - x.ToString(); // warn 1 - } - else - { - x.ToString(); // warn 2 - } - } -} -" }, options: WithNonNullTypesTrue()); - - c.VerifyTypes(); - c.VerifyDiagnostics( - // (8,13): warning CS8602: Possible dereference of a null reference. - // x.ToString(); // warn 1 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(8, 13), - // (12,13): warning CS8602: Possible dereference of a null reference. - // x.ToString(); // warn 2 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(12, 13) - ); - } - - [Fact] - public void ConditionalBranching_IsVarDeclarationPattern_AlreadyTestedAsNonNull() - { - CSharpCompilation c = CreateCompilation(new[] { @" -class C -{ - void Test(object? x) - { - if (x != null) - { - if (x is var c) - { - c /*T:object!*/ .ToString(); - c = null; // warn - } - } - } -} -" }, options: WithNonNullTypesTrue()); - - c.VerifyTypes(); - c.VerifyDiagnostics( - // (11,21): warning CS8600: Converting null literal or possible null value to non-nullable type. - // c = null; // warn - Diagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, "null").WithLocation(11, 21) - ); - } - [Fact] public void ConditionalOperator_01() { @@ -29456,6 +29138,7 @@ static void F(B x, B y) } }"; var comp = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue(), references: new[] { ref0 }); + comp.VerifyTypes(); comp.VerifyDiagnostics( // (9,46): warning CS8619: Nullability of reference types in value of type 'B' doesn't match target type 'B'. // F(i => { switch (i) { case 0: return x; default: return y; }})/*T:B!*/; // 1 @@ -29470,7 +29153,6 @@ static void F(B x, B y) // F(i => { switch (i) { case 0: return z; case 1: return y; default: return x; }})/*T:B*/; // 4 Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "x").WithArguments("B", "B").WithLocation(18, 83) ); - comp.VerifyTypes(); } [Fact] @@ -41697,590 +41379,6 @@ static void G(object? o) Diagnostic(ErrorCode.WRN_NullReferenceArgument, "o").WithArguments("o", "void C.F(object o)").WithLocation(8, 15)); } - [Fact] - public void IsPattern_01() - { - var source = -@"class C -{ - static void F(object x) { } - static void G(string s) - { - F(s is var o); - } -}"; - var comp = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue()); - comp.VerifyDiagnostics(); - } - - [Fact] - [WorkItem(29909, "https://github.com/dotnet/roslyn/issues/29909")] - public void IsPattern_02() - { - var source = -@"class C -{ - static void F(string s) { } - static void G(string? s) - { - if (s is string t) - { - F(t); - F(s); - } - } -}"; - var comp = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue()); - comp.VerifyDiagnostics(); - } - - [Fact] - public void IsPattern_AffectsNullConditionalOperator_DeclarationPattern() - { - var source = -@"class C -{ - static void G(string? s) - { - if (s?.ToString() is string t) - { - s.ToString(); - } - else - { - s.ToString(); // warn - } - } -}"; - var comp = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue()); - comp.VerifyDiagnostics( - // (11,13): warning CS8602: Possible dereference of a null reference. - // s.ToString(); // warn - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(11, 13) - ); - } - - [Fact] - public void IsPattern_AffectsNullConditionalOperator_NullableValueType() - { - var source = -@"class C -{ - static void G(int? i) - { - if (i?.ToString() is string t) - { - i.Value.ToString(); - } - else - { - i.Value.ToString(); // warn - } - } -}"; - var comp = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue()); - comp.VerifyDiagnostics( - // (11,13): warning CS8629: Nullable value type may be null. - // i.Value.ToString(); // warn - Diagnostic(ErrorCode.WRN_NullableValueTypeMayBeNull, "i").WithLocation(11, 13) - ); - } - - [Fact] - public void IsPattern_AffectsNullConditionalOperator_NullableValueType_Nested() - { - var source = @" -public struct S -{ - public int? field; -} -class C -{ - static void G(S? s) - { - if (s?.field?.ToString() is string t) - { - s.Value.ToString(); - s.Value.field.Value.ToString(); - } - else - { - s.Value.ToString(); // warn - s.Value.field.Value.ToString(); // warn - } - } -}"; - var comp = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue()); - comp.VerifyDiagnostics( - // (17,13): warning CS8629: Nullable value type may be null. - // s.Value.ToString(); // warn - Diagnostic(ErrorCode.WRN_NullableValueTypeMayBeNull, "s").WithLocation(17, 13), - // (18,13): warning CS8629: Nullable value type may be null. - // s.Value.field.Value.ToString(); // warn - Diagnostic(ErrorCode.WRN_NullableValueTypeMayBeNull, "s.Value.field").WithLocation(18, 13) - ); - } - - [Fact, WorkItem(28798, "https://github.com/dotnet/roslyn/issues/28798")] - public void IsPattern_AffectsNullConditionalOperator_VarPattern() - { - var source = -@"class C -{ - static void G(string? s) - { - if (s?.ToString() is var t) - { - s.ToString(); // 1 - } - else - { - s.ToString(); - } - } -}"; - var comp = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue()); - comp.VerifyDiagnostics( - // (7,13): warning CS8602: Possible dereference of a null reference. - // s.ToString(); // 1 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(7, 13) - ); - } - - [Fact] - public void IsPattern_AffectsNullConditionalOperator_NullConstantPattern() - { - var source = -@"class C -{ - static void G(string? s) - { - if (s?.ToString() is null) - { - s.ToString(); // warn - } - else - { - s.ToString(); - } - } -}"; - var comp = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue()); - comp.VerifyDiagnostics( - // (7,13): warning CS8602: Possible dereference of a null reference. - // s.ToString(); // warn - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(7, 13) - ); - } - - // https://github.com/dotnet/roslyn/issues/29909: Should only warn on F(x) in `case null`. - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/29909")] - [WorkItem(29909, "https://github.com/dotnet/roslyn/issues/29909")] - public void PatternSwitch() - { - var source = -@"class C -{ - static void F(object o) { } - static void G(object? x) - { - switch (x) - { - case string s: - F(s); - F(x); // string s - break; - case object y when y is string t: - F(y); - F(t); - F(x); // object y - break; - case null: - F(x); // null - break; - default: - F(x); // default - break; - } - } -}"; - var comp = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue()); - comp.VerifyDiagnostics( - // (18,19): warning CS8604: Possible null reference argument for parameter 'o' in 'void C.F(object o)'. - // F(x); // null - Diagnostic(ErrorCode.WRN_NullReferenceArgument, "x").WithArguments("o", "void C.F(object o)").WithLocation(18, 19)); - } - - [Fact] - public void IsDeclarationPattern_01() - { - // https://github.com/dotnet/roslyn/issues/30952: `is` declaration does not set not nullable for declared local. - var source = -@"class Program -{ - static void F1(object x1) - { - if (x1 is string y1) - { - x1/*T:object!*/.ToString(); - y1/*T:string!*/.ToString(); - } - x1/*T:object!*/.ToString(); - } - static void F2(object? x2) - { - if (x2 is string y2) - { - x2/*T:object!*/.ToString(); - y2/*T:string!*/.ToString(); - } - x2/*T:object?*/.ToString(); // 1 - } - static void F3(object x3) - { - x3 = null; // 2 - if (x3 is string y3) - { - x3/*T:object!*/.ToString(); - y3/*T:string!*/.ToString(); - } - x3/*T:object?*/.ToString(); // 3 - } - static void F4(object? x4) - { - if (x4 == null) return; - if (x4 is string y4) - { - x4/*T:object!*/.ToString(); - y4/*T:string!*/.ToString(); - } - x4/*T:object!*/.ToString(); - } -}"; - var comp = CreateCompilation(source, options: WithNonNullTypesTrue()); - comp.VerifyDiagnostics( - // (19,9): warning CS8602: Possible dereference of a null reference. - // x2.ToString(); // 1 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x2").WithLocation(19, 9), - // (23,14): warning CS8600: Converting null literal or possible null value to non-nullable type. - // x3 = null; // 2 - Diagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, "null").WithLocation(23, 14), - // (29,9): warning CS8602: Possible dereference of a null reference. - // x3.ToString(); // 3 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x3").WithLocation(29, 9)); - comp.VerifyTypes(); - } - - [Fact] - public void IsDeclarationPattern_02() - { - var source = -@"class Program -{ - static void F1(T t1) - where T : class - where U : class - { - if (t1 is U u1) - { - t1.ToString(); - u1.ToString(); - } - t1.ToString(); - } - static void F2(T t2) - where T : class? - where U : class - { - if (t2 is U u2) - { - t2.ToString(); - u2.ToString(); - } - t2.ToString(); // 1 - } - static void F3(T t3) - where T : class - where U : class - { - t3 = null; // 2 - if (t3 is U u3) - { - t3.ToString(); - u3.ToString(); - } - t3.ToString(); // 3 - } - static void F4(T t4) - where T : class? - where U : class - { - if (t4 == null) return; - if (t4 is U u4) - { - t4.ToString(); - u4.ToString(); - } - t4.ToString(); - } -}"; - var comp = CreateCompilation(source, options: WithNonNullTypesTrue()); - comp.VerifyDiagnostics( - // (23,9): warning CS8602: Possible dereference of a null reference. - // t2.ToString(); // 1 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "t2").WithLocation(23, 9), - // (29,14): warning CS8600: Converting null literal or possible null value to non-nullable type. - // t3 = null; // 2 - Diagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, "null").WithLocation(29, 14), - // (35,9): warning CS8602: Possible dereference of a null reference. - // t3.ToString(); // 3 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "t3").WithLocation(35, 9)); - } - - [Fact] - public void IsDeclarationPattern_03() - { - var source = -@"class Program -{ - static void F1(T t1) - { - if (t1 is U u1) - { - t1.ToString(); - u1.ToString(); - } - t1.ToString(); // 1 - } - static void F2(T t2) - { - if (t2 == null) return; - if (t2 is U u2) - { - t2.ToString(); - u2.ToString(); - } - t2.ToString(); - } -}"; - var comp = CreateCompilation(source, options: WithNonNullTypesTrue()); - comp.VerifyDiagnostics( - // (10,9): warning CS8602: Possible dereference of a null reference. - // t1.ToString(); // 1 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "t1").WithLocation(10, 9)); - } - - [Fact] - public void IsDeclarationPattern_NeverNull_01() - { - var source = -@"class Program -{ - static void F1(object x1) - { - if (x1 is string y1) - { - x1.ToString(); - x1?.ToString(); - y1.ToString(); - y1?.ToString(); - } - x1.ToString(); // 1 - } - static void F2(object? x2) - { - if (x2 is string y2) - { - x2.ToString(); - x2?.ToString(); - y2.ToString(); - y2?.ToString(); - } - x2.ToString(); // 2 - } -}"; - // https://github.com/dotnet/roslyn/issues/30952: `is` declaration does not set not nullable for declared local. - var comp = CreateCompilation(source, options: WithNonNullTypesTrue()); - comp.VerifyDiagnostics( - // (12,9): warning CS8602: Possible dereference of a null reference. - // x1.ToString(); // 1 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x1").WithLocation(12, 9), - // (23,9): warning CS8602: Possible dereference of a null reference. - // x2.ToString(); // 2 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x2").WithLocation(23, 9)); - } - - [Fact] - public void IsDeclarationPattern_NeverNull_02() - { - var source = -@"class Program -{ - static void F1(T t1) - where T : class - where U : class - { - if (t1 is U u1) - { - t1?.ToString(); // 1 - u1?.ToString(); // 2 - } - t1?.ToString(); // 3 - } - static void F2(T t2) - where T : class? - where U : class - { - if (t2 is U u2) - { - t2?.ToString(); // 4 - u2?.ToString(); // 5 - } - t2?.ToString(); - } -}"; - // https://github.com/dotnet/roslyn/issues/30952: `is` declaration does not set not nullable for declared local. - var comp = CreateCompilation(source, options: WithNonNullTypesTrue()); - comp.VerifyDiagnostics( - ); - } - - [Fact] - public void IsDeclarationPattern_Unassigned_01() - { - var source = -@"class Program -{ - static void F1(object x1) - { - if (x1 is string y1) - { - } - else - { - x1.ToString(); - y1.ToString(); // 1 - } - } - static void F2(object? x2) - { - if (x2 is string y2) - { - } - else - { - x2.ToString(); // 2 - y2.ToString(); // 3 - } - } - static void F3(object x3) - { - x3 = null; // 4 - if (x3 is string y3) - { - } - else - { - x3.ToString(); // 5 - y3.ToString(); // 6 - } - } - static void F4(object? x4) - { - if (x4 == null) return; - if (x4 is string y4) - { - } - else - { - x4.ToString(); - y4.ToString(); // 7 - } - } -}"; - var comp = CreateCompilation(source, options: WithNonNullTypesTrue()); - comp.VerifyDiagnostics( - // (11,13): error CS0165: Use of unassigned local variable 'y1' - // y1.ToString(); // 1 - Diagnostic(ErrorCode.ERR_UseDefViolation, "y1").WithArguments("y1").WithLocation(11, 13), - // (21,13): warning CS8602: Possible dereference of a null reference. - // x2.ToString(); // 2 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x2").WithLocation(21, 13), - // (22,13): error CS0165: Use of unassigned local variable 'y2' - // y2.ToString(); // 3 - Diagnostic(ErrorCode.ERR_UseDefViolation, "y2").WithArguments("y2").WithLocation(22, 13), - // (27,14): warning CS8600: Converting null literal or possible null value to non-nullable type. - // x3 = null; // 4 - Diagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, "null").WithLocation(27, 14), - // (33,13): warning CS8602: Possible dereference of a null reference. - // x3.ToString(); // 5 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x3").WithLocation(33, 13), - // (34,13): error CS0165: Use of unassigned local variable 'y3' - // y3.ToString(); // 6 - Diagnostic(ErrorCode.ERR_UseDefViolation, "y3").WithArguments("y3").WithLocation(34, 13), - // (46,13): error CS0165: Use of unassigned local variable 'y4' - // y4.ToString(); // 7 - Diagnostic(ErrorCode.ERR_UseDefViolation, "y4").WithArguments("y4").WithLocation(46, 13)); - } - - [Fact] - public void IsDeclarationPattern_Unassigned_02() - { - var source = -@"class Program -{ - static void F1(object x1) - { - if (x1 is string y1) { } - x1.ToString(); - y1.ToString(); // 1 - } - static void F2(object? x2) - { - if (x2 is string y2) { } - x2.ToString(); // 2 - y2.ToString(); // 3 - } - static void F3(object x3) - { - x3 = null; // 4 - if (x3 is string y3) { } - x3.ToString(); // 5 - y3.ToString(); // 6 - } - static void F4(object? x4) - { - if (x4 == null) return; - if (x4 is string y4) { } - x4.ToString(); - y4.ToString(); // 7 - } -}"; - var comp = CreateCompilation(source, options: WithNonNullTypesTrue()); - comp.VerifyDiagnostics( - // (7,9): error CS0165: Use of unassigned local variable 'y1' - // y1.ToString(); // 1 - Diagnostic(ErrorCode.ERR_UseDefViolation, "y1").WithArguments("y1").WithLocation(7, 9), - // (12,9): warning CS8602: Possible dereference of a null reference. - // x2.ToString(); // 2 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x2").WithLocation(12, 9), - // (13,9): error CS0165: Use of unassigned local variable 'y2' - // y2.ToString(); // 3 - Diagnostic(ErrorCode.ERR_UseDefViolation, "y2").WithArguments("y2").WithLocation(13, 9), - // (17,14): warning CS8600: Converting null literal or possible null value to non-nullable type. - // x3 = null; // 4 - Diagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, "null").WithLocation(17, 14), - // (19,9): warning CS8602: Possible dereference of a null reference. - // x3.ToString(); // 5 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x3").WithLocation(19, 9), - // (20,9): error CS0165: Use of unassigned local variable 'y3' - // y3.ToString(); // 6 - Diagnostic(ErrorCode.ERR_UseDefViolation, "y3").WithArguments("y3").WithLocation(20, 9), - // (27,9): error CS0165: Use of unassigned local variable 'y4' - // y4.ToString(); // 7 - Diagnostic(ErrorCode.ERR_UseDefViolation, "y4").WithArguments("y4").WithLocation(27, 9)); - } - [Fact] public void Feature() { @@ -61049,56 +60147,6 @@ class C ); } - [Fact] - public void UnconstrainedTypeParameter_PatternMatching() - { - var source = -@" -class C -{ - static void F1(object o, T tin) - { - if (o is T t1) - { - t1.ToString(); - } - else - { - t1 = default; // 1 - } - - t1.ToString(); // 2 - - if (!(o is T t2)) - { - t2 = tin; - } - else - { - t2.ToString(); - } - t2.ToString(); // 3 - - if (!(o is T t3)) return; - t3.ToString(); - } -} -"; - - var comp = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue()); - comp.VerifyDiagnostics( - // (12,18): warning CS8652: A default expression introduces a null value when 'T' is a non-nullable reference type. - // t1 = default; // 1 - Diagnostic(ErrorCode.WRN_DefaultExpressionMayIntroduceNullT, "default").WithArguments("T").WithLocation(12, 18), - // (15,9): warning CS8602: Possible dereference of a null reference. - // t1.ToString(); // 2 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "t1").WithLocation(15, 9), - // (25,9): warning CS8602: Possible dereference of a null reference. - // t2.ToString(); // 3 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "t2").WithLocation(25, 9) - ); - } - [Fact] [WorkItem(29983, "https://github.com/dotnet/roslyn/issues/29983")] public void UnconstrainedTypeParameter_TypeInferenceThroughCall() @@ -70224,7 +69272,6 @@ void M0(object? x0, T z0) ref T M2(T x) => throw null!; } "; - // https://github.com/dotnet/roslyn/issues/30952 - Expect WRN_NullReferenceAssignment for [M2(y0) = z0;] CreateCompilation(source, options: WithNonNullTypesTrue()).VerifyDiagnostics(); } @@ -83363,27 +82410,6 @@ void M1(C? c1) Diagnostic(ErrorCode.WRN_NullReferenceArgument, "c1").WithArguments("c1", "C C.M2(C c1)").WithLocation(6, 19)); } - [WorkItem(32503, "https://github.com/dotnet/roslyn/issues/32503")] - [Fact] - public void PatternDeclarationBreaksNullableAnalysis() - { - var source = @" -#nullable enable -class A { } -class B : A -{ - A M() - { - var s = new A(); - if (s is B b) {} - return s; - } -} -"; - var comp = CreateCompilation(source); - comp.VerifyDiagnostics(); - } - [Fact] public void Deconstruction_01() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesVsPatterns.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesVsPatterns.cs new file mode 100644 index 000000000000..330789245525 --- /dev/null +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesVsPatterns.cs @@ -0,0 +1,1336 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Semantics +{ + [CompilerTrait(CompilerFeature.NullableReferenceTypes, CompilerFeature.Patterns)] + public class NullableReferenceTypesVsPatterns : CSharpTestBase + { + + [Fact] + public void ConditionalBranching_IsConstantPattern_Null() + { + CSharpCompilation c = CreateCompilation(new[] { @" +class C +{ + void Test(object? x) + { + if (x is null) + { + x.ToString(); // warn + } + else + { + x.ToString(); + } + } +} +" }, options: WithNonNullTypesTrue()); + + c.VerifyDiagnostics( + // (8,13): warning CS8602: Possible dereference of a null reference. + // x.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(8, 13) + ); + } + + [Fact] + public void ConditionalBranching_IsConstantPattern_NullInverted() + { + CSharpCompilation c = CreateCompilation(new[] { @" +class C +{ + void Test(object? x) + { + if (!(x is null)) + { + x.ToString(); + } + else + { + x.ToString(); // warn + } + } +} +" }, options: WithNonNullTypesTrue()); + + c.VerifyDiagnostics( + // (12,13): warning CS8602: Possible dereference of a null reference. + // x.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(12, 13) + ); + } + + [Fact] + public void ConditionalBranching_IsConstantPattern_NonNull() + { + CSharpCompilation c = CreateCompilation(new[] { @" +class C +{ + void Test(object? x) + { + const string nonNullConstant = ""hello""; + if (x is nonNullConstant) + { + x.ToString(); + } + else + { + x.ToString(); // warn + } + } +} +" }, options: WithNonNullTypesTrue()); + + c.VerifyDiagnostics( + // (13,13): warning CS8602: Possible dereference of a null reference. + // x.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(13, 13) + ); + } + + [Fact] + public void ConditionalBranching_IsConstantPattern_NullConstant() + { + CSharpCompilation c = CreateCompilation(new[] { @" +class C +{ + void Test(object? x) + { + const string? nullConstant = null; + if (x is nullConstant) + { + x.ToString(); // warn + } + else + { + x.ToString(); + } + } +} +" }, options: WithNonNullTypesTrue()); + + c.VerifyDiagnostics( + // (9,13): warning CS8602: Possible dereference of a null reference. + // x.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(9, 13) + ); + } + + [Fact] + public void ConditionalBranching_IsConstantPattern_NonConstant() + { + CSharpCompilation c = CreateCompilation(new[] { @" +class C +{ + void Test(object? x) + { + string nonConstant = ""hello""; + if (x is nonConstant) + { + x.ToString(); // warn + } + } +} +" }, options: WithNonNullTypesTrue()); + + c.VerifyDiagnostics( + // (7,18): error CS0150: A constant value is expected + // if (x is nonConstant) + Diagnostic(ErrorCode.ERR_ConstantExpected, "nonConstant").WithLocation(7, 18), + // (9,13): warning CS8602: Possible dereference of a null reference. + // x.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(9, 13) + ); + } + + [Fact] + [WorkItem(29868, "https://github.com/dotnet/roslyn/issues/29868")] + public void ConditionalBranching_IsConstantPattern_Null_AlreadyTestedAsNonNull() + { + // https://github.com/dotnet/roslyn/issues/29868: confirm that we want such hidden warnings + CSharpCompilation c = CreateCompilation(new[] { @" +class C +{ + void Test(object? x) + { + if (x != null) + { + if (x is null) // hidden + { + x.ToString(); // warn + } + else + { + x.ToString(); + } + } + } +} +" }, options: WithNonNullTypesTrue()); + + c.VerifyDiagnostics( + // (10,17): warning CS8602: Possible dereference of a null reference. + // x.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(10, 17) + ); + } + + [Fact] + public void ConditionalBranching_IsConstantPattern_Null_AlreadyTestedAsNull() + { + CSharpCompilation c = CreateCompilation(new[] { @" +class C +{ + void Test(object? x) + { + if (x == null) + { + if (x is null) + { + x.ToString(); // warn + } + else + { + x.ToString(); + } + } + } +} +" }, options: WithNonNullTypesTrue()); + + c.VerifyDiagnostics( + // (10,17): warning CS8602: Possible dereference of a null reference. + // x.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(10, 17) + ); + } + + [Fact] + public void ConditionalBranching_IsDeclarationPattern() + { + CSharpCompilation c = CreateCompilation(new[] { @" +class C +{ + void Test(object? x) + { + if (x is C c) + { + x.ToString(); + c.ToString(); + } + else + { + x.ToString(); // warn + } + } +} +" }, options: WithNonNullTypesTrue()); + + c.VerifyDiagnostics( + // (13,13): warning CS8602: Possible dereference of a null reference. + // x.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(13, 13) + ); + } + + [Fact] + public void ConditionalBranching_IsVarDeclarationPattern() + { + CSharpCompilation c = CreateCompilation(new[] { @" +class C +{ + void Test(object? x) + { + if (x is var c) + { + x.ToString(); // warn 1 + c /*T:object?*/ .ToString(); + } + else + { + x.ToString(); + } + } +} +" }, options: WithNonNullTypesTrue()); + + c.VerifyTypes(); + c.VerifyDiagnostics( + // (8,13): warning CS8602: Possible dereference of a null reference. + // x.ToString(); // warn 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(8, 13), + // (9,13): warning CS8602: Possible dereference of a null reference. + // c /*T:object?*/ .ToString(); // warn 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c").WithLocation(9, 13) + ); + } + + [Fact] + public void ConditionalBranching_IsVarDeclarationPattern_Discard() + { + CSharpCompilation c = CreateCompilation(new[] { @" +class C +{ + void Test(object? x) + { + if (x is var _) + { + x.ToString(); // 1 + } + else + { + x.ToString(); + } + } +} +" }, options: WithNonNullTypesTrue()); + + c.VerifyTypes(); + c.VerifyDiagnostics( + // (8,13): warning CS8602: Possible dereference of a null reference. + // x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(8, 13)); + } + + [Fact] + public void ConditionalBranching_IsVarDeclarationPattern_AlreadyTestedAsNonNull() + { + CSharpCompilation c = CreateCompilation(new[] { @" +class C +{ + void Test(object? x) + { + if (x != null) + { + if (x is var c) + { + c /*T:object!*/ .ToString(); + c = null; // warn + } + } + } +} +" }, options: WithNonNullTypesTrue()); + + c.VerifyTypes(); + c.VerifyDiagnostics( + // (11,21): warning CS8600: Converting null literal or possible null value to non-nullable type. + // c = null; // warn + Diagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, "null").WithLocation(11, 21) + ); + } + + [Fact] + public void IsPattern_01() + { + var source = +@"class C +{ + static void F(object x) { } + static void G(string s) + { + F(s is var o); + } +}"; + var comp = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue()); + comp.VerifyDiagnostics(); + } + + [Fact] + [WorkItem(29909, "https://github.com/dotnet/roslyn/issues/29909")] + public void IsPattern_02() + { + var source = +@"class C +{ + static void F(string s) { } + static void G(string? s) + { + if (s is string t) + { + F(t); + F(s); + } + } +}"; + var comp = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue()); + comp.VerifyDiagnostics(); + } + + [Fact] + public void IsPattern_AffectsNullConditionalOperator_DeclarationPattern() + { + var source = +@"class C +{ + static void G(string? s) + { + if (s?.ToString() is string t) + { + s.ToString(); + } + else + { + s.ToString(); // warn + } + } +}"; + var comp = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue()); + comp.VerifyDiagnostics( + // (11,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(11, 13) + ); + } + + [Fact] + public void IsPattern_AffectsNullConditionalOperator_NullableValueType() + { + var source = +@"class C +{ + static void G(int? i) + { + if (i?.ToString() is string t) + { + i.Value.ToString(); + } + else + { + i.Value.ToString(); // warn + } + } +}"; + var comp = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue()); + comp.VerifyDiagnostics( + // (11,13): warning CS8629: Nullable value type may be null. + // i.Value.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullableValueTypeMayBeNull, "i").WithLocation(11, 13) + ); + } + + [Fact] + public void IsPattern_AffectsNullConditionalOperator_NullableValueType_Nested() + { + var source = @" +public struct S +{ + public int? field; +} +class C +{ + static void G(S? s) + { + if (s?.field?.ToString() is string t) + { + s.Value.ToString(); + s.Value.field.Value.ToString(); + } + else + { + s.Value.ToString(); // warn + s.Value.field.Value.ToString(); // warn + } + } +}"; + var comp = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue()); + comp.VerifyDiagnostics( + // (17,13): warning CS8629: Nullable value type may be null. + // s.Value.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullableValueTypeMayBeNull, "s").WithLocation(17, 13), + // (18,13): warning CS8629: Nullable value type may be null. + // s.Value.field.Value.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullableValueTypeMayBeNull, "s.Value.field").WithLocation(18, 13) + ); + } + + [Fact, WorkItem(28798, "https://github.com/dotnet/roslyn/issues/28798")] + public void IsPattern_AffectsNullConditionalOperator_VarPattern() + { + var source = +@"class C +{ + static void G(string? s) + { + if (s?.ToString() is var t) + { + s.ToString(); // 1 + } + else + { + s.ToString(); + } + } +}"; + var comp = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue()); + comp.VerifyDiagnostics( + // (7,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(7, 13) + ); + } + + [Fact] + public void IsPattern_AffectsNullConditionalOperator_NullConstantPattern() + { + var source = +@"class C +{ + static void G(string? s) + { + if (s?.ToString() is null) + { + s.ToString(); // warn + } + else + { + s.ToString(); + } + } +}"; + var comp = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue()); + comp.VerifyDiagnostics( + // (7,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(7, 13) + ); + } + + [Fact] + [WorkItem(29909, "https://github.com/dotnet/roslyn/issues/29909")] + [WorkItem(23944, "https://github.com/dotnet/roslyn/issues/23944")] + public void PatternSwitch() + { + var source = +@"class C +{ + static void F(object o) { } + static void G(object? x) + { + switch (x) + { + case string s: + F(s); + F(x); // string s + break; + case object y when y is string t: + F(y); + F(t); + F(x); // object y + break; + case null: + F(x); // null + break; + default: + F(x); // default + break; + } + } +}"; + var comp = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue()); + comp.VerifyDiagnostics( + // (18,19): warning CS8604: Possible null reference argument for parameter 'o' in 'void C.F(object o)'. + // F(x); // null + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "x").WithArguments("o", "void C.F(object o)").WithLocation(18, 19)); + } + + [Fact] + public void IsDeclarationPattern_01() + { + var source = +@"class Program +{ + static void F1(object x1) + { + if (x1 is string y1) + { + x1/*T:object!*/.ToString(); + y1/*T:string!*/.ToString(); + } + x1/*T:object!*/.ToString(); + } + static void F2(object? x2) + { + if (x2 is string y2) + { + x2/*T:object!*/.ToString(); + y2/*T:string!*/.ToString(); + } + x2/*T:object?*/.ToString(); // 1 + } + static void F3(object x3) + { + x3 = null; // 2 + if (x3 is string y3) + { + x3/*T:object!*/.ToString(); + y3/*T:string!*/.ToString(); + } + x3/*T:object?*/.ToString(); // 3 + } + static void F4(object? x4) + { + if (x4 == null) return; + if (x4 is string y4) + { + x4/*T:object!*/.ToString(); + y4/*T:string!*/.ToString(); + } + x4/*T:object!*/.ToString(); + } +}"; + var comp = CreateCompilation(source, options: WithNonNullTypesTrue()); + comp.VerifyDiagnostics( + // (19,9): warning CS8602: Possible dereference of a null reference. + // x2.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x2").WithLocation(19, 9), + // (23,14): warning CS8600: Converting null literal or possible null value to non-nullable type. + // x3 = null; // 2 + Diagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, "null").WithLocation(23, 14), + // (29,9): warning CS8602: Possible dereference of a null reference. + // x3.ToString(); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x3").WithLocation(29, 9)); + comp.VerifyTypes(); + } + + [Fact] + public void IsDeclarationPattern_02() + { + var source = +@"class Program +{ + static void F1(T t1) + where T : class + where U : class + { + if (t1 is U u1) + { + t1.ToString(); + u1.ToString(); + } + t1.ToString(); + } + static void F2(T t2) + where T : class? + where U : class + { + if (t2 is U u2) + { + t2.ToString(); + u2.ToString(); + } + t2.ToString(); // 1 + } + static void F3(T t3) + where T : class + where U : class + { + t3 = null; // 2 + if (t3 is U u3) + { + t3.ToString(); + u3.ToString(); + } + t3.ToString(); // 3 + } + static void F4(T t4) + where T : class? + where U : class + { + if (t4 == null) return; + if (t4 is U u4) + { + t4.ToString(); + u4.ToString(); + } + t4.ToString(); + } +}"; + var comp = CreateCompilation(source, options: WithNonNullTypesTrue()); + comp.VerifyDiagnostics( + // (23,9): warning CS8602: Possible dereference of a null reference. + // t2.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "t2").WithLocation(23, 9), + // (29,14): warning CS8600: Converting null literal or possible null value to non-nullable type. + // t3 = null; // 2 + Diagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, "null").WithLocation(29, 14), + // (35,9): warning CS8602: Possible dereference of a null reference. + // t3.ToString(); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "t3").WithLocation(35, 9)); + } + + [Fact] + public void IsDeclarationPattern_03() + { + var source = +@"class Program +{ + static void F1(T t1) + { + if (t1 is U u1) + { + t1.ToString(); + u1.ToString(); + } + t1.ToString(); // 1 + } + static void F2(T t2) + { + if (t2 == null) return; + if (t2 is U u2) + { + t2.ToString(); + u2.ToString(); + } + t2.ToString(); + } +}"; + var comp = CreateCompilation(source, options: WithNonNullTypesTrue()); + comp.VerifyDiagnostics( + // (10,9): warning CS8602: Possible dereference of a null reference. + // t1.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "t1").WithLocation(10, 9)); + } + + [Fact] + public void IsDeclarationPattern_NeverNull_01() + { + var source = +@"class Program +{ + static void F1(object x1) + { + if (x1 is string y1) + { + x1.ToString(); + x1?.ToString(); + y1.ToString(); + y1?.ToString(); + } + x1.ToString(); // 1 (because of x1?. above) + } + static void F2(object? x2) + { + if (x2 is string y2) + { + x2.ToString(); + x2?.ToString(); + y2.ToString(); + y2?.ToString(); + } + x2.ToString(); // 2 + } +}"; + var comp = CreateCompilation(source, options: WithNonNullTypesTrue()); + comp.VerifyDiagnostics( + // (12,9): warning CS8602: Possible dereference of a null reference. + // x1.ToString(); // 1 (because of x1?. above) + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x1").WithLocation(12, 9), + // (23,9): warning CS8602: Possible dereference of a null reference. + // x2.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x2").WithLocation(23, 9)); + } + + [Fact] + [WorkItem(30952, "https://github.com/dotnet/roslyn/issues/30952")] + public void IsDeclarationPattern_NeverNull_02() + { + var source = +@"class Program +{ + static void F1(T t1) + where T : class + where U : class + { + if (t1 is U u1) + { + t1.ToString(); + u1.ToString(); + } + t1.ToString(); + } + static void F2(T t2) + where T : class? + where U : class + { + if (t2 is U u2) + { + t2.ToString(); + u2.ToString(); + } + t2.ToString(); // 1 + } +}"; + var comp = CreateCompilation(source, options: WithNonNullTypesTrue()); + comp.VerifyDiagnostics( + // (23,9): warning CS8602: Possible dereference of a null reference. + // t2.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "t2").WithLocation(23, 9)); + } + + [Fact] + public void IsDeclarationPattern_Unassigned_01() + { + var source = +@"class Program +{ + static void F1(object x1) + { + if (x1 is string y1) + { + } + else + { + x1.ToString(); + y1.ToString(); // 1 + } + } + static void F2(object? x2) + { + if (x2 is string y2) + { + } + else + { + x2.ToString(); // 2 + y2.ToString(); // 3 + } + } + static void F3(object x3) + { + x3 = null; // 4 + if (x3 is string y3) + { + } + else + { + x3.ToString(); // 5 + y3.ToString(); // 6 + } + } + static void F4(object? x4) + { + if (x4 == null) return; + if (x4 is string y4) + { + } + else + { + x4.ToString(); + y4.ToString(); // 7 + } + } +}"; + var comp = CreateCompilation(source, options: WithNonNullTypesTrue()); + comp.VerifyDiagnostics( + // (11,13): error CS0165: Use of unassigned local variable 'y1' + // y1.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y1").WithArguments("y1").WithLocation(11, 13), + // (21,13): warning CS8602: Possible dereference of a null reference. + // x2.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x2").WithLocation(21, 13), + // (22,13): error CS0165: Use of unassigned local variable 'y2' + // y2.ToString(); // 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y2").WithArguments("y2").WithLocation(22, 13), + // (27,14): warning CS8600: Converting null literal or possible null value to non-nullable type. + // x3 = null; // 4 + Diagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, "null").WithLocation(27, 14), + // (33,13): warning CS8602: Possible dereference of a null reference. + // x3.ToString(); // 5 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x3").WithLocation(33, 13), + // (34,13): error CS0165: Use of unassigned local variable 'y3' + // y3.ToString(); // 6 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y3").WithArguments("y3").WithLocation(34, 13), + // (46,13): error CS0165: Use of unassigned local variable 'y4' + // y4.ToString(); // 7 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y4").WithArguments("y4").WithLocation(46, 13)); + } + + [Fact] + public void IsDeclarationPattern_Unassigned_02() + { + var source = +@"class Program +{ + static void F1(object x1) + { + if (x1 is string y1) { } + x1.ToString(); + y1.ToString(); // 1 + } + static void F2(object? x2) + { + if (x2 is string y2) { } + x2.ToString(); // 2 + y2.ToString(); // 3 + } + static void F3(object x3) + { + x3 = null; // 4 + if (x3 is string y3) { } + x3.ToString(); // 5 + y3.ToString(); // 6 + } + static void F4(object? x4) + { + if (x4 == null) return; + if (x4 is string y4) { } + x4.ToString(); + y4.ToString(); // 7 + } +}"; + var comp = CreateCompilation(source, options: WithNonNullTypesTrue()); + comp.VerifyDiagnostics( + // (7,9): error CS0165: Use of unassigned local variable 'y1' + // y1.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y1").WithArguments("y1").WithLocation(7, 9), + // (12,9): warning CS8602: Possible dereference of a null reference. + // x2.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x2").WithLocation(12, 9), + // (13,9): error CS0165: Use of unassigned local variable 'y2' + // y2.ToString(); // 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y2").WithArguments("y2").WithLocation(13, 9), + // (17,14): warning CS8600: Converting null literal or possible null value to non-nullable type. + // x3 = null; // 4 + Diagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, "null").WithLocation(17, 14), + // (19,9): warning CS8602: Possible dereference of a null reference. + // x3.ToString(); // 5 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x3").WithLocation(19, 9), + // (20,9): error CS0165: Use of unassigned local variable 'y3' + // y3.ToString(); // 6 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y3").WithArguments("y3").WithLocation(20, 9), + // (27,9): error CS0165: Use of unassigned local variable 'y4' + // y4.ToString(); // 7 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y4").WithArguments("y4").WithLocation(27, 9)); + } + + [Fact] + public void UnconstrainedTypeParameter_PatternMatching() + { + var source = +@" +class C +{ + static void F1(object o, T tin) + { + if (o is T t1) + { + t1.ToString(); + } + else + { + t1 = default; // 1 + } + + t1.ToString(); // 2 + + if (!(o is T t2)) + { + t2 = tin; + } + else + { + t2.ToString(); + } + t2.ToString(); // 3 + + if (!(o is T t3)) return; + t3.ToString(); + } +} +"; + + var comp = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue()); + comp.VerifyDiagnostics( + // (12,18): warning CS8652: A default expression introduces a null value when 'T' is a non-nullable reference type. + // t1 = default; // 1 + Diagnostic(ErrorCode.WRN_DefaultExpressionMayIntroduceNullT, "default").WithArguments("T").WithLocation(12, 18), + // (15,9): warning CS8602: Possible dereference of a null reference. + // t1.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "t1").WithLocation(15, 9), + // (25,9): warning CS8602: Possible dereference of a null reference. + // t2.ToString(); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "t2").WithLocation(25, 9) + ); + } + + [WorkItem(32503, "https://github.com/dotnet/roslyn/issues/32503")] + [Fact] + public void PatternDeclarationBreaksNullableAnalysis() + { + var source = @" +#nullable enable +class A { } +class B : A +{ + A M() + { + var s = new A(); + if (s is B b) {} + return s; + } +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + } + + [Fact] + public void InferenceWithITuplePattern() + { + var source = @" +#nullable enable +class A { } +class B : A +{ + A M() + { + var s = new A(); + if (s is B b) {} + return s; + } +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + } + + [Fact] + public void RecursivePatternNullInferenceWithDowncast_01() + { + var source = @" +#nullable enable +class Base +{ + public object Value = """"; +} +class Derived : Base +{ + public new object Value = """"; +} +class Program +{ + void M(Base? b) + { + if (b is Derived { Value: null }) + b.Value.ToString(); + } +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + } + + [Fact] + public void RecursivePatternNullInferenceWithDowncast_02() + { + var source = @" +#nullable enable +class Base +{ + public object Value = """"; +} +class Derived : Base +{ +} +class Program +{ + void M(Base? b) + { + if (b is Derived { Value: null }) + b.Value.ToString(); // 1 + } +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (15,13): warning CS8602: Possible dereference of a null reference. + // b.Value.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "b.Value").WithLocation(15, 13)); + } + + [Fact] + public void TuplePatternNullInference_01() + { + var source = @" +#nullable enable +class Program +{ + void M((object, object) t) + { + if (t is (1, null)) + { + } + else + { + t.Item2.ToString(); // 1 + } + } +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (12,13): warning CS8602: Possible dereference of a null reference. + // t.Item2.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "t.Item2").WithLocation(12, 13)); + } + + [Fact] + public void MultiplePathsThroughDecisionDag_01() + { + var source = @" +#nullable enable +class Program +{ + bool M(object? o, bool cond = true) + { + o = 1; + switch (o) + { + case null: + throw null!; + case """" when M(o = null): + break; + default: + if (cond) o.ToString(); // warning + break; + } + + return cond; + } +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (16,27): warning CS8602: Possible dereference of a null reference. + // if (cond) o.ToString(); // warning + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "o").WithLocation(15, 27)); + } + + [Fact] + [WorkItem(30597, "https://github.com/dotnet/roslyn/issues/30597")] + [WorkItem(32414, "https://github.com/dotnet/roslyn/issues/32414")] + public void NotExhaustiveForNull_01() + { + var source = @" +#nullable enable +class Program +{ + void M1(object o) + { + var t = (o, o); + _ = t switch // 1 not exhaustive + { + (null, 2) => 1, + ({}, {}) => 2, + }; + } + void M2(object o) + { + var t = (o, o); + _ = t switch + { + (1, 2) => 1, + ({}, {}) => 2, + }; + } + void M3(object o) + { + var t = (o, o); + _ = t switch + { + (null, 2) => 1, + ({}, {}) => 2, + (null, {}) => 3, + }; + } + void M4(object o) + { + var t = (o, o); + _ = t switch // 2 not exhaustive + { + { Item1: null, Item2: 2 } => 1, + { Item1: {}, Item2: {} } => 2, + }; + } + void M5(object o) + { + var t = (o, o); + _ = t switch + { + { Item1: 1, Item2: 2 } => 1, + { Item1: {}, Item2: {} } => 2, + }; + } + void M6(object o) + { + var t = (o, o); + _ = t switch + { + { Item1: null, Item2: 2 } => 1, + { Item1: {}, Item2: {} } => 2, + { Item1: null, Item2: {} } => 3, + }; + } + void M7(object o, bool b) + { + _ = o switch // 3 not exhaustive + { + null when b => 1, + {} => 2, + }; + } + void M8(object o, bool b) + { + _ = o switch + { + null when b => 1, + {} => 2, + null => 3, + }; + } +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (8,15): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). + // _ = t switch // 1 not exhaustive + Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithLocation(8, 15), + // (36,15): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). + // _ = t switch // 2 not exhaustive + Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithLocation(36, 15), + // (63,15): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). + // _ = o switch // 3 not exhaustive + Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithLocation(63, 15)); + } + + [Fact, WorkItem(31881, "https://github.com/dotnet/roslyn/issues/31881")] + public void NullableVsPattern_31881() + { + var source = @" +using System; +#nullable enable + +public class C +{ + public object? AProperty { get; set; } + public void M(C? input, int i) + { + if (input?.AProperty is string str) + { + Console.WriteLine(str.ToString()); + + switch (i) + { + case 1: + Console.WriteLine(input?.AProperty.ToString()); + break; + case 2: + Console.WriteLine(input.AProperty.ToString()); + break; + case 3: + Console.WriteLine(input.ToString()); + break; + } + } + } +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + } + + [Fact, WorkItem(33499, "https://github.com/dotnet/roslyn/issues/33499")] + public void PatternVariablesAreNotOblivious_33499() + { + var source = @" +#nullable enable +class Test +{ + static void M(object o) + { + if (o is string s) { } + s = null; + } +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (8,13): warning CS8600: Converting null literal or possible null value to non-nullable type. + // s = null; + Diagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, "null").WithLocation(8, 13)); + } + + [Fact] + [WorkItem(30597, "https://github.com/dotnet/roslyn/issues/30597")] + [WorkItem(32414, "https://github.com/dotnet/roslyn/issues/32414")] + public void NotExhaustiveForNull_02() + { + var source = @" +#nullable enable +class Test +{ + int M1(string s1, string s2) + { + return (s1, s2) switch { + (string x, string y) => x.Length + y.Length + }; + } + int M2(string? s1, string? s2) + { + return (s1, s2) switch { // 1 + (string x, string y) => x.Length + y.Length + }; + } +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (13,25): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). + // return (s1, s2) switch { // 1 + Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithLocation(13, 25)); + } + + [Fact] + public void IsPatternAlwaysFalse() + { + var source = @" +#nullable enable +class Test +{ + void M1(ref object o) + { + if (2 is 3) + o = null; + } +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (7,13): warning CS8519: The given expression never matches the provided pattern. + // if (2 is 3) + Diagnostic(ErrorCode.WRN_GivenExpressionNeverMatchesPattern, "2 is 3").WithLocation(7, 13)); + } + + [Fact] + [WorkItem(29619, "https://github.com/dotnet/roslyn/issues/29619")] + public void StructWithNotBackedProperty() + { + var source = @" +#nullable enable +struct Point +{ + public object X, Y; + public Point Mirror => new Point { X = Y, Y = X }; + bool Test => this is { X: 1, Y: 2, Mirror: { X: 2, Y: 1 } }; +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + } + } +} diff --git a/src/Compilers/CSharp/Test/WinRT/Metadata/WinMdMetadataTests.cs b/src/Compilers/CSharp/Test/WinRT/Metadata/WinMdMetadataTests.cs index dc43a5345a33..e066f2283222 100644 --- a/src/Compilers/CSharp/Test/WinRT/Metadata/WinMdMetadataTests.cs +++ b/src/Compilers/CSharp/Test/WinRT/Metadata/WinMdMetadataTests.cs @@ -35,7 +35,7 @@ public class WinMdMetadataTests : CSharpTestBase /// System.Runtime.WindowsRuntime assembly. /// [Fact] - public void FunctionPrototypeForwarded() + public void FunctionSignatureForwarded() { var text = "public class A{};"; var comp = CreateCompilationWithWinRT(text);