Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: CallerArgumentExpressionAttribute #22596

Closed
jamesqo opened this issue Jul 2, 2017 · 41 comments
Closed

Proposal: CallerArgumentExpressionAttribute #22596

jamesqo opened this issue Jul 2, 2017 · 41 comments
Assignees
Labels
api-approved API was approved in API review, it can be implemented area-System.Runtime.CompilerServices Hackathon Issues picked for Hackathon help wanted [up-for-grabs] Good issue for external contributors
Milestone

Comments

@jamesqo
Copy link
Contributor

jamesqo commented Jul 2, 2017

Rationale

In order to facilitate the language feature at dotnet/csharplang#287, which has been championed and discussed in the LDM, we should add CallerArgumentExpressionAttribute to the framework. The LDM notes seem to approve of getting this into the framework: see here.

Proposal

namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
    public sealed class CallerArgumentExpressionAttribute : Attribute
    {
        public CallerArgumentExpressionAttribute(string parameterName);

        // Added in response to @svick's comment
        public string ParameterName { get; }
    }
}

Notes

  • The name of the constructor argument is intentionally parameterName and not argumentName. Parameters refer to the variables declared inside the callee, while arguments are the values passed by the caller.

image

@jamesqo
Copy link
Contributor Author

jamesqo commented Jul 2, 2017

cc'ing people from the csharplang threads (dotnet/csharplang#287 and dotnet/csharplang#205): @gafter, @CyrusNajmabadi, @HaloFour, @ig-sinicyn, @tannergooding, @svick, @jnm2

@jjvanzon
Copy link

jjvanzon commented Jul 2, 2017

Cool proposal. I have made several frameworks in the past to accomplish something similar, with either less performance or more verbose notation, that might become redundant by this.

https://github.com/jjvanzon/JJ.Framework/tree/master/Framework/Exceptions
https://github.com/jjvanzon/JJ.Framework/tree/master/Framework/Testing

Those are not even the only ones!

@svick
Copy link
Contributor

svick commented Jul 3, 2017

Should the parameterName also be exposed as a public property? I'm not sure how useful would that actually be (compilers are probably going to directly read the metadata, they're not going to instantiate the attribute object), but I think it's consistent with the rest of BCL. It's also consistent with Framework Design Guidelines.

@jamesqo
Copy link
Contributor Author

jamesqo commented Jul 3, 2017

@svick Sure, why not. I think such a property may even have a practical use for reflective code that wants to determine the parameter being referenced. (Although, that may already be possible (in a slightly less cleaner way) through this API which allows you to get the ctor arguments of an attribute.) Updated the proposal.

@jamesqo
Copy link
Contributor Author

jamesqo commented Jul 3, 2017

@karelz, I think this api is safe to mark ready-for-review. The C# team has already approved this and explicitly said we should start the process for getting this in, see here. The addition of this api to corefx is what is causing them to push this feature back to 7.x, I do not think we should risk further delays.

@HaloFour
Copy link

HaloFour commented Jul 3, 2017

@CyrusNajmabadi

What of the other caller info attributes?

@karelz
Copy link
Member

karelz commented Jul 4, 2017

@AlexGhiondea @joperezr are area owners and should do first-level API review. If they are happy with it, they will be able to mark it 'api-ready-for-review'.

@jamesqo
Copy link
Contributor Author

jamesqo commented Jul 30, 2017

@AlexGhiondea @joperezr Please consider marking this ready-for-review: the C# language design team has already approved of it, and this attribute needs to get into the fx for them to take any further action.

@joperezr
Copy link
Member

proposal looks reasonable. Marking as ready for review.

@jamesqo
Copy link
Contributor Author

jamesqo commented Sep 9, 2017

@karelz When are you planning to do an API review for this issue? There seem to be quite a few issues-- some since January-- marked ready-for-review that are not approved yet.

@karelz
Copy link
Member

karelz commented Sep 9, 2017

We have a small backlog accumulated over the summer. Except one special-case exception, all api-ready-for-review issues were marked as such after 6/24.
We are slowly chewing through our backlog every Tuesday (we have 1-2h slot for GitHub triage, with exception of next week when we will focus on larger API review of System.Threading.Tasks.Channels the whole 2 hours).

So far I have been trying to bundle API review issues into similar blocks (Reflection last week, sadly the recording of the session failed to record properly), and prioritize the easy ones. This one should be part of next badge in ~10 days or the week after.
Does it help?

@jamesqo
Copy link
Contributor Author

jamesqo commented Sep 10, 2017

@karelz Yeah, it helps. Thanks for the update! 😄

@karelz
Copy link
Member

karelz commented Sep 29, 2017

API review: Looks good as proposed, but we should wait for final language feature.
We do not want to ship new API which won't be used by the language feature.

In general, I would recommend to not file API proposals ahead of LDM being further down the road.

@jamesqo
Copy link
Contributor Author

jamesqo commented Dec 3, 2017

@karelz I don't think it makes sense to mark this as blocked. The compiler team has said that they are waiting on the type to be added before they will discuss it further... it's like an issue deadlock.

@gafter
Copy link
Member

gafter commented Dec 4, 2017

I expect the compiler team to add the relevant language feature in the next release (minor or major) following the addition of this supporting attribute.

/cc @jcouv

@karelz
Copy link
Member

karelz commented Dec 4, 2017

@gafter if you can confirm the API shape fits Roslyn needs, we can unblock the API addition in 2.1.

@jcouv
Copy link
Member

jcouv commented Dec 20, 2017

@karelz From discussion with LDM over email, the API shape was confirmed to be good. I forwarded you the thread. Thanks!

@karelz
Copy link
Member

karelz commented Dec 20, 2017

Unblocked, it is ready to roll. Any takers? -- should be fairly straightforward to add

@jcouv
Copy link
Member

jcouv commented Dec 20, 2017

Tagging @alrz who is working on compiler change to see if he could implement the type as well.

@alrz
Copy link

alrz commented Dec 20, 2017

I didn't find other caller info attributes in this repo. sounds like System.Private.CoreLib.csproj is private?

@jcouv
Copy link
Member

jcouv commented Dec 20, 2017

@alrz The implementation lives in corert (example).

I'm not sure whether adding the new attribute to corert is sufficient for it to appear (or whether some change in corefx is also needed). @karelz can clarify.

@jamesqo
Copy link
Contributor Author

jamesqo commented Dec 21, 2017

From discussion with LDM over email, the API shape was confirmed to be good. I forwarded you the thread. Thanks!

WOOT! Been waiting for this for so long 🎉

@jcouv
Copy link
Member

jcouv commented Dec 21, 2017

@jamesqo glad to hear. I must say there is one more language issue that came up afterwards: how can existing APIs adopt the attribute? Adding an optional string parameter causes problems in typical methods we're thinking of (such as asserts).

@jamesqo
Copy link
Contributor Author

jamesqo commented Dec 21, 2017

@jcouv Just gave you a really long response about binary compatibility on the csharplang issue.

@karelz
Copy link
Member

karelz commented Dec 21, 2017

Marking as blocked again, until we resolve the language feature.

@jamesqo
Copy link
Contributor Author

jamesqo commented May 30, 2018

@karelz I think it's resolved. They said there wouldn't be a binary compatibility issue.

@jcouv
Copy link
Member

jcouv commented May 30, 2018

Yes, here are the relevant LDM notes. Thanks

@jcouv
Copy link
Member

jcouv commented May 30, 2018

Removed the "blocked" label accordingly.

@jswolf19
Copy link
Contributor

jswolf19 commented Jun 2, 2018

It looks like this API is approved, but how it'll actually manifest is still under debate. However, if putting this in'll get things moving, this is something I'd like to see.

I'm not sure where this would be added in corefx, though. There are some CompilerServices Attributes in Common\src\CoreLib\System\Runtime\CompilerServices, but these files are just for including in other projects, if I understand correctly.

I'd be glad to add it if I know where to ^_^

@karelz
Copy link
Member

karelz commented Jun 2, 2018

@jswolf19 I didn't read the LDM discussion carefully. My naive understanding was that it is ready to be added into CoreFX.
CoreLib is shared with CoreCLR and CoreRT repos. There are members of the namespace which are in various ref assemblies (see search). I am not quite sure where this one would go.

@danmosemsft would probably know more ...

@jswolf19
Copy link
Contributor

jswolf19 commented Jun 2, 2018

@karelz
I got the impression from the discussion at csharplang issue 287 that the general consensus is to apply this to a string argument and supply a parameter name, but there was some discussion of dealing with multiple arguments, using expressions over strings, or some other method which might affect the makeup of this class.

So, if I add to the Common\src\CoreLib\System\Runtime\CompilerServices, it'll eventually make its way to the CoreCLR and CoreRT repos?

@karelz
Copy link
Member

karelz commented Jun 2, 2018

I admit I don't know which way the mirror goes :( ... either check history of such additions (I suspect CoreCLR is the primary source), or wait for someone knowledgeable to chime in ...

@jswolf19
Copy link
Contributor

jswolf19 commented Jun 2, 2018

The LDM linked is newer than much of the discussion at the issue, so it would seem that this API is what has gotten consensus. I'll see what I can find as far as addition history.

@jswolf19
Copy link
Contributor

jswolf19 commented Jun 2, 2018

@karelz @danmosemsft Looking at IntrinsicAttribute, it would appear that CoreCLR is the start point, so I'll look at adding this there?

CoreCLR
CoreFX
CoreRT

@jswolf19
Copy link
Contributor

jswolf19 commented Jun 3, 2018

So this is what I'm perceiving the order of things that needs to get done is. Please let me know if I should do things differently, otherwise, I'll submit the CoreCLR pull request this week.

  • Add CallerArgumentExpressionAttribute to System.Private.CoreLib (CoreCLR)
  • CoreCLR gets merged to CoreFX (by someone else)
  • Add CallerArgumentExpressionAttribute to the ref assembly in System.Runtime (CoreFX)
  • Add a simple test showing usage of CallerArgumentExpressionAttribute asserting that it shows up in the reflection metadata in System.Runtime.Tests (CoreFX)
  • Add disabled tests for the expected result once the compiler handles the CallerArgumentExpressionAttribute (CoreFX)

For the last step, I have questions about whitespace handling and corner cases (see below). If there are any other cases that I should include, please let me know.

void IntParamMethod(int val, [CallerArgumentExpression("val")] string expr = null) {...}

IntParamMethod(x); // "x"
IntParamMethod( x ); // "x"
IntParamMethod(x, "y"); // "y"
IntParamMethod( x  + 20); // "x  + 20" (2 spaces after x)

void NullOptionalParamMethod(string val = null, [CallerArgumentExpression("val")] string expr = null) {...}

NullOptionalParamMethod(); // "null"

void ConstOptionalParamMethod(string val = StringConst + "string literal", [CallerArgumentExpression("val")] string expr = null) {...}

ConstOptionalParamMethod(); // "StringConst + \"string literal\""

void CompilerSuppliedParamMethod([CallerFilePath()] string val = "", [CallerArgumentExpression("val")] string expr = null) {...}

CompilerSuppliedParamMethod(); // "\"\"" (???)
CompilerSuppliedParamMethod("not file path"); // "\"not file path\""

void ExtensionMethod(this int val, [CallerArgumentExpression("val")] string expr = null) {...}

Math.Min( x  + 20, //comment
          x * x
        ) . ExtensionMethod(); // "Math.Min( x  + 20, //comment\r\n          x * x\r\n        )" (??? assuming windows crlf)

void IntParamMethod(int val, [CallerArgumentExpression("notVal")] string expr = null) {...}

IntParamMethod(x); // "" as per proposal.

void IntParamMethod(int val, [CallerArgumentExpression("this")] string expr = null) {...}

IntParamMethod(x); // "" ?
this.IntParamMethod(x); // "this" ?

@danmoseley
Copy link
Member

@jswolf19 somehow I missed this issue. Yes your sequence looks correct. Note the mirroring you care about from coreclr to corefx is the insertion of the coreclr SHA not the mirroring of coreclr source code (which is for code reuse)

@danmoseley
Copy link
Member

Your questions about test expectations are msybe questions for Roslyn repo since they will be producing the result.

stephentoub referenced this issue in dotnet/corefx Jun 18, 2018
…ly (#30469)

* Expose CallerArgumentExpressionAttribute (#21809)

* added basic CallerArgumentExpressionAttribute tests

* added test to show that overload without optional parameter is taken over method with

* added tests to illustrate behavior of CallerArgumentExpressionAttribute (final behavior may differ)

* added type CallerArgumentExpressionAttribute to uapaot ApiCompatBaseline

* Moved CallerArgumentExpressionTests to non-netfx section

* added newlines as per review
@jamesqo
Copy link
Contributor Author

jamesqo commented Jun 23, 2018

@jswolf19 I've answered your questions in parentheses below:

void IntParamMethod(int val, [CallerArgumentExpression("val")] string expr = null) {...}

IntParamMethod(x); // "x" (yes)
IntParamMethod( x ); // "x" (yes)
IntParamMethod(x, "y"); // "y" (yes)
IntParamMethod( x  + 20); // "x  + 20" (2 spaces after x) (yes)

void NullOptionalParamMethod(string val = null, [CallerArgumentExpression("val")] string expr = null) {...}

NullOptionalParamMethod(); // "null" (yes)

void ConstOptionalParamMethod(string val = StringConst + "string literal", [CallerArgumentExpression("val")] string expr = null) {...}

ConstOptionalParamMethod(); // "StringConst + \"string literal\"" (yes)

void CompilerSuppliedParamMethod([CallerFilePath()] string val = "", [CallerArgumentExpression("val")] string expr = null) {...}

CompilerSuppliedParamMethod(); // "\"\"" (yes)
CompilerSuppliedParamMethod("not file path"); // "\"not file path\"" (yes)

void ExtensionMethod(this int val, [CallerArgumentExpression("val")] string expr = null) {...}

Math.Min( x  + 20, //comment
          x * x
        ) . ExtensionMethod(); // "Math.Min( x  + 20, //comment\r\n          x * x\r\n        )" (??? assuming windows crlf) (yes)

void IntParamMethod(int val, [CallerArgumentExpression("notVal")] string expr = null) {...}

IntParamMethod(x); // "" as per proposal. (yes)

(no: using CAE to find `this` expression will not be supported for instance methods, only extension methods)
void IntParamMethod(int val, [CallerArgumentExpression("this")] string expr = null) {...}

IntParamMethod(x); // "" ? (yes: will be "" since "this" is not recognized)
this.IntParamMethod(x); // "this" ? (no: will be "" since "this" is not recognized)

(note: for `ExtensionMethod(this int val, string expr = null)`, if it's possible to call it like `ExtensionMethod()` without prefixing with `this.` then the expression passed should be "".
 not sure if this is possible since extension methods normally require `this.` to call them even from the class they target, but if it is that should be the behavior)

@jswolf19
Copy link
Contributor

@jamesqo Thanks for your reply. There's an inline comment in the ExtensionMethod example, so I'm guessing that should not be included? (2 instead of 1 below)

Math.Min( x  + 20, //comment
          x * x
        ) . ExtensionMethod(); // assuming windows crlf

//1. "Math.Min( x  + 20, //comment\r\n          x * x\r\n        )"
//2. "Math.Min( x  + 20, \r\n          x * x\r\n        )"

(note: for ExtensionMethod(this int val, string expr = null), if it's possible to call it like ExtensionMethod() without prefixing with this. then the expression passed should be "".
not sure if this is possible since extension methods normally require this. to call them even from the class they target, but if it is that should be the behavior)

It doesn't appear to be possible in c#, but I'm pretty sure it is possible in vb.

@CyrusNajmabadi
Copy link
Member

@jamesqo Thanks for your reply. There's an inline comment in the ExtensionMethod example, so I'm guessing that should not be included? (2 instead of 1 below)

It should be included (IMO). The compiler will simply give you the unprocessed span of the code from the start of the first token in the arg to the end of the last token in the arg. All intermediate 'trivia' (including comments, whitespace, newlines, and even preprocessor directives) would be included.

@CyrusNajmabadi
Copy link
Member

CyrusNajmabadi commented Jun 23, 2018

Note: in Roslyn parlance, this would be equivalent to calling: sourceText.GetText(argNode.Expression.Span)

Note: another important case to consider is:

Foo(ref SomeArg)

Presumably we want "SomeArg" on the other end of that, not "ref SomeArg". This is why my Roslyn-parlance code is sourceText.GetText(argNode.Expression.Span) and not sourceText.GetText(argNode.Span)

@msftgits msftgits transferred this issue from dotnet/corefx Jan 31, 2020
@msftgits msftgits added this to the 3.0 milestone Jan 31, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Dec 21, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-approved API was approved in API review, it can be implemented area-System.Runtime.CompilerServices Hackathon Issues picked for Hackathon help wanted [up-for-grabs] Good issue for external contributors
Projects
None yet
Development

No branches or pull requests