diff --git a/docs/features/async-main.md b/docs/features/async-main.md new file mode 100644 index 0000000000000..b98068cd8c603 --- /dev/null +++ b/docs/features/async-main.md @@ -0,0 +1,12 @@ +# Async Task Main +## [dotnet/csharplang proposal](https://github.com/dotnet/csharplang/blob/master/proposals/async-main.md) + +## Technical Details + +* The compiler must recognize `Task` and `Task` as valid entrypoint return types in addition to `void` and `int`. +* The compiler must allow `async` to be placed on a main method that returns a `Task` or a `Task` (but not void). +* The compiler must generate a shim method `$EntrypointMain` that mimics the arguments of the user-defined main. + * `static async Task Main(...)` -> `static void $EntrypointMain(...)` + * `static async Task Main(...)` -> `static int $EntrypointMain(...)` + * The parameters between the user-defined main and the generated main should match exactly. +* The body of the generated main should be `return Main(args...).GetAwaiter().GetResult();` \ No newline at end of file diff --git a/docs/features/async-main.test.md b/docs/features/async-main.test.md new file mode 100644 index 0000000000000..9b4a51467e559 --- /dev/null +++ b/docs/features/async-main.test.md @@ -0,0 +1,99 @@ +Main areas to test: +* Signature acceptance / rejection +* Method body creation + +# Signature acceptance / rejection + +## Single Mains +Classes that contain only a single "main" method + +### Single legal main +* Past: Ok +* New 7: Ok +* New 7.1 Ok + +### Single async (void) main +* Past: ERR (Async can't be main) +* New 7: ERR (Update to get this to work) +* New 7.1: Ok + +### Single async (Task) main +* Past: ERR (No entrypoints found), WARN (has the wrong signature to be an entry point) +* New 7: ERR (Update to get this to work) +* New 7.1 Ok + +### Single async (Task) main +* Past: ERR (No entrypoints found), WARN (has the wrong signature) +* New 7: ERR (Update to get this to work) +* New 7.1: Ok + +## Multiple Mains +Classes that contain more than one main + +### Multiple legal mains +* Past: Err +* New 7: Err +* New 7.1: Err + +### Single legal main, single async (void) main +* Past: Err (an entrypoint cannot be marked with async) +* New 7: Err (new error here?) +* New 7.1: Err (new error here? "void is not an acceptable async return type") + +### Single legal main, single legal Task main +* Past: Ok (warning: task main has wrong signature) +* New 7: Ok (new warning here) +* New 7.1: Ok (new warning here?) + +### Single legal main, single legal Task main +* Past: Ok (warning: task main has wrong signature) +* New 7: Ok (new warning here) +* New 7.1: Ok (new warning here?) + +# Method body creation + +* Inspect IL for correct codegen. +* Make sure that attributes are correctly applied to the synthesized mains. + +## Broken methods on Task and Task + +* GetAwatier or GetResult are missing +* GetAwaiter or GetResult don't have the required signature +* Has the right shape, but types are missing + +## Task or Task is missing + +This will be caught during entrypoint detection, should be a binding error. + +## Void or int is missing + +If Task can be found, but void or int can't be found, then the compiler should behave gracefully. + +# Vlad Test Plan + +## Public interface of compiler APIs + -N/A? + +## General functionality +- `Task` and `Task` returns are allowed. Do we require `async`. +- Multiple valid/invalid async candidates. +- process exit code on success (void and int) and on exception +- `/main:` cmd switch is still functional + +## Old versions, compat +- langver +- when both async and regular Main avaialble. With old/new compiler versions. +- async void Main. With old.new langver. With another applicable Main present. + +## Type and members +- Access modifiers (public, protected, internal, protected internal, private), static modifier +- Parameter modifiers (ref, out, params) +- STA attribute +- Partial method +- Named and optional parameters +- `Task` +- Task-Like types + +## Debug +- F11 works +- stepping through Main diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Await.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Await.cs index 371874bcdeae1..61611318c2954 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Await.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Await.cs @@ -29,7 +29,7 @@ private BoundExpression BindAwait(BoundExpression expression, SyntaxNode node, D bool hasErrors = ReportBadAwaitWithoutAsync(node, diagnostics) | ReportBadAwaitContext(node, diagnostics) | - !GetAwaitableExpressionInfo(expression, out getAwaiter, out isCompleted, out getResult, node, diagnostics); + !GetAwaitableExpressionInfo(expression, out getAwaiter, out isCompleted, out getResult, out _, node, diagnostics); // Spec 7.7.7.2: // The expression await t is classified the same way as the expression (t).GetAwaiter().GetResult(). Thus, @@ -209,17 +209,19 @@ private bool ReportBadAwaitContext(SyntaxNode node, DiagnosticBag diagnostics) /// Finds and validates the required members of an awaitable expression, as described in spec 7.7.7.1. /// /// True if the expression is awaitable; false otherwise. - private bool GetAwaitableExpressionInfo( + internal bool GetAwaitableExpressionInfo( BoundExpression expression, out MethodSymbol getAwaiter, out PropertySymbol isCompleted, out MethodSymbol getResult, + out BoundExpression getAwaiterGetResultCall, SyntaxNode node, DiagnosticBag diagnostics) { getAwaiter = null; isCompleted = null; getResult = null; + getAwaiterGetResultCall = null; if (!ValidateAwaitedExpression(expression, node, diagnostics)) { @@ -231,7 +233,8 @@ private bool GetAwaitableExpressionInfo( return true; } - if (!GetGetAwaiterMethod(expression, node, diagnostics, out getAwaiter)) + BoundExpression getAwaiterCall = null; + if (!GetGetAwaiterMethod(expression, node, diagnostics, out getAwaiter, out getAwaiterCall)) { return false; } @@ -239,7 +242,7 @@ private bool GetAwaitableExpressionInfo( TypeSymbol awaiterType = getAwaiter.ReturnType; return GetIsCompletedProperty(awaiterType, node, expression.Type, diagnostics, out isCompleted) && AwaiterImplementsINotifyCompletion(awaiterType, node, diagnostics) - && GetGetResultMethod(awaiterType, node, expression.Type, diagnostics, out getResult); + && GetGetResultMethod(getAwaiterCall, node, expression.Type, diagnostics, out getResult, out getAwaiterGetResultCall); } /// @@ -273,19 +276,21 @@ private static bool ValidateAwaitedExpression(BoundExpression expression, Syntax /// NOTE: this is an error in the spec. An extension method of the form /// Awaiter<T> GetAwaiter<T>(this Task<T>) may be used. /// - private bool GetGetAwaiterMethod(BoundExpression expression, SyntaxNode node, DiagnosticBag diagnostics, out MethodSymbol getAwaiterMethod) + private bool GetGetAwaiterMethod(BoundExpression expression, SyntaxNode node, DiagnosticBag diagnostics, out MethodSymbol getAwaiterMethod, out BoundExpression getAwaiterCall) { if (expression.Type.SpecialType == SpecialType.System_Void) { Error(diagnostics, ErrorCode.ERR_BadAwaitArgVoidCall, node); getAwaiterMethod = null; + getAwaiterCall = null; return false; } - var getAwaiterCall = MakeInvocationExpression(node, expression, WellKnownMemberNames.GetAwaiter, ImmutableArray.Empty, diagnostics); + getAwaiterCall = MakeInvocationExpression(node, expression, WellKnownMemberNames.GetAwaiter, ImmutableArray.Empty, diagnostics); if (getAwaiterCall.HasAnyErrors) // && !expression.HasAnyErrors? { getAwaiterMethod = null; + getAwaiterCall = null; return false; } @@ -293,6 +298,7 @@ private bool GetGetAwaiterMethod(BoundExpression expression, SyntaxNode node, Di { Error(diagnostics, ErrorCode.ERR_BadAwaitArg, node, expression.Type); getAwaiterMethod = null; + getAwaiterCall = null; return false; } @@ -303,6 +309,7 @@ private bool GetGetAwaiterMethod(BoundExpression expression, SyntaxNode node, Di { Error(diagnostics, ErrorCode.ERR_BadAwaitArg, node, expression.Type); getAwaiterMethod = null; + getAwaiterCall = null; return false; } @@ -383,28 +390,31 @@ private bool AwaiterImplementsINotifyCompletion(TypeSymbol awaiterType, SyntaxNo /// Spec 7.7.7.1: /// An Awaiter A has an accessible instance method GetResult with no parameters and no type parameters. /// - private bool GetGetResultMethod(TypeSymbol awaiterType, SyntaxNode node, TypeSymbol awaitedExpressionType, DiagnosticBag diagnostics, out MethodSymbol getResultMethod) + private bool GetGetResultMethod(BoundExpression awaiterExpression, SyntaxNode node, TypeSymbol awaitedExpressionType, DiagnosticBag diagnostics, out MethodSymbol getResultMethod, out BoundExpression getAwaiterGetResultCall) { - var receiver = new BoundLiteral(node, ConstantValue.Null, awaiterType); - var getResultCall = MakeInvocationExpression(node, receiver, WellKnownMemberNames.GetResult, ImmutableArray.Empty, diagnostics); - if (getResultCall.HasAnyErrors) + var awaiterType = awaiterExpression.Type; + getAwaiterGetResultCall = MakeInvocationExpression(node, awaiterExpression, WellKnownMemberNames.GetResult, ImmutableArray.Empty, diagnostics); + if (getAwaiterGetResultCall.HasAnyErrors) { getResultMethod = null; + getAwaiterGetResultCall = null; return false; } - if (getResultCall.Kind != BoundKind.Call) + if (getAwaiterGetResultCall.Kind != BoundKind.Call) { Error(diagnostics, ErrorCode.ERR_NoSuchMember, node, awaiterType, WellKnownMemberNames.GetResult); getResultMethod = null; + getAwaiterGetResultCall = null; return false; } - getResultMethod = ((BoundCall)getResultCall).Method; + getResultMethod = ((BoundCall)getAwaiterGetResultCall).Method; if (getResultMethod.IsExtensionMethod) { Error(diagnostics, ErrorCode.ERR_NoSuchMember, node, awaiterType, WellKnownMemberNames.GetResult); getResultMethod = null; + getAwaiterGetResultCall = null; return false; } @@ -412,6 +422,7 @@ private bool GetGetResultMethod(TypeSymbol awaiterType, SyntaxNode node, TypeSym { Error(diagnostics, ErrorCode.ERR_BadAwaiterPattern, node, awaiterType, awaitedExpressionType); getResultMethod = null; + getAwaiterGetResultCall = null; return false; } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs index d02e32d22e28e..2cc61856e9e13 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs @@ -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 diff --git a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs index 7a7b71a958a5d..d4fd992814e89 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs +++ b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs @@ -6882,7 +6882,18 @@ internal static string ERR_NonInvocableMemberCalled { return ResourceManager.GetString("ERR_NonInvocableMemberCalled", resourceCulture); } } - + + /// + /// Looks up a localized string similar to Async Main methods must return Task or Task<int>. + /// + internal static string ERR_NonTaskMainCantBeAsync + { + get + { + return ResourceManager.GetString("ERR_NonTaskMainCantBeAsync", resourceCulture); + } + } + /// /// Looks up a localized string similar to Cannot embed interop types from assembly '{0}' because it is missing the '{1}' attribute.. /// @@ -9870,22 +9881,24 @@ internal static string IDS_Covariantly { return ResourceManager.GetString("IDS_Covariantly", resourceCulture); } } - + /// /// Looks up a localized string similar to /// Visual C# Compiler Options /// /// - OUTPUT FILES - - /// /out:<file> Specify output file name (default: base name of + /// /out:<file> Specify output file name (default: base name of /// file with main class or first file) - /// /target:exe Build a console executable (default) (Short + /// /target:exe Build a console executable (default) (Short /// form: /t:exe) - /// /target:winexe Build a Windows executable (Short form: + /// /target:winexe Build a Windows executable (Short form: /// /t:winexe) - /// /target:library [rest of string was truncated]";. + /// /target:library [rest of string was truncated]";. /// - internal static string IDS_CSCHelp { - get { + internal static string IDS_CSCHelp + { + get + { return ResourceManager.GetString("IDS_CSCHelp", resourceCulture); } } @@ -9935,6 +9948,15 @@ internal static string IDS_FeatureAsync { } } + /// + /// Looks up a localized string similar to async main. + /// + internal static string IDS_FeatureAsyncMain { + get { + return ResourceManager.GetString("IDS_FeatureAsyncMain", resourceCulture); + } + } + /// /// Looks up a localized string similar to automatically implemented properties. /// diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 9009bd391d8fe..292dc56aef635 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -1,17 +1,17 @@  - @@ -3599,9 +3599,6 @@ Give the compiler some way to differentiate the methods. For example, you can gi Async methods are not allowed in an Interface, Class, or Structure which has the 'SecurityCritical' or 'SecuritySafeCritical' attribute. - - '{0}': an entry point cannot be marked with the 'async' modifier - 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 @@ -4391,34 +4388,34 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Visual C# Compiler Options - OUTPUT FILES - - /out:<file> Specify output file name (default: base name of + /out:<file> Specify output file name (default: base name of file with main class or first file) - /target:exe Build a console executable (default) (Short + /target:exe Build a console executable (default) (Short form: /t:exe) - /target:winexe Build a Windows executable (Short form: + /target:winexe Build a Windows executable (Short form: /t:winexe) /target:library Build a library (Short form: /t:library) - /target:module Build a module that can be added to another + /target:module Build a module that can be added to another assembly (Short form: /t:module) - /target:appcontainerexe Build an Appcontainer executable (Short form: + /target:appcontainerexe Build an Appcontainer executable (Short form: /t:appcontainerexe) - /target:winmdobj Build a Windows Runtime intermediate file that + /target:winmdobj Build a Windows Runtime intermediate file that is consumed by WinMDExp (Short form: /t:winmdobj) /doc:<file> XML Documentation file to generate /platform:<string> Limit which platforms this code can run on: x86, - Itanium, x64, arm, anycpu32bitpreferred, or + Itanium, x64, arm, anycpu32bitpreferred, or anycpu. The default is anycpu. - INPUT FILES - - /recurse:<wildcard> Include all files in the current directory and - subdirectories according to the wildcard + /recurse:<wildcard> Include all files in the current directory and + subdirectories according to the wildcard specifications - /reference:<alias>=<file> Reference metadata from the specified assembly + /reference:<alias>=<file> Reference metadata from the specified assembly file using the given alias (Short form: /r) - /reference:<file list> Reference metadata from the specified assembly + /reference:<file list> Reference metadata from the specified assembly files (Short form: /r) /addmodule:<file list> Link the specified modules into this assembly - /link:<file list> Embed metadata from the specified interop + /link:<file list> Embed metadata from the specified interop assembly files (Short form: /l) /analyzer:<file list> Run the analyzers from this assembly (Short form: /a) @@ -4434,16 +4431,16 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ /win32manifest:<file> Specify a Win32 manifest file (.xml) /nowin32manifest Do not include the default Win32 manifest /resource:<resinfo> Embed the specified resource (Short form: /res) - /linkresource:<resinfo> Link the specified resource to this assembly - (Short form: /linkres) Where the resinfo format + /linkresource:<resinfo> Link the specified resource to this assembly + (Short form: /linkres) Where the resinfo format is <file>[,<string name>[,public|private]] - CODE GENERATION - /debug[+|-] Emit debugging information /debug:{full|pdbonly|portable|embedded} - Specify debugging type ('full' is default, + Specify debugging type ('full' is default, 'portable' is a cross-platform format, - 'embedded' is a cross-platform format embedded into + 'embedded' is a cross-platform format embedded into the target .dll or .exe) /optimize[+|-] Enable optimizations (Short form: /o) /deterministic Produce a deterministic assembly @@ -4463,17 +4460,17 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ diagnostics. /reportanalyzer Report additional analyzer information, such as execution time. - + - LANGUAGE - /checked[+|-] Generate overflow checks /unsafe[+|-] Allow 'unsafe' code - /define:<symbol list> Define conditional compilation symbol(s) (Short + /define:<symbol list> Define conditional compilation symbol(s) (Short form: /d) - /langversion:<string> Specify language version mode: ISO-1, ISO-2, 3, + /langversion:<string> Specify language version mode: ISO-1, ISO-2, 3, 4, 5, 6, 7, 7.1, Default, or Latest - SECURITY - - /delaysign[+|-] Delay-sign the assembly using only the public + /delaysign[+|-] Delay-sign the assembly using only the public portion of the strong name key /publicsign[+|-] Public-sign the assembly using only the public portion of the strong name key @@ -4491,36 +4488,36 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ - ADVANCED - /baseaddress:<address> Base address for the library to be built - /checksumalgorithm:<alg> Specify algorithm for calculating source file + /checksumalgorithm:<alg> Specify algorithm for calculating source file checksum stored in PDB. Supported values are: SHA1 (default) or SHA256. - /codepage:<n> Specify the codepage to use when opening source + /codepage:<n> Specify the codepage to use when opening source files /utf8output Output compiler messages in UTF-8 encoding - /main:<type> Specify the type that contains the entry point - (ignore all other possible entry points) (Short + /main:<type> Specify the type that contains the entry point + (ignore all other possible entry points) (Short form: /m) /fullpaths Compiler generates fully qualified paths - /filealign:<n> Specify the alignment used for output file + /filealign:<n> Specify the alignment used for output file sections /pathmap:<K1>=<V1>,<K2>=<V2>,... Specify a mapping for source path names output by the compiler. - /pdb:<file> Specify debug information file name (default: + /pdb:<file> Specify debug information file name (default: output file name with .pdb extension) - /errorendlocation Output line and column of the end location of + /errorendlocation Output line and column of the end location of each error /preferreduilang Specify the preferred output language name. /nostdlib[+|-] Do not reference standard library (mscorlib.dll) /subsystemversion:<string> Specify subsystem version of this assembly - /lib:<file list> Specify additional directories to search in for + /lib:<file list> Specify additional directories to search in for references - /errorreport:<string> Specify how to handle internal compiler errors: - prompt, send, queue, or none. The default is + /errorreport:<string> Specify how to handle internal compiler errors: + prompt, send, queue, or none. The default is queue. - /appconfig:<file> Specify an application configuration file + /appconfig:<file> Specify an application configuration file containing assembly binding settings - /moduleassemblyname:<string> Name of the assembly which this module will be + /moduleassemblyname:<string> Name of the assembly which this module will be a part of /modulename:<string> Specify the name of the source module @@ -5061,6 +5058,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Compiler version: '{0}'. Language version: {1}. + + async main + Tuple element name '{0}' is inferred. Please use language version {1} or greater to access an element by its inferred name. @@ -5073,6 +5073,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ A tuple may not contain a value of type 'void'. + + A void or int returning entry point cannot be async + An expression of type '{0}' cannot be handled by a pattern of type '{1}' in C# {2}. Please use language version {3} or greater. diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs index e30e81fa20749..b64ffb101ed45 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs @@ -19,6 +19,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Symbols; +using static Microsoft.CodeAnalysis.CSharp.Binder; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -40,7 +41,7 @@ public sealed partial class CSharpCompilation : Compilation // version. Do not make any changes to the public interface without making the corresponding // change to the VB version. // - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! internal static readonly ParallelOptions DefaultParallelOptions = new ParallelOptions(); @@ -93,9 +94,9 @@ internal Conversions Conversions /// /// Holds onto data related to reference binding. /// The manager is shared among multiple compilations that we expect to have the same result of reference binding. - /// In most cases this can be determined without performing the binding. If the compilation however contains a circular + /// In most cases this can be determined without performing the binding. If the compilation however contains a circular /// metadata reference (a metadata reference that refers back to the compilation) we need to avoid sharing of the binding results. - /// We do so by creating a new reference manager for such compilation. + /// We do so by creating a new reference manager for such compilation. /// private ReferenceManager _referenceManager; @@ -128,7 +129,7 @@ public override bool IsCaseSensitive } /// - /// The options the compilation was created with. + /// The options the compilation was created with. /// public new CSharpCompilationOptions Options { @@ -172,14 +173,14 @@ public LanguageVersion LanguageVersion protected override INamedTypeSymbol CommonCreateErrorTypeSymbol(INamespaceOrTypeSymbol container, string name, int arity) { return new ExtendedErrorTypeSymbol( - container.EnsureCSharpSymbolOrNull(nameof(container)), + container.EnsureCSharpSymbolOrNull(nameof(container)), name, arity, errorInfo: null); } protected override INamespaceSymbol CommonCreateErrorNamespaceSymbol(INamespaceSymbol container, string name) { return new MissingNamespaceSymbol( - container.EnsureCSharpSymbolOrNull(nameof(container)), + container.EnsureCSharpSymbolOrNull(nameof(container)), name); } @@ -408,8 +409,8 @@ private CSharpCompilation Update( /// public new CSharpCompilation WithAssemblyName(string assemblyName) { - // Can't reuse references since the source assembly name changed and the referenced symbols might - // have internals-visible-to relationship with this compilation or they might had a circular reference + // Can't reuse references since the source assembly name changed and the referenced symbols might + // have internals-visible-to relationship with this compilation or they might had a circular reference // to this compilation. return new CSharpCompilation( @@ -429,9 +430,9 @@ private CSharpCompilation Update( /// Creates a new compilation with the specified references. /// /// - /// The new will query the given for the underlying - /// metadata as soon as the are needed. - /// + /// The new will query the given for the underlying + /// metadata as soon as the are needed. + /// /// The new compilation uses whatever metadata is currently being provided by the . /// E.g. if the current compilation references a metadata file that has changed since the creation of the compilation /// the new compilation is going to use the updated version, while the current compilation will be using the previous (it doesn't change). @@ -684,7 +685,7 @@ internal override bool HasSubmissionResult() /// /// Creates a new compilation without the specified syntax trees. Preserves metadata info for use with trees - /// added later. + /// added later. /// public new CSharpCompilation RemoveSyntaxTrees(params SyntaxTree[] trees) { @@ -693,7 +694,7 @@ internal override bool HasSubmissionResult() /// /// Creates a new compilation without the specified syntax trees. Preserves metadata info for use with trees - /// added later. + /// added later. /// public new CSharpCompilation RemoveSyntaxTrees(IEnumerable trees) { @@ -746,7 +747,7 @@ internal override bool HasSubmissionResult() /// /// Creates a new compilation without any syntax trees. Preserves metadata info - /// from this compilation for use with trees added later. + /// from this compilation for use with trees added later. /// public new CSharpCompilation RemoveAllSyntaxTrees() { @@ -805,7 +806,7 @@ internal override bool HasSubmissionResult() } // TODO(tomat): Consider comparing #r's of the old and the new tree. If they are exactly the same we could still reuse. - // This could be a perf win when editing a script file in the IDE. The services create a new compilation every keystroke + // This could be a perf win when editing a script file in the IDE. The services create a new compilation every keystroke // that replaces the tree with a new one. var reuseReferenceManager = !oldTree.HasReferenceOrLoadDirectives() && !newTree.HasReferenceOrLoadDirectives(); syntaxAndDeclarations = syntaxAndDeclarations.ReplaceSyntaxTree(oldTree, newTree); @@ -872,7 +873,7 @@ internal IEnumerable ExternAliases /// /// or corresponding to the given reference or null if there is none. /// - /// Uses object identity when comparing two references. + /// Uses object identity when comparing two references. /// internal new Symbol GetAssemblyOrModuleSymbol(MetadataReference reference) { @@ -976,7 +977,7 @@ public MetadataReference GetDirectiveReference(ReferenceDirectiveTriviaSyntax di /// /// Get all modules in this compilation, including the source module, added modules, and all /// modules of referenced assemblies that do not come from an assembly with an extern alias. - /// Metadata imported from aliased assemblies is not visible at the source level except through + /// Metadata imported from aliased assemblies is not visible at the source level except through /// the use of an extern alias directive. So exclude them from this list which is used to construct /// the global namespace. /// @@ -1017,7 +1018,7 @@ internal void GetUnaliasedReferencedAssemblies(ArrayBuilder asse } /// - /// Gets the that corresponds to the assembly symbol. + /// Gets the that corresponds to the assembly symbol. /// public new MetadataReference GetMetadataReference(IAssemblySymbol assemblySymbol) { @@ -1065,7 +1066,7 @@ internal SourceAssemblySymbol SourceAssembly } /// - /// Gets the root namespace that contains all namespaces and types defined in source code or in + /// Gets the root namespace that contains all namespaces and types defined in source code or in /// referenced metadata, merged into a single namespace hierarchy. /// internal new NamespaceSymbol GlobalNamespace @@ -1075,7 +1076,7 @@ internal SourceAssemblySymbol SourceAssembly if ((object)_lazyGlobalNamespace == null) { // Get the root namespace from each module, and merge them all together - // Get all modules in this compilation, ones referenced directly by the compilation + // Get all modules in this compilation, ones referenced directly by the compilation // as well as those referenced by all referenced assemblies. var modules = ArrayBuilder.GetInstance(); @@ -1369,7 +1370,7 @@ internal bool DeclaresTheObjectClass internal new MethodSymbol GetEntryPoint(CancellationToken cancellationToken) { EntryPoint entryPoint = GetEntryPointAndDiagnostics(cancellationToken); - return entryPoint == null ? null : entryPoint.MethodSymbol; + return entryPoint?.MethodSymbol; } internal EntryPoint GetEntryPointAndDiagnostics(CancellationToken cancellationToken) @@ -1452,38 +1453,100 @@ private MethodSymbol FindEntryPoint(CancellationToken cancellationToken, out Imm } } - DiagnosticBag warnings = DiagnosticBag.GetInstance(); - var viableEntryPoints = ArrayBuilder.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 (!candidate.HasEntryPointSignature()) + 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; + } - if (candidate.IsAsync) + var viableEntryPoints = ArrayBuilder.GetInstance(); + + foreach (var candidate in entryPointCandidates) + { + var perCandidateBag = DiagnosticBag.GetInstance(); + var (IsCandidate, IsTaskLike) = HasEntryPointSignature(candidate, perCandidateBag); + + if (IsTaskLike) + { + taskEntryPoints.Add((IsCandidate, candidate, perCandidateBag)); + } + else { - diagnostics.Add(ErrorCode.ERR_MainCantBeAsync, candidate.Locations.First(), candidate); + if (CheckValid(candidate, IsCandidate, perCandidateBag)) + { + if (candidate.IsAsync) + { + diagnostics.Add(ErrorCode.ERR_NonTaskMainCantBeAsync, candidate.Locations.First(), candidate); + } + else + { + diagnostics.AddRange(perCandidateBag); + viableEntryPoints.Add(candidate); + } + } + perCandidateBag.Free(); } + } - viableEntryPoints.Add(candidate); + if (viableEntryPoints.Count == 0) + { + foreach (var (IsValid, Candidate, SpecificDiagnostics) in taskEntryPoints) + { + if (CheckValid(Candidate, IsValid, SpecificDiagnostics) && + CheckFeatureAvailability(Candidate.ExtractReturnTypeSyntax(), MessageID.IDS_FeatureAsyncMain, diagnostics)) + { + diagnostics.AddRange(SpecificDiagnostics); + viableEntryPoints.Add(Candidate); + } + } } - if ((object)mainType == null || viableEntryPoints.Count == 0) + foreach (var (_, _, SpecificDiagnostics) in taskEntryPoints) { - diagnostics.AddRange(warnings); + 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 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) @@ -1513,7 +1576,9 @@ private MethodSymbol FindEntryPoint(CancellationToken cancellationToken, out Imm entryPoint = viableEntryPoints[0]; } + taskEntryPoints.Free(); viableEntryPoints.Free(); + noMainFoundDiagnostics.Free(); return entryPoint; } finally @@ -1523,6 +1588,92 @@ private MethodSymbol FindEntryPoint(CancellationToken cancellationToken, out Imm } } + internal bool ReturnsAwaitableToVoidOrInt(MethodSymbol method, DiagnosticBag diagnostics) + { + // Common case optimization + if (method.ReturnType.SpecialType == SpecialType.System_Void || method.ReturnType.SpecialType == SpecialType.System_Int32) + { + return false; + } + + if (!(method.ReturnType is NamedTypeSymbol namedType)) + { + return false; + } + + // Early bail so we only even check things that are System.Threading.Tasks.Task() + if (!(namedType.ConstructedFrom == GetWellKnownType(WellKnownType.System_Threading_Tasks_Task) || + namedType.ConstructedFrom == GetWellKnownType(WellKnownType.System_Threading_Tasks_Task_T))) + { + return false; + } + + var syntax = method.ExtractReturnTypeSyntax(); + var dumbInstance = new BoundLiteral(syntax, ConstantValue.Null, method.ReturnType); + var binder = GetBinder(syntax); + BoundExpression result; + var success = binder.GetAwaitableExpressionInfo(dumbInstance, out _, out _, out _, out result, syntax, diagnostics); + + return success && + (result.Type.SpecialType == SpecialType.System_Void || result.Type.SpecialType == SpecialType.System_Int32); + } + + /// + /// Checks if the method has an entry point compatible signature, i.e. + /// - the return type is either void, int, or returns a , + /// or where the return type of GetAwaiter().GetResult() + /// is either void or int. + /// - has either no parameter or a single parameter of type string[] + /// + private (bool IsCandidate, bool IsTaskLike) HasEntryPointSignature(MethodSymbol method, DiagnosticBag bag) + { + if (method.IsVararg) + { + return (false, false); + } + + TypeSymbol returnType = method.ReturnType; + bool returnsTaskOrTaskOfInt = false; + if (returnType.SpecialType != SpecialType.System_Int32 && returnType.SpecialType != SpecialType.System_Void) + { + // Never look for ReturnsAwaitableToVoidOrInt on int32 or void + returnsTaskOrTaskOfInt = ReturnsAwaitableToVoidOrInt(method, bag); + if (!returnsTaskOrTaskOfInt) + { + return (false, false); + } + } + + if (method.RefKind != RefKind.None) + { + return (false, returnsTaskOrTaskOfInt); + } + + if (method.Parameters.Length == 0) + { + return (true, returnsTaskOrTaskOfInt); + } + + if (method.Parameters.Length > 1) + { + return (false, returnsTaskOrTaskOfInt); + } + + if (!method.ParameterRefKinds.IsDefault) + { + return (false, returnsTaskOrTaskOfInt); + } + + var firstType = method.Parameters[0].Type; + if (firstType.TypeKind != TypeKind.Array) + { + return (false, returnsTaskOrTaskOfInt); + } + + var array = (ArrayTypeSymbol)firstType; + return (array.IsSZArray && array.ElementType.SpecialType == SpecialType.System_String, returnsTaskOrTaskOfInt); + } + internal override bool IsUnreferencedAssemblyIdentityDiagnosticCode(int code) => code == (int)ErrorCode.ERR_NoTypeDef; @@ -2171,8 +2322,8 @@ internal ImmutableArray GetDiagnosticsForSyntaxTree( { //remove some errors that don't have locations in the tree, like "no suitable main method." //Members in trees other than the one being examined are not compiled. This includes field - //initializers which can result in 'field is never initialized' warnings for fields in partial - //types when the field is in a different source file than the one for which we're getting diagnostics. + //initializers which can result in 'field is never initialized' warnings for fields in partial + //types when the field is in a different source file than the one for which we're getting diagnostics. //For that reason the bag must be also filtered by tree. IEnumerable methodBodyDiagnostics = GetDiagnosticsForMethodBodiesInTree(syntaxTree, filterSpanWithinTree, cancellationToken); @@ -2730,15 +2881,15 @@ protected override INamedTypeSymbol CommonCreateTupleTypeSymbol( return TupleTypeSymbol.Create( locationOpt: null, // no location for the type declaration elementTypes: typesBuilder.ToImmutableAndFree(), - elementLocations: elementLocations, - elementNames: elementNames, + elementLocations: elementLocations, + elementNames: elementNames, compilation: this, shouldCheckConstraints: false, errorPositions: default(ImmutableArray)); } protected override INamedTypeSymbol CommonCreateTupleTypeSymbol( - INamedTypeSymbol underlyingType, + INamedTypeSymbol underlyingType, ImmutableArray elementNames, ImmutableArray elementLocations) { @@ -2758,7 +2909,7 @@ protected override INamedTypeSymbol CommonCreateTupleTypeSymbol( } protected override INamedTypeSymbol CommonCreateAnonymousTypeSymbol( - ImmutableArray memberTypes, + ImmutableArray memberTypes, ImmutableArray memberNames, ImmutableArray memberLocations, ImmutableArray memberIsReadOnly) @@ -2856,7 +3007,7 @@ internal override int CompareSourceLocations(Location loc1, Location loc2) #endregion /// - /// Returns if the compilation has all of the members necessary to emit metadata about + /// Returns if the compilation has all of the members necessary to emit metadata about /// dynamic types. /// /// @@ -2912,7 +3063,7 @@ internal bool EnableEnumArrayBlockInitialization return sustainedLowLatency != null && sustainedLowLatency.ContainingAssembly == Assembly.CorLibrary; } } - + internal override bool IsIOperationFeatureEnabled() { var options = (CSharpParseOptions)this.SyntaxTrees.FirstOrDefault()?.Options; diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs index 79a34de9eeed1..d16155984fb51 100644 --- a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs +++ b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs @@ -184,6 +184,8 @@ public static void CompileMethodBodies( } } + // Returns the MethodSymbol for the assembly entrypoint. If the user has a Task returning main, + // this function returns the synthesized Main MethodSymbol. private static MethodSymbol GetEntryPoint(CSharpCompilation compilation, PEModuleBuilder moduleBeingBuilt, bool hasDeclarationErrors, DiagnosticBag diagnostics, CancellationToken cancellationToken) { var entryPointAndDiagnostics = compilation.GetEntryPointAndDiagnostics(cancellationToken); @@ -194,21 +196,69 @@ private static MethodSymbol GetEntryPoint(CSharpCompilation compilation, PEModul Debug.Assert(!entryPointAndDiagnostics.Diagnostics.IsDefault); diagnostics.AddRange(entryPointAndDiagnostics.Diagnostics); - var entryPoint = entryPointAndDiagnostics.MethodSymbol; - var synthesizedEntryPoint = entryPoint as SynthesizedEntryPointSymbol; + + if ((object)entryPoint == null) + { + Debug.Assert(entryPointAndDiagnostics.Diagnostics.HasAnyErrors() || !compilation.Options.Errors.IsDefaultOrEmpty); + return null; + } + + // entryPoint can be a SynthesizedEntryPointSymbol if a script is being compiled. + SynthesizedEntryPointSymbol synthesizedEntryPoint = entryPoint as SynthesizedEntryPointSymbol; + if ((object)synthesizedEntryPoint == null && (entryPoint.ReturnType.IsGenericTaskType(compilation) || entryPoint.ReturnType.IsNonGenericTaskType(compilation))) + { + synthesizedEntryPoint = new SynthesizedEntryPointSymbol.AsyncForwardEntryPoint(compilation, entryPoint.ContainingType, entryPoint); + entryPoint = synthesizedEntryPoint; + if ((object)moduleBeingBuilt != null) + { + moduleBeingBuilt.AddSynthesizedDefinition(entryPoint.ContainingType, synthesizedEntryPoint); + } + } + if (((object)synthesizedEntryPoint != null) && (moduleBeingBuilt != null) && !hasDeclarationErrors && !diagnostics.HasAnyErrors()) { - var body = synthesizedEntryPoint.CreateBody(); + BoundStatement body = synthesizedEntryPoint.CreateBody(); + + var dynamicAnalysisSpans = ImmutableArray.Empty; + VariableSlotAllocator lazyVariableSlotAllocator = null; + var lambdaDebugInfoBuilder = ArrayBuilder.GetInstance(); + var closureDebugInfoBuilder = ArrayBuilder.GetInstance(); + StateMachineTypeSymbol stateMachineTypeOpt = null; const int methodOrdinal = -1; + + var loweredBody = LowerBodyOrInitializer( + synthesizedEntryPoint, + methodOrdinal, + body, + null, + new TypeCompilationState(synthesizedEntryPoint.ContainingType, compilation, moduleBeingBuilt), + false, + null, + ref dynamicAnalysisSpans, + diagnostics, + ref lazyVariableSlotAllocator, + lambdaDebugInfoBuilder, + closureDebugInfoBuilder, + out stateMachineTypeOpt); + + Debug.Assert((object)lazyVariableSlotAllocator == null); + Debug.Assert((object)stateMachineTypeOpt == null); + Debug.Assert(dynamicAnalysisSpans.IsEmpty); + Debug.Assert(lambdaDebugInfoBuilder.IsEmpty()); + Debug.Assert(closureDebugInfoBuilder.IsEmpty()); + + lambdaDebugInfoBuilder.Free(); + closureDebugInfoBuilder.Free(); + var emittedBody = GenerateMethodBody( moduleBeingBuilt, synthesizedEntryPoint, methodOrdinal, - body, + loweredBody, ImmutableArray.Empty, ImmutableArray.Empty, stateMachineTypeOpt: null, @@ -221,7 +271,6 @@ private static MethodSymbol GetEntryPoint(CSharpCompilation compilation, PEModul moduleBeingBuilt.SetMethodBody(synthesizedEntryPoint, emittedBody); } - Debug.Assert((object)entryPoint != null || entryPointAndDiagnostics.Diagnostics.HasAnyErrors() || !compilation.Options.Errors.IsDefaultOrEmpty); return entryPoint; } diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 3ed35efce1427..75155ab5bace5 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -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, diff --git a/src/Compilers/CSharp/Portable/Errors/MessageID.cs b/src/Compilers/CSharp/Portable/Errors/MessageID.cs index 78d7b2bd52e0a..fc13210b8b1dd 100644 --- a/src/Compilers/CSharp/Portable/Errors/MessageID.cs +++ b/src/Compilers/CSharp/Portable/Errors/MessageID.cs @@ -130,6 +130,7 @@ internal enum MessageID IDS_FeatureDefaultLiteral = MessageBase + 12718, IDS_FeatureInferredTupleNames = MessageBase + 12719, IDS_FeatureGenericPatternMatching = MessageBase + 12720, + IDS_FeatureAsyncMain = MessageBase + 12721, } // Message IDs may refer to strings that need to be localized. @@ -187,6 +188,7 @@ internal static LanguageVersion RequiredVersion(this MessageID feature) switch (feature) { // C# 7.1 features. + case MessageID.IDS_FeatureAsyncMain: case MessageID.IDS_FeatureDefaultLiteral: case MessageID.IDS_FeatureInferredTupleNames: case MessageID.IDS_FeatureGenericPatternMatching: diff --git a/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs index d4b9e5ef6aa03..c5f37ce315d96 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Symbols; using Roslyn.Utilities; @@ -84,7 +85,7 @@ public virtual bool IsGenericMethod internal virtual bool IsDirectlyExcludedFromCodeCoverage { get => false; } /// - /// Returns true if this method is an extension method. + /// Returns true if this method is an extension method. /// public abstract bool IsExtensionMethod { get; } @@ -114,7 +115,7 @@ public virtual bool IsGenericMethod internal abstract IEnumerable GetSecurityInformation(); /// - /// Marshalling information for return value (FieldMarshal in metadata). + /// Marshalling information for return value (FieldMarshal in metadata). /// internal abstract MarshalPseudoCustomAttributeData ReturnValueMarshallingInformation { get; } @@ -133,7 +134,7 @@ public virtual bool IsGenericMethod /// /// Returns true if this method hides base methods by name. This cannot be specified directly /// in the C# language, but can be true for methods defined in other languages imported from - /// metadata. The equivalent of the "hidebyname" flag in metadata. + /// metadata. The equivalent of the "hidebyname" flag in metadata. /// public abstract bool HidesBaseMethodsByName { get; } @@ -184,7 +185,7 @@ public virtual bool IsCheckedBuiltin public abstract TypeSymbol ReturnType { get; } /// - /// Returns the type arguments that have been substituted for the type parameters. + /// Returns the type arguments that have been substituted for the type parameters. /// If nothing has been substituted for a given type parameter, /// then the type parameter itself is consider the type argument. /// @@ -275,13 +276,13 @@ internal virtual bool IsExplicitInterfaceImplementation /// Returns interface methods explicitly implemented by this method. /// /// - /// Methods imported from metadata can explicitly implement more than one method, + /// Methods imported from metadata can explicitly implement more than one method, /// that is why return type is ImmutableArray. /// public abstract ImmutableArray ExplicitInterfaceImplementations { get; } /// - /// Returns the list of custom modifiers, if any, associated with the return type. + /// Returns the list of custom modifiers, if any, associated with the return type. /// public abstract ImmutableArray ReturnTypeCustomModifiers { get; } @@ -309,7 +310,7 @@ public virtual ImmutableArray GetReturnTypeAttributes() /// returns the property that this method is the getter or setter for. /// If this method has MethodKind of MethodKind.EventAdd or MethodKind.EventRemove, /// returns the event that this method is the adder or remover for. - /// Note, the set of possible associated symbols might be expanded in the future to + /// Note, the set of possible associated symbols might be expanded in the future to /// reflect changes in the languages. /// public abstract Symbol AssociatedSymbol { get; } @@ -327,19 +328,19 @@ internal MethodSymbol GetLeastOverriddenMethod(NamedTypeSymbol accessingTypeOpt) while (m.IsOverride && !m.HidesBaseMethodsByName) { // We might not be able to access the overridden method. For example, - // + // // .assembly A // { // InternalsVisibleTo("B") // public class A { internal virtual void M() { } } // } - // + // // .assembly B // { // InternalsVisibleTo("C") // public class B : A { internal override void M() { } } // } - // + // // .assembly C // { // public class C : B { ... new B().M ... } // A.M is not accessible from here @@ -374,9 +375,9 @@ internal MethodSymbol GetConstructedLeastOverriddenMethod(NamedTypeSymbol access /// /// If this method overrides another method (because it both had the override modifier /// and there correctly was a method to override), returns the overridden method. - /// Note that if an overriding method D.M overrides C.M, which in turn overrides + /// Note that if an overriding method D.M overrides C.M, which in turn overrides /// virtual method A.M, the "overridden method" of D.M is C.M, not the original virtual - /// method A.M. Note also that constructed generic methods are not considered to + /// method A.M. Note also that constructed generic methods are not considered to /// override anything. /// public MethodSymbol OverriddenMethod @@ -587,7 +588,7 @@ internal bool IsSubmissionInitializer } /// - /// Determines whether this method is a candidate for a default assembly entry point + /// Determines whether this method is a candidate for a default assembly entry point /// (i.e. it is a static method called "Main"). /// internal bool IsEntryPointCandidate @@ -595,53 +596,6 @@ internal bool IsEntryPointCandidate get { return IsStatic && Name == WellKnownMemberNames.EntryPointMethodName; } } - /// - /// Checks if the method has an entry point compatible signature, i.e. - /// - the return type is either void or int - /// - has either no parameter or a single parameter of type string[] - /// - internal bool HasEntryPointSignature() - { - if (this.IsVararg) - { - return false; - } - - TypeSymbol returnType = ReturnType; - if (returnType.SpecialType != SpecialType.System_Int32 && returnType.SpecialType != SpecialType.System_Void) - { - return false; - } - - if (RefKind != RefKind.None) - { - return false; - } - - if (Parameters.Length == 0) - { - return true; - } - - if (Parameters.Length > 1) - { - return false; - } - - if (!ParameterRefKinds.IsDefault) - { - return false; - } - - var firstType = Parameters[0].Type; - if (firstType.TypeKind != TypeKind.Array) - { - return false; - } - - var array = (ArrayTypeSymbol)firstType; - return array.IsSZArray && array.ElementType.SpecialType == SpecialType.System_String; - } internal override TResult Accept(CSharpSymbolVisitor visitor, TArgument argument) { @@ -738,7 +692,7 @@ public virtual TypeSymbol ReceiverType } /// - /// If this method is a reduced extension method, returns a type inferred during reduction process for the type parameter. + /// If this method is a reduced extension method, returns a type inferred during reduction process for the type parameter. /// /// Type parameter of the corresponding method. /// Inferred type or Nothing if nothing was inferred. @@ -888,7 +842,7 @@ internal bool CalculateUseSiteDiagnostic(ref DiagnosticInfo result) return true; } - // If the member is in an assembly with unified references, + // If the member is in an assembly with unified references, // we check if its definition depends on a type from a unified reference. if (this.ContainingModule.HasUnifiedReferences) { @@ -908,7 +862,7 @@ internal bool CalculateUseSiteDiagnostic(ref DiagnosticInfo result) } /// - /// Return error code that has highest priority while calculating use site error for this symbol. + /// Return error code that has highest priority while calculating use site error for this symbol. /// protected override int HighestPriorityUseSiteError { @@ -949,7 +903,7 @@ internal virtual TypeSymbol IteratorElementType /// /// Generates bound block representing method's body for methods in lowered form and adds it to - /// a collection of method bodies of the current module. This method is supposed to only be + /// a collection of method bodies of the current module. This method is supposed to only be /// called for method symbols which return SynthesizesLoweredBoundBody == true. /// internal virtual void GenerateMethodBody(TypeCompilationState compilationState, DiagnosticBag diagnostics) @@ -980,7 +934,7 @@ internal virtual bool SynthesizesLoweredBoundBody /// /// Syntax offset is a unique identifier for the local within the emitted method body. /// It's based on position of the local declarator. In single-part method bodies it's simply the distance - /// from the start of the method body syntax span. If a method body has multiple parts (such as a constructor + /// from the start of the method body syntax span. If a method body has multiple parts (such as a constructor /// comprising of code for member initializers and constructor initializer calls) the offset is calculated /// as if all source these parts were concatenated together and prepended to the constructor body. /// The resulting syntax offset is then negative for locals defined outside of the constructor body. @@ -1232,7 +1186,7 @@ public virtual bool IsTupleMethod /// /// If this is a method of a tuple type, return corresponding underlying method from the - /// tuple underlying type. Otherwise, null. + /// tuple underlying type. Otherwise, null. /// public virtual MethodSymbol TupleUnderlyingMethod { diff --git a/src/Compilers/CSharp/Portable/Symbols/MethodSymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/MethodSymbolExtensions.cs index a651f6f1b3a9e..8924222d9878d 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MethodSymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MethodSymbolExtensions.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Microsoft.CodeAnalysis.CSharp.Symbols { @@ -300,5 +301,19 @@ public static bool IsGenericTaskReturningAsync(this MethodSymbol method, CSharpC return method.IsAsync && method.ReturnType.IsGenericTaskType(compilation); } + + internal static CSharpSyntaxNode ExtractReturnTypeSyntax(this MethodSymbol method) + { + method = method.PartialDefinitionPart ?? method; + foreach (var reference in method.DeclaringSyntaxReferences) + { + if (reference.GetSyntax() is MethodDeclarationSyntax methodDeclaration) + { + return methodDeclaration.ReturnType; + } + } + + return (CSharpSyntaxNode)CSharpSyntaxTree.Dummy.GetRoot(); + } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs index 1d45cc848a95f..7dff9eb3426f5 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs @@ -1,9 +1,11 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Symbols @@ -17,7 +19,6 @@ internal abstract class SynthesizedEntryPointSymbol : MethodSymbol internal const string FactoryName = ""; private readonly NamedTypeSymbol _containingType; - private readonly TypeSymbol _returnType; internal static SynthesizedEntryPointSymbol Create(SynthesizedInteractiveInitializerMethod initializerMethod, DiagnosticBag diagnostics) { @@ -54,13 +55,11 @@ internal static SynthesizedEntryPointSymbol Create(SynthesizedInteractiveInitial } } - private SynthesizedEntryPointSymbol(NamedTypeSymbol containingType, TypeSymbol returnType) + private SynthesizedEntryPointSymbol(NamedTypeSymbol containingType) { Debug.Assert((object)containingType != null); - Debug.Assert((object)returnType != null); _containingType = containingType; - _returnType = returnType; } internal override bool GenerateDebugInfo @@ -128,11 +127,6 @@ internal override RefKind RefKind get { return RefKind.None; } } - public override TypeSymbol ReturnType - { - get { return _returnType; } - } - public override ImmutableArray ReturnTypeCustomModifiers { get { return ImmutableArray.Empty; } @@ -160,7 +154,7 @@ public override int Arity public override bool ReturnsVoid { - get { return _returnType.SpecialType == SpecialType.System_Void; } + get { return ReturnType.SpecialType == SpecialType.System_Void; } } public override MethodKind MethodKind @@ -286,7 +280,7 @@ internal override int CalculateLocalSyntaxOffset(int localPosition, SyntaxTree l throw ExceptionUtilities.Unreachable; } - private CSharpSyntaxNode GetSyntax() + private static CSharpSyntaxNode DummySyntax() { var syntaxTree = CSharpSyntaxTree.Dummy; return (CSharpSyntaxNode)syntaxTree.GetRoot(); @@ -329,30 +323,127 @@ private static BoundCall CreateParameterlessCall(CSharpSyntaxNode syntax, BoundE { WasCompilerGenerated = true }; } + /// A synthesized entrypoint that forwards all calls to an async Main Method + internal sealed class AsyncForwardEntryPoint : SynthesizedEntryPointSymbol + { + /// The user-defined asynchronous main method. + private readonly CSharpSyntaxNode _userMainReturnTypeSyntax; + + private readonly BoundExpression _getAwaiterGetResultCall; + + private readonly ImmutableArray _parameters; + + internal AsyncForwardEntryPoint(CSharpCompilation compilation, NamedTypeSymbol containingType, MethodSymbol userMain) : + base(containingType) + { + // There should be no way for a userMain to be passed in unless it already passed the + // parameter checks for determining entrypoint validity. + Debug.Assert(userMain.ParameterCount == 0 || userMain.ParameterCount == 1); + + _userMainReturnTypeSyntax = userMain.ExtractReturnTypeSyntax(); + var binder = compilation.GetBinder(_userMainReturnTypeSyntax); + _parameters = SynthesizedParameterSymbol.DeriveParameters(userMain, this); + + var arguments = Parameters.SelectAsArray((p, s) => (BoundExpression)new BoundParameter(s, p, p.Type), _userMainReturnTypeSyntax); + + // Main(args) or Main() + BoundCall userMainInvocation = new BoundCall( + syntax: _userMainReturnTypeSyntax, + receiverOpt: null, + method: userMain, + arguments: arguments, + argumentNamesOpt: default(ImmutableArray), + argumentRefKindsOpt: default(ImmutableArray), + isDelegateCall: false, + expanded: false, + invokedAsExtensionMethod: false, + argsToParamsOpt: default(ImmutableArray), + resultKind: LookupResultKind.Viable, + type: userMain.ReturnType) + { WasCompilerGenerated = true }; + + // The diagnostics that would be produced here will already have been captured and returned. + var droppedBag = DiagnosticBag.GetInstance(); + var success = binder.GetAwaitableExpressionInfo(userMainInvocation, out _, out _, out _, out _getAwaiterGetResultCall, _userMainReturnTypeSyntax, droppedBag); + droppedBag.Free(); + + Debug.Assert( + ReturnType.SpecialType == SpecialType.System_Void || + ReturnType.SpecialType == SpecialType.System_Int32); + } + + public override string Name => MainName; + + public override ImmutableArray Parameters => _parameters; + + public override TypeSymbol ReturnType => _getAwaiterGetResultCall.Type; + + internal override BoundBlock CreateBody() + { + var syntax = _userMainReturnTypeSyntax; + + if (ReturnsVoid) + { + return new BoundBlock( + syntax: syntax, + locals: ImmutableArray.Empty, + statements: ImmutableArray.Create( + new BoundExpressionStatement( + syntax: syntax, + expression: _getAwaiterGetResultCall + ) + { WasCompilerGenerated = true }, + new BoundReturnStatement( + syntax: syntax, + refKind: RefKind.None, + expressionOpt: null + ) + { WasCompilerGenerated = true } + ) + ) + { WasCompilerGenerated = true }; + + } + else + { + return new BoundBlock( + syntax: syntax, + locals: ImmutableArray.Empty, + statements: ImmutableArray.Create( + new BoundReturnStatement( + syntax: syntax, + refKind: RefKind.None, + expressionOpt: _getAwaiterGetResultCall + ) + ) + ) + { WasCompilerGenerated = true }; + } + } + } + private sealed class ScriptEntryPoint : SynthesizedEntryPointSymbol { private readonly MethodSymbol _getAwaiterMethod; private readonly MethodSymbol _getResultMethod; + private readonly TypeSymbol _returnType; internal ScriptEntryPoint(NamedTypeSymbol containingType, TypeSymbol returnType, MethodSymbol getAwaiterMethod, MethodSymbol getResultMethod) : - base(containingType, returnType) + base(containingType) { Debug.Assert(containingType.IsScriptClass); Debug.Assert(returnType.SpecialType == SpecialType.System_Void); _getAwaiterMethod = getAwaiterMethod; _getResultMethod = getResultMethod; + _returnType = returnType; } - public override string Name - { - get { return MainName; } - } + public override string Name => MainName; - public override ImmutableArray Parameters - { - get { return ImmutableArray.Empty; } - } + public override ImmutableArray Parameters => ImmutableArray.Empty; + + public override TypeSymbol ReturnType => _returnType; // private static void
() // { @@ -365,7 +456,7 @@ internal override BoundBlock CreateBody() Debug.Assert((object)_getAwaiterMethod != null); Debug.Assert((object)_getResultMethod != null); - var syntax = this.GetSyntax(); + var syntax = DummySyntax(); var ctor = _containingType.GetScriptConstructor(); Debug.Assert(ctor.ParameterCount == 0); @@ -423,13 +514,15 @@ internal override BoundBlock CreateBody() private sealed class SubmissionEntryPoint : SynthesizedEntryPointSymbol { private readonly ImmutableArray _parameters; + private readonly TypeSymbol _returnType; internal SubmissionEntryPoint(NamedTypeSymbol containingType, TypeSymbol returnType, TypeSymbol submissionArrayType) : - base(containingType, returnType) + base(containingType) { Debug.Assert(containingType.IsSubmissionClass); Debug.Assert(returnType.SpecialType != SpecialType.System_Void); _parameters = ImmutableArray.Create(SynthesizedParameterSymbol.Create(this, submissionArrayType, 0, RefKind.None, "submissionArray")); + _returnType = returnType; } public override string Name @@ -442,6 +535,8 @@ public override ImmutableArray Parameters get { return _parameters; } } + public override TypeSymbol ReturnType => _returnType; + // private static T (object[] submissionArray) // { // var submission = new Submission#N(submissionArray); @@ -449,7 +544,7 @@ public override ImmutableArray Parameters // } internal override BoundBlock CreateBody() { - var syntax = this.GetSyntax(); + var syntax = DummySyntax(); var ctor = _containingType.GetScriptConstructor(); Debug.Assert(ctor.ParameterCount == 1); diff --git a/src/Compilers/CSharp/Test/Emit/CSharpCompilerEmitTest.csproj b/src/Compilers/CSharp/Test/Emit/CSharpCompilerEmitTest.csproj index 2a8c915886eb3..768e822aefad3 100644 --- a/src/Compilers/CSharp/Test/Emit/CSharpCompilerEmitTest.csproj +++ b/src/Compilers/CSharp/Test/Emit/CSharpCompilerEmitTest.csproj @@ -70,6 +70,7 @@ + diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncMainTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncMainTests.cs new file mode 100644 index 0000000000000..34bd9f103093a --- /dev/null +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncMainTests.cs @@ -0,0 +1,1462 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Xunit; +using System.Threading; +using Microsoft.CodeAnalysis.Test.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen +{ + [CompilerTrait(CompilerFeature.AsyncMain)] + public class CodeGenAsyncMainTests : EmitMetadataTestBase + { + [Fact] + public void MultipleMainsOneOfWhichHasBadTaskType_CSharp71_WithMainType() + { + var source = @" +using System.Threading.Tasks; + +namespace System.Threading.Tasks { + public class Task { + public void GetAwaiter() {} + } +} + +static class Program { + static Task Main() { + return null; + } + static void Main(string[] args) { } +}"; + var sourceCompilation = CreateStandardCompilation(source, options: TestOptions.DebugExe.WithMainTypeName("Program"), parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + sourceCompilation.VerifyEmitDiagnostics( + // (11,12): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // static Task Main() { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(11, 12)); + } + + [Fact] + public void MultipleMainsOneOfWhichHasBadTaskType_CSharp7() + { + var source = @" +using System.Threading.Tasks; + +namespace System.Threading.Tasks { + public class Task { + public void GetAwaiter() {} + } +} + +static class Program { + static Task Main() { + return null; + } + static void Main(string[] args) { } +}"; + var sourceCompilation = CreateStandardCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)); + sourceCompilation.VerifyEmitDiagnostics( + // (11,12): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // static Task Main() { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(11, 12), + // (11,22): warning CS0028: 'Program.Main()' has the wrong signature to be an entry point + // static Task Main() { + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("Program.Main()").WithLocation(11, 22)); + } + + [Fact] + public void MultipleMainsOneOfWhichHasBadTaskType_CSharp7_WithExplicitMain() + { + var source = @" +using System.Threading.Tasks; + +namespace System.Threading.Tasks { + public class Task { + public void GetAwaiter() {} + } +} + +static class Program { + static Task Main() { + return null; + } + static void Main(string[] args) { } +}"; + var sourceCompilation = CreateStandardCompilation(source, options: TestOptions.DebugExe.WithMainTypeName("Program"), parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)); + sourceCompilation.VerifyEmitDiagnostics( + // (11,12): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // static Task Main() { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(11, 12)); + } + + [Fact] + public void MultipleMainsOneOfWhichHasBadTaskType_CSharp71() + { + var source = @" +using System.Threading.Tasks; + +namespace System.Threading.Tasks { + public class Task { + public void GetAwaiter() {} + } +} + +static class Program { + static Task Main() { + return null; + } + static void Main(string[] args) { } +}"; + var sourceCompilation = CreateStandardCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + sourceCompilation.VerifyEmitDiagnostics( + // (11,12): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // static Task Main() { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(11, 12), + // (11,22): warning CS0028: 'Program.Main()' has the wrong signature to be an entry point + // static Task Main() { + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("Program.Main()").WithLocation(11, 22)); + } + + [Fact] + public void GetResultReturnsSomethingElse_CSharp7() + { + var source = @" +using System.Threading.Tasks; +using System; + +namespace System.Runtime.CompilerServices { + public interface INotifyCompletion { + void OnCompleted(Action action); + } +} + +namespace System.Threading.Tasks { + public class Awaiter: System.Runtime.CompilerServices.INotifyCompletion { + public double GetResult() { return 0.0; } + public bool IsCompleted => true; + public void OnCompleted(Action action) {} + } + public class Task { + public Awaiter GetAwaiter() { + return new Awaiter(); + } + } +} + +static class Program { + static Task Main() { + return null; + } +}"; + var sourceCompilation = CreateStandardCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)); + sourceCompilation.VerifyEmitDiagnostics( + // (25,12): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // static Task Main() { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(25, 12), + // (25,22): warning CS0028: 'Program.Main()' has the wrong signature to be an entry point + // static Task Main() { + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("Program.Main()").WithLocation(25, 22), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void GetResultReturnsSomethingElse_CSharp71() + { + var source = @" +using System.Threading.Tasks; +using System; + +namespace System.Runtime.CompilerServices { + public interface INotifyCompletion { + void OnCompleted(Action action); + } +} + +namespace System.Threading.Tasks { + public class Awaiter: System.Runtime.CompilerServices.INotifyCompletion { + public double GetResult() { return 0.0; } + public bool IsCompleted => true; + public void OnCompleted(Action action) {} + } + public class Task { + public Awaiter GetAwaiter() { + return new Awaiter(); + } + } +} + +static class Program { + static Task Main() { + return null; + } +}"; + var sourceCompilation = CreateStandardCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + sourceCompilation.VerifyEmitDiagnostics( + // (25,12): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // static Task Main() { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(25, 12), + // (25,22): warning CS0028: 'Program.Main()' has the wrong signature to be an entry point + // static Task Main() { + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("Program.Main()").WithLocation(25, 22), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void TaskOfTGetAwaiterReturnsVoid_CSharp7() + { + var source = @" +using System; +using System.Threading.Tasks; + +namespace System.Threading.Tasks { + public class Task { + public void GetAwaiter() {} + } +} + +static class Program { + static Task Main() { + return null; + } +}"; + + var sourceCompilation = CreateStandardCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)); + sourceCompilation.VerifyDiagnostics( + // (12,12): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // static Task Main() { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(12, 12), + // (12,12): error CS1986: 'await' requires that the type Task have a suitable GetAwaiter method + // static Task Main() { + Diagnostic(ErrorCode.ERR_BadAwaitArg, "Task").WithArguments("System.Threading.Tasks.Task").WithLocation(12, 12), + // (12,22): warning CS0028: 'Program.Main()' has the wrong signature to be an entry point + // static Task Main() { + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("Program.Main()").WithLocation(12, 22), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1), + // (2,1): hidden CS8019: Unnecessary using directive. + // using System; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using System;").WithLocation(2, 1)); + } + + [Fact] + public void TaskOfTGetAwaiterReturnsVoid_CSharp71() + { + var source = @" +using System; +using System.Threading.Tasks; + +namespace System.Threading.Tasks { + public class Task { + public void GetAwaiter() {} + } +} + +static class Program { + static Task Main() { + return null; + } +}"; + + var sourceCompilation = CreateStandardCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + sourceCompilation.VerifyDiagnostics( + // (12,12): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // static Task Main() { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(12, 12), + // (12,12): error CS1986: 'await' requires that the type Task have a suitable GetAwaiter method + // static Task Main() { + Diagnostic(ErrorCode.ERR_BadAwaitArg, "Task").WithArguments("System.Threading.Tasks.Task").WithLocation(12, 12), + // (12,22): warning CS0028: 'Program.Main()' has the wrong signature to be an entry point + // static Task Main() { + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("Program.Main()").WithLocation(12, 22), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1), + // (2,1): hidden CS8019: Unnecessary using directive. + // using System; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using System;").WithLocation(2, 1)); + } + + [Fact] + public void TaskGetAwaiterReturnsVoid() + { + var source = @" +using System; +using System.Threading.Tasks; + +namespace System.Threading.Tasks { + public class Task { + public void GetAwaiter() {} + } +} + +static class Program { + static Task Main() { + return null; + } +}"; + var sourceCompilation = CreateStandardCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + sourceCompilation.VerifyEmitDiagnostics( + // (12,12): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // static Task Main() { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(12, 12), + // (12,12): error CS1986: 'await' requires that the type Task have a suitable GetAwaiter method + // static Task Main() { + Diagnostic(ErrorCode.ERR_BadAwaitArg, "Task").WithArguments("System.Threading.Tasks.Task").WithLocation(12, 12), + // (12,17): warning CS0028: 'Program.Main()' has the wrong signature to be an entry point + // static Task Main() { + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("Program.Main()").WithLocation(12, 17), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void MissingMethodsOnTask() + { + var source = @" +using System; +using System.Threading.Tasks; + +namespace System.Threading.Tasks { + public class Task {} +} + +static class Program { + static Task Main() { + return null; + } +}"; + var sourceCompilation = CreateStandardCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + sourceCompilation.VerifyEmitDiagnostics( + // (10,12): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // static Task Main() { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(10, 12), + // (10,12): error CS1061: 'Task' does not contain a definition for 'GetAwaiter' and no extension method 'GetAwaiter' accepting a first argument of type 'Task' could be found (are you missing a using directive or an assembly reference?) + // static Task Main() { + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "Task").WithArguments("System.Threading.Tasks.Task", "GetAwaiter").WithLocation(10, 12), + // (10,17): warning CS0028: 'Program.Main()' has the wrong signature to be an entry point + // static Task Main() { + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("Program.Main()").WithLocation(10, 17), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void EmitTaskOfIntReturningMainWithoutInt() + { + var corAssembly = @" +namespace System { + public class Object {} + public abstract class ValueType{} + public struct Int32{} +}"; + var corCompilation = CreateCompilation(corAssembly, options: TestOptions.DebugDll); + corCompilation.VerifyDiagnostics(); + + var taskAssembly = @" +namespace System.Threading.Tasks { + public class Task{} +}"; + var taskCompilation = CreateCompilationWithMscorlib45(taskAssembly, options: TestOptions.DebugDll); + taskCompilation.VerifyDiagnostics(); + + var source = @" +using System; +using System.Threading.Tasks; + +static class Program { + static Task Main() { + return null; + } +}"; + var sourceCompilation = CreateCompilation(source, new[] { corCompilation.ToMetadataReference(), taskCompilation.ToMetadataReference() }, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + sourceCompilation.VerifyEmitDiagnostics( + // warning CS8021: No value for RuntimeMetadataVersion found. No assembly containing System.Object was found nor was a value for RuntimeMetadataVersion specified through options. + Diagnostic(ErrorCode.WRN_NoRuntimeMetadataVersion).WithLocation(1, 1), + // (6,17): error CS0518: Predefined type 'System.Int32' is not defined or imported + // static Task Main() { + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "Task").WithArguments("System.Threading.Tasks.Task", "GetAwaiter").WithLocation(6, 12), + // (6,22): warning CS0028: 'Program.Main()' has the wrong signature to be an entry point + // static Task Main() { + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("Program.Main()").WithLocation(6, 22), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void EmitTaskReturningMainWithoutVoid() + { + var corAssembly = @" +namespace System { + public class Object {} +}"; + var corCompilation = CreateCompilation(corAssembly, options: TestOptions.DebugDll); + corCompilation.VerifyDiagnostics(); + + var taskAssembly = @" +namespace System.Threading.Tasks { + public class Task{} +}"; + var taskCompilation = CreateCompilationWithMscorlib45(taskAssembly, options: TestOptions.DebugDll); + taskCompilation.VerifyDiagnostics(); + + var source = @" +using System; +using System.Threading.Tasks; + +static class Program { + static Task Main() { + return null; + } +}"; + var sourceCompilation = CreateCompilation(source, new[] { corCompilation.ToMetadataReference(), taskCompilation.ToMetadataReference() }, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + sourceCompilation.VerifyEmitDiagnostics( + // warning CS8021: No value for RuntimeMetadataVersion found. No assembly containing System.Object was found nor was a value for RuntimeMetadataVersion specified through options. + Diagnostic(ErrorCode.WRN_NoRuntimeMetadataVersion).WithLocation(1, 1), + // (6,12): error CS1061: 'Task' does not contain a definition for 'GetAwaiter' and no extension method 'GetAwaiter' accepting a first argument of type 'Task' could be found (are you missing a using directive or an assembly reference?) + // static Task Main() { + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "Task").WithArguments("System.Threading.Tasks.Task", "GetAwaiter").WithLocation(6, 12), + // (6,17): warning CS0028: 'Program.Main()' has the wrong signature to be an entry point + // static Task Main() { + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("Program.Main()").WithLocation(6, 17), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void AsyncEmitMainTest() + { + var source = @" +using System; +using System.Threading.Tasks; + +class Program { + static async Task Main() { + Console.Write(""hello ""); + await Task.Factory.StartNew(() => 5); + Console.Write(""async main""); + } +}"; + var c = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + var verifier = CompileAndVerify(c, expectedOutput: "hello async main", expectedReturnCode: 0); + } + + [Fact] + public void AsyncMainTestCodegenWithErrors() + { + var source = @" +using System; +using System.Threading.Tasks; + +class Program { + static async Task Main() { + Console.WriteLine(""hello""); + await Task.Factory.StartNew(() => 5); + Console.WriteLine(""async main""); + } +}"; + var c = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + c.VerifyEmitDiagnostics( + // (6,28): error CS0161: 'Program.Main()': not all code paths return a value + // static async Task Main() { + Diagnostic(ErrorCode.ERR_ReturnExpected, "Main").WithArguments("Program.Main()").WithLocation(6, 28)); + } + + + [Fact] + public void AsyncEmitMainOfIntTest_StringArgs() + { + var source = @" +using System; +using System.Threading.Tasks; + +class Program { + static async Task Main(string[] args) { + Console.Write(""hello ""); + await Task.Factory.StartNew(() => 5); + Console.Write(""async main""); + return 10; + } +}"; + var c = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + var verifier = CompileAndVerify(c, expectedOutput: "hello async main", expectedReturnCode: 10); + } + + [Fact] + public void AsyncEmitMainOfIntTest_ParamsStringArgs() + { + var source = @" +using System; +using System.Threading.Tasks; + +class Program { + static async Task Main(params string[] args) { + Console.Write(""hello ""); + await Task.Factory.StartNew(() => 5); + Console.Write(""async main""); + return 10; + } +}"; + var c = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + var verifier = CompileAndVerify(c, expectedOutput: "hello async main", expectedReturnCode: 10); + } + + [Fact] + public void AsyncEmitMainTest_StringArgs() + { + var source = @" +using System; +using System.Threading.Tasks; + +class Program { + static async Task Main(string[] args) { + Console.Write(""hello ""); + await Task.Factory.StartNew(() => 5); + Console.Write(""async main""); + } +}"; + var c = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + var verifier = CompileAndVerify(c, expectedOutput: "hello async main", expectedReturnCode: 0); + } + + [Fact] + public void AsyncEmitMainTestCodegenWithErrors_StringArgs() + { + var source = @" +using System; +using System.Threading.Tasks; + +class Program { + static async Task Main(string[] args) { + Console.WriteLine(""hello""); + await Task.Factory.StartNew(() => 5); + Console.WriteLine(""async main""); + } +}"; + var c = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + c.VerifyEmitDiagnostics( + // (6,28): error CS0161: 'Program.Main()': not all code paths return a value + // static async Task Main() { + Diagnostic(ErrorCode.ERR_ReturnExpected, "Main").WithArguments("Program.Main(string[])").WithLocation(6, 28)); + } + + [Fact] + public void AsyncEmitMainOfIntTest_StringArgs_WithArgs() + { + var source = @" +using System; +using System.Threading.Tasks; + +class Program { + static async Task Main(string[] args) { + Console.Write(""hello ""); + await Task.Factory.StartNew(() => 5); + Console.Write(args[0]); + return 10; + } +}"; + var c = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + var verifier = CompileAndVerify(c, expectedOutput: "hello async main", expectedReturnCode: 10, args: new string[] { "async main" }); + } + + [Fact] + public void AsyncEmitMainTest_StringArgs_WithArgs() + { + var source = @" +using System; +using System.Threading.Tasks; + +class Program { + static async Task Main(string[] args) { + Console.Write(""hello ""); + await Task.Factory.StartNew(() => 5); + Console.Write(args[0]); + } +}"; + var c = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + var verifier = CompileAndVerify(c, expectedOutput: "hello async main", expectedReturnCode: 0, args: new string[] { "async main" }); + } + + [Fact] + public void MainCanBeAsyncWithArgs() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static Task Main(string[] args) + { + await Task.Factory.StartNew(() => { }); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + compilation.VerifyDiagnostics(); + var entry = compilation.GetEntryPoint(CancellationToken.None); + Assert.NotNull(entry); + Assert.Equal("System.Threading.Tasks.Task A.Main(System.String[] args)", entry.ToTestDisplayString()); + + CompileAndVerify(compilation, expectedReturnCode: 0); + } + + [Fact] + public void MainCanReturnTaskWithArgs_NoAsync() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + static Task Main(string[] args) + { + return Task.Factory.StartNew(() => { }); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + compilation.VerifyDiagnostics(); + var entry = compilation.GetEntryPoint(CancellationToken.None); + Assert.NotNull(entry); + Assert.Equal("System.Threading.Tasks.Task A.Main(System.String[] args)", entry.ToTestDisplayString()); + + CompileAndVerify(compilation, expectedReturnCode: 0); + } + + + [Fact] + public void MainCantBeAsyncWithRefTask() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + static ref Task Main(string[] args) + { + throw new System.Exception(); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + compilation.VerifyDiagnostics( + // (6,21): warning CS0028: 'A.Main(string[])' has the wrong signature to be an entry point + // static ref Task Main(string[] args) + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("A.Main(string[])").WithLocation(6, 21), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void MainCantBeAsyncWithArgs_CSharp7() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static Task Main(string[] args) + { + await Task.Factory.StartNew(() => { }); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)); + compilation.VerifyDiagnostics( + // (6,18): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater. + // async static Task Main(string[] args) + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7, "Task").WithArguments("async main", "7.1").WithLocation(6, 18), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void MainCantBeAsyncWithArgs_CSharp7_NoAwait() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + static Task Main(string[] args) + { + return Task.Factory.StartNew(() => { }); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)); + compilation.VerifyDiagnostics( + // (6,18): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater. + // async static Task Main(string[] args) + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7, "Task").WithArguments("async main", "7.1").WithLocation(6, 12), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void MainCanReturnTask() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static Task Main() + { + await Task.Factory.StartNew(() => { }); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + compilation.VerifyDiagnostics(); + var entry = compilation.GetEntryPoint(CancellationToken.None); + Assert.NotNull(entry); + Assert.Equal("System.Threading.Tasks.Task A.Main()", entry.ToTestDisplayString()); + } + [Fact] + public void MainCanReturnTask_NoAsync() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + static Task Main() + { + return Task.Factory.StartNew(() => { }); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + compilation.VerifyDiagnostics(); + var entry = compilation.GetEntryPoint(CancellationToken.None); + Assert.NotNull(entry); + Assert.Equal("System.Threading.Tasks.Task A.Main()", entry.ToTestDisplayString()); + } + + [Fact] + public void MainCantBeAsyncVoid_CSharp7() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static void Main() + { + await Task.Factory.StartNew(() => { }); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)); + compilation.VerifyDiagnostics( + // (6,23): error CS9003: Async Main methods must return Task or Task + // async static void Main() + Diagnostic(ErrorCode.ERR_NonTaskMainCantBeAsync, "Main").WithArguments("A.Main()").WithLocation(6, 23), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void MainCantBeAsyncInt() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static int Main() + { + return await Task.Factory.StartNew(() => 5); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + compilation.VerifyDiagnostics( + // (6,22): error CS1983: The return type of an async method must be void, Task or Task + // async static int Main() + Diagnostic(ErrorCode.ERR_BadAsyncReturn, "Main").WithLocation(6, 22), + // (6,22): error CS4009: A void or int returning entry point cannot be async + // async static int Main() + Diagnostic(ErrorCode.ERR_NonTaskMainCantBeAsync, "Main").WithArguments("A.Main()").WithLocation(6, 22), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + var entry = compilation.GetEntryPoint(CancellationToken.None); + Assert.Null(entry); + } + + [Fact] + public void MainCantBeAsyncInt_CSharp7() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static int Main() + { + return await Task.Factory.StartNew(() => 5); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)); + compilation.VerifyDiagnostics( + // (6,22): error CS1983: The return type of an async method must be void, Task or Task + // async static int Main() + Diagnostic(ErrorCode.ERR_BadAsyncReturn, "Main").WithLocation(6, 22), + // (6,22): error CS9003: Async Main methods must return Task or Task + // async static int Main() + Diagnostic(ErrorCode.ERR_NonTaskMainCantBeAsync, "Main").WithArguments("A.Main()").WithLocation(6, 22), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void MainCanReturnTaskAndGenericOnInt_WithArgs() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static Task Main(string[] args) + { + return await Task.Factory.StartNew(() => 5); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + compilation.VerifyDiagnostics(); + var entry = compilation.GetEntryPoint(CancellationToken.None); + Assert.NotNull(entry); + Assert.Equal("System.Threading.Tasks.Task A.Main(System.String[] args)", entry.ToTestDisplayString()); + } + + [Fact] + public void MainCanReturnTaskAndGenericOnInt_WithArgs_NoAsync() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + static Task Main(string[] args) + { + return Task.Factory.StartNew(() => 5); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + compilation.VerifyDiagnostics(); + var entry = compilation.GetEntryPoint(CancellationToken.None); + Assert.NotNull(entry); + Assert.Equal("System.Threading.Tasks.Task A.Main(System.String[] args)", entry.ToTestDisplayString()); + } + + [Fact] + public void MainCantBeAsyncAndGenericOnInt_WithArgs_Csharp7() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static Task Main(string[] args) + { + return await Task.Factory.StartNew(() => 5); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)); + compilation.VerifyDiagnostics( + // (6,18): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater. + // async static Task Main(string[] args) + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7, "Task").WithArguments("async main", "7.1").WithLocation(6, 18), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void MainCantBeAsyncAndGenericOnInt_WithArgs_Csharp7_NoAsync() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + static Task Main(string[] args) + { + return Task.Factory.StartNew(() => 5); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)); + compilation.VerifyDiagnostics( + // (6,18): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater. + // async static Task Main(string[] args) + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7, "Task").WithArguments("async main", "7.1").WithLocation(6, 12), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void MainCanReturnTaskAndGenericOnInt() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static Task Main() + { + return await Task.Factory.StartNew(() => 5); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + compilation.VerifyDiagnostics(); + var entry = compilation.GetEntryPoint(CancellationToken.None); + Assert.NotNull(entry); + Assert.Equal("System.Threading.Tasks.Task A.Main()", entry.ToTestDisplayString()); + } + + [Fact] + public void MainCanReturnTaskAndGenericOnInt_NoAsync() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + static Task Main() + { + return Task.Factory.StartNew(() => 5); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + compilation.VerifyDiagnostics(); + var entry = compilation.GetEntryPoint(CancellationToken.None); + Assert.NotNull(entry); + Assert.Equal("System.Threading.Tasks.Task A.Main()", entry.ToTestDisplayString()); + } + + [Fact] + public void MainCantBeAsyncAndGenericOnInt_CSharp7() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static Task Main() + { + return await Task.Factory.StartNew(() => 5); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)); + compilation.VerifyDiagnostics( + // (6,18): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater. + // async static Task Main() + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7, "Task").WithArguments("async main", "7.1").WithLocation(6, 18), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void MainCantBeAsyncAndGenericOnInt_CSharp7_NoAsync() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + static Task Main() + { + return Task.Factory.StartNew(() => 5); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)); + compilation.VerifyDiagnostics( + // (6,18): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater. + // async static Task Main() + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7, "Task").WithArguments("async main", "7.1").WithLocation(6, 12), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void MainCantBeAsyncAndGenericOverFloats() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static Task Main() + { + await Task.Factory.StartNew(() => { }); + return 0; + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + compilation.VerifyDiagnostics( + // (6,30): warning CS0028: 'A.Main()' has the wrong signature to be an entry point + // async static Task Main() + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("A.Main()").WithLocation(6, 30), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void MainCantBeAsync_AndGeneric() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static void Main() + { + await Task.Factory.StartNew(() => { }); + } +}"; + CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)).VerifyDiagnostics( + // (6,23): warning CS0402: 'A.Main()': an entry point cannot be generic or in a generic type + // async static void Main() + Diagnostic(ErrorCode.WRN_MainCantBeGeneric, "Main").WithArguments("A.Main()").WithLocation(6, 23), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void MainCantBeAsync_AndBadSig() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static void Main(bool truth) + { + await Task.Factory.StartNew(() => { }); + } +}"; + CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe).VerifyDiagnostics( + // (6,23): warning CS0028: 'A.Main(bool)' has the wrong signature to be an entry point + // async static void Main(bool truth) + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("A.Main(bool)").WithLocation(6, 23), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void MainCantBeAsync_AndGeneric_AndBadSig() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static void Main(bool truth) + { + await Task.Factory.StartNew(() => { }); + } +}"; + CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe).VerifyDiagnostics( + // (6,23): warning CS0028: 'A.Main(bool)' has the wrong signature to be an entry point + // async static void Main(bool truth) + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("A.Main(bool)").WithLocation(6, 23), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void TaskMainAndNonTaskMain() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + static void Main() + { + System.Console.WriteLine(""Non Task Main""); + } + async static Task Main(string[] args) + { + await Task.Factory.StartNew(() => { }); + System.Console.WriteLine(""Task Main""); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)).VerifyDiagnostics(); + CompileAndVerify(compilation, expectedOutput: "Non Task Main", expectedReturnCode: 0); + } + + [Fact] + public void TaskMainAndNonTaskMain_CSharp71() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + static void Main() + { + System.Console.WriteLine(""Non Task Main""); + } + async static Task Main(string[] args) + { + await Task.Factory.StartNew(() => { }); + System.Console.WriteLine(""Task Main""); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)).VerifyDiagnostics(); + CompileAndVerify(compilation, expectedOutput: "Non Task Main", expectedReturnCode: 0); + } + + [Fact] + public void AsyncVoidMain_CSharp7() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static void Main(string[] args) + { + await Task.Factory.StartNew(() => { }); + System.Console.WriteLine(""Async Void Main""); + } + async static int Main() + { + await Task.Factory.StartNew(() => { }); + System.Console.WriteLine(""Async Void Main""); + return 1; + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe).VerifyDiagnostics( + // (11,22): error CS1983: The return type of an async method must be void, Task or Task + // async static int Main() + Diagnostic(ErrorCode.ERR_BadAsyncReturn, "Main").WithLocation(11, 22), + // (11,22): error CS4009: A void or int returning entry point cannot be async + // async static int Main() + Diagnostic(ErrorCode.ERR_NonTaskMainCantBeAsync, "Main").WithArguments("A.Main()").WithLocation(11, 22), + // (6,23): error CS4009: A void or int returning entry point cannot be async + // async static void Main(string[] args) + Diagnostic(ErrorCode.ERR_NonTaskMainCantBeAsync, "Main").WithArguments("A.Main(string[])").WithLocation(6, 23), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void AsyncVoidMain_CSharp71() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + static async void Main(string[] args) + { + await Task.Factory.StartNew(() => { }); + System.Console.WriteLine(""Async Void Main""); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)).VerifyDiagnostics( + // (6,23): error CS4009: A void or int returning entry point cannot be async + // static async void Main(string[] args) + Diagnostic(ErrorCode.ERR_NonTaskMainCantBeAsync, "Main").WithArguments("A.Main(string[])").WithLocation(6, 23), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void TaskMainAndNonTaskMain_WithExplicitMain() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + static void Main() + { + System.Console.WriteLine(""Non Task Main""); + } + async static Task Main(string[] args) + { + await Task.Factory.StartNew(() => { }); + System.Console.WriteLine(""Task Main""); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe.WithMainTypeName("A")).VerifyDiagnostics(); + CompileAndVerify(compilation, expectedOutput: "Non Task Main", expectedReturnCode: 0); + } + + [Fact] + public void TaskIntAndTaskFloat_CSharp7() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + async static Task Main() + { + System.Console.WriteLine(""Task""); + return 0; + } + + async static Task Main(string[] args) + { + System.Console.WriteLine(""Task""); + return 0.0F; + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseDebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)).VerifyDiagnostics( + // (6,28): 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 static Task Main() + Diagnostic(ErrorCode.WRN_AsyncLacksAwaits, "Main").WithLocation(6, 28), + // (12,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 static Task Main(string[] args) + Diagnostic(ErrorCode.WRN_AsyncLacksAwaits, "Main").WithLocation(12, 30), + // (6,18): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater. + // async static Task Main() + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7, "Task").WithArguments("async main", "7.1").WithLocation(6, 18), + // (12,30): warning CS0028: 'A.Main(string[])' has the wrong signature to be an entry point + // async static Task Main(string[] args) + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("A.Main(string[])").WithLocation(12, 30), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void TaskIntAndTaskFloat_CSharp7_NoAsync() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + static Task Main() + { + System.Console.WriteLine(""Task""); + return Task.FromResult(0); + } + + static Task Main(string[] args) + { + System.Console.WriteLine(""Task""); + return Task.FromResult(0.0F); + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseDebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)).VerifyDiagnostics( + // (6,12): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater. + // static Task Main() + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7, "Task").WithArguments("async main", "7.1").WithLocation(6, 12), + // (12,24): warning CS0028: 'A.Main(string[])' has the wrong signature to be an entry point + // static Task Main(string[] args) + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("A.Main(string[])").WithLocation(12, 24), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [Fact] + public void TaskOfFloatMainAndNonTaskMain() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + static void Main() + { + System.Console.WriteLine(""Non Task Main""); + } + + async static Task Main(string[] args) + { + await Task.Factory.StartNew(() => { }); + System.Console.WriteLine(""Task Main""); + return 0.0f; + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe).VerifyDiagnostics( + // (10,30): warning CS0028: 'A.Main(string[])' has the wrong signature to be an entry point + // async static Task Main(string[] args) + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("A.Main(string[])").WithLocation(11, 30)); + CompileAndVerify(compilation, expectedOutput: "Non Task Main", expectedReturnCode: 0); + } + + [Fact] + public void TaskOfFloatMainAndNonTaskMain_WithExplicitMain() + { + var source = @" +using System.Threading.Tasks; + +class A +{ + static void Main() + { + System.Console.WriteLine(""Non Task Main""); + } + + async static Task Main(string[] args) + { + await Task.Factory.StartNew(() => { }); + System.Console.WriteLine(""Task Main""); + return 0.0f; + } +}"; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe.WithMainTypeName("A")).VerifyDiagnostics(); + CompileAndVerify(compilation, expectedOutput: "Non Task Main", expectedReturnCode: 0); + } + + [Fact] + public void ImplementGetAwaiterGetResultViaExtensionMethods() + { + var source = @" +using System.Threading.Tasks; + +namespace System.Runtime.CompilerServices { + public class ExtensionAttribute {} +} + +namespace System.Runtime.CompilerServices { + public interface INotifyCompletion { + void OnCompleted(Action action); + } +} + +namespace System.Threading.Tasks { + public class Awaiter: System.Runtime.CompilerServices.INotifyCompletion { + public bool IsCompleted => true; + public void OnCompleted(Action action) {} + public void GetResult() { + System.Console.Write(""GetResult called""); + } + } + public class Task {} +} + +public static class MyExtensions { + public static Awaiter GetAwaiter(this Task task) { + System.Console.Write(""GetAwaiter called | ""); + return new Awaiter(); + } +} + +static class Program { + static Task Main() { + return new Task(); + } +}"; + var sourceCompilation = CreateStandardCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + var verifier = sourceCompilation.VerifyEmitDiagnostics( + // (26,43): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // public static Awaiter GetAwaiter(this Task task) { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(26, 43), + // (33,12): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // static Task Main() { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(33, 12), + // (34,20): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // return new Task(); + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(34, 20)); + CompileAndVerify(sourceCompilation, expectedOutput: "GetAwaiter called | GetResult called"); + } + + [Fact] + public void ImplementGetAwaiterGetResultViaExtensionMethods_Obsolete() + { + var source = @" +using System.Threading.Tasks; + +namespace System.Runtime.CompilerServices { + public class ExtensionAttribute {} +} + +namespace System.Runtime.CompilerServices { + public interface INotifyCompletion { + void OnCompleted(Action action); + } +} + +namespace System.Threading.Tasks { + public class Awaiter: System.Runtime.CompilerServices.INotifyCompletion { + public bool IsCompleted => true; + public void OnCompleted(Action action) {} + public void GetResult() { + System.Console.Write(""GetResult called""); + } + } + public class Task {} +} + +public static class MyExtensions { + [System.Obsolete(""test"")] + public static Awaiter GetAwaiter(this Task task) { + System.Console.Write(""GetAwaiter called | ""); + return new Awaiter(); + } +} + +static class Program { + static Task Main() { + return new Task(); + } +}"; + var sourceCompilation = CreateStandardCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + var verifier = sourceCompilation.VerifyEmitDiagnostics( + // (34,12): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // static Task Main() { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(34, 12), + // (27,43): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // public static Awaiter GetAwaiter(this Task task) { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(27, 43), + // (35,20): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // return new Task(); + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(35, 20), + // (34,12): warning CS0618: 'MyExtensions.GetAwaiter(Task)' is obsolete: 'test' + // static Task Main() { + Diagnostic(ErrorCode.WRN_DeprecatedSymbolStr, "Task").WithArguments("MyExtensions.GetAwaiter(System.Threading.Tasks.Task)", "test").WithLocation(34, 12)); + + CompileAndVerify(sourceCompilation, expectedOutput: "GetAwaiter called | GetResult called"); + } + + [Fact] + public void ImplementGetAwaiterGetResultViaExtensionMethods_ObsoleteFailing() + { + var source = @" +using System.Threading.Tasks; + +namespace System.Runtime.CompilerServices { + public class ExtensionAttribute {} +} + +namespace System.Runtime.CompilerServices { + public interface INotifyCompletion { + void OnCompleted(Action action); + } +} + +namespace System.Threading.Tasks { + public class Awaiter: System.Runtime.CompilerServices.INotifyCompletion { + public bool IsCompleted => true; + public void OnCompleted(Action action) {} + public void GetResult() {} + } + public class Task {} +} + +public static class MyExtensions { + [System.Obsolete(""test"", true)] + public static Awaiter GetAwaiter(this Task task) { + return null; + } +} + +static class Program { + static Task Main() { + return new Task(); + } +}"; + var sourceCompilation = CreateStandardCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + var verifier = sourceCompilation.VerifyEmitDiagnostics( + // (25,43): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // public static Awaiter GetAwaiter(this Task task) { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(25, 43), + // (31,12): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // static Task Main() { + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(31, 12), + // (32,20): warning CS0436: The type 'Task' in '' conflicts with the imported type 'Task' in 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Using the type defined in ''. + // return new Task(); + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, "Task").WithArguments("", "System.Threading.Tasks.Task", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Threading.Tasks.Task").WithLocation(32, 20), + // (31,12): warning CS0618: 'MyExtensions.GetAwaiter(Task)' is obsolete: 'test' + // static Task Main() { + Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "Task").WithArguments("MyExtensions.GetAwaiter(System.Threading.Tasks.Task)", "test").WithLocation(31, 12)); + } + } +} diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/BindingAsyncTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/BindingAsyncTests.cs index c0eb4f87882d6..8ca2ec00d7152 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/BindingAsyncTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/BindingAsyncTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; +using System.Threading; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; @@ -88,7 +89,7 @@ public void BadAsyncConstructor() { var source = @" class C { - async public C() { } + async public C() { } }"; CreateCompilationWithMscorlib45(source).VerifyDiagnostics( Diagnostic(ErrorCode.ERR_BadMemberFlag, "C").WithArguments("async")); @@ -215,7 +216,7 @@ public void TaskRetNoObjectRequired() class C { static void InferTask(Func x) { } - + static void InferTaskOrTaskT(Func x) { } static void InferTaskOrTaskT(Func> x) { } @@ -939,7 +940,7 @@ public void BadAwaitInCatchFilter() class Test { - async static Task M1() + async static Task M1() { try { @@ -1067,7 +1068,7 @@ public static async void F() object o = new object(); lock(await Task.Factory.StartNew(() => o)) { - + } } @@ -1188,7 +1189,7 @@ public void AsyncExplicitInterfaceImplementation() interface IInterface { - void F(); + void F(); } class C : IInterface @@ -1200,7 +1201,7 @@ async void IInterface.F() static void Main() { - + } }"; CreateCompilationWithMscorlib45(source).VerifyDiagnostics(); @@ -1212,97 +1213,16 @@ public void AsyncInterfaceMember() var source = @" interface IInterface { - async void F(); + async void F(); }"; CreateCompilationWithMscorlib45(source).VerifyDiagnostics( // (4,16): error CS0106: The modifier 'async' is not valid for this item - // async void F(); + // async void F(); Diagnostic(ErrorCode.ERR_BadMemberFlag, "F").WithArguments("async")); } [Fact] - public void MainCantBeAsync() - { - var source = @" -using System.Threading.Tasks; - -class A -{ - async static void Main() - { - await Task.Factory.StartNew(() => { }); - } -}"; - CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe).VerifyDiagnostics( - // (4,23): error CS4009: 'A.Main()': an entry point cannot be marked with the 'async' modifier - // async static void Main() - Diagnostic(ErrorCode.ERR_MainCantBeAsync, "Main").WithArguments("A.Main()")); - } - - [Fact] - public void MainCantBeAsync_AndGeneric() - { - var source = @" -using System.Threading.Tasks; - -class A -{ - async static void Main() - { - await Task.Factory.StartNew(() => { }); - } -}"; - CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe).VerifyDiagnostics( - // (4,23): warning CS0402: 'A.Main()': an entry point cannot be generic or in a generic type - // async static void Main() - Diagnostic(ErrorCode.WRN_MainCantBeGeneric, "Main").WithArguments("A.Main()"), - // error CS5001: Program does not contain a static 'Main' method suitable for an entry point - Diagnostic(ErrorCode.ERR_NoEntryPoint)); - } - - [Fact] - public void MainCantBeAsync_AndBadSig() - { - var source = @" -using System.Threading.Tasks; - -class A -{ - async static void Main(bool truth) - { - await Task.Factory.StartNew(() => { }); - } -}"; - CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe).VerifyDiagnostics( - // (4,23): warning CS0028: 'A.Main(bool)' has the wrong signature to be an entry point - // async static void Main(bool truth) - Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("A.Main(bool)"), - // error CS5001: Program does not contain a static 'Main' method suitable for an entry point - Diagnostic(ErrorCode.ERR_NoEntryPoint)); - } - - [Fact] - public void MainCantBeAsync_AndGeneric_AndBadSig() - { - var source = @" -using System.Threading.Tasks; -class A -{ - async static void Main(bool truth) - { - await Task.Factory.StartNew(() => { }); - } -}"; - CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe).VerifyDiagnostics( - // (4,23): warning CS0028: 'A.Main(bool)' has the wrong signature to be an entry point - // async static void Main(bool truth) - Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("A.Main(bool)"), - // error CS5001: Program does not contain a static 'Main' method suitable for an entry point - Diagnostic(ErrorCode.ERR_NoEntryPoint)); - } - - [Fact] public void AwaitInQuery_FirstCollectionExpressionOfInitialFrom() { var source = @" @@ -1829,7 +1749,7 @@ static Task Foo(T t) { return Task.Run(async () => { return t; }); } - + static int Main() { return 0; @@ -1865,7 +1785,7 @@ class Test static async Task Meth1() { throw new EntryPointNotFoundException(); - Foo(); + Foo(); return """"; } @@ -1891,10 +1811,10 @@ static int Main() // static async Task Meth1() Diagnostic(ErrorCode.WRN_AsyncLacksAwaits, "Meth1"), // (23,9): warning CS4014: Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call. - // Foo(); + // Foo(); Diagnostic(ErrorCode.WRN_UnobservedAwaitableExpression, "Foo()"), // (23,9): warning CS0162: Unreachable code detected - // Foo(); + // Foo(); Diagnostic(ErrorCode.WRN_UnreachableCode, "Foo"), // (27,33): 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. // static async Task Meth2() @@ -2908,7 +2828,7 @@ public void Meth() Foo(); }); }; - }); + }); } static int Main() @@ -2950,7 +2870,7 @@ public void Meth() return """"; }; del3(); - + }; } @@ -2981,7 +2901,7 @@ public static Task ExMeth(this int i) return (Task) Foo(); } } -class Test +class Test { public static int amount=0; static int Main() @@ -3066,7 +2986,7 @@ public Task Foo2() return Task.Run(() => { }); } } -class Test +class Test { static int Main() { @@ -3097,7 +3017,7 @@ public async Task Foo() { await Task.Delay(10); } - public void Dispose() + public void Dispose() { Foo(); } @@ -3483,22 +3403,42 @@ 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; +class Test +{ + 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").WithLocation(5, 30)); + } + + [Fact, WorkItem(547081, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/547081")] + public void Repro_17885_CSharp7() { var source = @" +using System.Threading.Tasks; class Test { - async public static void Main() + async public static Task Main() { } }"; - CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe).VerifyDiagnostics( - // (4,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 void Main() - Diagnostic(ErrorCode.WRN_AsyncLacksAwaits, "Main"), - // (4,30): error CS4009: 'Test.Main()': an entry point cannot be marked with the 'async' modifier - // async public static void Main() - Diagnostic(ErrorCode.ERR_MainCantBeAsync, "Main").WithArguments("Test.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").WithLocation(5, 30), + // (5,25): 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, "Task").WithArguments("async main", "7.1").WithLocation(5, 25), + // 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")] @@ -3511,7 +3451,7 @@ static int Main() { return 1; } - + public async void Foo(ref int x) { } }"; @@ -3582,7 +3522,7 @@ public void GetAwaiterIsExtension() var source = @"using System; using A; - + namespace A { public class IAS @@ -3716,7 +3656,7 @@ public void ReturnExpressionNotConvertible() { string source = @" using System.Threading.Tasks; - + class Program { static async Task Foo() @@ -3739,9 +3679,9 @@ public void RefParameterOnAsyncLambda() string source = @" using System; using System.Threading.Tasks; - + delegate Task D(ref int x); - + class C { static void Main() @@ -3751,7 +3691,7 @@ static void Main() await Task.Delay(500); Console.WriteLine(i++); }; - + int x = 5; d(ref x).Wait(); Console.WriteLine(x); @@ -3828,7 +3768,7 @@ public void DelegateTypeWithNoInvokeMethod() @".class public auto ansi sealed D`1 extends [mscorlib]System.MulticastDelegate { - .method public hidebysig specialname rtspecialname + .method public hidebysig specialname rtspecialname instance void .ctor(object 'object', native int 'method') runtime managed { diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/MethodTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/MethodTests.cs index a0ca5014afd75..7b9c0c19250a6 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/MethodTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/MethodTests.cs @@ -5,8 +5,10 @@ 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; using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; @@ -202,6 +204,104 @@ partial void M() {} Assert.Equal(1, m.First().Locations.Length); } + [Fact] + public void PartialExtractSyntaxLocation_DeclBeforeDef() + { + var text = +@"public partial class A { + partial void M(); +} +public partial class A { + partial void M() {} +} +"; + var comp = CreateStandardCompilation(text); + var global = comp.GlobalNamespace; + var a = global.GetTypeMembers("A", 0).Single(); + var m = (MethodSymbol) a.GetMembers("M").Single(); + Assert.True(m.IsPartialDefinition()); + var returnSyntax = m.ExtractReturnTypeSyntax(); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(n => n.Keyword.Kind() == SyntaxKind.VoidKeyword).First(); + + var otherSymbol = m.PartialImplementationPart; + Assert.True(otherSymbol.IsPartialImplementation()); + + Assert.Equal(node, returnSyntax); + Assert.Equal(node, otherSymbol.ExtractReturnTypeSyntax()); + } + + [Fact] + public void PartialExtractSyntaxLocation_DefBeforeDecl() + { + var text = +@"public partial class A { + partial void M() {} +} +public partial class A { + partial void M(); +} +"; + var comp = CreateStandardCompilation(text); + var global = comp.GlobalNamespace; + var a = global.GetTypeMembers("A", 0).Single(); + var m = (MethodSymbol) a.GetMembers("M").Single(); + Assert.True(m.IsPartialDefinition()); + var returnSyntax = m.ExtractReturnTypeSyntax(); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(n => n.Keyword.Kind() == SyntaxKind.VoidKeyword).Last(); + + var otherSymbol = m.PartialImplementationPart; + Assert.True(otherSymbol.IsPartialImplementation()); + + Assert.Equal(node, returnSyntax); + Assert.Equal(node, otherSymbol.ExtractReturnTypeSyntax()); + } + + [Fact] + public void PartialExtractSyntaxLocation_OnlyDef() + { + var text = +@"public partial class A { + partial void M() {} +} +"; + var comp = CreateStandardCompilation(text); + var global = comp.GlobalNamespace; + var a = global.GetTypeMembers("A", 0).Single(); + var m = (MethodSymbol) a.GetMembers("M").Single(); + Assert.True(m.IsPartialImplementation()); + var returnSyntax = m.ExtractReturnTypeSyntax(); + + var tree = comp.SyntaxTrees.Single().GetRoot(); + var node = tree.DescendantNodes().OfType().Where(n => n.Keyword.Kind() == SyntaxKind.VoidKeyword).Single(); + + Assert.Equal(node, returnSyntax); + } + + [Fact] + public void PartialExtractSyntaxLocation_OnlyDecl() + { + var text = +@"public partial class A { + partial void M(); +} +"; + var comp = CreateStandardCompilation(text); + var global = comp.GlobalNamespace; + var a = global.GetTypeMembers("A", 0).Single(); + var m = (MethodSymbol) a.GetMembers("M").Single(); + Assert.True(m.IsPartialDefinition()); + var returnSyntax = m.ExtractReturnTypeSyntax(); + + var tree = comp.SyntaxTrees.Single().GetRoot(); + var node = tree.DescendantNodes().OfType().Where(n => n.Keyword.Kind() == SyntaxKind.VoidKeyword).Single(); + + Assert.Equal(node, returnSyntax); + } + [Fact] public void FullName() { diff --git a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs index 6e3c7bbbf80c9..917788c4d65bb 100644 --- a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs +++ b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs @@ -105,6 +105,8 @@ internal CompilationVerifier CompileAndVerify( Action symbolValidator = null, SignatureDescription[] expectedSignatures = null, string expectedOutput = null, + int? expectedReturnCode = null, + string[] args = null, CompilationOptions options = null, ParseOptions parseOptions = null, EmitOptions emitOptions = null, @@ -119,6 +121,8 @@ internal CompilationVerifier CompileAndVerify( Translate2(symbolValidator), expectedSignatures, expectedOutput, + expectedReturnCode, + args, options, parseOptions, emitOptions, @@ -185,6 +189,8 @@ internal CompilationVerifier CompileAndVerify( Action symbolValidator = null, SignatureDescription[] expectedSignatures = null, string expectedOutput = null, + int? expectedReturnCode = null, + string[] args = null, EmitOptions emitOptions = null, bool verify = true) { @@ -197,6 +203,8 @@ internal CompilationVerifier CompileAndVerify( Translate2(symbolValidator), expectedSignatures, expectedOutput, + expectedReturnCode, + args, emitOptions, verify); } diff --git a/src/Compilers/Test/Utilities/VisualBasic/BasicTestBase.vb b/src/Compilers/Test/Utilities/VisualBasic/BasicTestBase.vb index b4d2d8109833c..1f2063c4ad3bc 100644 --- a/src/Compilers/Test/Utilities/VisualBasic/BasicTestBase.vb +++ b/src/Compilers/Test/Utilities/VisualBasic/BasicTestBase.vb @@ -37,6 +37,8 @@ Public MustInherit Class BasicTestBase Friend Shadows Function CompileAndVerify( source As XElement, expectedOutput As XCData, + Optional expectedReturnCode As Integer? = Nothing, + Optional args As String() = Nothing, Optional additionalRefs As MetadataReference() = Nothing, Optional dependencies As IEnumerable(Of ModuleData) = Nothing, Optional sourceSymbolValidator As Action(Of ModuleSymbol) = Nothing, @@ -52,6 +54,8 @@ Public MustInherit Class BasicTestBase Return CompileAndVerify( source, XCDataToString(expectedOutput), + expectedReturnCode, + args, additionalRefs, dependencies, sourceSymbolValidator, @@ -73,6 +77,8 @@ Public MustInherit Class BasicTestBase Optional symbolValidator As Action(Of ModuleSymbol) = Nothing, Optional expectedSignatures As SignatureDescription() = Nothing, Optional expectedOutput As String = Nothing, + Optional expectedReturnCode As Integer? = Nothing, + Optional args As String() = Nothing, Optional emitOptions As EmitOptions = Nothing, Optional verify As Boolean = True) As CompilationVerifier @@ -85,6 +91,8 @@ Public MustInherit Class BasicTestBase Translate(symbolValidator), expectedSignatures, expectedOutput, + expectedReturnCode, + args, emitOptions, verify) End Function @@ -92,6 +100,7 @@ Public MustInherit Class BasicTestBase Friend Shadows Function CompileAndVerify( compilation As Compilation, expectedOutput As XCData, + Optional args As String() = Nothing, Optional manifestResources As IEnumerable(Of ResourceDescription) = Nothing, Optional dependencies As IEnumerable(Of ModuleData) = Nothing, Optional sourceSymbolValidator As Action(Of ModuleSymbol) = Nothing, @@ -110,6 +119,8 @@ Public MustInherit Class BasicTestBase symbolValidator, expectedSignatures, XCDataToString(expectedOutput), + Nothing, + args, emitOptions, verify) End Function @@ -117,6 +128,8 @@ Public MustInherit Class BasicTestBase Friend Shadows Function CompileAndVerify( source As XElement, Optional expectedOutput As String = Nothing, + Optional expectedReturnCode As Integer? = Nothing, + Optional args As String() = Nothing, Optional additionalRefs As MetadataReference() = Nothing, Optional dependencies As IEnumerable(Of ModuleData) = Nothing, Optional sourceSymbolValidator As Action(Of ModuleSymbol) = Nothing, @@ -136,6 +149,8 @@ Public MustInherit Class BasicTestBase Return Me.CompileAndVerify(source, allReferences, expectedOutput, + expectedReturnCode, + args, dependencies, sourceSymbolValidator, validator, @@ -152,6 +167,8 @@ Public MustInherit Class BasicTestBase source As XElement, allReferences As IEnumerable(Of MetadataReference), Optional expectedOutput As String = Nothing, + Optional expectedReturnCode As Integer? = Nothing, + Optional args As String() = Nothing, Optional dependencies As IEnumerable(Of ModuleData) = Nothing, Optional sourceSymbolValidator As Action(Of ModuleSymbol) = Nothing, Optional validator As Action(Of PEAssembly) = Nothing, @@ -180,6 +197,8 @@ Public MustInherit Class BasicTestBase Translate(symbolValidator), expectedSignatures, expectedOutput, + expectedReturnCode, + args, emitOptions, verify) End Function @@ -188,6 +207,8 @@ Public MustInherit Class BasicTestBase source As String, allReferences As IEnumerable(Of MetadataReference), Optional expectedOutput As String = Nothing, + Optional expectedReturnCode As Integer? = Nothing, + Optional args As String() = Nothing, Optional dependencies As IEnumerable(Of ModuleData) = Nothing, Optional sourceSymbolValidator As Action(Of ModuleSymbol) = Nothing, Optional validator As Action(Of PEAssembly) = Nothing, @@ -215,6 +236,8 @@ Public MustInherit Class BasicTestBase Translate(symbolValidator), expectedSignatures, expectedOutput, + expectedReturnCode, + args, emitOptions, verify) End Function @@ -223,6 +246,8 @@ Public MustInherit Class BasicTestBase source As XElement, allReferences As IEnumerable(Of MetadataReference), Optional expectedOutput As String = Nothing, + Optional expectedReturnCode As Integer? = Nothing, + Optional args As String() = Nothing, Optional dependencies As IEnumerable(Of ModuleData) = Nothing, Optional sourceSymbolValidator As Action(Of ModuleSymbol) = Nothing, Optional validator As Action(Of PEAssembly) = Nothing, @@ -236,6 +261,8 @@ Public MustInherit Class BasicTestBase source, allReferences, If(OSVersion.IsWin8, expectedOutput, Nothing), + If(OSVersion.IsWin8, expectedReturnCode, Nothing), + args, dependencies, sourceSymbolValidator, validator, @@ -249,6 +276,8 @@ Public MustInherit Class BasicTestBase Friend Shadows Function CompileAndVerifyOnWin8Only( source As XElement, expectedOutput As XCData, + Optional expectedReturnCode As Integer? = Nothing, + Optional args As String() = Nothing, Optional allReferences() As MetadataReference = Nothing, Optional dependencies As IEnumerable(Of ModuleData) = Nothing, Optional sourceSymbolValidator As Action(Of ModuleSymbol) = Nothing, @@ -263,6 +292,8 @@ Public MustInherit Class BasicTestBase source, allReferences, XCDataToString(expectedOutput), + expectedReturnCode, + args, dependencies, sourceSymbolValidator, validator, diff --git a/src/Test/Utilities/CoreClr/CoreCLRRuntimeEnvironment.cs b/src/Test/Utilities/CoreClr/CoreCLRRuntimeEnvironment.cs index 344c8311e0ee6..ff42a70857f42 100644 --- a/src/Test/Utilities/CoreClr/CoreCLRRuntimeEnvironment.cs +++ b/src/Test/Utilities/CoreClr/CoreCLRRuntimeEnvironment.cs @@ -72,23 +72,18 @@ public void Emit( } } - public (int ExitCode, string Output) Execute(string moduleName, int expectedOutputLength) + public int Execute(string moduleName, string[] args, string expectedOutput) { var emitData = GetEmitData(); emitData.RuntimeData.ExecuteRequested = true; - return emitData.LoadContext.Execute(GetMainImage(), expectedOutputLength); - } - - public int Execute(string moduleName, string expectedOutput) - { - var (exitCode, actualOutput) = Execute(moduleName, expectedOutput.Length); + var (ExitCode, Output) = emitData.LoadContext.Execute(GetMainImage(), args, expectedOutput?.Length); - if (expectedOutput.Trim() != actualOutput.Trim()) + if (expectedOutput != null && expectedOutput.Trim() != Output.Trim()) { - throw new ExecutionException(expectedOutput, actualOutput, moduleName); + throw new ExecutionException(expectedOutput, Output, moduleName); } - return exitCode; + return ExitCode; } private EmitData GetEmitData() => _emitData ?? throw new InvalidOperationException("Must call Emit before calling this method"); diff --git a/src/Test/Utilities/CoreClr/TestExecutionLoadContext.cs b/src/Test/Utilities/CoreClr/TestExecutionLoadContext.cs index 152eae58ea14a..ccc7c6b986657 100644 --- a/src/Test/Utilities/CoreClr/TestExecutionLoadContext.cs +++ b/src/Test/Utilities/CoreClr/TestExecutionLoadContext.cs @@ -95,7 +95,7 @@ private Assembly LoadImageAsAssembly(ImmutableArray mainImage) } } - internal (int ExitCode, string Output) Execute(ImmutableArray mainImage, int expectedOutputLength) + internal (int ExitCode, string Output) Execute(ImmutableArray mainImage, string[] mainArgs, int? expectedOutputLength) { var mainAssembly = LoadImageAsAssembly(mainImage); var entryPoint = mainAssembly.EntryPoint; @@ -113,7 +113,7 @@ private Assembly LoadImageAsAssembly(ImmutableArray mainImage) } else if (count == 1) { - args = new[] { Array.Empty() }; + args = new[] { mainArgs ?? Array.Empty() }; } else { @@ -121,7 +121,7 @@ private Assembly LoadImageAsAssembly(ImmutableArray mainImage) } exitCode = entryPoint.Invoke(null, args) is int exit ? exit : 0; - }, expectedOutputLength, out var stdOut, out var stdErr); + }, expectedOutputLength ?? 0, out var stdOut, out var stdErr); var output = stdOut + stdErr; return (exitCode, output); diff --git a/src/Test/Utilities/Desktop/CodeRuntime/DesktopRuntimeEnvironment.cs b/src/Test/Utilities/Desktop/CodeRuntime/DesktopRuntimeEnvironment.cs index 0ce20aa65f134..d53f86217b336 100644 --- a/src/Test/Utilities/Desktop/CodeRuntime/DesktopRuntimeEnvironment.cs +++ b/src/Test/Utilities/Desktop/CodeRuntime/DesktopRuntimeEnvironment.cs @@ -236,13 +236,22 @@ public void Emit( } } - public int Execute(string moduleName, int expectedOutputLength, out string processOutput) + public int Execute(string moduleName, string[] args, string expectedOutput) { try { var emitData = GetEmitData(); emitData.RuntimeData.ExecuteRequested = true; - return emitData.Manager.Execute(moduleName, expectedOutputLength, out processOutput); + var resultCode = emitData.Manager.Execute(moduleName, args, expectedOutput?.Length, out var output); + + if (expectedOutput != null && expectedOutput.Trim() != output.Trim()) + { + string dumpDir; + GetEmitData().Manager.DumpAssemblyData(out dumpDir); + throw new ExecutionException(expectedOutput, output, dumpDir); + } + + return resultCode; } catch (TargetInvocationException tie) { @@ -257,21 +266,6 @@ public int Execute(string moduleName, int expectedOutputLength, out string proce } } - public int Execute(string moduleName, string expectedOutput) - { - string actualOutput; - int exitCode = Execute(moduleName, expectedOutput.Length, out actualOutput); - - if (expectedOutput.Trim() != actualOutput.Trim()) - { - string dumpDir; - GetEmitData().Manager.DumpAssemblyData(out dumpDir); - throw new ExecutionException(expectedOutput, actualOutput, dumpDir); - } - - return exitCode; - } - private EmitData GetEmitData() { if (_emitData == null) diff --git a/src/Test/Utilities/Desktop/CodeRuntime/RuntimeAssemblyManager.cs b/src/Test/Utilities/Desktop/CodeRuntime/RuntimeAssemblyManager.cs index 6da88bfff43f6..d293baeee4b72 100644 --- a/src/Test/Utilities/Desktop/CodeRuntime/RuntimeAssemblyManager.cs +++ b/src/Test/Utilities/Desktop/CodeRuntime/RuntimeAssemblyManager.cs @@ -367,7 +367,7 @@ private SortedSet GetFullyQualifiedTypeNames(string assemblyName) return typeNames; } - public int Execute(string moduleName, int expectedOutputLength, out string output) + public int Execute(string moduleName, string[] mainArgs, int? expectedOutputLength, out string output) { ImmutableArray bytes = GetModuleBytesByName(moduleName); Assembly assembly = DesktopRuntimeUtil.LoadAsAssembly(moduleName, bytes); @@ -386,7 +386,7 @@ public int Execute(string moduleName, int expectedOutputLength, out string outpu } else if (count == 1) { - args = new object[] { new string[0] }; + args = new object[] { mainArgs ?? new string[0] }; } else { @@ -394,7 +394,7 @@ public int Execute(string moduleName, int expectedOutputLength, out string outpu } result = entryPoint.Invoke(null, args); - }, expectedOutputLength, out stdOut, out stdErr); + }, expectedOutputLength ?? 0, out stdOut, out stdErr); output = stdOut + stdErr; return result is int ? (int)result : 0; diff --git a/src/Test/Utilities/Portable/CommonTestBase.CompilationVerifier.cs b/src/Test/Utilities/Portable/CommonTestBase.CompilationVerifier.cs index 67a41c836262f..a6d74a7b1b800 100644 --- a/src/Test/Utilities/Portable/CommonTestBase.CompilationVerifier.cs +++ b/src/Test/Utilities/Portable/CommonTestBase.CompilationVerifier.cs @@ -88,7 +88,7 @@ internal ImmutableArray GetAllModuleMetadata() return modules; } - public void Emit(string expectedOutput, IEnumerable manifestResources, EmitOptions emitOptions, bool peVerify, SignatureDescription[] expectedSignatures) + public void Emit(string expectedOutput, int? expectedReturnCode, string[] args, IEnumerable manifestResources, EmitOptions emitOptions, bool peVerify, SignatureDescription[] expectedSignatures) { using (var testEnvironment = RuntimeEnvironmentFactory.Create(_dependencies)) { @@ -105,9 +105,14 @@ public void Emit(string expectedOutput, IEnumerable manifes MetadataSignatureUnitTestHelper.VerifyMemberSignatures(testEnvironment, expectedSignatures); } - if (expectedOutput != null) + if (expectedOutput != null || expectedReturnCode != null) { - testEnvironment.Execute(mainModuleName, expectedOutput); + var returnCode = testEnvironment.Execute(mainModuleName, args, expectedOutput); + + if (expectedReturnCode is int exCode) + { + Assert.Equal(exCode, returnCode); + } } } } diff --git a/src/Test/Utilities/Portable/CommonTestBase.cs b/src/Test/Utilities/Portable/CommonTestBase.cs index 1b944cf6208fd..d1adacc3af754 100644 --- a/src/Test/Utilities/Portable/CommonTestBase.cs +++ b/src/Test/Utilities/Portable/CommonTestBase.cs @@ -73,6 +73,8 @@ internal CompilationVerifier CompileAndVerify( Action symbolValidator = null, SignatureDescription[] expectedSignatures = null, string expectedOutput = null, + int? expectedReturnCode = null, + string[] args = null, CompilationOptions options = null, ParseOptions parseOptions = null, EmitOptions emitOptions = null, @@ -94,6 +96,8 @@ internal CompilationVerifier CompileAndVerify( symbolValidator, expectedSignatures, expectedOutput, + expectedReturnCode, + args, emitOptions, verify); } @@ -107,6 +111,8 @@ internal CompilationVerifier CompileAndVerify( Action symbolValidator = null, SignatureDescription[] expectedSignatures = null, string expectedOutput = null, + int? expectedReturnCode = null, + string[] args = null, EmitOptions emitOptions = null, bool verify = true) { @@ -136,6 +142,8 @@ internal CompilationVerifier CompileAndVerify( manifestResources, expectedSignatures, expectedOutput, + expectedReturnCode, + args ?? Array.Empty(), assemblyValidator, symbolValidator, emitOptions, @@ -199,6 +207,8 @@ internal CompilationVerifier Emit( IEnumerable manifestResources, SignatureDescription[] expectedSignatures, string expectedOutput, + int? expectedReturnCode, + string[] args, Action assemblyValidator, Action symbolValidator, EmitOptions emitOptions, @@ -208,7 +218,7 @@ internal CompilationVerifier Emit( verifier = new CompilationVerifier(this, compilation, dependencies); - verifier.Emit(expectedOutput, manifestResources, emitOptions, verify, expectedSignatures); + verifier.Emit(expectedOutput, expectedReturnCode, args, manifestResources, emitOptions, verify, expectedSignatures); // We're dual-purposing emitters here. In this context, it // tells the validator the version of Emit that is calling it. diff --git a/src/Test/Utilities/Portable/Compilation/IRuntimeEnvironment.cs b/src/Test/Utilities/Portable/Compilation/IRuntimeEnvironment.cs index 11b640f8dcfa6..30d39f1b4cb0c 100644 --- a/src/Test/Utilities/Portable/Compilation/IRuntimeEnvironment.cs +++ b/src/Test/Utilities/Portable/Compilation/IRuntimeEnvironment.cs @@ -367,7 +367,7 @@ public interface IRuntimeEnvironmentFactory public interface IRuntimeEnvironment : IDisposable { void Emit(Compilation mainCompilation, IEnumerable manifestResources, EmitOptions emitOptions, bool usePdbForDebugging = false); - int Execute(string moduleName, string expectedOutput); + int Execute(string moduleName, string[] args, string expectedOutput); ImmutableArray GetMainImage(); ImmutableArray GetMainPdb(); ImmutableArray GetDiagnostics(); diff --git a/src/Test/Utilities/Portable/Traits/CompilerFeature.cs b/src/Test/Utilities/Portable/Traits/CompilerFeature.cs index e3ac493c97775..0530fb6217152 100644 --- a/src/Test/Utilities/Portable/Traits/CompilerFeature.cs +++ b/src/Test/Utilities/Portable/Traits/CompilerFeature.cs @@ -20,5 +20,6 @@ public enum CompilerFeature OutVar, Patterns, DefaultLiteral, + AsyncMain, } }