From dde1b78469784ece490a6551879565fc04142d5d Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Tue, 26 Aug 2025 23:53:04 -0700 Subject: [PATCH 1/4] Extensions: propagate return-targeted and local function parameter attributes --- .../Lowering/SynthesizedMethodBaseSymbol.cs | 4 +- .../Extensions/RewrittenMethodSymbol.cs | 6 +- .../FunctionPointerParameterSymbol.cs | 1 + .../Symbols/Metadata/PE/PEParameterSymbol.cs | 2 + .../Portable/Symbols/ParameterSymbol.cs | 7 + .../Symbols/SignatureOnlyParameterSymbol.cs | 2 + .../Source/SourceParameterSymbolBase.cs | 2 - .../Symbols/Source/ThisParameterSymbol.cs | 5 + .../SynthesizedIntrinsicOperatorSymbol.cs | 2 + .../Synthesized/SynthesizedParameterSymbol.cs | 19 +- .../Symbols/Wrapped/WrappedParameterSymbol.cs | 5 + .../Emit/CodeGen/CodeGenLocalFunctionTests.cs | 6 - .../Test/Emit3/Semantics/ExtensionTests.cs | 14 +- .../Test/Emit3/Semantics/ExtensionTests2.cs | 378 +++++++++++++++++- 14 files changed, 424 insertions(+), 29 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/SynthesizedMethodBaseSymbol.cs b/src/Compilers/CSharp/Portable/Lowering/SynthesizedMethodBaseSymbol.cs index 09f0b8d2868c5..9e74f7c63b03b 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SynthesizedMethodBaseSymbol.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SynthesizedMethodBaseSymbol.cs @@ -129,8 +129,9 @@ private ImmutableArray MakeParameters() p.ExplicitDefaultConstantValue, // the synthesized parameter doesn't need to have the same ref custom modifiers as the base refCustomModifiers: default, - inheritAttributes ? p as SourceComplexParameterSymbolBase : null)); + baseParameterForAttributes: inheritAttributes ? p : null)); } + var extraSynthed = ExtraSynthesizedRefParameters; if (!extraSynthed.IsDefaultOrEmpty) { @@ -139,6 +140,7 @@ private ImmutableArray MakeParameters() builder.Add(SynthesizedParameterSymbol.Create(this, this.TypeMap.SubstituteType(extra), ordinal++, RefKind.Ref)); } } + return builder.ToImmutableAndFree(); } diff --git a/src/Compilers/CSharp/Portable/Symbols/Extensions/RewrittenMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Extensions/RewrittenMethodSymbol.cs index ef8a2031a1318..3c823cacaf1c4 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Extensions/RewrittenMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Extensions/RewrittenMethodSymbol.cs @@ -4,7 +4,6 @@ using System.Collections.Immutable; using System.Diagnostics; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Symbols { @@ -87,6 +86,11 @@ public sealed override ImmutableArray GetAttributes() return _originalMethod.GetAttributes(); } + public override ImmutableArray GetReturnTypeAttributes() + { + return _originalMethod.GetReturnTypeAttributes(); + } + internal sealed override UseSiteInfo GetUseSiteInfo() { return _originalMethod.GetUseSiteInfo(); diff --git a/src/Compilers/CSharp/Portable/Symbols/FunctionPointers/FunctionPointerParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/FunctionPointers/FunctionPointerParameterSymbol.cs index 3b5a7349b2ae8..18596dafad5e2 100644 --- a/src/Compilers/CSharp/Portable/Symbols/FunctionPointers/FunctionPointerParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/FunctionPointers/FunctionPointerParameterSymbol.cs @@ -81,6 +81,7 @@ internal int MethodHashCode() internal override bool IsMetadataIn => RefKind is RefKind.In or RefKind.RefReadOnlyParameter; internal override bool IsMetadataOut => RefKind == RefKind.Out; internal override ConstantValue? ExplicitDefaultConstantValue => null; + internal override ConstantValue DefaultValueFromAttributes => ConstantValue.NotAvailable; internal override bool IsIDispatchConstant => false; internal override bool IsIUnknownConstant => false; internal override bool IsCallerFilePath => false; diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs index 94b5ccb89421e..dfb2db43ab42b 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs @@ -569,6 +569,8 @@ internal override ConstantValue? ExplicitDefaultConstantValue } } + internal override ConstantValue DefaultValueFromAttributes => ConstantValue.NotAvailable; + private ConstantValue? GetDefaultDecimalOrDateTimeValue() { Debug.Assert(!_handle.IsNil); diff --git a/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs index 59086f41e2b1c..5e487f27f54e6 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs @@ -254,6 +254,13 @@ public object? ExplicitDefaultValue /// internal abstract ConstantValue? ExplicitDefaultConstantValue { get; } + /// + /// Returns the default value from attributes on the parameter, + /// for symbols from source or derived from source symbols. + /// Returns when not applicable. + /// + internal abstract ConstantValue DefaultValueFromAttributes { get; } + /// /// Gets the kind of this symbol. /// diff --git a/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyParameterSymbol.cs index 333d87da9252f..ada118476704b 100644 --- a/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyParameterSymbol.cs @@ -78,6 +78,8 @@ internal override ScopedKind EffectiveScope internal override ConstantValue ExplicitDefaultConstantValue { get { throw ExceptionUtilities.Unreachable(); } } + internal override ConstantValue DefaultValueFromAttributes { get { throw ExceptionUtilities.Unreachable(); } } + internal override bool IsIDispatchConstant { get { throw ExceptionUtilities.Unreachable(); } } internal override bool IsIUnknownConstant { get { throw ExceptionUtilities.Unreachable(); } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceParameterSymbolBase.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceParameterSymbolBase.cs index c3bccd9523a34..a9962334e044e 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceParameterSymbolBase.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceParameterSymbolBase.cs @@ -64,8 +64,6 @@ public sealed override AssemblySymbol ContainingAssembly get { return _containingSymbol.ContainingAssembly; } } - internal abstract ConstantValue DefaultValueFromAttributes { get; } - internal override void AddSynthesizedAttributes(PEModuleBuilder moduleBuilder, ref ArrayBuilder attributes) { base.AddSynthesizedAttributes(moduleBuilder, ref attributes); diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/ThisParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/ThisParameterSymbol.cs index 90b181223ef08..c79718e0711b2 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/ThisParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/ThisParameterSymbol.cs @@ -25,6 +25,11 @@ internal sealed override ConstantValue? ExplicitDefaultConstantValue get { return null; } } + internal override ConstantValue DefaultValueFromAttributes + { + get { return ConstantValue.NotAvailable; } + } + internal sealed override bool IsMetadataOptional { get { return false; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedIntrinsicOperatorSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedIntrinsicOperatorSymbol.cs index 0e96ec4aba326..5540896c9b6bb 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedIntrinsicOperatorSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedIntrinsicOperatorSymbol.cs @@ -476,6 +476,8 @@ string name internal override bool IsMetadataOut => RefKind == RefKind.Out; + internal override ConstantValue DefaultValueFromAttributes => ConstantValue.NotAvailable; + public override bool Equals(Symbol obj, TypeCompareKind compareKind) { if (obj == (object)this) diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedParameterSymbol.cs index 8815e71f76476..2ba727f3141b1 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedParameterSymbol.cs @@ -85,8 +85,6 @@ internal override ConstantValue? ExplicitDefaultConstantValue get { return null; } } - internal virtual ConstantValue? DefaultValueFromAttributes => null; - internal override bool IsIDispatchConstant { get { throw ExceptionUtilities.Unreachable(); } @@ -254,6 +252,8 @@ private SynthesizedParameterSymbol( internal sealed override bool IsMetadataOut => RefKind == RefKind.Out; + internal override ConstantValue DefaultValueFromAttributes => ConstantValue.NotAvailable; + public static ParameterSymbol Create( Symbol? container, TypeWithAnnotations type, @@ -263,7 +263,7 @@ public static ParameterSymbol Create( ScopedKind scope = ScopedKind.None, ConstantValue? defaultValue = null, ImmutableArray refCustomModifiers = default, - SourceComplexParameterSymbolBase? baseParameterForAttributes = null, + ParameterSymbol? baseParameterForAttributes = null, bool isParams = false, bool hasUnscopedRefAttribute = false) { @@ -340,7 +340,7 @@ internal sealed class SynthesizedComplexParameterSymbol : SynthesizedParameterSy private readonly ImmutableArray _refCustomModifiers; // The parameter containing attributes to inherit into this synthesized parameter, if any. - private readonly SourceComplexParameterSymbolBase? _baseParameterForAttributes; + private readonly ParameterSymbol? _baseParameterForAttributes; private readonly ConstantValue? _defaultValue; private readonly bool _isParams; private readonly bool _hasUnscopedRefAttribute; @@ -354,7 +354,7 @@ public SynthesizedComplexParameterSymbol( ConstantValue? defaultValue, string name, ImmutableArray refCustomModifiers, - SourceComplexParameterSymbolBase? baseParameterForAttributes, + ParameterSymbol? baseParameterForAttributes, bool isParams, bool hasUnscopedRefAttribute) : base(container, type, ordinal, refKind, scope, name) @@ -362,6 +362,7 @@ public SynthesizedComplexParameterSymbol( Debug.Assert(!refCustomModifiers.IsDefault); Debug.Assert(isParams || !refCustomModifiers.IsEmpty || baseParameterForAttributes is object || defaultValue is not null || hasUnscopedRefAttribute); Debug.Assert(baseParameterForAttributes is null || baseParameterForAttributes.ExplicitDefaultConstantValue == defaultValue); + Debug.Assert(baseParameterForAttributes is null || baseParameterForAttributes.RefKind == refKind); _refCustomModifiers = refCustomModifiers; _baseParameterForAttributes = baseParameterForAttributes; @@ -407,13 +408,13 @@ internal override bool IsCallerMemberName get => _baseParameterForAttributes?.IsCallerMemberName ?? false; } - internal override bool IsMetadataIn => RefKind is RefKind.In or RefKind.RefReadOnlyParameter || _baseParameterForAttributes?.GetDecodedWellKnownAttributeData()?.HasInAttribute == true; + internal override bool IsMetadataIn => RefKind is RefKind.In or RefKind.RefReadOnlyParameter || _baseParameterForAttributes?.IsMetadataIn == true; - internal override bool IsMetadataOut => RefKind == RefKind.Out || _baseParameterForAttributes?.GetDecodedWellKnownAttributeData()?.HasOutAttribute == true; + internal override bool IsMetadataOut => RefKind == RefKind.Out || _baseParameterForAttributes?.IsMetadataOut == true; - internal override ConstantValue? ExplicitDefaultConstantValue => _baseParameterForAttributes?.ExplicitDefaultConstantValue ?? _defaultValue; + internal override ConstantValue? ExplicitDefaultConstantValue => _defaultValue; - internal override ConstantValue? DefaultValueFromAttributes => _baseParameterForAttributes?.DefaultValueFromAttributes; + internal override ConstantValue DefaultValueFromAttributes => _baseParameterForAttributes?.DefaultValueFromAttributes ?? ConstantValue.NotAvailable; internal override FlowAnalysisAnnotations FlowAnalysisAnnotations { diff --git a/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedParameterSymbol.cs index 2f0f1383bfc4b..ef180ffae2369 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedParameterSymbol.cs @@ -81,6 +81,11 @@ internal sealed override ConstantValue? ExplicitDefaultConstantValue get { return _underlyingParameter.ExplicitDefaultConstantValue; } } + internal override ConstantValue DefaultValueFromAttributes + { + get { return _underlyingParameter.DefaultValueFromAttributes; } + } + public override int Ordinal { get { return _underlyingParameter.Ordinal; } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenLocalFunctionTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenLocalFunctionTests.cs index a8dcc43ef9c49..38e847af922f1 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenLocalFunctionTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenLocalFunctionTests.cs @@ -6164,12 +6164,6 @@ static void local([Attr] in State state) var param = localFunction.Parameters[0]; Assert.True(param.IsMetadataIn); Assert.False(param.IsMetadataOut); - - // Test a scenario where the baseParameterAttributes has a different RefKind than the synthesized parameter. - // We expect the RefKind of the base parameter to be ignored here. - var synthesizedParam = SynthesizedParameterSymbol.Create(localFunction, param.TypeWithAnnotations, ordinal: 0, RefKind.Out, param.Name, baseParameterForAttributes: (SourceComplexParameterSymbolBase)param); - Assert.False(synthesizedParam.IsMetadataIn); - Assert.True(synthesizedParam.IsMetadataOut); } [Fact] diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs index 345129bcb32dc..bf759ae2a3583 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs @@ -33845,6 +33845,12 @@ public static class E1 var comp2 = CreateCompilation(source, references: [libComp.EmitToImageReference()]); CompileAndVerify(comp2, expectedOutput: "ran").VerifyDiagnostics(); + source = """ +E1.M(42, 43); +"""; + var comp3 = CreateCompilation([source, libSrc, OverloadResolutionPriorityAttributeDefinition]); + CompileAndVerify(comp3, expectedOutput: "ran", symbolValidator: verify).VerifyDiagnostics(); + static void verify(ModuleSymbol m) { var implementations = m.ContainingAssembly.GetTypeByMetadataName("E1").GetMembers().OfType().ToArray(); @@ -44226,6 +44232,7 @@ public void ConditionalAttribute_01() var src = """ 42.M(); 42.M2(); +E.M(42); static class E { @@ -44236,7 +44243,7 @@ static class E } [System.Diagnostics.Conditional("DEBUG")] - public static void M2(this int i) { System.Console.Write("ran"); } + public static void M2(this int i) { System.Console.Write("ran "); } } """; var comp = CreateCompilation(src); @@ -44248,6 +44255,7 @@ static class E 42.M(); 42.M2(); +E.M(42); static class E { @@ -44258,12 +44266,12 @@ static class E } [System.Diagnostics.Conditional("DEBUG")] - public static void M2(this int i) { System.Console.Write("ran"); } + public static void M2(this int i) { System.Console.Write("ran "); } } """; comp = CreateCompilation(src); comp.VerifyEmitDiagnostics(); - CompileAndVerify(comp, expectedOutput: "ran ran"); + CompileAndVerify(comp, expectedOutput: "ran ran ran"); } [Fact] diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs index 2cc376a270475..fcb5d07ae297a 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs @@ -30616,7 +30616,7 @@ static void validate(CSharpCompilation comp) public void PropagateAttributes_13(bool useCompilationReference) { // unmanaged constraint on extension type parameter - var libSrc = $$""" + var libSrc = """ public static class E { extension(int i) where T : unmanaged @@ -30659,10 +30659,10 @@ .param type T 01 00 00 00 ) // Methods - .method public hidebysig specialname static + .method public hidebysig specialname static void '$' ( int32 i - ) cil managed + ) cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 @@ -30674,8 +30674,8 @@ .maxstack 8 } // end of method '$A888E0AEEFB4AB1872CCB8E7D5472CC8'::'$' } // end of class $A888E0AEEFB4AB1872CCB8E7D5472CC8 // Methods - .method public hidebysig - instance void M () cil managed + .method public hidebysig + instance void M () cil managed { .custom instance void System.Runtime.CompilerServices.ExtensionMarkerAttribute::.ctor(string) = ( 01 00 24 3c 4d 3e 24 41 38 38 38 45 30 41 45 45 @@ -30690,10 +30690,10 @@ .maxstack 8 } // end of method '$8A1E908054B5C3DCE56554F1F294FA98'::M } // end of class $8A1E908054B5C3DCE56554F1F294FA98 // Methods - .method public hidebysig static + .method public hidebysig static void M ( int32 i - ) cil managed + ) cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 @@ -30872,5 +30872,369 @@ static void validate(CSharpCompilation comp) Assert.Equal("AAttribute", implementation.GetAttributes().Single().ToString()); } } + + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/80017")] + public void PropagateAttributes_18(bool useCompilationReference) + { + // return attribute on property + var libSrc = """ +public static class E +{ + extension(int i) + { + public int P + { + [return: A] + get => 0; + } + } +} + +public class AAttribute : System.Attribute { } +"""; + + var libComp = CreateCompilation(libSrc); + validate(libComp); + + var comp = CreateCompilation("", references: [AsReference(libComp, useCompilationReference)]); + validate(comp); + + CompileAndVerify(libComp).VerifyTypeIL("E", """ +.class public auto ansi abstract sealed beforefieldinit E + extends [mscorlib]System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( + 01 00 00 00 + ) + // Nested Types + .class nested public auto ansi sealed specialname '$BA41CFE2B5EDAEB8C1B9062F59ED4D69' + extends [mscorlib]System.Object + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( + 01 00 00 00 + ) + // Nested Types + .class nested public auto ansi abstract sealed specialname '$F4B4FFE41AB49E80A4ECF390CF6EB372' + extends [mscorlib]System.Object + { + // Methods + .method public hidebysig specialname static + void '$' ( + int32 i + ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( + 01 00 00 00 + ) + // Method begins at RVA 0x206d + // Code size 1 (0x1) + .maxstack 8 + IL_0000: ret + } // end of method '$F4B4FFE41AB49E80A4ECF390CF6EB372'::'$' + } // end of class $F4B4FFE41AB49E80A4ECF390CF6EB372 + // Methods + .method public hidebysig specialname + instance int32 get_P () cil managed + { + .custom instance void System.Runtime.CompilerServices.ExtensionMarkerAttribute::.ctor(string) = ( + 01 00 24 3c 4d 3e 24 46 34 42 34 46 46 45 34 31 + 41 42 34 39 45 38 30 41 34 45 43 46 33 39 30 43 + 46 36 45 42 33 37 32 00 00 + ) + .param [0] + .custom instance void AAttribute::.ctor() = ( + 01 00 00 00 + ) + // Method begins at RVA 0x206a + // Code size 2 (0x2) + .maxstack 8 + IL_0000: ldnull + IL_0001: throw + } // end of method '$BA41CFE2B5EDAEB8C1B9062F59ED4D69'::get_P + // Properties + .property instance int32 P() + { + .custom instance void System.Runtime.CompilerServices.ExtensionMarkerAttribute::.ctor(string) = ( + 01 00 24 3c 4d 3e 24 46 34 42 34 46 46 45 34 31 + 41 42 34 39 45 38 30 41 34 45 43 46 33 39 30 43 + 46 36 45 42 33 37 32 00 00 + ) + .get instance int32 E/'$BA41CFE2B5EDAEB8C1B9062F59ED4D69'::get_P() + } + } // end of class $BA41CFE2B5EDAEB8C1B9062F59ED4D69 + // Methods + .method public hidebysig static + int32 get_P ( + int32 i + ) cil managed + { + .param [0] + .custom instance void AAttribute::.ctor() = ( + 01 00 00 00 + ) + // Method begins at RVA 0x2067 + // Code size 2 (0x2) + .maxstack 8 + IL_0000: ldc.i4.0 + IL_0001: ret + } // end of method E::get_P +} // end of class E +""".Replace("[mscorlib]", ExecutionConditionUtil.IsMonoOrCoreClr ? "[netstandard]" : "[mscorlib]")); + + static void validate(CSharpCompilation comp) + { + var extension = comp.GlobalNamespace.GetTypeMember("E").GetTypeMembers().Single(); + Assert.True(extension.IsExtension); + var extensionProperty = extension.GetMember("P"); + Assert.Empty(extensionProperty.GetAttributes()); + var extensionGetter = extensionProperty.GetMethod; + Assert.Equal("AAttribute", extensionGetter.GetReturnTypeAttributes().Single().ToString()); + + var implementation = (MethodSymbol)comp.GlobalNamespace.GetTypeMember("E").GetMember("get_P"); + Assert.False(implementation.IsExtensionMethod); + Assert.Equal("AAttribute", implementation.GetReturnTypeAttributes().Single().ToString()); + } + } + + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/80017")] + public void PropagateAttributes_19(bool useCompilationReference) + { + // return attribute on method + var libSrc = """ +public static class E +{ + extension(int i) + { + [return: A] + public void M() { } + } +} + +public class AAttribute : System.Attribute { } +"""; + + var libComp = CreateCompilation(libSrc); + validate(libComp); + + var comp = CreateCompilation("", references: [AsReference(libComp, useCompilationReference)]); + validate(comp); + + static void validate(CSharpCompilation comp) + { + var extension = comp.GlobalNamespace.GetTypeMember("E").GetTypeMembers().Single(); + Assert.True(extension.IsExtension); + var extensionMethod = extension.GetMember("M"); + Assert.Equal("AAttribute", extensionMethod.GetReturnTypeAttributes().Single().ToString()); + + var implementation = comp.GlobalNamespace.GetTypeMember("E").GetMember("M"); + Assert.True(implementation.IsExtensionMethod); + Assert.Equal("AAttribute", implementation.GetReturnTypeAttributes().Single().ToString()); + } + } + + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/80017")] + public void PropagateAttributes_20(bool withPreserve) + { + // attributes on local function in extension + var src = $$""" +public static class E +{ + extension(int i1) + { + public void M() + { + local(0); + + [return: A] + [B] + void local([C] int i2) { } + } + } +} + +{{(withPreserve ? "[System.Runtime.CompilerServices.CompilerLoweringPreserve]" : "")}} +public class AAttribute : System.Attribute { } + +{{(withPreserve ? "[System.Runtime.CompilerServices.CompilerLoweringPreserve]" : "")}} +public class BAttribute : System.Attribute { } + +{{(withPreserve ? "[System.Runtime.CompilerServices.CompilerLoweringPreserve]" : "")}} +public class CAttribute : System.Attribute { } +"""; + + var verifier = CompileAndVerify([src, CompilerLoweringPreserveAttributeDefinition]).VerifyDiagnostics(); + verifier.VerifyTypeIL("E", """ +.class public auto ansi abstract sealed beforefieldinit E + extends [mscorlib]System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( + 01 00 00 00 + ) + // Nested Types + .class nested public auto ansi sealed specialname '$BA41CFE2B5EDAEB8C1B9062F59ED4D69' + extends [mscorlib]System.Object + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( + 01 00 00 00 + ) + // Nested Types + .class nested public auto ansi abstract sealed specialname '$531E7AC45D443AE2243E7FFAB9455D60' + extends [mscorlib]System.Object + { + // Methods + .method public hidebysig specialname static + void '$' ( + int32 i1 + ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( + 01 00 00 00 + ) + // Method begins at RVA 0x206f + // Code size 1 (0x1) + .maxstack 8 + IL_0000: ret + } // end of method '$531E7AC45D443AE2243E7FFAB9455D60'::'$' + } // end of class $531E7AC45D443AE2243E7FFAB9455D60 + // Methods + .method public hidebysig + instance void M () cil managed + { + .custom instance void System.Runtime.CompilerServices.ExtensionMarkerAttribute::.ctor(string) = ( + 01 00 24 3c 4d 3e 24 35 33 31 45 37 41 43 34 35 + 44 34 34 33 41 45 32 32 34 33 45 37 46 46 41 42 + 39 34 35 35 44 36 30 00 00 + ) + // Method begins at RVA 0x2071 + // Code size 2 (0x2) + .maxstack 8 + IL_0000: ldnull + IL_0001: throw + } // end of method '$BA41CFE2B5EDAEB8C1B9062F59ED4D69'::M + } // end of class $BA41CFE2B5EDAEB8C1B9062F59ED4D69 + // Methods + .method public hidebysig static + void M ( + int32 i1 + ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( + 01 00 00 00 + ) + // Method begins at RVA 0x2067 + // Code size 7 (0x7) + .maxstack 8 + IL_0000: ldc.i4.0 + IL_0001: call void E::'g__local|1_0'(int32) + IL_0006: ret + } // end of method E::M + .method assembly hidebysig static + void 'g__local|1_0' ( + int32 i2 + ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( + 01 00 00 00 + ) + .custom instance void BAttribute::.ctor() = ( + 01 00 00 00 + ) + .param [0] + .custom instance void AAttribute::.ctor() = ( + 01 00 00 00 + ) + .param [1] + .custom instance void CAttribute::.ctor() = ( + 01 00 00 00 + ) + // Method begins at RVA 0x206f + // Code size 1 (0x1) + .maxstack 8 + IL_0000: ret + } // end of method E::'g__local|1_0' +} // end of class E +""".Replace("[mscorlib]", ExecutionConditionUtil.IsMonoOrCoreClr ? "[netstandard]" : "[mscorlib]")); + } + + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/80017")] + public void PropagateAttributes_21(bool withPreserve) + { + // attributes on local function + var src = $$""" +public class C +{ + public void M() + { + local(0); + + [return: A] + [B] + void local([C] int i) { } + } +} + +{{(withPreserve ? "[System.Runtime.CompilerServices.CompilerLoweringPreserve]" : "")}} +public class AAttribute : System.Attribute { } + +{{(withPreserve ? "[System.Runtime.CompilerServices.CompilerLoweringPreserve]" : "")}} +public class BAttribute : System.Attribute { } + +{{(withPreserve ? "[System.Runtime.CompilerServices.CompilerLoweringPreserve]" : "")}} +public class CAttribute : System.Attribute { } +"""; + + var verifier = CompileAndVerify([src, CompilerLoweringPreserveAttributeDefinition]).VerifyDiagnostics(); + verifier.VerifyTypeIL("C", """ +.class public auto ansi beforefieldinit C + extends [mscorlib]System.Object +{ + // Methods + .method public hidebysig + instance void M () cil managed + { + // Method begins at RVA 0x2067 + // Code size 7 (0x7) + .maxstack 8 + IL_0000: ldc.i4.0 + IL_0001: call void C::'g__local|0_0'(int32) + IL_0006: ret + } // end of method C::M + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x206f + // Code size 7 (0x7) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: ret + } // end of method C::.ctor + .method assembly hidebysig static + void 'g__local|0_0' ( + int32 i + ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( + 01 00 00 00 + ) + .custom instance void BAttribute::.ctor() = ( + 01 00 00 00 + ) + .param [0] + .custom instance void AAttribute::.ctor() = ( + 01 00 00 00 + ) + .param [1] + .custom instance void CAttribute::.ctor() = ( + 01 00 00 00 + ) + // Method begins at RVA 0x2077 + // Code size 1 (0x1) + .maxstack 8 + IL_0000: ret + } // end of method C::'g__local|0_0' +} // end of class C +""".Replace("[mscorlib]", ExecutionConditionUtil.IsMonoOrCoreClr ? "[netstandard]" : "[mscorlib]")); + } } From e1c91215232c90137359ec3016ee3d39e71ceeca Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Fri, 29 Aug 2025 08:19:25 -0700 Subject: [PATCH 2/4] Address feedback --- .../FunctionPointerParameterSymbol.cs | 2 +- .../Symbols/Metadata/PE/PEParameterSymbol.cs | 4 +- .../Portable/Symbols/ParameterSymbol.cs | 4 +- .../Source/SourceClonedParameterSymbol.cs | 6 ++- .../Source/SourceComplexParameterSymbol.cs | 22 +++++++---- .../Source/SourceSimpleParameterSymbol.cs | 4 +- .../Symbols/Source/ThisParameterSymbol.cs | 4 +- .../SynthesizedIntrinsicOperatorSymbol.cs | 4 +- .../Synthesized/SynthesizedParameterSymbol.cs | 4 +- .../Symbols/Wrapped/WrappedParameterSymbol.cs | 2 +- .../Emit/CodeGen/CodeGenLocalFunctionTests.cs | 38 ------------------- src/Compilers/Core/Portable/ConstantValue.cs | 6 +-- 12 files changed, 36 insertions(+), 64 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/FunctionPointers/FunctionPointerParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/FunctionPointers/FunctionPointerParameterSymbol.cs index 18596dafad5e2..23faa55acf3af 100644 --- a/src/Compilers/CSharp/Portable/Symbols/FunctionPointers/FunctionPointerParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/FunctionPointers/FunctionPointerParameterSymbol.cs @@ -81,7 +81,7 @@ internal int MethodHashCode() internal override bool IsMetadataIn => RefKind is RefKind.In or RefKind.RefReadOnlyParameter; internal override bool IsMetadataOut => RefKind == RefKind.Out; internal override ConstantValue? ExplicitDefaultConstantValue => null; - internal override ConstantValue DefaultValueFromAttributes => ConstantValue.NotAvailable; + internal override ConstantValue? DefaultValueFromAttributes => null; internal override bool IsIDispatchConstant => false; internal override bool IsIUnknownConstant => false; internal override bool IsCallerFilePath => false; diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs index dfb2db43ab42b..630d6710d0e66 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEParameterSymbol.cs @@ -551,7 +551,7 @@ internal override bool HasMetadataConstantValue return value; } - internal override ConstantValue? ExplicitDefaultConstantValue + internal sealed override ConstantValue? ExplicitDefaultConstantValue { get { @@ -569,7 +569,7 @@ internal override ConstantValue? ExplicitDefaultConstantValue } } - internal override ConstantValue DefaultValueFromAttributes => ConstantValue.NotAvailable; + internal sealed override ConstantValue? DefaultValueFromAttributes => null; private ConstantValue? GetDefaultDecimalOrDateTimeValue() { diff --git a/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs index 5e487f27f54e6..76ab7cfb02e0f 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ParameterSymbol.cs @@ -257,9 +257,9 @@ public object? ExplicitDefaultValue /// /// Returns the default value from attributes on the parameter, /// for symbols from source or derived from source symbols. - /// Returns when not applicable. + /// Returns null when not applicable. /// - internal abstract ConstantValue DefaultValueFromAttributes { get; } + internal abstract ConstantValue? DefaultValueFromAttributes { get; } /// /// Gets the kind of this symbol. diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs index 5fd934d20f603..9a4f2931ad6c3 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs @@ -71,7 +71,8 @@ internal override bool IsMetadataOptional internal sealed override bool UseUpdatedEscapeRules => _originalParam.UseUpdatedEscapeRules; - internal override ConstantValue ExplicitDefaultConstantValue +#nullable enable + internal override ConstantValue? ExplicitDefaultConstantValue { get { @@ -80,10 +81,11 @@ internal override ConstantValue ExplicitDefaultConstantValue } } - internal override ConstantValue DefaultValueFromAttributes + internal override ConstantValue? DefaultValueFromAttributes { get { return _originalParam.DefaultValueFromAttributes; } } +#nullable disable #region Forwarded diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs index 689b923e26d3a..c8dc80ad4a636 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs @@ -36,7 +36,9 @@ private enum ParameterFlags : byte private ThreeState _lazyHasOptionalAttribute; private CustomAttributesBag _lazyCustomAttributesBag; - protected ConstantValue _lazyDefaultSyntaxValue; +#nullable enable + protected ConstantValue? _lazyDefaultSyntaxValue; +#nullable disable protected SourceComplexParameterSymbolBase( Symbol owner, @@ -88,7 +90,8 @@ protected SourceComplexParameterSymbolBase( public override bool IsDiscard => false; - internal sealed override ConstantValue ExplicitDefaultConstantValue +#nullable enable + internal sealed override ConstantValue? ExplicitDefaultConstantValue { get { @@ -106,14 +109,15 @@ internal sealed override ConstantValue ExplicitDefaultConstantValue } } - internal sealed override ConstantValue DefaultValueFromAttributes + internal sealed override ConstantValue? DefaultValueFromAttributes { get { ParameterEarlyWellKnownAttributeData data = GetEarlyDecodedWellKnownAttributeData(); - return (data != null && data.DefaultParameterValue != ConstantValue.Unset) ? data.DefaultParameterValue : ConstantValue.NotAvailable; + return (data != null && data.DefaultParameterValue != ConstantValue.Unset) ? data.DefaultParameterValue : null; } } +#nullable disable internal sealed override bool IsIDispatchConstant => GetDecodedWellKnownAttributeData()?.HasIDispatchConstantAttribute == true; @@ -240,7 +244,7 @@ internal sealed override ScopedKind EffectiveScope return parameterEqualsValue; } - private ConstantValue DefaultSyntaxValue + private ConstantValue? DefaultSyntaxValue { get { @@ -264,6 +268,8 @@ private ConstantValue DefaultSyntaxValue { NullableWalker.AnalyzeIfNeeded(binder, parameterEqualsValue, valueSyntax, diagnostics.DiagnosticBag); } + + Debug.Assert(_lazyDefaultSyntaxValue is not null); if (!_lazyDefaultSyntaxValue.IsBad) { VerifyParamDefaultValueMatchesAttributeIfAny(_lazyDefaultSyntaxValue, parameterEqualsValue.Value.Syntax, diagnostics); @@ -358,7 +364,7 @@ private void NullableAnalyzeParameterDefaultValueFromAttributes() // This method *must not* depend on attributes on the parameter symbol. // Otherwise we will have cycles when binding usage of attributes whose constructors have optional parameters - private ConstantValue MakeDefaultExpression(BindingDiagnosticBag diagnostics, out Binder? binder, out BoundParameterEqualsValue? parameterEqualsValue) + private ConstantValue? MakeDefaultExpression(BindingDiagnosticBag diagnostics, out Binder? binder, out BoundParameterEqualsValue? parameterEqualsValue) { binder = null; parameterEqualsValue = null; @@ -366,13 +372,13 @@ private ConstantValue MakeDefaultExpression(BindingDiagnosticBag diagnostics, ou var parameterSyntax = this.ParameterSyntax; if (parameterSyntax == null) { - return ConstantValue.NotAvailable; + return null; } var defaultSyntax = parameterSyntax.Default; if (defaultSyntax == null) { - return ConstantValue.NotAvailable; + return null; } MessageID.IDS_FeatureOptionalParameter.CheckFeatureAvailability(diagnostics, defaultSyntax.EqualsToken); diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceSimpleParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceSimpleParameterSymbol.cs index 828a1a1c5d58b..383c961e240dd 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceSimpleParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceSimpleParameterSymbol.cs @@ -157,9 +157,9 @@ internal override CustomAttributesBag GetAttributesBag() return CustomAttributesBag.Empty; } - internal override ConstantValue DefaultValueFromAttributes + internal override ConstantValue? DefaultValueFromAttributes { - get { return ConstantValue.NotAvailable; } + get { return null; } } internal override ScopedKind EffectiveScope => CalculateEffectiveScopeIgnoringAttributes(); diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/ThisParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/ThisParameterSymbol.cs index c79718e0711b2..fa6b7ac247735 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/ThisParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/ThisParameterSymbol.cs @@ -25,9 +25,9 @@ internal sealed override ConstantValue? ExplicitDefaultConstantValue get { return null; } } - internal override ConstantValue DefaultValueFromAttributes + internal sealed override ConstantValue? DefaultValueFromAttributes { - get { return ConstantValue.NotAvailable; } + get { return null; } } internal sealed override bool IsMetadataOptional diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedIntrinsicOperatorSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedIntrinsicOperatorSymbol.cs index 5540896c9b6bb..6864729af603c 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedIntrinsicOperatorSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedIntrinsicOperatorSymbol.cs @@ -476,7 +476,9 @@ string name internal override bool IsMetadataOut => RefKind == RefKind.Out; - internal override ConstantValue DefaultValueFromAttributes => ConstantValue.NotAvailable; +#nullable enable + internal override ConstantValue? DefaultValueFromAttributes => null; +#nullable disable public override bool Equals(Symbol obj, TypeCompareKind compareKind) { diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedParameterSymbol.cs index 2ba727f3141b1..171a739d176d3 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedParameterSymbol.cs @@ -252,7 +252,7 @@ private SynthesizedParameterSymbol( internal sealed override bool IsMetadataOut => RefKind == RefKind.Out; - internal override ConstantValue DefaultValueFromAttributes => ConstantValue.NotAvailable; + internal override ConstantValue? DefaultValueFromAttributes => null; public static ParameterSymbol Create( Symbol? container, @@ -414,7 +414,7 @@ internal override bool IsCallerMemberName internal override ConstantValue? ExplicitDefaultConstantValue => _defaultValue; - internal override ConstantValue DefaultValueFromAttributes => _baseParameterForAttributes?.DefaultValueFromAttributes ?? ConstantValue.NotAvailable; + internal override ConstantValue? DefaultValueFromAttributes => _baseParameterForAttributes?.DefaultValueFromAttributes ?? ConstantValue.NotAvailable; internal override FlowAnalysisAnnotations FlowAnalysisAnnotations { diff --git a/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedParameterSymbol.cs index ef180ffae2369..e61be17f3af42 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedParameterSymbol.cs @@ -81,7 +81,7 @@ internal sealed override ConstantValue? ExplicitDefaultConstantValue get { return _underlyingParameter.ExplicitDefaultConstantValue; } } - internal override ConstantValue DefaultValueFromAttributes + internal sealed override ConstantValue? DefaultValueFromAttributes { get { return _underlyingParameter.DefaultValueFromAttributes; } } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenLocalFunctionTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenLocalFunctionTests.cs index 38e847af922f1..01cb490c17c35 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenLocalFunctionTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenLocalFunctionTests.cs @@ -6128,44 +6128,6 @@ void validateMetadata(ModuleSymbol module) } } - [Fact] - [WorkItem(57325, "https://github.com/dotnet/roslyn/issues/57325")] - public void BaseParameterWithDifferentRefKind() - { - var source = $$""" -using System; - -class Attr : Attribute { } - -public class State -{ - public bool B; -} - -static class Program -{ - static void M() - { - local(new State()); - - static void local([Attr] in State state) - { - } - } -} -"""; - var comp = CreateCompilation(source); - comp.VerifyDiagnostics(); - - var tree = comp.SyntaxTrees[0]; - var model = comp.GetSemanticModel(tree); - var localFunctionSyntax = tree.GetRoot().DescendantNodes().OfType().Single(); - var localFunction = model.GetDeclaredSymbol(localFunctionSyntax).GetSymbol(); - var param = localFunction.Parameters[0]; - Assert.True(param.IsMetadataIn); - Assert.False(param.IsMetadataOut); - } - [Fact] [WorkItem(49599, "https://github.com/dotnet/roslyn/issues/49599")] public void MultipleLocalFunctionsUsingDynamic_01() diff --git a/src/Compilers/Core/Portable/ConstantValue.cs b/src/Compilers/Core/Portable/ConstantValue.cs index 502c08fececcb..314904dd14245 100644 --- a/src/Compilers/Core/Portable/ConstantValue.cs +++ b/src/Compilers/Core/Portable/ConstantValue.cs @@ -86,7 +86,7 @@ internal abstract partial class ConstantValue : IEquatable, IFor // It appears that in all cases so far we considered isDefaultValue, and not about value being // arithmetic zero (especially when definition is ambiguous). - public const ConstantValue NotAvailable = null; + public const ConstantValue? NotAvailable = null; public static ConstantValue Bad { get { return ConstantValueBad.Instance; } } public static ConstantValue Null { get { return ConstantValueNull.Instance; } } @@ -359,10 +359,10 @@ public static ConstantValue Create(object value, SpecialType st) return Create(value, discriminator); } - public static ConstantValue CreateSizeOf(SpecialType st) + public static ConstantValue? CreateSizeOf(SpecialType st) { int size = st.SizeInBytes(); - return (size == 0) ? ConstantValue.NotAvailable : ConstantValue.Create(size); + return (size == 0) ? null : ConstantValue.Create(size); } public static ConstantValue Create(object value, ConstantValueTypeDiscriminator discriminator) From a83515277aa7cb8cc4cc2f49c9417ca932218870 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Fri, 29 Aug 2025 08:45:18 -0700 Subject: [PATCH 3/4] sealed --- .../CSharp/Portable/Symbols/Extensions/RewrittenMethodSymbol.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/Extensions/RewrittenMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Extensions/RewrittenMethodSymbol.cs index 3c823cacaf1c4..e828f89622a9d 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Extensions/RewrittenMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Extensions/RewrittenMethodSymbol.cs @@ -86,7 +86,7 @@ public sealed override ImmutableArray GetAttributes() return _originalMethod.GetAttributes(); } - public override ImmutableArray GetReturnTypeAttributes() + public sealed override ImmutableArray GetReturnTypeAttributes() { return _originalMethod.GetReturnTypeAttributes(); } From 0643c497f8f81074258e182693baf8c82fbe4758 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Fri, 29 Aug 2025 11:45:09 -0700 Subject: [PATCH 4/4] Address feedback --- .../Emit/CodeGen/CodeGenLocalFunctionTests.cs | 81 +++++++++++++++++++ .../Test/Emit3/Semantics/ExtensionTests.cs | 33 ++------ .../Test/Emit3/Semantics/ExtensionTests2.cs | 81 ------------------- 3 files changed, 87 insertions(+), 108 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenLocalFunctionTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenLocalFunctionTests.cs index 01cb490c17c35..690b2abecb1bd 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenLocalFunctionTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenLocalFunctionTests.cs @@ -6254,6 +6254,87 @@ static void Local1() CompileAndVerify(source, targetFramework: TargetFramework.StandardAndCSharp, expectedOutput: "44"); } + [Theory, CombinatorialData] + public void PropagateAttributes_01(bool withPreserve) + { + // attributes on local function + var src = $$""" +public class C +{ + public void M() + { + local(0); + + [return: A] + [B] + void local([C] int i) { } + } +} + +{{(withPreserve ? "[System.Runtime.CompilerServices.CompilerLoweringPreserve]" : "")}} +public class AAttribute : System.Attribute { } + +{{(withPreserve ? "[System.Runtime.CompilerServices.CompilerLoweringPreserve]" : "")}} +public class BAttribute : System.Attribute { } + +{{(withPreserve ? "[System.Runtime.CompilerServices.CompilerLoweringPreserve]" : "")}} +public class CAttribute : System.Attribute { } +"""; + + var verifier = CompileAndVerify([src, CompilerLoweringPreserveAttributeDefinition]).VerifyDiagnostics(); + verifier.VerifyTypeIL("C", """ +.class public auto ansi beforefieldinit C + extends [mscorlib]System.Object +{ + // Methods + .method public hidebysig + instance void M () cil managed + { + // Method begins at RVA 0x2067 + // Code size 7 (0x7) + .maxstack 8 + IL_0000: ldc.i4.0 + IL_0001: call void C::'g__local|0_0'(int32) + IL_0006: ret + } // end of method C::M + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x206f + // Code size 7 (0x7) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: ret + } // end of method C::.ctor + .method assembly hidebysig static + void 'g__local|0_0' ( + int32 i + ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( + 01 00 00 00 + ) + .custom instance void BAttribute::.ctor() = ( + 01 00 00 00 + ) + .param [0] + .custom instance void AAttribute::.ctor() = ( + 01 00 00 00 + ) + .param [1] + .custom instance void CAttribute::.ctor() = ( + 01 00 00 00 + ) + // Method begins at RVA 0x2077 + // Code size 1 (0x1) + .maxstack 8 + IL_0000: ret + } // end of method C::'g__local|0_0' +} // end of class C +""".Replace("[mscorlib]", ExecutionConditionUtil.IsMonoOrCoreClr ? "[netstandard]" : "[mscorlib]")); + } + internal CompilationVerifier VerifyOutput(string source, string output, CSharpCompilationOptions options, Verification verify = default) { var comp = CreateCompilationWithMscorlib461AndCSharp(source, options: options); diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs index bf759ae2a3583..523e950d1ca9d 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs @@ -44226,32 +44226,11 @@ public void M() { } Diagnostic(ErrorCode.ERR_ExtensionParameterDisallowsDefaultValue, @"[System.Runtime.CompilerServices.CallerMemberName] string name = """"").WithLocation(5, 15)); } - [Fact] - public void ConditionalAttribute_01() - { - var src = """ -42.M(); -42.M2(); -E.M(42); - -static class E -{ - extension(int i) + [Theory, CombinatorialData] + public void ConditionalAttribute_01(bool withDebug) { - [System.Diagnostics.Conditional("DEBUG")] - public void M() { System.Console.Write("ran "); } - } - - [System.Diagnostics.Conditional("DEBUG")] - public static void M2(this int i) { System.Console.Write("ran "); } -} -"""; - var comp = CreateCompilation(src); - comp.VerifyEmitDiagnostics(); - CompileAndVerify(comp, expectedOutput: ""); - - src = """ -#define DEBUG + var src = $$""" +{{(withDebug ? "#define DEBUG" : "")}} 42.M(); 42.M2(); @@ -44269,9 +44248,9 @@ static class E public static void M2(this int i) { System.Console.Write("ran "); } } """; - comp = CreateCompilation(src); + var comp = CreateCompilation(src); comp.VerifyEmitDiagnostics(); - CompileAndVerify(comp, expectedOutput: "ran ran ran"); + CompileAndVerify(comp, expectedOutput: withDebug ? "ran ran ran" : ""); } [Fact] diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs index fcb5d07ae297a..1b8dd6f1176a6 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs @@ -31153,87 +31153,6 @@ .maxstack 8 IL_0000: ret } // end of method E::'g__local|1_0' } // end of class E -""".Replace("[mscorlib]", ExecutionConditionUtil.IsMonoOrCoreClr ? "[netstandard]" : "[mscorlib]")); - } - - [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/80017")] - public void PropagateAttributes_21(bool withPreserve) - { - // attributes on local function - var src = $$""" -public class C -{ - public void M() - { - local(0); - - [return: A] - [B] - void local([C] int i) { } - } -} - -{{(withPreserve ? "[System.Runtime.CompilerServices.CompilerLoweringPreserve]" : "")}} -public class AAttribute : System.Attribute { } - -{{(withPreserve ? "[System.Runtime.CompilerServices.CompilerLoweringPreserve]" : "")}} -public class BAttribute : System.Attribute { } - -{{(withPreserve ? "[System.Runtime.CompilerServices.CompilerLoweringPreserve]" : "")}} -public class CAttribute : System.Attribute { } -"""; - - var verifier = CompileAndVerify([src, CompilerLoweringPreserveAttributeDefinition]).VerifyDiagnostics(); - verifier.VerifyTypeIL("C", """ -.class public auto ansi beforefieldinit C - extends [mscorlib]System.Object -{ - // Methods - .method public hidebysig - instance void M () cil managed - { - // Method begins at RVA 0x2067 - // Code size 7 (0x7) - .maxstack 8 - IL_0000: ldc.i4.0 - IL_0001: call void C::'g__local|0_0'(int32) - IL_0006: ret - } // end of method C::M - .method public hidebysig specialname rtspecialname - instance void .ctor () cil managed - { - // Method begins at RVA 0x206f - // Code size 7 (0x7) - .maxstack 8 - IL_0000: ldarg.0 - IL_0001: call instance void [mscorlib]System.Object::.ctor() - IL_0006: ret - } // end of method C::.ctor - .method assembly hidebysig static - void 'g__local|0_0' ( - int32 i - ) cil managed - { - .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( - 01 00 00 00 - ) - .custom instance void BAttribute::.ctor() = ( - 01 00 00 00 - ) - .param [0] - .custom instance void AAttribute::.ctor() = ( - 01 00 00 00 - ) - .param [1] - .custom instance void CAttribute::.ctor() = ( - 01 00 00 00 - ) - // Method begins at RVA 0x2077 - // Code size 1 (0x1) - .maxstack 8 - IL_0000: ret - } // end of method C::'g__local|0_0' -} // end of class C """.Replace("[mscorlib]", ExecutionConditionUtil.IsMonoOrCoreClr ? "[netstandard]" : "[mscorlib]")); } }