From 3d012903c8dde5d9e6c885f4a09d6dc9bc88983f Mon Sep 17 00:00:00 2001 From: Fredric Silberberg Date: Fri, 25 Mar 2022 12:30:33 -0700 Subject: [PATCH] Support SetsRequiredMembersAttribute The SetsRequiredMembersAttribute prevents the compiler from checking the required member list of a type when calling that constructor, and suppresses any errors from a base type's list being invalid. Specification: https://github.com/dotnet/csharplang/blob/main/proposals/required-members.md Test plan: https://github.com/dotnet/roslyn/issues/57046 --- .../Portable/Binder/Binder_Expressions.cs | 45 +- .../Compiler/MethodBodySynthesizer.Lowered.cs | 2 + .../Portable/Compiler/MethodCompiler.cs | 4 +- .../Portable/FlowAnalysis/NullableWalker.cs | 7 +- .../IteratorFinallyMethodSymbol.cs | 2 + .../AnonymousType.SynthesizedMethodBase.cs | 2 + .../Portable/Symbols/ErrorMethodSymbol.cs | 10 + .../FunctionPointerMethodSymbol.cs | 1 + .../Symbols/Metadata/PE/PEMethodSymbol.cs | 33 +- .../CSharp/Portable/Symbols/MethodSymbol.cs | 7 + .../Symbols/ReducedExtensionMethodSymbol.cs | 2 + .../Symbols/SignatureOnlyMethodSymbol.cs | 2 + .../Source/SourceConstructorSymbolBase.cs | 35 + .../Symbols/Source/SourceMethodSymbol.cs | 3 + .../SourceMethodSymbolWithAttributes.cs | 2 +- .../Portable/Symbols/SymbolExtensions.cs | 3 +- .../Records/SynthesizedRecordCopyCtor.cs | 9 + .../Synthesized/SynthesizedDelegateSymbol.cs | 2 + .../SynthesizedEntryPointSymbol.cs | 2 + .../SynthesizedGlobalMethodSymbol.cs | 2 + .../SynthesizedImplementationMethod.cs | 2 + .../SynthesizedInstanceConstructor.cs | 1 + ...SynthesizedInteractiveInitializerMethod.cs | 2 + .../SynthesizedIntrinsicOperatorSymbol.cs | 2 + .../SynthesizedStaticConstructor.cs | 2 + .../Portable/Symbols/TypeSymbolExtensions.cs | 6 + .../Symbols/Wrapped/WrappedMethodSymbol.cs | 2 + .../Symbol/Symbols/RequiredMembersTests.cs | 1203 +++++++++++------ .../Attributes/AttributeDescription.cs | 1 + ...CommonMethodEarlyWellKnownAttributeData.cs | 18 + .../Core/Portable/WellKnownMember.cs | 1 + .../Core/Portable/WellKnownMembers.cs | 8 + src/Compilers/Core/Portable/WellKnownTypes.cs | 4 +- .../Test/Utilities/CSharp/CSharpTestBase.cs | 13 + 34 files changed, 995 insertions(+), 445 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index a5fa7098a451d..ff5ce48abf376 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -5846,25 +5846,7 @@ internal bool TryPerformConstructorOverloadResolution( } } - if (suppressUnsupportedRequiredMembersError && useSiteInfo.AccumulatesDiagnostics && useSiteInfo.Diagnostics is { Count: not 0 }) - { - diagnostics.AddDependencies(useSiteInfo); - foreach (var diagnostic in useSiteInfo.Diagnostics) - { - // We don't want to report this error here because we'll report ERR_RequiredMembersBaseTypeInvalid. That error is suppressable by the - // user using the `SetsRequiredMembers` attribute on the constructor, so reporting this error would prevent that from working. - if ((ErrorCode)diagnostic.Code == ErrorCode.ERR_RequiredMembersInvalid) - { - continue; - } - - diagnostics.ReportUseSiteDiagnostic(diagnostic, errorLocation); - } - } - else - { - diagnostics.Add(errorLocation, useSiteInfo); - } + ReportConstructorUseSiteDiagnostics(errorLocation, diagnostics, suppressUnsupportedRequiredMembersError, useSiteInfo); if (succeededIgnoringAccessibility) { @@ -5921,6 +5903,31 @@ internal bool TryPerformConstructorOverloadResolution( return succeededConsideringAccessibility; } + internal static bool ReportConstructorUseSiteDiagnostics(Location errorLocation, BindingDiagnosticBag diagnostics, bool suppressUnsupportedRequiredMembersError, CompoundUseSiteInfo useSiteInfo) + { + if (suppressUnsupportedRequiredMembersError && useSiteInfo.AccumulatesDiagnostics && useSiteInfo.Diagnostics is { Count: not 0 }) + { + diagnostics.AddDependencies(useSiteInfo); + foreach (var diagnostic in useSiteInfo.Diagnostics) + { + // We don't want to report this error here because we'll report ERR_RequiredMembersBaseTypeInvalid. That error is suppressable by the + // user using the `SetsRequiredMembers` attribute on the constructor, so reporting this error would prevent that from working. + if ((ErrorCode)diagnostic.Code == ErrorCode.ERR_RequiredMembersInvalid) + { + continue; + } + + diagnostics.ReportUseSiteDiagnostic(diagnostic, errorLocation); + } + + return true; + } + else + { + return diagnostics.Add(errorLocation, useSiteInfo); + } + } + private ImmutableArray GetAccessibleConstructorsForOverloadResolution(NamedTypeSymbol type, ref CompoundUseSiteInfo useSiteInfo) { ImmutableArray allInstanceConstructors; diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodBodySynthesizer.Lowered.cs b/src/Compilers/CSharp/Portable/Compiler/MethodBodySynthesizer.Lowered.cs index e61efd3329cb3..f359679064a45 100644 --- a/src/Compilers/CSharp/Portable/Compiler/MethodBodySynthesizer.Lowered.cs +++ b/src/Compilers/CSharp/Portable/Compiler/MethodBodySynthesizer.Lowered.cs @@ -195,6 +195,8 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, F.CloseMethod(F.ThrowNull()); } } + + protected sealed override bool HasSetsRequiredMembersImpl => throw ExceptionUtilities.Unreachable; } internal abstract partial class MethodToClassRewriter diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs index 29ed8c74a05ca..fa62c3d18bfb0 100644 --- a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs +++ b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs @@ -2142,7 +2142,9 @@ private static BoundCall GenerateBaseCopyConstructorInitializer(SynthesizedRecor return null; } - if (Binder.ReportUseSite(baseConstructor, diagnostics, diagnosticsLocation)) + var constructorUseSiteInfo = CompoundUseSiteInfo.DiscardedDependencies; + constructorUseSiteInfo.Add(baseConstructor.GetUseSiteInfo()); + if (Binder.ReportConstructorUseSiteDiagnostics(diagnosticsLocation, diagnostics, suppressUnsupportedRequiredMembersError: constructor.HasSetsRequiredMembers, constructorUseSiteInfo)) { return null; } diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 123ff8ce5b272..10b7730eefdb7 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -871,6 +871,8 @@ void makeNotNullMembersMaybeNull() } } + return; + ImmutableArray getMembersNeedingDefaultInitialState() { if (_hasInitialState) @@ -880,12 +882,13 @@ ImmutableArray getMembersNeedingDefaultInitialState() // We don't use a default initial state for value type instance constructors without `: this()` because // any usages of uninitialized fields will get definite assignment errors anyway. - bool hasConstructorInitializer = method.HasThisConstructorInitializer(out _); - if (!hasConstructorInitializer && (!method.ContainingType.IsValueType || method.IsStatic)) + if (!method.HasThisConstructorInitializer(out _) && (!method.ContainingType.IsValueType || method.IsStatic)) { return membersToBeInitialized(method.ContainingType, includeAllMembers: true); } + // We want to presume all required members of the type are uninitialized, and in addition we want to set all fields to + // default if we can get to this constructor by doing so (ie, : this() in a value type). return membersToBeInitialized(method.ContainingType, includeAllMembers: method.IncludeFieldInitializersInBody()); static ImmutableArray membersToBeInitialized(NamedTypeSymbol containingType, bool includeAllMembers) diff --git a/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorFinallyMethodSymbol.cs b/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorFinallyMethodSymbol.cs index 1240b4c79e1c7..93d78aeb2f272 100644 --- a/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorFinallyMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorFinallyMethodSymbol.cs @@ -255,5 +255,7 @@ internal override int CalculateLocalSyntaxOffset(int localPosition, SyntaxTree l { return _stateMachineType.KickoffMethod.CalculateLocalSyntaxOffset(localPosition, localTree); } + + protected sealed override bool HasSetsRequiredMembersImpl => throw ExceptionUtilities.Unreachable; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/SynthesizedSymbols/AnonymousType.SynthesizedMethodBase.cs b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/SynthesizedSymbols/AnonymousType.SynthesizedMethodBase.cs index 6a19be4e5f635..41e9f8a26619e 100644 --- a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/SynthesizedSymbols/AnonymousType.SynthesizedMethodBase.cs +++ b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/SynthesizedSymbols/AnonymousType.SynthesizedMethodBase.cs @@ -231,6 +231,8 @@ internal sealed override int CalculateLocalSyntaxOffset(int localPosition, Synta { throw ExceptionUtilities.Unreachable; } + + protected sealed override bool HasSetsRequiredMembersImpl => throw ExceptionUtilities.Unreachable; } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/ErrorMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/ErrorMethodSymbol.cs index 297301f6b0a83..c28022499e6cd 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ErrorMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ErrorMethodSymbol.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Symbols @@ -274,5 +275,14 @@ internal override bool GenerateDebugInfo } internal sealed override bool IsNullableAnalysisEnabled() => false; + + protected override bool HasSetsRequiredMembersImpl + { + get + { + Debug.Assert(MethodKind == MethodKind.Constructor); + return false; + } + } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/FunctionPointers/FunctionPointerMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/FunctionPointers/FunctionPointerMethodSymbol.cs index 2f41d2d20166b..3d33ae01354b3 100644 --- a/src/Compilers/CSharp/Portable/Symbols/FunctionPointers/FunctionPointerMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/FunctionPointers/FunctionPointerMethodSymbol.cs @@ -830,5 +830,6 @@ public override bool IsVararg internal override int CalculateLocalSyntaxOffset(int localPosition, SyntaxTree localTree) => throw ExceptionUtilities.Unreachable; internal override IEnumerable GetSecurityInformation() => throw ExceptionUtilities.Unreachable; internal sealed override bool IsNullableAnalysisEnabled() => throw ExceptionUtilities.Unreachable; + protected sealed override bool HasSetsRequiredMembersImpl => throw ExceptionUtilities.Unreachable; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEMethodSymbol.cs index 13dffcbcdea8c..4f1422e1d986c 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEMethodSymbol.cs @@ -47,7 +47,7 @@ private struct PackedFlags { // We currently pack everything into a 32-bit int with the following layout: // - // |u|t|s|r|q|p|ooo|n|m|l|k|j|i|h|g|f|e|d|c|b|aaaaa| + // |w|v|u|t|s|r|q|p|ooo|n|m|l|k|j|i|h|g|f|e|d|c|b|aaaaa| // // a = method kind. 5 bits. // b = method kind populated. 1 bit. @@ -72,7 +72,9 @@ private struct PackedFlags // s = IsInitOnlyBit. 1 bit. // t = IsInitOnlyPopulatedBit. 1 bit. // u = IsUnmanagedCallersOnlyAttributePopulated. 1 bit. - // 6 bits remain for future purposes. + // v = IsSetsRequiredMembersBit. 1 bit. + // w = IsSetsRequiredMembersPopulated. 1 bit. + // 4 bits remain for future purposes. private const int MethodKindOffset = 0; private const int MethodKindMask = 0x1F; @@ -98,6 +100,8 @@ private struct PackedFlags private const int IsInitOnlyBit = 0x1 << 24; private const int IsInitOnlyPopulatedBit = 0x1 << 25; private const int IsUnmanagedCallersOnlyAttributePopulatedBit = 0x1 << 26; + private const int HasSetsRequiredMembersBit = 0x1 << 27; + private const int HasSetsRequiredMembersPopulatedBit = 0x1 << 28; private int _bits; @@ -134,6 +138,8 @@ public MethodKind MethodKind public bool IsInitOnly => (_bits & IsInitOnlyBit) != 0; public bool IsInitOnlyPopulated => (_bits & IsInitOnlyPopulatedBit) != 0; public bool IsUnmanagedCallersOnlyAttributePopulated => (_bits & IsUnmanagedCallersOnlyAttributePopulatedBit) != 0; + public bool HasSetsRequiredMembers => (_bits & HasSetsRequiredMembersBit) != 0; + public bool HasSetsRequiredMembersPopulated => (_bits & HasSetsRequiredMembersPopulatedBit) != 0; #if DEBUG static PackedFlags() @@ -240,6 +246,14 @@ public void SetIsUnmanagedCallersOnlyAttributePopulated() { ThreadSafeFlagOperations.Set(ref _bits, IsUnmanagedCallersOnlyAttributePopulatedBit); } + + public bool InitializeSetsRequiredMembersBit(bool value) + { + int bitsToSet = HasSetsRequiredMembersPopulatedBit; + if (value) bitsToSet |= HasSetsRequiredMembersBit; + + return ThreadSafeFlagOperations.Set(ref _bits, bitsToSet); + } } /// @@ -1407,6 +1421,21 @@ internal override ImmutableArray GetAppliedConditionalSymbols() } } + protected override bool HasSetsRequiredMembersImpl + { + get + { + Debug.Assert(MethodKind == MethodKind.Constructor); + if (!_packedFlags.HasSetsRequiredMembersPopulated) + { + var result = _containingType.ContainingPEModule.Module.HasAttribute(_handle, AttributeDescription.SetsRequiredMembersAttribute); + _packedFlags.InitializeSetsRequiredMembersBit(result); + } + + return _packedFlags.HasSetsRequiredMembers; + } + } + internal override int CalculateLocalSyntaxOffset(int localPosition, SyntaxTree localTree) { throw ExceptionUtilities.Unreachable; diff --git a/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs index 85030a8a2eddf..9cc089a91b540 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs @@ -579,6 +579,13 @@ public bool IsConditional } } + /// + /// Returns true if this is a constructor attributed with HasSetsRequiredMembers + /// + internal bool HasSetsRequiredMembers => MethodKind == MethodKind.Constructor && HasSetsRequiredMembersImpl; + + protected abstract bool HasSetsRequiredMembersImpl { get; } + /// /// Some method kinds do not participate in overriding/hiding (e.g. constructors). /// diff --git a/src/Compilers/CSharp/Portable/Symbols/ReducedExtensionMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/ReducedExtensionMethodSymbol.cs index acaf3d1f1e2d6..13b88243aa82d 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ReducedExtensionMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ReducedExtensionMethodSymbol.cs @@ -588,6 +588,8 @@ public override int GetHashCode() return _reducedFrom.GetHashCode(); } + protected sealed override bool HasSetsRequiredMembersImpl => throw ExceptionUtilities.Unreachable; + #nullable enable private sealed class ReducedExtensionMethodParameterSymbol : WrappedParameterSymbol diff --git a/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyMethodSymbol.cs index 59a09861eb1cf..bb92c1a6e5afb 100644 --- a/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyMethodSymbol.cs @@ -172,6 +172,8 @@ internal override bool IsMetadataFinal internal override int CalculateLocalSyntaxOffset(int localPosition, SyntaxTree localTree) { throw ExceptionUtilities.Unreachable; } + protected sealed override bool HasSetsRequiredMembersImpl => throw ExceptionUtilities.Unreachable; + #endregion } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbolBase.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbolBase.cs index 1c35bb314b9a9..4689b1cf024a1 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbolBase.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbolBase.cs @@ -246,5 +246,40 @@ internal sealed override int CalculateLocalSyntaxOffset(int position, SyntaxTree protected abstract CSharpSyntaxNode GetInitializer(); protected abstract bool IsWithinExpressionOrBlockBody(int position, out int offset); + +#nullable enable + protected sealed override bool HasSetsRequiredMembersImpl + => GetEarlyDecodedWellKnownAttributeData()?.HasSetsRequiredMembersAttribute == true; + + internal sealed override (CSharpAttributeData?, BoundAttribute?) EarlyDecodeWellKnownAttribute(ref EarlyDecodeWellKnownAttributeArguments arguments) + { + if (arguments.SymbolPart == AttributeLocation.None) + { + if (CSharpAttributeData.IsTargetEarlyAttribute(arguments.AttributeType, arguments.AttributeSyntax, AttributeDescription.SetsRequiredMembersAttribute)) + { + var earlyData = arguments.GetOrCreateData(); + earlyData.HasSetsRequiredMembersAttribute = true; + + if (ContainingType.IsWellKnownSetsRequiredMembersAttribute()) + { + // Avoid a binding cycle for this scenario. + return (null, null); + } + + var (attributeData, boundAttribute) = arguments.Binder.GetAttribute(arguments.AttributeSyntax, arguments.AttributeType, beforeAttributePartBound: null, afterAttributePartBound: null, out bool hasAnyDiagnostics); + + if (!hasAnyDiagnostics) + { + return (attributeData, boundAttribute); + } + else + { + return (null, null); + } + } + } + + return base.EarlyDecodeWellKnownAttribute(ref arguments); + } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbol.cs index 276e745c4547e..127b3aecc35dc 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbol.cs @@ -5,6 +5,7 @@ using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Symbols { @@ -78,5 +79,7 @@ internal void ReportAsyncParameterErrors(BindingDiagnosticBag diagnostics, Locat static Location getLocation(ParameterSymbol parameter, Location location) => parameter.Locations.FirstOrDefault() ?? location; } + + protected override bool HasSetsRequiredMembersImpl => throw ExceptionUtilities.Unreachable; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs index ae7179d9ef187..e3102401bc21e 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs @@ -318,7 +318,7 @@ public override ImmutableArray GetReturnTypeAttributes() } #nullable enable - internal sealed override (CSharpAttributeData?, BoundAttribute?) EarlyDecodeWellKnownAttribute(ref EarlyDecodeWellKnownAttributeArguments arguments) + internal override (CSharpAttributeData?, BoundAttribute?) EarlyDecodeWellKnownAttribute(ref EarlyDecodeWellKnownAttributeArguments arguments) { Debug.Assert(arguments.SymbolPart == AttributeLocation.None || arguments.SymbolPart == AttributeLocation.Return); diff --git a/src/Compilers/CSharp/Portable/Symbols/SymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/SymbolExtensions.cs index 30000f258033d..f5d9ea7070f6b 100644 --- a/src/Compilers/CSharp/Portable/Symbols/SymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/SymbolExtensions.cs @@ -843,7 +843,6 @@ internal static bool HasAsyncMethodBuilderAttribute(this Symbol symbol, [NotNull internal static bool IsRequired(this Symbol symbol) => symbol is FieldSymbol { IsRequired: true } or PropertySymbol { IsRequired: true }; internal static bool ShouldCheckRequiredMembers(this MethodSymbol method) - // PROTOTYPE(req): Check for the SetsRequiredMembersAttribute and return false for that case - => method.MethodKind == MethodKind.Constructor; + => method.MethodKind == MethodKind.Constructor && !method.HasSetsRequiredMembers; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordCopyCtor.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordCopyCtor.cs index 511c0e79b50e0..87d8720c55e8a 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordCopyCtor.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordCopyCtor.cs @@ -64,6 +64,11 @@ internal override void AddSynthesizedAttributes(PEModuleBuilder moduleBuilder, r var compilation = this.DeclaringCompilation; AddSynthesizedAttribute(ref attributes, compilation.TrySynthesizeAttribute(WellKnownMember.System_Runtime_CompilerServices_CompilerGeneratedAttribute__ctor)); Debug.Assert(WellKnownMembers.IsSynthesizedAttributeOptional(WellKnownMember.System_Runtime_CompilerServices_CompilerGeneratedAttribute__ctor)); + + if (HasSetsRequiredMembersImpl) + { + AddSynthesizedAttribute(ref attributes, compilation.TrySynthesizeAttribute(WellKnownMember.System_Diagnostics_CodeAnalysis_SetsRequiredMembersAttribute__ctor)); + } } internal static MethodSymbol? FindCopyConstructor(NamedTypeSymbol containingType, NamedTypeSymbol within, ref CompoundUseSiteInfo useSiteInfo) @@ -127,5 +132,9 @@ internal static bool HasCopyConstructorSignature(MethodSymbol member) method.Parameters[0].Type.Equals(containingType, TypeCompareKind.AllIgnoreOptions) && method.Parameters[0].RefKind == RefKind.None; } + + protected sealed override bool HasSetsRequiredMembersImpl + // If the record type has a required members error, then it does have required members of some kind, we emit the SetsRequiredMembers attribute. + => !ContainingType.AllRequiredMembers.IsEmpty || ContainingType.HasRequiredMembersError; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedDelegateSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedDelegateSymbol.cs index e5c17ffc70887..b8c36fde38207 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedDelegateSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedDelegateSymbol.cs @@ -238,5 +238,7 @@ public override bool IsExtern { get { return false; } } + + protected sealed override bool HasSetsRequiredMembersImpl => throw ExceptionUtilities.Unreachable; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs index 6778b714ce4a4..22aa8e6b65c79 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs @@ -307,6 +307,8 @@ private static BoundCall CreateParameterlessCall(CSharpSyntaxNode syntax, BoundE { WasCompilerGenerated = true }; } + protected sealed override bool HasSetsRequiredMembersImpl => throw ExceptionUtilities.Unreachable; + /// A synthesized entrypoint that forwards all calls to an async Main Method internal sealed class AsyncForwardEntryPoint : SynthesizedEntryPointSymbol { diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedGlobalMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedGlobalMethodSymbol.cs index 4b66d9d66ebfe..893bb85ea8ed1 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedGlobalMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedGlobalMethodSymbol.cs @@ -335,5 +335,7 @@ internal override int CalculateLocalSyntaxOffset(int localPosition, SyntaxTree l } internal sealed override bool IsNullableAnalysisEnabled() => false; + + protected sealed override bool HasSetsRequiredMembersImpl => throw ExceptionUtilities.Unreachable; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedImplementationMethod.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedImplementationMethod.cs index 23fe785515aa3..eeecc9bf23d90 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedImplementationMethod.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedImplementationMethod.cs @@ -265,5 +265,7 @@ internal override ImmutableArray GetAppliedConditionalSymbols() { return ImmutableArray.Empty; } + + protected sealed override bool HasSetsRequiredMembersImpl => throw ExceptionUtilities.Unreachable; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedInstanceConstructor.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedInstanceConstructor.cs index 0c27009d34a61..47ee92c9851bc 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedInstanceConstructor.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedInstanceConstructor.cs @@ -316,5 +316,6 @@ internal virtual void GenerateMethodBodyStatements(SyntheticBoundNodeFactory fac // overridden in a derived class to add extra statements to the body of the generated constructor } + protected override bool HasSetsRequiredMembersImpl => false; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedInteractiveInitializerMethod.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedInteractiveInitializerMethod.cs index c6f7bc3fbe8dc..69baa906a9872 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedInteractiveInitializerMethod.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedInteractiveInitializerMethod.cs @@ -275,5 +275,7 @@ private static void CalculateReturnType( : compilation.GetTypeByReflectionType(submissionReturnTypeOpt, diagnostics); returnType = taskT.Construct(resultType); } + + protected sealed override bool HasSetsRequiredMembersImpl => throw ExceptionUtilities.Unreachable; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedIntrinsicOperatorSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedIntrinsicOperatorSymbol.cs index 95de7c40285d0..d9960d15c5940 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedIntrinsicOperatorSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedIntrinsicOperatorSymbol.cs @@ -423,6 +423,8 @@ internal override int CalculateLocalSyntaxOffset(int localPosition, SyntaxTree l internal sealed override bool IsNullableAnalysisEnabled() => false; + protected sealed override bool HasSetsRequiredMembersImpl => throw ExceptionUtilities.Unreachable; + public override bool Equals(Symbol obj, TypeCompareKind compareKind) { if (obj == (object)this) diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedStaticConstructor.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedStaticConstructor.cs index 1166c2218d61a..948bd9f64071d 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedStaticConstructor.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedStaticConstructor.cs @@ -428,5 +428,7 @@ private bool CalculateShouldEmit(ImmutableArray boundInitializ return false; } + + protected sealed override bool HasSetsRequiredMembersImpl => throw ExceptionUtilities.Unreachable; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs index a83da25243d79..c82986c2d5a9f 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs @@ -2002,6 +2002,12 @@ private static bool IsWellKnownCompilerServicesTopLevelType(this TypeSymbol type internal static bool IsCompilerServicesTopLevelType(this TypeSymbol typeSymbol) => typeSymbol.ContainingType is null && IsContainedInNamespace(typeSymbol, "System", "Runtime", "CompilerServices"); + internal static bool IsWellKnownSetsRequiredMembersAttribute(this TypeSymbol type) + => type.Name == "SetsRequiredMembersAttribute" && type.IsWellKnownDiagnosticsCodeAnalysisTopLevelType(); + + private static bool IsWellKnownDiagnosticsCodeAnalysisTopLevelType(this TypeSymbol typeSymbol) + => typeSymbol.ContainingType is null && IsContainedInNamespace(typeSymbol, "System", "Diagnostics", "CodeAnalysis"); + private static bool IsContainedInNamespace(this TypeSymbol typeSymbol, string outerNS, string midNS, string innerNS) { var innerNamespace = typeSymbol.ContainingNamespace; diff --git a/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedMethodSymbol.cs index 19343a9b2d55b..06f156ca7690b 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedMethodSymbol.cs @@ -358,5 +358,7 @@ internal override bool GenerateDebugInfo internal override bool IsDeclaredReadOnly => UnderlyingMethod.IsDeclaredReadOnly; internal override bool IsInitOnly => UnderlyingMethod.IsInitOnly; + + protected sealed override bool HasSetsRequiredMembersImpl => UnderlyingMethod.HasSetsRequiredMembers; } } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/RequiredMembersTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/RequiredMembersTests.cs index bc1701d1b9733..420c6ac269cfe 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/RequiredMembersTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/RequiredMembersTests.cs @@ -24,10 +24,16 @@ Namespace System.Runtime.CompilerServices Public Class RequiredMemberAttribute Inherits Attribute End Class +End Namespace +Namespace System.Diagnostics.CodeAnalysis + + Public Class SetsRequiredMembersAttribute + Inherits Attribute + End Class End Namespace"; private static CSharpCompilation CreateCompilationWithRequiredMembers(CSharpTestSource source, IEnumerable? references = null, CSharpParseOptions? parseOptions = null, CSharpCompilationOptions? options = null, string? assemblyName = null, TargetFramework targetFramework = TargetFramework.Standard) - => CreateCompilation(new[] { source, RequiredMemberAttribute }, references, options: options, parseOptions: parseOptions, assemblyName: assemblyName, targetFramework: targetFramework); + => CreateCompilation(new[] { source, RequiredMemberAttribute, SetsRequiredMembersAttribute }, references, options: options, parseOptions: parseOptions, assemblyName: assemblyName, targetFramework: targetFramework); private Compilation CreateVisualBasicCompilationWithRequiredMembers(string source) => CreateVisualBasicCompilation(new[] { source, RequiredMemberAttributeVB }); @@ -1138,7 +1144,6 @@ class C } "); - // PROTOTYPE(req): Confirm with LDM whether we want a warning here. comp.VerifyDiagnostics(); } @@ -1153,7 +1158,6 @@ class C } "); - // PROTOTYPE(req): Confirm with LDM whether we want an error here. comp.VerifyDiagnostics( // (5,29): error CS9505: Required member 'C.Prop' must be settable. // public required ref int Prop => ref i; @@ -1170,17 +1174,21 @@ class C { public required readonly int Field; public required int Prop1 { get; } + public required int Prop2 { get; protected set; } } "); - // PROTOTYPE(req): Confirm with LDM whether we want an error here. comp.VerifyDiagnostics( // (5,34): error CS9505: Required member 'C.Field' must be settable. // public required readonly int Field; Diagnostic(ErrorCode.ERR_RequiredMemberMustBeSettable, "Field").WithArguments("C.Field").WithLocation(5, 34), // (6,25): error CS9505: Required member 'C.Prop1' must be settable. // public required int Prop1 { get; } - Diagnostic(ErrorCode.ERR_RequiredMemberMustBeSettable, "Prop1").WithArguments("C.Prop1").WithLocation(6, 25) + Diagnostic(ErrorCode.ERR_RequiredMemberMustBeSettable, "Prop1").WithArguments("C.Prop1").WithLocation(6, 25), + // PROTOTYPE(req): Better error message? + // (7,25): error CS9503: Required member 'C.Prop2' cannot be less visible or have a setter less visible than the containing type 'C'. + // public required int Prop2 { get; protected set; } + Diagnostic(ErrorCode.ERR_RequiredMemberCannotBeLessVisibleThanContainingType, "Prop2").WithArguments("C.Prop2", "C").WithLocation(7, 25) ); } @@ -1265,6 +1273,33 @@ public class C comp.VerifyDiagnostics(expectedDiagnostics); } + [Theory] + [CombinatorialData] + public void EnforcedRequiredMembers_NoInheritance_NoneSet_HasSetsRequiredMembers(bool useMetadataReference, [CombinatorialValues("", " C")] string constructor) + { + var c = @" +using System.Diagnostics.CodeAnalysis; +public class C +{ + public required int Prop { get; set; } + public required int Field; + + [SetsRequiredMembers] + public C() {} +} +"; + + var creation = $@"C c = new{constructor}();"; + var comp = CreateCompilationWithRequiredMembers(new[] { c, creation }); + + comp.VerifyDiagnostics(); + + var cComp = CreateCompilationWithRequiredMembers(c); + comp = CreateCompilation(creation, references: new[] { useMetadataReference ? cComp.ToMetadataReference() : cComp.EmitToImageReference() }); + + comp.VerifyDiagnostics(); + } + [Theory] [CombinatorialData] public void EnforcedRequiredMembers_NoInheritance_PartialSet(bool useMetadataReference, [CombinatorialValues("", " C")] string constructor) @@ -1407,6 +1442,40 @@ End Class ); } + [Fact] + public void EnforcedRequiredMembers_NoInheritance_Unsettable_HasSetsRequiredMembers() + { + var c = @" +using System.Diagnostics.CodeAnalysis; +var c = new C(); +c = new C() { Prop1 = 1, Field1 = 1 }; + +public class C +{ + public required int Prop1 { get; } + public required readonly int Field1; + + [SetsRequiredMembers] + public C() {} +} +"; + var comp = CreateCompilationWithRequiredMembers(c); + comp.VerifyDiagnostics( + // (4,15): error CS0200: Property or indexer 'C.Prop1' cannot be assigned to -- it is read only + // c = new C() { Prop1 = 1, Field1 = 1 }; + Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "Prop1").WithArguments("C.Prop1").WithLocation(4, 15), + // (4,26): error CS0191: A readonly field cannot be assigned to (except in a constructor or init-only setter of the type in which the field is defined or a variable initializer) + // c = new C() { Prop1 = 1, Field1 = 1 }; + Diagnostic(ErrorCode.ERR_AssgReadonly, "Field1").WithLocation(4, 26), + // (8,25): error CS9505: Required member 'C.Prop1' must be settable. + // public required int Prop1 { get; } + Diagnostic(ErrorCode.ERR_RequiredMemberMustBeSettable, "Prop1").WithArguments("C.Prop1").WithLocation(8, 25), + // (9,34): error CS9505: Required member 'C.Field1' must be settable. + // public required readonly int Field1; + Diagnostic(ErrorCode.ERR_RequiredMemberMustBeSettable, "Field1").WithArguments("C.Field1").WithLocation(9, 34) + ); + } + [Fact] public void EnforcedRequiredMembers_NoInheritance_DisallowedNestedObjectInitializer() { @@ -1434,6 +1503,30 @@ public class D ); } + [Fact] + public void EnforcedRequiredMembers_NoInheritance_DisallowedNestedObjectInitializer_HasSetsRequiredMembers() + { + var c = @" +using System.Diagnostics.CodeAnalysis; +var c = new C() { D1 = { NestedProp = 1 }, D2 = { NestedProp = 2 } }; + +public class C +{ + public required D D1 { get; set; } + public required D D2; + + [SetsRequiredMembers] + public C() {} +} +public class D +{ + public int NestedProp { get; set; } +} +"; + var comp = CreateCompilationWithRequiredMembers(c); + comp.VerifyDiagnostics(); + } + [Fact] public void EnforcedRequiredMembers_NoInheritance_NestedObjectCreationAllowed() { @@ -1478,6 +1571,27 @@ public class C ); } + [Fact] + public void EnforcedRequiredMembers_NoInheritance_DisallowedNestedCollectionInitializer_HasSetsRequiredMember() + { + var c = @" +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +var c = new C() { L1 = { 1, 2, 3 }, L2 = { 4, 5, 6 } }; + +public class C +{ + public required List L1 { get; set; } + public required List L2; + + [SetsRequiredMembers] + public C() {} +} +"; + var comp = CreateCompilationWithRequiredMembers(c); + comp.VerifyDiagnostics(); + } + [Fact] public void EnforcedRequiredMembers_NoInheritance_NestedObjectCreationWithCollectionInitializerAllowed() { @@ -1543,6 +1657,48 @@ public class Derived : Base comp.VerifyDiagnostics(expectedDiagnostics); } + [Theory] + [CombinatorialData] + public void EnforcedRequiredMembers_Inheritance_NoneSet_HasSetsRequiredMembers(bool useMetadataReference) + { + var @base = @" +public class Base +{ + public required int Prop1 { get; set; } + public required int Field1; +} +"; + + var derived = @" +using System.Diagnostics.CodeAnalysis; + +public class Derived : Base +{ + public required int Prop2 { get; set; } + public required int Field2; + + [SetsRequiredMembers] + public Derived() {} +} +"; + + var code = @"_ = new Derived();"; + + var comp = CreateCompilationWithRequiredMembers(new[] { @base, derived, code }); + + comp.VerifyDiagnostics(); + + var baseComp = CreateCompilationWithRequiredMembers(@base); + baseComp.VerifyEmitDiagnostics(); + var baseRef = useMetadataReference ? baseComp.ToMetadataReference() : baseComp.EmitToImageReference(); + + var derivedComp = CreateCompilation(derived, new[] { baseRef }); + derivedComp.VerifyEmitDiagnostics(); + + comp = CreateCompilation(code, new[] { baseRef, useMetadataReference ? derivedComp.ToMetadataReference() : derivedComp.EmitToImageReference() }); + comp.VerifyDiagnostics(); + } + [Theory] [CombinatorialData] public void EnforcedRequiredMembers_Inheritance_PartialSet(bool useMetadataReference) @@ -1676,6 +1832,52 @@ public class Derived : Base Assert.IsType(baseSymbol); } + [Fact] + public void EnforcedRequiredMembers_ThroughRetargeting_NoneSet_HasSetsRequiredMembers() + { + var retargetedCode = @"public class C {}"; + + var originalC = CreateCompilation(new AssemblyIdentity("Ret", new Version(1, 0, 0, 0), isRetargetable: true), retargetedCode, TargetFrameworkUtil.StandardReferences); + + var @base = @" +public class Base +{ + public required C Prop1 { get; set; } + public required C Field1; +} +"; + + var originalCRef = originalC.ToMetadataReference(); + var baseComp = CreateCompilationWithRequiredMembers(@base, new[] { originalCRef }, targetFramework: TargetFramework.Standard); + + var derived = @" +using System.Diagnostics.CodeAnalysis; +public class Derived : Base +{ + public required C Prop2 { get; set; } + public required C Field2; + + [SetsRequiredMembers] + public Derived() {} +} +"; + + var baseRef = baseComp.ToMetadataReference(); + var derivedComp = CreateCompilation(derived, new[] { baseRef, originalCRef }, targetFramework: TargetFramework.Standard); + + var retargetedC = CreateCompilation(new AssemblyIdentity("Ret", new Version(2, 0, 0, 0), isRetargetable: true), retargetedCode, TargetFrameworkUtil.StandardReferences); + + var code = @" +_ = new Derived(); +"; + + var comp = CreateCompilation(code, new[] { baseRef, derivedComp.ToMetadataReference(), retargetedC.ToMetadataReference() }, targetFramework: TargetFramework.Standard); + comp.VerifyDiagnostics(); + + var baseSymbol = comp.GetTypeByMetadataName("Derived"); + Assert.IsType(baseSymbol); + } + [Fact] public void EnforcedRequiredMembers_ThroughRetargeting_AllSet() { @@ -1749,6 +1951,41 @@ public class Derived : Base comp.VerifyDiagnostics(expectedDiagnostics); } + [Theory] + [CombinatorialData] + public void EnforcedRequiredMembers_Override_NoneSet_HasSetsRequiredMembers(bool useMetadataReference) + { + var @base = @" +public class Base +{ + public virtual required int Prop1 { get; set; } +} +"; + + var code = @" +using System.Diagnostics.CodeAnalysis; +_ = new Derived(); + +public class Derived : Base +{ + public override required int Prop1 { get; set; } + + [SetsRequiredMembers] + public Derived() {} +} +"; + + var comp = CreateCompilationWithRequiredMembers(new[] { @base, code }); + + comp.VerifyDiagnostics(); + + var baseComp = CreateCompilationWithRequiredMembers(@base); + baseComp.VerifyEmitDiagnostics(); + + comp = CreateCompilation(code, new[] { useMetadataReference ? baseComp.ToMetadataReference() : baseComp.EmitToImageReference() }); + comp.VerifyDiagnostics(); + } + [Theory] [CombinatorialData] public void EnforcedRequiredMembers_Override_PartialSet(bool useMetadataReference) @@ -1933,6 +2170,44 @@ class Derived3 : Derived { }"; ); } + [Fact] + public void EnforcedRequiredMembers_ShadowedInSource_01_HasSetsRequiredMembers() + { + var vb = @" +Imports System.Runtime.CompilerServices + +Public Class Base + + Public Property P As Integer +End Class + + +Public Class Derived + Inherits Base + + Public Shadows Property P As Integer +End Class +"; + + var vbComp = CreateVisualBasicCompilationWithRequiredMembers(vb); + CompileAndVerify(vbComp).VerifyDiagnostics(); + + var c = @" +using System.Diagnostics.CodeAnalysis; +_ = new Derived2(); + +class Derived2 : Derived +{ + [SetsRequiredMembers] + public Derived2() {} + [SetsRequiredMembers] + public Derived2(int x) {} +}"; + + var comp = CreateCompilation(c, new[] { vbComp.EmitToImageReference() }); + comp.VerifyDiagnostics(); + } + [Fact] public void EnforcedRequiredMembers_ShadowedInSource_02() { @@ -2016,6 +2291,187 @@ class Derived3 : Derived { }"; ); } + + /// + /// This IL is the equivalent of: + /// public record Derived : Base + /// { + /// public {propertyIsRequired ? required : ""} new int P { get; init; } + /// } + /// + private static string GetShadowingRecordIl(bool propertyIsRequired) + { + var propertyAttribute = propertyIsRequired ? + """ + .custom instance void [original]RequiredMemberAttribute::.ctor() = ( + 01 00 00 00 + ) + """ : ""; + return $$""" + .assembly extern original {} + + .class public auto ansi beforefieldinit Derived + extends [original]Base + implements class [mscorlib]System.IEquatable`1 + { + .custom instance void [original]RequiredMemberAttribute::.ctor() = ( + 01 00 00 00 + ) + // Fields + .field private initonly int32 '

k__BackingField' + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( + 01 00 00 00 + ) + .custom instance void [mscorlib]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggerBrowsableState) = ( + 01 00 00 00 00 00 00 00 + ) + + // Methods + .method family hidebysig specialname virtual + instance class [mscorlib]System.Type get_EqualityContract () cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( + 01 00 00 00 + ) + ldnull + throw + } // end of method Derived::get_EqualityContract + + .method public hidebysig specialname + instance int32 get_P () cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( + 01 00 00 00 + ) + ldnull + throw + } // end of method Derived::get_P + + .method public hidebysig specialname + instance void modreq([mscorlib]System.Runtime.CompilerServices.IsExternalInit) set_P ( + int32 'value' + ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( + 01 00 00 00 + ) + ldnull + throw + } // end of method Derived::set_P + + .method public hidebysig virtual + instance string ToString () cil managed + { + ldnull + throw + } // end of method Derived::ToString + + .method family hidebysig virtual + instance bool PrintMembers ( + class [mscorlib]System.Text.StringBuilder builder + ) cil managed + { + ldnull + throw + } // end of method Derived::PrintMembers + + .method public hidebysig specialname static + bool op_Inequality ( + class Derived left, + class Derived right + ) cil managed + { + ldnull + throw + } // end of method Derived::op_Inequality + + .method public hidebysig specialname static + bool op_Equality ( + class Derived left, + class Derived right + ) cil managed + { + ldnull + throw + } // end of method Derived::op_Equality + + .method public hidebysig virtual + instance int32 GetHashCode () cil managed + { + ldnull + throw + } // end of method Derived::GetHashCode + + .method public hidebysig virtual + instance bool Equals ( + object obj + ) cil managed + { + ldnull + throw + } // end of method Derived::Equals + + .method public final hidebysig virtual + instance bool Equals ( + class [original]Base other + ) cil managed + { + ldnull + throw + } // end of method Derived::Equals + + .method public hidebysig newslot virtual + instance bool Equals ( + class Derived other + ) cil managed + { + ldnull + throw + } // end of method Derived::Equals + + .method public hidebysig newslot virtual + instance class Derived '$' () cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.PreserveBaseOverridesAttribute::.ctor() = ( + 01 00 00 00 + ) + .override method instance class [original]Base [original]Base::'$'() + ldnull + throw + } // end of method Derived::'$' + + .method family hidebysig specialname rtspecialname + instance void .ctor ( + class Derived original + ) cil managed + { + ldnull + throw + } // end of method Derived::.ctor + + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + ldnull + throw + } // end of method Derived::.ctor + + // Properties + .property instance class [mscorlib]System.Type EqualityContract() + { + .get instance class [mscorlib]System.Type Derived::get_EqualityContract() + } + .property instance int32 P() + { + {{propertyAttribute}} + .get instance int32 Derived::get_P() + .set instance void modreq([mscorlib]System.Runtime.CompilerServices.IsExternalInit) Derived::set_P(int32) + } + + } // end of class Derived + """; + } + [Fact] public void EnforcedRequiredMembers_ShadowedInSource_04() { @@ -2029,174 +2485,7 @@ public record Base var originalComp = CreateCompilationWithRequiredMembers(new[] { original, IsExternalInitTypeDefinition }, assemblyName: "original"); CompileAndVerify(originalComp, verify: ExecutionConditionUtil.IsCoreClr ? Verification.Passes : Verification.Skipped).VerifyDiagnostics(); - // This IL is the equivalent of: - // public record Derived : Base - // { - // public new int P { get; init; } - // } - - var ilSource = @" -.assembly extern original {} - -.class public auto ansi beforefieldinit Derived - extends [original]Base - implements class [mscorlib]System.IEquatable`1 -{ - .custom instance void [original]RequiredMemberAttribute::.ctor() = ( - 01 00 00 00 - ) - // Fields - .field private initonly int32 '

k__BackingField' - .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( - 01 00 00 00 - ) - .custom instance void [mscorlib]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggerBrowsableState) = ( - 01 00 00 00 00 00 00 00 - ) - - // Methods - .method family hidebysig specialname virtual - instance class [mscorlib]System.Type get_EqualityContract () cil managed - { - .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( - 01 00 00 00 - ) - ldnull - throw - } // end of method Derived::get_EqualityContract - - .method public hidebysig specialname - instance int32 get_P () cil managed - { - .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( - 01 00 00 00 - ) - ldnull - throw - } // end of method Derived::get_P - - .method public hidebysig specialname - instance void modreq([mscorlib]System.Runtime.CompilerServices.IsExternalInit) set_P ( - int32 'value' - ) cil managed - { - .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( - 01 00 00 00 - ) - ldnull - throw - } // end of method Derived::set_P - - .method public hidebysig virtual - instance string ToString () cil managed - { - ldnull - throw - } // end of method Derived::ToString - - .method family hidebysig virtual - instance bool PrintMembers ( - class [mscorlib]System.Text.StringBuilder builder - ) cil managed - { - ldnull - throw - } // end of method Derived::PrintMembers - - .method public hidebysig specialname static - bool op_Inequality ( - class Derived left, - class Derived right - ) cil managed - { - ldnull - throw - } // end of method Derived::op_Inequality - - .method public hidebysig specialname static - bool op_Equality ( - class Derived left, - class Derived right - ) cil managed - { - ldnull - throw - } // end of method Derived::op_Equality - - .method public hidebysig virtual - instance int32 GetHashCode () cil managed - { - ldnull - throw - } // end of method Derived::GetHashCode - - .method public hidebysig virtual - instance bool Equals ( - object obj - ) cil managed - { - ldnull - throw - } // end of method Derived::Equals - - .method public final hidebysig virtual - instance bool Equals ( - class [original]Base other - ) cil managed - { - ldnull - throw - } // end of method Derived::Equals - - .method public hidebysig newslot virtual - instance bool Equals ( - class Derived other - ) cil managed - { - ldnull - throw - } // end of method Derived::Equals - - .method public hidebysig newslot virtual - instance class Derived '$' () cil managed - { - .custom instance void [mscorlib]System.Runtime.CompilerServices.PreserveBaseOverridesAttribute::.ctor() = ( - 01 00 00 00 - ) - .override method instance class [original]Base [original]Base::'$'() - ldnull - throw - } // end of method Derived::'$' - - .method family hidebysig specialname rtspecialname - instance void .ctor ( - class Derived original - ) cil managed - { - ldnull - throw - } // end of method Derived::.ctor - - .method public hidebysig specialname rtspecialname - instance void .ctor () cil managed - { - ldnull - throw - } // end of method Derived::.ctor - - // Properties - .property instance class [mscorlib]System.Type EqualityContract() - { - .get instance class [mscorlib]System.Type Derived::get_EqualityContract() - } - .property instance int32 P() - { - .get instance int32 Derived::get_P() - .set instance void modreq([mscorlib]System.Runtime.CompilerServices.IsExternalInit) Derived::set_P(int32) - } - -} // end of class Derived -"; + var ilSource = GetShadowingRecordIl(propertyIsRequired: false); var il = CompileIL(ilSource); @@ -2218,40 +2507,21 @@ record DerivedDerived3() : Derived; "; var comp = CreateCompilation(c, new[] { il, originalComp.EmitToImageReference() }); - // PROTOTYPE(req): do we want to take the effort to remove some of these duplicate errors? comp.VerifyDiagnostics( - // (6,8): error CS9509: The required members list for the base type 'Derived' is malformed and cannot be interpreted. To use this constructor, apply the 'SetsRequiredMembers' attribute. - // record DerivedDerived1 : Derived - Diagnostic(ErrorCode.ERR_RequiredMembersBaseTypeInvalid, "DerivedDerived1").WithArguments("Derived").WithLocation(6, 8), - // (6,8): error CS9508: The required members list for 'Derived' is malformed and cannot be interpreted. - // record DerivedDerived1 : Derived - Diagnostic(ErrorCode.ERR_RequiredMembersInvalid, "DerivedDerived1").WithArguments("Derived").WithLocation(6, 8), // (8,12): error CS9509: The required members list for the base type 'Derived' is malformed and cannot be interpreted. To use this constructor, apply the 'SetsRequiredMembers' attribute. // public DerivedDerived1() Diagnostic(ErrorCode.ERR_RequiredMembersBaseTypeInvalid, "DerivedDerived1").WithArguments("Derived").WithLocation(8, 12), // (12,8): error CS9509: The required members list for the base type 'Derived' is malformed and cannot be interpreted. To use this constructor, apply the 'SetsRequiredMembers' attribute. // record DerivedDerived2 : Derived Diagnostic(ErrorCode.ERR_RequiredMembersBaseTypeInvalid, "DerivedDerived2").WithArguments("Derived").WithLocation(12, 8), - // (12,8): error CS9509: The required members list for the base type 'Derived' is malformed and cannot be interpreted. To use this constructor, apply the 'SetsRequiredMembers' attribute. - // record DerivedDerived2 : Derived - Diagnostic(ErrorCode.ERR_RequiredMembersBaseTypeInvalid, "DerivedDerived2").WithArguments("Derived").WithLocation(12, 8), - // (12,8): error CS9508: The required members list for 'Derived' is malformed and cannot be interpreted. - // record DerivedDerived2 : Derived - Diagnostic(ErrorCode.ERR_RequiredMembersInvalid, "DerivedDerived2").WithArguments("Derived").WithLocation(12, 8), // (15,8): error CS9509: The required members list for the base type 'Derived' is malformed and cannot be interpreted. To use this constructor, apply the 'SetsRequiredMembers' attribute. // record DerivedDerived3() : Derived; - Diagnostic(ErrorCode.ERR_RequiredMembersBaseTypeInvalid, "DerivedDerived3").WithArguments("Derived").WithLocation(15, 8), - // (15,8): error CS9509: The required members list for the base type 'Derived' is malformed and cannot be interpreted. To use this constructor, apply the 'SetsRequiredMembers' attribute. - // record DerivedDerived3() : Derived; - Diagnostic(ErrorCode.ERR_RequiredMembersBaseTypeInvalid, "DerivedDerived3").WithArguments("Derived").WithLocation(15, 8), - // (15,8): error CS9508: The required members list for 'Derived' is malformed and cannot be interpreted. - // record DerivedDerived3() : Derived; - Diagnostic(ErrorCode.ERR_RequiredMembersInvalid, "DerivedDerived3").WithArguments("Derived").WithLocation(15, 8) + Diagnostic(ErrorCode.ERR_RequiredMembersBaseTypeInvalid, "DerivedDerived3").WithArguments("Derived").WithLocation(15, 8) ); } [Fact] - public void EnforcedRequiredMembers_ShadowedInSource_06() + public void EnforcedRequiredMembers_ShadowedInSource_04_HasSetsRequiredMembers() { var original = @" public record Base @@ -2263,178 +2533,90 @@ public record Base var originalComp = CreateCompilationWithRequiredMembers(new[] { original, IsExternalInitTypeDefinition }, assemblyName: "original"); CompileAndVerify(originalComp, verify: ExecutionConditionUtil.IsCoreClr ? Verification.Passes : Verification.Skipped).VerifyDiagnostics(); - // This IL is the equivalent of: - // public record Derived : Base - // { - // public new required int P { get; init; } - // } - - var ilSource = @" -.assembly extern original {} - -.class public auto ansi beforefieldinit Derived - extends [original]Base - implements class [mscorlib]System.IEquatable`1 -{ - .custom instance void [original]RequiredMemberAttribute::.ctor() = ( - 01 00 00 00 - ) - // Fields - .field private initonly int32 '

k__BackingField' - .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( - 01 00 00 00 - ) - .custom instance void [mscorlib]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggerBrowsableState) = ( - 01 00 00 00 00 00 00 00 - ) - - // Methods - .method family hidebysig specialname virtual - instance class [mscorlib]System.Type get_EqualityContract () cil managed - { - .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( - 01 00 00 00 - ) - ldnull - throw - } // end of method Derived::get_EqualityContract - - .method public hidebysig specialname - instance int32 get_P () cil managed - { - .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( - 01 00 00 00 - ) - ldnull - throw - } // end of method Derived::get_P - - .method public hidebysig specialname - instance void modreq([mscorlib]System.Runtime.CompilerServices.IsExternalInit) set_P ( - int32 'value' - ) cil managed - { - .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( - 01 00 00 00 - ) - ldnull - throw - } // end of method Derived::set_P - - .method public hidebysig virtual - instance string ToString () cil managed - { - ldnull - throw - } // end of method Derived::ToString - - .method family hidebysig virtual - instance bool PrintMembers ( - class [mscorlib]System.Text.StringBuilder builder - ) cil managed - { - ldnull - throw - } // end of method Derived::PrintMembers - - .method public hidebysig specialname static - bool op_Inequality ( - class Derived left, - class Derived right - ) cil managed - { - ldnull - throw - } // end of method Derived::op_Inequality - - .method public hidebysig specialname static - bool op_Equality ( - class Derived left, - class Derived right - ) cil managed - { - ldnull - throw - } // end of method Derived::op_Equality - - .method public hidebysig virtual - instance int32 GetHashCode () cil managed - { - ldnull - throw - } // end of method Derived::GetHashCode - - .method public hidebysig virtual - instance bool Equals ( - object obj - ) cil managed - { - ldnull - throw - } // end of method Derived::Equals - - .method public final hidebysig virtual - instance bool Equals ( - class [original]Base other - ) cil managed - { - ldnull - throw - } // end of method Derived::Equals - - .method public hidebysig newslot virtual - instance bool Equals ( - class Derived other - ) cil managed - { - ldnull - throw - } // end of method Derived::Equals - - .method public hidebysig newslot virtual - instance class Derived '$' () cil managed - { - .custom instance void [mscorlib]System.Runtime.CompilerServices.PreserveBaseOverridesAttribute::.ctor() = ( - 01 00 00 00 - ) - .override method instance class [original]Base [original]Base::'$'() - ldnull - throw - } // end of method Derived::'$' + var ilSource = GetShadowingRecordIl(propertyIsRequired: false); - .method family hidebysig specialname rtspecialname - instance void .ctor ( - class Derived original - ) cil managed + var il = CompileIL(ilSource); + + var c = @" +using System.Diagnostics.CodeAnalysis; + +_ = new DerivedDerived1(); + +record DerivedDerived1 : Derived +{ + [SetsRequiredMembers] + public DerivedDerived1() { - ldnull - throw - } // end of method Derived::.ctor + } +} +"; - .method public hidebysig specialname rtspecialname - instance void .ctor () cil managed + var comp = CreateCompilation(c, new[] { il, originalComp.EmitToImageReference() }); + comp.VerifyDiagnostics(); + } + + [Fact] + public void EnforcedRequiredMembers_ShadowedInSource_04_HasSetsRequiredMembers_ManualBaseCall() { - ldnull - throw - } // end of method Derived::.ctor + var original = @" +public record Base +{ + public required int P { get; init; } +} +"; + + var originalComp = CreateCompilationWithRequiredMembers(new[] { original, IsExternalInitTypeDefinition }, assemblyName: "original"); + CompileAndVerify(originalComp, verify: ExecutionConditionUtil.IsCoreClr ? Verification.Passes : Verification.Skipped).VerifyDiagnostics(); + + var ilSource = GetShadowingRecordIl(propertyIsRequired: false); + + var il = CompileIL(ilSource); - // Properties - .property instance class [mscorlib]System.Type EqualityContract() + var c = @" +using System.Diagnostics.CodeAnalysis; + +_ = new DerivedDerived1(); + +record DerivedDerived1 : Derived +{ + [SetsRequiredMembers] + public DerivedDerived1() { - .get instance class [mscorlib]System.Type Derived::get_EqualityContract() } - .property instance int32 P() + + [SetsRequiredMembers] + public DerivedDerived1(int unused) : base(null) { - .custom instance void [original]RequiredMemberAttribute::.ctor() = ( - 01 00 00 00 - ) - .get instance int32 Derived::get_P() - .set instance void modreq([mscorlib]System.Runtime.CompilerServices.IsExternalInit) Derived::set_P(int32) } -} // end of class Derived + public DerivedDerived1(bool unused) : base(null) + { + } +} +"; + + var comp = CreateCompilation(c, new[] { il, originalComp.EmitToImageReference() }); + comp.VerifyDiagnostics( + // (18,12): error CS9509: The required members list for the base type 'Derived' is malformed and cannot be interpreted. To use this constructor, apply the 'SetsRequiredMembers' attribute. + // public DerivedDerived1(bool unused) : base(null) + Diagnostic(ErrorCode.ERR_RequiredMembersBaseTypeInvalid, "DerivedDerived1").WithArguments("Derived").WithLocation(18, 12) + ); + } + + [Fact] + public void EnforcedRequiredMembers_ShadowedInSource_06() + { + var original = @" +public record Base +{ + public required int P { get; init; } +} "; + var originalComp = CreateCompilationWithRequiredMembers(new[] { original, IsExternalInitTypeDefinition }, assemblyName: "original"); + CompileAndVerify(originalComp, verify: ExecutionConditionUtil.IsCoreClr ? Verification.Passes : Verification.Skipped).VerifyDiagnostics(); + + var ilSource = GetShadowingRecordIl(propertyIsRequired: true); var il = CompileIL(ilSource); var c = @" @@ -2455,42 +2637,58 @@ record DerivedDerived3() : Derived; "; var comp = CreateCompilation(c, new[] { il, originalComp.EmitToImageReference() }); - // PROTOTYPE(req): do we want to take the effort to remove some of these duplicate errors? comp.VerifyDiagnostics( - // (6,8): error CS9509: The required members list for the base type 'Derived' is malformed and cannot be interpreted. To use this constructor, apply the 'SetsRequiredMembers' attribute. - // record DerivedDerived1 : Derived - Diagnostic(ErrorCode.ERR_RequiredMembersBaseTypeInvalid, "DerivedDerived1").WithArguments("Derived").WithLocation(6, 8), - // (6,8): error CS9508: The required members list for 'Derived' is malformed and cannot be interpreted. - // record DerivedDerived1 : Derived - Diagnostic(ErrorCode.ERR_RequiredMembersInvalid, "DerivedDerived1").WithArguments("Derived").WithLocation(6, 8), // (8,12): error CS9509: The required members list for the base type 'Derived' is malformed and cannot be interpreted. To use this constructor, apply the 'SetsRequiredMembers' attribute. // public DerivedDerived1() Diagnostic(ErrorCode.ERR_RequiredMembersBaseTypeInvalid, "DerivedDerived1").WithArguments("Derived").WithLocation(8, 12), // (12,8): error CS9509: The required members list for the base type 'Derived' is malformed and cannot be interpreted. To use this constructor, apply the 'SetsRequiredMembers' attribute. // record DerivedDerived2 : Derived Diagnostic(ErrorCode.ERR_RequiredMembersBaseTypeInvalid, "DerivedDerived2").WithArguments("Derived").WithLocation(12, 8), - // (12,8): error CS9509: The required members list for the base type 'Derived' is malformed and cannot be interpreted. To use this constructor, apply the 'SetsRequiredMembers' attribute. - // record DerivedDerived2 : Derived - Diagnostic(ErrorCode.ERR_RequiredMembersBaseTypeInvalid, "DerivedDerived2").WithArguments("Derived").WithLocation(12, 8), - // (12,8): error CS9508: The required members list for 'Derived' is malformed and cannot be interpreted. - // record DerivedDerived2 : Derived - Diagnostic(ErrorCode.ERR_RequiredMembersInvalid, "DerivedDerived2").WithArguments("Derived").WithLocation(12, 8), - // (15,8): error CS9509: The required members list for the base type 'Derived' is malformed and cannot be interpreted. To use this constructor, apply the 'SetsRequiredMembers' attribute. - // record DerivedDerived3() : Derived; - Diagnostic(ErrorCode.ERR_RequiredMembersBaseTypeInvalid, "DerivedDerived3").WithArguments("Derived").WithLocation(15, 8), // (15,8): error CS9509: The required members list for the base type 'Derived' is malformed and cannot be interpreted. To use this constructor, apply the 'SetsRequiredMembers' attribute. // record DerivedDerived3() : Derived; - Diagnostic(ErrorCode.ERR_RequiredMembersBaseTypeInvalid, "DerivedDerived3").WithArguments("Derived").WithLocation(15, 8), - // (15,8): error CS9508: The required members list for 'Derived' is malformed and cannot be interpreted. - // record DerivedDerived3() : Derived; - Diagnostic(ErrorCode.ERR_RequiredMembersInvalid, "DerivedDerived3").WithArguments("Derived").WithLocation(15, 8) + Diagnostic(ErrorCode.ERR_RequiredMembersBaseTypeInvalid, "DerivedDerived3").WithArguments("Derived").WithLocation(15, 8) ); } + [Fact] + public void EnforcedRequiredMembers_ShadowedInSource_06_HasSetsRequiredMembers() + { + var original = @" +public record Base +{ + public required int P { get; init; } +} +"; + + var originalComp = CreateCompilationWithRequiredMembers(new[] { original, IsExternalInitTypeDefinition }, assemblyName: "original"); + CompileAndVerify(originalComp, verify: ExecutionConditionUtil.IsCoreClr ? Verification.Passes : Verification.Skipped).VerifyDiagnostics(); + + var ilSource = GetShadowingRecordIl(propertyIsRequired: true); + var il = CompileIL(ilSource); + + var c = @" +using System.Diagnostics.CodeAnalysis; + +_ = new DerivedDerived1(); + +record DerivedDerived1 : Derived +{ + [SetsRequiredMembers] + public DerivedDerived1() + { + } +} +"; + + var comp = CreateCompilation(c, new[] { il, originalComp.EmitToImageReference() }); + comp.VerifyDiagnostics(); + } + [Fact] public void EnforcedRequiredMembers_ShadowedFromMetadata_01() { var vb = @" +Imports System.Diagnostics.CodeAnalysis Imports System.Runtime.CompilerServices Public Class Base @@ -2503,13 +2701,23 @@ Public Class Derived Inherits Base Public Shadows Property P As Integer + + Public Sub New() + End Sub + + + Public Sub New(unused As Integer) + End Sub End Class "; var vbComp = CreateVisualBasicCompilationWithRequiredMembers(vb); CompileAndVerify(vbComp).VerifyDiagnostics(); - var c = @"_ = new Derived();"; + var c = """ + _ = new Derived(); + _ = new Derived(1); + """; var comp = CreateCompilation(c, new[] { vbComp.EmitToImageReference() }); comp.VerifyDiagnostics( // (1,9): error CS9508: The required members list for 'Derived' is malformed and cannot be interpreted. @@ -2522,6 +2730,7 @@ End Class public void EnforcedRequiredMembers_ShadowedFromMetadata_02() { var vb = @" +Imports System.Diagnostics.CodeAnalysis Imports System.Runtime.CompilerServices Public Class Base @@ -2533,13 +2742,23 @@ End Class Public Class Derived Inherits Base Public Shadows Property P As Integer + + Public Sub New() + End Sub + + + Public Sub New(unused As Integer) + End Sub End Class "; var vbComp = CreateVisualBasicCompilationWithRequiredMembers(vb); CompileAndVerify(vbComp).VerifyDiagnostics(); - var c = @"_ = new Derived();"; + var c = """ + _ = new Derived(); + _ = new Derived(1); + """; var comp = CreateCompilation(c, new[] { vbComp.EmitToImageReference() }); comp.VerifyDiagnostics( // (1,9): error CS9508: The required members list for 'Derived' is malformed and cannot be interpreted. @@ -2562,13 +2781,23 @@ End Class Public Class Derived Inherits Base Public Shadows Property P As Integer + + Public Sub New() + End Sub + + + Public Sub New(unused As Integer) + End Sub End Class "; var vbComp = CreateVisualBasicCompilationWithRequiredMembers(vb); CompileAndVerify(vbComp).VerifyDiagnostics(); - var c = @"_ = new Derived();"; + var c = """ + _ = new Derived(); + _ = new Derived(1); + """; var comp = CreateCompilation(c, new[] { vbComp.EmitToImageReference() }); comp.VerifyDiagnostics( // (1,9): error CS9508: The required members list for 'Derived' is malformed and cannot be interpreted. @@ -2851,6 +3080,42 @@ public class RequiredMemberAttribute : Attribute ); } + [Fact] + public void SetsRequiredMemberInAttribute_Recursive() + { + var code = @" +namespace System.Diagnostics.CodeAnalysis; + +public class SetsRequiredMembersAttribute : Attribute +{ + public required int P { get; set; } + + [SetsRequiredMembers] + public SetsRequiredMembersAttribute() + { + } +} + +public class C +{ + public required int Prop { get; set; } + + [SetsRequiredMembers] + public C() + { + } + + static void M() + { + _ = new C(); + } +} +"; + + var comp = CreateCompilation(new[] { code, RequiredMemberAttribute }); + comp.VerifyDiagnostics(); + } + [Fact, CompilerTrait(CompilerFeature.NullableReferenceTypes)] public void RequiredMemberSuppressesNullabilityWarnings_01() { @@ -3033,21 +3298,26 @@ public void RequiredMemberSuppressesNullabilityWarnings_ChainedConstructor_01() public class C { public required string Prop { get; set; } + public required string Field; public C(bool unused) { } public C() : this(true) { Prop.ToString(); + Field.ToString(); } } "; var comp = CreateCompilationWithRequiredMembers(code); comp.VerifyDiagnostics( - // (11,9): warning CS8602: Dereference of a possibly null reference. + // (12,9): warning CS8602: Dereference of a possibly null reference. // Prop.ToString(); - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop").WithLocation(11, 9) + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop").WithLocation(12, 9), + // (13,9): warning CS8602: Dereference of a possibly null reference. + // Field.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Field").WithLocation(13, 9) ); } @@ -3059,12 +3329,14 @@ public void RequiredMemberSuppressesNullabilityWarnings_ChainedConstructor_02() public struct C { public required string Prop { get; set; } + public required string Field; public C(bool unused) { } public C() : this(true) { Prop.ToString(); + Field.ToString(); } } "; @@ -3072,12 +3344,6 @@ public C() : this(true) var comp = CreateCompilationWithRequiredMembers(code); // PROTOTYPE(req): These errors should change with Rikki's DA changes. comp.VerifyDiagnostics( - // (7,12): error CS0843: Auto-implemented property 'C.Prop' must be fully assigned before control is returned to the caller. - // public C(bool unused) { } - Diagnostic(ErrorCode.ERR_UnassignedThisAutoProperty, "C").WithArguments("C.Prop").WithLocation(7, 12), - // (11,9): warning CS8602: Dereference of a possibly null reference. - // Prop.ToString(); - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop").WithLocation(11, 9) ); } @@ -3099,9 +3365,18 @@ public C(bool unused) : this() var comp = CreateCompilationWithRequiredMembers(code); comp.VerifyDiagnostics( - // (9,9): warning CS8602: Dereference of a possibly null reference. + // (8,12): error CS0171: Field 'C.Field' must be fully assigned before control is returned to the caller + // public C(bool unused) { } + Diagnostic(ErrorCode.ERR_UnassignedThis, "C").WithArguments("C.Field").WithLocation(8, 12), + // (8,12): error CS0843: Auto-implemented property 'C.Prop' must be fully assigned before control is returned to the caller. + // public C(bool unused) { } + Diagnostic(ErrorCode.ERR_UnassignedThisAutoProperty, "C").WithArguments("C.Prop").WithLocation(8, 12), + // (12,9): warning CS8602: Dereference of a possibly null reference. // Prop.ToString(); - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop").WithLocation(9, 9) + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop").WithLocation(12, 9), + // (13,9): warning CS8602: Dereference of a possibly null reference. + // Field.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Field").WithLocation(13, 9) ); } @@ -3110,29 +3385,36 @@ public C(bool unused) : this() [InlineData("")] public void RequiredMemberSuppressesNullabilityWarnings_ChainedConstructor_04(string baseSyntax) { - var code = $@" + var code = $$""" #nullable enable public class Base -{{ - public required string Prop1 {{ get; set; }} - public string Prop2 {{ get; set; }} = null!; -}} +{ + public required string Prop1 { get; set; } + public string Prop2 { get; set; } = null!; + public required string Field1; + public string Field2 = null!; +} public class Derived : Base -{{ - public Derived() {baseSyntax} - {{ +{ + public Derived() {{baseSyntax}} + { Prop1.ToString(); Prop2.ToString(); - }} -}} -"; + Field1.ToString(); + Field2.ToString(); + } +} +"""; var comp = CreateCompilationWithRequiredMembers(code); comp.VerifyDiagnostics( - // (13,9): warning CS8602: Dereference of a possibly null reference. + // (14,9): warning CS8602: Dereference of a possibly null reference. // Prop1.ToString(); - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop1").WithLocation(13, 9) + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop1").WithLocation(14, 9), + // (16,9): warning CS8602: Dereference of a possibly null reference. + // Field1.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Field1").WithLocation(16, 9) ); } @@ -3141,37 +3423,51 @@ public Derived() {baseSyntax} [InlineData("")] public void RequiredMemberSuppressesNullabilityWarnings_ChainedConstructor_05(string baseSyntax) { - var code = @$" + var code = $$""" #nullable enable public class Base -{{ - public required string Prop1 {{ get; set; }} - public string Prop2 {{ get; set; }} = null!; -}} +{ + public required string Prop1 { get; set; } + public string Prop2 { get; set; } = null!; + public required string Field1; + public string Field2 = null!; +} public class Derived : Base -{{ - public required string Prop3 {{ get; set; }} - public string Prop4 {{ get; set; }} = null!; +{ + public required string Prop3 { get; set; } + public string Prop4 { get; set; } = null!; + public required string Field3; + public string Field4 = null!; - public Derived() {baseSyntax} - {{ + public Derived() {{baseSyntax}} + { Prop1.ToString(); // 1 Prop2.ToString(); Prop3.ToString(); // 2 Prop4.ToString(); - }} -}} -"; + Field1.ToString(); // 1 + Field2.ToString(); + Field3.ToString(); // 2 + Field4.ToString(); + } +} +"""; var comp = CreateCompilationWithRequiredMembers(code); comp.VerifyDiagnostics( - // (16,9): warning CS8602: Dereference of a possibly null reference. + // (19,9): warning CS8602: Dereference of a possibly null reference. // Prop1.ToString(); // 1 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop1").WithLocation(16, 9), - // (18,9): warning CS8602: Dereference of a possibly null reference. + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop1").WithLocation(19, 9), + // (21,9): warning CS8602: Dereference of a possibly null reference. // Prop3.ToString(); // 2 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop3").WithLocation(18, 9) + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop3").WithLocation(21, 9), + // (23,9): warning CS8602: Dereference of a possibly null reference. + // Field1.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Field1").WithLocation(23, 9), + // (25,9): warning CS8602: Dereference of a possibly null reference. + // Field3.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Field3").WithLocation(25, 9) ); } @@ -3255,4 +3551,77 @@ public Derived() : this(true) Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "Prop3").WithLocation(20, 9) ); } + + [Fact] + public void SetsRequiredMembersAppliedToRecordCopyConstructor_DeclaredInType() + { + var code = """ + public record C + { + public required string Prop1 { get; set; } + public required int Field1; + } + """; + + var comp = CreateCompilationWithRequiredMembers(code); + CompileAndVerify(comp, sourceSymbolValidator: validate, symbolValidator: validate); + + void validate(ModuleSymbol module) + { + var c = module.GlobalNamespace.GetTypeMember("C"); + var copyCtor = c.GetMembers(".ctor").Cast().Single(m => m.ParameterCount == 1); + + if (copyCtor is SynthesizedRecordCopyCtor) + { + Assert.Empty(copyCtor.GetAttributes()); + } + else + { + AssertEx.Equal("System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute..ctor()", + copyCtor.GetAttributes().Single(a => a.AttributeClass!.IsWellKnownSetsRequiredMembersAttribute()).AttributeConstructor.ToTestDisplayString()); + } + } + } + + [Theory] + [CombinatorialData] + public void SetsRequiredMembersAppliedToRecordCopyConstructor_DeclaredInBase(bool useMetadataReference) + { + var @base = """ + public record Base + { + public required string Prop1 { get; set; } + public required int Field1; + } + """; + + var code = """ + public record Derived : Base; + """; + + var comp = CreateCompilationWithRequiredMembers(new[] { @base, code }); + CompileAndVerify(comp, sourceSymbolValidator: validate, symbolValidator: validate); + + var baseComp = CreateCompilationWithRequiredMembers(@base); + CompileAndVerify(baseComp).VerifyDiagnostics(); + + comp = CreateCompilation(code, references: new[] { useMetadataReference ? baseComp.ToMetadataReference() : baseComp.EmitToImageReference() }); + CompileAndVerify(comp, sourceSymbolValidator: validate, symbolValidator: validate); + + void validate(ModuleSymbol module) + { + var c = module.GlobalNamespace.GetTypeMember("Derived"); + var copyCtor = c.GetMembers(".ctor").Cast().Single(m => m.ParameterCount == 1); + + if (copyCtor is SynthesizedRecordCopyCtor) + { + Assert.Empty(copyCtor.GetAttributes()); + } + else + { + AssertEx.Equal("System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute..ctor()", + copyCtor.GetAttributes().Single(a => a.AttributeClass!.IsWellKnownSetsRequiredMembersAttribute()).AttributeConstructor.ToTestDisplayString()); + } + } + } } diff --git a/src/Compilers/Core/Portable/Symbols/Attributes/AttributeDescription.cs b/src/Compilers/Core/Portable/Symbols/Attributes/AttributeDescription.cs index 0cc374d095026..c472cfd8df66f 100644 --- a/src/Compilers/Core/Portable/Symbols/Attributes/AttributeDescription.cs +++ b/src/Compilers/Core/Portable/Symbols/Attributes/AttributeDescription.cs @@ -479,5 +479,6 @@ static AttributeDescription() internal static readonly AttributeDescription InterpolatedStringHandlerAttribute = new AttributeDescription("System.Runtime.CompilerServices", "InterpolatedStringHandlerAttribute", s_signatures_HasThis_Void_Only); internal static readonly AttributeDescription InterpolatedStringHandlerArgumentAttribute = new AttributeDescription("System.Runtime.CompilerServices", "InterpolatedStringHandlerArgumentAttribute", s_signaturesOfInterpolatedStringArgumentAttribute); internal static readonly AttributeDescription RequiredMemberAttribute = new AttributeDescription("System.Runtime.CompilerServices", "RequiredMemberAttribute", s_signatures_HasThis_Void_Only); + internal static readonly AttributeDescription SetsRequiredMembersAttribute = new AttributeDescription("System.Diagnostics.CodeAnalysis", "SetsRequiredMembersAttribute", s_signatures_HasThis_Void_Only); } } diff --git a/src/Compilers/Core/Portable/Symbols/Attributes/CommonMethodEarlyWellKnownAttributeData.cs b/src/Compilers/Core/Portable/Symbols/Attributes/CommonMethodEarlyWellKnownAttributeData.cs index 94ee22f574fa9..d41640ca4e082 100644 --- a/src/Compilers/Core/Portable/Symbols/Attributes/CommonMethodEarlyWellKnownAttributeData.cs +++ b/src/Compilers/Core/Portable/Symbols/Attributes/CommonMethodEarlyWellKnownAttributeData.cs @@ -52,5 +52,23 @@ public ObsoleteAttributeData? ObsoleteAttributeData } } #endregion + + #region SetsRequiredMembers + private bool _hasSetsRequiredMembers = false; + public bool HasSetsRequiredMembersAttribute + { + get + { + VerifySealed(expected: true); + return _hasSetsRequiredMembers; + } + set + { + VerifySealed(false); + _hasSetsRequiredMembers = value; + SetDataStored(); + } + } + #endregion } } diff --git a/src/Compilers/Core/Portable/WellKnownMember.cs b/src/Compilers/Core/Portable/WellKnownMember.cs index b43227dd5bd20..2a3a3c34cb98e 100644 --- a/src/Compilers/Core/Portable/WellKnownMember.cs +++ b/src/Compilers/Core/Portable/WellKnownMember.cs @@ -518,6 +518,7 @@ internal enum WellKnownMember System_Runtime_CompilerServices_DefaultInterpolatedStringHandler__ToStringAndClear, System_Runtime_CompilerServices_RequiredMemberAttribute__ctor, + System_Diagnostics_CodeAnalysis_SetsRequiredMembersAttribute__ctor, Count diff --git a/src/Compilers/Core/Portable/WellKnownMembers.cs b/src/Compilers/Core/Portable/WellKnownMembers.cs index 74577d8c0ab6a..1fe3638459746 100644 --- a/src/Compilers/Core/Portable/WellKnownMembers.cs +++ b/src/Compilers/Core/Portable/WellKnownMembers.cs @@ -3542,6 +3542,13 @@ static WellKnownMembers() 0, // Method Signature (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, // Return Type + // System_Diagnostics_CodeAnalysis_SetsRequiredMembersAttribute__ctor + (byte)MemberFlags.Constructor, // Flags + (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Diagnostics_CodeAnalysis_SetsRequiredMembersAttribute - WellKnownType.ExtSentinel), // DeclaringTypeId + 0, // Arity + 0, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, // Return Type + }; string[] allNames = new string[(int)WellKnownMember.Count] @@ -3987,6 +3994,7 @@ static WellKnownMembers() ".ctor", // System_Text_StringBuilder__ctor "ToStringAndClear", // System_Runtime_CompilerServices_DefaultInterpolatedStringHandler__ToStringAndClear ".ctor", // System_Runtime_CompilerServices_RequiredMemberAttribute__ctor + ".ctor", // System_Diagnostics_CodeAnalysis_SetsRequiredMembersAttribute__ctor }; s_descriptors = MemberDescriptor.InitializeFromStream(new System.IO.MemoryStream(initializationBytes, writable: false), allNames); diff --git a/src/Compilers/Core/Portable/WellKnownTypes.cs b/src/Compilers/Core/Portable/WellKnownTypes.cs index fd3366365333f..91064608d6142 100644 --- a/src/Compilers/Core/Portable/WellKnownTypes.cs +++ b/src/Compilers/Core/Portable/WellKnownTypes.cs @@ -318,6 +318,7 @@ internal enum WellKnownType System_ArgumentNullException, System_Runtime_CompilerServices_RequiredMemberAttribute, + System_Diagnostics_CodeAnalysis_SetsRequiredMembersAttribute, NextAvailable, // Remember to update the AllWellKnownTypes tests when making changes here @@ -628,7 +629,8 @@ internal static class WellKnownTypes "System.Runtime.CompilerServices.DefaultInterpolatedStringHandler", "System.ArgumentNullException", - "System.Runtime.CompilerServices.RequiredMemberAttribute" + "System.Runtime.CompilerServices.RequiredMemberAttribute", + "System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute", }; private static readonly Dictionary s_nameToTypeIdMap = new Dictionary((int)Count); diff --git a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs index 953e494b360f7..8241509bd0d1f 100644 --- a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs +++ b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs @@ -636,6 +636,19 @@ public RequiredMemberAttribute() } } } +"; + + protected const string SetsRequiredMembersAttribute = @" +namespace System.Diagnostics.CodeAnalysis +{ + [AttributeUsage(AttributeTargets.Constructor, Inherited = false, AllowMultiple = false)] + public class SetsRequiredMembersAttribute : Attribute + { + public SetsRequiredMembersAttribute() + { + } + } +} "; protected static CSharpCompilationOptions WithNullableEnable(CSharpCompilationOptions options = null)