diff --git a/src/Compilers/CSharp/Portable/Symbols/OverriddenOrHiddenMembersHelpers.cs b/src/Compilers/CSharp/Portable/Symbols/OverriddenOrHiddenMembersHelpers.cs index b38e316698d8c..66ecb6e402895 100644 --- a/src/Compilers/CSharp/Portable/Symbols/OverriddenOrHiddenMembersHelpers.cs +++ b/src/Compilers/CSharp/Portable/Symbols/OverriddenOrHiddenMembersHelpers.cs @@ -129,9 +129,22 @@ private static OverriddenOrHiddenMembersResult MakeOverriddenOrHiddenMembersWork return MakeInterfaceOverriddenOrHiddenMembers(member, memberIsFromSomeCompilation); } - Symbol bestMatch = null; - ArrayBuilder hiddenBuilder = null; + ArrayBuilder hiddenBuilder; + ImmutableArray overriddenMembers; + ImmutableArray runtimeOverriddenMembers; + FindOverriddenOrHiddenMembers(member, containingType, memberIsFromSomeCompilation, out hiddenBuilder, out overriddenMembers, out runtimeOverriddenMembers); + + ImmutableArray hiddenMembers = hiddenBuilder == null ? ImmutableArray.Empty : hiddenBuilder.ToImmutableAndFree(); + return OverriddenOrHiddenMembersResult.Create(overriddenMembers, hiddenMembers, runtimeOverriddenMembers); + } + private static void FindOverriddenOrHiddenMembers(Symbol member, NamedTypeSymbol containingType, bool memberIsFromSomeCompilation, + out ArrayBuilder hiddenBuilder, + out ImmutableArray overriddenMembers, + out ImmutableArray runtimeOverriddenMembers) + { + Symbol bestMatch = null; + hiddenBuilder = null; for (NamedTypeSymbol currType = containingType.BaseTypeNoUseSiteDiagnostics; (object)currType != null && (object)bestMatch == null && hiddenBuilder == null; currType = currType.BaseTypeNoUseSiteDiagnostics) @@ -149,12 +162,19 @@ private static OverriddenOrHiddenMembersResult MakeOverriddenOrHiddenMembersWork // Based on bestMatch, find other methods that will be overridden, hidden, or runtime overridden // (in bestMatch.ContainingType). - ImmutableArray overriddenMembers; - ImmutableArray runtimeOverriddenMembers; FindRelatedMembers(member.IsOverride, memberIsFromSomeCompilation, member.Kind, bestMatch, out overriddenMembers, out runtimeOverriddenMembers, ref hiddenBuilder); + } - ImmutableArray hiddenMembers = hiddenBuilder == null ? ImmutableArray.Empty : hiddenBuilder.ToImmutableAndFree(); - return OverriddenOrHiddenMembersResult.Create(overriddenMembers, hiddenMembers, runtimeOverriddenMembers); + public static Symbol FindFirstHiddenMemberIfAny(Symbol member, bool memberIsFromSomeCompilation) + { + ArrayBuilder hiddenBuilder; + FindOverriddenOrHiddenMembers(member, member.ContainingType, memberIsFromSomeCompilation, out hiddenBuilder, + overriddenMembers: out _, runtimeOverriddenMembers: out _); + + Symbol result = hiddenBuilder?.FirstOrDefault(); + hiddenBuilder?.Free(); + + return result; } /// diff --git a/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyPropertySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyPropertySymbol.cs index 0f83ba1710640..237627a0ed41d 100644 --- a/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyPropertySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyPropertySymbol.cs @@ -75,7 +75,7 @@ public SignatureOnlyPropertySymbol( public override bool IsVirtual { get { throw ExceptionUtilities.Unreachable; } } - public override bool IsOverride { get { throw ExceptionUtilities.Unreachable; } } + public override bool IsOverride => false; public override bool IsAbstract { get { throw ExceptionUtilities.Unreachable; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs index 43a51ded1a056..abec828b22fb8 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs @@ -341,7 +341,18 @@ public override string MetadataName AttributeLocation IAttributeTargetSymbol.DefaultAttributeLocation => AttributeLocation.Parameter; - AttributeLocation IAttributeTargetSymbol.AllowedAttributeLocations => AttributeLocation.Parameter; + AttributeLocation IAttributeTargetSymbol.AllowedAttributeLocations + { + get + { + if (SynthesizedRecordPropertySymbol.HaveCorrespondingSynthesizedRecordPropertySymbol(this)) + { + return AttributeLocation.Parameter | AttributeLocation.Property | AttributeLocation.Field; + } + + return AttributeLocation.Parameter; + } + } /// /// Symbol to copy bound attributes from, or null if the attributes are not shared among multiple source parameter symbols. diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index 20091a51a3860..5e82a3b7a16b8 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -3079,16 +3079,24 @@ ImmutableArray addProperties(ImmutableArray rec { bool isInherited = false; var syntax = param.GetNonNullSyntaxNode(); - var property = new SynthesizedRecordPropertySymbol(this, syntax, param, isOverride: false, diagnostics); - if (!memberSignatures.TryGetValue(property, out var existingMember)) + + var targetProperty = new SignatureOnlyPropertySymbol(param.Name, + this, + ImmutableArray.Empty, + RefKind.None, + param.TypeWithAnnotations, + ImmutableArray.Empty, + isStatic: false, + ImmutableArray.Empty); + + if (!memberSignatures.TryGetValue(targetProperty, out var existingMember)) { - Debug.Assert(property.OverriddenOrHiddenMembers.OverriddenMembers.Length == 0); // property is not virtual and should not have overrides - existingMember = property.OverriddenOrHiddenMembers.HiddenMembers.FirstOrDefault(); + existingMember = OverriddenOrHiddenMembersHelpers.FindFirstHiddenMemberIfAny(targetProperty, memberIsFromSomeCompilation: true); isInherited = true; } if (existingMember is null) { - addProperty(property); + addProperty(new SynthesizedRecordPropertySymbol(this, syntax, param, isOverride: false, diagnostics)); } else if (existingMember is PropertySymbol { IsStatic: false, GetMethod: { } } prop && prop.TypeWithAnnotations.Equals(param.TypeWithAnnotations, TypeCompareKind.AllIgnoreOptions)) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs index 93c2d581445cb..f9c910966591c 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs @@ -139,6 +139,8 @@ private static bool HasInitializer(SyntaxNode syntax) public override SyntaxList AttributeDeclarationSyntaxList => ((BasePropertyDeclarationSyntax)CSharpSyntaxNode).AttributeLists; + public override IAttributeTargetSymbol AttributesOwner => this; + private static void GetAccessorDeclarations( CSharpSyntaxNode syntaxNode, DiagnosticBag diagnostics, diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs index e343cd4be2122..1ced902c94128 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs @@ -705,8 +705,6 @@ internal CSharpSyntaxNode CSharpSyntaxNode } } - public abstract SyntaxList AttributeDeclarationSyntaxList { get; } - internal SyntaxTree SyntaxTree { get @@ -980,7 +978,11 @@ private SynthesizedSealedPropertyAccessor MakeSynthesizedSealedAccessor() #region Attributes - IAttributeTargetSymbol IAttributeTargetSymbol.AttributesOwner => this; + public abstract SyntaxList AttributeDeclarationSyntaxList { get; } + + public abstract IAttributeTargetSymbol AttributesOwner { get; } + + IAttributeTargetSymbol IAttributeTargetSymbol.AttributesOwner => AttributesOwner; AttributeLocation IAttributeTargetSymbol.DefaultAttributeLocation => AttributeLocation.Property; diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordClone.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordClone.cs index 7230bc2ef7750..0c0c7e1ea09fd 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordClone.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordClone.cs @@ -119,12 +119,12 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, { var F = new SyntheticBoundNodeFactory(this, ContainingType.GetNonNullSyntaxNode(), compilationState, diagnostics); - var members = ContainingType.GetMembers(WellKnownMemberNames.InstanceConstructorName); + var members = ContainingType.InstanceConstructors; foreach (var member in members) { var ctor = (MethodSymbol)member; - if (ctor.ParameterCount == 1 && - ctor.Parameters[0].Type.Equals(ContainingType, TypeCompareKind.ConsiderEverything)) + if (ctor.ParameterCount == 1 && ctor.Parameters[0].RefKind == RefKind.None && + ctor.Parameters[0].Type.Equals(ContainingType, TypeCompareKind.AllIgnoreOptions)) { F.CloseMethod(F.Return(F.New(ctor, F.This()))); return; diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityContractProperty.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityContractProperty.cs index 48276309f3ab5..032b1b8df8063 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityContractProperty.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityContractProperty.cs @@ -15,7 +15,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols { - internal sealed class SynthesizedRecordEqualityContractProperty : SourcePropertySymbolBase, IAttributeTargetSymbol + internal sealed class SynthesizedRecordEqualityContractProperty : SourcePropertySymbolBase { internal const string PropertyName = "EqualityContract"; @@ -52,11 +52,10 @@ public SynthesizedRecordEqualityContractProperty( public override ImmutableArray DeclaringSyntaxReferences => ImmutableArray.Empty; - IAttributeTargetSymbol IAttributeTargetSymbol.AttributesOwner => this; - - AttributeLocation IAttributeTargetSymbol.AllowedAttributeLocations => AttributeLocation.None; + public override SyntaxList AttributeDeclarationSyntaxList + => new SyntaxList(); - AttributeLocation IAttributeTargetSymbol.DefaultAttributeLocation => AttributeLocation.None; + public override IAttributeTargetSymbol AttributesOwner => this; protected override Location TypeLocation => ContainingType.Locations[0]; @@ -64,9 +63,6 @@ protected override Location TypeLocation protected override SyntaxTokenList GetModifierTokens(SyntaxNode syntax) => new SyntaxTokenList(); - public override SyntaxList AttributeDeclarationSyntaxList - => new SyntaxList(); - protected override void CheckForBlockAndExpressionBody(CSharpSyntaxNode syntax, DiagnosticBag diagnostics) { // Nothing to do here diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPropertySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPropertySymbol.cs index d5341d6c46761..5ec6e9aa03908 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPropertySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPropertySymbol.cs @@ -11,9 +11,9 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols { - internal sealed class SynthesizedRecordPropertySymbol : SourcePropertySymbolBase, IAttributeTargetSymbol + internal sealed class SynthesizedRecordPropertySymbol : SourcePropertySymbolBase { - public ParameterSymbol BackingParameter { get; } + public SourceParameterSymbol BackingParameter { get; } public SynthesizedRecordPropertySymbol( SourceMemberContainerTypeSymbol containingType, @@ -42,14 +42,11 @@ public SynthesizedRecordPropertySymbol( hasParameters: false, diagnostics) { - BackingParameter = backingParameter; + BackingParameter = (SourceParameterSymbol)backingParameter; } - IAttributeTargetSymbol IAttributeTargetSymbol.AttributesOwner => this; - AttributeLocation IAttributeTargetSymbol.AllowedAttributeLocations => AttributeLocation.None; - - AttributeLocation IAttributeTargetSymbol.DefaultAttributeLocation => AttributeLocation.None; + public override IAttributeTargetSymbol AttributesOwner => BackingParameter as IAttributeTargetSymbol ?? this; protected override Location TypeLocation => ((ParameterSyntax)CSharpSyntaxNode).Type!.Location; @@ -58,7 +55,7 @@ protected override SyntaxTokenList GetModifierTokens(SyntaxNode syntax) => new SyntaxTokenList(); public override SyntaxList AttributeDeclarationSyntaxList - => new SyntaxList(); + => BackingParameter.AttributeDeclarationList; protected override void CheckForBlockAndExpressionBody(CSharpSyntaxNode syntax, DiagnosticBag diagnostics) { @@ -112,5 +109,10 @@ protected override TypeWithAnnotations ComputeType(Binder? binder, SyntaxNode sy protected override bool HasPointerTypeSyntactically // Since we already bound the type, don't bother looking at syntax => TypeWithAnnotations.DefaultType.IsPointerOrFunctionPointer(); + + public static bool HaveCorrespondingSynthesizedRecordPropertySymbol(SourceParameterSymbol parameter) + { + return parameter.ContainingType.GetMembersUnordered().Any((s, parameter) => (s as SynthesizedRecordPropertySymbol)?.BackingParameter == (object)parameter, parameter); + } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs index 40e379ec1dd2c..d957b72866c8a 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs @@ -41,7 +41,7 @@ public SynthesizedBackingFieldSymbol( } protected override IAttributeTargetSymbol AttributeOwner - => _property; + => _property.AttributesOwner; internal override Location ErrorLocation => _property.Location; diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs index 789a575bcacf7..98bb862cebc41 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis.Test.Extensions; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; +using Roslyn.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Semantics @@ -1442,6 +1443,227 @@ public static void Main() // Note: we do load the Clone method from metadata } + [Fact] + public void WithExpr24() + { + string source = @" +record C(int X) +{ + public static void Main() + { + var c1 = new C(1); + c1 = c1 with { }; + var c2 = c1 with { X = 11 }; + System.Console.WriteLine(c1.X); + System.Console.WriteLine(c2.X); + } + + protected C(ref C other) : this(-1) + { + } + + protected C(C other) + { + X = other.X; + } +} +"; + var verifier = CompileAndVerify(source, expectedOutput: @"1 +11"); + + verifier.VerifyIL("C.<>Clone", @" +{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: newobj ""C..ctor(C)"" + IL_0006: ret +} +"); + } + + [Fact] + public void WithExpr25() + { + string source = @" +record C(int X) +{ + public static void Main() + { + var c1 = new C(1); + c1 = c1 with { }; + var c2 = c1 with { X = 11 }; + System.Console.WriteLine(c1.X); + System.Console.WriteLine(c2.X); + } + + protected C(in C other) : this(-1) + { + } + + protected C(C other) + { + X = other.X; + } +} +"; + var verifier = CompileAndVerify(source, expectedOutput: @"1 +11"); + + verifier.VerifyIL("C.<>Clone", @" +{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: newobj ""C..ctor(C)"" + IL_0006: ret +} +"); + } + + [Fact] + public void WithExpr26() + { + string source = @" +record C(int X) +{ + public static void Main() + { + var c1 = new C(1); + c1 = c1 with { }; + var c2 = c1 with { X = 11 }; + System.Console.WriteLine(c1.X); + System.Console.WriteLine(c2.X); + } + + protected C(out C other) : this(-1) + { + other = null; + } + + protected C(C other) + { + X = other.X; + } +} +"; + var verifier = CompileAndVerify(source, expectedOutput: @"1 +11"); + + verifier.VerifyIL("C.<>Clone", @" +{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: newobj ""C..ctor(C)"" + IL_0006: ret +} +"); + } + + [Fact] + public void WithExpr27() + { + string source = @" +record C(int X) +{ + public static void Main() + { + var c1 = new C(1); + c1 = c1 with { }; + var c2 = c1 with { X = 11 }; + System.Console.WriteLine(c1.X); + System.Console.WriteLine(c2.X); + } + + protected C(ref C other) : this(-1) + { + } +} +"; + var verifier = CompileAndVerify(source, expectedOutput: @"1 +11"); + + verifier.VerifyIL("C.<>Clone", @" +{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: newobj ""C..ctor(C)"" + IL_0006: ret +} +"); + } + + [Fact] + public void WithExpr28() + { + string source = @" +record C(int X) +{ + public static void Main() + { + var c1 = new C(1); + c1 = c1 with { }; + var c2 = c1 with { X = 11 }; + System.Console.WriteLine(c1.X); + System.Console.WriteLine(c2.X); + } + + protected C(in C other) : this(-1) + { + } +} +"; + var verifier = CompileAndVerify(source, expectedOutput: @"1 +11"); + + verifier.VerifyIL("C.<>Clone", @" +{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: newobj ""C..ctor(C)"" + IL_0006: ret +} +"); + } + + [Fact] + public void WithExpr29() + { + string source = @" +record C(int X) +{ + public static void Main() + { + var c1 = new C(1); + c1 = c1 with { }; + var c2 = c1 with { X = 11 }; + System.Console.WriteLine(c1.X); + System.Console.WriteLine(c2.X); + } + + protected C(out C other) : this(-1) + { + other = null; + } +} +"; + var verifier = CompileAndVerify(source, expectedOutput: @"1 +11"); + + verifier.VerifyIL("C.<>Clone", @" +{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: newobj ""C..ctor(C)"" + IL_0006: ret +} +"); + } + [Fact] public void AccessibilityOfBaseCtor_01() { @@ -15638,5 +15860,528 @@ record R(int P1, int* P2, delegate* P3);"; p = comp.GlobalNamespace.GetTypeMember("R").GetMember("P3"); Assert.True(p.HasPointerType); } + + [Fact] + public void AttributesOnPrimaryConstructorParameters_01() + { + string source = @" +[System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = true) ] +public class A : System.Attribute +{ +} +[System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple = true) ] +public class B : System.Attribute +{ +} + +[System.AttributeUsage(System.AttributeTargets.Parameter, AllowMultiple = true) ] +public class C : System.Attribute +{ +} + +[System.AttributeUsage(System.AttributeTargets.Parameter, AllowMultiple = true) ] +public class D : System.Attribute +{ +} + +public record Test( + [field: A] + [property: B] + [param: C] + [D] + int P1) +{ +} +"; + Action symbolValidator = moduleSymbol => + { + var @class = moduleSymbol.GlobalNamespace.GetMember("Test"); + + var prop1 = @class.GetMember("P1"); + AssertEx.SetEqual(new[] { "B" }, getAttributeStrings(prop1)); + + var field1 = @class.GetMember("k__BackingField"); + AssertEx.SetEqual(new[] { "A" }, getAttributeStrings(field1)); + + var param1 = @class.GetMembers(".ctor").OfType().Where(m => m.Parameters.AsSingleton()?.Name == "P1").Single().Parameters[0]; + AssertEx.SetEqual(new[] { "C", "D" }, getAttributeStrings(param1)); + }; + + var comp = CompileAndVerify(new[] { source, IsExternalInitTypeDefinition }, sourceSymbolValidator: symbolValidator, symbolValidator: symbolValidator, + parseOptions: TestOptions.RegularPreview, + // init-only is unverifiable + verify: Verification.Skipped, + options: TestOptions.DebugDll.WithMetadataImportOptions(MetadataImportOptions.All)); + + comp.VerifyDiagnostics(); + + IEnumerable getAttributeStrings(Symbol symbol) + { + return GetAttributeStrings(symbol.GetAttributes().Where(a => + { + switch (a.AttributeClass!.Name) + { + case "A": + case "B": + case "C": + case "D": + return true; + } + + return false; + })); + } + } + + [Fact] + public void AttributesOnPrimaryConstructorParameters_02() + { + string source = @" +[System.AttributeUsage(System.AttributeTargets.All, AllowMultiple = true) ] +public class A : System.Attribute +{ +} +[System.AttributeUsage(System.AttributeTargets.All, AllowMultiple = true) ] +public class B : System.Attribute +{ +} + +[System.AttributeUsage(System.AttributeTargets.All, AllowMultiple = true) ] +public class C : System.Attribute +{ +} + +[System.AttributeUsage(System.AttributeTargets.All, AllowMultiple = true) ] +public class D : System.Attribute +{ +} + +public record Test( + [field: A] + [property: B] + [param: C] + [D] + int P1) +{ +} +"; + Action symbolValidator = moduleSymbol => + { + var @class = moduleSymbol.GlobalNamespace.GetMember("Test"); + + var prop1 = @class.GetMember("P1"); + AssertEx.SetEqual(new[] { "B" }, getAttributeStrings(prop1)); + + var field1 = @class.GetMember("k__BackingField"); + AssertEx.SetEqual(new[] { "A" }, getAttributeStrings(field1)); + + var param1 = @class.GetMembers(".ctor").OfType().Where(m => m.Parameters.AsSingleton()?.Name == "P1").Single().Parameters[0]; + AssertEx.SetEqual(new[] { "C", "D" }, getAttributeStrings(param1)); + }; + + var comp = CompileAndVerify(new[] { source, IsExternalInitTypeDefinition }, sourceSymbolValidator: symbolValidator, symbolValidator: symbolValidator, + parseOptions: TestOptions.RegularPreview, + // init-only is unverifiable + verify: Verification.Skipped, + options: TestOptions.DebugDll.WithMetadataImportOptions(MetadataImportOptions.All)); + + comp.VerifyDiagnostics(); + + IEnumerable getAttributeStrings(Symbol symbol) + { + return GetAttributeStrings(symbol.GetAttributes().Where(a => + { + switch (a.AttributeClass!.Name) + { + case "A": + case "B": + case "C": + case "D": + return true; + } + + return false; + })); + } + } + + [Fact] + public void AttributesOnPrimaryConstructorParameters_03() + { + string source = @" +[System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = true) ] +public class A : System.Attribute +{ +} +[System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple = true) ] +public class B : System.Attribute +{ +} + +[System.AttributeUsage(System.AttributeTargets.Parameter, AllowMultiple = true) ] +public class C : System.Attribute +{ +} + +[System.AttributeUsage(System.AttributeTargets.Parameter, AllowMultiple = true) ] +public class D : System.Attribute +{ +} + +public abstract record Base +{ + public abstract int P1 { get; init; } +} + +public record Test( + [field: A] + [property: B] + [param: C] + [D] + int P1) : Base +{ +} +"; + Action symbolValidator = moduleSymbol => + { + var @class = moduleSymbol.GlobalNamespace.GetMember("Test"); + + var prop1 = @class.GetMember("P1"); + AssertEx.SetEqual(new[] { "B" }, getAttributeStrings(prop1)); + + var field1 = @class.GetMember("k__BackingField"); + AssertEx.SetEqual(new[] { "A" }, getAttributeStrings(field1)); + + var param1 = @class.GetMembers(".ctor").OfType().Where(m => m.Parameters.AsSingleton()?.Name == "P1").Single().Parameters[0]; + AssertEx.SetEqual(new[] { "C", "D" }, getAttributeStrings(param1)); + }; + + var comp = CompileAndVerify(new[] { source, IsExternalInitTypeDefinition }, sourceSymbolValidator: symbolValidator, symbolValidator: symbolValidator, + parseOptions: TestOptions.RegularPreview, + // init-only is unverifiable + verify: Verification.Skipped, + options: TestOptions.DebugDll.WithMetadataImportOptions(MetadataImportOptions.All)); + + comp.VerifyDiagnostics(); + + IEnumerable getAttributeStrings(Symbol symbol) + { + return GetAttributeStrings(symbol.GetAttributes().Where(a => + { + switch (a.AttributeClass!.Name) + { + case "A": + case "B": + case "C": + case "D": + return true; + } + + return false; + })); + } + } + + [Fact] + public void AttributesOnPrimaryConstructorParameters_04() + { + string source = @" +[System.AttributeUsage(System.AttributeTargets.Method, AllowMultiple = true) ] +public class A : System.Attribute +{ +} + +public record Test( + [method: A] + int P1) +{ + [method: A] + void M1() {} +} +"; + Action symbolValidator = moduleSymbol => + { + var @class = moduleSymbol.GlobalNamespace.GetMember("Test"); + + var prop1 = @class.GetMember("P1"); + AssertEx.SetEqual(new string[] { }, getAttributeStrings(prop1)); + + var field1 = @class.GetMember("k__BackingField"); + AssertEx.SetEqual(new string[] { }, getAttributeStrings(field1)); + + var param1 = @class.GetMembers(".ctor").OfType().Where(m => m.Parameters.AsSingleton()?.Name == "P1").Single().Parameters[0]; + AssertEx.SetEqual(new string[] { }, getAttributeStrings(param1)); + }; + + var comp = CompileAndVerify(new[] { source, IsExternalInitTypeDefinition }, sourceSymbolValidator: symbolValidator, symbolValidator: symbolValidator, + parseOptions: TestOptions.RegularPreview, + // init-only is unverifiable + verify: Verification.Skipped, + options: TestOptions.DebugDll.WithMetadataImportOptions(MetadataImportOptions.All)); + + comp.VerifyDiagnostics( + // (8,6): warning CS0657: 'method' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'field, property, param'. All attributes in this block will be ignored. + // [method: A] + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "method").WithArguments("method", "field, property, param").WithLocation(8, 6) + ); + + IEnumerable getAttributeStrings(Symbol symbol) + { + return GetAttributeStrings(symbol.GetAttributes().Where(a => + { + switch (a.AttributeClass!.Name) + { + case "A": + return true; + } + + return false; + })); + } + } + + [Fact] + public void AttributesOnPrimaryConstructorParameters_05() + { + string source = @" +[System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = true) ] +public class A : System.Attribute +{ +} +[System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple = true) ] +public class B : System.Attribute +{ +} + +[System.AttributeUsage(System.AttributeTargets.Parameter, AllowMultiple = true) ] +public class C : System.Attribute +{ +} + +[System.AttributeUsage(System.AttributeTargets.Parameter, AllowMultiple = true) ] +public class D : System.Attribute +{ +} + +public abstract record Base +{ + public virtual int P1 { get; init; } +} + +public record Test( + [field: A] + [property: B] + [param: C] + [D] + int P1) : Base +{ +} +"; + Action symbolValidator = moduleSymbol => + { + var @class = moduleSymbol.GlobalNamespace.GetMember("Test"); + + Assert.Null(@class.GetMember("P1")); + Assert.Null(@class.GetMember("k__BackingField")); + + var param1 = @class.GetMembers(".ctor").OfType().Where(m => m.Parameters.AsSingleton()?.Name == "P1").Single().Parameters[0]; + AssertEx.SetEqual(new[] { "C", "D" }, getAttributeStrings(param1)); + }; + + var comp = CompileAndVerify(new[] { source, IsExternalInitTypeDefinition }, sourceSymbolValidator: symbolValidator, symbolValidator: symbolValidator, + parseOptions: TestOptions.RegularPreview, + // init-only is unverifiable + verify: Verification.Skipped, + options: TestOptions.DebugDll.WithMetadataImportOptions(MetadataImportOptions.All)); + + comp.VerifyDiagnostics( + // (27,6): warning CS0657: 'field' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'param'. All attributes in this block will be ignored. + // [field: A] + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "field").WithArguments("field", "param").WithLocation(27, 6), + // (28,6): warning CS0657: 'property' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'param'. All attributes in this block will be ignored. + // [property: B] + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "property").WithArguments("property", "param").WithLocation(28, 6) + ); + + IEnumerable getAttributeStrings(Symbol symbol) + { + return GetAttributeStrings(symbol.GetAttributes().Where(a => + { + switch (a.AttributeClass!.Name) + { + case "A": + case "B": + case "C": + case "D": + return true; + } + + return false; + })); + } + } + + [Fact] + public void AttributesOnPrimaryConstructorParameters_06() + { + string source = @" +[System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = true) ] +public class A : System.Attribute +{ +} +[System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple = true) ] +public class B : System.Attribute +{ +} + +[System.AttributeUsage(System.AttributeTargets.Parameter, AllowMultiple = true) ] +public class C : System.Attribute +{ +} + +[System.AttributeUsage(System.AttributeTargets.Parameter, AllowMultiple = true) ] +public class D : System.Attribute +{ +} + +public abstract record Base +{ + public int P1 { get; init; } +} + +public record Test( + [field: A] + [property: B] + [param: C] + [D] + int P1) : Base +{ +} +"; + Action symbolValidator = moduleSymbol => + { + var @class = moduleSymbol.GlobalNamespace.GetMember("Test"); + + Assert.Null(@class.GetMember("P1")); + Assert.Null(@class.GetMember("k__BackingField")); + + var param1 = @class.GetMembers(".ctor").OfType().Where(m => m.Parameters.AsSingleton()?.Name == "P1").Single().Parameters[0]; + AssertEx.SetEqual(new[] { "C", "D" }, getAttributeStrings(param1)); + }; + + var comp = CompileAndVerify(new[] { source, IsExternalInitTypeDefinition }, sourceSymbolValidator: symbolValidator, symbolValidator: symbolValidator, + parseOptions: TestOptions.RegularPreview, + // init-only is unverifiable + verify: Verification.Skipped, + options: TestOptions.DebugDll.WithMetadataImportOptions(MetadataImportOptions.All)); + + comp.VerifyDiagnostics( + // (27,6): warning CS0657: 'field' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'param'. All attributes in this block will be ignored. + // [field: A] + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "field").WithArguments("field", "param").WithLocation(27, 6), + // (28,6): warning CS0657: 'property' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'param'. All attributes in this block will be ignored. + // [property: B] + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "property").WithArguments("property", "param").WithLocation(28, 6) + ); + + IEnumerable getAttributeStrings(Symbol symbol) + { + return GetAttributeStrings(symbol.GetAttributes().Where(a => + { + switch (a.AttributeClass!.Name) + { + case "A": + case "B": + case "C": + case "D": + return true; + } + + return false; + })); + } + } + + [Fact] + public void AttributesOnPrimaryConstructorParameters_07() + { + string source = @" +[System.AttributeUsage(System.AttributeTargets.Parameter, AllowMultiple = true) ] +public class C : System.Attribute +{ +} + +[System.AttributeUsage(System.AttributeTargets.Parameter, AllowMultiple = true) ] +public class D : System.Attribute +{ +} + +public abstract record Base +{ + public int P1 { get; init; } +} + +public record Test( + [param: C] + [D] + int P1) : Base +{ +} +"; + Action symbolValidator = moduleSymbol => + { + var @class = moduleSymbol.GlobalNamespace.GetMember("Test"); + + var param1 = @class.GetMembers(".ctor").OfType().Where(m => m.Parameters.AsSingleton()?.Name == "P1").Single().Parameters[0]; + AssertEx.SetEqual(new[] { "C", "D" }, getAttributeStrings(param1)); + }; + + var comp = CompileAndVerify(new[] { source, IsExternalInitTypeDefinition }, sourceSymbolValidator: symbolValidator, symbolValidator: symbolValidator, + parseOptions: TestOptions.RegularPreview, + // init-only is unverifiable + verify: Verification.Skipped, + options: TestOptions.DebugDll.WithMetadataImportOptions(MetadataImportOptions.All)); + + comp.VerifyDiagnostics(); + + IEnumerable getAttributeStrings(Symbol symbol) + { + return GetAttributeStrings(symbol.GetAttributes().Where(a => + { + switch (a.AttributeClass!.Name) + { + case "C": + case "D": + return true; + } + + return false; + })); + } + } + + [Fact] + public void AttributesOnPrimaryConstructorParameters_08() + { + string source = @" +#nullable enable +using System.Diagnostics.CodeAnalysis; + +record C([property: NotNull] T? P1, T? P2) where T : class +{ + protected C(C other) + { + T x = P1; + T y = P2; + } +} +"; + var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition, NotNullAttributeDefinition }, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics( + // (10,15): warning CS8600: Converting null literal or possible null value to non-nullable type. + // T y = P2; + Diagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, "P2").WithLocation(10, 15) + ); + } } } diff --git a/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs b/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs index 0c67f218ee0c1..a7f21921ee42d 100644 --- a/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs +++ b/src/Compilers/Core/Portable/Collections/ImmutableArrayExtensions.cs @@ -359,6 +359,22 @@ private static ImmutableArray WhereAsArrayImpl(ImmutableArray arr } } + public static bool Any(this ImmutableArray array, Func predicate, TArg arg) + { + int n = array.Length; + for (int i = 0; i < n; i++) + { + var a = array[i]; + + if (predicate(a, arg)) + { + return true; + } + } + + return false; + } + /// /// Casts the immutable array of a Type to an immutable array of its base type. /// diff --git a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs index 9f947c7a95313..6765faee1172c 100644 --- a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs +++ b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs @@ -1506,6 +1506,11 @@ internal static IEnumerable GetAttributeStrings(ImmutableArray a.ToString()); } + internal static IEnumerable GetAttributeStrings(IEnumerable attributes) + { + return attributes.Select(a => a.ToString()); + } + #endregion #region Documentation Comments