Skip to content

Commit

Permalink
Support emitting CompilerFeatureRequiredAttribute for contructors of …
Browse files Browse the repository at this point in the history
…types with required members.
  • Loading branch information
333fred committed May 19, 2022
1 parent 475701e commit ab2c3a8
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -952,14 +952,26 @@ public override ImmutableArray<CSharpAttributeData> GetAttributes()
? _packedFlags.IsReadOnly
: IsValidReadOnlyTarget;

bool checkForRequiredMembers = this.ShouldCheckRequiredMembers() && this.ContainingType.HasAnyRequiredMembers;

bool isExtensionMethod = false;
bool isReadOnly = false;
if (checkForExtension || checkForIsReadOnly)
if (checkForExtension || checkForIsReadOnly || checkForRequiredMembers)
{
containingPEModuleSymbol.LoadCustomAttributesFilterCompilerAttributes(_handle,
ref attributeData,
out isExtensionMethod,
out isReadOnly);
attributeData = containingPEModuleSymbol.GetCustomAttributesForToken(_handle,
filteredOutAttribute1: out CustomAttributeHandle extensionAttribute,
filterOut1: AttributeDescription.CaseSensitiveExtensionAttribute,
filteredOutAttribute2: out CustomAttributeHandle isReadOnlyAttribute,
filterOut2: AttributeDescription.IsReadOnlyAttribute,
filteredOutAttribute3: out _,
filterOut3: (checkForRequiredMembers && DeriveCompilerFeatureRequiredDiagnostic() is null) ? AttributeDescription.CompilerFeatureRequiredAttribute : default,
filteredOutAttribute4: out _,
filterOut4: (checkForRequiredMembers && ObsoleteAttributeData is null) ? AttributeDescription.ObsoleteAttribute : default,
filteredOutAttribute5: out _,
filterOut5: default);

isExtensionMethod = !extensionAttribute.IsNil;
isReadOnly = !isReadOnlyAttribute.IsNil;
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,15 +283,6 @@ internal void LoadCustomAttributes(EntityHandle token, ref ImmutableArray<CSharp
ImmutableInterlocked.InterlockedInitialize(ref customAttributes, loaded);
}

internal void LoadCustomAttributesFilterCompilerAttributes(EntityHandle token,
ref ImmutableArray<CSharpAttributeData> customAttributes,
out bool foundExtension,
out bool foundReadOnly)
{
var loadedCustomAttributes = GetCustomAttributesFilterCompilerAttributes(token, out foundExtension, out foundReadOnly);
ImmutableInterlocked.InterlockedInitialize(ref customAttributes, loadedCustomAttributes);
}

internal void LoadCustomAttributesFilterExtensions(EntityHandle token,
ref ImmutableArray<CSharpAttributeData> customAttributes)
{
Expand Down
6 changes: 5 additions & 1 deletion src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1219,15 +1219,19 @@ protected static void AddRequiredMembersMarkerAttributes(ref ArrayBuilder<Synthe
var obsoleteData = methodToAttribute.ObsoleteAttributeData;
Debug.Assert(obsoleteData != ObsoleteAttributeData.Uninitialized, "getting synthesized attributes before attributes are decoded");

CSharpCompilation declaringCompilation = methodToAttribute.DeclaringCompilation;
if (obsoleteData == null)
{
CSharpCompilation declaringCompilation = methodToAttribute.DeclaringCompilation;
AddSynthesizedAttribute(ref attributes, declaringCompilation.TrySynthesizeAttribute(WellKnownMember.System_ObsoleteAttribute__ctor,
ImmutableArray.Create(
new TypedConstant(declaringCompilation.GetSpecialType(SpecialType.System_String), TypedConstantKind.Primitive, PEModule.RequiredMembersMarker), // message
new TypedConstant(declaringCompilation.GetSpecialType(SpecialType.System_Boolean), TypedConstantKind.Primitive, true)) // error
));
}

AddSynthesizedAttribute(ref attributes, declaringCompilation.TrySynthesizeAttribute(WellKnownMember.System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute__ctor,
ImmutableArray.Create(new TypedConstant(declaringCompilation.GetSpecialType(SpecialType.System_String), TypedConstantKind.Primitive, nameof(CompilerFeatureRequiredFeatures.RequiredMembers)))
));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@

namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
// PROTOTYPE(req): Add obsolete marker to constructors if required members and Obsolete hasn't already been emitted
// PROTOTYPE(req): Add poison type marker to constructors if required members and Obsolete hasn't already been emitted,
// pending framework design review
internal sealed class SourceConstructorSymbol : SourceConstructorSymbolBase
{
private readonly bool _isExpressionBodied;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2428,6 +2428,12 @@ 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 (HasAnyRequiredMembers)
{
_ = Binder.GetWellKnownTypeMember(DeclaringCompilation, WellKnownMember.System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute__ctor, diagnostics, Locations[0]);

if (this.IsRecord)
{
// Copy constructors need to emit SetsRequiredMembers on the ctor
Expand Down
92 changes: 71 additions & 21 deletions src/Compilers/CSharp/Test/Symbol/Symbols/RequiredMembersTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ End Class
End Namespace";

private static CSharpCompilation CreateCompilationWithRequiredMembers(CSharpTestSource source, IEnumerable<MetadataReference>? references = null, CSharpParseOptions? parseOptions = null, CSharpCompilationOptions? options = null, string? assemblyName = null, TargetFramework targetFramework = TargetFramework.Standard)
=> CreateCompilation(new[] { source, RequiredMemberAttribute, SetsRequiredMembersAttribute }, references, options: options, parseOptions: parseOptions, assemblyName: assemblyName, targetFramework: targetFramework);
=> CreateCompilation(new[] { source, RequiredMemberAttribute, SetsRequiredMembersAttribute, CompilerFeatureRequiredAttribute }, references, options: options, parseOptions: parseOptions, assemblyName: assemblyName, targetFramework: targetFramework);

private Compilation CreateVisualBasicCompilationWithRequiredMembers(string source)
=> CreateVisualBasicCompilation(new[] { source, RequiredMemberAttributeVB });
Expand Down Expand Up @@ -89,18 +89,35 @@ private static void AssertTypeRequiredMembersInvariants(ModuleSymbol module, Nam
{
Assert.True(type.HasAnyRequiredMembers);

var peModule = module as PEModuleSymbol;
foreach (var ctor in type.GetMembers().Where(m => m is MethodSymbol { MethodKind: MethodKind.Constructor }))
{
var ctorAttributes = ctor.GetAttributes();
if (ctorAttributes.Any(attr => attr.AttributeClass.ToTestDisplayString() == "System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute"))
{
Assert.DoesNotContain(ctorAttributes, attr => attr.AttributeClass.ToTestDisplayString() == "System.ObsoleteAttribute");
}
else if (module is not SourceModuleSymbol)

// Attributes should be filtered out when loaded from metadata, and are only added during emit in source
Assert.DoesNotContain(ctorAttributes, attr => attr.AttributeClass.ToTestDisplayString() is "System.ObsoleteAttribute" or "System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute");

if (peModule is not null)
{
Assert.Contains(ctorAttributes, attr =>
attr.AttributeConstructor.ToTestDisplayString() == "System.ObsoleteAttribute..ctor(System.String message, System.Boolean error)"
&& attr.ConstructorArguments.ToArray() is [{ ValueInternal: PEModule.RequiredMembersMarker }, { ValueInternal: true }]);
var peMethod = (PEMethodSymbol)ctor;
var decoder = new MetadataDecoder(peModule, peMethod);
var obsoleteAttribute = peModule.Module.TryGetDeprecatedOrExperimentalOrObsoleteAttribute(peMethod.Handle, decoder, ignoreByRefLikeMarker: false, ignoreRequiredMemberMarker: false);
string? unsupportedCompilerFeatureToken = peModule.Module.GetFirstUnsupportedCompilerFeatureFromToken(peMethod.Handle, decoder, CompilerFeatureRequiredFeatures.None);

if (ctorAttributes.Any(attr => attr.AttributeClass.ToTestDisplayString() == "System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute"))
{
Assert.Null(obsoleteAttribute);
Assert.Null(unsupportedCompilerFeatureToken);
}
else
{
Assert.NotNull(obsoleteAttribute);
Assert.Equal(PEModule.RequiredMembersMarker, obsoleteAttribute.Message);
Assert.True(obsoleteAttribute.IsError);

Assert.Equal(nameof(CompilerFeatureRequiredFeatures.RequiredMembers), unsupportedCompilerFeatureToken);
Assert.Null(peModule.Module.GetFirstUnsupportedCompilerFeatureFromToken(peMethod.Handle, decoder, CompilerFeatureRequiredFeatures.RequiredMembers));
}
}
}
}
Expand Down Expand Up @@ -386,12 +403,14 @@ class required<T> {}
[Fact]
public void MissingRequiredMemberAttribute()
{
var comp = CreateCompilation(@"
var comp = CreateCompilationWithRequiredMembers(@"
class C
{
public required int I { get; set; }
}");

comp.MakeTypeMissing(WellKnownType.System_Runtime_CompilerServices_RequiredMemberAttribute);

// (2,7): error CS0656: Missing compiler required member 'System.Runtime.CompilerServices.RequiredMemberAttribute..ctor'
// class C
var expected = Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "C").WithArguments("System.Runtime.CompilerServices.RequiredMemberAttribute", ".ctor").WithLocation(2, 7);
Expand All @@ -402,25 +421,56 @@ class C
[Fact]
public void MissingRequiredMemberAttributeCtor()
{
var comp = CreateCompilation(@"
var comp = CreateCompilationWithRequiredMembers(@"
class C
{
public required int I { get; set; }
}
");

namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class RequiredMemberAttribute : Attribute
comp.MakeMemberMissing(WellKnownMember.System_Runtime_CompilerServices_RequiredMemberAttribute__ctor);

// (2,7): error CS0656: Missing compiler required member 'System.Runtime.CompilerServices.RequiredMemberAttribute..ctor'
// class C
var expected = Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "C").WithArguments("System.Runtime.CompilerServices.RequiredMemberAttribute", ".ctor").WithLocation(2, 7);
comp.VerifyDiagnostics(expected);
comp.VerifyEmitDiagnostics(expected);
}

[Fact]
public void MissingCompilerFeatureRequiredAttribute()
{
public RequiredMemberAttribute(int i) {}
var comp = CreateCompilationWithRequiredMembers(@"
class C
{
public required int I { get; set; }
}");

comp.MakeTypeMissing(WellKnownType.System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute);

// (2,7): error CS0656: Missing compiler required member 'System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute..ctor'
// class C
var expected = Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "C").WithArguments("System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute", ".ctor").WithLocation(2, 7);

comp.VerifyDiagnostics(expected);
comp.VerifyEmitDiagnostics(expected);
}

[Fact]
public void MissingCompilerFeatureRequiredAttributeCtor()
{
var comp = CreateCompilationWithRequiredMembers(@"
class C
{
public required int I { get; set; }
}
");

// (2,7): error CS0656: Missing compiler required member 'System.Runtime.CompilerServices.RequiredMemberAttribute..ctor'
comp.MakeMemberMissing(WellKnownMember.System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute__ctor);

// (2,7): error CS0656: Missing compiler required member 'System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute..ctor'
// class C
var expected = Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "C").WithArguments("System.Runtime.CompilerServices.RequiredMemberAttribute", ".ctor").WithLocation(2, 7);
var expected = Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "C").WithArguments("System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute", ".ctor").WithLocation(2, 7);
comp.VerifyDiagnostics(expected);
comp.VerifyEmitDiagnostics(expected);
}
Expand Down Expand Up @@ -3116,7 +3166,7 @@ public class RequiredMemberAttribute : Attribute
}
";

var comp = CreateCompilation(code);
var comp = CreateCompilation(new[] { code, CompilerFeatureRequiredAttribute });
comp.VerifyDiagnostics(
// (4,2): error CS9506: Required member 'RequiredMemberAttribute.P' must be set in the object initializer or attribute constructor.
// [RequiredMember]
Expand All @@ -3140,7 +3190,7 @@ public class RequiredMemberAttribute : Attribute
}
";

var comp = CreateCompilation(code);
var comp = CreateCompilation(new[] { code, CompilerFeatureRequiredAttribute });
comp.VerifyDiagnostics(
// (6,6): error CS9506: Required member 'RequiredMemberAttribute.P' must be set in the object initializer or attribute constructor.
// [RequiredMember]
Expand Down Expand Up @@ -3183,7 +3233,7 @@ static void M()
}
";

var comp = CreateCompilation(new[] { code, RequiredMemberAttribute });
var comp = CreateCompilation(new[] { code, RequiredMemberAttribute, CompilerFeatureRequiredAttribute });
comp.VerifyDiagnostics();
}

Expand Down

0 comments on commit ab2c3a8

Please sign in to comment.