From f35fdedca2b9d4560a0e20b576aea535aefe4685 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Sat, 5 May 2018 09:59:20 -0700 Subject: [PATCH 01/18] Add well-known Debug.Assert method --- .../Portable/FlowAnalysis/NullableWalker.cs | 41 +++- .../Lowering/SyntheticBoundNodeFactory.cs | 8 +- .../Semantic/Semantics/StaticNullChecking.cs | 178 ++++++++++++++++++ .../Core/Portable/WellKnownMember.cs | 5 + .../Core/Portable/WellKnownMembers.cs | 41 ++++ src/Compilers/Core/Portable/WellKnownTypes.cs | 4 + 6 files changed, 271 insertions(+), 6 deletions(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index fa81d9b3e96e4..a547abe77691d 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -59,6 +59,8 @@ internal sealed partial class NullableWalker : DataFlowPassBase private PooledDictionary _placeholderLocals; + private bool _disableDiagnostics = false; + protected override void Free() { _variableTypes.Free(); @@ -464,7 +466,10 @@ private bool IsByRefTarget(int slot) private void ReportStaticNullCheckingDiagnostics(ErrorCode errorCode, SyntaxNode syntaxNode, params object[] arguments) { - Diagnostics.Add(errorCode, syntaxNode.GetLocation(), arguments); + if (!_disableDiagnostics) + { + Diagnostics.Add(errorCode, syntaxNode.GetLocation(), arguments); + } } private void InheritNullableStateOfTrackableStruct(TypeSymbol targetType, int targetSlot, int valueSlot, bool isByRefTarget, int slotWatermark) @@ -1574,16 +1579,17 @@ public override BoundNode VisitCall(BoundCall node) // PROTOTYPE(NullableReferenceTypes): Can we handle some error cases? // (Compare with CSharpOperationFactory.CreateBoundCallOperation.) + ImmutableArray arguments = node.Arguments; if (!node.HasErrors) { ImmutableArray refKindsOpt = node.ArgumentRefKindsOpt; - ImmutableArray arguments = RemoveArgumentConversions(node.Arguments, refKindsOpt); - ImmutableArray results = VisitArgumentsEvaluate(arguments, refKindsOpt, node.Expanded); + ImmutableArray strippedArguments = RemoveArgumentConversions(arguments, refKindsOpt); + ImmutableArray results = VisitArgumentsEvaluate(strippedArguments, refKindsOpt, node.Expanded); if (method.IsGenericMethod && HasImplicitTypeArguments(node)) { method = InferMethod(node, method, results.SelectAsArray(r => r.Type)); } - VisitArgumentsWarn(arguments, refKindsOpt, method.Parameters, node.ArgsToParamsOpt, node.Expanded, results); + VisitArgumentsWarn(strippedArguments, refKindsOpt, method.Parameters, node.ArgsToParamsOpt, node.Expanded, results); } UpdateStateForCall(node); @@ -1594,6 +1600,11 @@ public override BoundNode VisitCall(BoundCall node) ReplayReadsAndWrites(localFunc, node.Syntax, writes: true); } + if (MethodEnsuresTrueWhenExits(method) && arguments.Length > 0) + { + VisitTrueWhenExits(condition: arguments[0]); + } + Debug.Assert(!IsConditionalState); //if (this.State.Reachable) // PROTOTYPE(NullableReferenceTypes): Consider reachability? { @@ -1603,6 +1614,28 @@ public override BoundNode VisitCall(BoundCall node) return null; } + private bool MethodEnsuresTrueWhenExits(MethodSymbol method) + { + return method.Equals(compilation.GetWellKnownTypeMember(WellKnownMember.System_Diagnostics_Debug_Assert1)) || + method.Equals(compilation.GetWellKnownTypeMember(WellKnownMember.System_Diagnostics_Debug_Assert2)) || + method.Equals(compilation.GetWellKnownTypeMember(WellKnownMember.System_Diagnostics_Debug_Assert3)) || + method.Equals(compilation.GetWellKnownTypeMember(WellKnownMember.System_Diagnostics_Debug_Assert4)); + } + + private void VisitTrueWhenExits(BoundExpression condition) + { + // We only need to update the null-state, so we'll do the flow analysis without diagnostics + + bool savedDisableDiagnostics = _disableDiagnostics; + _disableDiagnostics = true; + + // if (!condition) throw; + VisitIfStatement(new BoundIfStatement(condition.Syntax, SyntheticBoundNodeFactory.NotCore(condition), + new BoundThrowStatement(condition.Syntax, expressionOpt: null), alternativeOpt: null)); + + _disableDiagnostics = savedDisableDiagnostics; + } + // PROTOTYPE(NullableReferenceTypes): Record in the node whether type // arguments were implicit, to allow for cases where the syntax is not an // invocation (such as a synthesized call from a query interpretation). diff --git a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs index 8d6d0476b72d4..adf67869fdd27 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs @@ -1252,8 +1252,12 @@ internal static BoundExpression NullOrDefault(TypeSymbol typeSymbol, SyntaxNode return typeSymbol.IsReferenceType ? Null(typeSymbol, syntax) : Default(typeSymbol, syntax); } - internal BoundExpression Not( - BoundExpression expression) + internal BoundExpression Not(BoundExpression expression) + { + return NotCore(expression); + } + + internal static BoundUnaryOperator NotCore(BoundExpression expression) { return new BoundUnaryOperator(expression.Syntax, UnaryOperatorKind.BoolLogicalNegation, expression, null, null, LookupResultKind.Viable, expression.Type); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs index de793af9db693..639e22e48278e 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs @@ -5437,6 +5437,184 @@ class CL1 ); } + [Fact] + public void Wellknown_Debug_Assert1_NotNull() + { + CSharpCompilation c = CreateCompilation(@" +class C +{ + void Main(C? c) + { + System.Diagnostics.Debug.Assert(c != null); + c.ToString(); + } +} +", parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics(); + } + + [Fact] + public void Wellknown_Debug_Assert1_NotNullAndNotEmpty() + { + CSharpCompilation c = CreateCompilation(@" +class C +{ + void Main(string? c) + { + System.Diagnostics.Debug.Assert(c != null && c != """"); + c.ToString(); + } +} +", parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics(); + } + + [Fact] + public void Wellknown_Debug_Assert1_NotNullOrUnknown() + { + CSharpCompilation c = CreateCompilation(@" +class C +{ + void Main(string? c, bool b) + { + System.Diagnostics.Debug.Assert(c != null || b); + c.ToString(); + } +} +", parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (7,9): warning CS8602: Possible dereference of a null reference. + // c.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c").WithLocation(7, 9) + ); + } + + [Fact] + public void Wellknown_Debug_Assert2_NotNull() + { + CSharpCompilation c = CreateCompilation(@" +class C +{ + void Main(C? c) + { + System.Diagnostics.Debug.Assert(c != null, ""hello""); + c.ToString(); + } +} +", parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics(); + } + + [Fact] + public void Wellknown_Debug_Assert3_NotNull() + { + CSharpCompilation c = CreateCompilation(@" +class C +{ + void Main(C? c) + { + System.Diagnostics.Debug.Assert(c != null, ""hello"", ""world""); + c.ToString(); + } +} +", parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics(); + } + + [Fact] + public void Wellknown_Debug_Assert4_NotNull() + { + CSharpCompilation c = CreateCompilation(@" +class C +{ + void Main(C? c) + { + System.Diagnostics.Debug.Assert(c != null, ""hello"", ""world"", new object[] { }); + c.ToString(); + } +} +", parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics(); + } + + [Fact] + public void Wellknown_Debug_Assert_IsNull() + { + CSharpCompilation c = CreateCompilation(@" +class C +{ + void Main(C c) + { + System.Diagnostics.Debug.Assert(c == null, ""hello""); + c.ToString(); + } +} +", parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (6,41): hidden CS8606: Result of the comparison is possibly always false. + // System.Diagnostics.Debug.Assert(c == null, "hello"); + Diagnostic(ErrorCode.HDN_NullCheckIsProbablyAlwaysFalse, "c == null").WithLocation(6, 41) + ); + } + + [Fact] + public void Wellknown_Debug_Assert_NoDuplicateDiagnostics() + { + CSharpCompilation c = CreateCompilation(@" +class C +{ + void Main(C? c) + { + System.Diagnostics.Debug.Assert(Method(null), ""hello""); + c.ToString(); + } + bool Method(string x) => throw null; +} +", parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (6,48): warning CS8625: Cannot convert null literal to non-nullable reference or unconstrained type parameter. + // System.Diagnostics.Debug.Assert(Method(null), "hello"); + Diagnostic(ErrorCode.WRN_NullAsNonNullable, "null").WithLocation(6, 48), + // (7,9): warning CS8602: Possible dereference of a null reference. + // c.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c").WithLocation(7, 9) + ); + } + + [Fact] + public void Wellknown_Debug_Assert_InTry() + { + CSharpCompilation c = CreateCompilation(@" +class C +{ + void Main(C? c) + { + try + { + System.Diagnostics.Debug.Assert(c != null, ""hello""); + } + catch { } + + c.ToString(); + } +} +", parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (12,9): warning CS8602: Possible dereference of a null reference. + // c.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c").WithLocation(12, 9) + ); + } + [Fact] public void ConditionalBranching_01() { diff --git a/src/Compilers/Core/Portable/WellKnownMember.cs b/src/Compilers/Core/Portable/WellKnownMember.cs index 1c5f44d104896..003a947844bda 100644 --- a/src/Compilers/Core/Portable/WellKnownMember.cs +++ b/src/Compilers/Core/Portable/WellKnownMember.cs @@ -435,6 +435,11 @@ internal enum WellKnownMember System_Runtime_CompilerServices_IsUnmanagedAttribute__ctor, + System_Diagnostics_Debug_Assert1, + System_Diagnostics_Debug_Assert2, + System_Diagnostics_Debug_Assert3, + System_Diagnostics_Debug_Assert4, + Count } } diff --git a/src/Compilers/Core/Portable/WellKnownMembers.cs b/src/Compilers/Core/Portable/WellKnownMembers.cs index 0626982ba19de..a2dd186e2a505 100644 --- a/src/Compilers/Core/Portable/WellKnownMembers.cs +++ b/src/Compilers/Core/Portable/WellKnownMembers.cs @@ -2991,6 +2991,43 @@ static WellKnownMembers() 0, // Method Signature (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, + // System_Diagnostics_Debug_Assert + (byte)(MemberFlags.Method | MemberFlags.Static), // Flags + (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Diagnostics_Debug - WellKnownType.ExtSentinel), // DeclaringTypeId + 0, // Arity + 1, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Boolean, + + // System_Diagnostics_Debug_Assert + (byte)(MemberFlags.Method | MemberFlags.Static), // Flags + (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Diagnostics_Debug - WellKnownType.ExtSentinel), // DeclaringTypeId + 0, // Arity + 2, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Boolean, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, + + // System_Diagnostics_Debug_Assert + (byte)(MemberFlags.Method | MemberFlags.Static), // Flags + (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Diagnostics_Debug - WellKnownType.ExtSentinel), // DeclaringTypeId + 0, // Arity + 3, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Boolean, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, + + // System_Diagnostics_Debug_Assert + (byte)(MemberFlags.Method | MemberFlags.Static), // Flags + (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Diagnostics_Debug - WellKnownType.ExtSentinel), // DeclaringTypeId + 0, // Arity + 4, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Boolean, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, + (byte)SignatureTypeCode.SZArray, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Object, }; string[] allNames = new string[(int)WellKnownMember.Count] @@ -3364,6 +3401,10 @@ static WellKnownMembers() "get_Item", // System_ReadOnlySpan__get_Item "get_Length", // System_ReadOnlySpan__get_Length ".ctor", // System_Runtime_CompilerServices_IsUnmanagedAttribute__ctor + "Assert", // System_Diagnostics_Debug_Assert1 + "Assert", // System_Diagnostics_Debug_Assert2 + "Assert", // System_Diagnostics_Debug_Assert3 + "Assert", // System_Diagnostics_Debug_Assert4 }; s_descriptors = MemberDescriptor.InitializeFromStream(new System.IO.MemoryStream(initializationBytes, writable: false), allNames); diff --git a/src/Compilers/Core/Portable/WellKnownTypes.cs b/src/Compilers/Core/Portable/WellKnownTypes.cs index 46587f06dc14c..c8b9e58deab35 100644 --- a/src/Compilers/Core/Portable/WellKnownTypes.cs +++ b/src/Compilers/Core/Portable/WellKnownTypes.cs @@ -275,6 +275,8 @@ internal enum WellKnownType System_Runtime_InteropServices_UnmanagedType, System_Runtime_CompilerServices_IsUnmanagedAttribute, + System_Diagnostics_Debug, + NextAvailable, } @@ -543,6 +545,8 @@ internal static class WellKnownTypes "System.ReadOnlySpan`1", "System.Runtime.InteropServices.UnmanagedType", "System.Runtime.CompilerServices.IsUnmanagedAttribute", + + "System.Diagnostics.Debug", }; private readonly static Dictionary s_nameToTypeIdMap = new Dictionary((int)Count); From e0ca0e3724a264a9fb39559dba37a2e8e19bd7cd Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Sat, 5 May 2018 12:06:34 -0700 Subject: [PATCH 02/18] Make IsNullOrEmpty well-known --- .../Portable/FlowAnalysis/NullableWalker.cs | 45 ++++++++ .../Semantic/Semantics/StaticNullChecking.cs | 100 ++++++++++++++++++ .../Core/Portable/WellKnownMember.cs | 1 + .../Core/Portable/WellKnownMembers.cs | 10 ++ src/Compilers/Core/Portable/WellKnownTypes.cs | 2 + 5 files changed, 158 insertions(+) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index a547abe77691d..1b81384fc3ba1 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -1605,6 +1605,13 @@ public override BoundNode VisitCall(BoundCall node) VisitTrueWhenExits(condition: arguments[0]); } + if (MethodReturnsTrueWhenNotNull(method) && arguments.Length > 0) + { + VisitTrueWhenNotNull(arguments[0], method.ReturnType.TypeSymbol); + _result = method.ReturnType; + return null; + } + Debug.Assert(!IsConditionalState); //if (this.State.Reachable) // PROTOTYPE(NullableReferenceTypes): Consider reachability? { @@ -1614,6 +1621,44 @@ public override BoundNode VisitCall(BoundCall node) return null; } + private bool MethodReturnsTrueWhenNotNull(MethodSymbol method) + { + if (method.ReturnType.SpecialType != SpecialType.System_Boolean) + { + return false; + } + + return method.Equals(compilation.GetWellKnownTypeMember(WellKnownMember.System_String_IsNullOrEmpty)); + } + + private void VisitTrueWhenNotNull(BoundExpression operand, TypeSymbol boolType) + { + if (!operand.Type.IsReferenceType) + { + return; + } + + // operand == null || someUnknownBoolean + BoundExpression equivalent = LogicalOr( + new BoundBinaryOperator(operand.Syntax, BinaryOperatorKind.Equal, operand, + new BoundLiteral(operand.Syntax, ConstantValue.Null, operand.Type), + constantValueOpt: null, methodOpt: null, LookupResultKind.Viable, boolType), + new BoundLiteral(operand.Syntax, constantValueOpt: null, boolType)); + + // We only need to update the null-state, so we'll do the flow analysis without diagnostics + bool savedDisableDiagnostics = _disableDiagnostics; + _disableDiagnostics = true; + + Visit(equivalent); + + _disableDiagnostics = savedDisableDiagnostics; + } + + internal static BoundBinaryOperator LogicalOr(BoundExpression left, BoundExpression right) + { + return new BoundBinaryOperator(left.Syntax, BinaryOperatorKind.LogicalBoolOr, left, right, constantValueOpt: null, methodOpt: null, LookupResultKind.Viable, left.Type); + } + private bool MethodEnsuresTrueWhenExits(MethodSymbol method) { return method.Equals(compilation.GetWellKnownTypeMember(WellKnownMember.System_Diagnostics_Debug_Assert1)) || diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs index 639e22e48278e..51535c99bafe5 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs @@ -5615,6 +5615,106 @@ void Main(C? c) ); } + [Fact] + public void Wellknown_String_IsNullOrEmpty() + { + CSharpCompilation c = CreateCompilation(@" +class C +{ + void Main(string? s) + { + if (string.IsNullOrEmpty(s)) + { + s.ToString(); // warn + } + else + { + s.ToString(); // ok + } + } +} +", parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (8,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(8, 13) + ); + } + + [Fact] + public void Wellknown_String_NotIsNullOrEmpty() + { + CSharpCompilation c = CreateCompilation(@" +class C +{ + void Main(string? s) + { + if (!string.IsNullOrEmpty(s)) + { + s.ToString(); // ok + } + else + { + s.ToString(); // warn + } + } +} +", parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (12,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(12, 13) + ); + } + + [Fact] + public void Wellknown_String_NotIsNullOrEmpty_NoDuplicateWarnings() + { + CSharpCompilation c = CreateCompilation(@" +class C +{ + void M() + { + if (!string.IsNullOrEmpty(M2(null))) + { + } + } + string? M2(string s) => throw null; +} +", parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (6,38): warning CS8625: Cannot convert null literal to non-nullable reference or unconstrained type parameter. + // if (!string.IsNullOrEmpty(M2(null))) + Diagnostic(ErrorCode.WRN_NullAsNonNullable, "null").WithLocation(6, 38) + ); + } + + [Fact] + public void Wellknown_String_NotIsNullOrEmpty_NotAString() + { + CSharpCompilation c = CreateCompilation(@" +class C +{ + void M() + { + if (!string.IsNullOrEmpty(M2(null))) + { + } + } + void M2(string s) => throw null; +} +", parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (6,35): error CS1503: Argument 1: cannot convert from 'void' to 'string' + // if (!string.IsNullOrEmpty(M2(null))) + Diagnostic(ErrorCode.ERR_BadArgType, "M2(null)").WithArguments("1", "void", "string").WithLocation(6, 35) + ); + } + [Fact] public void ConditionalBranching_01() { diff --git a/src/Compilers/Core/Portable/WellKnownMember.cs b/src/Compilers/Core/Portable/WellKnownMember.cs index 003a947844bda..95f375f195cc5 100644 --- a/src/Compilers/Core/Portable/WellKnownMember.cs +++ b/src/Compilers/Core/Portable/WellKnownMember.cs @@ -439,6 +439,7 @@ internal enum WellKnownMember System_Diagnostics_Debug_Assert2, System_Diagnostics_Debug_Assert3, System_Diagnostics_Debug_Assert4, + System_String_IsNullOrEmpty, Count } diff --git a/src/Compilers/Core/Portable/WellKnownMembers.cs b/src/Compilers/Core/Portable/WellKnownMembers.cs index a2dd186e2a505..f0c5a524e8d1f 100644 --- a/src/Compilers/Core/Portable/WellKnownMembers.cs +++ b/src/Compilers/Core/Portable/WellKnownMembers.cs @@ -3028,6 +3028,15 @@ static WellKnownMembers() (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, (byte)SignatureTypeCode.SZArray, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Object, + + // System_String_IsNullOrEmpty + (byte)(MemberFlags.Method | MemberFlags.Static), // Flags + (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_String - WellKnownType.ExtSentinel), // DeclaringTypeId + 0, // Arity + 1, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Boolean, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, + }; string[] allNames = new string[(int)WellKnownMember.Count] @@ -3405,6 +3414,7 @@ static WellKnownMembers() "Assert", // System_Diagnostics_Debug_Assert2 "Assert", // System_Diagnostics_Debug_Assert3 "Assert", // System_Diagnostics_Debug_Assert4 + "IsNullOrEmpty", // System_IsNullOrEmpty }; s_descriptors = MemberDescriptor.InitializeFromStream(new System.IO.MemoryStream(initializationBytes, writable: false), allNames); diff --git a/src/Compilers/Core/Portable/WellKnownTypes.cs b/src/Compilers/Core/Portable/WellKnownTypes.cs index c8b9e58deab35..72a959955ade0 100644 --- a/src/Compilers/Core/Portable/WellKnownTypes.cs +++ b/src/Compilers/Core/Portable/WellKnownTypes.cs @@ -276,6 +276,7 @@ internal enum WellKnownType System_Runtime_CompilerServices_IsUnmanagedAttribute, System_Diagnostics_Debug, + System_String, NextAvailable, } @@ -547,6 +548,7 @@ internal static class WellKnownTypes "System.Runtime.CompilerServices.IsUnmanagedAttribute", "System.Diagnostics.Debug", + "System.String", }; private readonly static Dictionary s_nameToTypeIdMap = new Dictionary((int)Count); From 8160ab78c236b20902571467090cdd5a54f5d30d Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Sun, 6 May 2018 00:17:57 -0700 Subject: [PATCH 03/18] Use attributes as annotation --- .../Portable/FlowAnalysis/NullableWalker.cs | 68 +++- .../Metadata/PE/ExternalAnnotations.cs | 61 +++- .../Semantic/Semantics/StaticNullChecking.cs | 316 +++++++++++++++++- .../Attributes/AttributeDescription.cs | 10 + .../Core/Portable/WellKnownMember.cs | 6 - .../Core/Portable/WellKnownMembers.cs | 51 --- src/Compilers/Core/Portable/WellKnownTypes.cs | 6 - 7 files changed, 428 insertions(+), 90 deletions(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 1b81384fc3ba1..7240fb2741291 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -11,6 +11,7 @@ using System.Diagnostics; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; @@ -1600,16 +1601,26 @@ public override BoundNode VisitCall(BoundCall node) ReplayReadsAndWrites(localFunc, node.Syntax, writes: true); } - if (MethodEnsuresTrueWhenExits(method) && arguments.Length > 0) + string key = ExtraAnnotations.MakeMethodKey(method); + ImmutableArray extraAttributes = ExtraAnnotations.GetExtraAttributes(key); + if (MethodEnsuresTrueWhenExits(method, extraAttributes)) { - VisitTrueWhenExits(condition: arguments[0]); + var condition = TryGetFirstArgument(method, arguments); + if (condition != null) + { + VisitTrueWhenExits(condition); + } } - if (MethodReturnsTrueWhenNotNull(method) && arguments.Length > 0) + if (MethodReturnsTrueWhenNotNull(method, extraAttributes)) { - VisitTrueWhenNotNull(arguments[0], method.ReturnType.TypeSymbol); - _result = method.ReturnType; - return null; + var condition = TryGetFirstArgument(method, arguments); + if (condition != null) + { + VisitTrueWhenNotNull(condition, method.ReturnType.TypeSymbol); + _result = method.ReturnType; + return null; + } } Debug.Assert(!IsConditionalState); @@ -1621,14 +1632,24 @@ public override BoundNode VisitCall(BoundCall node) return null; } - private bool MethodReturnsTrueWhenNotNull(MethodSymbol method) + private BoundExpression TryGetFirstArgument(MethodSymbol method, ImmutableArray arguments) + { + if (method.IsExtensionMethod) + { + return (arguments.Length > 1) ? arguments[1] : null; + } + + return (arguments.Length > 0) ? arguments[0] : null; + } + + private bool MethodReturnsTrueWhenNotNull(MethodSymbol method, ImmutableArray extraAttributes) { if (method.ReturnType.SpecialType != SpecialType.System_Boolean) { return false; } - return method.Equals(compilation.GetWellKnownTypeMember(WellKnownMember.System_String_IsNullOrEmpty)); + return MethodHasAttribute(method, extraAttributes, AttributeDescription.TrueWhenNotNullAttribute); } private void VisitTrueWhenNotNull(BoundExpression operand, TypeSymbol boolType) @@ -1659,12 +1680,33 @@ internal static BoundBinaryOperator LogicalOr(BoundExpression left, BoundExpress return new BoundBinaryOperator(left.Syntax, BinaryOperatorKind.LogicalBoolOr, left, right, constantValueOpt: null, methodOpt: null, LookupResultKind.Viable, left.Type); } - private bool MethodEnsuresTrueWhenExits(MethodSymbol method) + private bool MethodEnsuresTrueWhenExits(MethodSymbol method, ImmutableArray extraAttributes) + { + return MethodHasAttribute(method, extraAttributes, AttributeDescription.EnsuresTrueWhenExitsAttribute); + } + + private bool MethodHasAttribute(MethodSymbol method, ImmutableArray extraAttributes, AttributeDescription description) { - return method.Equals(compilation.GetWellKnownTypeMember(WellKnownMember.System_Diagnostics_Debug_Assert1)) || - method.Equals(compilation.GetWellKnownTypeMember(WellKnownMember.System_Diagnostics_Debug_Assert2)) || - method.Equals(compilation.GetWellKnownTypeMember(WellKnownMember.System_Diagnostics_Debug_Assert3)) || - method.Equals(compilation.GetWellKnownTypeMember(WellKnownMember.System_Diagnostics_Debug_Assert4)); + foreach (var attribute in method.GetAttributes()) + { + if (attribute.IsTargetAttribute(method, description)) + { + return true; + } + } + + if (!extraAttributes.IsDefault) + { + foreach (var attribute in extraAttributes) + { + if (attribute.Equals(description)) + { + return true; + } + } + } + + return false; } private void VisitTrueWhenExits(BoundExpression condition) diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/ExternalAnnotations.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/ExternalAnnotations.cs index ef9ee57835704..cda45680f4fc5 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/ExternalAnnotations.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/ExternalAnnotations.cs @@ -31,6 +31,17 @@ internal static class ExtraAnnotations { "System.String System.String.Concat(System.String, System.String)", Parameters(Nullable(false), Nullable(true), Nullable(true)) }, }.ToImmutableDictionary(); + private static readonly ImmutableDictionary> Attributes = + new Dictionary> + { + { "System.Void System.Diagnostics.Debug.Assert(System.Boolean)", Descriptions(AttributeDescription.EnsuresTrueWhenExitsAttribute) }, + { "System.Void System.Diagnostics.Debug.Assert(System.Boolean, System.String)", Descriptions(AttributeDescription.EnsuresTrueWhenExitsAttribute) }, + { "System.Void System.Diagnostics.Debug.Assert(System.Boolean, System.String, System.String)", Descriptions(AttributeDescription.EnsuresTrueWhenExitsAttribute) }, + { "System.Void System.Diagnostics.Debug.Assert(System.Boolean, System.String, System.String, System.Object[])", Descriptions(AttributeDescription.EnsuresTrueWhenExitsAttribute) }, + { "System.Boolean System.String.IsNullOrEmpty(System.String)", Descriptions(AttributeDescription.TrueWhenNotNullAttribute) }, + { "System.Boolean System.String.IsNullOrWhiteSpace(System.String)", Descriptions(AttributeDescription.TrueWhenNotNullAttribute) }, + }.ToImmutableDictionary(); + internal static string MakeMethodKey(PEMethodSymbol method, ParamInfo[] paramInfo) { var pooledBuilder = PooledStringBuilder.GetInstance(); @@ -65,6 +76,41 @@ internal static string MakeMethodKey(PEMethodSymbol method, ParamInfo /// All types in a member which can be annotated should be annotated. Value types and void can be skipped. /// @@ -73,6 +119,9 @@ internal static string MakeMethodKey(PEMethodSymbol method, ParamInfo> Parameters(params ImmutableArray[] values) => values.ToImmutableArray(); + private static ImmutableArray Descriptions(params AttributeDescription[] values) + => values.ToImmutableArray(); + private static ImmutableArray Nullable(params bool[] values) { Debug.Assert(values.Length > 0); @@ -96,6 +145,16 @@ private static void Add(TypeSymbol type, StringBuilder builder) .RemoveMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes) .AddMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier) // displaying tuple syntax causes to load the members of ValueTuple, which can cause a cycle, so we use long-hand format instead - .WithCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.UseValueTuple))); + .WithCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.IncludeNullableReferenceTypeModifier | SymbolDisplayCompilerInternalOptions.UseValueTuple))); + + internal static ImmutableArray GetExtraAttributes(string key) + { + if (!Attributes.TryGetValue(key, out var descriptions)) + { + return default; + } + + return descriptions; + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs index 51535c99bafe5..ec3cb742abc9b 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs @@ -72,6 +72,31 @@ public NullableOptOutAttribute(bool flag = true) { } } "; + private const string EnsuresTrueWhenExitsAttributeDefinition = @" +namespace System.Runtime.CompilerServices +{ + [AttributeUsage(AttributeTargets.Method, + AllowMultiple = false)] + public class EnsuresTrueWhenExitsAttribute : Attribute + { + public EnsuresTrueWhenExitsAttribute () { } + } +} +"; + + private const string TrueWhenNotNullAttributeDefinition = @" +namespace System.Runtime.CompilerServices +{ + [AttributeUsage(AttributeTargets.Method, + AllowMultiple = false)] + public class TrueWhenNotNullAttribute : Attribute + { + public TrueWhenNotNullAttribute() { } + } +} +"; + + [Fact] public void Test0() { @@ -5438,7 +5463,80 @@ class CL1 } [Fact] - public void Wellknown_Debug_Assert1_NotNull() + public void EnsuresTrueWhenExits_NotNull() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +class C +{ + void Main(C? c) + { + MyAssert(c != null); + c.ToString(); + } + + [EnsuresTrueWhenExits] + void MyAssert(bool condition) => throw null; +} +" + EnsuresTrueWhenExitsAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics(); + } + + [Fact] + public void EnsuresTrueWhenExits_Null() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +class C +{ + void Main(C? c) + { + MyAssert(c == null); + c.ToString(); + } + + [EnsuresTrueWhenExits] + void MyAssert(bool condition) => throw null; +} +" + EnsuresTrueWhenExitsAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (8,9): warning CS8602: Possible dereference of a null reference. + // c.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c").WithLocation(8, 9) + ); + } + + [Fact] + public void EnsuresTrueWhenExits_MethodWithReturnType() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +class C +{ + void Main(C? c) + { + if (MyAssert(c != null)) + { + c.ToString(); + } + else + { + c.ToString(); + } + } + + [EnsuresTrueWhenExits] + bool MyAssert(bool condition) => throw null; +} +" + EnsuresTrueWhenExitsAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics(); + } + + [Fact] + public void EnsuresTrueWhenExits_Debug_Assert1_NotNull() { CSharpCompilation c = CreateCompilation(@" class C @@ -5455,7 +5553,7 @@ void Main(C? c) } [Fact] - public void Wellknown_Debug_Assert1_NotNullAndNotEmpty() + public void EnsuresTrueWhenExits_Debug_Assert1_NotNullAndNotEmpty() { CSharpCompilation c = CreateCompilation(@" class C @@ -5472,7 +5570,7 @@ void Main(string? c) } [Fact] - public void Wellknown_Debug_Assert1_NotNullOrUnknown() + public void EnsuresTrueWhenExits_Debug_Assert1_NotNullOrUnknown() { CSharpCompilation c = CreateCompilation(@" class C @@ -5493,7 +5591,7 @@ void Main(string? c, bool b) } [Fact] - public void Wellknown_Debug_Assert2_NotNull() + public void EnsuresTrueWhenExits_Debug_Assert2_NotNull() { CSharpCompilation c = CreateCompilation(@" class C @@ -5510,7 +5608,7 @@ void Main(C? c) } [Fact] - public void Wellknown_Debug_Assert3_NotNull() + public void EnsuresTrueWhenExits_Debug_Assert3_NotNull() { CSharpCompilation c = CreateCompilation(@" class C @@ -5527,7 +5625,7 @@ void Main(C? c) } [Fact] - public void Wellknown_Debug_Assert4_NotNull() + public void EnsuresTrueWhenExits_Debug_Assert4_NotNull() { CSharpCompilation c = CreateCompilation(@" class C @@ -5544,7 +5642,7 @@ void Main(C? c) } [Fact] - public void Wellknown_Debug_Assert_IsNull() + public void EnsuresTrueWhenExits_Debug_Assert_IsNull() { CSharpCompilation c = CreateCompilation(@" class C @@ -5565,7 +5663,7 @@ void Main(C c) } [Fact] - public void Wellknown_Debug_Assert_NoDuplicateDiagnostics() + public void EnsuresTrueWhenExits_Debug_Assert_NoDuplicateDiagnostics() { CSharpCompilation c = CreateCompilation(@" class C @@ -5590,7 +5688,7 @@ void Main(C? c) } [Fact] - public void Wellknown_Debug_Assert_InTry() + public void EnsuresTrueWhenExits_Debug_Assert_InTry() { CSharpCompilation c = CreateCompilation(@" class C @@ -5616,7 +5714,172 @@ void Main(C? c) } [Fact] - public void Wellknown_String_IsNullOrEmpty() + public void TrueWhenNotNull() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +class C +{ + void Main(string? s) + { + if (MyIsNullOrEmpty(s)) + { + s.ToString(); // warn + } + else + { + s.ToString(); // ok + } + } + [TrueWhenNotNull] + static bool MyIsNullOrEmpty(string? s) => throw null; +} +" + TrueWhenNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (9,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(9, 13) + ); + } + + [Fact] + public void TrueWhenNotNull_BadAttribute() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +class C +{ + void Main(string? s) + { + if (MyIsNullOrEmpty(s)) + { + s.ToString(); // warn 1 + } + else + { + s.ToString(); // warn 2 + } + } + [TrueWhenNotNull(true)] + static bool MyIsNullOrEmpty(string? s) => throw null; +} +namespace System.Runtime.CompilerServices +{ + [AttributeUsage(AttributeTargets.Method, + AllowMultiple = false)] + public class TrueWhenNotNullAttribute : Attribute + { + public TrueWhenNotNullAttribute(bool bad) { } + } +} +", parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (9,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(9, 13), + // (13,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(13, 13) + ); + } + + [Fact] + public void TrueWhenNotNull_InvertIf() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +class C +{ + void Main(string? s) + { + if (!MyIsNullOrEmpty(s)) + { + s.ToString(); // ok + } + else + { + s.ToString(); // warn + } + } + [TrueWhenNotNull] + static bool MyIsNullOrEmpty(string? s) => throw null; +} +" + TrueWhenNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (13,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(13, 13) + ); + } + + [Fact] + public void TrueWhenNotNull_InstanceMethod() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +class C +{ + void Main(string? s) + { + if (this.MyIsNullOrEmpty(s)) + { + s.ToString(); // warn + } + else + { + s.ToString(); // ok + } + } + [TrueWhenNotNull] + bool MyIsNullOrEmpty(string? s) => throw null; +} +" + TrueWhenNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (9,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(9, 13) + ); + } + + [Fact] + public void TrueWhenNotNull_ExtensionMethod() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +public class C +{ + void Main(string? s) + { + if (this.MyIsNullOrEmpty(s)) + { + s.ToString(); // warn + } + else + { + s.ToString(); // ok + } + } +} +public static class Extension +{ + [TrueWhenNotNull] + public static bool MyIsNullOrEmpty(this C c, string? s) => throw null; +} +" + TrueWhenNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (9,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(9, 13) + ); + } + + [Fact] + public void TrueWhenNotNull_String_IsNullOrEmpty() { CSharpCompilation c = CreateCompilation(@" class C @@ -5643,7 +5906,7 @@ void Main(string? s) } [Fact] - public void Wellknown_String_NotIsNullOrEmpty() + public void TrueWhenNotNull_String_NotIsNullOrEmpty() { CSharpCompilation c = CreateCompilation(@" class C @@ -5670,7 +5933,7 @@ void Main(string? s) } [Fact] - public void Wellknown_String_NotIsNullOrEmpty_NoDuplicateWarnings() + public void TrueWhenNotNull_String_NotIsNullOrEmpty_NoDuplicateWarnings() { CSharpCompilation c = CreateCompilation(@" class C @@ -5693,7 +5956,7 @@ void M() } [Fact] - public void Wellknown_String_NotIsNullOrEmpty_NotAString() + public void TrueWhenNotNull_String_NotIsNullOrEmpty_NotAString() { CSharpCompilation c = CreateCompilation(@" class C @@ -5715,6 +5978,33 @@ void M() ); } + [Fact] + public void TrueWhenNotNull_String_IsNullOrWhiteSpace() + { + CSharpCompilation c = CreateCompilation(@" +class C +{ + void Main(string? s) + { + if (string.IsNullOrWhiteSpace(s)) + { + s.ToString(); // warn + } + else + { + s.ToString(); // ok + } + } +} +", parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (8,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(8, 13) + ); + } + [Fact] public void ConditionalBranching_01() { diff --git a/src/Compilers/Core/Portable/Symbols/Attributes/AttributeDescription.cs b/src/Compilers/Core/Portable/Symbols/Attributes/AttributeDescription.cs index fe3fd861adcc8..367fd0c91f8cc 100644 --- a/src/Compilers/Core/Portable/Symbols/Attributes/AttributeDescription.cs +++ b/src/Compilers/Core/Portable/Symbols/Attributes/AttributeDescription.cs @@ -28,6 +28,12 @@ public AttributeDescription(string @namespace, string name, byte[][] signatures, this.MatchIgnoringCase = matchIgnoringCase; } + public bool Equals(AttributeDescription other) + => this.Namespace == other.Namespace && + this.Name == other.Name && + ReferenceEquals(this.Signatures, other.Signatures) && + this.MatchIgnoringCase == other.MatchIgnoringCase; + public string FullName { get { return Namespace + "." + Name; } @@ -258,6 +264,8 @@ static AttributeDescription() private static readonly byte[][] s_signaturesOfOutAttribute = { s_signature_HasThis_Void }; private static readonly byte[][] s_signaturesOfIsReadOnlyAttribute = { s_signature_HasThis_Void }; private static readonly byte[][] s_signaturesOfIsUnmanagedAttribute = { s_signature_HasThis_Void }; + private static readonly byte[][] s_signaturesOfTrueWhenNotNullAttribute = { s_signature_HasThis_Void }; + private static readonly byte[][] s_signaturesOfEnsuresTrueWhenExitsAttribute = { s_signature_HasThis_Void }; private static readonly byte[][] s_signaturesOfFixedBufferAttribute = { s_signature_HasThis_Void_Type_Int32 }; private static readonly byte[][] s_signaturesOfSuppressUnmanagedCodeSecurityAttribute = { s_signature_HasThis_Void }; private static readonly byte[][] s_signaturesOfPrincipalPermissionAttribute = { s_signature_HasThis_Void_SecurityAction }; @@ -454,6 +462,8 @@ static AttributeDescription() internal static readonly AttributeDescription StructLayoutAttribute = new AttributeDescription("System.Runtime.InteropServices", "StructLayoutAttribute", s_signaturesOfStructLayoutAttribute); internal static readonly AttributeDescription FieldOffsetAttribute = new AttributeDescription("System.Runtime.InteropServices", "FieldOffsetAttribute", s_signaturesOfFieldOffsetAttribute); internal static readonly AttributeDescription FixedBufferAttribute = new AttributeDescription("System.Runtime.CompilerServices", "FixedBufferAttribute", s_signaturesOfFixedBufferAttribute); + internal static readonly AttributeDescription TrueWhenNotNullAttribute = new AttributeDescription("System.Runtime.CompilerServices", "TrueWhenNotNullAttribute", s_signaturesOfTrueWhenNotNullAttribute); + internal static readonly AttributeDescription EnsuresTrueWhenExitsAttribute = new AttributeDescription("System.Runtime.CompilerServices", "EnsuresTrueWhenExitsAttribute", s_signaturesOfEnsuresTrueWhenExitsAttribute); internal static readonly AttributeDescription MarshalAsAttribute = new AttributeDescription("System.Runtime.InteropServices", "MarshalAsAttribute", s_signaturesOfMarshalAsAttribute); internal static readonly AttributeDescription InAttribute = new AttributeDescription("System.Runtime.InteropServices", "InAttribute", s_signaturesOfInAttribute); internal static readonly AttributeDescription OutAttribute = new AttributeDescription("System.Runtime.InteropServices", "OutAttribute", s_signaturesOfOutAttribute); diff --git a/src/Compilers/Core/Portable/WellKnownMember.cs b/src/Compilers/Core/Portable/WellKnownMember.cs index 95f375f195cc5..1c5f44d104896 100644 --- a/src/Compilers/Core/Portable/WellKnownMember.cs +++ b/src/Compilers/Core/Portable/WellKnownMember.cs @@ -435,12 +435,6 @@ internal enum WellKnownMember System_Runtime_CompilerServices_IsUnmanagedAttribute__ctor, - System_Diagnostics_Debug_Assert1, - System_Diagnostics_Debug_Assert2, - System_Diagnostics_Debug_Assert3, - System_Diagnostics_Debug_Assert4, - System_String_IsNullOrEmpty, - Count } } diff --git a/src/Compilers/Core/Portable/WellKnownMembers.cs b/src/Compilers/Core/Portable/WellKnownMembers.cs index f0c5a524e8d1f..0626982ba19de 100644 --- a/src/Compilers/Core/Portable/WellKnownMembers.cs +++ b/src/Compilers/Core/Portable/WellKnownMembers.cs @@ -2991,52 +2991,6 @@ static WellKnownMembers() 0, // Method Signature (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, - // System_Diagnostics_Debug_Assert - (byte)(MemberFlags.Method | MemberFlags.Static), // Flags - (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Diagnostics_Debug - WellKnownType.ExtSentinel), // DeclaringTypeId - 0, // Arity - 1, // Method Signature - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Boolean, - - // System_Diagnostics_Debug_Assert - (byte)(MemberFlags.Method | MemberFlags.Static), // Flags - (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Diagnostics_Debug - WellKnownType.ExtSentinel), // DeclaringTypeId - 0, // Arity - 2, // Method Signature - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Boolean, - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, - - // System_Diagnostics_Debug_Assert - (byte)(MemberFlags.Method | MemberFlags.Static), // Flags - (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Diagnostics_Debug - WellKnownType.ExtSentinel), // DeclaringTypeId - 0, // Arity - 3, // Method Signature - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Boolean, - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, - - // System_Diagnostics_Debug_Assert - (byte)(MemberFlags.Method | MemberFlags.Static), // Flags - (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Diagnostics_Debug - WellKnownType.ExtSentinel), // DeclaringTypeId - 0, // Arity - 4, // Method Signature - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Boolean, - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, - (byte)SignatureTypeCode.SZArray, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Object, - - // System_String_IsNullOrEmpty - (byte)(MemberFlags.Method | MemberFlags.Static), // Flags - (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_String - WellKnownType.ExtSentinel), // DeclaringTypeId - 0, // Arity - 1, // Method Signature - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Boolean, - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, - }; string[] allNames = new string[(int)WellKnownMember.Count] @@ -3410,11 +3364,6 @@ static WellKnownMembers() "get_Item", // System_ReadOnlySpan__get_Item "get_Length", // System_ReadOnlySpan__get_Length ".ctor", // System_Runtime_CompilerServices_IsUnmanagedAttribute__ctor - "Assert", // System_Diagnostics_Debug_Assert1 - "Assert", // System_Diagnostics_Debug_Assert2 - "Assert", // System_Diagnostics_Debug_Assert3 - "Assert", // System_Diagnostics_Debug_Assert4 - "IsNullOrEmpty", // System_IsNullOrEmpty }; s_descriptors = MemberDescriptor.InitializeFromStream(new System.IO.MemoryStream(initializationBytes, writable: false), allNames); diff --git a/src/Compilers/Core/Portable/WellKnownTypes.cs b/src/Compilers/Core/Portable/WellKnownTypes.cs index 72a959955ade0..46587f06dc14c 100644 --- a/src/Compilers/Core/Portable/WellKnownTypes.cs +++ b/src/Compilers/Core/Portable/WellKnownTypes.cs @@ -275,9 +275,6 @@ internal enum WellKnownType System_Runtime_InteropServices_UnmanagedType, System_Runtime_CompilerServices_IsUnmanagedAttribute, - System_Diagnostics_Debug, - System_String, - NextAvailable, } @@ -546,9 +543,6 @@ internal static class WellKnownTypes "System.ReadOnlySpan`1", "System.Runtime.InteropServices.UnmanagedType", "System.Runtime.CompilerServices.IsUnmanagedAttribute", - - "System.Diagnostics.Debug", - "System.String", }; private readonly static Dictionary s_nameToTypeIdMap = new Dictionary((int)Count); From 9823b66d1a85d71e525cab50ea07ca31bc55dc7e Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Sun, 6 May 2018 20:44:32 -0700 Subject: [PATCH 04/18] Fix test regressions --- .../Symbols/Metadata/PE/ExternalAnnotations.cs | 18 +++++++++++++++++- .../Semantic/Semantics/StaticNullChecking.cs | 17 +++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/ExternalAnnotations.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/ExternalAnnotations.cs index cda45680f4fc5..7924cbd0f3909 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/ExternalAnnotations.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/ExternalAnnotations.cs @@ -78,13 +78,19 @@ internal static string MakeMethodKey(PEMethodSymbol method, ParamInfo Nullable(params bool[] values) internal static ImmutableArray> GetExtraAnnotations(string key) { + if (key is null) + { + return default; + } + if (!Annotations.TryGetValue(key, out var flags)) { return default; @@ -149,6 +160,11 @@ private static void Add(TypeSymbol type, StringBuilder builder) internal static ImmutableArray GetExtraAttributes(string key) { + if (key is null) + { + return default; + } + if (!Attributes.TryGetValue(key, out var descriptions)) { return default; diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs index ec3cb742abc9b..6f80b2e17b366 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs @@ -5483,6 +5483,23 @@ void Main(C? c) c.VerifyDiagnostics(); } + [Fact] + public void MakeMethodKeyForWhereMethod() + { + CSharpCompilation c = CreateCompilation(@" +class Test +{ + public void SimpleWhere() + { + int[] numbers = { 1, 2, 3 }; + var lowNums = from n in numbers + where n < 5 + select n; + } +}", parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics(); + } [Fact] public void EnsuresTrueWhenExits_Null() { From 967dafa2c03eddd8823da9ff9496ace00a5222a2 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Mon, 7 May 2018 11:24:56 -0700 Subject: [PATCH 05/18] Implement NotNullWhenFalse and EnsuresNotNull --- .../Portable/FlowAnalysis/NullableWalker.cs | 310 +++++--- .../Lowering/SyntheticBoundNodeFactory.cs | 8 +- .../{Metadata/PE => }/ExternalAnnotations.cs | 57 +- .../Symbols/Metadata/PE/PEParameterSymbol.cs | 49 +- .../Portable/Symbols/ParameterSymbol.cs | 50 ++ .../Symbols/SignatureOnlyParameterSymbol.cs | 4 + .../Source/SourceClonedParameterSymbol.cs | 10 + .../Source/SourceComplexParameterSymbol.cs | 16 + .../Source/SourceSimpleParameterSymbol.cs | 10 + .../Symbols/Source/ThisParameterSymbol.cs | 10 + .../Synthesized/SynthesizedParameterSymbol.cs | 10 + .../Symbols/Wrapped/WrappedParameterSymbol.cs | 12 +- .../Semantic/Semantics/StaticNullChecking.cs | 726 +++++++++++++----- .../Attributes/AttributeDescription.cs | 14 +- ...monParameterEarlyWellKnownAttributeData.cs | 32 + 15 files changed, 936 insertions(+), 382 deletions(-) rename src/Compilers/CSharp/Portable/Symbols/{Metadata/PE => }/ExternalAnnotations.cs (71%) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 7240fb2741291..7ebbaefd2d28f 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -11,7 +11,6 @@ using System.Diagnostics; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; -using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; @@ -60,6 +59,10 @@ internal sealed partial class NullableWalker : DataFlowPassBase private PooledDictionary _placeholderLocals; + /// + /// For methods with annotations, we'll need to visit the arguments twice. + /// Once for diagnostics and once for result state (but disabling diagnostics). + /// private bool _disableDiagnostics = false; protected override void Free() @@ -1580,17 +1583,18 @@ public override BoundNode VisitCall(BoundCall node) // PROTOTYPE(NullableReferenceTypes): Can we handle some error cases? // (Compare with CSharpOperationFactory.CreateBoundCallOperation.) - ImmutableArray arguments = node.Arguments; if (!node.HasErrors) { ImmutableArray refKindsOpt = node.ArgumentRefKindsOpt; - ImmutableArray strippedArguments = RemoveArgumentConversions(arguments, refKindsOpt); - ImmutableArray results = VisitArgumentsEvaluate(strippedArguments, refKindsOpt, node.Expanded); + ImmutableArray arguments = RemoveArgumentConversions(node.Arguments, refKindsOpt); + ImmutableArray argsToParamsOpt = node.ArgsToParamsOpt; + ImmutableArray results = VisitArgumentsEvaluate(arguments, refKindsOpt, method.Parameters, argsToParamsOpt, node.Expanded); + if (method.IsGenericMethod && HasImplicitTypeArguments(node)) { method = InferMethod(node, method, results.SelectAsArray(r => r.Type)); } - VisitArgumentsWarn(strippedArguments, refKindsOpt, method.Parameters, node.ArgsToParamsOpt, node.Expanded, results); + VisitArgumentsWarn(arguments, refKindsOpt, method.Parameters, argsToParamsOpt, node.Expanded, results); } UpdateStateForCall(node); @@ -1601,29 +1605,6 @@ public override BoundNode VisitCall(BoundCall node) ReplayReadsAndWrites(localFunc, node.Syntax, writes: true); } - string key = ExtraAnnotations.MakeMethodKey(method); - ImmutableArray extraAttributes = ExtraAnnotations.GetExtraAttributes(key); - if (MethodEnsuresTrueWhenExits(method, extraAttributes)) - { - var condition = TryGetFirstArgument(method, arguments); - if (condition != null) - { - VisitTrueWhenExits(condition); - } - } - - if (MethodReturnsTrueWhenNotNull(method, extraAttributes)) - { - var condition = TryGetFirstArgument(method, arguments); - if (condition != null) - { - VisitTrueWhenNotNull(condition, method.ReturnType.TypeSymbol); - _result = method.ReturnType; - return null; - } - } - - Debug.Assert(!IsConditionalState); //if (this.State.Reachable) // PROTOTYPE(NullableReferenceTypes): Consider reachability? { _result = method.ReturnType; @@ -1632,95 +1613,35 @@ public override BoundNode VisitCall(BoundCall node) return null; } - private BoundExpression TryGetFirstArgument(MethodSymbol method, ImmutableArray arguments) - { - if (method.IsExtensionMethod) - { - return (arguments.Length > 1) ? arguments[1] : null; - } - - return (arguments.Length > 0) ? arguments[0] : null; - } - - private bool MethodReturnsTrueWhenNotNull(MethodSymbol method, ImmutableArray extraAttributes) + /// + /// For each argument, figure out if its corresponding parameter is annotated with NotNullWhenFalse or + /// EnsuresNotNull. + /// + private ImmutableArray<(bool notNullWhenFalse, bool ensuresNotNull)> GetAnnotations(int numArguments, + bool expanded, ImmutableArray parameters, ImmutableArray argsToParamsOpt) { - if (method.ReturnType.SpecialType != SpecialType.System_Boolean) - { - return false; - } - - return MethodHasAttribute(method, extraAttributes, AttributeDescription.TrueWhenNotNullAttribute); - } + ArrayBuilder<(bool notNullWhenFalse, bool ensuresNotNull)> builder = null; - private void VisitTrueWhenNotNull(BoundExpression operand, TypeSymbol boolType) - { - if (!operand.Type.IsReferenceType) + for (int i = 0; i < numArguments; i++) { - return; - } - - // operand == null || someUnknownBoolean - BoundExpression equivalent = LogicalOr( - new BoundBinaryOperator(operand.Syntax, BinaryOperatorKind.Equal, operand, - new BoundLiteral(operand.Syntax, ConstantValue.Null, operand.Type), - constantValueOpt: null, methodOpt: null, LookupResultKind.Viable, boolType), - new BoundLiteral(operand.Syntax, constantValueOpt: null, boolType)); - - // We only need to update the null-state, so we'll do the flow analysis without diagnostics - bool savedDisableDiagnostics = _disableDiagnostics; - _disableDiagnostics = true; - - Visit(equivalent); - - _disableDiagnostics = savedDisableDiagnostics; - } + (ParameterSymbol parameter, _) = GetCorrespondingParameter(i, parameters, argsToParamsOpt, expanded); - internal static BoundBinaryOperator LogicalOr(BoundExpression left, BoundExpression right) - { - return new BoundBinaryOperator(left.Syntax, BinaryOperatorKind.LogicalBoolOr, left, right, constantValueOpt: null, methodOpt: null, LookupResultKind.Viable, left.Type); - } - - private bool MethodEnsuresTrueWhenExits(MethodSymbol method, ImmutableArray extraAttributes) - { - return MethodHasAttribute(method, extraAttributes, AttributeDescription.EnsuresTrueWhenExitsAttribute); - } + bool notNullWhenFalse = parameter?.NotNullWhenFalse == true; + bool ensuresNotNull = parameter?.EnsuresNotNull == true; - private bool MethodHasAttribute(MethodSymbol method, ImmutableArray extraAttributes, AttributeDescription description) - { - foreach (var attribute in method.GetAttributes()) - { - if (attribute.IsTargetAttribute(method, description)) + if ((notNullWhenFalse || ensuresNotNull) && builder == null) { - return true; + builder = ArrayBuilder<(bool, bool)>.GetInstance(numArguments); + builder.AddMany((false, false), i); } - } - if (!extraAttributes.IsDefault) - { - foreach (var attribute in extraAttributes) + if (builder != null) { - if (attribute.Equals(description)) - { - return true; - } + builder.Add((notNullWhenFalse, ensuresNotNull)); } } - return false; - } - - private void VisitTrueWhenExits(BoundExpression condition) - { - // We only need to update the null-state, so we'll do the flow analysis without diagnostics - - bool savedDisableDiagnostics = _disableDiagnostics; - _disableDiagnostics = true; - - // if (!condition) throw; - VisitIfStatement(new BoundIfStatement(condition.Syntax, SyntheticBoundNodeFactory.NotCore(condition), - new BoundThrowStatement(condition.Syntax, expressionOpt: null), alternativeOpt: null)); - - _disableDiagnostics = savedDisableDiagnostics; + return builder == null ? default : builder.ToImmutableAndFree(); } // PROTOTYPE(NullableReferenceTypes): Record in the node whether type @@ -1781,7 +1702,7 @@ private void VisitArguments( bool expanded) { Debug.Assert(!arguments.IsDefault); - ImmutableArray results = VisitArgumentsEvaluate(arguments, refKindsOpt, expanded); + ImmutableArray results = VisitArgumentsEvaluate(arguments, refKindsOpt, parameters, argsToParamsOpt, expanded); // PROTOTYPE(NullableReferenceTypes): Can we handle some error cases? // (Compare with CSharpOperationFactory.CreateBoundCallOperation.) if (!node.HasErrors && !parameters.IsDefault) @@ -1790,6 +1711,25 @@ private void VisitArguments( } } + private ImmutableArray VisitArgumentsEvaluate( + ImmutableArray arguments, + ImmutableArray refKindsOpt, + ImmutableArray parameters, + ImmutableArray argsToParamsOpt, + bool expanded) + { + var savedState = this.State.Clone(); + // We do a first pass to work through the arguments without making any assumptions + var results = VisitArgumentsEvaluate(arguments, refKindsOpt, expanded); + + // We do a second pass through the arguments, ignoring any diagnostics produced, but honoring the annotations, + // to get the proper result state. + this.SetState(savedState); + VisitArgumentsEvaluateHonoringAnnotations(arguments, refKindsOpt, parameters, argsToParamsOpt, expanded); + + return results; + } + private ImmutableArray VisitArgumentsEvaluate( ImmutableArray arguments, ImmutableArray refKindsOpt, @@ -1804,22 +1744,134 @@ private ImmutableArray VisitArgumentsEvaluate( var builder = ArrayBuilder.GetInstance(n); for (int i = 0; i < n; i++) { - RefKind refKind = GetRefKind(refKindsOpt, i); + VisitArgumentEvaluate(arguments, refKindsOpt, i); + builder.Add(_result); + } + + _result = _invalidType; + return builder.ToImmutableAndFree(); + } + + private void VisitArgumentEvaluate(ImmutableArray arguments, ImmutableArray refKindsOpt, int i) + { + RefKind refKind = GetRefKind(refKindsOpt, i); + var argument = arguments[i]; + if (refKind != RefKind.Out) + { + // PROTOTYPE(NullReferenceTypes): `ref` arguments should be treated as l-values + // for assignment. See `ref x3` in StaticNullChecking.PassingParameters_01. + VisitRvalue(argument); + } + else + { + VisitLvalue(argument); + } + } + + /// + /// Visit all the arguments for the purpose of computing the exit state of the method, + /// given the annotations, but disabling warnings. + /// + private void VisitArgumentsEvaluateHonoringAnnotations( + ImmutableArray arguments, + ImmutableArray refKindsOpt, + ImmutableArray parameters, + ImmutableArray argsToParamsOpt, + bool expanded) + { + Debug.Assert(!IsConditionalState); + + ImmutableArray<(bool notNullWhenFalse, bool ensuresNotNull)> annotations = GetAnnotations(arguments.Length, + expanded, parameters, argsToParamsOpt); + + if (annotations.IsDefault) + { + return; + } + + bool saveDisableDiagnostics = _disableDiagnostics; + _disableDiagnostics = true; + + Debug.Assert(annotations.Length == arguments.Length); + + // Since we may be splitting states to represent the method returning true/false, + // we'll prepare all the slots ahead of time. + var slots = ArrayBuilder.GetInstance(arguments.Length); + for (int i = 0; i < arguments.Length; i++) + { var argument = arguments[i]; - if (refKind != RefKind.Out) + int slot = MakeSlot(argument); + if (slot <= 0) { - // PROTOTYPE(NullReferenceTypes): `ref` arguments should be treated as l-values - // for assignment. See `ref x3` in StaticNullChecking.PassingParameters_01. - VisitRvalue(argument); + slots.Add(null); + continue; + } + if (slot >= this.State.Capacity) Normalize(ref this.State); + slots.Add(slot); + } + + for (int i = 0; i < arguments.Length; i++) + { + if (this.IsConditionalState) + { + // We could be in a conditional state because of NotNullWhenFalse annotation + // Then WhenTrue/False states correspond to the invocation returning true/false + + // We'll successively assume that we're in the unconditional state where the method returns true, + // then we'll repeat assuming the method returns false. + + LocalState whenTrue = this.StateWhenTrue.Clone(); + LocalState whenFalse = this.StateWhenFalse.Clone(); + + this.SetState(whenTrue); + VisitArgumentEvaluate(arguments, refKindsOpt, i); + Debug.Assert(!IsConditionalState); + + this.SetState(whenFalse); + VisitArgumentEvaluate(arguments, refKindsOpt, i); + Debug.Assert(!IsConditionalState); + + this.SetConditionalState(whenTrue, whenFalse); } else { - VisitLvalue(argument); + VisitArgumentEvaluate(arguments, refKindsOpt, i); + } + + var argument = arguments[i]; + if (argument.Type?.IsReferenceType != true || slots[i] == null) + { + continue; + } + + int slot = slots[i].Value; + (bool notNullWhenFalse, bool ensuresNotNull) = annotations[i]; + + if (ensuresNotNull) + { + // The variable in this slot is not null + if (this.IsConditionalState) + { + this.StateWhenTrue[slot] = true; + this.StateWhenFalse[slot] = true; + } + else + { + this.State[slot] = true; + } + } + else if (notNullWhenFalse) + { + // We'll use the WhenTrue/False states to represent whether the invocation returns true/false + Split(); + + // The variable in this slot is not null when the method returns false + this.StateWhenFalse[slot] = true; } - builder.Add(_result); } - _result = _invalidType; - return builder.ToImmutableAndFree(); + + slots.Free(); + _disableDiagnostics = saveDisableDiagnostics; } private void VisitArgumentsWarn( @@ -1933,7 +1985,10 @@ private static ImmutableArray RemoveArgumentConversions(Immutab private static (ParameterSymbol, TypeSymbolWithAnnotations) GetCorrespondingParameter(int argumentOrdinal, ImmutableArray parameters, ImmutableArray argsToParamsOpt, bool expanded) { - Debug.Assert(!parameters.IsDefault); + if (parameters.IsDefault) + { + return (null, null); + } int n = parameters.Length; ParameterSymbol parameter; @@ -3188,7 +3243,7 @@ public override BoundNode VisitArgList(BoundArgList node) public override BoundNode VisitArgListOperator(BoundArgListOperator node) { - VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt, expanded: false); + VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt, parameters: default, argsToParamsOpt: default, expanded: false); Debug.Assert((object)node.Type == null); SetResult(node); return null; @@ -3268,7 +3323,7 @@ public override BoundNode VisitDynamicMemberAccess(BoundDynamicMemberAccess node public override BoundNode VisitDynamicInvocation(BoundDynamicInvocation node) { VisitRvalue(node.Expression); - VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt, expanded: false); + VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt, parameters: default, argsToParamsOpt: default, expanded: false); Debug.Assert(node.Type.IsDynamic()); Debug.Assert(node.Type.IsReferenceType); @@ -3296,7 +3351,7 @@ public override BoundNode VisitEventAssignmentOperator(BoundEventAssignmentOpera public override BoundNode VisitDynamicObjectCreationExpression(BoundDynamicObjectCreationExpression node) { Debug.Assert(!IsConditionalState); - VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt, expanded: false); + VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt, parameters: default, argsToParamsOpt: default, expanded: false); VisitObjectOrDynamicObjectCreation(node, node.InitializerExpressionOpt); return null; } @@ -3375,7 +3430,7 @@ public override BoundNode VisitDynamicIndexerAccess(BoundDynamicIndexerAccess no var receiver = node.ReceiverOpt; VisitRvalue(receiver); CheckPossibleNullReceiver(receiver); - VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt, expanded: false); + VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt, parameters: default, argsToParamsOpt: default, expanded: false); Debug.Assert(node.Type.IsDynamic()); @@ -3693,6 +3748,21 @@ public bool Reachable return _knownNullState.Capacity > 0; } } + +#if DEBUG + public override string ToString() + { + var pooledBuilder = PooledStringBuilder.GetInstance(); + var builder = pooledBuilder.Builder; + builder.Append(" "); + for (int i = this.Capacity - 1; i >= 0; i--) + { + builder.Append(_knownNullState[i] ? (_notNull[i] ? "! " : "? ") : ". "); + } + + return pooledBuilder.ToStringAndFree(); + } +#endif } } } diff --git a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs index adf67869fdd27..8d6d0476b72d4 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs @@ -1252,12 +1252,8 @@ internal static BoundExpression NullOrDefault(TypeSymbol typeSymbol, SyntaxNode return typeSymbol.IsReferenceType ? Null(typeSymbol, syntax) : Default(typeSymbol, syntax); } - internal BoundExpression Not(BoundExpression expression) - { - return NotCore(expression); - } - - internal static BoundUnaryOperator NotCore(BoundExpression expression) + internal BoundExpression Not( + BoundExpression expression) { return new BoundUnaryOperator(expression.Syntax, UnaryOperatorKind.BoolLogicalNegation, expression, null, null, LookupResultKind.Viable, expression.Type); } diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/ExternalAnnotations.cs b/src/Compilers/CSharp/Portable/Symbols/ExternalAnnotations.cs similarity index 71% rename from src/Compilers/CSharp/Portable/Symbols/Metadata/PE/ExternalAnnotations.cs rename to src/Compilers/CSharp/Portable/Symbols/ExternalAnnotations.cs index 7924cbd0f3909..5b1d7a3ca62f0 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/ExternalAnnotations.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ExternalAnnotations.cs @@ -4,42 +4,45 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Text; +using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; using Microsoft.CodeAnalysis.PooledObjects; -namespace Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE +namespace Microsoft.CodeAnalysis.CSharp.Symbols { // PROTOTYPE(NullableReferenceTypes): external annotations should be removed or fully designed/productized // If we choose to stick with an ad-hoc key (rather than annotations as source or as PE/ref assembly), // we should consider the assembly qualified name format used in metadata (with backticks and such). internal static class ExtraAnnotations { + private static readonly ImmutableArray EnsuresNotNull = ImmutableArray.Create(AttributeDescription.EnsuresNotNullAttribute); + + private static readonly ImmutableArray NotNullWhenFalse = ImmutableArray.Create(AttributeDescription.NotNullWhenFalseAttribute); + // APIs that are useful to annotate: // 1) don't accept null input // 2) return a reference type + // All types in a member which can be annotated should be annotated. Value types and void can be skipped (with a `default`) private static readonly ImmutableDictionary>> Annotations = new Dictionary>> { - { "System.Boolean System.Boolean.Parse(System.String)", Parameters(skip, Nullable(false)) }, - { "System.Void System.Buffer.BlockCopy(System.Array, System.Int32, System.Array, System.Int32, System.Int32)", Parameters(skip, Nullable(false), skip, Nullable(false), skip, skip) }, - { "System.Int32 System.Buffer.ByteLength(System.Array)", Parameters(skip, Nullable(false)) }, - { "System.Byte System.Buffer.GetByte(System.Array, System.Int32)", Parameters(skip, Nullable(false), skip) }, - { "System.Void System.Buffer.SetByte(System.Array, System.Int32, System.Byte)", Parameters(skip, Nullable(false), skip, skip) }, - { "System.Byte System.Byte.Parse(System.String)", Parameters(skip, Nullable(false)) }, - { "System.Byte System.Byte.Parse(System.String, System.IFormatProvider)", Parameters(skip, Nullable(false), skip) }, - { "System.Byte System.Byte.Parse(System.String, System.Globalization.NumberStyles)", Parameters(skip, Nullable(false), Nullable(false)) }, - { "System.Byte System.Byte.Parse(System.String, System.Globalization.NumberStyles, System.IFormatProvider)", Parameters(skip, Nullable(false), Nullable(false), skip) }, - { "System.String System.String.Concat(System.String, System.String)", Parameters(Nullable(false), Nullable(true), Nullable(true)) }, + { "System.Boolean System.Boolean.Parse(System.String)", Array(default, Nullable(false)) }, + { "System.Void System.Buffer.BlockCopy(System.Array, System.Int32, System.Array, System.Int32, System.Int32)", Array(default, Nullable(false), default, Nullable(false), default, default) }, + { "System.Int32 System.Buffer.ByteLength(System.Array)", Array(default, Nullable(false)) }, + { "System.Byte System.Buffer.GetByte(System.Array, System.Int32)", Array(default, Nullable(false), default) }, + { "System.Void System.Buffer.SetByte(System.Array, System.Int32, System.Byte)", Array(default, Nullable(false), default, default) }, + { "System.Byte System.Byte.Parse(System.String)", Array(default, Nullable(false)) }, + { "System.Byte System.Byte.Parse(System.String, System.IFormatProvider)", Array(default, Nullable(false), default) }, + { "System.Byte System.Byte.Parse(System.String, System.Globalization.NumberStyles)", Array(default, Nullable(false), Nullable(false)) }, + { "System.Byte System.Byte.Parse(System.String, System.Globalization.NumberStyles, System.IFormatProvider)", Array(default, Nullable(false), Nullable(false), default) }, + { "System.String System.String.Concat(System.String, System.String)", Array(Nullable(false), Nullable(true), Nullable(true)) }, }.ToImmutableDictionary(); - private static readonly ImmutableDictionary> Attributes = - new Dictionary> + private static readonly ImmutableDictionary>> Attributes = + new Dictionary>> { - { "System.Void System.Diagnostics.Debug.Assert(System.Boolean)", Descriptions(AttributeDescription.EnsuresTrueWhenExitsAttribute) }, - { "System.Void System.Diagnostics.Debug.Assert(System.Boolean, System.String)", Descriptions(AttributeDescription.EnsuresTrueWhenExitsAttribute) }, - { "System.Void System.Diagnostics.Debug.Assert(System.Boolean, System.String, System.String)", Descriptions(AttributeDescription.EnsuresTrueWhenExitsAttribute) }, - { "System.Void System.Diagnostics.Debug.Assert(System.Boolean, System.String, System.String, System.Object[])", Descriptions(AttributeDescription.EnsuresTrueWhenExitsAttribute) }, - { "System.Boolean System.String.IsNullOrEmpty(System.String)", Descriptions(AttributeDescription.TrueWhenNotNullAttribute) }, - { "System.Boolean System.String.IsNullOrWhiteSpace(System.String)", Descriptions(AttributeDescription.TrueWhenNotNullAttribute) }, + { "System.Boolean System.String.IsNullOrEmpty(System.String)", Array(default, NotNullWhenFalse) }, + { "System.Boolean System.String.IsNullOrWhiteSpace(System.String)", Array(default, NotNullWhenFalse) }, + { "System.Boolean System.String.Contains(System.String)", Array(default, EnsuresNotNull) }, }.ToImmutableDictionary(); internal static string MakeMethodKey(PEMethodSymbol method, ParamInfo[] paramInfo) @@ -117,15 +120,10 @@ internal static string MakeMethodKey(MethodSymbol method) // use assembly qualified name format (used in metadata) rather than symbol display? } - /// - /// All types in a member which can be annotated should be annotated. Value types and void can be skipped. - /// - private static readonly ImmutableArray skip = default; - - private static ImmutableArray> Parameters(params ImmutableArray[] values) + private static ImmutableArray> Array(params ImmutableArray[] values) => values.ToImmutableArray(); - private static ImmutableArray Descriptions(params AttributeDescription[] values) + private static ImmutableArray> Array(params ImmutableArray[] values) => values.ToImmutableArray(); private static ImmutableArray Nullable(params bool[] values) @@ -158,19 +156,20 @@ private static void Add(TypeSymbol type, StringBuilder builder) // displaying tuple syntax causes to load the members of ValueTuple, which can cause a cycle, so we use long-hand format instead .WithCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.IncludeNullableReferenceTypeModifier | SymbolDisplayCompilerInternalOptions.UseValueTuple))); - internal static ImmutableArray GetExtraAttributes(string key) + internal static ImmutableArray GetExtraAttributes(string key, int index) { if (key is null) { return default; } - if (!Attributes.TryGetValue(key, out var descriptions)) + _ = Attributes.TryGetValue(key, out var extraAttributes); + if (extraAttributes.IsDefault) { return default; } - return descriptions; + return extraAttributes[index]; } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs index b128edded1722..0a5b03e5160dc 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs @@ -31,27 +31,29 @@ private enum WellKnownAttributeFlags IsCallerFilePath = 0x1 << 5, IsCallerLineNumber = 0x1 << 6, IsCallerMemberName = 0x1 << 7, + NotNullWhenFalse = 0x1 << 8, + EnsuresNotNull = 0x1 << 9, } private struct PackedFlags { // Layout: - // |.............|n|rr|cccccccc|vvvvvvvv| + // |.........|n|rr|cccccccccc|vvvvvvvvvv| // - // v = decoded well known attribute values. 8 bits. - // c = completion states for well known attributes. 1 if given attribute has been decoded, 0 otherwise. 8 bits. + // v = decoded well known attribute values. 10 bits. + // c = completion states for well known attributes. 1 if given attribute has been decoded, 0 otherwise. 10 bits. // r = RefKind. 2 bits. // n = hasNameInMetadata. 1 bit. private const int WellKnownAttributeDataOffset = 0; - private const int WellKnownAttributeCompletionFlagOffset = 8; - private const int RefKindOffset = 16; + private const int WellKnownAttributeCompletionFlagOffset = 10; + private const int RefKindOffset = 20; private const int RefKindMask = 0x3; - private const int WellKnownAttributeDataMask = 0xFF; + private const int WellKnownAttributeDataMask = 0x3FF; private const int WellKnownAttributeCompletionFlagMask = WellKnownAttributeDataMask; - private const int HasNameInMetadataBit = 0x1 << 18; + private const int HasNameInMetadataBit = 0x1 << 22; private const int AllWellKnownAttributesCompleteNoData = WellKnownAttributeCompletionFlagMask << WellKnownAttributeCompletionFlagOffset; @@ -645,6 +647,38 @@ internal override bool IsCallerMemberName } } + internal override bool NotNullWhenFalse + { + get + { + const WellKnownAttributeFlags flag = WellKnownAttributeFlags.NotNullWhenFalse; + + bool value; + if (!_packedFlags.TryGetWellKnownAttribute(flag, out value)) + { + value = _packedFlags.SetWellKnownAttribute(flag, + _moduleSymbol.Module.HasAttribute(_handle, AttributeDescription.NotNullWhenFalseAttribute)); + } + return value || HasExtraAttribute(AttributeDescription.NotNullWhenFalseAttribute); + } + } + + internal override bool EnsuresNotNull + { + get + { + const WellKnownAttributeFlags flag = WellKnownAttributeFlags.EnsuresNotNull; + + bool value; + if (!_packedFlags.TryGetWellKnownAttribute(flag, out value)) + { + value = _packedFlags.SetWellKnownAttribute(flag, + _moduleSymbol.Module.HasAttribute(_handle, AttributeDescription.EnsuresNotNullAttribute)); + } + return value || HasExtraAttribute(AttributeDescription.EnsuresNotNullAttribute); + } + } + public override TypeSymbolWithAnnotations Type { get @@ -653,7 +687,6 @@ public override TypeSymbolWithAnnotations Type } } - public override ImmutableArray RefCustomModifiers { get diff --git a/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs index 1ac342cad96c2..cc362c9fe9f9d 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs @@ -385,6 +385,56 @@ internal sealed override ObsoleteAttributeData ObsoleteAttributeData internal abstract bool IsCallerMemberName { get; } + /// + /// If the method returns false, then we can infer that the argument used for this parameter was not null. + /// + internal abstract bool NotNullWhenFalse { get; } + + /// + /// When the method returns, then we can infer that the argument used for this parameter was not null. + /// + internal abstract bool EnsuresNotNull { get; } + + protected bool HasExtraAttribute(AttributeDescription description) + { + ParameterSymbol originalParameter = this.OriginalDefinition; + var containingMethod = originalParameter.ContainingSymbol as MethodSymbol; + + if (containingMethod is null) + { + return false; + } + + string key = ExtraAnnotations.MakeMethodKey(containingMethod); + + // index 0 is used for return type + // `this` parameter is at index 1 + // other parameters follow + int index = this.Ordinal + (containingMethod.IsExtensionMethod ? 2 : 1); + ImmutableArray extraAttributes = ExtraAnnotations.GetExtraAttributes(key, index); + + if (!extraAttributes.IsDefault) + { + foreach (var attribute in extraAttributes) + { + if (equals(attribute, description)) + { + return true; + } + } + } + + return false; + + bool equals(AttributeDescription first, AttributeDescription second) + { + return first.Namespace == second.Namespace && + first.Name == second.Name && + ReferenceEquals(first.Signatures, second.Signatures) && + first.MatchIgnoringCase == second.MatchIgnoringCase; + } + } + protected sealed override int HighestPriorityUseSiteError { get diff --git a/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyParameterSymbol.cs index 51522bb85a1e7..9a563be35ce38 100644 --- a/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyParameterSymbol.cs @@ -71,6 +71,10 @@ public override bool IsImplicitlyDeclared internal override bool IsCallerMemberName { get { throw ExceptionUtilities.Unreachable; } } + internal override bool NotNullWhenFalse { get { throw ExceptionUtilities.Unreachable; } } + + internal override bool EnsuresNotNull { get { throw ExceptionUtilities.Unreachable; } } + public override Symbol ContainingSymbol { get { throw ExceptionUtilities.Unreachable; } } public override ImmutableArray Locations { get { throw ExceptionUtilities.Unreachable; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs index c9452b9304c78..4285d498af590 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs @@ -151,6 +151,16 @@ internal override bool IsCallerMemberName get { return _originalParam.IsCallerMemberName; } } + internal override bool NotNullWhenFalse + { + get { return _originalParam.NotNullWhenFalse; } + } + + internal override bool EnsuresNotNull + { + get { return _originalParam.EnsuresNotNull; } + } + #endregion } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs index bc305c6134578..302642a03206a 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs @@ -127,6 +127,14 @@ private bool HasCallerMemberNameAttribute && !HasCallerFilePathAttribute && HasCallerMemberNameAttribute; + internal override bool NotNullWhenFalse + => GetEarlyDecodedWellKnownAttributeData()?.HasNotNullWhenFalseAttribute == true || + HasExtraAttribute(AttributeDescription.NotNullWhenFalseAttribute); + + internal override bool EnsuresNotNull + => GetEarlyDecodedWellKnownAttributeData()?.HasEnsuresNotNullAttribute == true || + HasExtraAttribute(AttributeDescription.EnsuresNotNullAttribute); + private ConstantValue DefaultSyntaxValue { get @@ -473,6 +481,14 @@ internal override CSharpAttributeData EarlyDecodeWellKnownAttribute(ref EarlyDec { arguments.GetOrCreateData().HasCallerMemberNameAttribute = true; } + else if (CSharpAttributeData.IsTargetEarlyAttribute(arguments.AttributeType, arguments.AttributeSyntax, AttributeDescription.NotNullWhenFalseAttribute)) + { + arguments.GetOrCreateData().HasNotNullWhenFalseAttribute = true; + } + else if (CSharpAttributeData.IsTargetEarlyAttribute(arguments.AttributeType, arguments.AttributeSyntax, AttributeDescription.EnsuresNotNullAttribute)) + { + arguments.GetOrCreateData().HasEnsuresNotNullAttribute = true; + } } return base.EarlyDecodeWellKnownAttribute(ref arguments); diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceSimpleParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceSimpleParameterSymbol.cs index 037efa5046799..1810662716f3e 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceSimpleParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceSimpleParameterSymbol.cs @@ -82,6 +82,16 @@ internal override bool IsCallerMemberName get { return false; } } + internal override bool NotNullWhenFalse + { + get { return HasExtraAttribute(AttributeDescription.NotNullWhenFalseAttribute); } + } + + internal override bool EnsuresNotNull + { + get { return HasExtraAttribute(AttributeDescription.EnsuresNotNullAttribute); } + } + internal override MarshalPseudoCustomAttributeData MarshallingInformation { get { return null; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/ThisParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/ThisParameterSymbol.cs index 7df824a4c6cf6..320f6561c98b7 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/ThisParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/ThisParameterSymbol.cs @@ -114,6 +114,16 @@ internal override bool IsCallerMemberName get { return false; } } + internal override bool NotNullWhenFalse + { + get { return HasExtraAttribute(AttributeDescription.NotNullWhenFalseAttribute); } + } + + internal override bool EnsuresNotNull + { + get { return HasExtraAttribute(AttributeDescription.EnsuresNotNullAttribute); } + } + public override int Ordinal { get { return -1; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedParameterSymbol.cs index a49b86318f420..c3129733d364b 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedParameterSymbol.cs @@ -115,6 +115,16 @@ internal override bool IsCallerMemberName get { return false; } } + internal override bool NotNullWhenFalse + { + get { return false; } + } + + internal override bool EnsuresNotNull + { + get { return false; } + } + public override Symbol ContainingSymbol { get { return _container; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedParameterSymbol.cs index 832d824c1aff2..4f6b175cef7da 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedParameterSymbol.cs @@ -153,7 +153,17 @@ internal override bool IsCallerMemberName get { return _underlyingParameter.IsCallerMemberName; } } - public override string GetDocumentationCommentXml(CultureInfo preferredCulture = null, bool expandIncludes = false, CancellationToken cancellationToken = default(CancellationToken)) + internal override bool NotNullWhenFalse + { + get { return _underlyingParameter.NotNullWhenFalse; } + } + + internal override bool EnsuresNotNull + { + get { return _underlyingParameter.EnsuresNotNull; } + } + + public override string GetDocumentationCommentXml(CultureInfo preferredCulture = null, bool expandIncludes = false, CancellationToken cancellationToken = default) { return _underlyingParameter.GetDocumentationCommentXml(preferredCulture, expandIncludes, cancellationToken); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs index 6f80b2e17b366..74ced158aa578 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs @@ -72,31 +72,30 @@ public NullableOptOutAttribute(bool flag = true) { } } "; - private const string EnsuresTrueWhenExitsAttributeDefinition = @" + private const string NotNullWhenFalseAttributeDefinition = @" namespace System.Runtime.CompilerServices { - [AttributeUsage(AttributeTargets.Method, + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)] - public class EnsuresTrueWhenExitsAttribute : Attribute + public class NotNullWhenFalseAttribute : Attribute { - public EnsuresTrueWhenExitsAttribute () { } + public NotNullWhenFalseAttribute() { } } } "; - private const string TrueWhenNotNullAttributeDefinition = @" + private const string EnsuresNotNullAttributeDefinition = @" namespace System.Runtime.CompilerServices { - [AttributeUsage(AttributeTargets.Method, + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)] - public class TrueWhenNotNullAttribute : Attribute + public class EnsuresNotNullAttribute : Attribute { - public TrueWhenNotNullAttribute() { } + public EnsuresNotNullAttribute() { } } } "; - [Fact] public void Test0() { @@ -5462,30 +5461,10 @@ class CL1 ); } - [Fact] - public void EnsuresTrueWhenExits_NotNull() - { - CSharpCompilation c = CreateCompilation(@" -using System.Runtime.CompilerServices; -class C -{ - void Main(C? c) - { - MyAssert(c != null); - c.ToString(); - } - - [EnsuresTrueWhenExits] - void MyAssert(bool condition) => throw null; -} -" + EnsuresTrueWhenExitsAttributeDefinition, parseOptions: TestOptions.Regular8); - - c.VerifyDiagnostics(); - } - [Fact] public void MakeMethodKeyForWhereMethod() { + // this test verifies that a bad method symbol doesn't crash when generating a key for external annotations CSharpCompilation c = CreateCompilation(@" class Test { @@ -5498,243 +5477,197 @@ where n < 5 } }", parseOptions: TestOptions.Regular8); - c.VerifyDiagnostics(); - } - [Fact] - public void EnsuresTrueWhenExits_Null() - { - CSharpCompilation c = CreateCompilation(@" -using System.Runtime.CompilerServices; -class C -{ - void Main(C? c) - { - MyAssert(c == null); - c.ToString(); - } - - [EnsuresTrueWhenExits] - void MyAssert(bool condition) => throw null; -} -" + EnsuresTrueWhenExitsAttributeDefinition, parseOptions: TestOptions.Regular8); - c.VerifyDiagnostics( - // (8,9): warning CS8602: Possible dereference of a null reference. - // c.ToString(); - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c").WithLocation(8, 9) + // (7,33): error CS1935: Could not find an implementation of the query pattern for source type 'int[]'. 'Where' not found. Are you missing a reference to 'System.Core.dll' or a using directive for 'System.Linq'? + // var lowNums = from n in numbers + Diagnostic(ErrorCode.ERR_QueryNoProviderStandard, "numbers").WithArguments("int[]", "Where").WithLocation(7, 33) ); } [Fact] - public void EnsuresTrueWhenExits_MethodWithReturnType() + public void NotNullWhenFalse() { CSharpCompilation c = CreateCompilation(@" using System.Runtime.CompilerServices; class C { - void Main(C? c) + void Main(string? s) { - if (MyAssert(c != null)) + if (MyIsNullOrEmpty(s)) { - c.ToString(); + s.ToString(); // warn } else { - c.ToString(); + s.ToString(); // ok } - } - [EnsuresTrueWhenExits] - bool MyAssert(bool condition) => throw null; + s.ToString(); // warn 2 + } + static bool MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; } -" + EnsuresTrueWhenExitsAttributeDefinition, parseOptions: TestOptions.Regular8); +" + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); - c.VerifyDiagnostics(); + c.VerifyDiagnostics( + // (9,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(9, 13), + // (16,9): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(16, 9) + ); + + VerifyNotNullWhenFalse(c, "C.Main", false); + VerifyNotNullWhenFalse(c, "C.MyIsNullOrEmpty", true); } [Fact] - public void EnsuresTrueWhenExits_Debug_Assert1_NotNull() + public void NotNullWhenFalse_OnTwoParameters() { CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; class C { - void Main(C? c) + void Main(string? s, string? s2) { - System.Diagnostics.Debug.Assert(c != null); - c.ToString(); - } -} -", parseOptions: TestOptions.Regular8); - - c.VerifyDiagnostics(); + if (MyIsNullOrEmpty(s, s2)) + { + s.ToString(); // warn 1 + s2.ToString(); // warn 2 } - - [Fact] - public void EnsuresTrueWhenExits_Debug_Assert1_NotNullAndNotEmpty() + else { - CSharpCompilation c = CreateCompilation(@" -class C -{ - void Main(string? c) - { - System.Diagnostics.Debug.Assert(c != null && c != """"); - c.ToString(); - } -} -", parseOptions: TestOptions.Regular8); - - c.VerifyDiagnostics(); + s.ToString(); // ok + s2.ToString(); // ok } - [Fact] - public void EnsuresTrueWhenExits_Debug_Assert1_NotNullOrUnknown() - { - CSharpCompilation c = CreateCompilation(@" -class C -{ - void Main(string? c, bool b) - { - System.Diagnostics.Debug.Assert(c != null || b); - c.ToString(); + s.ToString(); // warn 3 + s2.ToString(); // warn 4 } + static bool MyIsNullOrEmpty([NotNullWhenFalse] string? s, [NotNullWhenFalse] string? s2) => throw null; } -", parseOptions: TestOptions.Regular8); +" + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); c.VerifyDiagnostics( - // (7,9): warning CS8602: Possible dereference of a null reference. - // c.ToString(); - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c").WithLocation(7, 9) + // (9,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(9, 13), + // (10,13): warning CS8602: Possible dereference of a null reference. + // s2.ToString(); // warn 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s2").WithLocation(10, 13), + // (18,9): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(18, 9), + // (19,9): warning CS8602: Possible dereference of a null reference. + // s2.ToString(); // warn 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s2").WithLocation(19, 9) ); - } - [Fact] - public void EnsuresTrueWhenExits_Debug_Assert2_NotNull() - { - CSharpCompilation c = CreateCompilation(@" -class C -{ - void Main(C? c) - { - System.Diagnostics.Debug.Assert(c != null, ""hello""); - c.ToString(); - } -} -", parseOptions: TestOptions.Regular8); - - c.VerifyDiagnostics(); + VerifyNotNullWhenFalse(c, "C.MyIsNullOrEmpty", true, true); } [Fact] - public void EnsuresTrueWhenExits_Debug_Assert3_NotNull() + public void NotNullWhenFalse_OnIndexer() { CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; class C { - void Main(C? c) + void Main(string? s, int x) { - System.Diagnostics.Debug.Assert(c != null, ""hello"", ""world""); - c.ToString(); - } -} -", parseOptions: TestOptions.Regular8); - - c.VerifyDiagnostics(); + if (this[s, x]) + { + s.ToString(); // warn } - - [Fact] - public void EnsuresTrueWhenExits_Debug_Assert4_NotNull() + else { - CSharpCompilation c = CreateCompilation(@" -class C -{ - void Main(C? c) - { - System.Diagnostics.Debug.Assert(c != null, ""hello"", ""world"", new object[] { }); - c.ToString(); - } -} -", parseOptions: TestOptions.Regular8); - - c.VerifyDiagnostics(); + s.ToString(); // ok } - [Fact] - public void EnsuresTrueWhenExits_Debug_Assert_IsNull() - { - CSharpCompilation c = CreateCompilation(@" -class C -{ - void Main(C c) - { - System.Diagnostics.Debug.Assert(c == null, ""hello""); - c.ToString(); + s.ToString(); // warn 2 } + public bool this[[NotNullWhenFalse] string? s, int x] => throw null; } -", parseOptions: TestOptions.Regular8); +" + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); c.VerifyDiagnostics( - // (6,41): hidden CS8606: Result of the comparison is possibly always false. - // System.Diagnostics.Debug.Assert(c == null, "hello"); - Diagnostic(ErrorCode.HDN_NullCheckIsProbablyAlwaysFalse, "c == null").WithLocation(6, 41) + // (9,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(9, 13), + // (16,9): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(16, 9) ); } [Fact] - public void EnsuresTrueWhenExits_Debug_Assert_NoDuplicateDiagnostics() + public void NotNullWhenFalse_SecondArgumentDereferences() { CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; class C { - void Main(C? c) + void Main(string? s) { - System.Diagnostics.Debug.Assert(Method(null), ""hello""); - c.ToString(); + if (Method(s, s.ToString())) // warn 1 + { + s.ToString(); // warn 2 + } + else + { + s.ToString(); // ok + } } - bool Method(string x) => throw null; + static bool Method([NotNullWhenFalse] string? s, string s2) => throw null; } -", parseOptions: TestOptions.Regular8); +" + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); c.VerifyDiagnostics( - // (6,48): warning CS8625: Cannot convert null literal to non-nullable reference or unconstrained type parameter. - // System.Diagnostics.Debug.Assert(Method(null), "hello"); - Diagnostic(ErrorCode.WRN_NullAsNonNullable, "null").WithLocation(6, 48), - // (7,9): warning CS8602: Possible dereference of a null reference. - // c.ToString(); - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c").WithLocation(7, 9) + // (7,23): warning CS8602: Possible dereference of a null reference. + // if (Method(s, s.ToString())) // warn 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(7, 23), + // (9,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(9, 13) ); } [Fact] - public void EnsuresTrueWhenExits_Debug_Assert_InTry() + public void NotNullWhenFalse_SecondArgumentAssigns() { CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; class C { - void Main(C? c) + void Main(string? s) { - try + if (Method(s, s = null)) { - System.Diagnostics.Debug.Assert(c != null, ""hello""); + s.ToString(); // warn 1 + } + else + { + s.ToString(); // warn 2 } - catch { } - - c.ToString(); } + static bool Method([NotNullWhenFalse] string? s, string? s2) => throw null; } -", parseOptions: TestOptions.Regular8); +" + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); c.VerifyDiagnostics( - // (12,9): warning CS8602: Possible dereference of a null reference. - // c.ToString(); - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c").WithLocation(12, 9) + // (9,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(9, 13), + // (13,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(13, 13) ); } [Fact] - public void TrueWhenNotNull() + public void NotNullWhenFalse_MissingAttribute() { CSharpCompilation c = CreateCompilation(@" -using System.Runtime.CompilerServices; class C { void Main(string? s) @@ -5747,21 +5680,52 @@ void Main(string? s) { s.ToString(); // ok } + + s.ToString(); // warn 2 } - [TrueWhenNotNull] - static bool MyIsNullOrEmpty(string? s) => throw null; + static bool MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; } -" + TrueWhenNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); +", parseOptions: TestOptions.Regular8); c.VerifyDiagnostics( - // (9,13): warning CS8602: Possible dereference of a null reference. + // (17,34): error CS0246: The type or namespace name 'NotNullWhenFalseAttribute' could not be found (are you missing a using directive or an assembly reference?) + // static bool MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "NotNullWhenFalse").WithArguments("NotNullWhenFalseAttribute").WithLocation(17, 34), + // (17,34): error CS0246: The type or namespace name 'NotNullWhenFalse' could not be found (are you missing a using directive or an assembly reference?) + // static bool MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "NotNullWhenFalse").WithArguments("NotNullWhenFalse").WithLocation(17, 34), + // (8,13): warning CS8602: Possible dereference of a null reference. // s.ToString(); // warn - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(9, 13) + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(8, 13), + // (12,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // ok + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(12, 13), + // (15,9): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(15, 9) ); + + VerifyNotNullWhenFalse(c, "C.MyIsNullOrEmpty", false); + } + + private void VerifyNotNullWhenFalse(Compilation compilation, string memberName, params bool[] hasNotNullWhenFalse) + { + var method = compilation.GetMember(memberName); + Assert.True((object)method != null, $"Could not find method '{memberName}'"); + var actual = method.Parameters.Select(p => p.NotNullWhenFalse); + Assert.Equal(hasNotNullWhenFalse, actual); + } + + private void VerifyEnsuresNotNull(Compilation compilation, string memberName, params bool[] hasEnsuresNotNull) + { + var method = compilation.GetMember(memberName); + Assert.True((object)method != null, $"Could not find method '{memberName}'"); + var actual = method.Parameters.Select(p => p.EnsuresNotNull); + Assert.Equal(hasEnsuresNotNull, actual); } [Fact] - public void TrueWhenNotNull_BadAttribute() + public void NotNullWhenFalse_BadAttribute() { CSharpCompilation c = CreateCompilation(@" using System.Runtime.CompilerServices; @@ -5778,16 +5742,15 @@ void Main(string? s) s.ToString(); // warn 2 } } - [TrueWhenNotNull(true)] - static bool MyIsNullOrEmpty(string? s) => throw null; + static bool MyIsNullOrEmpty([NotNullWhenFalse(true)] string? s) => throw null; } namespace System.Runtime.CompilerServices { - [AttributeUsage(AttributeTargets.Method, + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)] - public class TrueWhenNotNullAttribute : Attribute + public class NotNullWhenFalseAttribute : Attribute { - public TrueWhenNotNullAttribute(bool bad) { } + public NotNullWhenFalseAttribute(bool bad) { } } } ", parseOptions: TestOptions.Regular8); @@ -5800,10 +5763,12 @@ public TrueWhenNotNullAttribute(bool bad) { } // s.ToString(); // warn 2 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(13, 13) ); + + VerifyNotNullWhenFalse(c, "C.MyIsNullOrEmpty", false); } [Fact] - public void TrueWhenNotNull_InvertIf() + public void NotNullWhenFalse_InvertIf() { CSharpCompilation c = CreateCompilation(@" using System.Runtime.CompilerServices; @@ -5820,20 +5785,21 @@ void Main(string? s) s.ToString(); // warn } } - [TrueWhenNotNull] - static bool MyIsNullOrEmpty(string? s) => throw null; + static bool MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; } -" + TrueWhenNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); +" + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); c.VerifyDiagnostics( // (13,13): warning CS8602: Possible dereference of a null reference. // s.ToString(); // warn Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(13, 13) ); + + VerifyNotNullWhenFalse(c, "C.MyIsNullOrEmpty", true); } [Fact] - public void TrueWhenNotNull_InstanceMethod() + public void NotNullWhenFalse_InstanceMethod() { CSharpCompilation c = CreateCompilation(@" using System.Runtime.CompilerServices; @@ -5850,20 +5816,21 @@ void Main(string? s) s.ToString(); // ok } } - [TrueWhenNotNull] - bool MyIsNullOrEmpty(string? s) => throw null; + bool MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; } -" + TrueWhenNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); +" + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); c.VerifyDiagnostics( // (9,13): warning CS8602: Possible dereference of a null reference. // s.ToString(); // warn Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(9, 13) ); + + VerifyNotNullWhenFalse(c, "C.MyIsNullOrEmpty", true); } [Fact] - public void TrueWhenNotNull_ExtensionMethod() + public void NotNullWhenFalse_ExtensionMethod() { CSharpCompilation c = CreateCompilation(@" using System.Runtime.CompilerServices; @@ -5883,20 +5850,21 @@ void Main(string? s) } public static class Extension { - [TrueWhenNotNull] - public static bool MyIsNullOrEmpty(this C c, string? s) => throw null; + public static bool MyIsNullOrEmpty(this C c, [NotNullWhenFalse] string? s) => throw null; } -" + TrueWhenNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); +" + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); c.VerifyDiagnostics( // (9,13): warning CS8602: Possible dereference of a null reference. // s.ToString(); // warn Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(9, 13) ); + + VerifyNotNullWhenFalse(c, "Extension.MyIsNullOrEmpty", false, true); } [Fact] - public void TrueWhenNotNull_String_IsNullOrEmpty() + public void NotNullWhenFalse_String_IsNullOrEmpty() { CSharpCompilation c = CreateCompilation(@" class C @@ -5920,10 +5888,98 @@ void Main(string? s) // s.ToString(); // warn Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(8, 13) ); + + VerifyNotNullWhenFalse(c, "System.String.IsNullOrEmpty", true); + } + + [Fact] + public void NotNullWhenFalse_String_IsNullOrEmpty_WithoutCorlib() + { + CSharpCompilation c = CreateEmptyCompilation(@" +class C +{ + void Main(string? s) + { + if (string.IsNullOrEmpty(s)) + { + s.ToString(); // warn + } + else + { + s.ToString(); // ok + } + } +} +namespace System +{ + public class Object + { + public virtual string ToString() => throw null; + } + public class String + { + public static bool IsNullOrEmpty(string? s) => throw null; + } + public struct Void { } + public struct Boolean { } + public class ValueType { } +} +", parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (8,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(8, 13) + ); + + VerifyNotNullWhenFalse(c, "System.String.IsNullOrEmpty", true); + } + + [Fact] + public void NotNullWhenFalse_String_IsNullOrWhiteSpace_WithoutCorlib() + { + CSharpCompilation c = CreateEmptyCompilation(@" +class C +{ + void Main(string? s) + { + if (string.IsNullOrWhiteSpace(s)) + { + s.ToString(); // warn + } + else + { + s.ToString(); // ok + } + } +} +namespace System +{ + public class Object + { + public virtual string ToString() => throw null; + } + public class String + { + public static bool IsNullOrWhiteSpace(string? s) => throw null; + } + public struct Void { } + public struct Boolean { } + public class ValueType { } +} +", parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (8,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(8, 13) + ); + + VerifyNotNullWhenFalse(c, "System.String.IsNullOrWhiteSpace", true); } [Fact] - public void TrueWhenNotNull_String_NotIsNullOrEmpty() + public void NotNullWhenFalse_String_NotIsNullOrEmpty() { CSharpCompilation c = CreateCompilation(@" class C @@ -5947,10 +6003,12 @@ void Main(string? s) // s.ToString(); // warn Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(12, 13) ); + + VerifyNotNullWhenFalse(c, "System.String.IsNullOrEmpty", true); } [Fact] - public void TrueWhenNotNull_String_NotIsNullOrEmpty_NoDuplicateWarnings() + public void NotNullWhenFalse_String_NotIsNullOrEmpty_NoDuplicateWarnings() { CSharpCompilation c = CreateCompilation(@" class C @@ -5973,7 +6031,7 @@ void M() } [Fact] - public void TrueWhenNotNull_String_NotIsNullOrEmpty_NotAString() + public void NotNullWhenFalse_String_NotIsNullOrEmpty_NotAString() { CSharpCompilation c = CreateCompilation(@" class C @@ -5996,7 +6054,7 @@ void M() } [Fact] - public void TrueWhenNotNull_String_IsNullOrWhiteSpace() + public void NotNullWhenFalse_String_IsNullOrWhiteSpace() { CSharpCompilation c = CreateCompilation(@" class C @@ -6020,6 +6078,258 @@ void Main(string? s) // s.ToString(); // warn Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(8, 13) ); + + VerifyNotNullWhenFalse(c, "System.String.IsNullOrWhiteSpace", true); + } + + [Fact] + public void NotNullWhenFalse_PartialMethod() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +public partial class C +{ + partial void M1(string? s); + partial void M1([NotNullWhenFalse] string? s) => throw null; + + partial void M2([NotNullWhenFalse] string? s); + partial void M2(string? s) => throw null; + + partial void M3([NotNullWhenFalse] string? s); + partial void M3([NotNullWhenFalse] string? s) => throw null; +} +" + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (11,22): error CS0579: Duplicate 'NotNullWhenFalse' attribute + // partial void M3([NotNullWhenFalse] string? s); + Diagnostic(ErrorCode.ERR_DuplicateAttribute, "NotNullWhenFalse").WithArguments("NotNullWhenFalse").WithLocation(11, 22) + ); + + VerifyNotNullWhenFalse(c, "C.M1", false); + VerifyNotNullWhenFalse(c, "C.M2", true); + VerifyNotNullWhenFalse(c, "C.M3", true); + } + + [Fact] + public void NotNullWhenFalse_ReturningDynamic() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +class C +{ + void Main(string? s) + { + if (string.IsNullOrWhiteSpace(s)) + { + s.ToString(); // warn + } + else + { + s.ToString(); // ok + } + } + dynamic MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; +} +" + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (9,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(9, 13) + ); + + VerifyNotNullWhenFalse(c, "C.MyIsNullOrEmpty", true); + } + + [Fact] + public void NotNullWhenFalse_ReturningObject() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +class C +{ + void Main(string? s) + { + string.IsNullOrWhiteSpace(s); + s.ToString(); // warn + } + object MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; +} +" + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (8,9): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(8, 9) + ); + } + + [Fact] + public void NotNullWhenFalse_FollowedByEnsuresNotNull() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +class C +{ + void Main(string? s) + { + if (MyIsNullOrEmpty(s, s)) + { + s.ToString(); // ok + } + else + { + s.ToString(); // ok + } + + s.ToString(); // ok + } + static bool MyIsNullOrEmpty([NotNullWhenFalse] string? s, [EnsuresNotNull] string? s2) => throw null; +} +" + NotNullWhenFalseAttributeDefinition + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics(); + + VerifyNotNullWhenFalse(c, "C.MyIsNullOrEmpty", true, false); + VerifyEnsuresNotNull(c, "C.MyIsNullOrEmpty", false, true); + } + + [Fact] + public void NotNullWhenFalse_AndEnsuresNotNull() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +class C +{ + void Main(string? s) + { + if (MyIsNullOrEmpty(s)) + { + s.ToString(); // ok + } + else + { + s.ToString(); // ok + } + + s.ToString(); // ok + } + static bool MyIsNullOrEmpty([NotNullWhenFalse, EnsuresNotNull] string? s) => throw null; +} +" + NotNullWhenFalseAttributeDefinition + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics(); + + VerifyNotNullWhenFalse(c, "C.MyIsNullOrEmpty", true); + VerifyEnsuresNotNull(c, "C.MyIsNullOrEmpty", true); + } + + [Fact] + public void EnsuresNotNull() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +class C +{ + void Main(string? s) + { + ThrowIfNull(42, s); + s.ToString(); // ok + } + static void ThrowIfNull(int x, [EnsuresNotNull] string? s) => throw null; +} +" + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics(); + + VerifyEnsuresNotNull(c, "C.ThrowIfNull", false, true); + } + + [Fact] + public void EnsuresNotNull_SecondArgumentDereferences() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +class C +{ + void Main(string? s) + { + ThrowIfNull(s, s.ToString()); // warn + s.ToString(); // ok + } + static void ThrowIfNull([EnsuresNotNull] string? s, string s2) => throw null; +} +" + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (7,24): warning CS8602: Possible dereference of a null reference. + // ThrowIfNull(s, s.ToString()); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(7, 24) + ); + + VerifyEnsuresNotNull(c, "C.ThrowIfNull", true, false); + } + + [Fact] + public void EnsuresNotNull_SecondArgumentAssigns() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +class C +{ + void Main(string? s) + { + ThrowIfNull(s, s = null); + s.ToString(); // warn + } + static void ThrowIfNull([EnsuresNotNull] string? s, string? s2) => throw null; +} +" + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (8,9): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(8, 9) + ); + } + + [Fact] + public void EnsuresNotNull_String_Contains() + { + CSharpCompilation c = CreateCompilation(@" +class C +{ + void Main(string? s) + { + ""hello"".Contains(s); + s.ToString(); // ok + } +} +", parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics(); + + VerifyEnsuresNotNull(c, "System.String.Contains", true); + } + + [Fact] + public void EnsuresNotNull_Indexer() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +class C +{ + void Main(string? s) + { + _ = this[42, s]; + s.ToString(); // ok + } + public int this[int x, [EnsuresNotNull] string? s] => throw null; +} +" + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics(); } [Fact] diff --git a/src/Compilers/Core/Portable/Symbols/Attributes/AttributeDescription.cs b/src/Compilers/Core/Portable/Symbols/Attributes/AttributeDescription.cs index 367fd0c91f8cc..3e40971c2cce3 100644 --- a/src/Compilers/Core/Portable/Symbols/Attributes/AttributeDescription.cs +++ b/src/Compilers/Core/Portable/Symbols/Attributes/AttributeDescription.cs @@ -28,12 +28,6 @@ public AttributeDescription(string @namespace, string name, byte[][] signatures, this.MatchIgnoringCase = matchIgnoringCase; } - public bool Equals(AttributeDescription other) - => this.Namespace == other.Namespace && - this.Name == other.Name && - ReferenceEquals(this.Signatures, other.Signatures) && - this.MatchIgnoringCase == other.MatchIgnoringCase; - public string FullName { get { return Namespace + "." + Name; } @@ -264,8 +258,8 @@ static AttributeDescription() private static readonly byte[][] s_signaturesOfOutAttribute = { s_signature_HasThis_Void }; private static readonly byte[][] s_signaturesOfIsReadOnlyAttribute = { s_signature_HasThis_Void }; private static readonly byte[][] s_signaturesOfIsUnmanagedAttribute = { s_signature_HasThis_Void }; - private static readonly byte[][] s_signaturesOfTrueWhenNotNullAttribute = { s_signature_HasThis_Void }; - private static readonly byte[][] s_signaturesOfEnsuresTrueWhenExitsAttribute = { s_signature_HasThis_Void }; + private static readonly byte[][] s_signaturesOfNotNullWhenFalseAttribute = { s_signature_HasThis_Void }; + private static readonly byte[][] s_signaturesOfEnsuresNotNullAttribute = { s_signature_HasThis_Void }; private static readonly byte[][] s_signaturesOfFixedBufferAttribute = { s_signature_HasThis_Void_Type_Int32 }; private static readonly byte[][] s_signaturesOfSuppressUnmanagedCodeSecurityAttribute = { s_signature_HasThis_Void }; private static readonly byte[][] s_signaturesOfPrincipalPermissionAttribute = { s_signature_HasThis_Void_SecurityAction }; @@ -462,8 +456,8 @@ static AttributeDescription() internal static readonly AttributeDescription StructLayoutAttribute = new AttributeDescription("System.Runtime.InteropServices", "StructLayoutAttribute", s_signaturesOfStructLayoutAttribute); internal static readonly AttributeDescription FieldOffsetAttribute = new AttributeDescription("System.Runtime.InteropServices", "FieldOffsetAttribute", s_signaturesOfFieldOffsetAttribute); internal static readonly AttributeDescription FixedBufferAttribute = new AttributeDescription("System.Runtime.CompilerServices", "FixedBufferAttribute", s_signaturesOfFixedBufferAttribute); - internal static readonly AttributeDescription TrueWhenNotNullAttribute = new AttributeDescription("System.Runtime.CompilerServices", "TrueWhenNotNullAttribute", s_signaturesOfTrueWhenNotNullAttribute); - internal static readonly AttributeDescription EnsuresTrueWhenExitsAttribute = new AttributeDescription("System.Runtime.CompilerServices", "EnsuresTrueWhenExitsAttribute", s_signaturesOfEnsuresTrueWhenExitsAttribute); + internal static readonly AttributeDescription NotNullWhenFalseAttribute = new AttributeDescription("System.Runtime.CompilerServices", "NotNullWhenFalseAttribute", s_signaturesOfNotNullWhenFalseAttribute); + internal static readonly AttributeDescription EnsuresNotNullAttribute = new AttributeDescription("System.Runtime.CompilerServices", "EnsuresNotNullAttribute", s_signaturesOfEnsuresNotNullAttribute); internal static readonly AttributeDescription MarshalAsAttribute = new AttributeDescription("System.Runtime.InteropServices", "MarshalAsAttribute", s_signaturesOfMarshalAsAttribute); internal static readonly AttributeDescription InAttribute = new AttributeDescription("System.Runtime.InteropServices", "InAttribute", s_signaturesOfInAttribute); internal static readonly AttributeDescription OutAttribute = new AttributeDescription("System.Runtime.InteropServices", "OutAttribute", s_signaturesOfOutAttribute); diff --git a/src/Compilers/Core/Portable/Symbols/Attributes/CommonParameterEarlyWellKnownAttributeData.cs b/src/Compilers/Core/Portable/Symbols/Attributes/CommonParameterEarlyWellKnownAttributeData.cs index fb5e65a584bc8..f240d501d04e2 100644 --- a/src/Compilers/Core/Portable/Symbols/Attributes/CommonParameterEarlyWellKnownAttributeData.cs +++ b/src/Compilers/Core/Portable/Symbols/Attributes/CommonParameterEarlyWellKnownAttributeData.cs @@ -77,5 +77,37 @@ public bool HasCallerMemberNameAttribute } } #endregion + + private bool _hasNotNullWhenFalseAttribute; + public bool HasNotNullWhenFalseAttribute + { + get + { + VerifySealed(expected: true); + return _hasNotNullWhenFalseAttribute; + } + set + { + VerifySealed(expected: false); + _hasNotNullWhenFalseAttribute = value; + SetDataStored(); + } + } + + private bool _hasEnsuresNotNullAttribute; + public bool HasEnsuresNotNullAttribute + { + get + { + VerifySealed(expected: true); + return _hasEnsuresNotNullAttribute; + } + set + { + VerifySealed(expected: false); + _hasEnsuresNotNullAttribute = value; + SetDataStored(); + } + } } } From 2470a6f4ba0d1d24ea19e63aef50c216538c14da Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Wed, 9 May 2018 13:25:44 -0700 Subject: [PATCH 06/18] Address PR feedback from Chuck --- .../Portable/FlowAnalysis/DataFlowPassBase.cs | 11 +++- .../Portable/FlowAnalysis/NullableWalker.cs | 42 +++++-------- ...rnalAnnotations.cs => ExtraAnnotations.cs} | 8 ++- .../Portable/Symbols/ParameterSymbol.cs | 7 +-- .../Semantic/Semantics/StaticNullChecking.cs | 59 +++++++++++++++++++ 5 files changed, 92 insertions(+), 35 deletions(-) rename src/Compilers/CSharp/Portable/Symbols/{ExternalAnnotations.cs => ExtraAnnotations.cs} (97%) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPassBase.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPassBase.cs index 87ba7a1f08263..6b5903774890d 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPassBase.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPassBase.cs @@ -116,7 +116,16 @@ protected int GetOrCreateSlot(Symbol symbol, int containingSlot = 0) variableBySlot[slot] = identifier; } - Normalize(ref this.State); + if (IsConditionalState) + { + Normalize(ref this.StateWhenTrue); + Normalize(ref this.StateWhenFalse); + } + else + { + Normalize(ref this.State); + } + return slot; } diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 7ebbaefd2d28f..676dcdb4ce123 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -1617,10 +1617,10 @@ public override BoundNode VisitCall(BoundCall node) /// For each argument, figure out if its corresponding parameter is annotated with NotNullWhenFalse or /// EnsuresNotNull. /// - private ImmutableArray<(bool notNullWhenFalse, bool ensuresNotNull)> GetAnnotations(int numArguments, + private static ImmutableArray<(bool NotNullWhenFalse, bool EnsuresNotNull)> GetAnnotations(int numArguments, bool expanded, ImmutableArray parameters, ImmutableArray argsToParamsOpt) { - ArrayBuilder<(bool notNullWhenFalse, bool ensuresNotNull)> builder = null; + ArrayBuilder<(bool NotNullWhenFalse, bool EnsuresNotNull)> builder = null; for (int i = 0; i < numArguments; i++) { @@ -1794,22 +1794,6 @@ private void VisitArgumentsEvaluateHonoringAnnotations( Debug.Assert(annotations.Length == arguments.Length); - // Since we may be splitting states to represent the method returning true/false, - // we'll prepare all the slots ahead of time. - var slots = ArrayBuilder.GetInstance(arguments.Length); - for (int i = 0; i < arguments.Length; i++) - { - var argument = arguments[i]; - int slot = MakeSlot(argument); - if (slot <= 0) - { - slots.Add(null); - continue; - } - if (slot >= this.State.Capacity) Normalize(ref this.State); - slots.Add(slot); - } - for (int i = 0; i < arguments.Length; i++) { if (this.IsConditionalState) @@ -1817,7 +1801,7 @@ private void VisitArgumentsEvaluateHonoringAnnotations( // We could be in a conditional state because of NotNullWhenFalse annotation // Then WhenTrue/False states correspond to the invocation returning true/false - // We'll successively assume that we're in the unconditional state where the method returns true, + // We'll assume that we're in the unconditional state where the method returns true, // then we'll repeat assuming the method returns false. LocalState whenTrue = this.StateWhenTrue.Clone(); @@ -1826,10 +1810,12 @@ private void VisitArgumentsEvaluateHonoringAnnotations( this.SetState(whenTrue); VisitArgumentEvaluate(arguments, refKindsOpt, i); Debug.Assert(!IsConditionalState); + whenTrue = this.State; // LocalState may be a struct this.SetState(whenFalse); VisitArgumentEvaluate(arguments, refKindsOpt, i); Debug.Assert(!IsConditionalState); + whenFalse = this.State; // LocalState may be a struct this.SetConditionalState(whenTrue, whenFalse); } @@ -1839,12 +1825,17 @@ private void VisitArgumentsEvaluateHonoringAnnotations( } var argument = arguments[i]; - if (argument.Type?.IsReferenceType != true || slots[i] == null) + if (argument.Type?.IsReferenceType != true) + { + continue; + } + + int slot = MakeSlot(argument); + if (slot <= 0) { continue; } - int slot = slots[i].Value; (bool notNullWhenFalse, bool ensuresNotNull) = annotations[i]; if (ensuresNotNull) @@ -1870,7 +1861,6 @@ private void VisitArgumentsEvaluateHonoringAnnotations( } } - slots.Free(); _disableDiagnostics = saveDisableDiagnostics; } @@ -3243,7 +3233,7 @@ public override BoundNode VisitArgList(BoundArgList node) public override BoundNode VisitArgListOperator(BoundArgListOperator node) { - VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt, parameters: default, argsToParamsOpt: default, expanded: false); + VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt, expanded: false); Debug.Assert((object)node.Type == null); SetResult(node); return null; @@ -3323,7 +3313,7 @@ public override BoundNode VisitDynamicMemberAccess(BoundDynamicMemberAccess node public override BoundNode VisitDynamicInvocation(BoundDynamicInvocation node) { VisitRvalue(node.Expression); - VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt, parameters: default, argsToParamsOpt: default, expanded: false); + VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt, expanded: false); Debug.Assert(node.Type.IsDynamic()); Debug.Assert(node.Type.IsReferenceType); @@ -3351,7 +3341,7 @@ public override BoundNode VisitEventAssignmentOperator(BoundEventAssignmentOpera public override BoundNode VisitDynamicObjectCreationExpression(BoundDynamicObjectCreationExpression node) { Debug.Assert(!IsConditionalState); - VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt, parameters: default, argsToParamsOpt: default, expanded: false); + VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt, expanded: false); VisitObjectOrDynamicObjectCreation(node, node.InitializerExpressionOpt); return null; } @@ -3430,7 +3420,7 @@ public override BoundNode VisitDynamicIndexerAccess(BoundDynamicIndexerAccess no var receiver = node.ReceiverOpt; VisitRvalue(receiver); CheckPossibleNullReceiver(receiver); - VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt, parameters: default, argsToParamsOpt: default, expanded: false); + VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt, expanded: false); Debug.Assert(node.Type.IsDynamic()); diff --git a/src/Compilers/CSharp/Portable/Symbols/ExternalAnnotations.cs b/src/Compilers/CSharp/Portable/Symbols/ExtraAnnotations.cs similarity index 97% rename from src/Compilers/CSharp/Portable/Symbols/ExternalAnnotations.cs rename to src/Compilers/CSharp/Portable/Symbols/ExtraAnnotations.cs index 5b1d7a3ca62f0..1b03ce2fcc692 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ExternalAnnotations.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ExtraAnnotations.cs @@ -156,7 +156,11 @@ private static void Add(TypeSymbol type, StringBuilder builder) // displaying tuple syntax causes to load the members of ValueTuple, which can cause a cycle, so we use long-hand format instead .WithCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.IncludeNullableReferenceTypeModifier | SymbolDisplayCompilerInternalOptions.UseValueTuple))); - internal static ImmutableArray GetExtraAttributes(string key, int index) + /// + /// index 0 is used for return type + /// other parameters follow + /// + internal static ImmutableArray GetExtraAttributes(string key, int parameterIndex) { if (key is null) { @@ -169,7 +173,7 @@ internal static ImmutableArray GetExtraAttributes(string k return default; } - return extraAttributes[index]; + return extraAttributes[parameterIndex + 1]; } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs index cc362c9fe9f9d..0e6498f373bf8 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs @@ -406,12 +406,7 @@ protected bool HasExtraAttribute(AttributeDescription description) } string key = ExtraAnnotations.MakeMethodKey(containingMethod); - - // index 0 is used for return type - // `this` parameter is at index 1 - // other parameters follow - int index = this.Ordinal + (containingMethod.IsExtensionMethod ? 2 : 1); - ImmutableArray extraAttributes = ExtraAnnotations.GetExtraAttributes(key, index); + ImmutableArray extraAttributes = ExtraAnnotations.GetExtraAttributes(key, this.Ordinal); if (!extraAttributes.IsDefault) { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs index 74ced158aa578..fc3e5ee3fc8c6 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs @@ -5798,6 +5798,24 @@ void Main(string? s) VerifyNotNullWhenFalse(c, "C.MyIsNullOrEmpty", true); } + [Fact] + public void NotNullWhenFalse_WithNullLiteral() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +class C +{ + void Main(string? s) + { + _ = MyIsNullOrEmpty(null); + } + static bool MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; +} +" + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics(); + } + [Fact] public void NotNullWhenFalse_InstanceMethod() { @@ -6246,6 +6264,47 @@ void Main(string? s) VerifyEnsuresNotNull(c, "C.ThrowIfNull", false, true); } + [Fact] + public void EnsuresNotNull_GenericMethod() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +class C +{ + void Main(string? s) + { + ThrowIfNull(s); + s.ToString(); // ok + } + static void ThrowIfNull([EnsuresNotNull] T s) => throw null; +} +" + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics(); + + VerifyEnsuresNotNull(c, "C.ThrowIfNull", true); + } + + [Fact] + public void EnsuresNotNull_ConditionalMethodInReleaseMode() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +class C +{ + void Main(string? s) + { + ThrowIfNull(42, s); + s.ToString(); // ok + } + [System.Diagnostics.Conditional(""DEBUG"")] + static void ThrowIfNull(int x, [EnsuresNotNull] string? s) => throw null; +} +" + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8, options: TestOptions.ReleaseDll); + + c.VerifyDiagnostics(); + } + [Fact] public void EnsuresNotNull_SecondArgumentDereferences() { From 5ae61fa20c53890f1ddd7f89457f4aedc0e04e78 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Wed, 9 May 2018 20:03:19 -0700 Subject: [PATCH 07/18] Add tests for metadata. Fix regression. Use regular, not early, attributes. --- .../Portable/CSharpResources.Designer.cs | 18 ++ .../CSharp/Portable/CSharpResources.resx | 3 + .../CSharp/Portable/Errors/ErrorCode.cs | 1 + .../Portable/FlowAnalysis/NullableWalker.cs | 27 ++- .../Symbols/Metadata/PE/PEParameterSymbol.cs | 10 +- .../Source/SourceComplexParameterSymbol.cs | 27 ++- .../Symbols/Source/ThisParameterSymbol.cs | 4 +- .../Portable/xlf/CSharpResources.cs.xlf | 5 + .../Portable/xlf/CSharpResources.de.xlf | 5 + .../Portable/xlf/CSharpResources.es.xlf | 5 + .../Portable/xlf/CSharpResources.fr.xlf | 5 + .../Portable/xlf/CSharpResources.it.xlf | 5 + .../Portable/xlf/CSharpResources.ja.xlf | 5 + .../Portable/xlf/CSharpResources.ko.xlf | 5 + .../Portable/xlf/CSharpResources.pl.xlf | 5 + .../Portable/xlf/CSharpResources.pt-BR.xlf | 5 + .../Portable/xlf/CSharpResources.ru.xlf | 5 + .../Portable/xlf/CSharpResources.tr.xlf | 5 + .../Portable/xlf/CSharpResources.zh-Hans.xlf | 5 + .../Portable/xlf/CSharpResources.zh-Hant.xlf | 5 + .../Semantic/Semantics/StaticNullChecking.cs | 213 ++++++++++++++---- ...monParameterEarlyWellKnownAttributeData.cs | 32 --- .../CommonParameterWellKnownAttributeData.cs | 32 +++ .../Test/Utilities/CSharp/CSharpTestBase.cs | 6 +- 24 files changed, 336 insertions(+), 102 deletions(-) diff --git a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs index 1904045ddddb9..2b192bb45b552 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs +++ b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs @@ -4660,6 +4660,15 @@ internal static string ERR_FeatureNotAvailableInVersion7_2 { } } + /// + /// Looks up a localized string similar to Feature '{0}' is not available in C# 7.3. Please use language version {1} or greater.. + /// + internal static string ERR_FeatureNotAvailableInVersion7_3 { + get { + return ResourceManager.GetString("ERR_FeatureNotAvailableInVersion7_3", resourceCulture); + } + } + /// /// Looks up a localized string similar to Feature '{0}' is not available in C# 8. Please use language version {1} or greater.. /// @@ -7324,6 +7333,15 @@ internal static string ERR_NotNullRefDefaultParameter { } } + /// + /// Looks up a localized string similar to The NotNullWhenFalse attribute is only applicable on members that return a boolean type.. + /// + internal static string ERR_NotNullWhenFalseRequiresBoolReturn { + get { + return ResourceManager.GetString("ERR_NotNullWhenFalseRequiresBoolReturn", resourceCulture); + } + } + /// /// Looks up a localized string similar to This language feature ('{0}') is not yet implemented.. /// diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index f57710f04e80e..473185c058c0e 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -5357,6 +5357,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed. + + The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + The ! operator can only be applied to reference types. diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 0faeca8448c18..17d730c45da4f 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -1609,6 +1609,7 @@ internal enum ErrorCode ERR_NotNullableOperatorNotReferenceType = 8624, WRN_NullAsNonNullable = 8625, WRN_NoBestNullabilityConditionalExpression = 8626, + ERR_NotNullWhenFalseRequiresBoolReturn = 8627, } // Note: you will need to re-generate compiler code after adding warnings (build\scripts\generate-compiler-code.cmd) } diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 676dcdb4ce123..72abb3308a8db 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -1626,7 +1626,10 @@ public override BoundNode VisitCall(BoundCall node) { (ParameterSymbol parameter, _) = GetCorrespondingParameter(i, parameters, argsToParamsOpt, expanded); - bool notNullWhenFalse = parameter?.NotNullWhenFalse == true; + // We'll ignore NotNullWhenFalse that is misused in metadata + bool notNullWhenFalse = parameter?.NotNullWhenFalse == true && + parameter.ContainingSymbol.GetTypeOrReturnType().SpecialType == SpecialType.System_Boolean; + bool ensuresNotNull = parameter?.EnsuresNotNull == true; if ((notNullWhenFalse || ensuresNotNull) && builder == null) @@ -1724,8 +1727,14 @@ private ImmutableArray VisitArgumentsEvaluate( // We do a second pass through the arguments, ignoring any diagnostics produced, but honoring the annotations, // to get the proper result state. - this.SetState(savedState); - VisitArgumentsEvaluateHonoringAnnotations(arguments, refKindsOpt, parameters, argsToParamsOpt, expanded); + ImmutableArray<(bool notNullWhenFalse, bool ensuresNotNull)> annotations = GetAnnotations(arguments.Length, + expanded, parameters, argsToParamsOpt); + + if (!annotations.IsDefault) + { + this.SetState(savedState); + VisitArgumentsEvaluateHonoringAnnotations(arguments, refKindsOpt, annotations); + } return results; } @@ -1775,20 +1784,10 @@ private void VisitArgumentEvaluate(ImmutableArray arguments, Im private void VisitArgumentsEvaluateHonoringAnnotations( ImmutableArray arguments, ImmutableArray refKindsOpt, - ImmutableArray parameters, - ImmutableArray argsToParamsOpt, - bool expanded) + ImmutableArray<(bool notNullWhenFalse, bool ensuresNotNull)> annotations) { Debug.Assert(!IsConditionalState); - ImmutableArray<(bool notNullWhenFalse, bool ensuresNotNull)> annotations = GetAnnotations(arguments.Length, - expanded, parameters, argsToParamsOpt); - - if (annotations.IsDefault) - { - return; - } - bool saveDisableDiagnostics = _disableDiagnostics; _disableDiagnostics = true; diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs index 0a5b03e5160dc..5589608e3adc6 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs @@ -657,9 +657,10 @@ internal override bool NotNullWhenFalse if (!_packedFlags.TryGetWellKnownAttribute(flag, out value)) { value = _packedFlags.SetWellKnownAttribute(flag, - _moduleSymbol.Module.HasAttribute(_handle, AttributeDescription.NotNullWhenFalseAttribute)); + _moduleSymbol.Module.HasAttribute(_handle, AttributeDescription.NotNullWhenFalseAttribute) || + HasExtraAttribute(AttributeDescription.NotNullWhenFalseAttribute)); } - return value || HasExtraAttribute(AttributeDescription.NotNullWhenFalseAttribute); + return value; } } @@ -673,9 +674,10 @@ internal override bool EnsuresNotNull if (!_packedFlags.TryGetWellKnownAttribute(flag, out value)) { value = _packedFlags.SetWellKnownAttribute(flag, - _moduleSymbol.Module.HasAttribute(_handle, AttributeDescription.EnsuresNotNullAttribute)); + _moduleSymbol.Module.HasAttribute(_handle, AttributeDescription.EnsuresNotNullAttribute) || + HasExtraAttribute(AttributeDescription.EnsuresNotNullAttribute)); } - return value || HasExtraAttribute(AttributeDescription.EnsuresNotNullAttribute); + return value; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs index 302642a03206a..792a6a2ac42fa 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs @@ -128,11 +128,11 @@ private bool HasCallerMemberNameAttribute && HasCallerMemberNameAttribute; internal override bool NotNullWhenFalse - => GetEarlyDecodedWellKnownAttributeData()?.HasNotNullWhenFalseAttribute == true || + => GetDecodedWellKnownAttributeData()?.HasNotNullWhenFalseAttribute == true || HasExtraAttribute(AttributeDescription.NotNullWhenFalseAttribute); internal override bool EnsuresNotNull - => GetEarlyDecodedWellKnownAttributeData()?.HasEnsuresNotNullAttribute == true || + => GetDecodedWellKnownAttributeData()?.HasEnsuresNotNullAttribute == true || HasExtraAttribute(AttributeDescription.EnsuresNotNullAttribute); private ConstantValue DefaultSyntaxValue @@ -481,14 +481,6 @@ internal override CSharpAttributeData EarlyDecodeWellKnownAttribute(ref EarlyDec { arguments.GetOrCreateData().HasCallerMemberNameAttribute = true; } - else if (CSharpAttributeData.IsTargetEarlyAttribute(arguments.AttributeType, arguments.AttributeSyntax, AttributeDescription.NotNullWhenFalseAttribute)) - { - arguments.GetOrCreateData().HasNotNullWhenFalseAttribute = true; - } - else if (CSharpAttributeData.IsTargetEarlyAttribute(arguments.AttributeType, arguments.AttributeSyntax, AttributeDescription.EnsuresNotNullAttribute)) - { - arguments.GetOrCreateData().HasEnsuresNotNullAttribute = true; - } } return base.EarlyDecodeWellKnownAttribute(ref arguments); @@ -621,6 +613,21 @@ internal override void DecodeWellKnownAttribute(ref DecodeWellKnownAttributeArgu // NullableAttribute should not be set explicitly. arguments.Diagnostics.Add(ErrorCode.ERR_ExplicitNullableAttribute, arguments.AttributeSyntaxOpt.Location); } + else if (attribute.IsTargetAttribute(this, AttributeDescription.NotNullWhenFalseAttribute)) + { + if (this.ContainingSymbol.GetTypeOrReturnType().SpecialType == SpecialType.System_Boolean) + { + arguments.GetOrCreateData().HasNotNullWhenFalseAttribute = true; + } + else + { + arguments.Diagnostics.Add(ErrorCode.ERR_NotNullWhenFalseRequiresBoolReturn, arguments.AttributeSyntaxOpt.Location); + } + } + else if (attribute.IsTargetAttribute(this, AttributeDescription.EnsuresNotNullAttribute)) + { + arguments.GetOrCreateData().HasEnsuresNotNullAttribute = true; + } } private void DecodeDefaultParameterValueAttribute(AttributeDescription description, ref DecodeWellKnownAttributeArguments arguments) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/ThisParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/ThisParameterSymbol.cs index 320f6561c98b7..d664fe1098fd6 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/ThisParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/ThisParameterSymbol.cs @@ -116,12 +116,12 @@ internal override bool IsCallerMemberName internal override bool NotNullWhenFalse { - get { return HasExtraAttribute(AttributeDescription.NotNullWhenFalseAttribute); } + get { return false; } } internal override bool EnsuresNotNull { - get { return HasExtraAttribute(AttributeDescription.EnsuresNotNullAttribute); } + get { return false; } } public override int Ordinal diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index a7ef160a6f997..83695670e950a 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -9000,6 +9000,11 @@ Pokud chcete odstranit toto varování, můžete místo toho použít /reference Converting null literal or possible null value to non-nullable type. + + The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 2c0a00109fb47..0956237fa1318 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -9000,6 +9000,11 @@ Um die Warnung zu beheben, können Sie stattdessen /reference verwenden (Einbett Converting null literal or possible null value to non-nullable type. + + The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index 4e961679401b7..0c426316eff91 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -9000,6 +9000,11 @@ Para eliminar la advertencia puede usar /reference (establezca la propiedad Embe Converting null literal or possible null value to non-nullable type. + + The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 3e1357e789929..a0046a913ac47 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -9000,6 +9000,11 @@ Pour supprimer l'avertissement, vous pouvez utiliser la commande /reference (dé Converting null literal or possible null value to non-nullable type. + + The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 771122be8f266..d852b5e1996a2 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -9000,6 +9000,11 @@ Per rimuovere l'avviso, è invece possibile usare /reference (impostare la propr Converting null literal or possible null value to non-nullable type. + + The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 6bc8ab8882d63..430c5751e806f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -9000,6 +9000,11 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Converting null literal or possible null value to non-nullable type. + + The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index 9fb16366280a5..1ce5e82dfa55f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -9000,6 +9000,11 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Converting null literal or possible null value to non-nullable type. + + The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index 85eec0b0b3b95..5ebe62205a4a0 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -9000,6 +9000,11 @@ Aby usunąć ostrzeżenie, możesz zamiast tego użyć opcji /reference (ustaw w Converting null literal or possible null value to non-nullable type. + + The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 862ee2313cfe6..d219b029dbf25 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -9000,6 +9000,11 @@ Para incorporar informações de tipo de interoperabilidade para os dois assembl Converting null literal or possible null value to non-nullable type. + + The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 9df028701c569..773f7e5fa2691 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -9000,6 +9000,11 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Converting null literal or possible null value to non-nullable type. + + The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index 206e251c06c5f..fe1c446561b0a 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -9000,6 +9000,11 @@ Uyarıyı kaldırmak için, /reference kullanabilirsiniz (Birlikte Çalışma T Converting null literal or possible null value to non-nullable type. + + The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index 07b4cc9ce568f..9afa953371bc9 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -9000,6 +9000,11 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Converting null literal or possible null value to non-nullable type. + + The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 299d1526958f8..feacca3cada52 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -9000,6 +9000,11 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Converting null literal or possible null value to non-nullable type. + + The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs index fc3e5ee3fc8c6..29b2556d47a62 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs @@ -5489,9 +5489,9 @@ public void NotNullWhenFalse() { CSharpCompilation c = CreateCompilation(@" using System.Runtime.CompilerServices; -class C +public class C { - void Main(string? s) + public void Main(string? s) { if (MyIsNullOrEmpty(s)) { @@ -5504,7 +5504,7 @@ void Main(string? s) s.ToString(); // warn 2 } - static bool MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; + public static bool MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; } " + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); @@ -5521,12 +5521,32 @@ void Main(string? s) VerifyNotNullWhenFalse(c, "C.MyIsNullOrEmpty", true); } + [Fact] + public void NotNullWhenFalse_RequiresBoolReturn() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +public class C +{ + public static object MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; +} +" + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (5,43): error CS8627: The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + // public static object MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; + Diagnostic(ErrorCode.ERR_NotNullWhenFalseRequiresBoolReturn, "NotNullWhenFalse").WithLocation(5, 43) + ); + + VerifyNotNullWhenFalseInSource(c, "C.MyIsNullOrEmpty", false); + } + [Fact] public void NotNullWhenFalse_OnTwoParameters() { CSharpCompilation c = CreateCompilation(@" using System.Runtime.CompilerServices; -class C +public class C { void Main(string? s, string? s2) { @@ -5544,7 +5564,7 @@ void Main(string? s, string? s2) s.ToString(); // warn 3 s2.ToString(); // warn 4 } - static bool MyIsNullOrEmpty([NotNullWhenFalse] string? s, [NotNullWhenFalse] string? s2) => throw null; + public static bool MyIsNullOrEmpty([NotNullWhenFalse] string? s, [NotNullWhenFalse] string? s2) => throw null; } " + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); @@ -5705,23 +5725,41 @@ void Main(string? s) Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(15, 9) ); - VerifyNotNullWhenFalse(c, "C.MyIsNullOrEmpty", false); + VerifyNotNullWhenFalseInSource(c, "C.MyIsNullOrEmpty", false); + } + + private static void VerifyNotNullWhenFalseInSource(Compilation compilation, string memberName, params bool[] hasNotNullWhenFalse) + { + Func valueGetter = p => p.NotNullWhenFalse; + VerifyParameterProperties(compilation, memberName, valueGetter, hasNotNullWhenFalse); + } + + private static void VerifyNotNullWhenFalse(Compilation compilation, string memberName, params bool[] hasNotNullWhenFalse) + { + Func valueGetter = p => p.NotNullWhenFalse; + VerifyParameterProperties(compilation, memberName, valueGetter, hasNotNullWhenFalse); + + // Also verify from metadata + var compilation2 = CreateCompilation("", references: new[] { compilation.EmitToImageReference() }); + VerifyParameterProperties(compilation2, memberName, valueGetter, hasNotNullWhenFalse); } - private void VerifyNotNullWhenFalse(Compilation compilation, string memberName, params bool[] hasNotNullWhenFalse) + private static void VerifyParameterProperties(Compilation compilation, string memberName, Func valueGetter, params bool[] expected) { var method = compilation.GetMember(memberName); Assert.True((object)method != null, $"Could not find method '{memberName}'"); - var actual = method.Parameters.Select(p => p.NotNullWhenFalse); - Assert.Equal(hasNotNullWhenFalse, actual); + var actual = method.Parameters.Select(valueGetter); + Assert.Equal(expected, actual); } private void VerifyEnsuresNotNull(Compilation compilation, string memberName, params bool[] hasEnsuresNotNull) { - var method = compilation.GetMember(memberName); - Assert.True((object)method != null, $"Could not find method '{memberName}'"); - var actual = method.Parameters.Select(p => p.EnsuresNotNull); - Assert.Equal(hasEnsuresNotNull, actual); + Func valueGetter = p => p.EnsuresNotNull; + VerifyParameterProperties(compilation, memberName, valueGetter, hasEnsuresNotNull); + + // Also verify from metadata + var compilation2 = CreateCompilation("", references: new[] { compilation.EmitToImageReference() }); + VerifyParameterProperties(compilation2, memberName, valueGetter, hasEnsuresNotNull); } [Fact] @@ -5729,7 +5767,7 @@ public void NotNullWhenFalse_BadAttribute() { CSharpCompilation c = CreateCompilation(@" using System.Runtime.CompilerServices; -class C +public class C { void Main(string? s) { @@ -5742,7 +5780,7 @@ void Main(string? s) s.ToString(); // warn 2 } } - static bool MyIsNullOrEmpty([NotNullWhenFalse(true)] string? s) => throw null; + public static bool MyIsNullOrEmpty([NotNullWhenFalse(true)] string? s) => throw null; } namespace System.Runtime.CompilerServices { @@ -5772,7 +5810,7 @@ public void NotNullWhenFalse_InvertIf() { CSharpCompilation c = CreateCompilation(@" using System.Runtime.CompilerServices; -class C +public class C { void Main(string? s) { @@ -5785,7 +5823,7 @@ void Main(string? s) s.ToString(); // warn } } - static bool MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; + public static bool MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; } " + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); @@ -5821,7 +5859,7 @@ public void NotNullWhenFalse_InstanceMethod() { CSharpCompilation c = CreateCompilation(@" using System.Runtime.CompilerServices; -class C +public class C { void Main(string? s) { @@ -5834,7 +5872,7 @@ void Main(string? s) s.ToString(); // ok } } - bool MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; + public bool MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; } " + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); @@ -5950,7 +5988,7 @@ public class ValueType { } Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(8, 13) ); - VerifyNotNullWhenFalse(c, "System.String.IsNullOrEmpty", true); + VerifyNotNullWhenFalseInSource(c, "System.String.IsNullOrEmpty", true); } [Fact] @@ -5993,7 +6031,7 @@ public class ValueType { } Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(8, 13) ); - VerifyNotNullWhenFalse(c, "System.String.IsNullOrWhiteSpace", true); + VerifyNotNullWhenFalseInSource(c, "System.String.IsNullOrWhiteSpace", true); } [Fact] @@ -6119,14 +6157,23 @@ public partial class C " + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); c.VerifyDiagnostics( + // (8,22): error CS8627: The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + // partial void M2([NotNullWhenFalse] string? s); + Diagnostic(ErrorCode.ERR_NotNullWhenFalseRequiresBoolReturn, "NotNullWhenFalse").WithLocation(8, 22), + // (12,22): error CS8627: The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + // partial void M3([NotNullWhenFalse] string? s) => throw null; + Diagnostic(ErrorCode.ERR_NotNullWhenFalseRequiresBoolReturn, "NotNullWhenFalse").WithLocation(12, 22), // (11,22): error CS0579: Duplicate 'NotNullWhenFalse' attribute // partial void M3([NotNullWhenFalse] string? s); - Diagnostic(ErrorCode.ERR_DuplicateAttribute, "NotNullWhenFalse").WithArguments("NotNullWhenFalse").WithLocation(11, 22) + Diagnostic(ErrorCode.ERR_DuplicateAttribute, "NotNullWhenFalse").WithArguments("NotNullWhenFalse").WithLocation(11, 22), + // (6,22): error CS8627: The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + // partial void M1([NotNullWhenFalse] string? s) => throw null; + Diagnostic(ErrorCode.ERR_NotNullWhenFalseRequiresBoolReturn, "NotNullWhenFalse").WithLocation(6, 22) ); - VerifyNotNullWhenFalse(c, "C.M1", false); - VerifyNotNullWhenFalse(c, "C.M2", true); - VerifyNotNullWhenFalse(c, "C.M3", true); + VerifyNotNullWhenFalseInSource(c, "C.M1", false); + VerifyNotNullWhenFalseInSource(c, "C.M2", false); + VerifyNotNullWhenFalseInSource(c, "C.M3", false); } [Fact] @@ -6134,7 +6181,7 @@ public void NotNullWhenFalse_ReturningDynamic() { CSharpCompilation c = CreateCompilation(@" using System.Runtime.CompilerServices; -class C +public class C { void Main(string? s) { @@ -6147,17 +6194,104 @@ void Main(string? s) s.ToString(); // ok } } - dynamic MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; + public dynamic MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; } " + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); c.VerifyDiagnostics( + // (16,37): error CS8627: The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + // public dynamic MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; + Diagnostic(ErrorCode.ERR_NotNullWhenFalseRequiresBoolReturn, "NotNullWhenFalse").WithLocation(16, 37), // (9,13): warning CS8602: Possible dereference of a null reference. // s.ToString(); // warn Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(9, 13) ); - VerifyNotNullWhenFalse(c, "C.MyIsNullOrEmpty", true); + VerifyNotNullWhenFalseInSource(c, "C.MyIsNullOrEmpty", false); + } + + [Fact] + public void NotNullWhenFalse_ReturningObject_FromMetadata() + { + string il = @" +.class private auto ansi sealed beforefieldinit System.Runtime.CompilerServices.NullableAttribute + extends [mscorlib]System.Attribute +{ + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + IL_0000: ldnull + IL_0001: throw + } +} + +.class public auto ansi beforefieldinit C + extends [mscorlib]System.Object +{ + .method public hidebysig + instance object MyIsNullOrEmpty (string s) cil managed + { + .param [1] + .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor() = ( + 01 00 00 00 + ) + .custom instance void System.Runtime.CompilerServices.NotNullWhenFalseAttribute::.ctor() = ( + 01 00 00 00 + ) + + IL_0000: ldnull + IL_0001: throw + } + + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + IL_0000: ldnull + IL_0001: throw + } +} + +.class public auto ansi beforefieldinit System.Runtime.CompilerServices.NotNullWhenFalseAttribute + extends [mscorlib]System.Attribute +{ + .custom instance void [mscorlib]System.AttributeUsageAttribute::.ctor(valuetype [mscorlib]System.AttributeTargets) = ( + 01 00 00 08 00 00 01 00 54 02 0d 41 6c 6c 6f 77 4d 75 6c 74 69 70 6c 65 00 + ) + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + IL_0000: ldnull + IL_0001: throw + } +} +"; + string source = @" +public class D +{ + void Main(C c, string? s) + { + if ((bool)c.MyIsNullOrEmpty(s)) + { + s.ToString(); // warn 1 + } + else + { + s.ToString(); // warn 2 + } + } +} +"; + var compilation = CreateCompilationWithIL(source, il, parseOptions: TestOptions.Regular8); + compilation.VerifyDiagnostics( + // (8,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(8, 13), + // (12,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(12, 13) + ); + + VerifyNotNullWhenFalseInSource(compilation, "C.MyIsNullOrEmpty", true); } [Fact] @@ -6177,6 +6311,9 @@ void Main(string? s) " + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); c.VerifyDiagnostics( + // (10,29): error CS8627: The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + // object MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; + Diagnostic(ErrorCode.ERR_NotNullWhenFalseRequiresBoolReturn, "NotNullWhenFalse").WithLocation(10, 29), // (8,9): warning CS8602: Possible dereference of a null reference. // s.ToString(); // warn Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(8, 9) @@ -6188,7 +6325,7 @@ public void NotNullWhenFalse_FollowedByEnsuresNotNull() { CSharpCompilation c = CreateCompilation(@" using System.Runtime.CompilerServices; -class C +public class C { void Main(string? s) { @@ -6203,7 +6340,7 @@ void Main(string? s) s.ToString(); // ok } - static bool MyIsNullOrEmpty([NotNullWhenFalse] string? s, [EnsuresNotNull] string? s2) => throw null; + public static bool MyIsNullOrEmpty([NotNullWhenFalse] string? s, [EnsuresNotNull] string? s2) => throw null; } " + NotNullWhenFalseAttributeDefinition + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); @@ -6218,7 +6355,7 @@ public void NotNullWhenFalse_AndEnsuresNotNull() { CSharpCompilation c = CreateCompilation(@" using System.Runtime.CompilerServices; -class C +public class C { void Main(string? s) { @@ -6233,7 +6370,7 @@ void Main(string? s) s.ToString(); // ok } - static bool MyIsNullOrEmpty([NotNullWhenFalse, EnsuresNotNull] string? s) => throw null; + public static bool MyIsNullOrEmpty([NotNullWhenFalse, EnsuresNotNull] string? s) => throw null; } " + NotNullWhenFalseAttributeDefinition + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); @@ -6248,14 +6385,14 @@ public void EnsuresNotNull() { CSharpCompilation c = CreateCompilation(@" using System.Runtime.CompilerServices; -class C +public class C { void Main(string? s) { ThrowIfNull(42, s); s.ToString(); // ok } - static void ThrowIfNull(int x, [EnsuresNotNull] string? s) => throw null; + public static void ThrowIfNull(int x, [EnsuresNotNull] string? s) => throw null; } " + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); @@ -6269,14 +6406,14 @@ public void EnsuresNotNull_GenericMethod() { CSharpCompilation c = CreateCompilation(@" using System.Runtime.CompilerServices; -class C +public class C { void Main(string? s) { ThrowIfNull(s); s.ToString(); // ok } - static void ThrowIfNull([EnsuresNotNull] T s) => throw null; + public static void ThrowIfNull([EnsuresNotNull] T s) => throw null; } " + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); @@ -6310,14 +6447,14 @@ public void EnsuresNotNull_SecondArgumentDereferences() { CSharpCompilation c = CreateCompilation(@" using System.Runtime.CompilerServices; -class C +public class C { void Main(string? s) { ThrowIfNull(s, s.ToString()); // warn s.ToString(); // ok } - static void ThrowIfNull([EnsuresNotNull] string? s, string s2) => throw null; + public static void ThrowIfNull([EnsuresNotNull] string? s, string s2) => throw null; } " + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); diff --git a/src/Compilers/Core/Portable/Symbols/Attributes/CommonParameterEarlyWellKnownAttributeData.cs b/src/Compilers/Core/Portable/Symbols/Attributes/CommonParameterEarlyWellKnownAttributeData.cs index f240d501d04e2..fb5e65a584bc8 100644 --- a/src/Compilers/Core/Portable/Symbols/Attributes/CommonParameterEarlyWellKnownAttributeData.cs +++ b/src/Compilers/Core/Portable/Symbols/Attributes/CommonParameterEarlyWellKnownAttributeData.cs @@ -77,37 +77,5 @@ public bool HasCallerMemberNameAttribute } } #endregion - - private bool _hasNotNullWhenFalseAttribute; - public bool HasNotNullWhenFalseAttribute - { - get - { - VerifySealed(expected: true); - return _hasNotNullWhenFalseAttribute; - } - set - { - VerifySealed(expected: false); - _hasNotNullWhenFalseAttribute = value; - SetDataStored(); - } - } - - private bool _hasEnsuresNotNullAttribute; - public bool HasEnsuresNotNullAttribute - { - get - { - VerifySealed(expected: true); - return _hasEnsuresNotNullAttribute; - } - set - { - VerifySealed(expected: false); - _hasEnsuresNotNullAttribute = value; - SetDataStored(); - } - } } } diff --git a/src/Compilers/Core/Portable/Symbols/Attributes/CommonParameterWellKnownAttributeData.cs b/src/Compilers/Core/Portable/Symbols/Attributes/CommonParameterWellKnownAttributeData.cs index 74cc508d9fa43..acfee1e3718ce 100644 --- a/src/Compilers/Core/Portable/Symbols/Attributes/CommonParameterWellKnownAttributeData.cs +++ b/src/Compilers/Core/Portable/Symbols/Attributes/CommonParameterWellKnownAttributeData.cs @@ -109,5 +109,37 @@ public bool HasIUnknownConstantAttribute } } #endregion + + private bool _hasNotNullWhenFalseAttribute; + public bool HasNotNullWhenFalseAttribute + { + get + { + VerifySealed(expected: true); + return _hasNotNullWhenFalseAttribute; + } + set + { + VerifySealed(expected: false); + _hasNotNullWhenFalseAttribute = value; + SetDataStored(); + } + } + + private bool _hasEnsuresNotNullAttribute; + public bool HasEnsuresNotNullAttribute + { + get + { + VerifySealed(expected: true); + return _hasEnsuresNotNullAttribute; + } + set + { + VerifySealed(expected: false); + _hasEnsuresNotNullAttribute = value; + SetDataStored(); + } + } } } diff --git a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs index 046b8bee9ab17..b61dacbc377eb 100644 --- a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs +++ b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs @@ -306,7 +306,8 @@ public static CSharpCompilation CreateCompilationWithIL( string ilSource, IEnumerable references = null, CSharpCompilationOptions options = null, - bool appendDefaultHeader = true) => CreateCompilationWithILAndMscorlib40(source, ilSource, TargetFramework.Standard, references, options, appendDefaultHeader); + CSharpParseOptions parseOptions = null, + bool appendDefaultHeader = true) => CreateCompilationWithILAndMscorlib40(source, ilSource, TargetFramework.Standard, references, options, parseOptions, appendDefaultHeader); public static CSharpCompilation CreateCompilationWithILAndMscorlib40( CSharpTestSource source, @@ -314,11 +315,12 @@ public static CSharpCompilation CreateCompilationWithILAndMscorlib40( TargetFramework targetFramework = TargetFramework.Mscorlib40, IEnumerable references = null, CSharpCompilationOptions options = null, + CSharpParseOptions parseOptions = null, bool appendDefaultHeader = true) { MetadataReference ilReference = CompileIL(ilSource, appendDefaultHeader); var allReferences = TargetFrameworkUtil.GetReferences(targetFramework, references).Add(ilReference); - return CreateEmptyCompilation(source, allReferences, options); + return CreateEmptyCompilation(source, allReferences, options, parseOptions); } public static CSharpCompilation CreateCompilationWithMscorlib40( From 78402d92bf87ca6752bbb20c7a614ba009fcdbb4 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Thu, 10 May 2018 13:10:14 -0700 Subject: [PATCH 08/18] Use enum. Annotations win --- .../Portable/FlowAnalysis/NullableWalker.cs | 37 +++++--- .../Portable/Symbols/ExtraAnnotations.cs | 49 ++++++++--- .../Symbols/Metadata/PE/PEParameterSymbol.cs | 50 ++++++----- .../Portable/Symbols/ParameterSymbol.cs | 37 +------- .../Symbols/SignatureOnlyParameterSymbol.cs | 4 +- .../Source/SourceClonedParameterSymbol.cs | 9 +- .../Source/SourceComplexParameterSymbol.cs | 22 +++-- .../Source/SourceSimpleParameterSymbol.cs | 13 ++- .../Symbols/Source/ThisParameterSymbol.cs | 9 +- .../Synthesized/SynthesizedParameterSymbol.cs | 9 +- .../Symbols/Wrapped/WrappedParameterSymbol.cs | 9 +- .../Semantic/Semantics/StaticNullChecking.cs | 84 ++++++++----------- 12 files changed, 155 insertions(+), 177 deletions(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 72abb3308a8db..3411c487ee5d7 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -1617,30 +1617,39 @@ public override BoundNode VisitCall(BoundCall node) /// For each argument, figure out if its corresponding parameter is annotated with NotNullWhenFalse or /// EnsuresNotNull. /// - private static ImmutableArray<(bool NotNullWhenFalse, bool EnsuresNotNull)> GetAnnotations(int numArguments, + private static ImmutableArray GetAnnotations(int numArguments, bool expanded, ImmutableArray parameters, ImmutableArray argsToParamsOpt) { - ArrayBuilder<(bool NotNullWhenFalse, bool EnsuresNotNull)> builder = null; + ArrayBuilder builder = null; for (int i = 0; i < numArguments; i++) { (ParameterSymbol parameter, _) = GetCorrespondingParameter(i, parameters, argsToParamsOpt, expanded); + AttributeAnnotations annotations = parameter?.FlowAnalysisAnnotations ?? AttributeAnnotations.None; // We'll ignore NotNullWhenFalse that is misused in metadata - bool notNullWhenFalse = parameter?.NotNullWhenFalse == true && - parameter.ContainingSymbol.GetTypeOrReturnType().SpecialType == SpecialType.System_Boolean; + if ((annotations & AttributeAnnotations.NotNullWhenFalse) != 0 && + parameter.ContainingSymbol.GetTypeOrReturnType().SpecialType != SpecialType.System_Boolean) + { + annotations &= ~AttributeAnnotations.NotNullWhenFalse; + } - bool ensuresNotNull = parameter?.EnsuresNotNull == true; + // We'll ignore EnsuresNotNull that is misused in metadata + if ((annotations & AttributeAnnotations.EnsuresNotNull) != 0 && + parameter.Type?.IsReferenceType == false) + { + annotations &= ~AttributeAnnotations.EnsuresNotNull; + } - if ((notNullWhenFalse || ensuresNotNull) && builder == null) + if (annotations != AttributeAnnotations.None && builder == null) { - builder = ArrayBuilder<(bool, bool)>.GetInstance(numArguments); - builder.AddMany((false, false), i); + builder = ArrayBuilder.GetInstance(numArguments); + builder.AddMany(AttributeAnnotations.None, i); } if (builder != null) { - builder.Add((notNullWhenFalse, ensuresNotNull)); + builder.Add(annotations); } } @@ -1727,7 +1736,7 @@ private ImmutableArray VisitArgumentsEvaluate( // We do a second pass through the arguments, ignoring any diagnostics produced, but honoring the annotations, // to get the proper result state. - ImmutableArray<(bool notNullWhenFalse, bool ensuresNotNull)> annotations = GetAnnotations(arguments.Length, + ImmutableArray annotations = GetAnnotations(arguments.Length, expanded, parameters, argsToParamsOpt); if (!annotations.IsDefault) @@ -1784,7 +1793,7 @@ private void VisitArgumentEvaluate(ImmutableArray arguments, Im private void VisitArgumentsEvaluateHonoringAnnotations( ImmutableArray arguments, ImmutableArray refKindsOpt, - ImmutableArray<(bool notNullWhenFalse, bool ensuresNotNull)> annotations) + ImmutableArray annotations) { Debug.Assert(!IsConditionalState); @@ -1835,7 +1844,9 @@ private void VisitArgumentsEvaluateHonoringAnnotations( continue; } - (bool notNullWhenFalse, bool ensuresNotNull) = annotations[i]; + AttributeAnnotations annotation = annotations[i]; + bool notNullWhenFalse = (annotation & AttributeAnnotations.NotNullWhenFalse) != 0; + bool ensuresNotNull = (annotation & AttributeAnnotations.EnsuresNotNull) != 0; if (ensuresNotNull) { @@ -3585,7 +3596,7 @@ public override BoundNode VisitThrowExpression(BoundThrowExpression node) return result; } -#endregion Visitors + #endregion Visitors protected override string Dump(LocalState state) { diff --git a/src/Compilers/CSharp/Portable/Symbols/ExtraAnnotations.cs b/src/Compilers/CSharp/Portable/Symbols/ExtraAnnotations.cs index 1b03ce2fcc692..021ac7665d690 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ExtraAnnotations.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ExtraAnnotations.cs @@ -1,23 +1,29 @@ // 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.Text; using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; using Microsoft.CodeAnalysis.PooledObjects; +using static Microsoft.CodeAnalysis.CSharp.Symbols.AttributeAnnotations; namespace Microsoft.CodeAnalysis.CSharp.Symbols { + [Flags] + internal enum AttributeAnnotations + { + None = 0, + NotNullWhenFalse, + EnsuresNotNull, + } + // PROTOTYPE(NullableReferenceTypes): external annotations should be removed or fully designed/productized // If we choose to stick with an ad-hoc key (rather than annotations as source or as PE/ref assembly), // we should consider the assembly qualified name format used in metadata (with backticks and such). internal static class ExtraAnnotations { - private static readonly ImmutableArray EnsuresNotNull = ImmutableArray.Create(AttributeDescription.EnsuresNotNullAttribute); - - private static readonly ImmutableArray NotNullWhenFalse = ImmutableArray.Create(AttributeDescription.NotNullWhenFalseAttribute); - // APIs that are useful to annotate: // 1) don't accept null input // 2) return a reference type @@ -37,8 +43,8 @@ internal static class ExtraAnnotations { "System.String System.String.Concat(System.String, System.String)", Array(Nullable(false), Nullable(true), Nullable(true)) }, }.ToImmutableDictionary(); - private static readonly ImmutableDictionary>> Attributes = - new Dictionary>> + private static readonly ImmutableDictionary> Attributes = + new Dictionary> { { "System.Boolean System.String.IsNullOrEmpty(System.String)", Array(default, NotNullWhenFalse) }, { "System.Boolean System.String.IsNullOrWhiteSpace(System.String)", Array(default, NotNullWhenFalse) }, @@ -123,7 +129,7 @@ internal static string MakeMethodKey(MethodSymbol method) private static ImmutableArray> Array(params ImmutableArray[] values) => values.ToImmutableArray(); - private static ImmutableArray> Array(params ImmutableArray[] values) + private static ImmutableArray Array(params AttributeAnnotations[] values) => values.ToImmutableArray(); private static ImmutableArray Nullable(params bool[] values) @@ -160,20 +166,37 @@ private static void Add(TypeSymbol type, StringBuilder builder) /// index 0 is used for return type /// other parameters follow /// - internal static ImmutableArray GetExtraAttributes(string key, int parameterIndex) + internal static (bool hasAny, AttributeAnnotations annotations) GetExtraAttributes(string key, int parameterIndex) { if (key is null) { - return default; + return (false, default); } - _ = Attributes.TryGetValue(key, out var extraAttributes); - if (extraAttributes.IsDefault) + if (!Attributes.TryGetValue(key, out var extraAttributes)) { - return default; + return (false, default); + } + + return (true, extraAttributes[parameterIndex + 1]); + } + } + + internal static class ParameterAnnotationsExtensions + { + internal static AttributeAnnotations With(this AttributeAnnotations value, bool notNullWhenFalse, bool ensuresNotNull) + { + if (notNullWhenFalse) + { + value |= NotNullWhenFalse; + } + + if (ensuresNotNull) + { + value |= EnsuresNotNull; } - return extraAttributes[parameterIndex + 1]; + return value; } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs index 5589608e3adc6..dcc40d089c8ae 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs @@ -647,37 +647,41 @@ internal override bool IsCallerMemberName } } - internal override bool NotNullWhenFalse + internal override AttributeAnnotations FlowAnalysisAnnotations { get { - const WellKnownAttributeFlags flag = WellKnownAttributeFlags.NotNullWhenFalse; + const WellKnownAttributeFlags notNullWhenFalse = WellKnownAttributeFlags.NotNullWhenFalse; + const WellKnownAttributeFlags ensuresNotNull = WellKnownAttributeFlags.EnsuresNotNull; - bool value; - if (!_packedFlags.TryGetWellKnownAttribute(flag, out value)) + // PROTOTYPE(NullableReferenceTypes): the flags could be packed more + if (!_packedFlags.TryGetWellKnownAttribute(notNullWhenFalse, out bool hasNotNullWhenFalse) || + !_packedFlags.TryGetWellKnownAttribute(ensuresNotNull, out bool hasEnsuresNotNull)) { - value = _packedFlags.SetWellKnownAttribute(flag, - _moduleSymbol.Module.HasAttribute(_handle, AttributeDescription.NotNullWhenFalseAttribute) || - HasExtraAttribute(AttributeDescription.NotNullWhenFalseAttribute)); - } - return value; - } - } + (bool memberHasAny, AttributeAnnotations annotations) = TryGetExtraAttributeAnnotations(); - internal override bool EnsuresNotNull - { - get - { - const WellKnownAttributeFlags flag = WellKnownAttributeFlags.EnsuresNotNull; + if (memberHasAny) + { + // External annotations win, if any is present on the member + hasNotNullWhenFalse = (annotations & AttributeAnnotations.NotNullWhenFalse) != 0; + hasEnsuresNotNull = (annotations & AttributeAnnotations.EnsuresNotNull) != 0; + } + else + { + hasNotNullWhenFalse = _moduleSymbol.Module.HasAttribute(_handle, AttributeDescription.NotNullWhenFalseAttribute); + hasEnsuresNotNull = _moduleSymbol.Module.HasAttribute(_handle, AttributeDescription.EnsuresNotNullAttribute); + } - bool value; - if (!_packedFlags.TryGetWellKnownAttribute(flag, out value)) - { - value = _packedFlags.SetWellKnownAttribute(flag, - _moduleSymbol.Module.HasAttribute(_handle, AttributeDescription.EnsuresNotNullAttribute) || - HasExtraAttribute(AttributeDescription.EnsuresNotNullAttribute)); + _packedFlags.SetWellKnownAttribute(notNullWhenFalse, hasNotNullWhenFalse); + _packedFlags.SetWellKnownAttribute(ensuresNotNull, hasEnsuresNotNull); + + if (memberHasAny) + { + return annotations; + } } - return value; + + return AttributeAnnotations.None.With(notNullWhenFalse: hasNotNullWhenFalse, ensuresNotNull: hasEnsuresNotNull); } } diff --git a/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs index 0e6498f373bf8..e17e84388923a 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs @@ -385,49 +385,20 @@ internal sealed override ObsoleteAttributeData ObsoleteAttributeData internal abstract bool IsCallerMemberName { get; } - /// - /// If the method returns false, then we can infer that the argument used for this parameter was not null. - /// - internal abstract bool NotNullWhenFalse { get; } - - /// - /// When the method returns, then we can infer that the argument used for this parameter was not null. - /// - internal abstract bool EnsuresNotNull { get; } + internal abstract AttributeAnnotations FlowAnalysisAnnotations { get; } - protected bool HasExtraAttribute(AttributeDescription description) + protected (bool memberHasExtra, AttributeAnnotations annotations) TryGetExtraAttributeAnnotations() { ParameterSymbol originalParameter = this.OriginalDefinition; var containingMethod = originalParameter.ContainingSymbol as MethodSymbol; if (containingMethod is null) { - return false; + return (false, AttributeAnnotations.None); } string key = ExtraAnnotations.MakeMethodKey(containingMethod); - ImmutableArray extraAttributes = ExtraAnnotations.GetExtraAttributes(key, this.Ordinal); - - if (!extraAttributes.IsDefault) - { - foreach (var attribute in extraAttributes) - { - if (equals(attribute, description)) - { - return true; - } - } - } - - return false; - - bool equals(AttributeDescription first, AttributeDescription second) - { - return first.Namespace == second.Namespace && - first.Name == second.Name && - ReferenceEquals(first.Signatures, second.Signatures) && - first.MatchIgnoringCase == second.MatchIgnoringCase; - } + return ExtraAnnotations.GetExtraAttributes(key, this.Ordinal); } protected sealed override int HighestPriorityUseSiteError diff --git a/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyParameterSymbol.cs index 9a563be35ce38..926d0121d9ef4 100644 --- a/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyParameterSymbol.cs @@ -71,9 +71,7 @@ public override bool IsImplicitlyDeclared internal override bool IsCallerMemberName { get { throw ExceptionUtilities.Unreachable; } } - internal override bool NotNullWhenFalse { get { throw ExceptionUtilities.Unreachable; } } - - internal override bool EnsuresNotNull { get { throw ExceptionUtilities.Unreachable; } } + internal override AttributeAnnotations FlowAnalysisAnnotations { get { throw ExceptionUtilities.Unreachable; } } public override Symbol ContainingSymbol { get { throw ExceptionUtilities.Unreachable; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs index 4285d498af590..d8ae0ebdbbffc 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs @@ -151,14 +151,9 @@ internal override bool IsCallerMemberName get { return _originalParam.IsCallerMemberName; } } - internal override bool NotNullWhenFalse + internal override AttributeAnnotations FlowAnalysisAnnotations { - get { return _originalParam.NotNullWhenFalse; } - } - - internal override bool EnsuresNotNull - { - get { return _originalParam.EnsuresNotNull; } + get { return _originalParam.FlowAnalysisAnnotations; } } #endregion diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs index 792a6a2ac42fa..dbd4f5f89a1f8 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs @@ -127,13 +127,23 @@ private bool HasCallerMemberNameAttribute && !HasCallerFilePathAttribute && HasCallerMemberNameAttribute; - internal override bool NotNullWhenFalse - => GetDecodedWellKnownAttributeData()?.HasNotNullWhenFalseAttribute == true || - HasExtraAttribute(AttributeDescription.NotNullWhenFalseAttribute); + internal override AttributeAnnotations FlowAnalysisAnnotations + { + get + { + (bool memberHasAny, AttributeAnnotations annotations) = TryGetExtraAttributeAnnotations(); + if (memberHasAny) + { + return annotations; + } + + CommonParameterWellKnownAttributeData attributeData = GetDecodedWellKnownAttributeData(); - internal override bool EnsuresNotNull - => GetDecodedWellKnownAttributeData()?.HasEnsuresNotNullAttribute == true || - HasExtraAttribute(AttributeDescription.EnsuresNotNullAttribute); + return AttributeAnnotations.None + .With(notNullWhenFalse: attributeData?.HasNotNullWhenFalseAttribute == true, + ensuresNotNull: attributeData?.HasEnsuresNotNullAttribute == true); + } + } private ConstantValue DefaultSyntaxValue { diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceSimpleParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceSimpleParameterSymbol.cs index 1810662716f3e..176c2e34ace97 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceSimpleParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceSimpleParameterSymbol.cs @@ -82,14 +82,13 @@ internal override bool IsCallerMemberName get { return false; } } - internal override bool NotNullWhenFalse + internal override AttributeAnnotations FlowAnalysisAnnotations { - get { return HasExtraAttribute(AttributeDescription.NotNullWhenFalseAttribute); } - } - - internal override bool EnsuresNotNull - { - get { return HasExtraAttribute(AttributeDescription.EnsuresNotNullAttribute); } + get + { + (_, AttributeAnnotations annotations) = TryGetExtraAttributeAnnotations(); + return annotations; + } } internal override MarshalPseudoCustomAttributeData MarshallingInformation diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/ThisParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/ThisParameterSymbol.cs index d664fe1098fd6..d6ab91dc3a47f 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/ThisParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/ThisParameterSymbol.cs @@ -114,14 +114,9 @@ internal override bool IsCallerMemberName get { return false; } } - internal override bool NotNullWhenFalse + internal override AttributeAnnotations FlowAnalysisAnnotations { - get { return false; } - } - - internal override bool EnsuresNotNull - { - get { return false; } + get { return AttributeAnnotations.None; } } public override int Ordinal diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedParameterSymbol.cs index c3129733d364b..fc006dece1250 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedParameterSymbol.cs @@ -115,14 +115,9 @@ internal override bool IsCallerMemberName get { return false; } } - internal override bool NotNullWhenFalse + internal override AttributeAnnotations FlowAnalysisAnnotations { - get { return false; } - } - - internal override bool EnsuresNotNull - { - get { return false; } + get { return AttributeAnnotations.None; } } public override Symbol ContainingSymbol diff --git a/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedParameterSymbol.cs index 4f6b175cef7da..84a20f99c1936 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedParameterSymbol.cs @@ -153,14 +153,9 @@ internal override bool IsCallerMemberName get { return _underlyingParameter.IsCallerMemberName; } } - internal override bool NotNullWhenFalse + internal override AttributeAnnotations FlowAnalysisAnnotations { - get { return _underlyingParameter.NotNullWhenFalse; } - } - - internal override bool EnsuresNotNull - { - get { return _underlyingParameter.EnsuresNotNull; } + get { return _underlyingParameter.FlowAnalysisAnnotations; } } public override string GetDocumentationCommentXml(CultureInfo preferredCulture = null, bool expandIncludes = false, CancellationToken cancellationToken = default) diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs index 29b2556d47a62..af99293e4424c 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; +using static Microsoft.CodeAnalysis.CSharp.Symbols.AttributeAnnotations; namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Semantics { @@ -5485,7 +5486,7 @@ where n < 5 } [Fact] - public void NotNullWhenFalse() + public void NotNullWhenFalse_Simple() { CSharpCompilation c = CreateCompilation(@" using System.Runtime.CompilerServices; @@ -5517,8 +5518,8 @@ public void Main(string? s) Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(16, 9) ); - VerifyNotNullWhenFalse(c, "C.Main", false); - VerifyNotNullWhenFalse(c, "C.MyIsNullOrEmpty", true); + VerifyAnnotations(c, "C.Main", None); + VerifyAnnotations(c, "C.MyIsNullOrEmpty", NotNullWhenFalse); } [Fact] @@ -5538,7 +5539,7 @@ public class C Diagnostic(ErrorCode.ERR_NotNullWhenFalseRequiresBoolReturn, "NotNullWhenFalse").WithLocation(5, 43) ); - VerifyNotNullWhenFalseInSource(c, "C.MyIsNullOrEmpty", false); + VerifyAnnotationsInSource(c, "C.MyIsNullOrEmpty", None); } [Fact] @@ -5583,7 +5584,7 @@ void Main(string? s, string? s2) Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s2").WithLocation(19, 9) ); - VerifyNotNullWhenFalse(c, "C.MyIsNullOrEmpty", true, true); + VerifyAnnotations(c, "C.MyIsNullOrEmpty", NotNullWhenFalse, NotNullWhenFalse); } [Fact] @@ -5725,41 +5726,24 @@ void Main(string? s) Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(15, 9) ); - VerifyNotNullWhenFalseInSource(c, "C.MyIsNullOrEmpty", false); + VerifyAnnotationsInSource(c, "C.MyIsNullOrEmpty", None); } - private static void VerifyNotNullWhenFalseInSource(Compilation compilation, string memberName, params bool[] hasNotNullWhenFalse) - { - Func valueGetter = p => p.NotNullWhenFalse; - VerifyParameterProperties(compilation, memberName, valueGetter, hasNotNullWhenFalse); - } - - private static void VerifyNotNullWhenFalse(Compilation compilation, string memberName, params bool[] hasNotNullWhenFalse) - { - Func valueGetter = p => p.NotNullWhenFalse; - VerifyParameterProperties(compilation, memberName, valueGetter, hasNotNullWhenFalse); - - // Also verify from metadata - var compilation2 = CreateCompilation("", references: new[] { compilation.EmitToImageReference() }); - VerifyParameterProperties(compilation2, memberName, valueGetter, hasNotNullWhenFalse); - } - - private static void VerifyParameterProperties(Compilation compilation, string memberName, Func valueGetter, params bool[] expected) + private static void VerifyAnnotationsInSource(Compilation compilation, string memberName, params AttributeAnnotations[] expected) { var method = compilation.GetMember(memberName); Assert.True((object)method != null, $"Could not find method '{memberName}'"); - var actual = method.Parameters.Select(valueGetter); + var actual = method.Parameters.Select(p => p.FlowAnalysisAnnotations); Assert.Equal(expected, actual); } - private void VerifyEnsuresNotNull(Compilation compilation, string memberName, params bool[] hasEnsuresNotNull) + private void VerifyAnnotations(Compilation compilation, string memberName, params AttributeAnnotations[] expected) { - Func valueGetter = p => p.EnsuresNotNull; - VerifyParameterProperties(compilation, memberName, valueGetter, hasEnsuresNotNull); + VerifyAnnotationsInSource(compilation, memberName, expected); // Also verify from metadata var compilation2 = CreateCompilation("", references: new[] { compilation.EmitToImageReference() }); - VerifyParameterProperties(compilation2, memberName, valueGetter, hasEnsuresNotNull); + VerifyAnnotationsInSource(compilation2, memberName, expected); } [Fact] @@ -5802,7 +5786,7 @@ public NotNullWhenFalseAttribute(bool bad) { } Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(13, 13) ); - VerifyNotNullWhenFalse(c, "C.MyIsNullOrEmpty", false); + VerifyAnnotations(c, "C.MyIsNullOrEmpty", None); } [Fact] @@ -5833,7 +5817,7 @@ void Main(string? s) Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(13, 13) ); - VerifyNotNullWhenFalse(c, "C.MyIsNullOrEmpty", true); + VerifyAnnotations(c, "C.MyIsNullOrEmpty", NotNullWhenFalse); } [Fact] @@ -5882,7 +5866,7 @@ void Main(string? s) Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(9, 13) ); - VerifyNotNullWhenFalse(c, "C.MyIsNullOrEmpty", true); + VerifyAnnotations(c, "C.MyIsNullOrEmpty", NotNullWhenFalse); } [Fact] @@ -5916,7 +5900,7 @@ public static class Extension Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(9, 13) ); - VerifyNotNullWhenFalse(c, "Extension.MyIsNullOrEmpty", false, true); + VerifyAnnotations(c, "Extension.MyIsNullOrEmpty", None, NotNullWhenFalse); } [Fact] @@ -5945,7 +5929,7 @@ void Main(string? s) Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(8, 13) ); - VerifyNotNullWhenFalse(c, "System.String.IsNullOrEmpty", true); + VerifyAnnotations(c, "System.String.IsNullOrEmpty", NotNullWhenFalse); } [Fact] @@ -5988,7 +5972,7 @@ public class ValueType { } Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(8, 13) ); - VerifyNotNullWhenFalseInSource(c, "System.String.IsNullOrEmpty", true); + VerifyAnnotationsInSource(c, "System.String.IsNullOrEmpty", NotNullWhenFalse); } [Fact] @@ -6031,7 +6015,7 @@ public class ValueType { } Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(8, 13) ); - VerifyNotNullWhenFalseInSource(c, "System.String.IsNullOrWhiteSpace", true); + VerifyAnnotationsInSource(c, "System.String.IsNullOrWhiteSpace", NotNullWhenFalse); } [Fact] @@ -6060,7 +6044,7 @@ void Main(string? s) Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(12, 13) ); - VerifyNotNullWhenFalse(c, "System.String.IsNullOrEmpty", true); + VerifyAnnotations(c, "System.String.IsNullOrEmpty", NotNullWhenFalse); } [Fact] @@ -6135,7 +6119,7 @@ void Main(string? s) Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(8, 13) ); - VerifyNotNullWhenFalse(c, "System.String.IsNullOrWhiteSpace", true); + VerifyAnnotations(c, "System.String.IsNullOrWhiteSpace", NotNullWhenFalse); } [Fact] @@ -6171,9 +6155,9 @@ public partial class C Diagnostic(ErrorCode.ERR_NotNullWhenFalseRequiresBoolReturn, "NotNullWhenFalse").WithLocation(6, 22) ); - VerifyNotNullWhenFalseInSource(c, "C.M1", false); - VerifyNotNullWhenFalseInSource(c, "C.M2", false); - VerifyNotNullWhenFalseInSource(c, "C.M3", false); + VerifyAnnotationsInSource(c, "C.M1", None); + VerifyAnnotationsInSource(c, "C.M2", None); + VerifyAnnotationsInSource(c, "C.M3", None); } [Fact] @@ -6207,7 +6191,7 @@ void Main(string? s) Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(9, 13) ); - VerifyNotNullWhenFalseInSource(c, "C.MyIsNullOrEmpty", false); + VerifyAnnotationsInSource(c, "C.MyIsNullOrEmpty", None); } [Fact] @@ -6291,7 +6275,7 @@ void Main(C c, string? s) Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(12, 13) ); - VerifyNotNullWhenFalseInSource(compilation, "C.MyIsNullOrEmpty", true); + VerifyAnnotationsInSource(compilation, "C.MyIsNullOrEmpty", NotNullWhenFalse); } [Fact] @@ -6346,8 +6330,7 @@ void Main(string? s) c.VerifyDiagnostics(); - VerifyNotNullWhenFalse(c, "C.MyIsNullOrEmpty", true, false); - VerifyEnsuresNotNull(c, "C.MyIsNullOrEmpty", false, true); + VerifyAnnotations(c, "C.MyIsNullOrEmpty", NotNullWhenFalse, EnsuresNotNull); } [Fact] @@ -6376,12 +6359,11 @@ void Main(string? s) c.VerifyDiagnostics(); - VerifyNotNullWhenFalse(c, "C.MyIsNullOrEmpty", true); - VerifyEnsuresNotNull(c, "C.MyIsNullOrEmpty", true); + VerifyAnnotations(c, "C.MyIsNullOrEmpty", NotNullWhenFalse | EnsuresNotNull); } [Fact] - public void EnsuresNotNull() + public void EnsuresNotNull_Simple() { CSharpCompilation c = CreateCompilation(@" using System.Runtime.CompilerServices; @@ -6398,7 +6380,7 @@ void Main(string? s) c.VerifyDiagnostics(); - VerifyEnsuresNotNull(c, "C.ThrowIfNull", false, true); + VerifyAnnotations(c, "C.ThrowIfNull", None, EnsuresNotNull); } [Fact] @@ -6419,7 +6401,7 @@ void Main(string? s) c.VerifyDiagnostics(); - VerifyEnsuresNotNull(c, "C.ThrowIfNull", true); + VerifyAnnotations(c, "C.ThrowIfNull", EnsuresNotNull); } [Fact] @@ -6464,7 +6446,7 @@ void Main(string? s) Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(7, 24) ); - VerifyEnsuresNotNull(c, "C.ThrowIfNull", true, false); + VerifyAnnotations(c, "C.ThrowIfNull", EnsuresNotNull, None); } [Fact] @@ -6506,7 +6488,7 @@ void Main(string? s) c.VerifyDiagnostics(); - VerifyEnsuresNotNull(c, "System.String.Contains", true); + VerifyAnnotations(c, "System.String.Contains", EnsuresNotNull); } [Fact] From 07a9ecd74c014df95996c2c093ebe25c8c31342a Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Thu, 10 May 2018 13:27:52 -0700 Subject: [PATCH 09/18] Diagnostics for invalid usage of attributes --- .../Portable/CSharpResources.Designer.cs | 27 ++++++++----- .../CSharp/Portable/CSharpResources.resx | 7 +++- .../CSharp/Portable/Errors/ErrorCode.cs | 3 +- .../Source/SourceComplexParameterSymbol.cs | 11 ++++- .../Portable/xlf/CSharpResources.cs.xlf | 11 +++-- .../Portable/xlf/CSharpResources.de.xlf | 11 +++-- .../Portable/xlf/CSharpResources.es.xlf | 11 +++-- .../Portable/xlf/CSharpResources.fr.xlf | 11 +++-- .../Portable/xlf/CSharpResources.it.xlf | 11 +++-- .../Portable/xlf/CSharpResources.ja.xlf | 11 +++-- .../Portable/xlf/CSharpResources.ko.xlf | 11 +++-- .../Portable/xlf/CSharpResources.pl.xlf | 11 +++-- .../Portable/xlf/CSharpResources.pt-BR.xlf | 11 +++-- .../Portable/xlf/CSharpResources.ru.xlf | 11 +++-- .../Portable/xlf/CSharpResources.tr.xlf | 11 +++-- .../Portable/xlf/CSharpResources.zh-Hans.xlf | 11 +++-- .../Portable/xlf/CSharpResources.zh-Hant.xlf | 11 +++-- .../Semantic/Semantics/StaticNullChecking.cs | 40 ++++++++++++++----- 18 files changed, 169 insertions(+), 62 deletions(-) diff --git a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs index 2b192bb45b552..9a5631656a524 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs +++ b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs @@ -700,6 +700,15 @@ internal static string ERR_AttributeCtorInParameter { } } + /// + /// Looks up a localized string similar to The '{0}' attribute is not applicable on a value type.. + /// + internal static string ERR_AttributeNotApplicableOnValueType { + get { + return ResourceManager.GetString("ERR_AttributeNotApplicableOnValueType", resourceCulture); + } + } + /// /// Looks up a localized string similar to Attribute '{0}' is not valid on property or event accessors. It is only valid on '{1}' declarations.. /// @@ -736,6 +745,15 @@ internal static string ERR_AttributeParameterRequired2 { } } + /// + /// Looks up a localized string similar to The '{0}' attribute is only applicable on members that return a boolean type.. + /// + internal static string ERR_AttributeRequiresBoolReturn { + get { + return ResourceManager.GetString("ERR_AttributeRequiresBoolReturn", resourceCulture); + } + } + /// /// Looks up a localized string similar to Attributes are not allowed on local function parameters or type parameters. /// @@ -7333,15 +7351,6 @@ internal static string ERR_NotNullRefDefaultParameter { } } - /// - /// Looks up a localized string similar to The NotNullWhenFalse attribute is only applicable on members that return a boolean type.. - /// - internal static string ERR_NotNullWhenFalseRequiresBoolReturn { - get { - return ResourceManager.GetString("ERR_NotNullWhenFalseRequiresBoolReturn", resourceCulture); - } - } - /// /// Looks up a localized string similar to This language feature ('{0}') is not yet implemented.. /// diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 473185c058c0e..775b124078ed3 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -5357,8 +5357,11 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed. - - The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + + The '{0}' attribute is only applicable on members that return a boolean type. + + + The '{0}' attribute is not applicable on a value type. The ! operator can only be applied to reference types. diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 17d730c45da4f..43c3401ccb6d2 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -1609,7 +1609,8 @@ internal enum ErrorCode ERR_NotNullableOperatorNotReferenceType = 8624, WRN_NullAsNonNullable = 8625, WRN_NoBestNullabilityConditionalExpression = 8626, - ERR_NotNullWhenFalseRequiresBoolReturn = 8627, + ERR_AttributeRequiresBoolReturn = 8627, + ERR_AttributeNotApplicableOnValueType = 8628, } // Note: you will need to re-generate compiler code after adding warnings (build\scripts\generate-compiler-code.cmd) } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs index dbd4f5f89a1f8..966d8c9ffbf50 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs @@ -631,12 +631,19 @@ internal override void DecodeWellKnownAttribute(ref DecodeWellKnownAttributeArgu } else { - arguments.Diagnostics.Add(ErrorCode.ERR_NotNullWhenFalseRequiresBoolReturn, arguments.AttributeSyntaxOpt.Location); + arguments.Diagnostics.Add(ErrorCode.ERR_AttributeRequiresBoolReturn, arguments.AttributeSyntaxOpt.Location, AttributeDescription.NotNullWhenFalseAttribute.Name); } } else if (attribute.IsTargetAttribute(this, AttributeDescription.EnsuresNotNullAttribute)) { - arguments.GetOrCreateData().HasEnsuresNotNullAttribute = true; + if (this.Type.IsValueType) + { + arguments.Diagnostics.Add(ErrorCode.ERR_AttributeNotApplicableOnValueType, arguments.AttributeSyntaxOpt.Location, AttributeDescription.EnsuresNotNullAttribute.Name); + } + else + { + arguments.GetOrCreateData().HasEnsuresNotNullAttribute = true; + } } } diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index 83695670e950a..4955dc73696ac 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -9000,9 +9000,14 @@ Pokud chcete odstranit toto varování, můžete místo toho použít /reference Converting null literal or possible null value to non-nullable type. - - The NotNullWhenFalse attribute is only applicable on members that return a boolean type. - The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + + The '{0}' attribute is only applicable on members that return a boolean type. + The '{0}' attribute is only applicable on members that return a boolean type. + + + + The '{0}' attribute is not applicable on a value type. + The '{0}' attribute is not applicable on a value type. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 0956237fa1318..d636553b46602 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -9000,9 +9000,14 @@ Um die Warnung zu beheben, können Sie stattdessen /reference verwenden (Einbett Converting null literal or possible null value to non-nullable type. - - The NotNullWhenFalse attribute is only applicable on members that return a boolean type. - The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + + The '{0}' attribute is only applicable on members that return a boolean type. + The '{0}' attribute is only applicable on members that return a boolean type. + + + + The '{0}' attribute is not applicable on a value type. + The '{0}' attribute is not applicable on a value type. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index 0c426316eff91..a5d406168b093 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -9000,9 +9000,14 @@ Para eliminar la advertencia puede usar /reference (establezca la propiedad Embe Converting null literal or possible null value to non-nullable type. - - The NotNullWhenFalse attribute is only applicable on members that return a boolean type. - The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + + The '{0}' attribute is only applicable on members that return a boolean type. + The '{0}' attribute is only applicable on members that return a boolean type. + + + + The '{0}' attribute is not applicable on a value type. + The '{0}' attribute is not applicable on a value type. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index a0046a913ac47..81edd6b230c20 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -9000,9 +9000,14 @@ Pour supprimer l'avertissement, vous pouvez utiliser la commande /reference (dé Converting null literal or possible null value to non-nullable type. - - The NotNullWhenFalse attribute is only applicable on members that return a boolean type. - The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + + The '{0}' attribute is only applicable on members that return a boolean type. + The '{0}' attribute is only applicable on members that return a boolean type. + + + + The '{0}' attribute is not applicable on a value type. + The '{0}' attribute is not applicable on a value type. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index d852b5e1996a2..af63e91bcd8d8 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -9000,9 +9000,14 @@ Per rimuovere l'avviso, è invece possibile usare /reference (impostare la propr Converting null literal or possible null value to non-nullable type. - - The NotNullWhenFalse attribute is only applicable on members that return a boolean type. - The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + + The '{0}' attribute is only applicable on members that return a boolean type. + The '{0}' attribute is only applicable on members that return a boolean type. + + + + The '{0}' attribute is not applicable on a value type. + The '{0}' attribute is not applicable on a value type. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 430c5751e806f..68afb4e83616d 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -9000,9 +9000,14 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Converting null literal or possible null value to non-nullable type. - - The NotNullWhenFalse attribute is only applicable on members that return a boolean type. - The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + + The '{0}' attribute is only applicable on members that return a boolean type. + The '{0}' attribute is only applicable on members that return a boolean type. + + + + The '{0}' attribute is not applicable on a value type. + The '{0}' attribute is not applicable on a value type. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index 1ce5e82dfa55f..d3796d4ee4b70 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -9000,9 +9000,14 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Converting null literal or possible null value to non-nullable type. - - The NotNullWhenFalse attribute is only applicable on members that return a boolean type. - The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + + The '{0}' attribute is only applicable on members that return a boolean type. + The '{0}' attribute is only applicable on members that return a boolean type. + + + + The '{0}' attribute is not applicable on a value type. + The '{0}' attribute is not applicable on a value type. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index 5ebe62205a4a0..33a52c0a91e43 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -9000,9 +9000,14 @@ Aby usunąć ostrzeżenie, możesz zamiast tego użyć opcji /reference (ustaw w Converting null literal or possible null value to non-nullable type. - - The NotNullWhenFalse attribute is only applicable on members that return a boolean type. - The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + + The '{0}' attribute is only applicable on members that return a boolean type. + The '{0}' attribute is only applicable on members that return a boolean type. + + + + The '{0}' attribute is not applicable on a value type. + The '{0}' attribute is not applicable on a value type. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index d219b029dbf25..96b8bb85b15b7 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -9000,9 +9000,14 @@ Para incorporar informações de tipo de interoperabilidade para os dois assembl Converting null literal or possible null value to non-nullable type. - - The NotNullWhenFalse attribute is only applicable on members that return a boolean type. - The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + + The '{0}' attribute is only applicable on members that return a boolean type. + The '{0}' attribute is only applicable on members that return a boolean type. + + + + The '{0}' attribute is not applicable on a value type. + The '{0}' attribute is not applicable on a value type. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 773f7e5fa2691..d7bc90fecf0da 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -9000,9 +9000,14 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Converting null literal or possible null value to non-nullable type. - - The NotNullWhenFalse attribute is only applicable on members that return a boolean type. - The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + + The '{0}' attribute is only applicable on members that return a boolean type. + The '{0}' attribute is only applicable on members that return a boolean type. + + + + The '{0}' attribute is not applicable on a value type. + The '{0}' attribute is not applicable on a value type. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index fe1c446561b0a..de99ea6b64406 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -9000,9 +9000,14 @@ Uyarıyı kaldırmak için, /reference kullanabilirsiniz (Birlikte Çalışma T Converting null literal or possible null value to non-nullable type. - - The NotNullWhenFalse attribute is only applicable on members that return a boolean type. - The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + + The '{0}' attribute is only applicable on members that return a boolean type. + The '{0}' attribute is only applicable on members that return a boolean type. + + + + The '{0}' attribute is not applicable on a value type. + The '{0}' attribute is not applicable on a value type. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index 9afa953371bc9..4d20b3e1db940 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -9000,9 +9000,14 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Converting null literal or possible null value to non-nullable type. - - The NotNullWhenFalse attribute is only applicable on members that return a boolean type. - The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + + The '{0}' attribute is only applicable on members that return a boolean type. + The '{0}' attribute is only applicable on members that return a boolean type. + + + + The '{0}' attribute is not applicable on a value type. + The '{0}' attribute is not applicable on a value type. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index feacca3cada52..a9e6d9bb32401 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -9000,9 +9000,14 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Converting null literal or possible null value to non-nullable type. - - The NotNullWhenFalse attribute is only applicable on members that return a boolean type. - The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + + The '{0}' attribute is only applicable on members that return a boolean type. + The '{0}' attribute is only applicable on members that return a boolean type. + + + + The '{0}' attribute is not applicable on a value type. + The '{0}' attribute is not applicable on a value type. diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs index af99293e4424c..c7f97fe72e1c3 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs @@ -5536,7 +5536,7 @@ public class C c.VerifyDiagnostics( // (5,43): error CS8627: The NotNullWhenFalse attribute is only applicable on members that return a boolean type. // public static object MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; - Diagnostic(ErrorCode.ERR_NotNullWhenFalseRequiresBoolReturn, "NotNullWhenFalse").WithLocation(5, 43) + Diagnostic(ErrorCode.ERR_AttributeRequiresBoolReturn, "NotNullWhenFalse").WithLocation(5, 43) ); VerifyAnnotationsInSource(c, "C.MyIsNullOrEmpty", None); @@ -6141,18 +6141,18 @@ public partial class C " + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); c.VerifyDiagnostics( - // (8,22): error CS8627: The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + // (8,22): error CS8627: The 'NotNullWhenFalseAttribute' attribute is only applicable on members that return a boolean type. // partial void M2([NotNullWhenFalse] string? s); - Diagnostic(ErrorCode.ERR_NotNullWhenFalseRequiresBoolReturn, "NotNullWhenFalse").WithLocation(8, 22), - // (12,22): error CS8627: The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + Diagnostic(ErrorCode.ERR_AttributeRequiresBoolReturn, "NotNullWhenFalse").WithArguments("NotNullWhenFalseAttribute").WithLocation(8, 22), + // (12,22): error CS8627: The 'NotNullWhenFalseAttribute' attribute is only applicable on members that return a boolean type. // partial void M3([NotNullWhenFalse] string? s) => throw null; - Diagnostic(ErrorCode.ERR_NotNullWhenFalseRequiresBoolReturn, "NotNullWhenFalse").WithLocation(12, 22), + Diagnostic(ErrorCode.ERR_AttributeRequiresBoolReturn, "NotNullWhenFalse").WithArguments("NotNullWhenFalseAttribute").WithLocation(12, 22), // (11,22): error CS0579: Duplicate 'NotNullWhenFalse' attribute // partial void M3([NotNullWhenFalse] string? s); Diagnostic(ErrorCode.ERR_DuplicateAttribute, "NotNullWhenFalse").WithArguments("NotNullWhenFalse").WithLocation(11, 22), - // (6,22): error CS8627: The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + // (6,22): error CS8627: The 'NotNullWhenFalseAttribute' attribute is only applicable on members that return a boolean type. // partial void M1([NotNullWhenFalse] string? s) => throw null; - Diagnostic(ErrorCode.ERR_NotNullWhenFalseRequiresBoolReturn, "NotNullWhenFalse").WithLocation(6, 22) + Diagnostic(ErrorCode.ERR_AttributeRequiresBoolReturn, "NotNullWhenFalse").WithArguments("NotNullWhenFalseAttribute").WithLocation(6, 22) ); VerifyAnnotationsInSource(c, "C.M1", None); @@ -6185,7 +6185,7 @@ void Main(string? s) c.VerifyDiagnostics( // (16,37): error CS8627: The NotNullWhenFalse attribute is only applicable on members that return a boolean type. // public dynamic MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; - Diagnostic(ErrorCode.ERR_NotNullWhenFalseRequiresBoolReturn, "NotNullWhenFalse").WithLocation(16, 37), + Diagnostic(ErrorCode.ERR_AttributeRequiresBoolReturn, "NotNullWhenFalse").WithLocation(16, 37), // (9,13): warning CS8602: Possible dereference of a null reference. // s.ToString(); // warn Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(9, 13) @@ -6297,7 +6297,7 @@ void Main(string? s) c.VerifyDiagnostics( // (10,29): error CS8627: The NotNullWhenFalse attribute is only applicable on members that return a boolean type. // object MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; - Diagnostic(ErrorCode.ERR_NotNullWhenFalseRequiresBoolReturn, "NotNullWhenFalse").WithLocation(10, 29), + Diagnostic(ErrorCode.ERR_AttributeRequiresBoolReturn, "NotNullWhenFalse").WithLocation(10, 29), // (8,9): warning CS8602: Possible dereference of a null reference. // s.ToString(); // warn Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(8, 9) @@ -6383,6 +6383,28 @@ void Main(string? s) VerifyAnnotations(c, "C.ThrowIfNull", None, EnsuresNotNull); } + [Fact] + public void EnsuresNotNull_OnDifferentTypes() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +public class C +{ + public static void Bad([EnsuresNotNull] int i) => throw null; + public static void ThrowIfNull([EnsuresNotNull] T t) => throw null; +} +" + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (5,32): error CS8628: The 'EnsuresNotNullAttribute' attribute is not applicable on a value type. + // public static void Bad([EnsuresNotNull] int i) => throw null; + Diagnostic(ErrorCode.ERR_AttributeNotApplicableOnValueType, "EnsuresNotNull").WithArguments("EnsuresNotNullAttribute").WithLocation(5, 32) + ); + + VerifyAnnotationsInSource(c, "C.Bad", None); + VerifyAnnotationsInSource(c, "C.ThrowIfNull", EnsuresNotNull); + } + [Fact] public void EnsuresNotNull_GenericMethod() { From 84ff12c9a72dacb211ff970ff73d2419308626c1 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Thu, 10 May 2018 14:53:43 -0700 Subject: [PATCH 10/18] Address PR feedback from Chuck --- .../Portable/FlowAnalysis/NullableWalker.cs | 7 +- .../Semantic/Semantics/StaticNullChecking.cs | 275 ++++++++++++++---- 2 files changed, 229 insertions(+), 53 deletions(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 3411c487ee5d7..8a33b34c7a5d8 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -1636,7 +1636,7 @@ private static ImmutableArray GetAnnotations(int numArgume // We'll ignore EnsuresNotNull that is misused in metadata if ((annotations & AttributeAnnotations.EnsuresNotNull) != 0 && - parameter.Type?.IsReferenceType == false) + parameter.Type?.IsValueType != false) { annotations &= ~AttributeAnnotations.EnsuresNotNull; } @@ -1871,6 +1871,7 @@ private void VisitArgumentsEvaluateHonoringAnnotations( } } + _result = _invalidType; _disableDiagnostics = saveDisableDiagnostics; } @@ -3749,7 +3750,6 @@ public bool Reachable } } -#if DEBUG public override string ToString() { var pooledBuilder = PooledStringBuilder.GetInstance(); @@ -3757,12 +3757,11 @@ public override string ToString() builder.Append(" "); for (int i = this.Capacity - 1; i >= 0; i--) { - builder.Append(_knownNullState[i] ? (_notNull[i] ? "! " : "? ") : ". "); + builder.Append(_knownNullState[i] ? (_notNull[i] ? "!" : "?") : "_"); } return pooledBuilder.ToStringAndFree(); } -#endif } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs index c7f97fe72e1c3..b1fe7a8cad761 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs @@ -5518,8 +5518,8 @@ public void Main(string? s) Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(16, 9) ); - VerifyAnnotations(c, "C.Main", None); - VerifyAnnotations(c, "C.MyIsNullOrEmpty", NotNullWhenFalse); + VerifyAnnotationsAndMetadata(c, "C.Main", None); + VerifyAnnotationsAndMetadata(c, "C.MyIsNullOrEmpty", NotNullWhenFalse); } [Fact] @@ -5534,12 +5534,12 @@ public class C " + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); c.VerifyDiagnostics( - // (5,43): error CS8627: The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + // (5,43): error CS8627: The 'NotNullWhenFalseAttribute' attribute is only applicable on members that return a boolean type. // public static object MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; - Diagnostic(ErrorCode.ERR_AttributeRequiresBoolReturn, "NotNullWhenFalse").WithLocation(5, 43) + Diagnostic(ErrorCode.ERR_AttributeRequiresBoolReturn, "NotNullWhenFalse").WithArguments("NotNullWhenFalseAttribute").WithLocation(5, 43) ); - VerifyAnnotationsInSource(c, "C.MyIsNullOrEmpty", None); + VerifyAnnotations(c, "C.MyIsNullOrEmpty", None); } [Fact] @@ -5584,7 +5584,7 @@ void Main(string? s, string? s2) Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s2").WithLocation(19, 9) ); - VerifyAnnotations(c, "C.MyIsNullOrEmpty", NotNullWhenFalse, NotNullWhenFalse); + VerifyAnnotationsAndMetadata(c, "C.MyIsNullOrEmpty", NotNullWhenFalse, NotNullWhenFalse); } [Fact] @@ -5699,10 +5699,10 @@ void Main(string? s) } else { - s.ToString(); // ok + s.ToString(); // warn 2 } - s.ToString(); // warn 2 + s.ToString(); // warn 3 } static bool MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; } @@ -5719,17 +5719,17 @@ void Main(string? s) // s.ToString(); // warn Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(8, 13), // (12,13): warning CS8602: Possible dereference of a null reference. - // s.ToString(); // ok + // s.ToString(); // warn 2 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(12, 13), // (15,9): warning CS8602: Possible dereference of a null reference. - // s.ToString(); // warn 2 + // s.ToString(); // warn 3 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(15, 9) ); - VerifyAnnotationsInSource(c, "C.MyIsNullOrEmpty", None); + VerifyAnnotations(c, "C.MyIsNullOrEmpty", None); } - private static void VerifyAnnotationsInSource(Compilation compilation, string memberName, params AttributeAnnotations[] expected) + private static void VerifyAnnotations(Compilation compilation, string memberName, params AttributeAnnotations[] expected) { var method = compilation.GetMember(memberName); Assert.True((object)method != null, $"Could not find method '{memberName}'"); @@ -5737,13 +5737,13 @@ private static void VerifyAnnotationsInSource(Compilation compilation, string me Assert.Equal(expected, actual); } - private void VerifyAnnotations(Compilation compilation, string memberName, params AttributeAnnotations[] expected) + private void VerifyAnnotationsAndMetadata(Compilation compilation, string memberName, params AttributeAnnotations[] expected) { - VerifyAnnotationsInSource(compilation, memberName, expected); + VerifyAnnotations(compilation, memberName, expected); // Also verify from metadata var compilation2 = CreateCompilation("", references: new[] { compilation.EmitToImageReference() }); - VerifyAnnotationsInSource(compilation2, memberName, expected); + VerifyAnnotations(compilation2, memberName, expected); } [Fact] @@ -5786,7 +5786,7 @@ public NotNullWhenFalseAttribute(bool bad) { } Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(13, 13) ); - VerifyAnnotations(c, "C.MyIsNullOrEmpty", None); + VerifyAnnotationsAndMetadata(c, "C.MyIsNullOrEmpty", None); } [Fact] @@ -5817,7 +5817,7 @@ void Main(string? s) Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(13, 13) ); - VerifyAnnotations(c, "C.MyIsNullOrEmpty", NotNullWhenFalse); + VerifyAnnotationsAndMetadata(c, "C.MyIsNullOrEmpty", NotNullWhenFalse); } [Fact] @@ -5866,7 +5866,7 @@ void Main(string? s) Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(9, 13) ); - VerifyAnnotations(c, "C.MyIsNullOrEmpty", NotNullWhenFalse); + VerifyAnnotationsAndMetadata(c, "C.MyIsNullOrEmpty", NotNullWhenFalse); } [Fact] @@ -5900,7 +5900,7 @@ public static class Extension Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(9, 13) ); - VerifyAnnotations(c, "Extension.MyIsNullOrEmpty", None, NotNullWhenFalse); + VerifyAnnotationsAndMetadata(c, "Extension.MyIsNullOrEmpty", None, NotNullWhenFalse); } [Fact] @@ -5929,7 +5929,7 @@ void Main(string? s) Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(8, 13) ); - VerifyAnnotations(c, "System.String.IsNullOrEmpty", NotNullWhenFalse); + VerifyAnnotationsAndMetadata(c, "System.String.IsNullOrEmpty", NotNullWhenFalse); } [Fact] @@ -5972,7 +5972,7 @@ public class ValueType { } Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(8, 13) ); - VerifyAnnotationsInSource(c, "System.String.IsNullOrEmpty", NotNullWhenFalse); + VerifyAnnotations(c, "System.String.IsNullOrEmpty", NotNullWhenFalse); } [Fact] @@ -6015,7 +6015,7 @@ public class ValueType { } Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(8, 13) ); - VerifyAnnotationsInSource(c, "System.String.IsNullOrWhiteSpace", NotNullWhenFalse); + VerifyAnnotations(c, "System.String.IsNullOrWhiteSpace", NotNullWhenFalse); } [Fact] @@ -6044,7 +6044,7 @@ void Main(string? s) Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(12, 13) ); - VerifyAnnotations(c, "System.String.IsNullOrEmpty", NotNullWhenFalse); + VerifyAnnotationsAndMetadata(c, "System.String.IsNullOrEmpty", NotNullWhenFalse); } [Fact] @@ -6119,7 +6119,7 @@ void Main(string? s) Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(8, 13) ); - VerifyAnnotations(c, "System.String.IsNullOrWhiteSpace", NotNullWhenFalse); + VerifyAnnotationsAndMetadata(c, "System.String.IsNullOrWhiteSpace", NotNullWhenFalse); } [Fact] @@ -6155,9 +6155,9 @@ public partial class C Diagnostic(ErrorCode.ERR_AttributeRequiresBoolReturn, "NotNullWhenFalse").WithArguments("NotNullWhenFalseAttribute").WithLocation(6, 22) ); - VerifyAnnotationsInSource(c, "C.M1", None); - VerifyAnnotationsInSource(c, "C.M2", None); - VerifyAnnotationsInSource(c, "C.M3", None); + VerifyAnnotations(c, "C.M1", None); + VerifyAnnotations(c, "C.M2", None); + VerifyAnnotations(c, "C.M3", None); } [Fact] @@ -6169,13 +6169,13 @@ public class C { void Main(string? s) { - if (string.IsNullOrWhiteSpace(s)) + if (MyIsNullOrEmpty(s)) { s.ToString(); // warn } else { - s.ToString(); // ok + s.ToString(); // warn 2 } } public dynamic MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; @@ -6183,15 +6183,18 @@ void Main(string? s) " + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); c.VerifyDiagnostics( - // (16,37): error CS8627: The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + // (16,37): error CS8627: The 'NotNullWhenFalseAttribute' attribute is only applicable on members that return a boolean type. // public dynamic MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; - Diagnostic(ErrorCode.ERR_AttributeRequiresBoolReturn, "NotNullWhenFalse").WithLocation(16, 37), + Diagnostic(ErrorCode.ERR_AttributeRequiresBoolReturn, "NotNullWhenFalse").WithArguments("NotNullWhenFalseAttribute").WithLocation(16, 37), // (9,13): warning CS8602: Possible dereference of a null reference. // s.ToString(); // warn - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(9, 13) + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(9, 13), + // (13,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(13, 13) ); - VerifyAnnotationsInSource(c, "C.MyIsNullOrEmpty", None); + VerifyAnnotations(c, "C.MyIsNullOrEmpty", None); } [Fact] @@ -6275,7 +6278,7 @@ void Main(C c, string? s) Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(12, 13) ); - VerifyAnnotationsInSource(compilation, "C.MyIsNullOrEmpty", NotNullWhenFalse); + VerifyAnnotations(compilation, "C.MyIsNullOrEmpty", NotNullWhenFalse); } [Fact] @@ -6287,7 +6290,7 @@ class C { void Main(string? s) { - string.IsNullOrWhiteSpace(s); + MyIsNullOrEmpty(s); s.ToString(); // warn } object MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; @@ -6295,9 +6298,9 @@ void Main(string? s) " + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); c.VerifyDiagnostics( - // (10,29): error CS8627: The NotNullWhenFalse attribute is only applicable on members that return a boolean type. + // (10,29): error CS8627: The 'NotNullWhenFalseAttribute' attribute is only applicable on members that return a boolean type. // object MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; - Diagnostic(ErrorCode.ERR_AttributeRequiresBoolReturn, "NotNullWhenFalse").WithLocation(10, 29), + Diagnostic(ErrorCode.ERR_AttributeRequiresBoolReturn, "NotNullWhenFalse").WithArguments("NotNullWhenFalseAttribute").WithLocation(10, 29), // (8,9): warning CS8602: Possible dereference of a null reference. // s.ToString(); // warn Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(8, 9) @@ -6330,7 +6333,7 @@ void Main(string? s) c.VerifyDiagnostics(); - VerifyAnnotations(c, "C.MyIsNullOrEmpty", NotNullWhenFalse, EnsuresNotNull); + VerifyAnnotationsAndMetadata(c, "C.MyIsNullOrEmpty", NotNullWhenFalse, EnsuresNotNull); } [Fact] @@ -6359,7 +6362,7 @@ void Main(string? s) c.VerifyDiagnostics(); - VerifyAnnotations(c, "C.MyIsNullOrEmpty", NotNullWhenFalse | EnsuresNotNull); + VerifyAnnotationsAndMetadata(c, "C.MyIsNullOrEmpty", NotNullWhenFalse | EnsuresNotNull); } [Fact] @@ -6380,7 +6383,172 @@ void Main(string? s) c.VerifyDiagnostics(); - VerifyAnnotations(c, "C.ThrowIfNull", None, EnsuresNotNull); + VerifyAnnotationsAndMetadata(c, "C.ThrowIfNull", None, EnsuresNotNull); + } + + [Fact] + public void EnsuresNotNull_OnInterface() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +public class C +{ + void Main(string? s, Interface i) + { + i.ThrowIfNull(42, s); + s.ToString(); // ok + } +} +public interface Interface +{ + void ThrowIfNull(int x, [EnsuresNotNull] string? s); +} +" + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics(); + + VerifyAnnotationsAndMetadata(c, "Interface.ThrowIfNull", None, EnsuresNotNull); + } + + [Fact] + public void EnsuresNotNull_OnInterface_ImplementedWithoutAttribute() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +public class C : Interface +{ + void Main(string? s) + { + this.ThrowIfNull(42, s); + s.ToString(); // warn + ((Interface)this).ThrowIfNull(42, s); + s.ToString(); // ok + } + public void ThrowIfNull(int x, string? s) => throw null; +} +public interface Interface +{ + void ThrowIfNull(int x, [EnsuresNotNull] string? s); +} +" + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (8,9): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(8, 9) + ); + + VerifyAnnotationsAndMetadata(c, "Interface.ThrowIfNull", None, EnsuresNotNull); + VerifyAnnotationsAndMetadata(c, "C.ThrowIfNull", None, None); + } + + [Fact] + public void EnsuresNotNull_OnInterface_ImplementedWithAttribute() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +public class C : Interface +{ + void Main(string? s) + { + ((Interface)this).ThrowIfNull(42, s); + s.ToString(); // warn + this.ThrowIfNull(42, s); + s.ToString(); // ok + } + public void ThrowIfNull(int x, [EnsuresNotNull] string? s) => throw null; +} +public interface Interface +{ + void ThrowIfNull(int x, string? s); +} +" + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (8,9): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(8, 9) + ); + + VerifyAnnotationsAndMetadata(c, "Interface.ThrowIfNull", None, None); + VerifyAnnotationsAndMetadata(c, "C.ThrowIfNull", None, EnsuresNotNull); + } + + [Fact] + public void EnsuresNotNull_OnDelegate() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +delegate void D([EnsuresNotNull] object? o); +public class C +{ + void Main(string? s, D d) + { + d(s); + s.ToString(); // ok + } +} +" + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics(); + } + + [Fact] + public void EnsuresNotNull_WithParams() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +public class C +{ + static void EnsuresNotNull([EnsuresNotNull] params object?[]? args) { } + static void F(object? x, object? y, object[]? a) + { + EnsuresNotNull(); + a.ToString(); // warn 1 + + EnsuresNotNull(a); + a.ToString(); // ok + + EnsuresNotNull(x, y); + x.ToString(); // warn 2 + y.ToString(); // warn 3 + } +} +" + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); + + // PROTOTYPE(NullableReferenceTypes): bug on x and y + c.VerifyDiagnostics( + // (9,9): warning CS8602: Possible dereference of a null reference. + // a.ToString(); // warn 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "a").WithLocation(9, 9) + ); + } + + [Fact] + public void EnsuresNotNull_WithNamedArguments() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +public class C +{ + static void EnsuresNotNull1([EnsuresNotNull] object? x = null, object? y = null) { } + static void EnsuresNotNull2(object? x = null, [EnsuresNotNull] object? y = null) { } + static void F(object? x) + { + EnsuresNotNull1(); + EnsuresNotNull1(y: x); + x.ToString(); // warn + EnsuresNotNull2(y: x); + x.ToString(); // ok + } +} +" + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (11,9): warning CS8602: Possible dereference of a null reference. + // x.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(11, 9) + ); } [Fact] @@ -6401,8 +6569,8 @@ public class C Diagnostic(ErrorCode.ERR_AttributeNotApplicableOnValueType, "EnsuresNotNull").WithArguments("EnsuresNotNullAttribute").WithLocation(5, 32) ); - VerifyAnnotationsInSource(c, "C.Bad", None); - VerifyAnnotationsInSource(c, "C.ThrowIfNull", EnsuresNotNull); + VerifyAnnotations(c, "C.Bad", None); + VerifyAnnotations(c, "C.ThrowIfNull", EnsuresNotNull); } [Fact] @@ -6412,18 +6580,27 @@ public void EnsuresNotNull_GenericMethod() using System.Runtime.CompilerServices; public class C { - void Main(string? s) + void M(T t) { - ThrowIfNull(s); - s.ToString(); // ok + t.ToString(); // warn + ThrowIfNull(t); + t.ToString(); // ok } public static void ThrowIfNull([EnsuresNotNull] T s) => throw null; } " + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); - c.VerifyDiagnostics(); + // PROTOTYPE(NullableReferenceTypes): We're not tracking unconstrained generic types + c.VerifyDiagnostics( + // (7,9): warning CS8602: Possible dereference of a null reference. + // t.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "t").WithLocation(7, 9), + // (9,9): warning CS8602: Possible dereference of a null reference. + // t.ToString(); // ok + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "t").WithLocation(9, 9) + ); - VerifyAnnotations(c, "C.ThrowIfNull", EnsuresNotNull); + VerifyAnnotationsAndMetadata(c, "C.ThrowIfNull", EnsuresNotNull); } [Fact] @@ -6468,7 +6645,7 @@ void Main(string? s) Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(7, 24) ); - VerifyAnnotations(c, "C.ThrowIfNull", EnsuresNotNull, None); + VerifyAnnotationsAndMetadata(c, "C.ThrowIfNull", EnsuresNotNull, None); } [Fact] @@ -6510,7 +6687,7 @@ void Main(string? s) c.VerifyDiagnostics(); - VerifyAnnotations(c, "System.String.Contains", EnsuresNotNull); + VerifyAnnotationsAndMetadata(c, "System.String.Contains", EnsuresNotNull); } [Fact] From 4e72481861d8e27ffd1430fb4db7404b216a4283 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Thu, 10 May 2018 16:16:25 -0700 Subject: [PATCH 11/18] Block params --- .../Portable/FlowAnalysis/NullableWalker.cs | 2 +- .../Semantic/Semantics/StaticNullChecking.cs | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 8a33b34c7a5d8..c3fb0aa0e4c75 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -1636,7 +1636,7 @@ private static ImmutableArray GetAnnotations(int numArgume // We'll ignore EnsuresNotNull that is misused in metadata if ((annotations & AttributeAnnotations.EnsuresNotNull) != 0 && - parameter.Type?.IsValueType != false) + (parameter.Type?.IsValueType != false || parameter.IsParams)) { annotations &= ~AttributeAnnotations.EnsuresNotNull; } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs index b1fe7a8cad761..db28fd590a7c0 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs @@ -6507,20 +6507,29 @@ static void F(object? x, object? y, object[]? a) a.ToString(); // warn 1 EnsuresNotNull(a); - a.ToString(); // ok + a.ToString(); // warn 2 EnsuresNotNull(x, y); - x.ToString(); // warn 2 - y.ToString(); // warn 3 + x.ToString(); // warn 3 + y.ToString(); // warn 4 } } " + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); - // PROTOTYPE(NullableReferenceTypes): bug on x and y + // PROTOTYPE(NullableReferenceTypes): Consider allowing EnsuresNotNull on params parameter c.VerifyDiagnostics( // (9,9): warning CS8602: Possible dereference of a null reference. // a.ToString(); // warn 1 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "a").WithLocation(9, 9) + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "a").WithLocation(9, 9), + // (12,9): warning CS8602: Possible dereference of a null reference. + // a.ToString(); // warn 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "a").WithLocation(12, 9), + // (15,9): warning CS8602: Possible dereference of a null reference. + // x.ToString(); // warn 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(15, 9), + // (16,9): warning CS8602: Possible dereference of a null reference. + // y.ToString(); // warn 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y").WithLocation(16, 9) ); } From b893f7b413b20ea2e7975f3fd8c91f667548f9df Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Thu, 10 May 2018 17:14:04 -0700 Subject: [PATCH 12/18] Remove unused parameter --- .../CSharp/Portable/FlowAnalysis/NullableWalker.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index c3fb0aa0e4c75..955127b5d0fa5 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -1732,7 +1732,7 @@ private ImmutableArray VisitArgumentsEvaluate( { var savedState = this.State.Clone(); // We do a first pass to work through the arguments without making any assumptions - var results = VisitArgumentsEvaluate(arguments, refKindsOpt, expanded); + var results = VisitArgumentsEvaluate(arguments, refKindsOpt); // We do a second pass through the arguments, ignoring any diagnostics produced, but honoring the annotations, // to get the proper result state. @@ -1750,8 +1750,7 @@ private ImmutableArray VisitArgumentsEvaluate( private ImmutableArray VisitArgumentsEvaluate( ImmutableArray arguments, - ImmutableArray refKindsOpt, - bool expanded) + ImmutableArray refKindsOpt) { Debug.Assert(!IsConditionalState); int n = arguments.Length; @@ -3244,7 +3243,7 @@ public override BoundNode VisitArgList(BoundArgList node) public override BoundNode VisitArgListOperator(BoundArgListOperator node) { - VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt, expanded: false); + VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt); Debug.Assert((object)node.Type == null); SetResult(node); return null; @@ -3324,7 +3323,7 @@ public override BoundNode VisitDynamicMemberAccess(BoundDynamicMemberAccess node public override BoundNode VisitDynamicInvocation(BoundDynamicInvocation node) { VisitRvalue(node.Expression); - VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt, expanded: false); + VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt); Debug.Assert(node.Type.IsDynamic()); Debug.Assert(node.Type.IsReferenceType); @@ -3352,7 +3351,7 @@ public override BoundNode VisitEventAssignmentOperator(BoundEventAssignmentOpera public override BoundNode VisitDynamicObjectCreationExpression(BoundDynamicObjectCreationExpression node) { Debug.Assert(!IsConditionalState); - VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt, expanded: false); + VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt); VisitObjectOrDynamicObjectCreation(node, node.InitializerExpressionOpt); return null; } @@ -3431,7 +3430,7 @@ public override BoundNode VisitDynamicIndexerAccess(BoundDynamicIndexerAccess no var receiver = node.ReceiverOpt; VisitRvalue(receiver); CheckPossibleNullReceiver(receiver); - VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt, expanded: false); + VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt); Debug.Assert(node.Type.IsDynamic()); From 21888133506c095017dcfda732bbc41214b7fef0 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Thu, 10 May 2018 18:05:51 -0700 Subject: [PATCH 13/18] Fix merge conflict --- src/Compilers/CSharp/Portable/Symbols/ExtraAnnotations.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/ExtraAnnotations.cs b/src/Compilers/CSharp/Portable/Symbols/ExtraAnnotations.cs index 021ac7665d690..86b28364d801f 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ExtraAnnotations.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ExtraAnnotations.cs @@ -160,7 +160,7 @@ private static void Add(TypeSymbol type, StringBuilder builder) .RemoveMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes) .AddMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier) // displaying tuple syntax causes to load the members of ValueTuple, which can cause a cycle, so we use long-hand format instead - .WithCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.IncludeNullableReferenceTypeModifier | SymbolDisplayCompilerInternalOptions.UseValueTuple))); + .WithCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.UseValueTuple))); /// /// index 0 is used for return type From 7c7573d3871d555fa833fb64d5fff3f841b82827 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Mon, 14 May 2018 08:45:53 -0700 Subject: [PATCH 14/18] WIP --- .../Portable/Symbols/Source/SourceClonedParameterSymbol.cs | 1 + .../Portable/Symbols/Source/SourceComplexParameterSymbol.cs | 1 + .../Portable/Symbols/Wrapped/WrappedParameterSymbol.cs | 1 + .../Attributes/CommonParameterWellKnownAttributeData.cs | 1 + src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs | 5 ++++- 5 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs index d8ae0ebdbbffc..60b28a2122908 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs @@ -153,6 +153,7 @@ internal override bool IsCallerMemberName internal override AttributeAnnotations FlowAnalysisAnnotations { + // PROTOTYPE(NullableReferenceTypes): Make sure this is covered by test get { return _originalParam.FlowAnalysisAnnotations; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs index 966d8c9ffbf50..1c14d38dedcea 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs @@ -134,6 +134,7 @@ internal override AttributeAnnotations FlowAnalysisAnnotations (bool memberHasAny, AttributeAnnotations annotations) = TryGetExtraAttributeAnnotations(); if (memberHasAny) { + // PROTOTYPE(NullableReferenceTypes): Make sure this is covered by test return annotations; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedParameterSymbol.cs index 84a20f99c1936..240da630b9095 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedParameterSymbol.cs @@ -155,6 +155,7 @@ internal override bool IsCallerMemberName internal override AttributeAnnotations FlowAnalysisAnnotations { + // PROTOTYPE(NullableReferenceTypes): Ensure this is covered and consider moving to leaf types get { return _underlyingParameter.FlowAnalysisAnnotations; } } diff --git a/src/Compilers/Core/Portable/Symbols/Attributes/CommonParameterWellKnownAttributeData.cs b/src/Compilers/Core/Portable/Symbols/Attributes/CommonParameterWellKnownAttributeData.cs index acfee1e3718ce..020d56fa86c07 100644 --- a/src/Compilers/Core/Portable/Symbols/Attributes/CommonParameterWellKnownAttributeData.cs +++ b/src/Compilers/Core/Portable/Symbols/Attributes/CommonParameterWellKnownAttributeData.cs @@ -110,6 +110,7 @@ public bool HasIUnknownConstantAttribute } #endregion + // PROTOTYPE(NullableReferenceTypes): Consider moving the attribute for nullability to C#-specific type private bool _hasNotNullWhenFalseAttribute; public bool HasNotNullWhenFalseAttribute { diff --git a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs index b61dacbc377eb..0f81d94e7fd63 100644 --- a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs +++ b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs @@ -307,7 +307,10 @@ public static CSharpCompilation CreateCompilationWithIL( IEnumerable references = null, CSharpCompilationOptions options = null, CSharpParseOptions parseOptions = null, - bool appendDefaultHeader = true) => CreateCompilationWithILAndMscorlib40(source, ilSource, TargetFramework.Standard, references, options, parseOptions, appendDefaultHeader); + bool appendDefaultHeader = true) + { + return CreateCompilationWithILAndMscorlib40(source, ilSource, TargetFramework.Standard, references, options, parseOptions, appendDefaultHeader); + } public static CSharpCompilation CreateCompilationWithILAndMscorlib40( CSharpTestSource source, From 2036a25e721d52a8179d8da9df6318a85dda3afa Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Mon, 14 May 2018 08:54:16 -0700 Subject: [PATCH 15/18] Remove warning for misused attributes --- .../Portable/CSharpResources.Designer.cs | 18 -------- .../CSharp/Portable/CSharpResources.resx | 6 --- .../CSharp/Portable/Errors/ErrorCode.cs | 2 - .../Source/SourceComplexParameterSymbol.cs | 18 +------- .../Portable/xlf/CSharpResources.cs.xlf | 10 ----- .../Portable/xlf/CSharpResources.de.xlf | 10 ----- .../Portable/xlf/CSharpResources.es.xlf | 10 ----- .../Portable/xlf/CSharpResources.fr.xlf | 10 ----- .../Portable/xlf/CSharpResources.it.xlf | 10 ----- .../Portable/xlf/CSharpResources.ja.xlf | 10 ----- .../Portable/xlf/CSharpResources.ko.xlf | 10 ----- .../Portable/xlf/CSharpResources.pl.xlf | 10 ----- .../Portable/xlf/CSharpResources.pt-BR.xlf | 10 ----- .../Portable/xlf/CSharpResources.ru.xlf | 10 ----- .../Portable/xlf/CSharpResources.tr.xlf | 10 ----- .../Portable/xlf/CSharpResources.zh-Hans.xlf | 10 ----- .../Portable/xlf/CSharpResources.zh-Hant.xlf | 10 ----- .../Semantic/Semantics/StaticNullChecking.cs | 41 ++++--------------- 18 files changed, 11 insertions(+), 204 deletions(-) diff --git a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs index 9a5631656a524..665592bb7cdba 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs +++ b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs @@ -700,15 +700,6 @@ internal static string ERR_AttributeCtorInParameter { } } - /// - /// Looks up a localized string similar to The '{0}' attribute is not applicable on a value type.. - /// - internal static string ERR_AttributeNotApplicableOnValueType { - get { - return ResourceManager.GetString("ERR_AttributeNotApplicableOnValueType", resourceCulture); - } - } - /// /// Looks up a localized string similar to Attribute '{0}' is not valid on property or event accessors. It is only valid on '{1}' declarations.. /// @@ -7324,15 +7315,6 @@ internal static string ERR_NotConstantExpression { } } - /// - /// Looks up a localized string similar to The ! operator can only be applied to reference types.. - /// - internal static string ERR_NotNullableOperatorNotReferenceType { - get { - return ResourceManager.GetString("ERR_NotNullableOperatorNotReferenceType", resourceCulture); - } - } - /// /// Looks up a localized string similar to '{0}' is of type '{1}'. A const field of a reference type other than string can only be initialized with null.. /// diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 775b124078ed3..4daafdcc80072 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -5360,12 +5360,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ The '{0}' attribute is only applicable on members that return a boolean type. - - The '{0}' attribute is not applicable on a value type. - - - The ! operator can only be applied to reference types. - A void or int returning entry point cannot be async diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 43c3401ccb6d2..0faeca8448c18 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -1609,8 +1609,6 @@ internal enum ErrorCode ERR_NotNullableOperatorNotReferenceType = 8624, WRN_NullAsNonNullable = 8625, WRN_NoBestNullabilityConditionalExpression = 8626, - ERR_AttributeRequiresBoolReturn = 8627, - ERR_AttributeNotApplicableOnValueType = 8628, } // Note: you will need to re-generate compiler code after adding warnings (build\scripts\generate-compiler-code.cmd) } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs index 1c14d38dedcea..1bd2f254c26d2 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs @@ -626,25 +626,11 @@ internal override void DecodeWellKnownAttribute(ref DecodeWellKnownAttributeArgu } else if (attribute.IsTargetAttribute(this, AttributeDescription.NotNullWhenFalseAttribute)) { - if (this.ContainingSymbol.GetTypeOrReturnType().SpecialType == SpecialType.System_Boolean) - { - arguments.GetOrCreateData().HasNotNullWhenFalseAttribute = true; - } - else - { - arguments.Diagnostics.Add(ErrorCode.ERR_AttributeRequiresBoolReturn, arguments.AttributeSyntaxOpt.Location, AttributeDescription.NotNullWhenFalseAttribute.Name); - } + arguments.GetOrCreateData().HasNotNullWhenFalseAttribute = true; } else if (attribute.IsTargetAttribute(this, AttributeDescription.EnsuresNotNullAttribute)) { - if (this.Type.IsValueType) - { - arguments.Diagnostics.Add(ErrorCode.ERR_AttributeNotApplicableOnValueType, arguments.AttributeSyntaxOpt.Location, AttributeDescription.EnsuresNotNullAttribute.Name); - } - else - { - arguments.GetOrCreateData().HasEnsuresNotNullAttribute = true; - } + arguments.GetOrCreateData().HasEnsuresNotNullAttribute = true; } } diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index 4955dc73696ac..a40ac7ed3268e 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -8975,11 +8975,6 @@ Pokud chcete odstranit toto varování, můžete místo toho použít /reference Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed. - - The ! operator can only be applied to reference types. - The ! operator can only be applied to reference types. - - No best nullability for operands of conditional expression '{0}' and '{1}'. No best nullability for operands of conditional expression '{0}' and '{1}'. @@ -9005,11 +9000,6 @@ Pokud chcete odstranit toto varování, můžete místo toho použít /reference The '{0}' attribute is only applicable on members that return a boolean type. - - The '{0}' attribute is not applicable on a value type. - The '{0}' attribute is not applicable on a value type. - - \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index d636553b46602..1b81e8feab33f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -8975,11 +8975,6 @@ Um die Warnung zu beheben, können Sie stattdessen /reference verwenden (Einbett Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed. - - The ! operator can only be applied to reference types. - The ! operator can only be applied to reference types. - - No best nullability for operands of conditional expression '{0}' and '{1}'. No best nullability for operands of conditional expression '{0}' and '{1}'. @@ -9005,11 +9000,6 @@ Um die Warnung zu beheben, können Sie stattdessen /reference verwenden (Einbett The '{0}' attribute is only applicable on members that return a boolean type. - - The '{0}' attribute is not applicable on a value type. - The '{0}' attribute is not applicable on a value type. - - \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index a5d406168b093..07213cc6ab73f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -8975,11 +8975,6 @@ Para eliminar la advertencia puede usar /reference (establezca la propiedad Embe Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed. - - The ! operator can only be applied to reference types. - The ! operator can only be applied to reference types. - - No best nullability for operands of conditional expression '{0}' and '{1}'. No best nullability for operands of conditional expression '{0}' and '{1}'. @@ -9005,11 +9000,6 @@ Para eliminar la advertencia puede usar /reference (establezca la propiedad Embe The '{0}' attribute is only applicable on members that return a boolean type. - - The '{0}' attribute is not applicable on a value type. - The '{0}' attribute is not applicable on a value type. - - \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 81edd6b230c20..0c6f684937a3d 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -8975,11 +8975,6 @@ Pour supprimer l'avertissement, vous pouvez utiliser la commande /reference (dé Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed. - - The ! operator can only be applied to reference types. - The ! operator can only be applied to reference types. - - No best nullability for operands of conditional expression '{0}' and '{1}'. No best nullability for operands of conditional expression '{0}' and '{1}'. @@ -9005,11 +9000,6 @@ Pour supprimer l'avertissement, vous pouvez utiliser la commande /reference (dé The '{0}' attribute is only applicable on members that return a boolean type. - - The '{0}' attribute is not applicable on a value type. - The '{0}' attribute is not applicable on a value type. - - \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index af63e91bcd8d8..2b43b20f3b4f6 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -8975,11 +8975,6 @@ Per rimuovere l'avviso, è invece possibile usare /reference (impostare la propr Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed. - - The ! operator can only be applied to reference types. - The ! operator can only be applied to reference types. - - No best nullability for operands of conditional expression '{0}' and '{1}'. No best nullability for operands of conditional expression '{0}' and '{1}'. @@ -9005,11 +9000,6 @@ Per rimuovere l'avviso, è invece possibile usare /reference (impostare la propr The '{0}' attribute is only applicable on members that return a boolean type. - - The '{0}' attribute is not applicable on a value type. - The '{0}' attribute is not applicable on a value type. - - \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 68afb4e83616d..57e85fcf2d52b 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -8975,11 +8975,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed. - - The ! operator can only be applied to reference types. - The ! operator can only be applied to reference types. - - No best nullability for operands of conditional expression '{0}' and '{1}'. No best nullability for operands of conditional expression '{0}' and '{1}'. @@ -9005,11 +9000,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ The '{0}' attribute is only applicable on members that return a boolean type. - - The '{0}' attribute is not applicable on a value type. - The '{0}' attribute is not applicable on a value type. - - \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index d3796d4ee4b70..2819d74494f00 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -8975,11 +8975,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed. - - The ! operator can only be applied to reference types. - The ! operator can only be applied to reference types. - - No best nullability for operands of conditional expression '{0}' and '{1}'. No best nullability for operands of conditional expression '{0}' and '{1}'. @@ -9005,11 +9000,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ The '{0}' attribute is only applicable on members that return a boolean type. - - The '{0}' attribute is not applicable on a value type. - The '{0}' attribute is not applicable on a value type. - - \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index 33a52c0a91e43..65db0e9e1d830 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -8975,11 +8975,6 @@ Aby usunąć ostrzeżenie, możesz zamiast tego użyć opcji /reference (ustaw w Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed. - - The ! operator can only be applied to reference types. - The ! operator can only be applied to reference types. - - No best nullability for operands of conditional expression '{0}' and '{1}'. No best nullability for operands of conditional expression '{0}' and '{1}'. @@ -9005,11 +9000,6 @@ Aby usunąć ostrzeżenie, możesz zamiast tego użyć opcji /reference (ustaw w The '{0}' attribute is only applicable on members that return a boolean type. - - The '{0}' attribute is not applicable on a value type. - The '{0}' attribute is not applicable on a value type. - - \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 96b8bb85b15b7..a8ce07c7475ab 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -8975,11 +8975,6 @@ Para incorporar informações de tipo de interoperabilidade para os dois assembl Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed. - - The ! operator can only be applied to reference types. - The ! operator can only be applied to reference types. - - No best nullability for operands of conditional expression '{0}' and '{1}'. No best nullability for operands of conditional expression '{0}' and '{1}'. @@ -9005,11 +9000,6 @@ Para incorporar informações de tipo de interoperabilidade para os dois assembl The '{0}' attribute is only applicable on members that return a boolean type. - - The '{0}' attribute is not applicable on a value type. - The '{0}' attribute is not applicable on a value type. - - \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index d7bc90fecf0da..688a2dbe146f0 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -8975,11 +8975,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed. - - The ! operator can only be applied to reference types. - The ! operator can only be applied to reference types. - - No best nullability for operands of conditional expression '{0}' and '{1}'. No best nullability for operands of conditional expression '{0}' and '{1}'. @@ -9005,11 +9000,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ The '{0}' attribute is only applicable on members that return a boolean type. - - The '{0}' attribute is not applicable on a value type. - The '{0}' attribute is not applicable on a value type. - - \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index de99ea6b64406..a11853bcd07de 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -8975,11 +8975,6 @@ Uyarıyı kaldırmak için, /reference kullanabilirsiniz (Birlikte Çalışma T Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed. - - The ! operator can only be applied to reference types. - The ! operator can only be applied to reference types. - - No best nullability for operands of conditional expression '{0}' and '{1}'. No best nullability for operands of conditional expression '{0}' and '{1}'. @@ -9005,11 +9000,6 @@ Uyarıyı kaldırmak için, /reference kullanabilirsiniz (Birlikte Çalışma T The '{0}' attribute is only applicable on members that return a boolean type. - - The '{0}' attribute is not applicable on a value type. - The '{0}' attribute is not applicable on a value type. - - \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index 4d20b3e1db940..338b6712c80d3 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -8975,11 +8975,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed. - - The ! operator can only be applied to reference types. - The ! operator can only be applied to reference types. - - No best nullability for operands of conditional expression '{0}' and '{1}'. No best nullability for operands of conditional expression '{0}' and '{1}'. @@ -9005,11 +9000,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ The '{0}' attribute is only applicable on members that return a boolean type. - - The '{0}' attribute is not applicable on a value type. - The '{0}' attribute is not applicable on a value type. - - \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index a9e6d9bb32401..8487de3209940 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -8975,11 +8975,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed. - - The ! operator can only be applied to reference types. - The ! operator can only be applied to reference types. - - No best nullability for operands of conditional expression '{0}' and '{1}'. No best nullability for operands of conditional expression '{0}' and '{1}'. @@ -9005,11 +9000,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ The '{0}' attribute is only applicable on members that return a boolean type. - - The '{0}' attribute is not applicable on a value type. - The '{0}' attribute is not applicable on a value type. - - \ No newline at end of file diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs index db28fd590a7c0..462d05d8a857c 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs @@ -5533,13 +5533,9 @@ public class C } " + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); - c.VerifyDiagnostics( - // (5,43): error CS8627: The 'NotNullWhenFalseAttribute' attribute is only applicable on members that return a boolean type. - // public static object MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; - Diagnostic(ErrorCode.ERR_AttributeRequiresBoolReturn, "NotNullWhenFalse").WithArguments("NotNullWhenFalseAttribute").WithLocation(5, 43) - ); + c.VerifyDiagnostics(); - VerifyAnnotations(c, "C.MyIsNullOrEmpty", None); + VerifyAnnotations(c, "C.MyIsNullOrEmpty", NotNullWhenFalse); } [Fact] @@ -6141,23 +6137,14 @@ public partial class C " + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); c.VerifyDiagnostics( - // (8,22): error CS8627: The 'NotNullWhenFalseAttribute' attribute is only applicable on members that return a boolean type. - // partial void M2([NotNullWhenFalse] string? s); - Diagnostic(ErrorCode.ERR_AttributeRequiresBoolReturn, "NotNullWhenFalse").WithArguments("NotNullWhenFalseAttribute").WithLocation(8, 22), - // (12,22): error CS8627: The 'NotNullWhenFalseAttribute' attribute is only applicable on members that return a boolean type. - // partial void M3([NotNullWhenFalse] string? s) => throw null; - Diagnostic(ErrorCode.ERR_AttributeRequiresBoolReturn, "NotNullWhenFalse").WithArguments("NotNullWhenFalseAttribute").WithLocation(12, 22), // (11,22): error CS0579: Duplicate 'NotNullWhenFalse' attribute // partial void M3([NotNullWhenFalse] string? s); - Diagnostic(ErrorCode.ERR_DuplicateAttribute, "NotNullWhenFalse").WithArguments("NotNullWhenFalse").WithLocation(11, 22), - // (6,22): error CS8627: The 'NotNullWhenFalseAttribute' attribute is only applicable on members that return a boolean type. - // partial void M1([NotNullWhenFalse] string? s) => throw null; - Diagnostic(ErrorCode.ERR_AttributeRequiresBoolReturn, "NotNullWhenFalse").WithArguments("NotNullWhenFalseAttribute").WithLocation(6, 22) + Diagnostic(ErrorCode.ERR_DuplicateAttribute, "NotNullWhenFalse").WithArguments("NotNullWhenFalse").WithLocation(11, 22) ); - VerifyAnnotations(c, "C.M1", None); - VerifyAnnotations(c, "C.M2", None); - VerifyAnnotations(c, "C.M3", None); + VerifyAnnotations(c, "C.M1", NotNullWhenFalse); + VerifyAnnotations(c, "C.M2", NotNullWhenFalse); + VerifyAnnotations(c, "C.M3", NotNullWhenFalse); } [Fact] @@ -6183,9 +6170,6 @@ void Main(string? s) " + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); c.VerifyDiagnostics( - // (16,37): error CS8627: The 'NotNullWhenFalseAttribute' attribute is only applicable on members that return a boolean type. - // public dynamic MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; - Diagnostic(ErrorCode.ERR_AttributeRequiresBoolReturn, "NotNullWhenFalse").WithArguments("NotNullWhenFalseAttribute").WithLocation(16, 37), // (9,13): warning CS8602: Possible dereference of a null reference. // s.ToString(); // warn Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(9, 13), @@ -6194,7 +6178,7 @@ void Main(string? s) Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(13, 13) ); - VerifyAnnotations(c, "C.MyIsNullOrEmpty", None); + VerifyAnnotations(c, "C.MyIsNullOrEmpty", NotNullWhenFalse); } [Fact] @@ -6298,9 +6282,6 @@ void Main(string? s) " + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); c.VerifyDiagnostics( - // (10,29): error CS8627: The 'NotNullWhenFalseAttribute' attribute is only applicable on members that return a boolean type. - // object MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; - Diagnostic(ErrorCode.ERR_AttributeRequiresBoolReturn, "NotNullWhenFalse").WithArguments("NotNullWhenFalseAttribute").WithLocation(10, 29), // (8,9): warning CS8602: Possible dereference of a null reference. // s.ToString(); // warn Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(8, 9) @@ -6572,13 +6553,9 @@ public class C } " + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); - c.VerifyDiagnostics( - // (5,32): error CS8628: The 'EnsuresNotNullAttribute' attribute is not applicable on a value type. - // public static void Bad([EnsuresNotNull] int i) => throw null; - Diagnostic(ErrorCode.ERR_AttributeNotApplicableOnValueType, "EnsuresNotNull").WithArguments("EnsuresNotNullAttribute").WithLocation(5, 32) - ); + c.VerifyDiagnostics(); - VerifyAnnotations(c, "C.Bad", None); + VerifyAnnotations(c, "C.Bad", EnsuresNotNull); VerifyAnnotations(c, "C.ThrowIfNull", EnsuresNotNull); } From aea98d8c2d7976f58eff9b174a9107bc530ce341 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Mon, 14 May 2018 14:17:27 -0700 Subject: [PATCH 16/18] Address PR feedback from Aleksey --- .../Portable/FlowAnalysis/NullableWalker.cs | 1 + .../Portable/Symbols/ParameterSymbol.cs | 5 + .../Source/SourceClonedParameterSymbol.cs | 3 +- .../Symbols/Wrapped/WrappedParameterSymbol.cs | 2 +- .../Semantic/Semantics/StaticNullChecking.cs | 310 ++++++++++++++++++ 5 files changed, 318 insertions(+), 3 deletions(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 955127b5d0fa5..3a1afd333c436 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -1863,6 +1863,7 @@ private void VisitArgumentsEvaluateHonoringAnnotations( else if (notNullWhenFalse) { // We'll use the WhenTrue/False states to represent whether the invocation returns true/false + // PROTOTYPE(NullableReferenceTypes): Consider splitting for the entire method, not just once the first annotated argument is encountered Split(); // The variable in this slot is not null when the method returns false diff --git a/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs index e17e84388923a..8f130640373b0 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs @@ -387,6 +387,11 @@ internal sealed override ObsoleteAttributeData ObsoleteAttributeData internal abstract AttributeAnnotations FlowAnalysisAnnotations { get; } + /// + /// If there are any annotations on the member (not just that parameter), then memberHasExtra is true. The purpose is to ensure + /// that if some annotations are present on the member, then annotations win over the attributes on the member in all positions. + /// That could mean removing an attribute. + /// protected (bool memberHasExtra, AttributeAnnotations annotations) TryGetExtraAttributeAnnotations() { ParameterSymbol originalParameter = this.OriginalDefinition; diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs index 60b28a2122908..e80348dab543c 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs @@ -153,8 +153,7 @@ internal override bool IsCallerMemberName internal override AttributeAnnotations FlowAnalysisAnnotations { - // PROTOTYPE(NullableReferenceTypes): Make sure this is covered by test - get { return _originalParam.FlowAnalysisAnnotations; } + get { return AttributeAnnotations.None; } } #endregion diff --git a/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedParameterSymbol.cs index 240da630b9095..c8d016c50725e 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedParameterSymbol.cs @@ -155,7 +155,7 @@ internal override bool IsCallerMemberName internal override AttributeAnnotations FlowAnalysisAnnotations { - // PROTOTYPE(NullableReferenceTypes): Ensure this is covered and consider moving to leaf types + // PROTOTYPE(NullableReferenceTypes): Consider moving to leaf types get { return _underlyingParameter.FlowAnalysisAnnotations; } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs index 462d05d8a857c..4466e27ab585c 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs @@ -5522,6 +5522,82 @@ public void Main(string? s) VerifyAnnotationsAndMetadata(c, "C.MyIsNullOrEmpty", NotNullWhenFalse); } + [Fact] + public void NotNullWhenFalse_ComparedToTrue() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +public class C +{ + public void Main(string? s) + { + if (MyIsNullOrEmpty(s) == true) + { + s.ToString(); // warn + } + else + { + s.ToString(); // ok + } + + s.ToString(); // warn 2 + } + public static bool MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; +} +" + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); + + // PROTOTYPE(NullableReferenceTypes): there should only be two diagnostics + c.VerifyDiagnostics( + // (9,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(9, 13), + // (13,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // ok + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(13, 13), + // (16,9): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(16, 9) + ); + } + + [Fact] + public void NotNullWhenFalse_ComparedToFalse() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +public class C +{ + public void Main(string? s) + { + if (false == MyIsNullOrEmpty(s)) + { + s.ToString(); // warn + } + else + { + s.ToString(); // ok + } + + s.ToString(); // warn 2 + } + public static bool MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; +} +" + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); + + // PROTOTYPE(NullableReferenceTypes): there should only be two diagnostics + c.VerifyDiagnostics( + // (9,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(9, 13), + // (13,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // ok + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(13, 13), + // (16,9): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(16, 9) + ); + } + [Fact] public void NotNullWhenFalse_RequiresBoolReturn() { @@ -5538,6 +5614,35 @@ public class C VerifyAnnotations(c, "C.MyIsNullOrEmpty", NotNullWhenFalse); } + [Fact] + public void NotNullWhenFalse_RequiresBoolReturn_OnGenericMethod() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +public class C +{ + void M(string? s) + { + if (MyIsNullOrEmpty(s, true)) + { + s.ToString(); // warn + } + else + { + s.ToString(); // ok + } + } + public static T MyIsNullOrEmpty([NotNullWhenFalse] string? s, T t) => throw null; +} +" + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (9,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(9, 13) + ); + } + [Fact] public void NotNullWhenFalse_OnTwoParameters() { @@ -6367,6 +6472,63 @@ void Main(string? s) VerifyAnnotationsAndMetadata(c, "C.ThrowIfNull", None, EnsuresNotNull); } + [Fact] + public void EnsuresNotNull_Generic_WithRefType() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +public class C +{ + void Main(string? s) + { + ThrowIfNull(s); + s.ToString(); // ok + } + public static void ThrowIfNull([EnsuresNotNull] T s) => throw null; +} +" + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics(); + } + + [Fact] + public void EnsuresNotNull_Generic_WithValueType() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +public class C +{ + void Main(int s) + { + ThrowIfNull(s); + s.ToString(); + } + public static void ThrowIfNull([EnsuresNotNull] T s) => throw null; +} +" + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics(); + } + + [Fact] + public void EnsuresNotNull_Generic_WithUnconstrainedGenericType() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +public class C +{ + void M(U u) + { + ThrowIfNull(u); + u.ToString(); + } + public static void ThrowIfNull([EnsuresNotNull] T s) => throw null; +} +" + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics(); + } + [Fact] public void EnsuresNotNull_OnInterface() { @@ -6589,6 +6751,154 @@ void M(T t) VerifyAnnotationsAndMetadata(c, "C.ThrowIfNull", EnsuresNotNull); } + [Fact] + public void EnsuresNotNull_BeginInvoke() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +public delegate void Delegate([EnsuresNotNull] string? s); +public class C +{ + void M(Delegate d, string? s) + { + s.ToString(); // warn + d.BeginInvoke(s, null, null); + s.ToString(); // warn 2 + } +} +" + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (8,9): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(8, 9), + // (9,26): warning CS8625: Cannot convert null literal to non-nullable reference or unconstrained type parameter. + // d.BeginInvoke(s, null, null); + Diagnostic(ErrorCode.WRN_NullAsNonNullable, "null").WithLocation(9, 26), + // (9,32): warning CS8625: Cannot convert null literal to non-nullable reference or unconstrained type parameter. + // d.BeginInvoke(s, null, null); + Diagnostic(ErrorCode.WRN_NullAsNonNullable, "null").WithLocation(9, 32), + // (10,9): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // ok + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(10, 9) + ); + } + + [Fact] + public void EnsuresNotNull_BackEffect() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +public class C +{ + void M(string? s1, string? s2) + { + ThrowIfNull(s2 = s1, s1); + s2.ToString(); // warn + } + public static void ThrowIfNull(string? x1, [EnsuresNotNull] string? x2) => throw null; +} +" + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); + + // PROTOTYPE(NullableReferenceTypes): Should we be able to trace that s2 was assigned a non-null value? + c.VerifyDiagnostics( + // (9,9): warning CS8602: Possible dereference of a null reference. + // s2.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s2").WithLocation(9, 9) + ); + } + + [Fact] + public void EnsuresNotNull_ForwardEffect() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +public class C +{ + void M(string? s1, string? s2) + { + ThrowIfNull(s1, s2 = s1); + s2.ToString(); // ok + } + public static void ThrowIfNull([EnsuresNotNull] string? x1, string? x2) => throw null; +} +" + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics(); + } + + [Fact] + public void EnsuresNotNull_ForwardEffect2() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +public class C +{ + void M(string? s1) + { + ThrowIfNull(s1, s1 = null); + s1.ToString(); // warn + } + public static void ThrowIfNull([EnsuresNotNull] string? x1, string? x2) => throw null; +} +" + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (8,9): warning CS8602: Possible dereference of a null reference. + // s1.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s1").WithLocation(8, 9) + ); + } + + [Fact] + public void EnsuresNotNull_ForwardEffect3() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +public class C +{ + void M(string? s1, string? s2) + { + ThrowIfNull(s1 = null, s2 = s1, s1 = """", s1); + s2.ToString(); // warn + } + public static void ThrowIfNull(string? x1, string? x2, string? x3, [EnsuresNotNull] string? x4) => throw null; +} +" + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyDiagnostics( + // (8,9): warning CS8602: Possible dereference of a null reference. + // s2.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s2").WithLocation(8, 9) + ); + } + + [Fact] + public void EnsuresNotNull_TypeInference() + { + // PROTOTYPE(NullableReferenceTypes): This test raises the question of flowing information from annotations into the inferred type + // But the issue is currently masked by a broader problem with `out var` and nullability + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +public class C +{ + void M(string? s1) + { + ThrowIfNull(s1, out var s2); + s2/*T:string!*/.ToString(); + } + public static void ThrowIfNull([EnsuresNotNull] T x1, out T x2) => throw null; +} +" + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); + + c.VerifyTypes(); + c.VerifyDiagnostics( + // (7,21): warning CS8604: Possible null reference argument for parameter 'x1' in 'void C.ThrowIfNull(string x1, out string x2)'. + // ThrowIfNull(s1, out var s2); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "s1").WithArguments("x1", "void C.ThrowIfNull(string x1, out string x2)").WithLocation(7, 21) + ); + } + [Fact] public void EnsuresNotNull_ConditionalMethodInReleaseMode() { From fa24db3a76e5c2c7df885421962063c45e549b7a Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Mon, 14 May 2018 15:12:18 -0700 Subject: [PATCH 17/18] Missed change --- .../Test/Semantic/Semantics/StaticNullChecking.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs index 4466e27ab585c..2f1ffcfd3b380 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs @@ -6526,7 +6526,11 @@ void M(U u) } " + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8); - c.VerifyDiagnostics(); + c.VerifyDiagnostics( + // (8,9): warning CS8602: Possible dereference of a null reference. + // u.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "u").WithLocation(8, 9) + ); } [Fact] @@ -6802,9 +6806,9 @@ void M(string? s1, string? s2) // PROTOTYPE(NullableReferenceTypes): Should we be able to trace that s2 was assigned a non-null value? c.VerifyDiagnostics( - // (9,9): warning CS8602: Possible dereference of a null reference. + // (8,9): warning CS8602: Possible dereference of a null reference. // s2.ToString(); // warn - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s2").WithLocation(9, 9) + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s2").WithLocation(8, 9) ); } From 266b24c1d1470247cd26f092555d17d9f421d2e4 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Mon, 14 May 2018 15:26:29 -0700 Subject: [PATCH 18/18] Fix comment and add test --- .../Portable/CSharpResources.Designer.cs | 18 ++++---- .../CSharp/Portable/CSharpResources.resx | 4 +- .../Portable/xlf/CSharpResources.cs.xlf | 10 ++--- .../Portable/xlf/CSharpResources.de.xlf | 10 ++--- .../Portable/xlf/CSharpResources.es.xlf | 10 ++--- .../Portable/xlf/CSharpResources.fr.xlf | 10 ++--- .../Portable/xlf/CSharpResources.it.xlf | 10 ++--- .../Portable/xlf/CSharpResources.ja.xlf | 10 ++--- .../Portable/xlf/CSharpResources.ko.xlf | 10 ++--- .../Portable/xlf/CSharpResources.pl.xlf | 10 ++--- .../Portable/xlf/CSharpResources.pt-BR.xlf | 10 ++--- .../Portable/xlf/CSharpResources.ru.xlf | 10 ++--- .../Portable/xlf/CSharpResources.tr.xlf | 10 ++--- .../Portable/xlf/CSharpResources.zh-Hans.xlf | 10 ++--- .../Portable/xlf/CSharpResources.zh-Hant.xlf | 10 ++--- .../Semantic/Semantics/StaticNullChecking.cs | 42 ++++++++++++++++++- 16 files changed, 116 insertions(+), 78 deletions(-) diff --git a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs index 665592bb7cdba..c3532eadac9b6 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs +++ b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs @@ -736,15 +736,6 @@ internal static string ERR_AttributeParameterRequired2 { } } - /// - /// Looks up a localized string similar to The '{0}' attribute is only applicable on members that return a boolean type.. - /// - internal static string ERR_AttributeRequiresBoolReturn { - get { - return ResourceManager.GetString("ERR_AttributeRequiresBoolReturn", resourceCulture); - } - } - /// /// Looks up a localized string similar to Attributes are not allowed on local function parameters or type parameters. /// @@ -7315,6 +7306,15 @@ internal static string ERR_NotConstantExpression { } } + /// + /// Looks up a localized string similar to The ! operator can only be applied to reference types.. + /// + internal static string ERR_NotNullableOperatorNotReferenceType { + get { + return ResourceManager.GetString("ERR_NotNullableOperatorNotReferenceType", resourceCulture); + } + } + /// /// Looks up a localized string similar to '{0}' is of type '{1}'. A const field of a reference type other than string can only be initialized with null.. /// diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 4daafdcc80072..f57710f04e80e 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -5357,8 +5357,8 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed. - - The '{0}' attribute is only applicable on members that return a boolean type. + + The ! operator can only be applied to reference types. A void or int returning entry point cannot be async diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index a40ac7ed3268e..a7ef160a6f997 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -8975,6 +8975,11 @@ Pokud chcete odstranit toto varování, můžete místo toho použít /reference Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed. + + The ! operator can only be applied to reference types. + The ! operator can only be applied to reference types. + + No best nullability for operands of conditional expression '{0}' and '{1}'. No best nullability for operands of conditional expression '{0}' and '{1}'. @@ -8995,11 +9000,6 @@ Pokud chcete odstranit toto varování, můžete místo toho použít /reference Converting null literal or possible null value to non-nullable type. - - The '{0}' attribute is only applicable on members that return a boolean type. - The '{0}' attribute is only applicable on members that return a boolean type. - - \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 1b81e8feab33f..2c0a00109fb47 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -8975,6 +8975,11 @@ Um die Warnung zu beheben, können Sie stattdessen /reference verwenden (Einbett Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed. + + The ! operator can only be applied to reference types. + The ! operator can only be applied to reference types. + + No best nullability for operands of conditional expression '{0}' and '{1}'. No best nullability for operands of conditional expression '{0}' and '{1}'. @@ -8995,11 +9000,6 @@ Um die Warnung zu beheben, können Sie stattdessen /reference verwenden (Einbett Converting null literal or possible null value to non-nullable type. - - The '{0}' attribute is only applicable on members that return a boolean type. - The '{0}' attribute is only applicable on members that return a boolean type. - - \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index 07213cc6ab73f..4e961679401b7 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -8975,6 +8975,11 @@ Para eliminar la advertencia puede usar /reference (establezca la propiedad Embe Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed. + + The ! operator can only be applied to reference types. + The ! operator can only be applied to reference types. + + No best nullability for operands of conditional expression '{0}' and '{1}'. No best nullability for operands of conditional expression '{0}' and '{1}'. @@ -8995,11 +9000,6 @@ Para eliminar la advertencia puede usar /reference (establezca la propiedad Embe Converting null literal or possible null value to non-nullable type. - - The '{0}' attribute is only applicable on members that return a boolean type. - The '{0}' attribute is only applicable on members that return a boolean type. - - \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 0c6f684937a3d..3e1357e789929 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -8975,6 +8975,11 @@ Pour supprimer l'avertissement, vous pouvez utiliser la commande /reference (dé Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed. + + The ! operator can only be applied to reference types. + The ! operator can only be applied to reference types. + + No best nullability for operands of conditional expression '{0}' and '{1}'. No best nullability for operands of conditional expression '{0}' and '{1}'. @@ -8995,11 +9000,6 @@ Pour supprimer l'avertissement, vous pouvez utiliser la commande /reference (dé Converting null literal or possible null value to non-nullable type. - - The '{0}' attribute is only applicable on members that return a boolean type. - The '{0}' attribute is only applicable on members that return a boolean type. - - \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 2b43b20f3b4f6..771122be8f266 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -8975,6 +8975,11 @@ Per rimuovere l'avviso, è invece possibile usare /reference (impostare la propr Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed. + + The ! operator can only be applied to reference types. + The ! operator can only be applied to reference types. + + No best nullability for operands of conditional expression '{0}' and '{1}'. No best nullability for operands of conditional expression '{0}' and '{1}'. @@ -8995,11 +9000,6 @@ Per rimuovere l'avviso, è invece possibile usare /reference (impostare la propr Converting null literal or possible null value to non-nullable type. - - The '{0}' attribute is only applicable on members that return a boolean type. - The '{0}' attribute is only applicable on members that return a boolean type. - - \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 57e85fcf2d52b..6bc8ab8882d63 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -8975,6 +8975,11 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed. + + The ! operator can only be applied to reference types. + The ! operator can only be applied to reference types. + + No best nullability for operands of conditional expression '{0}' and '{1}'. No best nullability for operands of conditional expression '{0}' and '{1}'. @@ -8995,11 +9000,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Converting null literal or possible null value to non-nullable type. - - The '{0}' attribute is only applicable on members that return a boolean type. - The '{0}' attribute is only applicable on members that return a boolean type. - - \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index 2819d74494f00..9fb16366280a5 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -8975,6 +8975,11 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed. + + The ! operator can only be applied to reference types. + The ! operator can only be applied to reference types. + + No best nullability for operands of conditional expression '{0}' and '{1}'. No best nullability for operands of conditional expression '{0}' and '{1}'. @@ -8995,11 +9000,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Converting null literal or possible null value to non-nullable type. - - The '{0}' attribute is only applicable on members that return a boolean type. - The '{0}' attribute is only applicable on members that return a boolean type. - - \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index 65db0e9e1d830..85eec0b0b3b95 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -8975,6 +8975,11 @@ Aby usunąć ostrzeżenie, możesz zamiast tego użyć opcji /reference (ustaw w Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed. + + The ! operator can only be applied to reference types. + The ! operator can only be applied to reference types. + + No best nullability for operands of conditional expression '{0}' and '{1}'. No best nullability for operands of conditional expression '{0}' and '{1}'. @@ -8995,11 +9000,6 @@ Aby usunąć ostrzeżenie, możesz zamiast tego użyć opcji /reference (ustaw w Converting null literal or possible null value to non-nullable type. - - The '{0}' attribute is only applicable on members that return a boolean type. - The '{0}' attribute is only applicable on members that return a boolean type. - - \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index a8ce07c7475ab..862ee2313cfe6 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -8975,6 +8975,11 @@ Para incorporar informações de tipo de interoperabilidade para os dois assembl Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed. + + The ! operator can only be applied to reference types. + The ! operator can only be applied to reference types. + + No best nullability for operands of conditional expression '{0}' and '{1}'. No best nullability for operands of conditional expression '{0}' and '{1}'. @@ -8995,11 +9000,6 @@ Para incorporar informações de tipo de interoperabilidade para os dois assembl Converting null literal or possible null value to non-nullable type. - - The '{0}' attribute is only applicable on members that return a boolean type. - The '{0}' attribute is only applicable on members that return a boolean type. - - \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 688a2dbe146f0..9df028701c569 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -8975,6 +8975,11 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed. + + The ! operator can only be applied to reference types. + The ! operator can only be applied to reference types. + + No best nullability for operands of conditional expression '{0}' and '{1}'. No best nullability for operands of conditional expression '{0}' and '{1}'. @@ -8995,11 +9000,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Converting null literal or possible null value to non-nullable type. - - The '{0}' attribute is only applicable on members that return a boolean type. - The '{0}' attribute is only applicable on members that return a boolean type. - - \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index a11853bcd07de..206e251c06c5f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -8975,6 +8975,11 @@ Uyarıyı kaldırmak için, /reference kullanabilirsiniz (Birlikte Çalışma T Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed. + + The ! operator can only be applied to reference types. + The ! operator can only be applied to reference types. + + No best nullability for operands of conditional expression '{0}' and '{1}'. No best nullability for operands of conditional expression '{0}' and '{1}'. @@ -8995,11 +9000,6 @@ Uyarıyı kaldırmak için, /reference kullanabilirsiniz (Birlikte Çalışma T Converting null literal or possible null value to non-nullable type. - - The '{0}' attribute is only applicable on members that return a boolean type. - The '{0}' attribute is only applicable on members that return a boolean type. - - \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index 338b6712c80d3..07b4cc9ce568f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -8975,6 +8975,11 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed. + + The ! operator can only be applied to reference types. + The ! operator can only be applied to reference types. + + No best nullability for operands of conditional expression '{0}' and '{1}'. No best nullability for operands of conditional expression '{0}' and '{1}'. @@ -8995,11 +9000,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Converting null literal or possible null value to non-nullable type. - - The '{0}' attribute is only applicable on members that return a boolean type. - The '{0}' attribute is only applicable on members that return a boolean type. - - \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 8487de3209940..299d1526958f8 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -8975,6 +8975,11 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Explicit application of 'System.Runtime.CompilerServices.NullableAttribute' is not allowed. + + The ! operator can only be applied to reference types. + The ! operator can only be applied to reference types. + + No best nullability for operands of conditional expression '{0}' and '{1}'. No best nullability for operands of conditional expression '{0}' and '{1}'. @@ -8995,11 +9000,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Converting null literal or possible null value to non-nullable type. - - The '{0}' attribute is only applicable on members that return a boolean type. - The '{0}' attribute is only applicable on members that return a boolean type. - - \ No newline at end of file diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs index 2f1ffcfd3b380..bd43b0c8c95ce 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs @@ -5523,7 +5523,7 @@ public void Main(string? s) } [Fact] - public void NotNullWhenFalse_ComparedToTrue() + public void NotNullWhenFalse_EqualsTrue() { CSharpCompilation c = CreateCompilation(@" using System.Runtime.CompilerServices; @@ -5561,7 +5561,7 @@ public void Main(string? s) } [Fact] - public void NotNullWhenFalse_ComparedToFalse() + public void NotNullWhenFalse_EqualsFalse() { CSharpCompilation c = CreateCompilation(@" using System.Runtime.CompilerServices; @@ -5570,6 +5570,44 @@ public class C public void Main(string? s) { if (false == MyIsNullOrEmpty(s)) + { + s.ToString(); // ok + } + else + { + s.ToString(); // warn + } + + s.ToString(); // warn 2 + } + public static bool MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null; +} +" + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8); + + // PROTOTYPE(NullableReferenceTypes): there should only be two diagnostics + c.VerifyDiagnostics( + // (9,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // ok + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(9, 13), + // (13,13): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(13, 13), + // (16,9): warning CS8602: Possible dereference of a null reference. + // s.ToString(); // warn 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(16, 9) + ); + } + + [Fact] + public void NotNullWhenFalse_NotEqualsFalse() + { + CSharpCompilation c = CreateCompilation(@" +using System.Runtime.CompilerServices; +public class C +{ + public void Main(string? s) + { + if (false != MyIsNullOrEmpty(s)) { s.ToString(); // warn }