diff --git a/docs/features/NullableReferenceTypes/ImplementationNotes.md b/docs/features/NullableReferenceTypes/ImplementationNotes.md index 5e3a0f5d0d70be5eb57818fc69d23ff59d593f31..67f0d7b86a0deb54d8db3e17242eb791ffc32e97 100644 --- a/docs/features/NullableReferenceTypes/ImplementationNotes.md +++ b/docs/features/NullableReferenceTypes/ImplementationNotes.md @@ -121,7 +121,7 @@ namespace System.Runtime.CompilerServices **Opting in and opting out of nullability warnings** -It is possible to suppress all nulability warnings originating from declarations in certain referenced +It is possible to suppress all nullability warnings originating from declarations in certain referenced assembly by applying the following attribute: ``` namespace System.Runtime.CompilerServices diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index d7ece288794147a81d95b8f7d2a9804f9cf3c725..8065f6cc3d2bdd1c54e22b24c904e0105e37c8a8 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -2749,7 +2749,7 @@ private BoundExpression BindImplicitArrayCreationExpression(ImplicitArrayCreatio { // For now erase all notion about nullability of reference types as the algorithm is not taking it into account yet. // Default inferred reference type itself to a nullable state. - elementType = TypeSymbolWithAnnotations.Create(bestType.SetUnknownNullabilityForRefernceTypes(), isNullableIfReferenceType: true); + elementType = TypeSymbolWithAnnotations.Create(bestType.SetUnknownNullabilityForReferenceTypes(), isNullableIfReferenceType: true); } else { @@ -6087,7 +6087,7 @@ private static void CombineExtensionMethodArguments(BoundExpression receiver, An internal TypeSymbol GetFieldTypeWithAdjustedNullableAnnotations(FieldSymbol fieldSymbol, ConsList fieldsBeingBound) { - if (((CSharpParseOptions)Compilation.SyntaxTrees.FirstOrDefault()?.Options)?.IsFeatureEnabled(MessageID.IDS_FeatureStaticNullChecking) == true && + if (Compilation.IsFeatureEnabled(MessageID.IDS_FeatureStaticNullChecking) && !IsBindingModuleLevelAttribute()) // TODO: It is possible to get into cycle while binding module level attributes because Opt-In/Opt-Out state depends on them { return Compilation.GetFieldTypeWithAdjustedNullableAnnotations(fieldSymbol, fieldsBeingBound).TypeSymbol; diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs index 011a6c6327e8f05bbb236986b04dccc22e92e0b2..2e2bb5f81885e287b217787fb804a49639c6a968 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs @@ -1014,7 +1014,7 @@ private static void CheckRestrictedTypeReceiver(BoundExpression expression, Comp internal TypeSymbolWithAnnotations GetTypeOrReturnTypeWithAdjustedNullableAnnotations(Symbol symbol) { - if (((CSharpParseOptions)Compilation.SyntaxTrees.FirstOrDefault()?.Options)?.IsFeatureEnabled(MessageID.IDS_FeatureStaticNullChecking) == true && + if (Compilation.IsFeatureEnabled(MessageID.IDS_FeatureStaticNullChecking) && !IsBindingModuleLevelAttribute()) // TODO: It is possible to get into cycle while binding module level attributes because Opt-In/Opt-Out state depends on them { return Compilation.GetTypeOrReturnTypeWithAdjustedNullableAnnotations(symbol); diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MethodTypeInference.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MethodTypeInference.cs index e7683f15d6e33a939cae8c951fb4b2b3236560b7..57a130e296f48681f58147efce587f241876d96f 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MethodTypeInference.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MethodTypeInference.cs @@ -406,7 +406,7 @@ private ImmutableArray GetResults(bool eraseNullabili foreach (TypeSymbol t in _fixedResults) { var result = eraseNullability ? - TypeSymbolWithAnnotations.Create(t.SetUnknownNullabilityForRefernceTypes(), isNullableIfReferenceType: null) : + TypeSymbolWithAnnotations.Create(t.SetUnknownNullabilityForReferenceTypes(), isNullableIfReferenceType: null) : TypeSymbolWithAnnotations.Create(t); builder.Add(result); } @@ -521,7 +521,7 @@ private MethodTypeInferenceResult InferTypeArgs(Binder binder, ref HashSet + diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs index 0fb85a02776fb2ec659835a3ae704aca0f1b2dbf..0b74a0ce5373e73b3388ed385c3f1e45e1f02ad2 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs @@ -3077,8 +3077,7 @@ internal bool EnableEnumArrayBlockInitialization internal override bool IsIOperationFeatureEnabled() { - var options = (CSharpParseOptions)this.SyntaxTrees.FirstOrDefault()?.Options; - return options?.IsFeatureEnabled(MessageID.IDS_FeatureIOperation) ?? false; + return this.IsFeatureEnabled(MessageID.IDS_FeatureIOperation); } internal TypeSymbolWithAnnotations GetTypeOrReturnTypeWithAdjustedNullableAnnotations(Symbol symbol) @@ -3092,7 +3091,7 @@ internal TypeSymbolWithAnnotations GetTypeOrReturnTypeWithAdjustedNullableAnnota // Nullable annotations on definition should be ignored TypeSymbolWithAnnotations definitionType = definition.Kind == SymbolKind.Parameter ? ((ParameterSymbol)definition).Type : definition.GetTypeOrReturnType(); - TypeSymbolWithAnnotations adjustedDefinitionType = definitionType.SetUnknownNullabilityForRefernceTypes(); + TypeSymbolWithAnnotations adjustedDefinitionType = definitionType.SetUnknownNullabilityForReferenceTypes(); if ((object)definition == symbol) { @@ -3162,7 +3161,7 @@ internal TypeSymbolWithAnnotations GetFieldTypeWithAdjustedNullableAnnotations(F // Nullable annotations on definition should be ignored TypeSymbolWithAnnotations definitionType = definition.GetFieldType(fieldsBeingBound); - TypeSymbolWithAnnotations adjustedDefinitionType = definitionType.SetUnknownNullabilityForRefernceTypes(); + TypeSymbolWithAnnotations adjustedDefinitionType = definitionType.SetUnknownNullabilityForReferenceTypes(); if ((object)definition == field) { diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilationExtensions.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilationExtensions.cs new file mode 100644 index 0000000000000000000000000000000000000000..128ae822ddbd14731760ae1a1c3d0fec38efdc10 --- /dev/null +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilationExtensions.cs @@ -0,0 +1,14 @@ +// 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.Linq; + +namespace Microsoft.CodeAnalysis.CSharp +{ + internal static class CSharpCompilationExtensions + { + internal static bool IsFeatureEnabled(this CSharpCompilation compilation, MessageID feature) + { + return ((CSharpParseOptions)compilation.SyntaxTrees.FirstOrDefault()?.Options)?.IsFeatureEnabled(feature) == true; + } + } +} diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs index a7c2c2b758ead4ce327e472a176d3fd2208120af..94ffe681517c4b043115a4d443c4e0a2514a0634 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs @@ -784,6 +784,7 @@ protected int GetOrCreateSlot(Symbol symbol, int containingSlot = 0) NormalizeNullable(ref this.State); } + Debug.Assert(slot > 0); return slot; } @@ -1606,9 +1607,13 @@ private void InheritNullableStateOfFieldOrProperty(int targetContainerSlot, int } else if (EmptyStructTypeCache.IsTrackableStructType(fieldOrPropertyType.TypeSymbol)) { - InheritNullableStateOfTrackableStruct(fieldOrPropertyType.TypeSymbol, - GetOrCreateSlot(fieldOrProperty, targetContainerSlot), - valueContainerSlot > 0 ? GetOrCreateSlot(fieldOrProperty, valueContainerSlot) : -1, isByRefTarget); + var slot = GetOrCreateSlot(fieldOrProperty, targetContainerSlot); + if (slot > 0) + { + InheritNullableStateOfTrackableStruct(fieldOrPropertyType.TypeSymbol, + slot, + valueContainerSlot > 0 ? GetOrCreateSlot(fieldOrProperty, valueContainerSlot) : -1, isByRefTarget); + } } } @@ -2233,7 +2238,11 @@ public override BoundNode VisitObjectCreationExpression(BoundObjectCreationExpre EmptyStructTypeCache.IsTrackableStructType(node.Type)) { _implicitReceiver = GetOrCreateObjectCreationPlaceholder(node); - InheritNullableStateOfTrackableStruct(node.Type, MakeSlot(node), -1, false); + var slot = MakeSlot(node); + if (slot > 0) + { + InheritNullableStateOfTrackableStruct(node.Type, slot, -1, false); + } } var result = base.VisitObjectCreationExpression(node); diff --git a/src/Compilers/CSharp/Portable/Symbols/ArrayTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/ArrayTypeSymbol.cs index baabc6ba2e3e26f36f3b25b4c70096a0516141dc..80c9c35767721a753e0cd6e60e8faf71af1b877c 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ArrayTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ArrayTypeSymbol.cs @@ -399,10 +399,10 @@ internal override bool ApplyNullableTransforms(ImmutableArray transforms, return true; } - internal override TypeSymbol SetUnknownNullabilityForRefernceTypes() + internal override TypeSymbol SetUnknownNullabilityForReferenceTypes() { TypeSymbolWithAnnotations oldElementType = ElementType; - TypeSymbolWithAnnotations newElementType = oldElementType.SetUnknownNullabilityForRefernceTypes(); + TypeSymbolWithAnnotations newElementType = oldElementType.SetUnknownNullabilityForReferenceTypes(); if ((object)oldElementType == newElementType) { diff --git a/src/Compilers/CSharp/Portable/Symbols/DynamicTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/DynamicTypeSymbol.cs index 5846e5ae29e6cc1d910abe06e07fdfb2d0904830..c8c6c20c09bb164a1d6045e670837de3af994db4 100644 --- a/src/Compilers/CSharp/Portable/Symbols/DynamicTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/DynamicTypeSymbol.cs @@ -221,7 +221,7 @@ internal override bool ApplyNullableTransforms(ImmutableArray transforms, return true; } - internal override TypeSymbol SetUnknownNullabilityForRefernceTypes() + internal override TypeSymbol SetUnknownNullabilityForReferenceTypes() { return this; } diff --git a/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs index e8a561f6c93a18406f657bab2501d9d6f02e0bfc..c6f3abb60b35451e321f743701c596c11fd24727 100644 --- a/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs @@ -825,7 +825,7 @@ internal override bool ApplyNullableTransforms(ImmutableArray transforms, return true; } - internal override TypeSymbol SetUnknownNullabilityForRefernceTypes() + internal override TypeSymbol SetUnknownNullabilityForReferenceTypes() { if (!IsGenericType) { @@ -839,7 +839,7 @@ internal override TypeSymbol SetUnknownNullabilityForRefernceTypes() for (int i = 0; i < allTypeArguments.Count; i++) { TypeSymbolWithAnnotations oldTypeArgument = allTypeArguments[i]; - TypeSymbolWithAnnotations newTypeArgument = oldTypeArgument.SetUnknownNullabilityForRefernceTypes(); + TypeSymbolWithAnnotations newTypeArgument = oldTypeArgument.SetUnknownNullabilityForReferenceTypes(); if ((object)oldTypeArgument != newTypeArgument) { allTypeArguments[i] = newTypeArgument; diff --git a/src/Compilers/CSharp/Portable/Symbols/PointerTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/PointerTypeSymbol.cs index 8becf5e75eff16bef6c0ca33692f899918c47f02..1e78fb64dbdc40cc199f514c4588c9160def6678 100644 --- a/src/Compilers/CSharp/Portable/Symbols/PointerTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/PointerTypeSymbol.cs @@ -266,10 +266,10 @@ internal override bool ApplyNullableTransforms(ImmutableArray transforms, return true; } - internal override TypeSymbol SetUnknownNullabilityForRefernceTypes() + internal override TypeSymbol SetUnknownNullabilityForReferenceTypes() { TypeSymbolWithAnnotations oldPointedAtType = PointedAtType; - TypeSymbolWithAnnotations newPointedAtType = oldPointedAtType.SetUnknownNullabilityForRefernceTypes(); + TypeSymbolWithAnnotations newPointedAtType = oldPointedAtType.SetUnknownNullabilityForReferenceTypes(); if ((object)oldPointedAtType == newPointedAtType) { diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceModuleSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceModuleSymbol.cs index 15e5ad89feb949231b856ee593c33050c340b8e4..b7acfe9dd7b2aad2ff7e8eaf6dd084c33df5f0aa 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceModuleSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceModuleSymbol.cs @@ -623,7 +623,7 @@ internal override bool UtilizesNullableReferenceTypes { get { - return ((CSharpParseOptions)_assemblySymbol.DeclaringCompilation.SyntaxTrees.FirstOrDefault()?.Options)?.IsFeatureEnabled(MessageID.IDS_FeatureStaticNullChecking) == true; + return _assemblySymbol.DeclaringCompilation.IsFeatureEnabled(MessageID.IDS_FeatureStaticNullChecking); } } diff --git a/src/Compilers/CSharp/Portable/Symbols/SymbolWithAnnotations.cs b/src/Compilers/CSharp/Portable/Symbols/SymbolWithAnnotations.cs index 3b6b6e6999553063e3c368000f1c2658c2583dba..3b60a5dc2636358034cf41e2eace12a442795fab 100644 --- a/src/Compilers/CSharp/Portable/Symbols/SymbolWithAnnotations.cs +++ b/src/Compilers/CSharp/Portable/Symbols/SymbolWithAnnotations.cs @@ -506,7 +506,7 @@ public bool ApplyNullableTransforms(ImmutableArray transforms, ref int pos return false; } - public TypeSymbolWithAnnotations SetUnknownNullabilityForRefernceTypes() + public TypeSymbolWithAnnotations SetUnknownNullabilityForReferenceTypes() { var typeSymbol = TypeSymbol; @@ -514,7 +514,7 @@ public TypeSymbolWithAnnotations SetUnknownNullabilityForRefernceTypes() { if (!typeSymbol.IsNullableType() && typeSymbol.IsReferenceType) { - typeSymbol = typeSymbol.SetUnknownNullabilityForRefernceTypes(); + typeSymbol = typeSymbol.SetUnknownNullabilityForReferenceTypes(); var customModifiers = CustomModifiers; if (customModifiers.IsEmpty) @@ -526,7 +526,7 @@ public TypeSymbolWithAnnotations SetUnknownNullabilityForRefernceTypes() } } - var newTypeSymbol = typeSymbol.SetUnknownNullabilityForRefernceTypes(); + var newTypeSymbol = typeSymbol.SetUnknownNullabilityForReferenceTypes(); if ((object)newTypeSymbol != typeSymbol) { diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/TypeParameterSymbol.cs index c3b7873a20ea0a2f8019df4e2a745d5bca5e459d..85e9096379035613239ef6ad46827e9fc4dedc79 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeParameterSymbol.cs @@ -541,7 +541,7 @@ internal override bool ApplyNullableTransforms(ImmutableArray transforms, return true; } - internal override TypeSymbol SetUnknownNullabilityForRefernceTypes() + internal override TypeSymbol SetUnknownNullabilityForReferenceTypes() { return this; } diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/TypeSymbol.cs index 65bfd991144444b05ef853d5a3474f33ff27a201..be1b35d6639e33ab6d3da36678347a790b5b6ef0 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeSymbol.cs @@ -651,7 +651,7 @@ public virtual NamedTypeSymbol TupleUnderlyingType internal abstract bool ApplyNullableTransforms(ImmutableArray transforms, ref int position, out TypeSymbol result); - internal abstract TypeSymbol SetUnknownNullabilityForRefernceTypes(); + internal abstract TypeSymbol SetUnknownNullabilityForReferenceTypes(); #region ITypeSymbol Members diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs index 8cb2003362d8165234db08922d83b614c02dcbf5..5ff010fcf2c1de5e8657435aba6f0eaffed32562 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StaticNullChecking.cs @@ -14578,6 +14578,68 @@ class C : I Assert.Equal(new[] { "void I.M(T? x)" }, implementations.SelectAsArray(m => m.ToTestDisplayString())); } + [Fact] + public void EmptyStructDifferentAssembly() + { + var sourceA = +@"using System.Collections; +public struct S +{ + public S(string f, IEnumerable g) + { + F = f; + G = g; + } + private string F { get; } + private IEnumerable G { get; } +}"; + var compA = CreateStandardCompilation(sourceA); + var sourceB = +@"using System.Collections.Generic; +class C +{ + static void Main() + { + var c = new List(); + c.Add(new S(string.Empty, new object[0])); + } +}"; + var compB = CreateStandardCompilation( + new[] { sourceB, attributesDefinitions }, + options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithNullCheckingFeature(), + references: new[] { compA.EmitToImageReference() }); + CompileAndVerify(compB, expectedOutput: ""); + } + + [Fact] + public void EmptyStructField() + { + var source = +@"class A { } +struct B { } +struct S +{ + public readonly A A; + public readonly B B; + public S(B b) : this(null, b) + { + } + public S(A a, B b) + { + this.A = a; + this.B = b; + } +}"; + var comp = CreateStandardCompilation( + new[] { source, attributesDefinitions }, + parseOptions: TestOptions.Regular.WithNullCheckingFeature()); + comp.VerifyDiagnostics( + // (7,26): warning CS8604: Possible null reference argument for parameter 'a' in 'S.S(A a, B b)'. + // public S(B b) : this(null, b) + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "null").WithArguments("a", "S.S(A a, B b)").WithLocation(7, 26)); + } + // PROTOTYPE(NullableReferenceTypes) [Fact(Skip = "TODO")] public void Test2()