Skip to content

[Version 10.0] Feature support for CallerArgumentExpression attribute#1535

Draft
RexJaeschke wants to merge 8 commits into
draft-10from
v10-CallerArgumentExpression-attribute
Draft

[Version 10.0] Feature support for CallerArgumentExpression attribute#1535
RexJaeschke wants to merge 8 commits into
draft-10from
v10-CallerArgumentExpression-attribute

Conversation

@RexJaeschke

@RexJaeschke RexJaeschke commented Jan 19, 2026

Copy link
Copy Markdown
Contributor

This is Rex's adaptation of the corresponding MS proposal.

FYI, during testing, I discovered a feature that is not documented in the MS proposal: Not only is leading and trailing whitespace removed from an argument, but so too are any outermost grouping parens, with these two removal processes done repeatedly, as necessary, so an argument token set of ( ( (123) + 0) ) is reduced to (123) + 0.

@RexJaeschke RexJaeschke added this to the C# 10 milestone Jan 19, 2026
@RexJaeschke RexJaeschke added type: feature This issue describes a new feature Review: pending Proposal is available for review labels Jan 19, 2026
@RexJaeschke RexJaeschke marked this pull request as draft January 19, 2026 18:38
@BillWagner BillWagner force-pushed the v10-CallerArgumentExpression-attribute branch from dbed563 to f3c17e4 Compare March 19, 2026 01:15
Comment thread standard/attributes.md

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*

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or dynamic.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "First-class Span Types" proposal will also add a standard implicit conversion from string to ReadOnlySpan<char>.

Comment thread standard/attributes.md

- 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).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roslyn deletes some comments here:

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}>");
        M(/*a*/ ( /*b*/ 1 /*c*/ + /*d*/ 2 /*e*/ ) /*f*/);
    }
}

lowered to:

      M(3, "1 /*c*/ + /*d*/ 2");

@RexJaeschke RexJaeschke Mar 19, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KalleOlaviNiemitalo At a glance, this suggests that comments before the first token (redundant parens excluded) and after the last token are treated as whitespace---so are removed---while those between tokens are preserved. This follows the rule I wrote at Line 887 above.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

White space is defined in 6.3.4 and does not include comments.

@KalleOlaviNiemitalo

KalleOlaviNiemitalo commented Mar 19, 2026

Copy link
Copy Markdown
Contributor

I'm seeing odd interactions with params, may be a compiler bug:

using System.Runtime.CompilerServices;
#nullable enable
class Test
{
    string M(
        int first,
        [CallerArgumentExpression("vals")] string text = "N/A",
        params int[] vals)
    {
        return text;
    }

    static void N()
    {
        string a = new Test().M(1);
        string b = new Test().M(2, vals: new[] { 3 });
        string c = new Test().M(4, vals: 5);
    }
}

Lowered to:

    static void N()
    {
        // OK: the default value of text is used, because there is no argument for vals.
        string a = new Test().M(
            first: 1,
            text: "N/A",
            vals: System.Array.Empty<int>());

        // OK: the argument expression for vals is used.
        string b = new Test().M(
            first: 2,
            text: "new[] { 3 }",
            vals: new int[1] { 3 });

        // What on earth?
        // I expected text: "5".
        string c = new Test().M(
            first: 4,
            text: "new Test().M(4, vals: 5)",
            vals: new int[1] { 5 });
    }

Likewise in an attribute:

using System;
using System.Runtime.CompilerServices;

class TestAttribute: Attribute
{
    public TestAttribute(
        [CallerArgumentExpression("val")] string text = "N/A",
        params int[] val)
    {
    }
    
    public char C { get; set; }
}

// [Test("Test(val: 1, C = 'x')", new int[] { 1 })]
[Test(val: 1, C = 'x')]
class Test
{
}

Comment thread standard/attributes.md

- 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).

@KalleOlaviNiemitalo KalleOlaviNiemitalo Mar 19, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

input_element does not include PP_Directive but AFAICT those are also retained, if they occur within the argument expression.

Suggested change
- All other *input_element*s are retained verbatim (including white space, comments, *Unicode_Escape_Sequence*s, and `@` prefixes on identifiers).
- All other *input_element*s and *PP_Directive*s are retained verbatim (including white space, comments, *Unicode_Escape_Sequence*s, and `@` prefixes on identifiers).

Comment thread standard/attributes.md
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 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose "grouping parentheses" here means that the parentheses are part of the same parenthesized_expression, rather than a tuple_expression:

using System.Runtime.CompilerServices;
#nullable enable
class Test
{
    public static void M(
        (int, int) val,
        [CallerArgumentExpression("val")] string? text = null)
    {
    }
    
    static void N()
    {
        // text: "(1, 2)" rather than "1, 2"
        M(((1, 2)));
    }
}

(In the grammar, a deconstruction_tuple likewise has parentheses, but that cannot be an argument_value.)

Comment thread standard/attributes.md

> *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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently, "the text for the argument" means the expression or variable_reference part of argument_value, or the attribute_argument_expression. It would be good to have an example that shows the in/out/ref part of argument_value is not included in the text.

@BillWagner BillWagner force-pushed the v10-CallerArgumentExpression-attribute branch from f3c17e4 to d2bdab1 Compare April 14, 2026 18:56
BillWagner added a commit that referenced this pull request Apr 15, 2026
@BillWagner BillWagner force-pushed the v10-CallerArgumentExpression-attribute branch from d2bdab1 to 1ceddca Compare May 13, 2026 14:26
@BillWagner BillWagner force-pushed the v10-CallerArgumentExpression-attribute branch from 1ceddca to aecff93 Compare June 24, 2026 19:08
@BillWagner

Copy link
Copy Markdown
Member

An earlier version of this feature is already present on alpha-v10 from a prior meeting. Edits made to this PR since then are not yet on alpha-v10; they will land at the next propagation. If you need them on alpha-v10 sooner, please open a separate PR targeting alpha-v10.
Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Review: pending Proposal is available for review type: feature This issue describes a new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants