From 55b6ebe89b9418b0a0a2e33b64f8e52da9195cc4 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Thu, 24 Jun 2021 06:39:17 +0200 Subject: [PATCH 01/12] Make SourceClonedParameterSymbol abstract --- .../Source/SourceClonedParameterSymbol.cs | 35 ++----------------- ...onedParameterSymbolForBeginAndEndInvoke.cs | 33 +++++++++++++++++ .../Source/SourceDelegateMethodSymbol.cs | 4 +-- .../Source/SourcePropertyAccessorSymbol.cs | 2 +- ...opertyClonedParameterSymbolForAccessors.cs | 32 +++++++++++++++++ 5 files changed, 70 insertions(+), 36 deletions(-) create mode 100644 src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateClonedParameterSymbolForBeginAndEndInvoke.cs create mode 100644 src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyClonedParameterSymbolForAccessors.cs diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs index 3071a34f5fee1..f0cdc5f200a3b 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs @@ -4,7 +4,6 @@ #nullable disable -using System; using System.Collections.Immutable; using System.Diagnostics; @@ -15,18 +14,17 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols /// For example, parameters on a property symbol are cloned to generate parameters on accessors. /// Similarly parameters on delegate invoke method are cloned to delegate begin/end invoke methods. /// - internal sealed class SourceClonedParameterSymbol : SourceParameterSymbolBase + internal abstract class SourceClonedParameterSymbol : SourceParameterSymbolBase { // if true suppresses params-array and default value: private readonly bool _suppressOptional; - private readonly SourceParameterSymbol _originalParam; + protected readonly SourceParameterSymbol _originalParam; internal SourceClonedParameterSymbol(SourceParameterSymbol originalParam, Symbol newOwner, int newOrdinal, bool suppressOptional) : base(newOwner, newOrdinal) { Debug.Assert((object)originalParam != null); - _suppressOptional = suppressOptional; _originalParam = originalParam; } @@ -73,15 +71,6 @@ internal override ConstantValue DefaultValueFromAttributes get { return _originalParam.DefaultValueFromAttributes; } } - internal override ParameterSymbol WithCustomModifiersAndParams(TypeSymbol newType, ImmutableArray newCustomModifiers, ImmutableArray newRefCustomModifiers, bool newIsParams) - { - return new SourceClonedParameterSymbol( - _originalParam.WithCustomModifiersAndParamsCore(newType, newCustomModifiers, newRefCustomModifiers, newIsParams), - this.ContainingSymbol, - this.Ordinal, - _suppressOptional); - } - #region Forwarded public override TypeWithAnnotations TypeWithAnnotations @@ -139,26 +128,6 @@ internal override bool IsIUnknownConstant get { return _originalParam.IsIUnknownConstant; } } - internal override bool IsCallerFilePath - { - get { return _originalParam.IsCallerFilePath; } - } - - internal override bool IsCallerLineNumber - { - get { return _originalParam.IsCallerLineNumber; } - } - - internal override bool IsCallerMemberName - { - get { return _originalParam.IsCallerMemberName; } - } - - internal override int CallerArgumentExpressionParameterIndex - { - get { return _originalParam.CallerArgumentExpressionParameterIndex; } - } - internal override FlowAnalysisAnnotations FlowAnalysisAnnotations { get { return FlowAnalysisAnnotations.None; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateClonedParameterSymbolForBeginAndEndInvoke.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateClonedParameterSymbolForBeginAndEndInvoke.cs new file mode 100644 index 0000000000000..c0041098c96ab --- /dev/null +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateClonedParameterSymbolForBeginAndEndInvoke.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.Symbols +{ + internal sealed class SourceDelegateClonedParameterSymbolForBeginAndEndInvoke : SourceClonedParameterSymbol + { + internal SourceDelegateClonedParameterSymbolForBeginAndEndInvoke(SourceParameterSymbol originalParam, Symbol newOwner, int newOrdinal) + : base(originalParam, newOwner, newOrdinal, suppressOptional: true) + { + } + + internal override bool IsCallerFilePath => _originalParam.IsCallerFilePath; + + internal override bool IsCallerLineNumber => _originalParam.IsCallerLineNumber; + + internal override bool IsCallerMemberName => _originalParam.IsCallerMemberName; + + internal override int CallerArgumentExpressionParameterIndex => throw ExceptionUtilities.Unreachable; + + internal override ParameterSymbol WithCustomModifiersAndParams(TypeSymbol newType, ImmutableArray newCustomModifiers, ImmutableArray newRefCustomModifiers, bool newIsParams) + { + return new SourceDelegateClonedParameterSymbolForBeginAndEndInvoke( + _originalParam.WithCustomModifiersAndParamsCore(newType, newCustomModifiers, newRefCustomModifiers, newIsParams), + ContainingSymbol, + Ordinal); + } + } +} diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateMethodSymbol.cs index b4eab31db8f74..5eeee2e29a83e 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateMethodSymbol.cs @@ -353,7 +353,7 @@ internal BeginInvokeMethod( var parameters = ArrayBuilder.GetInstance(); foreach (SourceParameterSymbol p in invoke.Parameters) { - var synthesizedParam = new SourceClonedParameterSymbol(originalParam: p, newOwner: this, newOrdinal: p.Ordinal, suppressOptional: true); + var synthesizedParam = new SourceDelegateClonedParameterSymbolForBeginAndEndInvoke(originalParam: p, newOwner: this, newOrdinal: p.Ordinal); parameters.Add(synthesizedParam); } @@ -402,7 +402,7 @@ internal EndInvokeMethod( { if (p.RefKind != RefKind.None) { - var synthesizedParam = new SourceClonedParameterSymbol(originalParam: p, newOwner: this, newOrdinal: ordinal++, suppressOptional: true); + var synthesizedParam = new SourceDelegateClonedParameterSymbolForBeginAndEndInvoke(originalParam: p, newOwner: this, newOrdinal: ordinal++); parameters.Add(synthesizedParam); } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyAccessorSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyAccessorSymbol.cs index 66e950cdf97fb..fb359a7e40560 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyAccessorSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyAccessorSymbol.cs @@ -754,7 +754,7 @@ private ImmutableArray ComputeParameters(BindingDiagnosticBag d // since the ContainingSymbol needs to be set to the accessor. foreach (SourceParameterSymbol propertyParam in propertyParameters) { - parameters.Add(new SourceClonedParameterSymbol(propertyParam, this, propertyParam.Ordinal, suppressOptional: false)); + parameters.Add(new SourcePropertyClonedParameterSymbolForAccessors(propertyParam, this)); } if (!isGetMethod) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyClonedParameterSymbolForAccessors.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyClonedParameterSymbolForAccessors.cs new file mode 100644 index 0000000000000..37e7d4c44b0c3 --- /dev/null +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyClonedParameterSymbolForAccessors.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.Symbols +{ + internal sealed class SourcePropertyClonedParameterSymbolForAccessors : SourceClonedParameterSymbol + { + internal SourcePropertyClonedParameterSymbolForAccessors(SourceParameterSymbol originalParam, Symbol newOwner) + : base(originalParam, newOwner, originalParam.Ordinal, suppressOptional: false) + { + } + + internal override bool IsCallerFilePath => _originalParam.IsCallerFilePath; + + internal override bool IsCallerLineNumber => _originalParam.IsCallerLineNumber; + + internal override bool IsCallerMemberName => _originalParam.IsCallerMemberName; + + internal override int CallerArgumentExpressionParameterIndex => _originalParam.CallerArgumentExpressionParameterIndex; + + internal override ParameterSymbol WithCustomModifiersAndParams(TypeSymbol newType, ImmutableArray newCustomModifiers, ImmutableArray newRefCustomModifiers, bool newIsParams) + { + return new SourcePropertyClonedParameterSymbolForAccessors( + _originalParam.WithCustomModifiersAndParamsCore(newType, newCustomModifiers, newRefCustomModifiers, newIsParams), + this.ContainingSymbol); + } + } +} From 034ef63edf120e5670c5b2bd64d01deb5eab7d4b Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Mon, 28 Jun 2021 07:08:59 +0200 Subject: [PATCH 02/12] Fix implementation and add tests --- ...onedParameterSymbolForBeginAndEndInvoke.cs | 42 +++++- .../AttributeTests_CallerInfoAttributes.cs | 136 ++++++++++++++++++ 2 files changed, 175 insertions(+), 3 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateClonedParameterSymbolForBeginAndEndInvoke.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateClonedParameterSymbolForBeginAndEndInvoke.cs index c0041098c96ab..e216e848d4ae5 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateClonedParameterSymbolForBeginAndEndInvoke.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateClonedParameterSymbolForBeginAndEndInvoke.cs @@ -2,14 +2,20 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Immutable; +using System.Diagnostics; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Symbols { internal sealed class SourceDelegateClonedParameterSymbolForBeginAndEndInvoke : SourceClonedParameterSymbol { - internal SourceDelegateClonedParameterSymbolForBeginAndEndInvoke(SourceParameterSymbol originalParam, Symbol newOwner, int newOrdinal) + private const int UninitializedArgumentExpressionParameterIndex = int.MinValue; + private int _lazyCallerArgumentExpressionParameterIndex = UninitializedArgumentExpressionParameterIndex; + + + internal SourceDelegateClonedParameterSymbolForBeginAndEndInvoke(SourceParameterSymbol originalParam, SourceDelegateMethodSymbol newOwner, int newOrdinal) : base(originalParam, newOwner, newOrdinal, suppressOptional: true) { } @@ -20,13 +26,43 @@ internal SourceDelegateClonedParameterSymbolForBeginAndEndInvoke(SourceParameter internal override bool IsCallerMemberName => _originalParam.IsCallerMemberName; - internal override int CallerArgumentExpressionParameterIndex => throw ExceptionUtilities.Unreachable; + internal override int CallerArgumentExpressionParameterIndex + { + get + { + if (_originalParam.CallerArgumentExpressionParameterIndex == -1) + { + // If original param doesn't have a valid caller argument expression, don't try to recalculate. + // NOTE: Recalculation may result in different behavior if the delegate is declared like: + // delegate void D(string s1, [CallerArgumentExpression("callback")] [Optional] string s2); + // There is no parameter named "callback", however, the BeginInvoke has one. + return _lazyCallerArgumentExpressionParameterIndex = -1; + } + + if (_lazyCallerArgumentExpressionParameterIndex != UninitializedArgumentExpressionParameterIndex) + { + return _lazyCallerArgumentExpressionParameterIndex; + } + + var parameterName = _originalParam.ContainingSymbol.GetParameters()[_originalParam.CallerArgumentExpressionParameterIndex].Name; + var parameters = ((SourceDelegateMethodSymbol)ContainingSymbol).Parameters; + for (int i = 0; i < parameters.Length; i++) + { + if (parameters[i].Name.Equals(parameterName, StringComparison.Ordinal)) + { + return _lazyCallerArgumentExpressionParameterIndex = i; + } + } + + return _lazyCallerArgumentExpressionParameterIndex = -1; + } + } internal override ParameterSymbol WithCustomModifiersAndParams(TypeSymbol newType, ImmutableArray newCustomModifiers, ImmutableArray newRefCustomModifiers, bool newIsParams) { return new SourceDelegateClonedParameterSymbolForBeginAndEndInvoke( _originalParam.WithCustomModifiersAndParamsCore(newType, newCustomModifiers, newRefCustomModifiers, newIsParams), - ContainingSymbol, + (SourceDelegateMethodSymbol)ContainingSymbol, Ordinal); } } diff --git a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_CallerInfoAttributes.cs b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_CallerInfoAttributes.cs index 0b5b2ffcd7dde..1a5bfe590e32d 100644 --- a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_CallerInfoAttributes.cs +++ b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_CallerInfoAttributes.cs @@ -17,6 +17,142 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests { public class AttributeTests_CallerInfoAttributes : WellKnownAttributesTestBase { + [ConditionalFact(typeof(CoreClrOnly))] + public void TestBeginInvoke() + { + string source = @" +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace System.Runtime.InteropServices +{ + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + public sealed class OptionalAttribute : Attribute + { + public OptionalAttribute() + { + } + } + + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class DefaultParameterValueAttribute : Attribute + { + public DefaultParameterValueAttribute(object value) + { + Value = value; + } + public object Value { get; } + } +} + +class Program +{ + const string s1 = nameof(s1); + delegate void D(string s1, [CallerArgumentExpression(s1)] [Optional] [DefaultParameterValue(""default"")] string s2); + + static void M(string s1, string s2) + { + } + + static string GetString() => null; + + public static void Main() + { + D d = M; + d.BeginInvoke(GetString(), callback: null, @object: null); + } +} +"; + + var compilation = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularPreview); + CompileAndVerify(compilation).VerifyDiagnostics().VerifyIL("Program.Main", @" +{ + // Code size 31 (0x1f) + .maxstack 5 + IL_0000: ldnull + IL_0001: ldftn ""void Program.M(string, string)"" + IL_0007: newobj ""Program.D..ctor(object, System.IntPtr)"" + IL_000c: call ""string Program.GetString()"" + IL_0011: ldstr ""GetString()"" + IL_0016: ldnull + IL_0017: ldnull + IL_0018: callvirt ""System.IAsyncResult Program.D.BeginInvoke(string, string, System.AsyncCallback, object)"" + IL_001d: pop + IL_001e: ret +} +"); + } + + [ConditionalFact(typeof(CoreClrOnly))] + public void TestBeginInvoke_ReferringToCallbackParameter() + { + string source = @" +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace System.Runtime.InteropServices +{ + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + public sealed class OptionalAttribute : Attribute + { + public OptionalAttribute() + { + } + } + + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class DefaultParameterValueAttribute : Attribute + { + public DefaultParameterValueAttribute(object value) + { + Value = value; + } + public object Value { get; } + } +} + +class Program +{ + const string callback = nameof(callback); + delegate void D(string s1, [CallerArgumentExpression(callback)] [Optional] [DefaultParameterValue(""default"")] string s2); + + static void M(string s1, string s2) + { + } + + static string GetString() => null; + + public static void Main() + { + D d = M; + d.BeginInvoke(GetString(), callback: null, @object: null); + } +} +"; + + var compilation = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularPreview); + CompileAndVerify(compilation).VerifyDiagnostics( + // (29,33): warning CS9005: The CallerArgumentExpressionAttribute applied to parameter 's2' will have no effect. It is applied with an invalid parameter name. + // delegate void D(string s1, [CallerArgumentExpression(callback)] [Optional] [DefaultParameterValue("default")] string s2); + Diagnostic(ErrorCode.WRN_CallerArgumentExpressionAttributeHasInvalidParameterName, "CallerArgumentExpression").WithArguments("s2").WithLocation(29, 33) + ).VerifyIL("Program.Main", @" +{ + // Code size 31 (0x1f) + .maxstack 5 + IL_0000: ldnull + IL_0001: ldftn ""void Program.M(string, string)"" + IL_0007: newobj ""Program.D..ctor(object, System.IntPtr)"" + IL_000c: call ""string Program.GetString()"" + IL_0011: ldstr ""default"" + IL_0016: ldnull + IL_0017: ldnull + IL_0018: callvirt ""System.IAsyncResult Program.D.BeginInvoke(string, string, System.AsyncCallback, object)"" + IL_001d: pop + IL_001e: ret +} +"); + } + #region CallerArgumentExpression - Invocations [ConditionalFact(typeof(CoreClrOnly))] public void TestGoodCallerArgumentExpressionAttribute() From 5bc7e09e6a99225f268aa8f0edbb83355c3cb89f Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Mon, 28 Jun 2021 17:39:17 +0200 Subject: [PATCH 03/12] Return -1 for now --- ...onedParameterSymbolForBeginAndEndInvoke.cs | 37 +------------------ 1 file changed, 2 insertions(+), 35 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateClonedParameterSymbolForBeginAndEndInvoke.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateClonedParameterSymbolForBeginAndEndInvoke.cs index e216e848d4ae5..f063eb207e51b 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateClonedParameterSymbolForBeginAndEndInvoke.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateClonedParameterSymbolForBeginAndEndInvoke.cs @@ -11,10 +11,6 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols { internal sealed class SourceDelegateClonedParameterSymbolForBeginAndEndInvoke : SourceClonedParameterSymbol { - private const int UninitializedArgumentExpressionParameterIndex = int.MinValue; - private int _lazyCallerArgumentExpressionParameterIndex = UninitializedArgumentExpressionParameterIndex; - - internal SourceDelegateClonedParameterSymbolForBeginAndEndInvoke(SourceParameterSymbol originalParam, SourceDelegateMethodSymbol newOwner, int newOrdinal) : base(originalParam, newOwner, newOrdinal, suppressOptional: true) { @@ -26,37 +22,8 @@ internal SourceDelegateClonedParameterSymbolForBeginAndEndInvoke(SourceParameter internal override bool IsCallerMemberName => _originalParam.IsCallerMemberName; - internal override int CallerArgumentExpressionParameterIndex - { - get - { - if (_originalParam.CallerArgumentExpressionParameterIndex == -1) - { - // If original param doesn't have a valid caller argument expression, don't try to recalculate. - // NOTE: Recalculation may result in different behavior if the delegate is declared like: - // delegate void D(string s1, [CallerArgumentExpression("callback")] [Optional] string s2); - // There is no parameter named "callback", however, the BeginInvoke has one. - return _lazyCallerArgumentExpressionParameterIndex = -1; - } - - if (_lazyCallerArgumentExpressionParameterIndex != UninitializedArgumentExpressionParameterIndex) - { - return _lazyCallerArgumentExpressionParameterIndex; - } - - var parameterName = _originalParam.ContainingSymbol.GetParameters()[_originalParam.CallerArgumentExpressionParameterIndex].Name; - var parameters = ((SourceDelegateMethodSymbol)ContainingSymbol).Parameters; - for (int i = 0; i < parameters.Length; i++) - { - if (parameters[i].Name.Equals(parameterName, StringComparison.Ordinal)) - { - return _lazyCallerArgumentExpressionParameterIndex = i; - } - } - - return _lazyCallerArgumentExpressionParameterIndex = -1; - } - } + // From code review: Could we just return -1 for now and see if anyone would ask us to support the scenarios. + internal override int CallerArgumentExpressionParameterIndex => -1; internal override ParameterSymbol WithCustomModifiersAndParams(TypeSymbol newType, ImmutableArray newCustomModifiers, ImmutableArray newRefCustomModifiers, bool newIsParams) { From a53e76686c0f201fc1890f387f191e6586fac441 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Tue, 29 Jun 2021 07:18:59 +0200 Subject: [PATCH 04/12] Add test --- .../AttributeTests_CallerInfoAttributes.cs | 69 ++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_CallerInfoAttributes.cs b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_CallerInfoAttributes.cs index 09c74f9f67a92..eb2ebecbe7715 100644 --- a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_CallerInfoAttributes.cs +++ b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_CallerInfoAttributes.cs @@ -65,6 +65,7 @@ public static void Main() "; var compilation = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularPreview); + // Begin/EndInvoke are not currently supported. CompileAndVerify(compilation).VerifyDiagnostics().VerifyIL("Program.Main", @" { // Code size 31 (0x1f) @@ -73,7 +74,7 @@ .maxstack 5 IL_0001: ldftn ""void Program.M(string, string)"" IL_0007: newobj ""Program.D..ctor(object, System.IntPtr)"" IL_000c: call ""string Program.GetString()"" - IL_0011: ldstr ""GetString()"" + IL_0011: ldstr ""default"" IL_0016: ldnull IL_0017: ldnull IL_0018: callvirt ""System.IAsyncResult Program.D.BeginInvoke(string, string, System.AsyncCallback, object)"" @@ -83,6 +84,72 @@ .maxstack 5 "); } + [ConditionalFact(typeof(CoreClrOnly))] + public void TestEndInvoke() + { + string source = @" +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace System.Runtime.InteropServices +{ + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + public sealed class OptionalAttribute : Attribute + { + public OptionalAttribute() + { + } + } + + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class DefaultParameterValueAttribute : Attribute + { + public DefaultParameterValueAttribute(object value) + { + Value = value; + } + public object Value { get; } + } +} + +class Program +{ + const string s1 = nameof(s1); + delegate void D(ref string s1, [CallerArgumentExpression(s1)] [Optional] [DefaultParameterValue(""default"")] string s2); + + static void M(ref string s1, string s2) + { + } + + public static void Main() + { + D d = M; + string s = string.Empty; + d.EndInvoke(ref s, null); + } +} +"; + + var compilation = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularPreview); + // Begin/EndInvoke are not currently supported. + CompileAndVerify(compilation).VerifyDiagnostics().VerifyIL("Program.Main", @" +{ + // Code size 27 (0x1b) + .maxstack 3 + .locals init (string V_0) //s + IL_0000: ldnull + IL_0001: ldftn ""void Program.M(ref string, string)"" + IL_0007: newobj ""Program.D..ctor(object, System.IntPtr)"" + IL_000c: ldsfld ""string string.Empty"" + IL_0011: stloc.0 + IL_0012: ldloca.s V_0 + IL_0014: ldnull + IL_0015: callvirt ""void Program.D.EndInvoke(ref string, System.IAsyncResult)"" + IL_001a: ret +} +"); + } + [ConditionalFact(typeof(CoreClrOnly))] public void TestBeginInvoke_ReferringToCallbackParameter() { From 502df54b4d2a3cb3a6cba59437d6a06df0719507 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Tue, 29 Jun 2021 07:22:30 +0200 Subject: [PATCH 05/12] Delete PROTOTYPE comment --- .../Portable/Symbols/Source/SourceClonedParameterSymbol.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs index 3b30cff0765a5..f0cdc5f200a3b 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceClonedParameterSymbol.cs @@ -9,9 +9,6 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols { - - // PROTOTYPE(caller-expr): Make SourceClonedParameterSymbol abstract and introduce new specialized types (same as being done in VB). - /// /// Represents a source parameter cloned from another , when they must share attribute data and default constant value. /// For example, parameters on a property symbol are cloned to generate parameters on accessors. From 25dd0ff1843c081a0e325e9fe2c7d05d84843369 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Wed, 30 Jun 2021 17:30:34 +0200 Subject: [PATCH 06/12] Add test --- .../AttributeTests_CallerInfoAttributes.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_CallerInfoAttributes.cs b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_CallerInfoAttributes.cs index eb2ebecbe7715..190aa0f99ed96 100644 --- a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_CallerInfoAttributes.cs +++ b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_CallerInfoAttributes.cs @@ -150,6 +150,59 @@ .locals init (string V_0) //s "); } + [ConditionalFact(typeof(CoreClrOnly))] + public void TestEndInvoke2() + { + string source = @" +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace System.Runtime.InteropServices +{ + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + public sealed class OptionalAttribute : Attribute + { + public OptionalAttribute() + { + } + } + + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class DefaultParameterValueAttribute : Attribute + { + public DefaultParameterValueAttribute(object value) + { + Value = value; + } + public object Value { get; } + } +} + +class Program +{ + const string s2 = nameof(s2); + delegate void D([CallerArgumentExpression(s2)] [Optional] [DefaultParameterValue(""default"")] ref string s1, string s2); + + static void M(ref string s1, string s2) + { + } + + public static void Main() + { + D d = M; + string s = string.Empty; + d.EndInvoke(ref s, null); + } +} +"; + + var compilation = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularPreview); + compilation.VerifyDiagnostics( + // (29,22): error CS9006: The CallerArgumentExpressionAttribute may only be applied to parameters with default values + // delegate void D([CallerArgumentExpression(s2)] [Optional] [DefaultParameterValue("default")] ref string s1, string s2); + Diagnostic(ErrorCode.ERR_BadCallerArgumentExpressionParamWithoutDefaultValue, "CallerArgumentExpression").WithLocation(29, 22)); + } + [ConditionalFact(typeof(CoreClrOnly))] public void TestBeginInvoke_ReferringToCallbackParameter() { From 8ee88e0b69f32778dc7fc048ebe4c0465e2598f7 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Wed, 30 Jun 2021 19:03:03 +0200 Subject: [PATCH 07/12] Two more tests --- .../AttributeTests_CallerInfoAttributes.cs | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_CallerInfoAttributes.cs b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_CallerInfoAttributes.cs index 190aa0f99ed96..4b71b15690f1d 100644 --- a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_CallerInfoAttributes.cs +++ b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_CallerInfoAttributes.cs @@ -203,6 +203,114 @@ public static void Main() Diagnostic(ErrorCode.ERR_BadCallerArgumentExpressionParamWithoutDefaultValue, "CallerArgumentExpression").WithLocation(29, 22)); } + [ConditionalFact(typeof(CoreClrOnly))] + public void TestEndInvoke3() + { + string source = @" +using System.Runtime.CompilerServices; + +namespace System.Runtime.InteropServices +{ + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + public sealed class OptionalAttribute : Attribute + { + public OptionalAttribute() + { + } + } + + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class DefaultParameterValueAttribute : Attribute + { + public DefaultParameterValueAttribute(object value) + { + Value = value; + } + public object Value { get; } + } +} + +class Program +{ + const string s1 = nameof(s1); + delegate void D(string s1, [CallerArgumentExpression(s1)] ref string s2 = ""default""); + + static void M(string s1, ref string s2) + { + } + + public static void Main() + { + D d = M; + string s = string.Empty; + d.EndInvoke(ref s, null); + } +} +"; + + var compilation = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularPreview); + compilation.VerifyDiagnostics( + // (28,33): error CS9006: The CallerArgumentExpressionAttribute may only be applied to parameters with default values + // delegate void D(string s1, [CallerArgumentExpression(s1)] ref string s2 = "default"); + Diagnostic(ErrorCode.ERR_BadCallerArgumentExpressionParamWithoutDefaultValue, "CallerArgumentExpression").WithLocation(28, 33), + // (28,63): error CS1741: A ref or out parameter cannot have a default value + // delegate void D(string s1, [CallerArgumentExpression(s1)] ref string s2 = "default"); + Diagnostic(ErrorCode.ERR_RefOutDefaultValue, "ref").WithLocation(28, 63)); + } + + [ConditionalFact(typeof(CoreClrOnly))] + public void TestEndInvoke4() + { + string source = @" +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace System.Runtime.InteropServices +{ + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + public sealed class OptionalAttribute : Attribute + { + public OptionalAttribute() + { + } + } + + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class DefaultParameterValueAttribute : Attribute + { + public DefaultParameterValueAttribute(object value) + { + Value = value; + } + public object Value { get; } + } +} + +class Program +{ + const string s1 = nameof(s1); + delegate void D(string s1, [CallerArgumentExpression(s1)] [Optional] [DefaultParameterValue(""default"")] ref string s2); + + static void M(string s1, ref string s2) + { + } + + public static void Main() + { + D d = M; + string s = string.Empty; + d.EndInvoke(ref s, null); + } +} +"; + + var compilation = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularPreview); + compilation.VerifyDiagnostics( + // (29,33): error CS9006: The CallerArgumentExpressionAttribute may only be applied to parameters with default values + // delegate void D(string s1, [CallerArgumentExpression(s1)] [Optional] [DefaultParameterValue("default")] ref string s2); + Diagnostic(ErrorCode.ERR_BadCallerArgumentExpressionParamWithoutDefaultValue, "CallerArgumentExpression").WithLocation(29, 33)); + } + [ConditionalFact(typeof(CoreClrOnly))] public void TestBeginInvoke_ReferringToCallbackParameter() { From e14db713085cefa33844bd1476c585f47f21cb23 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Thu, 1 Jul 2021 16:07:54 +0200 Subject: [PATCH 08/12] Address feedback --- .../Attributes/AttributeTests_CallerInfoAttributes.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_CallerInfoAttributes.cs b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_CallerInfoAttributes.cs index 4b71b15690f1d..1e1ac83b158aa 100644 --- a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_CallerInfoAttributes.cs +++ b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_CallerInfoAttributes.cs @@ -180,10 +180,10 @@ public DefaultParameterValueAttribute(object value) class Program { - const string s2 = nameof(s2); - delegate void D([CallerArgumentExpression(s2)] [Optional] [DefaultParameterValue(""default"")] ref string s1, string s2); + const string s5 = nameof(s5); + delegate void D([CallerArgumentExpression(s5)] [Optional] [DefaultParameterValue(""default"")] ref string s1, string s2, string s3, string s4, string s5); - static void M(ref string s1, string s2) + static void M(ref string s1, string s2, string s3, string s4, string s5) { } @@ -199,7 +199,7 @@ public static void Main() var compilation = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularPreview); compilation.VerifyDiagnostics( // (29,22): error CS9006: The CallerArgumentExpressionAttribute may only be applied to parameters with default values - // delegate void D([CallerArgumentExpression(s2)] [Optional] [DefaultParameterValue("default")] ref string s1, string s2); + // delegate void D([CallerArgumentExpression(s4)] [Optional] [DefaultParameterValue("default")] ref string s1, string s2, string s3, string s4, string s5); Diagnostic(ErrorCode.ERR_BadCallerArgumentExpressionParamWithoutDefaultValue, "CallerArgumentExpression").WithLocation(29, 22)); } From 0701455baea58218d94c57d6cd8768360958325a Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Thu, 1 Jul 2021 16:09:43 +0200 Subject: [PATCH 09/12] Fix diagnostic comment --- .../Test/Emit/Attributes/AttributeTests_CallerInfoAttributes.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_CallerInfoAttributes.cs b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_CallerInfoAttributes.cs index 1e1ac83b158aa..890fa9d26fb88 100644 --- a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_CallerInfoAttributes.cs +++ b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_CallerInfoAttributes.cs @@ -199,7 +199,7 @@ public static void Main() var compilation = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularPreview); compilation.VerifyDiagnostics( // (29,22): error CS9006: The CallerArgumentExpressionAttribute may only be applied to parameters with default values - // delegate void D([CallerArgumentExpression(s4)] [Optional] [DefaultParameterValue("default")] ref string s1, string s2, string s3, string s4, string s5); + // delegate void D([CallerArgumentExpression(s5)] [Optional] [DefaultParameterValue("default")] ref string s1, string s2, string s3, string s4, string s5); Diagnostic(ErrorCode.ERR_BadCallerArgumentExpressionParamWithoutDefaultValue, "CallerArgumentExpression").WithLocation(29, 22)); } From d0fdc95bf2c2c1602d73bc6270427a84f0137c4b Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Sat, 3 Jul 2021 19:39:14 +0200 Subject: [PATCH 10/12] Address requested test change --- .../AttributeTests_CallerInfoAttributes.cs | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_CallerInfoAttributes.cs b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_CallerInfoAttributes.cs index 890fa9d26fb88..64ceaced43e5a 100644 --- a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_CallerInfoAttributes.cs +++ b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_CallerInfoAttributes.cs @@ -190,8 +190,7 @@ static void M(ref string s1, string s2, string s3, string s4, string s5) public static void Main() { D d = M; - string s = string.Empty; - d.EndInvoke(ref s, null); + d.EndInvoke(result: null); } } "; @@ -200,7 +199,11 @@ public static void Main() compilation.VerifyDiagnostics( // (29,22): error CS9006: The CallerArgumentExpressionAttribute may only be applied to parameters with default values // delegate void D([CallerArgumentExpression(s5)] [Optional] [DefaultParameterValue("default")] ref string s1, string s2, string s3, string s4, string s5); - Diagnostic(ErrorCode.ERR_BadCallerArgumentExpressionParamWithoutDefaultValue, "CallerArgumentExpression").WithLocation(29, 22)); + Diagnostic(ErrorCode.ERR_BadCallerArgumentExpressionParamWithoutDefaultValue, "CallerArgumentExpression").WithLocation(29, 22), + // (38,11): error CS7036: There is no argument given that corresponds to the required formal parameter 's1' of 'Program.D.EndInvoke(ref string, IAsyncResult)' + // d.EndInvoke(result: null); + Diagnostic(ErrorCode.ERR_NoCorrespondingArgument, "EndInvoke").WithArguments("s1", "Program.D.EndInvoke(ref string, System.IAsyncResult)").WithLocation(38, 11) + ); } [ConditionalFact(typeof(CoreClrOnly))] @@ -243,7 +246,7 @@ public static void Main() { D d = M; string s = string.Empty; - d.EndInvoke(ref s, null); + d.EndInvoke(result: null); } } "; @@ -255,7 +258,10 @@ public static void Main() Diagnostic(ErrorCode.ERR_BadCallerArgumentExpressionParamWithoutDefaultValue, "CallerArgumentExpression").WithLocation(28, 33), // (28,63): error CS1741: A ref or out parameter cannot have a default value // delegate void D(string s1, [CallerArgumentExpression(s1)] ref string s2 = "default"); - Diagnostic(ErrorCode.ERR_RefOutDefaultValue, "ref").WithLocation(28, 63)); + Diagnostic(ErrorCode.ERR_RefOutDefaultValue, "ref").WithLocation(28, 63), + // (38,11): error CS7036: There is no argument given that corresponds to the required formal parameter 's2' of 'Program.D.EndInvoke(ref string, IAsyncResult)' + // d.EndInvoke(result: null); + Diagnostic(ErrorCode.ERR_NoCorrespondingArgument, "EndInvoke").WithArguments("s2", "Program.D.EndInvoke(ref string, System.IAsyncResult)").WithLocation(38, 11)); } [ConditionalFact(typeof(CoreClrOnly))] @@ -299,7 +305,7 @@ public static void Main() { D d = M; string s = string.Empty; - d.EndInvoke(ref s, null); + d.EndInvoke(result: null); } } "; @@ -308,7 +314,10 @@ public static void Main() compilation.VerifyDiagnostics( // (29,33): error CS9006: The CallerArgumentExpressionAttribute may only be applied to parameters with default values // delegate void D(string s1, [CallerArgumentExpression(s1)] [Optional] [DefaultParameterValue("default")] ref string s2); - Diagnostic(ErrorCode.ERR_BadCallerArgumentExpressionParamWithoutDefaultValue, "CallerArgumentExpression").WithLocation(29, 33)); + Diagnostic(ErrorCode.ERR_BadCallerArgumentExpressionParamWithoutDefaultValue, "CallerArgumentExpression").WithLocation(29, 33), + // (39,11): error CS7036: There is no argument given that corresponds to the required formal parameter 's2' of 'Program.D.EndInvoke(ref string, IAsyncResult)' + // d.EndInvoke(result: null); + Diagnostic(ErrorCode.ERR_NoCorrespondingArgument, "EndInvoke").WithArguments("s2", "Program.D.EndInvoke(ref string, System.IAsyncResult)").WithLocation(39, 11)); } [ConditionalFact(typeof(CoreClrOnly))] From 447c1ba28217eecf9a6c9b7eb25203e94555cf59 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Wed, 7 Jul 2021 22:33:01 +0200 Subject: [PATCH 11/12] Update src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateClonedParameterSymbolForBeginAndEndInvoke.cs --- .../SourceDelegateClonedParameterSymbolForBeginAndEndInvoke.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateClonedParameterSymbolForBeginAndEndInvoke.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateClonedParameterSymbolForBeginAndEndInvoke.cs index f063eb207e51b..df0d42853725a 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateClonedParameterSymbolForBeginAndEndInvoke.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateClonedParameterSymbolForBeginAndEndInvoke.cs @@ -22,7 +22,7 @@ internal SourceDelegateClonedParameterSymbolForBeginAndEndInvoke(SourceParameter internal override bool IsCallerMemberName => _originalParam.IsCallerMemberName; - // From code review: Could we just return -1 for now and see if anyone would ask us to support the scenarios. + // We don't currently support caller argument expression for cloned begin/end invoke. internal override int CallerArgumentExpressionParameterIndex => -1; internal override ParameterSymbol WithCustomModifiersAndParams(TypeSymbol newType, ImmutableArray newCustomModifiers, ImmutableArray newRefCustomModifiers, bool newIsParams) From 63531dc7c1227f2f2022aac1b4322d4aa1aac225 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Wed, 7 Jul 2021 22:49:12 +0200 Subject: [PATCH 12/12] Update src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateClonedParameterSymbolForBeginAndEndInvoke.cs --- .../SourceDelegateClonedParameterSymbolForBeginAndEndInvoke.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateClonedParameterSymbolForBeginAndEndInvoke.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateClonedParameterSymbolForBeginAndEndInvoke.cs index df0d42853725a..b28cc9a953e4c 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateClonedParameterSymbolForBeginAndEndInvoke.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateClonedParameterSymbolForBeginAndEndInvoke.cs @@ -22,7 +22,8 @@ internal SourceDelegateClonedParameterSymbolForBeginAndEndInvoke(SourceParameter internal override bool IsCallerMemberName => _originalParam.IsCallerMemberName; - // We don't currently support caller argument expression for cloned begin/end invoke. + // We don't currently support caller argument expression for cloned begin/end invoke since + // they throw PlatformNotSupportedException at runtime and we feel it's unnecessary to support them. internal override int CallerArgumentExpressionParameterIndex => -1; internal override ParameterSymbol WithCustomModifiersAndParams(TypeSymbol newType, ImmutableArray newCustomModifiers, ImmutableArray newRefCustomModifiers, bool newIsParams)