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

Main attributes for simple programs #59471

Closed

Conversation

chsienki
Copy link
Contributor

@chsienki chsienki commented Feb 11, 2022

Implements [main: ...] attributes for top level / simple programs.

Handles parsing, attribute lookup for SynthesizedSimpleProgramEntryPointSymbol, as well as some basic diagnostics like langver and disallowing in scripting.

Doesn't yet work for regular program entry points or issue diagnostics if you use [main: in a non executable program.

CSharpLang tracking issue: dotnet/csharplang#5045
Relates to test plan #57047

@chsienki chsienki requested a review from a team as a code owner February 11, 2022 00:44
@chsienki
Copy link
Contributor Author

@dotnet/roslyn-compiler @cston @333fred for review please

@AlekseyTs
Copy link
Contributor

@chsienki Please add a link to the .md file with detailed feature specification.

@@ -22,7 +22,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols
/// </summary>
internal abstract class SourceMethodSymbolWithAttributes : SourceMethodSymbol, IAttributeTargetSymbol
{
private CustomAttributesBag<CSharpAttributeData> _lazyCustomAttributesBag;
internal CustomAttributesBag<CSharpAttributeData> _lazyCustomAttributesBag;
Copy link
Contributor

@AlekseyTs AlekseyTs Feb 11, 2022

Choose a reason for hiding this comment

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

internal

Why internal? This feels very wrong. In general, any operations with "lazy" fields should be encapsulated in the type that declares them. #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops, this was supposed to be protected not internal.

Was just re-using the attribute bag for the derived symbol, but I can make a copy on the derived symbol instead.

Copy link
Contributor

Choose a reason for hiding this comment

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

but I can make a copy on the derived symbol instead.

I don't think that would be the right thing to do either. See my other comments

@@ -233,7 +233,7 @@ internal ReturnTypeWellKnownAttributeData GetDecodedReturnTypeWellKnownAttribute
/// <remarks>
/// Forces binding and decoding of attributes.
/// </remarks>
private CustomAttributesBag<CSharpAttributeData> GetAttributesBag()
internal virtual CustomAttributesBag<CSharpAttributeData> GetAttributesBag()
Copy link
Contributor

@AlekseyTs AlekseyTs Feb 11, 2022

Choose a reason for hiding this comment

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

internal virtual

This change looks suspicious. It feels that we simply should override

/// <summary>
/// Gets the syntax list of custom attributes that declares attributes for this method symbol.
/// </summary>
internal virtual OneOrMany<SyntaxList<AttributeListSyntax>> GetAttributeDeclarations()

for SynthesizedSimpleProgramEntryPointSymbol #Closed

/// <remarks>
/// Forces binding and decoding of attributes.
/// </remarks>
internal override CustomAttributesBag<CSharpAttributeData> GetAttributesBag()
Copy link
Contributor

@AlekseyTs AlekseyTs Feb 11, 2022

Choose a reason for hiding this comment

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

override

As I commented on initial declaration of this method, this change looks suspicious. #Closed

return _lazyCustomAttributesBag!;
}

AttributeLocation IAttributeTargetSymbol.DefaultAttributeLocation => AttributeLocation.Main;
Copy link
Contributor

Choose a reason for hiding this comment

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

DefaultAttributeLocation

It feels like AllowedAttributeLocations should also be implemented

return _lazyCustomAttributesBag!;
}

AttributeLocation IAttributeTargetSymbol.DefaultAttributeLocation => AttributeLocation.Main;
Copy link
Contributor

Choose a reason for hiding this comment

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

AttributeLocation.Main

This implementation feels suspicious. We are supposed to return "Location of an attribute if an explicit location is not specified via attribute target specification syntax." However, attributes without explicit "main" target shouldn't be applicable to this symbol. Perhaps the default location should be AttributeLocation.Assembly. That is what we use for attributes on assembly?

{
if (_lazyCustomAttributesBag == null || !_lazyCustomAttributesBag.IsSealed)
{
var mergedAttributes = ((SourceAssemblySymbol)this.ContainingAssembly).GetAttributeDeclarations();
Copy link
Contributor

Choose a reason for hiding this comment

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

var

Please spell out the type

{
if (_lazyCustomAttributesBag == null || !_lazyCustomAttributesBag.IsSealed)
{
var mergedAttributes = ((SourceAssemblySymbol)this.ContainingAssembly).GetAttributeDeclarations();
Copy link
Contributor

@AlekseyTs AlekseyTs Feb 11, 2022

Choose a reason for hiding this comment

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

GetAttributeDeclarations()

Note that we are creating a SynthesizedSimpleProgramEntryPointSymbol per each compilation unit that has top-level statements. It doesn't feel right to bind all "main" attributes for every symbol like that. It looks like dotnet/csharplang#5045 suggests to allow "main" targeted attributes in any file, then perhaps we should bind attributes only for symbol that is returned by

internal static SynthesizedSimpleProgramEntryPointSymbol? GetSimpleProgramEntryPoint(CSharpCompilation compilation)
``` #Closed

@@ -244,6 +244,7 @@ internal enum MessageID

IDS_FeatureCacheStaticMethodGroupConversion = MessageBase + 12816,
IDS_FeatureRawStringLiterals = MessageBase + 12817,
IDS_FeatureMainAttrLoc = MessageBase + 12818,
Copy link
Contributor

@AlekseyTs AlekseyTs Feb 11, 2022

Choose a reason for hiding this comment

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

AttrLoc

Consider using full words #Closed

@@ -3,6 +3,7 @@ Microsoft.CodeAnalysis.CSharp.SyntaxKind.InterpolatedRawStringEndToken = 9082 ->
Microsoft.CodeAnalysis.CSharp.SyntaxKind.InterpolatedSingleLineRawStringStartToken = 9080 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind
Microsoft.CodeAnalysis.CSharp.SyntaxKind.MultiLineRawStringLiteralToken = 9073 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind
Microsoft.CodeAnalysis.CSharp.SyntaxKind.SingleLineRawStringLiteralToken = 9072 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind
Microsoft.CodeAnalysis.CSharp.SyntaxKind.MainKeyword = 8447 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind
Copy link
Contributor

@AlekseyTs AlekseyTs Feb 11, 2022

Choose a reason for hiding this comment

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

Since we are working in a feature branch, consider placing additions at the end. That is going to simplify merges. #Closed

@AlekseyTs
Copy link
Contributor

            if (ContainingSymbol is SourceMemberContainerTypeSymbol { AnyMemberHasAttributes: false })

It looks like implementation of this property should be adjusted to take "main" attributes into account


Refers to: src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs:384 in a07f5e0. [](commit_id = a07f5e0, deletion_comment = False)

@@ -556,6 +556,78 @@ public void TestMultipleGlobalAttributeDeclarations()
Assert.NotEqual(default, ad.CloseBracketToken);
}

[Fact]
public void TestGlobalMainAttribute()
Copy link
Contributor

Choose a reason for hiding this comment

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

TestGlobalMainAttribute

I think it would be better to use test infrastructure that allows to assert the shape of the tree by using tree-like baseline. See test at the end of this file, for example.

@@ -556,6 +556,78 @@ public void TestMultipleGlobalAttributeDeclarations()
Assert.NotEqual(default, ad.CloseBracketToken);
}

[Fact]
public void TestGlobalMainAttribute()
Copy link
Contributor

Choose a reason for hiding this comment

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

TestGlobalMainAttribute

Are we verifying lack of diagnostics for success scenarios?

public void TestGlobalMainAttribute_LangVersion()
{
var text = "[main:a]";
var file = this.ParseFile(text, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp10));
Copy link
Contributor

Choose a reason for hiding this comment

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

CSharp10

I think we should also have a test targeting RegularNext version and assert no errors for it.

@@ -5043,6 +5043,228 @@ class C<T>
});
}

[Fact]
public void TestMainAttributes()
Copy link
Contributor

Choose a reason for hiding this comment

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

TestMainAttributes

I would put all top-level statements tests to TopLevelStatementsTests

Copy link
Contributor

Choose a reason for hiding this comment

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

Alternatively we could have a dedicated file for all "main" related tests.

attribute.VerifyValue(0, TypedConstantKind.Primitive, "one");
Assert.Equal(@"MyAttribute(""one"")", attribute.ToString());

CompileAndVerify(source, expectedOutput: "Hello World");
Copy link
Contributor

Choose a reason for hiding this comment

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

CompileAndVerify

We should verify presence of the attribute in metadata.

");

CompileAndVerify(source, expectedOutput: "Hello World")
.VerifyTypeIL("Program", @"
Copy link
Contributor

Choose a reason for hiding this comment

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

VerifyTypeIL

I think it is an overkill to use IL to confirm presense of the attribute in metadata. IL includes to much of unrelated information and is hard to verify. I would use symbolVerifier to locate the method symbol and then check GetAttributes() for it.

}

[ConditionalFact(typeof(CoreClrOnly))]
public void TestMainAttributes_IL()
Copy link
Contributor

Choose a reason for hiding this comment

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

TestMainAttributes_IL

It feels like this unit test can be merged with the previous one, there is really no much benefit in splitting source and metadata validation.

verifyAttribute("two"),
verifyAttribute("three"));

CompileAndVerify(source, expectedOutput: "Hello World");
Copy link
Contributor

Choose a reason for hiding this comment

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

CompileAndVerify

Please verify attributes in metadata. This is applicable to all success cases.

[main: My(""one"")]
public class MyAttribute : Attribute { public MyAttribute(string name) {} }
");
source.VerifyDiagnostics(
Copy link
Contributor

Choose a reason for hiding this comment

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

source.VerifyDiagnostics

Please assert attributes on the symbol.

public class MyAttribute : Attribute {{ public MyAttribute(string name) {{}} }}
");

source.VerifyDiagnostics(valid ? Array.Empty<DiagnosticDescription>() :
Copy link
Contributor

Choose a reason for hiding this comment

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

source.VerifyDiagnostics

Please assert attributes on the symbol.

using System;

[main: My(""one"")]
[assembly: My(""two"")]
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it will be good to test where an attribute without an explicit target is going to end up.

@AlekseyTs
Copy link
Contributor

It looks like there are legitimate test failures.

CompileAndVerify(source, expectedOutput: "Hello World");
}

[ConditionalFact(typeof(CoreClrOnly))]
Copy link
Contributor

Choose a reason for hiding this comment

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

[ConditionalFact(typeof(CoreClrOnly))]

We should be able to test metadata on all platforms. There is no requirement to test IL for that. In fact symbol validation is more robust.

@AlekseyTs
Copy link
Contributor

Done with review pass (commit 7)

@AlekseyTs AlekseyTs closed this Feb 11, 2022
@AlekseyTs AlekseyTs reopened this Feb 11, 2022
@AlekseyTs
Copy link
Contributor

Sorry, closed by mistake. Reopened.

@chsienki chsienki marked this pull request as draft February 15, 2022 19:50
@@ -6986,4 +6986,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ERR_LineContainsDifferentWhitespace" xml:space="preserve">
<value>Line contains different whitespace than the closing line of the raw string literal: '{0}' versus '{1}'</value>
</data>
<data name="IDS_FeatureMainAttrLoc" xml:space="preserve">
<value>main as an attribute target specifier</value>
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
<value>main as an attribute target specifier</value>
<value>'main' as an attribute target specifier</value>

@chsienki
Copy link
Contributor Author

@dotnet/roslyn-compiler PR is updated and ready for re-review. Accompanying speclet is here: dotnet/csharplang#5817

@chsienki chsienki marked this pull request as ready for review February 23, 2022 01:02
@@ -302,7 +302,7 @@ private CustomAttributesBag<CSharpAttributeData> GetAttributesBag(ref CustomAttr
/// <summary>
/// Gets the attributes applied on this symbol.
/// Returns an empty array if there are no attributes.
/// </summary>
/// </summary>
Copy link
Contributor

Choose a reason for hiding this comment

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

///

It looks like there are no meaningful changes in this file. Consider reverting.

@chsienki
Copy link
Contributor Author

chsienki commented Mar 3, 2022

@cston and @333fred for review please.

Copy link
Member

@333fred 333fred left a comment

Choose a reason for hiding this comment

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

Done review pass (commit 15)

attrLocation = CheckFeatureAvailability(attrLocation, MessageID.IDS_FeatureModuleAttrLoc);
}
AttributeLocation.Module => CheckFeatureAvailability(attrLocation, MessageID.IDS_FeatureModuleAttrLoc),
AttributeLocation.Main => CheckFeatureAvailability(attrLocation, MessageID.IDS_FeatureMainAttributeLocation),
Copy link
Member

Choose a reason for hiding this comment

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

We generally want to prefer doing semantic checks for these types of things if reasonable to do.

// if we're the entry point that will ultimately be selected.
if (this == GetSimpleProgramEntryPoint(DeclaringCompilation))
{
return OneOrMany.Create(((SourceAssemblySymbol)ContainingAssembly).GetAttributeDeclarations());
Copy link
Member

Choose a reason for hiding this comment

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

Where is the later filtering on these for only main declarations?

};
return methods.Cast<MethodSymbol>().ToImmutableArray();
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Please add some test cases showing local function interactions, something like this scenario:

[main: My]
void M() {}

@jcouv
Copy link
Member

jcouv commented Mar 16, 2022

Moved to draft since LDM moved the feature to backlog (notes).

@jcouv jcouv marked this pull request as draft March 16, 2022 22:04
@chsienki chsienki closed this Sep 6, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants