From 8bbe88fe79d6b55ac022008fb3483acbf9e994d0 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Mon, 19 Jan 2026 13:09:46 -0500 Subject: [PATCH 01/10] Add files via upload --- .../additional-files/CallerArgumentAttrM.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 tools/example-templates/additional-files/CallerArgumentAttrM.cs diff --git a/tools/example-templates/additional-files/CallerArgumentAttrM.cs b/tools/example-templates/additional-files/CallerArgumentAttrM.cs new file mode 100644 index 000000000..4184b801d --- /dev/null +++ b/tools/example-templates/additional-files/CallerArgumentAttrM.cs @@ -0,0 +1,10 @@ +using System; +using System.Runtime.CompilerServices; +#nullable enable +class Test +{ + public static void M(int val = 0, [CallerArgumentExpression("val")] string? text = null) + { + Console.WriteLine($"val = {val}, text = <{text}>"); + } +} From 47d7dea0b13a46a16a71c8e3806b01210e87a8aa Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Mon, 19 Jan 2026 13:22:12 -0500 Subject: [PATCH 02/10] add the new attribute --- standard/attributes.md | 72 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/standard/attributes.md b/standard/attributes.md index 75ce05915..2ced3bb55 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -497,7 +497,7 @@ A number of attributes affect the language in some way. These attributes include - `System.Diagnostics.ConditionalAttribute` ([§23.5.3](attributes.md#2353-the-conditional-attribute)), is a multi-use attribute class which is used to define conditional methods and conditional attribute classes. This attribute indicates a condition by testing a conditional compilation symbol. - `System.ObsoleteAttribute` ([§23.5.4](attributes.md#2354-the-obsolete-attribute)), which is used to mark a member as obsolete. - `System.Runtime.CompilerServices.AsyncMethodBuilderAttribute` ([§23.5.5](attributes.md#2355-the-asyncmethodbuilder-attribute)), which is used to establish a task builder for an async method. -- `System.Runtime.CompilerServices.CallerLineNumberAttribute` ([§23.5.6.2](attributes.md#23562-the-callerlinenumber-attribute)), `System.Runtime.CompilerServices.CallerFilePathAttribute` ([§23.5.6.3](attributes.md#23563-the-callerfilepath-attribute)), and `System.Runtime.CompilerServices.CallerMemberNameAttribute` ([§23.5.6.4](attributes.md#23564-the-callermembername-attribute)), which are used to supply information about the calling context to optional parameters. +- `System.Runtime.CompilerServices.CallerLineNumberAttribute` ([§23.5.6.2](attributes.md#23562-the-callerlinenumber-attribute)), `System.Runtime.CompilerServices.CallerFilePathAttribute` ([§23.5.6.3](attributes.md#23563-the-callerfilepath-attribute)), `System.Runtime.CompilerServices.CallerMemberNameAttribute` ([§23.5.6.4](attributes.md#23564-the-callermembername-attribute)), and `System.Runtime.CompilerServices.CallerArgumentExpressionAttribute` (§callargexpattr), which are used to supply information about the calling context to optional parameters. - `System.Runtime.CompilerServices.EnumeratorCancellationAttribute` ([§23.5.8](attributes.md#2358-the-enumeratorcancellation-attribute)), which is used to specify parameter for the cancellation token in an asynchronous iterator. - `System.Runtime.CompilerServices.ModuleInitializerAttribute` ([§23.5.9](attributes.md#2359-the-moduleinitializer-attribute)), which is used to mark a method as a module initializer. @@ -886,6 +886,76 @@ For an invocation that occurs within a local function or an anonymous function, > > This attribute supplies the name of the calling function member, which for local function `F1` is the method `Main`. And even though `F2` is called by `F1`, a local function is *not* a function member, so the reported caller of that invocation of `F2` is also `Main`. Similarly, when `F2` is called by the anonymous function assigned to `anonymousFunction`, the reported caller is the method `Main`, which calls that anonymous function. *end example* +#### §callargexpattr The CallerArgumentExpression attribute + +The attribute `System.Runtime.CompilerServices.CallerArgumentExpression` is applied to a *target parameter*, and can result in the capture of the source-code text of a sibling parameter’s argument as a string, referred to here as the *captured string*. + +The target parameter shall have a *default_argument*. + +Consider the following method declaration: + + +```csharp +using System; +using System.Runtime.CompilerServices; +#nullable enable +class Test +{ + public static void M(int val = 0, [CallerArgumentExpression("val")] string? text = null) + { + Console.WriteLine($"val = {val}, text = <{text}>"); + } +} +``` + +in which the target parameter is `text` and the sibling parameter is `val`, whose corresponding argument’s source-code text can be captured in `text` when `M` is called. + +The attribute constructor takes an argument of type `string`. That string + +- Shall contain the name of a sibling parameter; otherwise, the attribute is ignored. +- Shall omit the leading `@` from a parameter name having that prefix. + +A *parameter_list* may contain multiple target parameters. + +The type of the target parameter shall have a standard conversion from `string`. + +> *Note:* This means no user-defined conversions from `string` are allowed, and in practice means the type of such a parameter must be `string`, `object`, or an interface implemented by `string`. *end note* + +If an explicit argument is passed for the target parameter, no string is captured, and that parameter takes on that argument’s value. Otherwise, the text for the argument corresponding to the sibling parameter is converted to a captured string, according to the following rules: + +- Leading and trailing white space is removed. +- All other *input_element*s are retained verbatim (including white space, comments, *Unicode_Escape_Sequence*s, and `@` prefixes on identifiers). + +The captured string is then passed as the argument corresponding to the target parameter. However, if the argument for the sibling parameter is omitted, the target parameter takes on its *default_argument* value. + +> *Example*: Given the declaration of `M` above, consider the following calls to `M`: +> +> +> ```csharp +> Test.M(); +> Test.M(123); +> Test.M(123, null); +> Test.M(123, "xyz"); +> Test.M( 1 + 2 ); +> int local = 10; +> Test.M(l\u006fcal /*...*/ + // xxx +> 5); +> ``` +> +> the output produced is +> +> ```console +> val = 0, text = <> +> val = 123, text = <123> +> val = 123, text = <> +> val = 123, text = +> val = 3, text = <1 + 2> +> val = 15, text = 5> +> ``` +> +> *end example* + ### 23.5.7 Code analysis attributes #### 23.5.7.1 General From 20cc26f07fbedb8441d3297b13951854934ae22d Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Mon, 19 Jan 2026 13:29:05 -0500 Subject: [PATCH 03/10] Add new attribute type --- standard/standard-library.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/standard/standard-library.md b/standard/standard-library.md index 375032f2f..761de74cc 100644 --- a/standard/standard-library.md +++ b/standard/standard-library.md @@ -789,6 +789,13 @@ namespace System.Runtime.CompilerServices public Type BuilderType { get; } } + [System.AttributeUsage(System.AttributeTargets.Parameter, AllowMultiple=false, + Inherited=false)] + public sealed class CallerArgumentExpressionAttribute : Attribute + { + public CallerArgumentExpressionAttribute (string parameterName); + } + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] public sealed class CallerFilePathAttribute : Attribute { @@ -1430,6 +1437,7 @@ The following library types are referenced in this specification. The full names - `global::System.Reflection.MemberInfo` - `global::System.Runtime.CompilerServices.AsyncMethodBuilderAttribute` - `global::System.Runtime.CompilerServices.CallerFilePathAttribute` +- `global::System.Runtime.CompilerServices.CallerArgumentExpressionAttribute` - `global::System.Runtime.CompilerServices.CallerLineNumberAttribute` - `global::System.Runtime.CompilerServices.CallerMemberNameAttribute` - `global::System.Runtime.CompilerServices.FormattableStringFactory` From 25e6cb103e58136368dbf5b02670480d71e5cf4b Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Mon, 19 Jan 2026 13:45:10 -0500 Subject: [PATCH 04/10] fix md spacing --- standard/attributes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/attributes.md b/standard/attributes.md index 2ced3bb55..7fb5de810 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -941,7 +941,7 @@ The captured string is then passed as the argument corresponding to the target p > Test.M(l\u006fcal /*...*/ + // xxx > 5); > ``` -> +> > the output produced is > > ```console From 86e2b812dadf800257f052d6fde50abe96bb02e2 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Mon, 19 Jan 2026 15:52:29 -0500 Subject: [PATCH 05/10] mention extension methods, and grouping parens --- standard/attributes.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/standard/attributes.md b/standard/attributes.md index 7fb5de810..c4fa9557f 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -890,7 +890,7 @@ For an invocation that occurs within a local function or an anonymous function, The attribute `System.Runtime.CompilerServices.CallerArgumentExpression` is applied to a *target parameter*, and can result in the capture of the source-code text of a sibling parameter’s argument as a string, referred to here as the *captured string*. -The target parameter shall have a *default_argument*. +Except when it is the first parameter in an extension method, the target parameter shall have a *default_argument*. Consider the following method declaration: @@ -923,7 +923,8 @@ The type of the target parameter shall have a standard conversion from `string`. If an explicit argument is passed for the target parameter, no string is captured, and that parameter takes on that argument’s value. Otherwise, the text for the argument corresponding to the sibling parameter is converted to a captured string, according to the following rules: -- Leading and trailing white space is removed. +- Leading and trailing white space is removed both before and after any outermost grouping parentheses are removed. +- All outermost grouping parentheses are removed both before and after any leading and trailing white space is removed. - All other *input_element*s are retained verbatim (including white space, comments, *Unicode_Escape_Sequence*s, and `@` prefixes on identifiers). The captured string is then passed as the argument corresponding to the target parameter. However, if the argument for the sibling parameter is omitted, the target parameter takes on its *default_argument* value. @@ -937,6 +938,7 @@ The captured string is then passed as the argument corresponding to the target p > Test.M(123, null); > Test.M(123, "xyz"); > Test.M( 1 + 2 ); +> Test.M( ( ((123) + 0) ) ); > int local = 10; > Test.M(l\u006fcal /*...*/ + // xxx > 5); @@ -950,6 +952,7 @@ The captured string is then passed as the argument corresponding to the target p > val = 123, text = <> > val = 123, text = > val = 3, text = <1 + 2> +> val = 123, text = <(123 + 0)> > val = 15, text = 5> > ``` From 3478a8c131844a1470b37755d3a45888a16a6d5a Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Mon, 19 Jan 2026 15:59:53 -0500 Subject: [PATCH 06/10] correct expected console output --- standard/attributes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/attributes.md b/standard/attributes.md index c4fa9557f..14119590b 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -952,7 +952,7 @@ The captured string is then passed as the argument corresponding to the target p > val = 123, text = <> > val = 123, text = > val = 3, text = <1 + 2> -> val = 123, text = <(123 + 0)> +> val = 123, text = <((123) + 0)> > val = 15, text = 5> > ``` From 30f80b1ded61c71459fcf82f9258b11d38b12ad9 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Mon, 19 Jan 2026 16:07:01 -0500 Subject: [PATCH 07/10] fix more console output --- standard/attributes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/standard/attributes.md b/standard/attributes.md index 14119590b..48adebfc7 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -938,7 +938,7 @@ The captured string is then passed as the argument corresponding to the target p > Test.M(123, null); > Test.M(123, "xyz"); > Test.M( 1 + 2 ); -> Test.M( ( ((123) + 0) ) ); +> Test.M(( ( (123) + 0) ) ); > int local = 10; > Test.M(l\u006fcal /*...*/ + // xxx > 5); @@ -952,7 +952,7 @@ The captured string is then passed as the argument corresponding to the target p > val = 123, text = <> > val = 123, text = > val = 3, text = <1 + 2> -> val = 123, text = <((123) + 0)> +> val = 123, text = <(123) + 0> > val = 15, text = 5> > ``` From 7edf3544e577c8830999491253a94440001183e8 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Wed, 18 Mar 2026 21:14:51 -0400 Subject: [PATCH 08/10] Initial review Review the changes for the feature and make a few small updates. --- standard/attributes.md | 12 ++++++++++-- standard/standard-library.md | 8 ++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/standard/attributes.md b/standard/attributes.md index 48adebfc7..c6c9f623d 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -800,13 +800,21 @@ Caller information is only substituted when a function is explicitly invoked in One exception is query expressions. These are considered syntactic expansions, and if the calls they expand to omit optional parameters with caller-info attributes, caller information will be substituted. The location used is the location of the query clause which the call was generated from. +<<<<<<< HEAD If more than one caller-info attribute is specified on a given parameter, they are recognized in the following order: `CallerLineNumberAttribute`, `CallerFilePathAttribute`, `CallerMemberNameAttribute`. Consider the following parameter declaration: +======= +If more than one caller-info attribute is specified on a given parameter, they are recognized in the following order: `CallerLineNumber`, `CallerFilePath`, `CallerMemberName`, `CallerArgumentExpression`. Consider the following parameter declaration: +>>>>>>> 6e659aa7 (Initial review) ```csharp [CallerMemberName, CallerFilePath, CallerLineNumber] object p = ... ``` +<<<<<<< HEAD `CallerLineNumberAttribute` takes precedence, and the other two attributes are ignored. If `CallerLineNumberAttribute` were omitted, `CallerFilePathAttribute` would take precedence, and `CallerMemberNameAttribute` would be ignored. The lexical ordering of these attributes is irrelevant. +======= +`CallerLineNumber` takes precedence, and the other three attributes are ignored. If `CallerLineNumber` were omitted, `CallerFilePath` would take precedence, and `CallerMemberName` and `CallerArgumentExpression` would be ignored. The lexical ordering of these attributes is irrelevant. +>>>>>>> 6e659aa7 (Initial review) #### 23.5.6.2 The CallerLineNumber attribute @@ -888,9 +896,9 @@ For an invocation that occurs within a local function or an anonymous function, #### §callargexpattr The CallerArgumentExpression attribute -The attribute `System.Runtime.CompilerServices.CallerArgumentExpression` is applied to a *target parameter*, and can result in the capture of the source-code text of a sibling parameter’s argument as a string, referred to here as the *captured string*. +The attribute `System.Runtime.CompilerServices.CallerArgumentExpressionAttribute` is applied to a *target parameter*, and can result in the capture of the source-code text of a sibling parameter’s argument as a string, referred to here as the *captured string*. -Except when it is the first parameter in an extension method, the target parameter shall have a *default_argument*. +Except when it is the first parameter in an extension method, the target parameter shall have a *default_argument*. When applied to the first parameter of an extension method, the captured string is the source text of the receiver expression in an extension method invocation. If the method is invoked using static method syntax, the captured string is the argument corresponding to the first parameter. Consider the following method declaration: diff --git a/standard/standard-library.md b/standard/standard-library.md index 761de74cc..fed7ad39a 100644 --- a/standard/standard-library.md +++ b/standard/standard-library.md @@ -789,11 +789,11 @@ namespace System.Runtime.CompilerServices public Type BuilderType { get; } } - [System.AttributeUsage(System.AttributeTargets.Parameter, AllowMultiple=false, - Inherited=false)] + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, + Inherited = false)] public sealed class CallerArgumentExpressionAttribute : Attribute { - public CallerArgumentExpressionAttribute (string parameterName); + public CallerArgumentExpressionAttribute(string parameterName); } [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] @@ -1436,8 +1436,8 @@ The following library types are referenced in this specification. The full names - `global::System.Linq.Expressions.Expression` - `global::System.Reflection.MemberInfo` - `global::System.Runtime.CompilerServices.AsyncMethodBuilderAttribute` -- `global::System.Runtime.CompilerServices.CallerFilePathAttribute` - `global::System.Runtime.CompilerServices.CallerArgumentExpressionAttribute` +- `global::System.Runtime.CompilerServices.CallerFilePathAttribute` - `global::System.Runtime.CompilerServices.CallerLineNumberAttribute` - `global::System.Runtime.CompilerServices.CallerMemberNameAttribute` - `global::System.Runtime.CompilerServices.FormattableStringFactory` From 7f7464eee89b62439f14cc49481ce1721ab825e9 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Mon, 14 Sep 2026 13:18:44 -0400 Subject: [PATCH 09/10] remove namespace --- standard/attributes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/attributes.md b/standard/attributes.md index c6c9f623d..8a1b3c87b 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -896,7 +896,7 @@ For an invocation that occurs within a local function or an anonymous function, #### §callargexpattr The CallerArgumentExpression attribute -The attribute `System.Runtime.CompilerServices.CallerArgumentExpressionAttribute` is applied to a *target parameter*, and can result in the capture of the source-code text of a sibling parameter’s argument as a string, referred to here as the *captured string*. +The attribute `CallerArgumentExpressionAttribute` is applied to a *target parameter*, and can result in the capture of the source-code text of a sibling parameter’s argument as a string, referred to here as the *captured string*. Except when it is the first parameter in an extension method, the target parameter shall have a *default_argument*. When applied to the first parameter of an extension method, the captured string is the source text of the receiver expression in an extension method invocation. If the method is invoked using static method syntax, the captured string is the argument corresponding to the first parameter. From bf2910a50124654c85a3e24505532541a2949ba9 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 18 Sep 2026 16:27:19 -0400 Subject: [PATCH 10/10] Resolve caller-information integration wording Preserve Attribute-suffixed terminology while adding CallerArgumentExpression precedence. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 449475b4-d25b-481a-83b0-52ff266362c1 --- standard/attributes.md | 12 ++--------- .../additional-files/CallerArgumentAttrM.cs | 20 +++++++++---------- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/standard/attributes.md b/standard/attributes.md index 8a1b3c87b..a9e6d1835 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -800,21 +800,13 @@ Caller information is only substituted when a function is explicitly invoked in One exception is query expressions. These are considered syntactic expansions, and if the calls they expand to omit optional parameters with caller-info attributes, caller information will be substituted. The location used is the location of the query clause which the call was generated from. -<<<<<<< HEAD -If more than one caller-info attribute is specified on a given parameter, they are recognized in the following order: `CallerLineNumberAttribute`, `CallerFilePathAttribute`, `CallerMemberNameAttribute`. Consider the following parameter declaration: -======= -If more than one caller-info attribute is specified on a given parameter, they are recognized in the following order: `CallerLineNumber`, `CallerFilePath`, `CallerMemberName`, `CallerArgumentExpression`. Consider the following parameter declaration: ->>>>>>> 6e659aa7 (Initial review) +If more than one caller-info attribute is specified on a given parameter, they are recognized in the following order: `CallerLineNumberAttribute`, `CallerFilePathAttribute`, `CallerMemberNameAttribute`, `CallerArgumentExpressionAttribute`. Consider the following parameter declaration: ```csharp [CallerMemberName, CallerFilePath, CallerLineNumber] object p = ... ``` -<<<<<<< HEAD -`CallerLineNumberAttribute` takes precedence, and the other two attributes are ignored. If `CallerLineNumberAttribute` were omitted, `CallerFilePathAttribute` would take precedence, and `CallerMemberNameAttribute` would be ignored. The lexical ordering of these attributes is irrelevant. -======= -`CallerLineNumber` takes precedence, and the other three attributes are ignored. If `CallerLineNumber` were omitted, `CallerFilePath` would take precedence, and `CallerMemberName` and `CallerArgumentExpression` would be ignored. The lexical ordering of these attributes is irrelevant. ->>>>>>> 6e659aa7 (Initial review) +`CallerLineNumberAttribute` takes precedence, and the other three attributes are ignored. If `CallerLineNumberAttribute` were omitted, `CallerFilePathAttribute` would take precedence, and `CallerMemberNameAttribute` and `CallerArgumentExpressionAttribute` would be ignored. The lexical ordering of these attributes is irrelevant. #### 23.5.6.2 The CallerLineNumber attribute diff --git a/tools/example-templates/additional-files/CallerArgumentAttrM.cs b/tools/example-templates/additional-files/CallerArgumentAttrM.cs index 4184b801d..daab28ff5 100644 --- a/tools/example-templates/additional-files/CallerArgumentAttrM.cs +++ b/tools/example-templates/additional-files/CallerArgumentAttrM.cs @@ -1,10 +1,10 @@ -using System; -using System.Runtime.CompilerServices; -#nullable enable -class Test -{ - public static void M(int val = 0, [CallerArgumentExpression("val")] string? text = null) - { - Console.WriteLine($"val = {val}, text = <{text}>"); - } -} +using System; +using System.Runtime.CompilerServices; +#nullable enable +class Test +{ + public static void M(int val = 0, [CallerArgumentExpression("val")] string? text = null) + { + Console.WriteLine($"val = {val}, text = <{text}>"); + } +}