提交 cc4ea469 编写于 作者: T Ty Overby 提交者: GitHub

Entrypoint detection stages (#19093)

* simplify partial method return type syntax finder tests

* Bug fixes and API use changes

* address first review

* better comment and compiler trait on class

* fix tests, pull entrypoint check out into local function

* unify diagnostics for intvoid or task mains

* add more tests, change error text

* address review

* finish tests

* be explicit on language version in tests

* add another test, fix typo, add diagnostics even when they aren't likely to exist

* rewrite error->warning code, add more cases to tests

* change test language versions

* formatting on comments and adding WithLocation to tests

* fix formatting for some tests

* fix some formatting and added withlocation

* fix withlocation
上级 f59086a4
......@@ -2060,12 +2060,12 @@ private AssemblySymbol GetForwardedToAssembly(string fullName, int arity, Diagno
return null;
}
internal static void CheckFeatureAvailability(SyntaxNode syntax, MessageID feature, DiagnosticBag diagnostics, Location locationOpt = null)
internal static bool CheckFeatureAvailability(SyntaxNode syntax, MessageID feature, DiagnosticBag diagnostics, Location locationOpt = null)
{
var options = (CSharpParseOptions)syntax.SyntaxTree.Options;
if (options.IsFeatureEnabled(feature))
{
return;
return true;
}
var location = locationOpt ?? syntax.GetLocation();
......@@ -2073,7 +2073,7 @@ internal static void CheckFeatureAvailability(SyntaxNode syntax, MessageID featu
if (requiredFeature != null)
{
diagnostics.Add(ErrorCode.ERR_FeatureIsExperimental, location, feature.Localize(), requiredFeature);
return;
return false;
}
LanguageVersion availableVersion = options.LanguageVersion;
......@@ -2081,7 +2081,10 @@ internal static void CheckFeatureAvailability(SyntaxNode syntax, MessageID featu
if (requiredVersion > availableVersion)
{
diagnostics.Add(availableVersion.GetErrorCode(), location, feature.Localize(), new CSharpRequiredLanguageVersion(requiredVersion));
return false;
}
return true;
}
}
}
\ No newline at end of file
......@@ -6883,6 +6883,17 @@ internal class CSharpResources {
}
}
/// <summary>
/// Looks up a localized string similar to Async Main methods must return Task or Task&lt;int&gt;.
/// </summary>
internal static string ERR_NonTaskMainCantBeAsync
{
get
{
return ResourceManager.GetString("ERR_NonTaskMainCantBeAsync", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cannot embed interop types from assembly &apos;{0}&apos; because it is missing the &apos;{1}&apos; attribute..
/// </summary>
......@@ -9866,8 +9877,10 @@ internal class CSharpResources {
/// /t:winexe)
/// /target:library [rest of string was truncated]&quot;;.
/// </summary>
internal static string IDS_CSCHelp {
get {
internal static string IDS_CSCHelp
{
get
{
return ResourceManager.GetString("IDS_CSCHelp", resourceCulture);
}
}
......
......@@ -3596,9 +3596,6 @@ Give the compiler some way to differentiate the methods. For example, you can gi
<data name="ERR_SecurityCriticalOrSecuritySafeCriticalOnAsyncInClassOrStruct" xml:space="preserve">
<value>Async methods are not allowed in an Interface, Class, or Structure which has the 'SecurityCritical' or 'SecuritySafeCritical' attribute.</value>
</data>
<data name="ERR_MainCantBeAsync" xml:space="preserve">
<value>'{0}': an entry point cannot be marked with the 'async' modifier</value>
</data>
<data name="ERR_BadAwaitInQuery" xml:space="preserve">
<value>The 'await' operator may only be used in a query expression within the first collection expression of the initial 'from' clause or within the collection expression of a 'join' clause</value>
</data>
......@@ -5073,4 +5070,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ERR_VoidInTuple" xml:space="preserve">
<value>A tuple may not contain a value of type 'void'.</value>
</data>
<data name="ERR_NonTaskMainCantBeAsync" xml:space="preserve">
<value>A void or int returning entry point cannot be async</value>
</data>
</root>
......@@ -1453,42 +1453,101 @@ private MethodSymbol FindEntryPoint(CancellationToken cancellationToken, out Imm
}
}
DiagnosticBag warnings = DiagnosticBag.GetInstance();
var viableEntryPoints = ArrayBuilder<MethodSymbol>.GetInstance();
foreach (var candidate in entryPointCandidates)
// 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();
// These diagnostics (warning only) are added to the compilation only if
// there were not any main methods found.
DiagnosticBag noMainFoundDiagnostics = DiagnosticBag.GetInstance();
bool CheckValid(MethodSymbol candidate, bool isCandidate, DiagnosticBag specificDiagnostics)
{
if (!HasEntryPointSignature(candidate, warnings))
if (!isCandidate)
{
// a single error for partial methods
warnings.Add(ErrorCode.WRN_InvalidMainSig, candidate.Locations.First(), candidate);
continue;
noMainFoundDiagnostics.Add(ErrorCode.WRN_InvalidMainSig, candidate.Locations.First(), candidate);
noMainFoundDiagnostics.AddRange(specificDiagnostics);
return false;
}
if (candidate.IsGenericMethod || candidate.ContainingType.IsGenericType)
{
// a single error for partial methods:
warnings.Add(ErrorCode.WRN_MainCantBeGeneric, candidate.Locations.First(), candidate);
continue;
noMainFoundDiagnostics.Add(ErrorCode.WRN_MainCantBeGeneric, candidate.Locations.First(), candidate);
return false;
}
return true;
}
var viableEntryPoints = ArrayBuilder<MethodSymbol>.GetInstance();
foreach (var candidate in entryPointCandidates)
{
var perCandidateBag = DiagnosticBag.GetInstance();
var (IsCandidate, IsTaskLike) = HasEntryPointSignature(candidate, perCandidateBag);
// PROTOTYPE(async-main): CheckFeatureAvailability should be called on non-async methods
// that return Task or Task<T>
if (IsTaskLike)
{
taskEntryPoints.Add((IsCandidate, candidate, perCandidateBag));
}
else
{
if (CheckValid(candidate, IsCandidate, perCandidateBag))
{
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);
}
else
{
diagnostics.AddRange(perCandidateBag);
viableEntryPoints.Add(candidate);
}
}
perCandidateBag.Free();
}
}
if ((object)mainType == null || viableEntryPoints.Count == 0)
if (viableEntryPoints.Count == 0)
{
foreach (var (IsValid, Candidate, SpecificDiagnostics) in taskEntryPoints)
{
diagnostics.AddRange(warnings);
// PROTOTYPE(async-main): Get the diagnostic to point to a smaller syntax piece.
if (CheckValid(Candidate, IsValid, SpecificDiagnostics) &&
CheckFeatureAvailability(Candidate.GetNonNullSyntaxNode(), MessageID.IDS_FeatureAsyncMain, diagnostics))
{
diagnostics.AddRange(SpecificDiagnostics);
viableEntryPoints.Add(Candidate);
}
}
}
foreach (var (_, _, SpecificDiagnostics) in taskEntryPoints)
{
SpecificDiagnostics.Free();
}
warnings.Free();
if (viableEntryPoints.Count == 0)
{
diagnostics.AddRange(noMainFoundDiagnostics);
}
else if ((object)mainType == null)
{
// Filters out diagnostics so that only InvalidMainSig and MainCant'BeGeneric are left.
// 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
foreach (var diagnostic in noMainFoundDiagnostics.AsEnumerable())
{
if (diagnostic.Code == (int)ErrorCode.WRN_InvalidMainSig || diagnostic.Code == (int)ErrorCode.WRN_MainCantBeGeneric)
{
diagnostics.Add(diagnostic);
}
}
}
MethodSymbol entryPoint = null;
if (viableEntryPoints.Count == 0)
......@@ -1518,7 +1577,9 @@ private MethodSymbol FindEntryPoint(CancellationToken cancellationToken, out Imm
entryPoint = viableEntryPoints[0];
}
taskEntryPoints.Free();
viableEntryPoints.Free();
noMainFoundDiagnostics.Free();
return entryPoint;
}
finally
......@@ -1561,15 +1622,16 @@ internal bool ReturnsAwaitableToVoidOrInt(MethodSymbol method, DiagnosticBag dia
/// <summary>
/// Checks if the method has an entry point compatible signature, i.e.
/// - the return type is either void, int, <see cref="System.Threading.Tasks.Task" />,
/// or <see cref="System.Threading.Tasks.Task{T}" /> where T is an int.
/// - the return type is either void, int, or returns a <see cref="System.Threading.Tasks.Task" />,
/// or <see cref="System.Threading.Tasks.Task{T}" /> where the return type of GetAwaiter().GetResult()
/// is either void or int.
/// - 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 +1642,38 @@ 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;
}
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)
......
......@@ -1104,7 +1104,7 @@ internal enum ErrorCode
ERR_VarargsAsync = 4006,
ERR_ByRefTypeAndAwait = 4007,
ERR_BadAwaitArgVoidCall = 4008,
// ERR_MainCantBeAsync = 4009,
ERR_NonTaskMainCantBeAsync = 4009,
ERR_CantConvAsyncAnonFuncReturns = 4010,
ERR_BadAwaiterPattern = 4011,
ERR_BadSpecialByRefLocal = 4012,
......
......@@ -388,7 +388,6 @@ internal override BoundBlock CreateBody()
{
var syntax = _userMainReturnTypeSyntax;
if (ReturnsVoid)
{
return new BoundBlock(
......
......@@ -3403,7 +3403,7 @@ public static int Main()
}
[Fact, WorkItem(547081, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/547081")]
public void Repro_17885()
public void Repro_17885_CSharp_71()
{
var source = @"
using System.Threading.Tasks;
......@@ -3416,7 +3416,7 @@ async public static Task Main()
CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)).VerifyDiagnostics(
// (5,30): warning CS1998: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
// async public static Task Main()
Diagnostic(ErrorCode.WRN_AsyncLacksAwaits, "Main"));
Diagnostic(ErrorCode.WRN_AsyncLacksAwaits, "Main").WithLocation(5, 30));
}
[Fact, WorkItem(547081, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/547081")]
......@@ -3433,12 +3433,14 @@ async public static Task Main()
CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)).VerifyDiagnostics(
// (5,30): warning CS1998: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
// async public static Task Main()
Diagnostic(ErrorCode.WRN_AsyncLacksAwaits, "Main"),
Diagnostic(ErrorCode.WRN_AsyncLacksAwaits, "Main").WithLocation(5, 30),
// (5,5): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater.
// async public static Task Main()
Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7, @"async public static Task Main()
{
}").WithArguments("async main", "7.1").WithLocation(5, 5));
}").WithArguments("async main", "7.1").WithLocation(5, 5),
// error CS5001: Program does not contain a static 'Main' method suitable for an entry point
Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1));
}
[Fact, WorkItem(547088, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/547088")]
......
......@@ -5,6 +5,7 @@
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Emit;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Text;
......@@ -222,12 +223,10 @@ public partial class A {
var returnSyntax = m.ExtractReturnTypeSyntax();
var tree = comp.SyntaxTrees.Single();
var model = comp.GetSemanticModel(tree);
var voidTokens = tree.GetRoot().DescendantTokens().Where(t => t.Kind() == SyntaxKind.VoidKeyword).ToList();
var node = tree.GetRoot().FindNode(voidTokens[0].Span);
var node = tree.GetRoot().DescendantNodes().OfType<PredefinedTypeSyntax>().Where(n => n.Keyword.Kind() == SyntaxKind.VoidKeyword).First();
var mLocations = tree.GetRoot().DescendantTokens().Where(t => t.ValueText == "M").ToList();
var otherSymbol = (MethodSymbol)model.GetDeclaredSymbolForNode(tree.GetRoot().FindNode(mLocations[1].Span));
var otherSymbol = m.PartialImplementationPart;
Assert.True(otherSymbol.IsPartialImplementation());
Assert.Equal(node, returnSyntax);
Assert.Equal(node, otherSymbol.ExtractReturnTypeSyntax());
......@@ -252,12 +251,10 @@ public partial class A {
var returnSyntax = m.ExtractReturnTypeSyntax();
var tree = comp.SyntaxTrees.Single();
var model = comp.GetSemanticModel(tree);
var token = tree.GetRoot().DescendantTokens().Where(t => t.Kind() == SyntaxKind.VoidKeyword).Skip(1).First();
var node = tree.GetRoot().FindNode(token.Span);
var node = tree.GetRoot().DescendantNodes().OfType<PredefinedTypeSyntax>().Where(n => n.Keyword.Kind() == SyntaxKind.VoidKeyword).Last();
var mLocations = tree.GetRoot().DescendantTokens().Where(t => t.ValueText == "M").ToList();
var otherSymbol = (MethodSymbol)model.GetDeclaredSymbolForNode(tree.GetRoot().FindNode(mLocations[0].Span));
var otherSymbol = m.PartialImplementationPart;
Assert.True(otherSymbol.IsPartialImplementation());
Assert.Equal(node, returnSyntax);
Assert.Equal(node, otherSymbol.ExtractReturnTypeSyntax());
......@@ -279,8 +276,7 @@ public void PartialExtractSyntaxLocation_OnlyDef()
var returnSyntax = m.ExtractReturnTypeSyntax();
var tree = comp.SyntaxTrees.Single().GetRoot();
var token = tree.DescendantTokens().Where(t => t.Kind() == SyntaxKind.VoidKeyword).First();
var node = tree.FindNode(token.Span);
var node = tree.DescendantNodes().OfType<PredefinedTypeSyntax>().Where(n => n.Keyword.Kind() == SyntaxKind.VoidKeyword).Single();
Assert.Equal(node, returnSyntax);
}
......@@ -301,8 +297,7 @@ public void PartialExtractSyntaxLocation_OnlyDecl()
var returnSyntax = m.ExtractReturnTypeSyntax();
var tree = comp.SyntaxTrees.Single().GetRoot();
var token = tree.DescendantTokens().Where(t => t.Kind() == SyntaxKind.VoidKeyword).First();
var node = tree.FindNode(token.Span);
var node = tree.DescendantNodes().OfType<PredefinedTypeSyntax>().Where(n => n.Keyword.Kind() == SyntaxKind.VoidKeyword).Single();
Assert.Equal(node, returnSyntax);
}
......
......@@ -20,5 +20,6 @@ public enum CompilerFeature
OutVar,
Patterns,
DefaultLiteral,
AsyncMain,
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册