Skip to content

Commit 8c77f52

Browse files
authored
Merge pull request #58338 from dotnet/features/param-nullchecking
Merge param-nullchecking to main
2 parents cdd0aca + e9c4789 commit 8c77f52

File tree

103 files changed

+9859
-160
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

103 files changed

+9859
-160
lines changed

src/Compilers/CSharp/Portable/Binder/Binder.QueryUnboundLambdaState.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public QueryUnboundLambdaState(Binder binder, RangeVariableMap rangeVariableMap,
3232

3333
public override string ParameterName(int index) { return _parameters[index].Name; }
3434
public override bool ParameterIsDiscard(int index) { return false; }
35+
public override bool ParameterIsNullChecked(int index) { return false; }
3536
public override SyntaxList<AttributeListSyntax> ParameterAttributes(int index) => default;
3637
public override bool HasNames { get { return true; } }
3738
public override bool HasSignature { get { return true; } }

src/Compilers/CSharp/Portable/Binder/Binder_Lambda.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ private UnboundLambda AnalyzeAnonymousFunction(
4040

4141
ImmutableArray<string> names = default;
4242
ImmutableArray<RefKind> refKinds = default;
43+
ImmutableArray<bool> nullCheckedOpt = default;
4344
ImmutableArray<TypeWithAnnotations> types = default;
4445
RefKind returnRefKind = RefKind.None;
4546
TypeWithAnnotations returnType = default;
@@ -63,6 +64,10 @@ private UnboundLambda AnalyzeAnonymousFunction(
6364
hasSignature = true;
6465
var simple = (SimpleLambdaExpressionSyntax)syntax;
6566
namesBuilder.Add(simple.Parameter.Identifier.ValueText);
67+
if (isNullChecked(simple.Parameter))
68+
{
69+
nullCheckedOpt = ImmutableArray.Create(true);
70+
}
6671
break;
6772
case SyntaxKind.ParenthesizedLambdaExpression:
6873
// (T x, U y) => ...
@@ -98,6 +103,7 @@ private UnboundLambda AnalyzeAnonymousFunction(
98103

99104
var typesBuilder = ArrayBuilder<TypeWithAnnotations>.GetInstance();
100105
var refKindsBuilder = ArrayBuilder<RefKind>.GetInstance();
106+
var nullCheckedBuilder = ArrayBuilder<bool>.GetInstance();
101107
var attributesBuilder = ArrayBuilder<SyntaxList<AttributeListSyntax>>.GetInstance();
102108

103109
// In the batch compiler case we probably should have given a syntax error if the
@@ -176,6 +182,7 @@ private UnboundLambda AnalyzeAnonymousFunction(
176182
namesBuilder.Add(p.Identifier.ValueText);
177183
typesBuilder.Add(type);
178184
refKindsBuilder.Add(refKind);
185+
nullCheckedBuilder.Add(isNullChecked(p));
179186
attributesBuilder.Add(syntax.Kind() == SyntaxKind.ParenthesizedLambdaExpression ? p.AttributeLists : default);
180187
}
181188

@@ -191,13 +198,19 @@ private UnboundLambda AnalyzeAnonymousFunction(
191198
refKinds = refKindsBuilder.ToImmutable();
192199
}
193200

201+
if (nullCheckedBuilder.Contains(true))
202+
{
203+
nullCheckedOpt = nullCheckedBuilder.ToImmutable();
204+
}
205+
194206
if (attributesBuilder.Any(a => a.Count > 0))
195207
{
196208
parameterAttributes = attributesBuilder.ToImmutable();
197209
}
198210

199211
typesBuilder.Free();
200212
refKindsBuilder.Free();
213+
nullCheckedBuilder.Free();
201214
attributesBuilder.Free();
202215
}
203216

@@ -208,7 +221,10 @@ private UnboundLambda AnalyzeAnonymousFunction(
208221

209222
namesBuilder.Free();
210223

211-
return UnboundLambda.Create(syntax, this, diagnostics.AccumulatesDependencies, returnRefKind, returnType, parameterAttributes, refKinds, types, names, discardsOpt, isAsync, isStatic);
224+
return UnboundLambda.Create(syntax, this, diagnostics.AccumulatesDependencies, returnRefKind, returnType, parameterAttributes, refKinds, types, names, discardsOpt, nullCheckedOpt, isAsync, isStatic);
225+
226+
static bool isNullChecked(ParameterSyntax parameter)
227+
=> parameter.ExclamationExclamationToken.IsKind(SyntaxKind.ExclamationExclamationToken);
212228

213229
static ImmutableArray<bool> computeDiscards(SeparatedSyntaxList<ParameterSyntax> parameters, int underscoresCount)
214230
{

src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,7 @@ public static UnboundLambda Create(
383383
ImmutableArray<TypeWithAnnotations> types,
384384
ImmutableArray<string> names,
385385
ImmutableArray<bool> discardsOpt,
386+
ImmutableArray<bool> nullCheckedOpt,
386387
bool isAsync,
387388
bool isStatic)
388389
{
@@ -391,7 +392,7 @@ public static UnboundLambda Create(
391392
bool hasErrors = !types.IsDefault && types.Any(t => t.Type?.Kind == SymbolKind.ErrorType);
392393

393394
var functionType = FunctionTypeSymbol.CreateIfFeatureEnabled(syntax, binder, static (binder, expr) => ((UnboundLambda)expr).Data.InferDelegateType());
394-
var data = new PlainUnboundLambdaState(binder, returnRefKind, returnType, parameterAttributes, names, discardsOpt, types, refKinds, isAsync, isStatic, includeCache: true);
395+
var data = new PlainUnboundLambdaState(binder, returnRefKind, returnType, parameterAttributes, names, discardsOpt, nullCheckedOpt, types, refKinds, isAsync, isStatic, includeCache: true);
395396
var lambda = new UnboundLambda(syntax, data, functionType, withDependencies, hasErrors: hasErrors);
396397
data.SetUnboundLambda(lambda);
397398
functionType?.SetExpression(lambda.WithNoCache());
@@ -458,6 +459,7 @@ public TypeWithAnnotations InferReturnType(ConversionsBase conversions, NamedTyp
458459
public Location ParameterLocation(int index) { return Data.ParameterLocation(index); }
459460
public string ParameterName(int index) { return Data.ParameterName(index); }
460461
public bool ParameterIsDiscard(int index) { return Data.ParameterIsDiscard(index); }
462+
public bool ParameterIsNullChecked(int index) { return Data.ParameterIsNullChecked(index); }
461463
}
462464

463465
internal abstract class UnboundLambdaState
@@ -517,6 +519,7 @@ internal UnboundLambdaState WithCaching(bool includeCache)
517519
public abstract MessageID MessageID { get; }
518520
public abstract string ParameterName(int index);
519521
public abstract bool ParameterIsDiscard(int index);
522+
public abstract bool ParameterIsNullChecked(int index);
520523
public abstract SyntaxList<AttributeListSyntax> ParameterAttributes(int index);
521524
public abstract bool HasSignature { get; }
522525
public abstract bool HasExplicitReturnType(out RefKind refKind, out TypeWithAnnotations returnType);
@@ -1321,6 +1324,7 @@ internal sealed class PlainUnboundLambdaState : UnboundLambdaState
13211324
private readonly ImmutableArray<SyntaxList<AttributeListSyntax>> _parameterAttributes;
13221325
private readonly ImmutableArray<string> _parameterNames;
13231326
private readonly ImmutableArray<bool> _parameterIsDiscardOpt;
1327+
private readonly ImmutableArray<bool> _parameterIsNullCheckedOpt;
13241328
private readonly ImmutableArray<TypeWithAnnotations> _parameterTypesWithAnnotations;
13251329
private readonly ImmutableArray<RefKind> _parameterRefKinds;
13261330
private readonly bool _isAsync;
@@ -1333,6 +1337,7 @@ internal PlainUnboundLambdaState(
13331337
ImmutableArray<SyntaxList<AttributeListSyntax>> parameterAttributes,
13341338
ImmutableArray<string> parameterNames,
13351339
ImmutableArray<bool> parameterIsDiscardOpt,
1340+
ImmutableArray<bool> parameterIsNullCheckedOpt,
13361341
ImmutableArray<TypeWithAnnotations> parameterTypesWithAnnotations,
13371342
ImmutableArray<RefKind> parameterRefKinds,
13381343
bool isAsync,
@@ -1345,6 +1350,7 @@ internal PlainUnboundLambdaState(
13451350
_parameterAttributes = parameterAttributes;
13461351
_parameterNames = parameterNames;
13471352
_parameterIsDiscardOpt = parameterIsDiscardOpt;
1353+
_parameterIsNullCheckedOpt = parameterIsNullCheckedOpt;
13481354
_parameterTypesWithAnnotations = parameterTypesWithAnnotations;
13491355
_parameterRefKinds = parameterRefKinds;
13501356
_isAsync = isAsync;
@@ -1414,6 +1420,11 @@ public override bool ParameterIsDiscard(int index)
14141420
return _parameterIsDiscardOpt.IsDefault ? false : _parameterIsDiscardOpt[index];
14151421
}
14161422

1423+
public override bool ParameterIsNullChecked(int index)
1424+
{
1425+
return _parameterIsNullCheckedOpt.IsDefault ? false : _parameterIsNullCheckedOpt[index];
1426+
}
1427+
14171428
public override RefKind RefKind(int index)
14181429
{
14191430
Debug.Assert(0 <= index && index < _parameterTypesWithAnnotations.Length);
@@ -1429,7 +1440,7 @@ public override TypeWithAnnotations ParameterTypeWithAnnotations(int index)
14291440

14301441
protected override UnboundLambdaState WithCachingCore(bool includeCache)
14311442
{
1432-
return new PlainUnboundLambdaState(Binder, _returnRefKind, _returnType, _parameterAttributes, _parameterNames, _parameterIsDiscardOpt, _parameterTypesWithAnnotations, _parameterRefKinds, _isAsync, _isStatic, includeCache);
1443+
return new PlainUnboundLambdaState(Binder, _returnRefKind, _returnType, _parameterAttributes, _parameterNames, _parameterIsDiscardOpt, _parameterIsNullCheckedOpt, _parameterTypesWithAnnotations, _parameterRefKinds, _isAsync, _isStatic, includeCache);
14331444
}
14341445

14351446
protected override BoundExpression? GetLambdaExpressionBody(BoundBlock body)

src/Compilers/CSharp/Portable/CSharpResources.resx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,9 @@
363363
<data name="IDS_FeatureTypeVariance" xml:space="preserve">
364364
<value>type variance</value>
365365
</data>
366+
<data name="IDS_ParameterNullChecking" xml:space="preserve">
367+
<value>parameter null-checking</value>
368+
</data>
366369
<data name="IDS_Parameter" xml:space="preserve">
367370
<value>parameter</value>
368371
</data>
@@ -6166,6 +6169,30 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
61666169
<data name="ERR_DuplicateNullSuppression" xml:space="preserve">
61676170
<value>Duplicate null suppression operator ('!')</value>
61686171
</data>
6172+
<data name="ERR_IncorrectNullCheckSyntax" xml:space="preserve">
6173+
<value>Incorrect parameter null checking syntax. Should be '!!'.</value>
6174+
</data>
6175+
<data name="ERR_MustNullCheckInImplementation" xml:space="preserve">
6176+
<value>Parameter '{0}' can only have exclamation-point null checking in implementation methods.</value>
6177+
</data>
6178+
<data name="ERR_NonNullableValueTypeIsNullChecked" xml:space="preserve">
6179+
<value>Parameter '{0}' is a non-nullable value type and cannot be null-checked.</value>
6180+
</data>
6181+
<data name="WRN_NullCheckedHasDefaultNull" xml:space="preserve">
6182+
<value>Parameter '{0}' is null-checked but is null by default.</value>
6183+
</data>
6184+
<data name="WRN_NullCheckedHasDefaultNull_Title" xml:space="preserve">
6185+
<value>Parameter is null-checked but is null by default.</value>
6186+
</data>
6187+
<data name="ERR_NullCheckingOnByRefParameter" xml:space="preserve">
6188+
<value>By-reference parameter '{0}' cannot be null-checked.</value>
6189+
</data>
6190+
<data name="WRN_NullCheckingOnNullableType" xml:space="preserve">
6191+
<value>Nullable type '{0}' is null-checked and will throw if null.</value>
6192+
</data>
6193+
<data name="WRN_NullCheckingOnNullableType_Title" xml:space="preserve">
6194+
<value>Nullable type is null-checked and will throw if null.</value>
6195+
</data>
61696196
<data name="ERR_ReAbstractionInNoPIAType" xml:space="preserve">
61706197
<value>Type '{0}' cannot be embedded because it has a re-abstraction of a member from base interface. Consider setting the 'Embed Interop Types' property to false.</value>
61716198
</data>

src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1211,7 +1211,7 @@ forSemanticModel.Syntax is { } semanticModelSyntax &&
12111211

12121212
hasErrors = hasErrors || (hasBody && loweredBodyOpt.HasErrors) || diagsForCurrentMethod.HasAnyErrors();
12131213
SetGlobalErrorIfTrue(hasErrors);
1214-
1214+
CSharpSyntaxNode syntax = methodSymbol.GetNonNullSyntaxNode();
12151215
// don't emit if the resulting method would contain initializers with errors
12161216
if (!hasErrors && (hasBody || includeNonEmptyInitializersInBody))
12171217
{
@@ -1290,12 +1290,29 @@ forSemanticModel.Syntax is { } semanticModelSyntax &&
12901290
{
12911291
boundStatements = boundStatements.Concat(ImmutableArray.Create(loweredBodyOpt));
12921292
}
1293-
}
12941293

1294+
var factory = new SyntheticBoundNodeFactory(methodSymbol, syntax, compilationState, diagsForCurrentMethod);
1295+
1296+
// Iterators handled in IteratorRewriter.cs
1297+
if (!methodSymbol.IsIterator)
1298+
{
1299+
var boundStatementsWithNullCheck = LocalRewriter.TryConstructNullCheckedStatementList(methodSymbol.Parameters, boundStatements, factory);
1300+
1301+
if (!boundStatementsWithNullCheck.IsDefault)
1302+
{
1303+
boundStatements = boundStatementsWithNullCheck;
1304+
hasErrors = boundStatementsWithNullCheck.HasErrors() || diagsForCurrentMethod.HasAnyErrors();
1305+
SetGlobalErrorIfTrue(hasErrors);
1306+
if (hasErrors)
1307+
{
1308+
_diagnostics.AddRange(diagsForCurrentMethod);
1309+
return;
1310+
}
1311+
}
1312+
}
1313+
}
12951314
if (_emitMethodBodies && (!(methodSymbol is SynthesizedStaticConstructor cctor) || cctor.ShouldEmit(processedInitializers.BoundInitializers)))
12961315
{
1297-
CSharpSyntaxNode syntax = methodSymbol.GetNonNullSyntaxNode();
1298-
12991316
var boundBody = BoundStatementList.Synthesized(syntax, boundStatements);
13001317

13011318
var emittedBody = GenerateMethodBody(

src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1289,18 +1289,19 @@ internal Cci.IMethodReference Translate(
12891289
}
12901290
}
12911291

1292+
#nullable enable
12921293
private Cci.IMethodReference Translate(
12931294
MethodSymbol methodSymbol,
12941295
SyntaxNode syntaxNodeOpt,
12951296
DiagnosticBag diagnostics,
12961297
bool needDeclaration)
12971298
{
1298-
object reference;
1299+
object? reference;
12991300
Cci.IMethodReference methodRef;
13001301
NamedTypeSymbol container = methodSymbol.ContainingType;
13011302

13021303
// Method of anonymous type being translated
1303-
if (container.IsAnonymousType)
1304+
if (container?.IsAnonymousType == true)
13041305
{
13051306
Debug.Assert(!needDeclaration);
13061307
methodSymbol = AnonymousTypeManager.TranslateAnonymousTypeMethodSymbol(methodSymbol);
@@ -1363,6 +1364,7 @@ private Cci.IMethodReference Translate(
13631364

13641365
return methodSymbol.GetCciAdapter();
13651366
}
1367+
#nullable disable
13661368

13671369
internal Cci.IMethodReference TranslateOverriddenMethodReference(
13681370
MethodSymbol methodSymbol,
@@ -1777,6 +1779,39 @@ internal void EnsureNativeIntegerAttributeExists()
17771779
EnsureEmbeddableAttributeExists(EmbeddableAttributes.NativeIntegerAttribute);
17781780
}
17791781

1782+
#nullable enable
1783+
/// <summary>
1784+
/// Creates the ThrowIfNull and Throw helpers if needed.
1785+
/// </summary>
1786+
/// <remarks>
1787+
/// The ThrowIfNull and Throw helpers are modeled off of the helpers on ArgumentNullException.
1788+
/// https://github.com/dotnet/runtime/blob/22663769611ba89cd92d14cfcb76e287f8af2335/src/libraries/System.Private.CoreLib/src/System/ArgumentNullException.cs#L56-L69
1789+
/// </remarks>
1790+
internal MethodSymbol EnsureThrowIfNullFunctionExists(SyntaxNode syntaxNode, SyntheticBoundNodeFactory factory, DiagnosticBag? diagnostics)
1791+
{
1792+
var privateImplClass = GetPrivateImplClass(syntaxNode, diagnostics);
1793+
var throwIfNullAdapter = privateImplClass.GetMethod(PrivateImplementationDetails.SynthesizedThrowIfNullFunctionName);
1794+
if (throwIfNullAdapter is null)
1795+
{
1796+
TypeSymbol returnType = factory.SpecialType(SpecialType.System_Void);
1797+
TypeSymbol argumentType = factory.SpecialType(SpecialType.System_Object);
1798+
TypeSymbol paramNameType = factory.SpecialType(SpecialType.System_String);
1799+
var sourceModule = SourceModule;
1800+
1801+
// use add-then-get pattern to ensure the symbol exists, and then ensure we use the single "canonical" instance added by whichever thread won the race.
1802+
privateImplClass.TryAddSynthesizedMethod(new SynthesizedThrowMethod(sourceModule, privateImplClass, returnType, paramNameType).GetCciAdapter());
1803+
var actuallyAddedThrowMethod = (SynthesizedThrowMethod)privateImplClass.GetMethod(PrivateImplementationDetails.SynthesizedThrowFunctionName)!.GetInternalSymbol()!;
1804+
privateImplClass.TryAddSynthesizedMethod(new SynthesizedThrowIfNullMethod(sourceModule, privateImplClass, actuallyAddedThrowMethod, returnType, argumentType, paramNameType).GetCciAdapter());
1805+
throwIfNullAdapter = privateImplClass.GetMethod(PrivateImplementationDetails.SynthesizedThrowIfNullFunctionName)!;
1806+
}
1807+
1808+
var internalSymbol = (SynthesizedThrowIfNullMethod)throwIfNullAdapter.GetInternalSymbol()!;
1809+
// the ThrowMethod referenced by the ThrowIfNullMethod must be the same instance as the ThrowMethod contained in the PrivateImplementationDetails
1810+
Debug.Assert((object?)privateImplClass.GetMethod(PrivateImplementationDetails.SynthesizedThrowFunctionName)!.GetInternalSymbol() == internalSymbol.ThrowMethod);
1811+
return internalSymbol;
1812+
}
1813+
#nullable disable
1814+
17801815
public override IEnumerable<Cci.INamespaceTypeDefinition> GetAdditionalTopLevelTypeDefinitions(EmitContext context)
17811816
{
17821817
return GetAdditionalTopLevelTypes()

src/Compilers/CSharp/Portable/Errors/ErrorCode.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2007,6 +2007,13 @@ internal enum ErrorCode
20072007
// at runtime.
20082008
ERR_CannotUseRefInUnmanagedCallersOnly = 8977,
20092009

2010+
ERR_IncorrectNullCheckSyntax = 8990,
2011+
ERR_MustNullCheckInImplementation = 8991,
2012+
ERR_NonNullableValueTypeIsNullChecked = 8992,
2013+
WRN_NullCheckedHasDefaultNull = 8993,
2014+
ERR_NullCheckingOnByRefParameter = 8994,
2015+
WRN_NullCheckingOnNullableType = 8995,
2016+
20102017
#endregion
20112018

20122019
#region diagnostics introduced for C# 11.0

src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,8 @@ internal static int GetWarningLevel(ErrorCode code)
455455
case ErrorCode.WRN_UndecoratedCancellationTokenParameter:
456456
case ErrorCode.WRN_NullabilityMismatchInTypeParameterNotNullConstraint:
457457
case ErrorCode.WRN_DisallowNullAttributeForbidsMaybeNullAssignment:
458+
case ErrorCode.WRN_NullCheckedHasDefaultNull:
459+
case ErrorCode.WRN_NullCheckingOnNullableType:
458460
case ErrorCode.WRN_ParameterConditionallyDisallowsNull:
459461
case ErrorCode.WRN_NullReferenceInitializer:
460462
case ErrorCode.WRN_ShouldNotReturn:

src/Compilers/CSharp/Portable/Errors/MessageID.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ internal enum MessageID
240240

241241
IDS_FeatureNewLinesInInterpolations = MessageBase + 12813,
242242
IDS_FeatureListPattern = MessageBase + 12814,
243+
IDS_ParameterNullChecking = MessageBase + 12815,
243244
}
244245

245246
// Message IDs may refer to strings that need to be localized.
@@ -353,6 +354,7 @@ internal static LanguageVersion RequiredVersion(this MessageID feature)
353354
case MessageID.IDS_FeatureGenericAttributes: // semantic check
354355
case MessageID.IDS_FeatureNewLinesInInterpolations: // semantic check
355356
case MessageID.IDS_FeatureListPattern: // semantic check
357+
case MessageID.IDS_ParameterNullChecking: // syntax check
356358
return LanguageVersion.Preview;
357359

358360
// C# 10.0 features.

0 commit comments

Comments
 (0)