-
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
Entrypoint detection stages #19093
Entrypoint detection stages #19093
Changes from 1 commit
83c2f0d
ee1a204
412ca9e
d4234c1
e9d9041
8328ea4
06bf4a2
6d96516
aae7023
49154bd
f173a7b
56ad35b
ba23bae
6ffa8ab
b5968a6
0f43ac2
4ac1006
f6a9818
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1453,42 +1453,94 @@ private MethodSymbol FindEntryPoint(CancellationToken cancellationToken, out Imm | |
} | ||
} | ||
|
||
DiagnosticBag warnings = DiagnosticBag.GetInstance(); | ||
var viableEntryPoints = ArrayBuilder<MethodSymbol>.GetInstance(); | ||
var intOrVoidEntryPoints = ArrayBuilder<MethodSymbol>.GetInstance(); | ||
// Validity and diagnostics are also tracked because they must be conditionally handled | ||
// if there are not any "traditional" entrypoints found. | ||
var taskEntryPoints = ArrayBuilder<(bool IsValid, MethodSymbol Candidate, DiagnosticBag SpecificDiagnostics)>.GetInstance(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
|
||
// These diagnostics (warning only) are added to the compilation only if | ||
// there were not any main methods found. | ||
DiagnosticBag noMainFoundDiagnostics = DiagnosticBag.GetInstance(); | ||
|
||
foreach (var candidate in entryPointCandidates) | ||
{ | ||
if (!HasEntryPointSignature(candidate, warnings)) | ||
var perCandidateBag = DiagnosticBag.GetInstance(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
var (IsCandidate, IsTaskLike) = HasEntryPointSignature(candidate, perCandidateBag); | ||
|
||
if (IsCandidate && !IsTaskLike) | ||
{ | ||
// a single error for partial methods | ||
warnings.Add(ErrorCode.WRN_InvalidMainSig, candidate.Locations.First(), candidate); | ||
continue; | ||
intOrVoidEntryPoints.Add(candidate); | ||
} | ||
else if (IsTaskLike) | ||
{ | ||
taskEntryPoints.Add((IsCandidate, candidate, perCandidateBag)); | ||
} | ||
else | ||
{ | ||
noMainFoundDiagnostics.Add(ErrorCode.WRN_InvalidMainSig, candidate.Locations.First(), candidate); | ||
noMainFoundDiagnostics.AddRange(perCandidateBag); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps check
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks good! Done |
||
} | ||
|
||
var viableEntryPoints = ArrayBuilder<MethodSymbol>.GetInstance(); | ||
|
||
foreach (var candidate in intOrVoidEntryPoints) | ||
{ | ||
if (candidate.IsGenericMethod || candidate.ContainingType.IsGenericType) | ||
{ | ||
// a single error for partial methods: | ||
warnings.Add(ErrorCode.WRN_MainCantBeGeneric, candidate.Locations.First(), candidate); | ||
noMainFoundDiagnostics.Add(ErrorCode.WRN_MainCantBeGeneric, candidate.Locations.First(), candidate); | ||
continue; | ||
} | ||
|
||
// PROTOTYPE(async-main): CheckFeatureAvailability should be called on non-async methods | ||
// that return Task or Task<T> | ||
if (candidate.IsAsync) | ||
{ | ||
// PROTOTYPE(async-main): Get the diagnostic to point to a smaller syntax piece. | ||
// PROTOTYPE(async-main): Switch diagnostics around if the type is not Task or Task<T> | ||
CheckFeatureAvailability(candidate.GetNonNullSyntaxNode(), MessageID.IDS_FeatureAsyncMain, diagnostics); | ||
diagnostics.Add(ErrorCode.ERR_NonTaskMainCantBeAsync, candidate.Locations.First(), candidate); | ||
continue; | ||
} | ||
|
||
viableEntryPoints.Add(candidate); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could the body of this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
|
||
if ((object)mainType == null || viableEntryPoints.Count == 0) | ||
if (viableEntryPoints.IsEmpty()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
{ | ||
diagnostics.AddRange(warnings); | ||
foreach (var (IsValid, Candidate, SpecificDiagnostics) in taskEntryPoints) | ||
{ | ||
if (Candidate.IsGenericMethod || Candidate.ContainingType.IsGenericType) | ||
{ | ||
// a single error for partial methods: | ||
noMainFoundDiagnostics.Add(ErrorCode.WRN_MainCantBeGeneric, Candidate.Locations.First(), Candidate); | ||
continue; | ||
} | ||
|
||
if (!IsValid) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It feels like this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can these two There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @AlekseyTs: Yep, that makes sense. Done. @cston: Unless I'm misunderstanding you, I don't think that there would be much to gain from pulling this out into a local function and performing the check earlier would break the diagnostic guarantees |
||
{ | ||
noMainFoundDiagnostics.Add(ErrorCode.WRN_InvalidMainSig, Candidate.Locations.First(), Candidate); | ||
noMainFoundDiagnostics.AddRange(SpecificDiagnostics); | ||
continue; | ||
} | ||
|
||
// PROTOTYPE(async-main): Get the diagnostic to point to a smaller syntax piece. | ||
if (CheckFeatureAvailability(Candidate.GetNonNullSyntaxNode(), MessageID.IDS_FeatureAsyncMain, diagnostics)) | ||
{ | ||
diagnostics.AddRange(SpecificDiagnostics); | ||
viableEntryPoints.Add(Candidate); | ||
} | ||
} | ||
} | ||
|
||
warnings.Free(); | ||
if (viableEntryPoints.Count == 0) | ||
{ | ||
diagnostics.AddRange(noMainFoundDiagnostics); | ||
} | ||
else if ((object)mainType == null) | ||
{ | ||
// Only add warning diagnostics. The reason that Error diagnostics can end up in `noMainFoundDiagnostics` is when | ||
// HasEntryPointSignature yields some Error Diagnostics when people implement Task or Task<T> incorrectly. | ||
// | ||
// We can't add those Errors to the general diagnostics bag because it would break previously-working programs. | ||
// The fact that these warnings are not added when csc is invoked with /main is possibly a bug, and is tracked at | ||
// https://github.com/dotnet/roslyn/issues/18964 | ||
diagnostics.AddRange(noMainFoundDiagnostics.AsEnumerable().Where(d => d.Severity == DiagnosticSeverity.Warning)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Linq is banned in compilers. Also, if the point of this code is to preserve old behavior, then we should probably be more precise and add only WRN_MainCantBeGeneric and WRN_InvalidMainSig warnings. #Closed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Fixed.
Nah, the point is the emit errors where there were errors before, and emit warnings where there were warnings before. I don't care about emitting the same ones because it's possible to add more helpful diagnostics now. |
||
} | ||
|
||
MethodSymbol entryPoint = null; | ||
if (viableEntryPoints.Count == 0) | ||
|
@@ -1519,6 +1571,7 @@ private MethodSymbol FindEntryPoint(CancellationToken cancellationToken, out Imm | |
} | ||
|
||
viableEntryPoints.Free(); | ||
noMainFoundDiagnostics.Free(); | ||
return entryPoint; | ||
} | ||
finally | ||
|
@@ -1565,11 +1618,11 @@ internal bool ReturnsAwaitableToVoidOrInt(MethodSymbol method, DiagnosticBag dia | |
/// or <see cref="System.Threading.Tasks.Task{T}" /> where T is an int. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It looks like this comment is no longer accurate. #Closed |
||
/// - has either no parameter or a single parameter of type string[] | ||
/// </summary> | ||
private bool HasEntryPointSignature(MethodSymbol method, DiagnosticBag bag) | ||
private (bool IsCandidate, bool IsTaskLike) HasEntryPointSignature(MethodSymbol method, DiagnosticBag bag) | ||
{ | ||
if (method.IsVararg) | ||
{ | ||
return false; | ||
return (false, false); | ||
} | ||
|
||
TypeSymbol returnType = method.ReturnType; | ||
|
@@ -1580,45 +1633,45 @@ private bool HasEntryPointSignature(MethodSymbol method, DiagnosticBag bag) | |
returnsTaskOrTaskOfInt = ReturnsAwaitableToVoidOrInt(method, bag); | ||
if (!returnsTaskOrTaskOfInt) | ||
{ | ||
return false; | ||
return (false, false); | ||
} | ||
} | ||
|
||
// Prior to 7.1, async methods were considered to "have the entrypoint signature". | ||
// In order to keep back-compat, we need to let these through if compiling using a previous language version. | ||
if (!returnsTaskOrTaskOfInt && method.IsAsync && this.LanguageVersion >= LanguageVersion.CSharp7_1) | ||
{ | ||
return false; | ||
return (false, returnsTaskOrTaskOfInt); | ||
} | ||
|
||
if (method.RefKind != RefKind.None) | ||
{ | ||
return false; | ||
return (false, returnsTaskOrTaskOfInt); | ||
} | ||
|
||
if (method.Parameters.Length == 0) | ||
{ | ||
return true; | ||
return (true, returnsTaskOrTaskOfInt); | ||
} | ||
|
||
if (method.Parameters.Length > 1) | ||
{ | ||
return false; | ||
return (false, returnsTaskOrTaskOfInt); | ||
} | ||
|
||
if (!method.ParameterRefKinds.IsDefault) | ||
{ | ||
return false; | ||
return (false, returnsTaskOrTaskOfInt); | ||
} | ||
|
||
var firstType = method.Parameters[0].Type; | ||
if (firstType.TypeKind != TypeKind.Array) | ||
{ | ||
return false; | ||
return (false, returnsTaskOrTaskOfInt); | ||
} | ||
|
||
var array = (ArrayTypeSymbol)firstType; | ||
return array.IsSZArray && array.ElementType.SpecialType == SpecialType.System_String; | ||
return (array.IsSZArray && array.ElementType.SpecialType == SpecialType.System_String, returnsTaskOrTaskOfInt); | ||
} | ||
|
||
internal override bool IsUnreferencedAssemblyIdentityDiagnosticCode(int code) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1479,5 +1479,6 @@ internal enum ErrorCode | |
ERR_BadDynamicMethodArgDefaultLiteral = 9000, | ||
ERR_DefaultLiteralNotValid = 9001, | ||
WRN_DefaultInSwitch = 9002, | ||
ERR_NonTaskMainCantBeAsync = 9003, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Can we simply reuse ERR_MainCantBeAsync slot? #Closed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wasn't sure about this. Are error numbers relevant to back-compat? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm reusing the old number, but with a new name and message. |
||
} | ||
} |
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.
Does it have to be
Task<int>
though? #ClosedThere 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.
Not technically, but I think this is fine for an error message.
I'd be willing to change it, but "Async Main methods must return either Task or Task whereby the return type of GetAwaiter().GetResult() is either
void
orint
" seems a bit verbose for an error message.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 we can simply say that int or void returning Main cannot be async. #Closed
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.
+1 for "void returning Main cannot be async"
In reply to: 114608904 [](ancestors = 114608904)
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.
Changed.
In reply to: 114619543 [](ancestors = 114619543,114608904)