diff --git a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs
index 1904045ddddb9..c3532eadac9b6 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..
///
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 fa81d9b3e96e4..3a1afd333c436 100644
--- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs
+++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs
@@ -59,6 +59,12 @@ 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()
{
_variableTypes.Free();
@@ -464,7 +470,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)
@@ -1578,12 +1587,14 @@ public override BoundNode VisitCall(BoundCall node)
{
ImmutableArray refKindsOpt = node.ArgumentRefKindsOpt;
ImmutableArray arguments = RemoveArgumentConversions(node.Arguments, refKindsOpt);
- ImmutableArray results = VisitArgumentsEvaluate(arguments, refKindsOpt, node.Expanded);
+ 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(arguments, refKindsOpt, method.Parameters, node.ArgsToParamsOpt, node.Expanded, results);
+ VisitArgumentsWarn(arguments, refKindsOpt, method.Parameters, argsToParamsOpt, node.Expanded, results);
}
UpdateStateForCall(node);
@@ -1594,7 +1605,6 @@ public override BoundNode VisitCall(BoundCall node)
ReplayReadsAndWrites(localFunc, node.Syntax, writes: true);
}
- Debug.Assert(!IsConditionalState);
//if (this.State.Reachable) // PROTOTYPE(NullableReferenceTypes): Consider reachability?
{
_result = method.ReturnType;
@@ -1603,6 +1613,49 @@ public override BoundNode VisitCall(BoundCall node)
return null;
}
+ ///
+ /// For each argument, figure out if its corresponding parameter is annotated with NotNullWhenFalse or
+ /// EnsuresNotNull.
+ ///
+ private static ImmutableArray GetAnnotations(int numArguments,
+ bool expanded, ImmutableArray parameters, ImmutableArray argsToParamsOpt)
+ {
+ 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
+ if ((annotations & AttributeAnnotations.NotNullWhenFalse) != 0 &&
+ parameter.ContainingSymbol.GetTypeOrReturnType().SpecialType != SpecialType.System_Boolean)
+ {
+ annotations &= ~AttributeAnnotations.NotNullWhenFalse;
+ }
+
+ // We'll ignore EnsuresNotNull that is misused in metadata
+ if ((annotations & AttributeAnnotations.EnsuresNotNull) != 0 &&
+ (parameter.Type?.IsValueType != false || parameter.IsParams))
+ {
+ annotations &= ~AttributeAnnotations.EnsuresNotNull;
+ }
+
+ if (annotations != AttributeAnnotations.None && builder == null)
+ {
+ builder = ArrayBuilder.GetInstance(numArguments);
+ builder.AddMany(AttributeAnnotations.None, i);
+ }
+
+ if (builder != null)
+ {
+ builder.Add(annotations);
+ }
+ }
+
+ return builder == null ? default : builder.ToImmutableAndFree();
+ }
+
// 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).
@@ -1661,7 +1714,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)
@@ -1673,7 +1726,31 @@ 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);
+
+ // We do a second pass through the arguments, ignoring any diagnostics produced, but honoring the annotations,
+ // to get the proper result state.
+ ImmutableArray annotations = GetAnnotations(arguments.Length,
+ expanded, parameters, argsToParamsOpt);
+
+ if (!annotations.IsDefault)
+ {
+ this.SetState(savedState);
+ VisitArgumentsEvaluateHonoringAnnotations(arguments, refKindsOpt, annotations);
+ }
+
+ return results;
+ }
+
+ private ImmutableArray VisitArgumentsEvaluate(
+ ImmutableArray arguments,
+ ImmutableArray refKindsOpt)
{
Debug.Assert(!IsConditionalState);
int n = arguments.Length;
@@ -1684,22 +1761,118 @@ private ImmutableArray VisitArgumentsEvaluate(
var builder = ArrayBuilder.GetInstance(n);
for (int i = 0; i < n; i++)
{
- RefKind refKind = GetRefKind(refKindsOpt, i);
- var argument = arguments[i];
- if (refKind != RefKind.Out)
+ 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 annotations)
+ {
+ Debug.Assert(!IsConditionalState);
+
+ bool saveDisableDiagnostics = _disableDiagnostics;
+ _disableDiagnostics = true;
+
+ Debug.Assert(annotations.Length == arguments.Length);
+
+ for (int i = 0; i < arguments.Length; i++)
+ {
+ if (this.IsConditionalState)
{
- // PROTOTYPE(NullReferenceTypes): `ref` arguments should be treated as l-values
- // for assignment. See `ref x3` in StaticNullChecking.PassingParameters_01.
- VisitRvalue(argument);
+ // We could be in a conditional state because of NotNullWhenFalse annotation
+ // Then WhenTrue/False states correspond to the invocation returning true/false
+
+ // 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();
+ LocalState whenFalse = this.StateWhenFalse.Clone();
+
+ 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);
}
else
{
- VisitLvalue(argument);
+ VisitArgumentEvaluate(arguments, refKindsOpt, i);
+ }
+
+ var argument = arguments[i];
+ if (argument.Type?.IsReferenceType != true)
+ {
+ continue;
+ }
+
+ int slot = MakeSlot(argument);
+ if (slot <= 0)
+ {
+ continue;
+ }
+
+ AttributeAnnotations annotation = annotations[i];
+ bool notNullWhenFalse = (annotation & AttributeAnnotations.NotNullWhenFalse) != 0;
+ bool ensuresNotNull = (annotation & AttributeAnnotations.EnsuresNotNull) != 0;
+
+ 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
+ // 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
+ this.StateWhenFalse[slot] = true;
}
- builder.Add(_result);
}
+
_result = _invalidType;
- return builder.ToImmutableAndFree();
+ _disableDiagnostics = saveDisableDiagnostics;
}
private void VisitArgumentsWarn(
@@ -1813,7 +1986,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;
@@ -3068,7 +3244,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;
@@ -3148,7 +3324,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);
@@ -3176,7 +3352,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;
}
@@ -3255,7 +3431,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());
@@ -3421,7 +3597,7 @@ public override BoundNode VisitThrowExpression(BoundThrowExpression node)
return result;
}
-#endregion Visitors
+ #endregion Visitors
protected override string Dump(LocalState state)
{
@@ -3573,6 +3749,19 @@ public bool Reachable
return _knownNullState.Capacity > 0;
}
}
+
+ 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();
+ }
}
}
}
diff --git a/src/Compilers/CSharp/Portable/Symbols/ExtraAnnotations.cs b/src/Compilers/CSharp/Portable/Symbols/ExtraAnnotations.cs
new file mode 100644
index 0000000000000..86b28364d801f
--- /dev/null
+++ b/src/Compilers/CSharp/Portable/Symbols/ExtraAnnotations.cs
@@ -0,0 +1,202 @@
+// 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
+ {
+ // 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)", 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>
+ {
+ { "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)
+ {
+ var pooledBuilder = PooledStringBuilder.GetInstance();
+
+ StringBuilder builder = pooledBuilder.Builder;
+ Add(paramInfo[0].Type, builder);
+ builder.Append(' ');
+
+ Add(method.ContainingType, builder);
+ builder.Append('.');
+
+ builder.Append(method.Name);
+ builder.Append('(');
+
+ for (int i = 1; i < paramInfo.Length; i++)
+ {
+ Add(paramInfo[i].Type, builder);
+ if (i < paramInfo.Length - 1)
+ {
+ builder.Append(", ");
+ }
+ }
+
+ builder.Append(')');
+ return pooledBuilder.ToStringAndFree();
+
+ // PROTOTYPE(NullableReferenceTypes): Many cases are not yet handled
+ // generic type args
+ // ref kind
+ // 'this'
+ // static vs. instance
+ // use assembly qualified name format (used in metadata) rather than symbol display?
+ }
+
+ internal static string MakeMethodKey(MethodSymbol method)
+ {
+ var containingType = method.ContainingSymbol as TypeSymbol;
+ if (containingType is null)
+ {
+ return null;
+ }
+
+ var pooledBuilder = PooledStringBuilder.GetInstance();
+
+ StringBuilder builder = pooledBuilder.Builder;
+ Add(method.ReturnType.TypeSymbol, builder);
+ builder.Append(' ');
+
+ Add(containingType, builder);
+ builder.Append('.');
+
+ builder.Append(method.Name);
+ builder.Append('(');
+
+ var parameterTypes = method.ParameterTypes;
+ for (int i = 0; i < parameterTypes.Length; i++)
+ {
+ Add(parameterTypes[i].TypeSymbol, builder);
+ if (i < parameterTypes.Length - 1)
+ {
+ builder.Append(", ");
+ }
+ }
+
+ builder.Append(')');
+ return pooledBuilder.ToStringAndFree();
+
+ // PROTOTYPE(NullableReferenceTypes): Many cases are not yet handled
+ // generic type args
+ // ref kind
+ // 'this'
+ // static vs. instance
+ // use assembly qualified name format (used in metadata) rather than symbol display?
+ }
+
+ private static ImmutableArray> Array(params ImmutableArray[] values)
+ => values.ToImmutableArray();
+
+ private static ImmutableArray Array(params AttributeAnnotations[] values)
+ => values.ToImmutableArray();
+
+ private static ImmutableArray Nullable(params bool[] values)
+ {
+ Debug.Assert(values.Length > 0);
+ return values.ToImmutableArray();
+ }
+
+ internal static ImmutableArray> GetExtraAnnotations(string key)
+ {
+ if (key is null)
+ {
+ return default;
+ }
+
+ if (!Annotations.TryGetValue(key, out var flags))
+ {
+ return default;
+ }
+
+ return flags;
+ }
+
+ private static void Add(TypeSymbol type, StringBuilder builder)
+ => builder.Append(
+ type.ToDisplayString(
+ SymbolDisplayFormat.CSharpErrorMessageFormat
+ .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)));
+
+ ///
+ /// index 0 is used for return type
+ /// other parameters follow
+ ///
+ internal static (bool hasAny, AttributeAnnotations annotations) GetExtraAttributes(string key, int parameterIndex)
+ {
+ if (key is null)
+ {
+ return (false, default);
+ }
+
+ if (!Attributes.TryGetValue(key, out var extraAttributes))
+ {
+ 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 value;
+ }
+ }
+}
diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/ExternalAnnotations.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/ExternalAnnotations.cs
deleted file mode 100644
index ef9ee57835704..0000000000000
--- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/ExternalAnnotations.cs
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.Diagnostics;
-using System.Text;
-using Microsoft.CodeAnalysis.PooledObjects;
-
-namespace Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE
-{
- // 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
- {
- // APIs that are useful to annotate:
- // 1) don't accept null input
- // 2) return a reference type
- 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)) },
- }.ToImmutableDictionary();
-
- internal static string MakeMethodKey(PEMethodSymbol method, ParamInfo[] paramInfo)
- {
- var pooledBuilder = PooledStringBuilder.GetInstance();
-
- StringBuilder builder = pooledBuilder.Builder;
- Add(paramInfo[0].Type, builder);
- builder.Append(' ');
-
- Add(method.ContainingType, builder);
- builder.Append('.');
-
- builder.Append(method.Name);
- builder.Append('(');
-
- for (int i = 1; i < paramInfo.Length; i++)
- {
- Add(paramInfo[i].Type, builder);
- if (i < paramInfo.Length - 1)
- {
- builder.Append(", ");
- }
- }
-
- builder.Append(')');
- return pooledBuilder.ToStringAndFree();
-
- // PROTOTYPE(NullableReferenceTypes): Many cases are not yet handled
- // generic type args
- // ref kind
- // 'this'
- // static vs. instance
- // 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)
- => values.ToImmutableArray();
-
- private static ImmutableArray Nullable(params bool[] values)
- {
- Debug.Assert(values.Length > 0);
- return values.ToImmutableArray();
- }
-
- internal static ImmutableArray> GetExtraAnnotations(string key)
- {
- if (!Annotations.TryGetValue(key, out var flags))
- {
- return default;
- }
-
- return flags;
- }
-
- private static void Add(TypeSymbol type, StringBuilder builder)
- => builder.Append(
- type.ToDisplayString(
- SymbolDisplayFormat.CSharpErrorMessageFormat
- .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)));
- }
-}
diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs
index b128edded1722..dcc40d089c8ae 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,44 @@ internal override bool IsCallerMemberName
}
}
+ internal override AttributeAnnotations FlowAnalysisAnnotations
+ {
+ get
+ {
+ const WellKnownAttributeFlags notNullWhenFalse = WellKnownAttributeFlags.NotNullWhenFalse;
+ const WellKnownAttributeFlags ensuresNotNull = WellKnownAttributeFlags.EnsuresNotNull;
+
+ // PROTOTYPE(NullableReferenceTypes): the flags could be packed more
+ if (!_packedFlags.TryGetWellKnownAttribute(notNullWhenFalse, out bool hasNotNullWhenFalse) ||
+ !_packedFlags.TryGetWellKnownAttribute(ensuresNotNull, out bool hasEnsuresNotNull))
+ {
+ (bool memberHasAny, AttributeAnnotations annotations) = TryGetExtraAttributeAnnotations();
+
+ 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);
+ }
+
+ _packedFlags.SetWellKnownAttribute(notNullWhenFalse, hasNotNullWhenFalse);
+ _packedFlags.SetWellKnownAttribute(ensuresNotNull, hasEnsuresNotNull);
+
+ if (memberHasAny)
+ {
+ return annotations;
+ }
+ }
+
+ return AttributeAnnotations.None.With(notNullWhenFalse: hasNotNullWhenFalse, ensuresNotNull: hasEnsuresNotNull);
+ }
+ }
+
public override TypeSymbolWithAnnotations Type
{
get
@@ -653,7 +693,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..8f130640373b0 100644
--- a/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs
@@ -385,6 +385,27 @@ internal sealed override ObsoleteAttributeData ObsoleteAttributeData
internal abstract bool IsCallerMemberName { get; }
+ 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;
+ var containingMethod = originalParameter.ContainingSymbol as MethodSymbol;
+
+ if (containingMethod is null)
+ {
+ return (false, AttributeAnnotations.None);
+ }
+
+ string key = ExtraAnnotations.MakeMethodKey(containingMethod);
+ return ExtraAnnotations.GetExtraAttributes(key, this.Ordinal);
+ }
+
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..926d0121d9ef4 100644
--- a/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyParameterSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyParameterSymbol.cs
@@ -71,6 +71,8 @@ public override bool IsImplicitlyDeclared
internal override bool IsCallerMemberName { get { throw ExceptionUtilities.Unreachable; } }
+ internal override AttributeAnnotations FlowAnalysisAnnotations { 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..e80348dab543c 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs
@@ -151,6 +151,11 @@ internal override bool IsCallerMemberName
get { return _originalParam.IsCallerMemberName; }
}
+ internal override AttributeAnnotations FlowAnalysisAnnotations
+ {
+ get { return AttributeAnnotations.None; }
+ }
+
#endregion
}
}
diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs
index bc305c6134578..1bd2f254c26d2 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs
@@ -127,6 +127,25 @@ private bool HasCallerMemberNameAttribute
&& !HasCallerFilePathAttribute
&& HasCallerMemberNameAttribute;
+ internal override AttributeAnnotations FlowAnalysisAnnotations
+ {
+ get
+ {
+ (bool memberHasAny, AttributeAnnotations annotations) = TryGetExtraAttributeAnnotations();
+ if (memberHasAny)
+ {
+ // PROTOTYPE(NullableReferenceTypes): Make sure this is covered by test
+ return annotations;
+ }
+
+ CommonParameterWellKnownAttributeData attributeData = GetDecodedWellKnownAttributeData();
+
+ return AttributeAnnotations.None
+ .With(notNullWhenFalse: attributeData?.HasNotNullWhenFalseAttribute == true,
+ ensuresNotNull: attributeData?.HasEnsuresNotNullAttribute == true);
+ }
+ }
+
private ConstantValue DefaultSyntaxValue
{
get
@@ -605,6 +624,14 @@ 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))
+ {
+ arguments.GetOrCreateData().HasNotNullWhenFalseAttribute = true;
+ }
+ 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/SourceSimpleParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceSimpleParameterSymbol.cs
index 037efa5046799..176c2e34ace97 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceSimpleParameterSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceSimpleParameterSymbol.cs
@@ -82,6 +82,15 @@ internal override bool IsCallerMemberName
get { return false; }
}
+ internal override AttributeAnnotations FlowAnalysisAnnotations
+ {
+ get
+ {
+ (_, AttributeAnnotations annotations) = TryGetExtraAttributeAnnotations();
+ return annotations;
+ }
+ }
+
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..d6ab91dc3a47f 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Source/ThisParameterSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Source/ThisParameterSymbol.cs
@@ -114,6 +114,11 @@ internal override bool IsCallerMemberName
get { return false; }
}
+ internal override AttributeAnnotations FlowAnalysisAnnotations
+ {
+ get { return AttributeAnnotations.None; }
+ }
+
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..fc006dece1250 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedParameterSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedParameterSymbol.cs
@@ -115,6 +115,11 @@ internal override bool IsCallerMemberName
get { return false; }
}
+ internal override AttributeAnnotations FlowAnalysisAnnotations
+ {
+ get { return AttributeAnnotations.None; }
+ }
+
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..c8d016c50725e 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedParameterSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedParameterSymbol.cs
@@ -153,7 +153,13 @@ internal override bool IsCallerMemberName
get { return _underlyingParameter.IsCallerMemberName; }
}
- public override string GetDocumentationCommentXml(CultureInfo preferredCulture = null, bool expandIncludes = false, CancellationToken cancellationToken = default(CancellationToken))
+ internal override AttributeAnnotations FlowAnalysisAnnotations
+ {
+ // PROTOTYPE(NullableReferenceTypes): Consider moving to leaf types
+ get { return _underlyingParameter.FlowAnalysisAnnotations; }
+ }
+
+ 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 de793af9db693..bd43b0c8c95ce 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
{
@@ -70,6 +71,30 @@ class NullableOptOutAttribute : Attribute
public NullableOptOutAttribute(bool flag = true) { }
}
}
+";
+
+ private const string NotNullWhenFalseAttributeDefinition = @"
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Parameter,
+ AllowMultiple = false)]
+ public class NotNullWhenFalseAttribute : Attribute
+ {
+ public NotNullWhenFalseAttribute() { }
+ }
+}
+";
+
+ private const string EnsuresNotNullAttributeDefinition = @"
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Parameter,
+ AllowMultiple = false)]
+ public class EnsuresNotNullAttribute : Attribute
+ {
+ public EnsuresNotNullAttribute() { }
+ }
+}
";
[Fact]
@@ -5437,6 +5462,1591 @@ class CL1
);
}
+ [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
+{
+ public void SimpleWhere()
+ {
+ int[] numbers = { 1, 2, 3 };
+ var lowNums = from n in numbers
+ where n < 5
+ select n;
+ }
+}", parseOptions: TestOptions.Regular8);
+
+ c.VerifyDiagnostics(
+ // (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 NotNullWhenFalse_Simple()
+ {
+ CSharpCompilation c = CreateCompilation(@"
+using System.Runtime.CompilerServices;
+public class C
+{
+ public void Main(string? s)
+ {
+ if (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);
+
+ 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)
+ );
+
+ VerifyAnnotationsAndMetadata(c, "C.Main", None);
+ VerifyAnnotationsAndMetadata(c, "C.MyIsNullOrEmpty", NotNullWhenFalse);
+ }
+
+ [Fact]
+ public void NotNullWhenFalse_EqualsTrue()
+ {
+ 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_EqualsFalse()
+ {
+ CSharpCompilation c = CreateCompilation(@"
+using System.Runtime.CompilerServices;
+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
+ }
+ 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()
+ {
+ 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();
+
+ 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()
+ {
+ CSharpCompilation c = CreateCompilation(@"
+using System.Runtime.CompilerServices;
+public class C
+{
+ void Main(string? s, string? s2)
+ {
+ if (MyIsNullOrEmpty(s, s2))
+ {
+ s.ToString(); // warn 1
+ s2.ToString(); // warn 2
+ }
+ else
+ {
+ s.ToString(); // ok
+ s2.ToString(); // ok
+ }
+
+ s.ToString(); // warn 3
+ s2.ToString(); // warn 4
+ }
+ public static bool MyIsNullOrEmpty([NotNullWhenFalse] string? s, [NotNullWhenFalse] string? s2) => throw null;
+}
+" + NotNullWhenFalseAttributeDefinition, 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),
+ // (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)
+ );
+
+ VerifyAnnotationsAndMetadata(c, "C.MyIsNullOrEmpty", NotNullWhenFalse, NotNullWhenFalse);
+ }
+
+ [Fact]
+ public void NotNullWhenFalse_OnIndexer()
+ {
+ CSharpCompilation c = CreateCompilation(@"
+using System.Runtime.CompilerServices;
+class C
+{
+ void Main(string? s, int x)
+ {
+ if (this[s, x])
+ {
+ s.ToString(); // warn
+ }
+ else
+ {
+ s.ToString(); // ok
+ }
+
+ s.ToString(); // warn 2
+ }
+ public bool this[[NotNullWhenFalse] string? s, int x] => 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),
+ // (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_SecondArgumentDereferences()
+ {
+ CSharpCompilation c = CreateCompilation(@"
+using System.Runtime.CompilerServices;
+class C
+{
+ void Main(string? s)
+ {
+ if (Method(s, s.ToString())) // warn 1
+ {
+ s.ToString(); // warn 2
+ }
+ else
+ {
+ s.ToString(); // ok
+ }
+ }
+ static bool Method([NotNullWhenFalse] string? s, string s2) => throw null;
+}
+" + NotNullWhenFalseAttributeDefinition, parseOptions: TestOptions.Regular8);
+
+ c.VerifyDiagnostics(
+ // (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 NotNullWhenFalse_SecondArgumentAssigns()
+ {
+ CSharpCompilation c = CreateCompilation(@"
+using System.Runtime.CompilerServices;
+class C
+{
+ void Main(string? s)
+ {
+ if (Method(s, s = null))
+ {
+ s.ToString(); // warn 1
+ }
+ else
+ {
+ s.ToString(); // warn 2
+ }
+ }
+ static bool Method([NotNullWhenFalse] string? s, string? s2) => throw null;
+}
+" + NotNullWhenFalseAttributeDefinition, 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 NotNullWhenFalse_MissingAttribute()
+ {
+ CSharpCompilation c = CreateCompilation(@"
+class C
+{
+ void Main(string? s)
+ {
+ if (MyIsNullOrEmpty(s))
+ {
+ s.ToString(); // warn
+ }
+ else
+ {
+ s.ToString(); // warn 2
+ }
+
+ s.ToString(); // warn 3
+ }
+ static bool MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null;
+}
+", parseOptions: TestOptions.Regular8);
+
+ c.VerifyDiagnostics(
+ // (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(8, 13),
+ // (12,13): warning CS8602: Possible dereference of a null reference.
+ // 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 3
+ Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(15, 9)
+ );
+
+ VerifyAnnotations(c, "C.MyIsNullOrEmpty", None);
+ }
+
+ 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}'");
+ var actual = method.Parameters.Select(p => p.FlowAnalysisAnnotations);
+ Assert.Equal(expected, actual);
+ }
+
+ private void VerifyAnnotationsAndMetadata(Compilation compilation, string memberName, params AttributeAnnotations[] expected)
+ {
+ VerifyAnnotations(compilation, memberName, expected);
+
+ // Also verify from metadata
+ var compilation2 = CreateCompilation("", references: new[] { compilation.EmitToImageReference() });
+ VerifyAnnotations(compilation2, memberName, expected);
+ }
+
+ [Fact]
+ public void NotNullWhenFalse_BadAttribute()
+ {
+ CSharpCompilation c = CreateCompilation(@"
+using System.Runtime.CompilerServices;
+public class C
+{
+ void Main(string? s)
+ {
+ if (MyIsNullOrEmpty(s))
+ {
+ s.ToString(); // warn 1
+ }
+ else
+ {
+ s.ToString(); // warn 2
+ }
+ }
+ public static bool MyIsNullOrEmpty([NotNullWhenFalse(true)] string? s) => throw null;
+}
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Parameter,
+ AllowMultiple = false)]
+ public class NotNullWhenFalseAttribute : Attribute
+ {
+ public NotNullWhenFalseAttribute(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)
+ );
+
+ VerifyAnnotationsAndMetadata(c, "C.MyIsNullOrEmpty", None);
+ }
+
+ [Fact]
+ public void NotNullWhenFalse_InvertIf()
+ {
+ CSharpCompilation c = CreateCompilation(@"
+using System.Runtime.CompilerServices;
+public class C
+{
+ void Main(string? s)
+ {
+ if (!MyIsNullOrEmpty(s))
+ {
+ s.ToString(); // ok
+ }
+ else
+ {
+ s.ToString(); // warn
+ }
+ }
+ public static bool MyIsNullOrEmpty([NotNullWhenFalse] string? s) => throw null;
+}
+" + 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)
+ );
+
+ VerifyAnnotationsAndMetadata(c, "C.MyIsNullOrEmpty", NotNullWhenFalse);
+ }
+
+ [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()
+ {
+ 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 bool 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)
+ );
+
+ VerifyAnnotationsAndMetadata(c, "C.MyIsNullOrEmpty", NotNullWhenFalse);
+ }
+
+ [Fact]
+ public void NotNullWhenFalse_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
+{
+ public static bool MyIsNullOrEmpty(this C c, [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)
+ );
+
+ VerifyAnnotationsAndMetadata(c, "Extension.MyIsNullOrEmpty", None, NotNullWhenFalse);
+ }
+
+ [Fact]
+ public void NotNullWhenFalse_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)
+ );
+
+ VerifyAnnotationsAndMetadata(c, "System.String.IsNullOrEmpty", NotNullWhenFalse);
+ }
+
+ [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)
+ );
+
+ VerifyAnnotations(c, "System.String.IsNullOrEmpty", NotNullWhenFalse);
+ }
+
+ [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)
+ );
+
+ VerifyAnnotations(c, "System.String.IsNullOrWhiteSpace", NotNullWhenFalse);
+ }
+
+ [Fact]
+ public void NotNullWhenFalse_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)
+ );
+
+ VerifyAnnotationsAndMetadata(c, "System.String.IsNullOrEmpty", NotNullWhenFalse);
+ }
+
+ [Fact]
+ public void NotNullWhenFalse_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 NotNullWhenFalse_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 NotNullWhenFalse_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)
+ );
+
+ VerifyAnnotationsAndMetadata(c, "System.String.IsNullOrWhiteSpace", NotNullWhenFalse);
+ }
+
+ [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)
+ );
+
+ VerifyAnnotations(c, "C.M1", NotNullWhenFalse);
+ VerifyAnnotations(c, "C.M2", NotNullWhenFalse);
+ VerifyAnnotations(c, "C.M3", NotNullWhenFalse);
+ }
+
+ [Fact]
+ public void NotNullWhenFalse_ReturningDynamic()
+ {
+ CSharpCompilation c = CreateCompilation(@"
+using System.Runtime.CompilerServices;
+public class C
+{
+ void Main(string? s)
+ {
+ if (MyIsNullOrEmpty(s))
+ {
+ s.ToString(); // warn
+ }
+ else
+ {
+ s.ToString(); // warn 2
+ }
+ }
+ public 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),
+ // (13,13): warning CS8602: Possible dereference of a null reference.
+ // s.ToString(); // warn 2
+ Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s").WithLocation(13, 13)
+ );
+
+ VerifyAnnotations(c, "C.MyIsNullOrEmpty", NotNullWhenFalse);
+ }
+
+ [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)
+ );
+
+ VerifyAnnotations(compilation, "C.MyIsNullOrEmpty", NotNullWhenFalse);
+ }
+
+ [Fact]
+ public void NotNullWhenFalse_ReturningObject()
+ {
+ CSharpCompilation c = CreateCompilation(@"
+using System.Runtime.CompilerServices;
+class C
+{
+ void Main(string? s)
+ {
+ MyIsNullOrEmpty(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;
+public class C
+{
+ void Main(string? s)
+ {
+ if (MyIsNullOrEmpty(s, s))
+ {
+ s.ToString(); // ok
+ }
+ else
+ {
+ s.ToString(); // ok
+ }
+
+ s.ToString(); // ok
+ }
+ public static bool MyIsNullOrEmpty([NotNullWhenFalse] string? s, [EnsuresNotNull] string? s2) => throw null;
+}
+" + NotNullWhenFalseAttributeDefinition + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8);
+
+ c.VerifyDiagnostics();
+
+ VerifyAnnotationsAndMetadata(c, "C.MyIsNullOrEmpty", NotNullWhenFalse, EnsuresNotNull);
+ }
+
+ [Fact]
+ public void NotNullWhenFalse_AndEnsuresNotNull()
+ {
+ CSharpCompilation c = CreateCompilation(@"
+using System.Runtime.CompilerServices;
+public class C
+{
+ void Main(string? s)
+ {
+ if (MyIsNullOrEmpty(s))
+ {
+ s.ToString(); // ok
+ }
+ else
+ {
+ s.ToString(); // ok
+ }
+
+ s.ToString(); // ok
+ }
+ public static bool MyIsNullOrEmpty([NotNullWhenFalse, EnsuresNotNull] string? s) => throw null;
+}
+" + NotNullWhenFalseAttributeDefinition + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8);
+
+ c.VerifyDiagnostics();
+
+ VerifyAnnotationsAndMetadata(c, "C.MyIsNullOrEmpty", NotNullWhenFalse | EnsuresNotNull);
+ }
+
+ [Fact]
+ public void EnsuresNotNull_Simple()
+ {
+ CSharpCompilation c = CreateCompilation(@"
+using System.Runtime.CompilerServices;
+public class C
+{
+ void Main(string? s)
+ {
+ ThrowIfNull(42, s);
+ s.ToString(); // ok
+ }
+ public static void ThrowIfNull(int x, [EnsuresNotNull] string? s) => throw null;
+}
+" + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8);
+
+ c.VerifyDiagnostics();
+
+ 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(
+ // (8,9): warning CS8602: Possible dereference of a null reference.
+ // u.ToString();
+ Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "u").WithLocation(8, 9)
+ );
+ }
+
+ [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(); // warn 2
+
+ EnsuresNotNull(x, y);
+ x.ToString(); // warn 3
+ y.ToString(); // warn 4
+ }
+}
+" + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8);
+
+ // 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),
+ // (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)
+ );
+ }
+
+ [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]
+ 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();
+
+ VerifyAnnotations(c, "C.Bad", EnsuresNotNull);
+ VerifyAnnotations(c, "C.ThrowIfNull", EnsuresNotNull);
+ }
+
+ [Fact]
+ public void EnsuresNotNull_GenericMethod()
+ {
+ CSharpCompilation c = CreateCompilation(@"
+using System.Runtime.CompilerServices;
+public class C
+{
+ void M(T t)
+ {
+ t.ToString(); // warn
+ ThrowIfNull(t);
+ t.ToString(); // ok
+ }
+ public static void ThrowIfNull([EnsuresNotNull] T s) => throw null;
+}
+" + EnsuresNotNullAttributeDefinition, parseOptions: TestOptions.Regular8);
+
+ // 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)
+ );
+
+ 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(
+ // (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_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()
+ {
+ 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()
+ {
+ CSharpCompilation c = CreateCompilation(@"
+using System.Runtime.CompilerServices;
+public class C
+{
+ void Main(string? s)
+ {
+ ThrowIfNull(s, s.ToString()); // warn
+ s.ToString(); // ok
+ }
+ public 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)
+ );
+
+ VerifyAnnotationsAndMetadata(c, "C.ThrowIfNull", EnsuresNotNull, None);
+ }
+
+ [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();
+
+ VerifyAnnotationsAndMetadata(c, "System.String.Contains", EnsuresNotNull);
+ }
+
+ [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]
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..3e40971c2cce3 100644
--- a/src/Compilers/Core/Portable/Symbols/Attributes/AttributeDescription.cs
+++ b/src/Compilers/Core/Portable/Symbols/Attributes/AttributeDescription.cs
@@ -258,6 +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_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 };
@@ -454,6 +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 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/CommonParameterWellKnownAttributeData.cs b/src/Compilers/Core/Portable/Symbols/Attributes/CommonParameterWellKnownAttributeData.cs
index 74cc508d9fa43..020d56fa86c07 100644
--- a/src/Compilers/Core/Portable/Symbols/Attributes/CommonParameterWellKnownAttributeData.cs
+++ b/src/Compilers/Core/Portable/Symbols/Attributes/CommonParameterWellKnownAttributeData.cs
@@ -109,5 +109,38 @@ public bool HasIUnknownConstantAttribute
}
}
#endregion
+
+ // PROTOTYPE(NullableReferenceTypes): Consider moving the attribute for nullability to C#-specific type
+ 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..0f81d94e7fd63 100644
--- a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs
+++ b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs
@@ -306,7 +306,11 @@ 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)
+ {
+ return CreateCompilationWithILAndMscorlib40(source, ilSource, TargetFramework.Standard, references, options, parseOptions, appendDefaultHeader);
+ }
public static CSharpCompilation CreateCompilationWithILAndMscorlib40(
CSharpTestSource source,
@@ -314,11 +318,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(