-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Function pointer calling convention IDE support #59062
Conversation
...ces/CSharp/Portable/Classification/SyntaxClassification/CSharpSyntaxClassificationService.cs
Outdated
Show resolved
Hide resolved
...p/Portable/Classification/SyntaxClassification/FunctionPointerCallingConventionClassifier.cs
Outdated
Show resolved
Hide resolved
var callingConventionSyntax = (FunctionPointerUnmanagedCallingConventionSyntax)syntax; | ||
if (CSharpSyntaxFacts.Instance.IsSpecialUnmanagedCallingConvention(callingConventionSyntax)) | ||
{ | ||
result.Add(new ClassifiedSpan(callingConventionSyntax.Span, ClassificationTypeNames.ClassName)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned on Discord, this might actually be more appropriate to be highlighted as a keyword rather than a class, given the compiler is not looking up any type, and there's no associated type symbol here. As discussed with Fred, these identifiers here are effectively "a sort of contextual keyword". Related, we might want to update the coloring for this case in the tooltip as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@CyrusNajmabadi What do you think about classifying as a keyword?
Also I've been looking about the logic that handles function pointers in quick info but can't find where it's. Can you point me out? (specifically, going to definition from quick info already works if only if the compiler is using the type from corlib - where is this logic?) (I think I figured out this. The question is whether it should be classified as a keyword).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i am fine with this being classified as a class
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't that incorrect here? As per our conversation with Fred yesterday (who said he'd be fine with these being classified as a keyword), those are more akin to keywords than classes, given the compiler does not look the types at all. If we classified those as classes, we'd both (1) have an inconsistency in that no symbol info would be available (eg. in quick info), and (2) not actually mirror what the compiler is doing at all, as it's not actually using or looking up those types at all. According to the ECMA spec those calling conventions here are just special symbols, not classes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't that incorrect here?
This is classification. There is no concept of 'correctness' :)
According to the ECMA spec those calling conventions here are just special symbols, not classes.
That's fine. I'm still ok with it being classified in this fashion. Classification is not hte compiler. It's whatever we want it to be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"You keep attacking me as if you think I'm saying let's do a worse solution"
It was absolutely not my intention to attack you (I mean why would I ever want to do that Cyrus?), so I'm really sorry if my messages came across that way. I was just genuinely confused about why the proposed changes were being an issue for you, is all. I do recognize that you just had a different view on the topic, and I was actively trying to understand that better. Nothing of what I said was ever meant to be an attack towards you at all, same as with all other times we happened to disagree on things before as well, of course. I do understand your point better now that you've listed those specific aspects related to complexity and maintainability 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It keeps compiler and ide classification in sync.
If this point is essential, then I think the key point to get to an agreement is to first define what's the expected output of the compiler API (especially that it has effect on the public ToDisplayParts
method).
You keep attacking me as of you think I'm saying let's do a worse solution
Absolutely sorry if it felt that way, I definitely didn't mean that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, 'attack' was too strong a word. I likely wasn't making my thoughts very clear (i tend to be juggling like 15 things at at a time :)).
If this point is essential, then I think the key point to get to an agreement is to first define what's the expected output of the compiler API (especially that it has effect on the public ToDisplayParts method).
Yes, i agree with that. If the compiler goes that way, i'm ok keepign IDE in sync.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll leave you decide which approach is the best, just happy we could clarify the misunderstanding here ❤️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The compiler will not give a type for a single word of Stdcall
, Thiscall
, Cdecl
, or Fastcall
. Regardless of whether we decide to return symbols for other combinations of those those words or other calling conventions, we specifically do not look at symbols for those cases. They do not correspond to types and are not emitted as modreqs on the function pointer.
public static bool IsSpecialUnmanagedCallingConvention(FunctionPointerUnmanagedCallingConventionSyntax syntax) | ||
{ | ||
if (syntax.Parent is not FunctionPointerUnmanagedCallingConventionListSyntax list) | ||
{ | ||
return false; | ||
} | ||
|
||
return list.CallingConventions.Count == 1 && | ||
syntax.Name.ValueText is "Cdecl" or "Stdcall" or "Thiscall" or "Fastcall"; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth adding a comment to spec and/or relevant part of compiler implementation?
src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ICompilationExtensions.cs
Outdated
Show resolved
Hide resolved
...paces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs
Outdated
Show resolved
Hide resolved
...paces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs
Outdated
Show resolved
Hide resolved
Co-authored-by: CyrusNajmabadi <[email protected]>
…3/Roslyn into func-pointer-conv-ide
case SignatureCallingConvention.CDecl: | ||
builder.Add(CreatePart(SymbolDisplayPartKind.ClassName, symbol, "Cdecl")); | ||
builder.Add(CreatePart(SymbolDisplayPartKind.Keyword, symbol, "Cdecl")); | ||
break; | ||
case SignatureCallingConvention.StdCall: | ||
builder.Add(CreatePart(SymbolDisplayPartKind.ClassName, symbol, "Stdcall")); | ||
builder.Add(CreatePart(SymbolDisplayPartKind.Keyword, symbol, "Stdcall")); | ||
break; | ||
case SignatureCallingConvention.ThisCall: | ||
builder.Add(CreatePart(SymbolDisplayPartKind.ClassName, symbol, "Thiscall")); | ||
builder.Add(CreatePart(SymbolDisplayPartKind.Keyword, symbol, "Thiscall")); | ||
break; | ||
case SignatureCallingConvention.FastCall: | ||
builder.Add(CreatePart(SymbolDisplayPartKind.ClassName, symbol, "Fastcall")); | ||
builder.Add(CreatePart(SymbolDisplayPartKind.Keyword, symbol, "Fastcall")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those changes are needed for the Quick Info in IDE to show them as keywords instead of classes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need to change this? why not just make the IDE side match the logic here and classify these as classnames? i don't get why we feel so strongly these need to be keywords.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@CyrusNajmabadi It was discussed on Discord and @333fred was concerned about classifying as class names when the compiler isn't actually using the class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like ClassName would be a strange choice. These are contextual keywords, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JoeRobich They're not contextual keywords per se. They're "special identifiers" that I can't think of anything similar to them in the language. There is no SyntaxKind.CdeclKeyword
for example.
They're "hard-coded" in the compiler:
roslyn/src/Compilers/CSharp/Portable/Symbols/FunctionPointers/FunctionPointerMethodSymbol.cs
Lines 147 to 154 in 7ba92d6
case { CallingConventions: { Count: 1 } specifiers }: | |
return specifiers[0].Name switch | |
{ | |
// Special identifiers cases | |
{ ValueText: "Cdecl" } => CallingConvention.CDecl, | |
{ ValueText: "Stdcall" } => CallingConvention.Standard, | |
{ ValueText: "Thiscall" } => CallingConvention.ThisCall, | |
{ ValueText: "Fastcall" } => CallingConvention.FastCall, |
To an end-user, they make look like contextual keywords since it's very similar. In fact, I think they could have been implemented as contextual keywords (but it's going to be a bit more complex implementation-wise for nothing).
var callingConventionSyntax = (FunctionPointerUnmanagedCallingConventionSyntax)syntax; | ||
if (callingConventionSyntax.IsSpecialUnmanagedCallingConvention()) | ||
{ | ||
result.Add(new ClassifiedSpan(callingConventionSyntax.Span, ClassificationTypeNames.Keyword)); | ||
return; | ||
} | ||
|
||
var type = semanticModel.Compilation.UnmanagedCallingConventionType(callingConventionSyntax.Name.ValueText); | ||
if (type is not null) | ||
{ | ||
result.Add(new ClassifiedSpan(callingConventionSyntax.Name.Span, ClassificationTypeNames.ClassName)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var callingConventionSyntax = (FunctionPointerUnmanagedCallingConventionSyntax)syntax; | |
if (callingConventionSyntax.IsSpecialUnmanagedCallingConvention()) | |
{ | |
result.Add(new ClassifiedSpan(callingConventionSyntax.Span, ClassificationTypeNames.Keyword)); | |
return; | |
} | |
var type = semanticModel.Compilation.UnmanagedCallingConventionType(callingConventionSyntax.Name.ValueText); | |
if (type is not null) | |
{ | |
result.Add(new ClassifiedSpan(callingConventionSyntax.Name.Span, ClassificationTypeNames.ClassName)); | |
} | |
var callingConventionSyntax = (FunctionPointerUnmanagedCallingConventionSyntax)syntax; | |
if (callingConventionSyntax.IsSpecialUnmanagedCallingConvention() || | |
semanticModel.Compilation.UnmanagedCallingConventionType(callingConventionSyntax.Name.ValueText) != null); | |
{ | |
result.Add(new ClassifiedSpan(callingConventionSyntax.Name.Span, ClassificationTypeNames.ClassName)); | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@CyrusNajmabadi This suggestion doesn't match the original intent.
I think we first need to settle on what behavior the IDE should have.
@@ -559,16 +559,16 @@ void visitFunctionPointerSignature(IMethodSymbol symbol) | |||
switch (symbol.CallingConvention) | |||
{ | |||
case SignatureCallingConvention.CDecl: | |||
builder.Add(CreatePart(SymbolDisplayPartKind.ClassName, symbol, "Cdecl")); | |||
builder.Add(CreatePart(SymbolDisplayPartKind.Keyword, symbol, "Cdecl")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I should pass a null
symbol for these if the team agreed they should be keywords.
@333fred @CyrusNajmabadi Please let me know how I should proceed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i would honestly just revert the compielr change, and do everything on the IDE side. i feel like we've spent an inordinate amount of time on something absolutely trivial and this is going in circles. let's make this simple and easy:
- no compiler changes. that definitely simplifies things.
- make the IDE operate in line with teh compiler today. also very simple on the IDE side.
bing bang boom.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@CyrusNajmabadi Alright. Will do that. I may take few days since my next exam is in few days.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@CyrusNajmabadi I'm now always classifying as class. Can you review please? Thanks!
...p/Portable/Classification/SyntaxClassification/FunctionPointerCallingConventionClassifier.cs
Outdated
Show resolved
Hide resolved
@@ -289,6 +289,10 @@ private static bool IsVerbatimStringToken(SyntaxToken token) | |||
{ | |||
return ClassificationTypeNames.LabelName; | |||
} | |||
else if (token.Parent.IsKind(SyntaxKind.FunctionPointerUnmanagedCallingConvention)) | |||
{ | |||
return ClassificationTypeNames.ClassName; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't we want to do this if GetCallingConventionSymbol returns non-null?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
e.g. if you write delegate[Gibberish]
we wouldn't classify that guy :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@CyrusNajmabadi This matches the compiler classification :)
roslyn/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Members.cs
Line 588 in eb95c2f
builder.Add(CreatePart(SymbolDisplayPartKind.ClassName, conventionType, conventionType.Name[CallConvLength..])); |
Quick info relies on compiler classification too:
|
||
public static INamedTypeSymbol? UnmanagedCallingConventionType(this Compilation compilation, string callConv) | ||
{ | ||
var corLibrary = compilation.GetSpecialType(SpecialType.System_Object).ContainingAssembly; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❔ Is this a requirement?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sharwell At compiler-side, yes.
roslyn/src/Compilers/CSharp/Portable/Symbols/FunctionPointers/FunctionPointerMethodSymbol.cs
Line 210 in eb95c2f
specifierType = compilation.Assembly.CorLibrary.LookupTopLevelMetadataType(ref metadataName, digThroughForwardedTypes: false); |
Hey @CyrusNajmabadi, could you take another look at this and check out the replies to your review notes when you have some time? It would be awesome to finally resolve this and get it merged for the next release. Thank you! 😄 |
@CyrusNajmabadi small ping, could we get another review on this one to confirm whether all concerns were addressed? |
|
||
public static INamedTypeSymbol? UnmanagedCallingConventionType(this Compilation compilation, string callConv) | ||
{ | ||
var corLibrary = compilation.GetSpecialType(SpecialType.System_Object).ContainingAssembly; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I remember @jkoritzinsky mentioning how this API could be particularly slow, so much so they tried to avoid using it entirely in the COM generators. Is that still a concern (and if so, should we consider using an alternative here), or has that been fixed now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Sergio0694 To my knowledge GetSpecialType is fast, and internally it should be calculated once, and then the lookup is a simple fast array index (not even a Dictionary<TKey, TValue>
).
Not sure how it caused issues in generators. I'd love to know more about that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a perf bug in Roslyn that made this call very slow a few months back. It's fixed now.
I'm not even sure what is the "exact" behavior we would like. I'd let @CyrusNajmabadi drive this to completion whenever he gets time. |
@CyrusNajmabadi friendly ping about this as you asked 😄 |
pr would need to be brought up to date. moving to draft in the meantime. |
@Youssef1313 would you have time to rebase/update this whenever you get a chance? 🙂 |
This was already fixed. I opened #70773 for fixing go to def |
Fixes #59052
@Sergio0694