diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
index 81feaabf66212..bc0191b8dd97e 100644
--- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
+++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
@@ -4967,6 +4967,78 @@ private static void ReportDuplicateObjectMemberInitializers(BoundExpression boun
}
}
+ private static void CheckRequiredMembersInObjectInitializer(
+ BoundObjectCreationExpression creation,
+ BindingDiagnosticBag diagnostics)
+ {
+ if (!creation.Constructor.ShouldCheckRequiredMembers())
+ {
+ return;
+ }
+
+ if (creation.Constructor.ContainingType.HasRequiredMembersError)
+ {
+ // An error will be reported on the constructor if from source, or a use-site diagnostic will be reported on the use if from metadata.
+ return;
+ }
+
+ var requiredMembers = creation.Constructor.ContainingType.AllRequiredMembers.ToBuilder();
+
+ if (requiredMembers.Count == 0)
+ {
+ return;
+ }
+
+ if (creation.InitializerExpressionOpt == null)
+ {
+ reportMembers();
+ return;
+ }
+
+ foreach (var initializer in creation.InitializerExpressionOpt.Initializers)
+ {
+ if (initializer is not BoundAssignmentOperator { Left: BoundObjectInitializerMember member, Right: { } initializerExpression })
+ {
+ continue;
+ }
+
+ if (!requiredMembers.TryGetValue(member.MemberSymbol.Name, out var requiredMember))
+ {
+ continue;
+ }
+
+ if (!member.MemberSymbol.Equals(requiredMember, TypeCompareKind.ConsiderEverything))
+ {
+ continue;
+ }
+
+ requiredMembers.Remove(member.MemberSymbol.Name);
+
+ if (initializerExpression is BoundObjectInitializerExpressionBase)
+ {
+ diagnostics.Add(ErrorCode.ERR_RequiredMembersMustBeAssignment, initializerExpression.Syntax.Location, requiredMember);
+ }
+ }
+
+ reportMembers();
+
+ void reportMembers()
+ {
+ Location location = creation.Syntax switch
+ {
+ ObjectCreationExpressionSyntax { Type: { } type } => type.Location,
+ BaseObjectCreationExpressionSyntax { NewKeyword: { } newKeyword } => newKeyword.GetLocation(),
+ _ => creation.Syntax.Location
+ };
+
+ foreach (var (_, member) in requiredMembers)
+ {
+ // Required member '{0}' must be set in the object initializer.
+ diagnostics.Add(ErrorCode.ERR_RequiredMemberMustBeSet, location, member);
+ }
+ }
+ }
+
private BoundCollectionInitializerExpression BindCollectionInitializerExpression(
InitializerExpressionSyntax initializerSyntax,
TypeSymbol initializerType,
@@ -5411,7 +5483,7 @@ protected BoundExpression BindClassCreationExpression(
}
boundInitializerOpt = makeBoundInitializerOpt();
- result = new BoundObjectCreationExpression(
+ var creation = new BoundObjectCreationExpression(
node,
method,
candidateConstructors,
@@ -5427,7 +5499,9 @@ protected BoundExpression BindClassCreationExpression(
type,
hasError);
- return result;
+ CheckRequiredMembersInObjectInitializer(creation, diagnostics);
+
+ return creation;
}
LookupResultKind resultKind;
diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx
index 338d3d7425350..5225f0c81ba09 100644
--- a/src/Compilers/CSharp/Portable/CSharpResources.resx
+++ b/src/Compilers/CSharp/Portable/CSharpResources.resx
@@ -7007,6 +7007,18 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
Required member '{0}' must be settable.
+
+ Required member '{0}' must be set in the object initializer.
+
+
+ Required member '{0}' must be assigned a value, it cannot use a nested member or collection initializer.
+
+
+ The required members list for '{0}' is malformed and cannot be interpreted.
+
+
+ The required members list for the base type '{0}' is malformed and cannot be interpreted. To use this constructor, apply the `SetsRequiredMembers` attribute.
+
Line contains different whitespace than the closing line of the raw string literal: '{0}' versus '{1}'
diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
index 947a0a7162abc..475e6d1e0eed2 100644
--- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
+++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
@@ -2058,5 +2058,9 @@ internal enum ErrorCode
ERR_RequiredMemberCannotBeLessVisibleThanContainingType = 9503,
ERR_ExplicitRequiredMember = 9504,
ERR_RequiredMemberMustBeSettable = 9505,
+ ERR_RequiredMemberMustBeSet = 9506,
+ ERR_RequiredMembersMustBeAssignment = 9507,
+ ERR_RequiredMembersInvalid = 9508,
+ ERR_RequiredMembersBaseTypeInvalid = 9509,
}
}
diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEMethodSymbol.cs
index c6d6e5ebc134f..13dffcbcdea8c 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEMethodSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEMethodSymbol.cs
@@ -1346,6 +1346,11 @@ internal override UseSiteInfo GetUseSiteInfo()
}
}
+ if (diagnosticInfo == null && this.ShouldCheckRequiredMembers() && ContainingType.HasRequiredMembersError)
+ {
+ diagnosticInfo = new CSDiagnosticInfo(ErrorCode.ERR_RequiredMembersInvalid, ContainingType);
+ }
+
return InitializeUseSiteDiagnostic(result.AdjustDiagnosticInfo(diagnosticInfo));
}
diff --git a/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs
index 7cb9d7c0f7086..5c525758f8be4 100644
--- a/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs
@@ -11,6 +11,8 @@
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using System.Threading;
+using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Symbols;
using Roslyn.Utilities;
@@ -24,6 +26,14 @@ internal abstract partial class NamedTypeSymbol : TypeSymbol, INamedTypeSymbolIn
{
private bool _hasNoBaseCycles;
+ ///
+ /// This field cannot be used to determine whether has been initialized.
+ /// Ensure that is checked for defaultness _before_ reading this field, which potentially
+ /// means using an `Interlocked.MemoryBarrier()` to ensure that reading this location is not reordered before the read to .
+ ///
+ private bool _lazyHasRequiredMembersErrorDoNotAccessDirectly = false;
+ private ImmutableSegmentedDictionary _lazyRequiredMembers;
+
// Only the compiler can create NamedTypeSymbols.
internal NamedTypeSymbol(TupleExtraData tupleData = null)
{
@@ -499,6 +509,118 @@ internal abstract bool MangleName
///
internal abstract bool HasDeclaredRequiredMembers { get; }
+#nullable enable
+ ///
+ /// Whether the type encountered an error while trying to build its complete list of required members.
+ ///
+ internal bool HasRequiredMembersError
+ {
+ get
+ {
+ CalculateRequiredMembersIfRequired();
+ Debug.Assert(!_lazyRequiredMembers.IsDefault);
+ return _lazyHasRequiredMembersErrorDoNotAccessDirectly;
+ }
+ }
+
+ ///
+ /// The full list of all required members for this type, including from base classes. If is true,
+ /// this returns empty.
+ ///
+ ///
+ /// Do not call this API if all you need are the required members declared on this type. Use instead, filtering for
+ /// required members, instead of calling this API.
+ ///
+ internal ImmutableSegmentedDictionary AllRequiredMembers
+ {
+ get
+ {
+ CalculateRequiredMembersIfRequired();
+ Debug.Assert(!_lazyRequiredMembers.IsDefault);
+ return _lazyRequiredMembers;
+ }
+ }
+
+ private void CalculateRequiredMembersIfRequired()
+ {
+ if (_lazyRequiredMembers.IsDefault)
+ {
+ ImmutableSegmentedDictionary.Builder? builder = null;
+ bool success = TryCalculateRequiredMembers(ref builder);
+ var requiredMembers = success
+ ? builder?.ToImmutable() ?? ImmutableSegmentedDictionary.Empty
+ : ImmutableSegmentedDictionary.Empty;
+
+ _lazyHasRequiredMembersErrorDoNotAccessDirectly = !success;
+ // InterlockedInitialize uses `Interlocked.CompareExchange` under the hood, which will introduce a full
+ // memory barrier, ensuring that the _lazyHasRequiredMembersErrorDoNotAccessDirectly write is not reordered
+ // after the initialization of _lazyRequiredMembers.
+ RoslynImmutableInterlocked.InterlockedInitialize(ref _lazyRequiredMembers, requiredMembers);
+ }
+ else
+ {
+ // Ensure that, after this method, _lazyRequiredMembersErrorDoNotAccessDirectly is able to be accessed safely.
+ Interlocked.MemoryBarrier();
+ }
+ }
+
+ ///
+ /// Attempts to calculate the required members for this type. Returns false if there were errors.
+ ///
+ private bool TryCalculateRequiredMembers(ref ImmutableSegmentedDictionary.Builder? requiredMembersBuilder)
+ {
+ var lazyRequiredMembers = _lazyRequiredMembers;
+ if (!lazyRequiredMembers.IsDefault)
+ {
+ requiredMembersBuilder = lazyRequiredMembers.ToBuilder();
+ // Ensure that this read is not reordered before the read to `IsDefault`.
+ Interlocked.MemoryBarrier();
+ return !_lazyHasRequiredMembersErrorDoNotAccessDirectly;
+ }
+
+ if (BaseTypeNoUseSiteDiagnostics?.TryCalculateRequiredMembers(ref requiredMembersBuilder) == false)
+ {
+ return false;
+ }
+
+ // We need to make sure that members from a base type weren't hidden by members from the current type.
+ if (!HasDeclaredRequiredMembers && requiredMembersBuilder == null)
+ {
+ return true;
+ }
+
+ return addCurrentTypeMembers(ref requiredMembersBuilder);
+
+ bool addCurrentTypeMembers(ref ImmutableSegmentedDictionary.Builder? requiredMembersBuilder)
+ {
+ requiredMembersBuilder ??= ImmutableSegmentedDictionary.CreateBuilder();
+
+ foreach (var member in GetMembersUnordered())
+ {
+ if (requiredMembersBuilder.ContainsKey(member.Name))
+ {
+ // This is only permitted if the member is an override of a required member from a base type, and is required itself.
+ if (!member.IsRequired()
+ || member.GetOverriddenMember() is not { } overriddenMember
+ || !overriddenMember.Equals(requiredMembersBuilder[member.Name], TypeCompareKind.ConsiderEverything))
+ {
+ return false;
+ }
+ }
+
+ if (!member.IsRequired())
+ {
+ continue;
+ }
+
+ requiredMembersBuilder[member.Name] = member;
+ }
+
+ return true;
+ }
+ }
+#nullable disable
+
///
/// Get all the members of this symbol.
///
diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs
index 94a6a797825de..c3c81cdbb0de2 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs
@@ -2400,6 +2400,20 @@ private void CheckForRequiredMemberAttribute(BindingDiagnosticBag diagnostics)
// Ensure that an error is reported if the required constructor isn't present.
_ = Binder.GetWellKnownTypeMember(DeclaringCompilation, WellKnownMember.System_Runtime_CompilerServices_RequiredMemberAttribute__ctor, diagnostics, Locations[0]);
}
+
+ if (BaseTypeNoUseSiteDiagnostics is (not SourceMemberContainerTypeSymbol) and { HasRequiredMembersError: true })
+ {
+ foreach (var member in GetMembersUnordered())
+ {
+ if (member is not MethodSymbol method || !method.ShouldCheckRequiredMembers())
+ {
+ continue;
+ }
+
+ // The required members list for the base type '{0}' is malformed and cannot be interpreted. To use this constructor, apply the `SetsRequiredMembers` attribute.
+ diagnostics.Add(ErrorCode.ERR_RequiredMembersBaseTypeInvalid, method.Locations[0], BaseTypeNoUseSiteDiagnostics);
+ }
+ }
}
private bool TypeOverridesObjectMethod(string name)
diff --git a/src/Compilers/CSharp/Portable/Symbols/SymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/SymbolExtensions.cs
index 2fa9cce9e30d4..b7a2bc2e045da 100644
--- a/src/Compilers/CSharp/Portable/Symbols/SymbolExtensions.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/SymbolExtensions.cs
@@ -841,5 +841,9 @@ 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 constructor)
+ // PROTOTYPE(req): Check for the SetsRequiredMembersAttribute and return false for that case
+ => constructor.MethodKind == MethodKind.Constructor;
}
}
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf
index eebde2822ddca..f876bc3fd56d3 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf
@@ -1162,11 +1162,31 @@
Required member '{0}' cannot be less visible or have a setter less visible than the containing type '{1}'.
+
+
+ Required member '{0}' must be set in the object initializer.
+
+ Required member '{0}' must be settable.
+
+
+ The required members list for the base type '{0}' is malformed and cannot be interpreted. To use this constructor, apply the `SetsRequiredMembers` attribute.
+
+
+
+
+ The required members list for '{0}' is malformed and cannot be interpreted.
+
+
+
+
+ Required member '{0}' must be assigned a value, it cannot use a nested member or collection initializer.
+
+ Types and aliases cannot be named 'required'.
@@ -11514,4 +11534,4 @@ Pokud chcete odstranit toto varování, můžete místo toho použít /reference