From b3243e8691ec2b97dc0b17f5fef76ad2626aa7a1 Mon Sep 17 00:00:00 2001 From: Ty Overby Date: Fri, 31 Mar 2017 09:57:44 -0700 Subject: [PATCH 1/8] enable main methods to be asynchronous (#18157) * enable main methods to be asynchronous * add cref to documentation * fix doc comment syntax * remove message from resx * move comments * add async int test * add more test * re-add errors for pre-71 compilers * add tests for previous versions of csharp * add feature detection * Disallow ref returning Main method The Main method cannot have a ref return. closes #17923 * add ref tests, add prototype comment * change prototype comment --- .../Portable/CSharpResources.Designer.cs | 9 + .../CSharp/Portable/CSharpResources.resx | 3 + .../Portable/Compilation/CSharpCompilation.cs | 66 +-- .../CSharp/Portable/Errors/ErrorCode.cs | 2 +- .../CSharp/Portable/Errors/MessageID.cs | 5 + .../CSharp/Portable/Symbols/MethodSymbol.cs | 84 ++-- .../CSharp/Test/Emit/Emit/EntryPointTests.cs | 40 ++ .../Semantic/Semantics/BindingAsyncTests.cs | 389 ++++++++++++++++-- 8 files changed, 505 insertions(+), 93 deletions(-) diff --git a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs index b2d5452c58a30..474abc016d6cf 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs +++ b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs @@ -9872,6 +9872,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 b97e92c32b452..c5f5ed764a3c4 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -5041,4 +5041,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ A value of type 'void' may not be assigned. + + async main + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs index a163e9ecc6b49..dc84d30bf09d2 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) @@ -1456,9 +1457,9 @@ private MethodSymbol FindEntryPoint(CancellationToken cancellationToken, out Imm var viableEntryPoints = ArrayBuilder.GetInstance(); foreach (var candidate in entryPointCandidates) { - if (!candidate.HasEntryPointSignature()) + if (!candidate.HasEntryPointSignature(this)) { - // a single error for partial methods: + // a single error for partial methods warnings.Add(ErrorCode.WRN_InvalidMainSig, candidate.Locations.First(), candidate); continue; } @@ -1472,7 +1473,8 @@ private MethodSymbol FindEntryPoint(CancellationToken cancellationToken, out Imm if (candidate.IsAsync) { - diagnostics.Add(ErrorCode.ERR_MainCantBeAsync, candidate.Locations.First(), candidate); + // PROTOTYPE(async-main): Get the diagnostic to point to a smaller syntax piece. + CheckFeatureAvailability(candidate.DeclaringSyntaxReferences.Single().GetSyntax(), MessageID.IDS_FeatureAsyncMain, diagnostics); } viableEntryPoints.Add(candidate); @@ -2171,8 +2173,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,14 +2732,14 @@ 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); } protected override INamedTypeSymbol CommonCreateTupleTypeSymbol( - INamedTypeSymbol underlyingType, + INamedTypeSymbol underlyingType, ImmutableArray elementNames, ImmutableArray elementLocations) { @@ -2757,7 +2759,7 @@ protected override INamedTypeSymbol CommonCreateTupleTypeSymbol( } protected override INamedTypeSymbol CommonCreateAnonymousTypeSymbol( - ImmutableArray memberTypes, + ImmutableArray memberTypes, ImmutableArray memberNames, ImmutableArray memberLocations, ImmutableArray memberIsReadOnly) @@ -2855,7 +2857,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. /// /// @@ -2911,7 +2913,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/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 5814b2db8a06b..83890349f82bd 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_MainCantBeAsync = 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 db433a2c5b01e..2f736750f6f49 100644 --- a/src/Compilers/CSharp/Portable/Errors/MessageID.cs +++ b/src/Compilers/CSharp/Portable/Errors/MessageID.cs @@ -127,6 +127,7 @@ internal enum MessageID IDS_FeatureExpressionBodiedAccessor = MessageBase + 12715, IDS_FeatureExpressionBodiedDeOrConstructor = MessageBase + 12716, IDS_ThrowExpression = MessageBase + 12717, + IDS_FeatureAsyncMain = MessageBase + 12718, } // Message IDs may refer to strings that need to be localized. @@ -183,6 +184,10 @@ internal static LanguageVersion RequiredVersion(this MessageID feature) // Checks are in the LanguageParser unless otherwise noted. switch (feature) { + // C# 7.1 features. + case MessageID.IDS_FeatureAsyncMain: + return LanguageVersion.CSharp7_1; + // C# 7 features. case MessageID.IDS_FeatureBinaryLiteral: case MessageID.IDS_FeatureDigitSeparator: diff --git a/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs index a308c50a9554c..1a4322b09f4af 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs @@ -84,7 +84,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 +114,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 +133,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 +184,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 +275,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 +309,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 +327,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 +374,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 +587,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 @@ -597,18 +597,56 @@ internal bool IsEntryPointCandidate /// /// Checks if the method has an entry point compatible signature, i.e. - /// - the return type is either void or int + /// - the return type is either void, int, , + /// or where T is an int. /// - has either no parameter or a single parameter of type string[] /// - internal bool HasEntryPointSignature() + internal bool HasEntryPointSignature(CSharpCompilation compilation) { - if (this.IsVararg) + + bool IsAsyncMainReturnType(TypeSymbol type) + { + var namedType = type as NamedTypeSymbol; + if (namedType == null) + { + return false; + } + else if (namedType.ConstructedFrom == compilation.GetWellKnownType(WellKnownType.System_Threading_Tasks_Task)) + { + // Change this to `namedType.IsNonGenericTaskType` if you want to support "task-like" objects. + return true; + } + else if (namedType.ConstructedFrom == compilation.GetWellKnownType(WellKnownType.System_Threading_Tasks_Task_T)) + { + // Change this to `namedType.IsGenericTaskType` if you want to support "task-like" objects. + return namedType.TypeArguments[0].SpecialType == SpecialType.System_Int32; + } + else + { + return false; + } + } + + if (IsVararg) { return false; } TypeSymbol returnType = ReturnType; - if (returnType.SpecialType != SpecialType.System_Int32 && returnType.SpecialType != SpecialType.System_Void) + bool isAsyncMainReturnType = IsAsyncMainReturnType(returnType); + if (returnType.SpecialType != SpecialType.System_Int32 && returnType.SpecialType != SpecialType.System_Void && !isAsyncMainReturnType) + { + return false; + } + + // Prior to 7.1, async methods were considered to "have the entrypoint signature". + // In order to keep back-compat, we need to let these through if compiling using a previous language version. + if (!isAsyncMainReturnType && IsAsync && compilation.LanguageVersion >= LanguageVersion.CSharp7_1) + { + return false; + } + + if (RefKind != RefKind.None) { return false; } @@ -733,7 +771,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. @@ -883,7 +921,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) { @@ -903,7 +941,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 { @@ -944,7 +982,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) @@ -975,7 +1013,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. @@ -1227,7 +1265,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/Test/Emit/Emit/EntryPointTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EntryPointTests.cs index d9f218f2d9ef7..27a16b65d939e 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EntryPointTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EntryPointTests.cs @@ -1444,5 +1444,45 @@ public static void Main(string[] args) {} Diagnostic(ErrorCode.ERR_MultipleEntryPoints, "Main").WithLocation(4, 24) ); } + + [WorkItem(17923, "https://github.com/dotnet/roslyn/issues/17923")] + [Fact] + [CompilerTrait(CompilerFeature.RefLocalsReturns)] + public void RefIntReturnMainEmpty() + { + var source = @" +class Program +{ + public static ref int Main() { throw new System.Exception(); } +}"; + + var compilation = CreateCompilationWithMscorlib(source, options: TestOptions.DebugExe); + compilation.VerifyDiagnostics( + // (4,27): warning CS0028: 'Program.Main()' has the wrong signature to be an entry point + // public static ref int Main() {} + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("Program.Main()").WithLocation(4, 27), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); + } + + [WorkItem(17923, "https://github.com/dotnet/roslyn/issues/17923")] + [Fact] + [CompilerTrait(CompilerFeature.RefLocalsReturns)] + public void RefIntReturnMainWithParams() + { + var source = @" +class Program +{ + public static ref int Main(string[] args) { throw new System.Exception(); } +}"; + + var compilation = CreateCompilationWithMscorlib(source, options: TestOptions.DebugExe); + compilation.VerifyDiagnostics( + // (4,27): warning CS0028: 'Program.Main(string[])' has the wrong signature to be an entry point + // public static ref int Main(string[] args) { throw new System.Exception(); } + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("Program.Main(string[])"), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint)); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/BindingAsyncTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/BindingAsyncTests.cs index 5fd8b1578b9a7..692b282000480 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,16 +1213,109 @@ 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() + public void MainCanBeAsyncWithArgs() + { + var origSource = @" +using System.Threading.Tasks; + +class A +{ + async static Task Main(string[] args) + { + await Task.Factory.StartNew(() => { }); + } +}"; + var sources = new string[] { origSource, origSource.Replace("async ", "").Replace("await", "return") }; + foreach (var source in sources) + { + 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 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,5): 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, @"async static Task Main(string[] args) + { + await Task.Factory.StartNew(() => { }); + }").WithArguments("async main", "7.1").WithLocation(6, 5)); + } + + [Fact] + public void MainCanBeAsync() + { + var origSource = @" +using System.Threading.Tasks; + +class A +{ + async static Task Main() + { + await Task.Factory.StartNew(() => { }); + } +}"; + var sources = new string[] { origSource, origSource.Replace("async ", "").Replace("await", "return") }; + foreach (var source in sources) + { + 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() { var source = @" using System.Threading.Tasks; @@ -1233,10 +1327,211 @@ 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 + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + compilation.VerifyDiagnostics( + // (6,23): warning CS0028: 'A.Main()' has the wrong signature to be an entry point + // async static void Main() + Diagnostic(ErrorCode.WRN_InvalidMainSig, "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)); + var entry = compilation.GetEntryPoint(CancellationToken.None); + Assert.Null(entry); + } + + [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,5): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater. + // async static void Main() + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7, @"async static void Main() + { + await Task.Factory.StartNew(() => { }); + }").WithArguments("async main", "7.1").WithLocation(6, 5)); + } + + [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,23): warning CS0028: 'A.Main()' has the wrong signature to be an entry point // async static void Main() - Diagnostic(ErrorCode.ERR_MainCantBeAsync, "Main").WithArguments("A.Main()")); + Diagnostic(ErrorCode.WRN_InvalidMainSig, "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"), + // (6,5): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater. + // async static int Main() + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7, @"async static int Main() + { + return await Task.Factory.StartNew(() => 5); + }").WithArguments("async main", "7.1").WithLocation(6, 5) +); + } + + [Fact] + public void MainCanBeAsyncAndGenericOnIntWithArgs() + { + var origSource = @" +using System.Threading.Tasks; + +class A +{ + async static Task Main(string[] args) + { + return await Task.Factory.StartNew(() => 5); + } +}"; + var sources = new string[] { origSource, origSource.Replace("async ", "").Replace("await", "") }; + foreach (var source in sources) + { + 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 MainCantBeAsyncAndGenericOnIntWithArgs_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,5): 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, @"async static Task Main(string[] args) + { + return await Task.Factory.StartNew(() => 5); + }").WithArguments("async main", "7.1").WithLocation(6, 5) +); + } + + [Fact] + public void MainCanBeAsyncAndGenericOnInt() + { + var origSource = @" +using System.Threading.Tasks; + +class A +{ + async static Task Main() + { + return await Task.Factory.StartNew(() => 5); + } +}"; + var sources = new string[] { origSource, origSource.Replace("async ", "").Replace("await", "") }; + foreach (var source in sources) + { + 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,5): 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, @"async static Task Main() + { + return await Task.Factory.StartNew(() => 5); + }").WithArguments("async main", "7.1").WithLocation(6, 5) + ); + } + + [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] @@ -1252,10 +1547,10 @@ async static void Main() await Task.Factory.StartNew(() => { }); } }"; - CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe).VerifyDiagnostics( + CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)).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()"), + Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("A.Main()"), // error CS5001: Program does not contain a static 'Main' method suitable for an entry point Diagnostic(ErrorCode.ERR_NoEntryPoint)); } @@ -1829,7 +2124,7 @@ static Task Foo(T t) { return Task.Run(async () => { return t; }); } - + static int Main() { return 0; @@ -1865,7 +2160,7 @@ class Test static async Task Meth1() { throw new EntryPointNotFoundException(); - Foo(); + Foo(); return """"; } @@ -1891,10 +2186,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 +3203,7 @@ public void Meth() Foo(); }); }; - }); + }); } static int Main() @@ -2950,7 +3245,7 @@ public void Meth() return """"; }; del3(); - + }; } @@ -2981,7 +3276,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 +3361,7 @@ public Task Foo2() return Task.Run(() => { }); } } -class Test +class Test { static int Main() { @@ -3097,7 +3392,7 @@ public async Task Foo() { await Task.Delay(10); } - public void Dispose() + public void Dispose() { Foo(); } @@ -3486,19 +3781,39 @@ public static int Main() public void Repro_17885() { 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() + 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")); + } + + [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 Task Main() + { + } +}"; + CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)).VerifyDiagnostics( + // (5,30): warning CS1998: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. + // async public static Task Main() Diagnostic(ErrorCode.WRN_AsyncLacksAwaits, "Main"), - // (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()")); + // (5,5): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater. + // async public static Task Main() + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7, @"async public static Task Main() + { + }").WithArguments("async main", "7.1").WithLocation(5, 5)); } [Fact, WorkItem(547088, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/547088")] @@ -3511,7 +3826,7 @@ static int Main() { return 1; } - + public async void Foo(ref int x) { } }"; @@ -3582,7 +3897,7 @@ public void GetAwaiterIsExtension() var source = @"using System; using A; - + namespace A { public class IAS @@ -3717,7 +4032,7 @@ public void ReturnExpressionNotConvertible() { string source = @" using System.Threading.Tasks; - + class Program { static async Task Foo() @@ -3740,9 +4055,9 @@ public void RefParameterOnAsyncLambda() string source = @" using System; using System.Threading.Tasks; - + delegate Task D(ref int x); - + class C { static void Main() @@ -3752,7 +4067,7 @@ static void Main() await Task.Delay(500); Console.WriteLine(i++); }; - + int x = 5; d(ref x).Wait(); Console.WriteLine(x); @@ -3830,7 +4145,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 { From 4060bb346dc0cd9eb31cce1a0886673afe65d49d Mon Sep 17 00:00:00 2001 From: Ty Overby Date: Fri, 14 Apr 2017 13:47:42 -0700 Subject: [PATCH 2/8] Async main doc (#18081) * Add markdown feature documents * Update testing plan --- docs/features/async-main.md | 12 +++++++ docs/features/async-main.test.md | 56 ++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 docs/features/async-main.md create mode 100644 docs/features/async-main.test.md 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..9901e16e0bc3e --- /dev/null +++ b/docs/features/async-main.test.md @@ -0,0 +1,56 @@ +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. From 306ca441920f60fb2051d12250e55b53131553c4 Mon Sep 17 00:00:00 2001 From: Ty Overby Date: Fri, 14 Apr 2017 15:32:16 -0700 Subject: [PATCH 3/8] Update async-main.test.md --- docs/features/async-main.test.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/features/async-main.test.md b/docs/features/async-main.test.md index 9901e16e0bc3e..4fc7de5c129fc 100644 --- a/docs/features/async-main.test.md +++ b/docs/features/async-main.test.md @@ -54,3 +54,17 @@ Classes that contain more than one main * 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. From 1f68c4e7a9e9b2fa5305c71530d1428a1db7fa5a Mon Sep 17 00:00:00 2001 From: Ty Overby Date: Tue, 18 Apr 2017 10:39:04 -0700 Subject: [PATCH 4/8] Update async-main.test.md --- docs/features/async-main.test.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/features/async-main.test.md b/docs/features/async-main.test.md index 4fc7de5c129fc..9b4a51467e559 100644 --- a/docs/features/async-main.test.md +++ b/docs/features/async-main.test.md @@ -68,3 +68,32 @@ 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 From 05391555f1fa8a74b1c78d7f92bacb6b401067d2 Mon Sep 17 00:00:00 2001 From: Ty Overby Date: Fri, 28 Apr 2017 09:08:53 -0700 Subject: [PATCH 5/8] Async main codegen (#18472) * attempt to get main entrypoints working * async main codegen * remove garbage file * fix vb tests * add some comments * moduleBeingBuilt can sometimes be null apparently * fix style nits * more whitespace fixes * address feedback * automated style fixer * move most work out into the constructor * protect against null in error condition * add comment * address cston review * autoformat * use cast * check module being built * add another object cast * address review * check for old cases where async methods called Main also exist * more suggestions * autoformat * mostly cleanup * use shorter Count and Single * remove superfluous diagnostics add * check exit code / revert to old entrypoint finding code * remove language version check in entrypoint compiler * fix debug.assert * switch to assert equal * codereview * add more tests; change conditions for test execution * fix up emit methods * narrow down error location * ammend GetAwaitableExpressionInfo to return a bound call to GetAwaiter().GetResult() * more comments * find entrypoint methods uses the async binder * autoformat * use diagnostics from async binder * relocate tests * fix suggestions for review 8346 * add back no-async tests with better location * only allow named types * remove empty file; remove null checks from helper * forgot to add csproj file * emit into warning diagnostics instead of regular diagnostics * autoformat * remove unused file from merge * use diagnostic locations * move some methods around, split some tests up * fix non-returning async tests, remove bool return from check availability * add params test * style and expected output fix * switch back to null because default emits warnings * add partial method tests * fix locations for unix * better partial syntax tests * use expected length of 0 * use default length of 0 for expected output * check implementation symbol --- .../CSharp/Portable/Binder/Binder_Await.cs | 35 +- .../Portable/Compilation/CSharpCompilation.cs | 100 +- .../Portable/Compiler/MethodCompiler.cs | 24 +- .../CSharp/Portable/Symbols/MethodSymbol.cs | 86 +- .../Symbols/MethodSymbolExtensions.cs | 15 + .../SynthesizedEntryPointSymbol.cs | 122 ++- .../Test/Emit/CSharpCompilerEmitTest.csproj | 3 +- .../Emit/CodeGen/CodeGenAsyncMainTests.cs | 912 ++++++++++++++++++ .../Semantic/Semantics/BindingAsyncTests.cs | 375 ------- .../Test/Symbol/Symbols/Source/MethodTests.cs | 105 ++ .../Test/Utilities/CSharp/CSharpTestBase.cs | 8 + .../Utilities/VisualBasic/BasicTestBase.vb | 31 + .../CoreClr/CoreCLRRuntimeEnvironment.cs | 15 +- .../CoreClr/TestExecutionLoadContext.cs | 6 +- .../CodeRuntime/DesktopRuntimeEnvironment.cs | 28 +- .../CodeRuntime/RuntimeAssemblyManager.cs | 6 +- .../CommonTestBase.CompilationVerifier.cs | 11 +- src/Test/Utilities/Portable/CommonTestBase.cs | 12 +- .../Compilation/IRuntimeEnvironment.cs | 2 +- 19 files changed, 1366 insertions(+), 530 deletions(-) create mode 100644 src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncMainTests.cs 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/Compilation/CSharpCompilation.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs index dc84d30bf09d2..33be4e8231c4d 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs @@ -1457,7 +1457,7 @@ private MethodSymbol FindEntryPoint(CancellationToken cancellationToken, out Imm var viableEntryPoints = ArrayBuilder.GetInstance(); foreach (var candidate in entryPointCandidates) { - if (!candidate.HasEntryPointSignature(this)) + if (!HasEntryPointSignature(candidate, warnings)) { // a single error for partial methods warnings.Add(ErrorCode.WRN_InvalidMainSig, candidate.Locations.First(), candidate); @@ -1471,10 +1471,13 @@ private MethodSymbol FindEntryPoint(CancellationToken cancellationToken, out Imm continue; } + // PROTOTYPE(async-main): CheckFeatureAvailability should be called on non-async methods + // that return Task or Task if (candidate.IsAsync) { // PROTOTYPE(async-main): Get the diagnostic to point to a smaller syntax piece. - CheckFeatureAvailability(candidate.DeclaringSyntaxReferences.Single().GetSyntax(), MessageID.IDS_FeatureAsyncMain, diagnostics); + // PROTOTYPE(async-main): Switch diagnostics around if the type is not Task or Task + CheckFeatureAvailability(candidate.GetNonNullSyntaxNode(), MessageID.IDS_FeatureAsyncMain, diagnostics); } viableEntryPoints.Add(candidate); @@ -1525,6 +1528,99 @@ 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); + // PROTOTYPE(async-main): We might need to adjust the containing member of the binder to be the Main method + 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 where T is an int. + /// - has either no parameter or a single parameter of type string[] + /// + private bool HasEntryPointSignature(MethodSymbol method, DiagnosticBag bag) + { + if (method.IsVararg) + { + return 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; + } + } + + // Prior to 7.1, async methods were considered to "have the entrypoint signature". + // In order to keep back-compat, we need to let these through if compiling using a previous language version. + if (!returnsTaskOrTaskOfInt && method.IsAsync && this.LanguageVersion >= LanguageVersion.CSharp7_1) + { + return false; + } + + if (method.RefKind != RefKind.None) + { + return false; + } + + if (method.Parameters.Length == 0) + { + return true; + } + + if (method.Parameters.Length > 1) + { + return false; + } + + if (!method.ParameterRefKinds.IsDefault) + { + return false; + } + + var firstType = method.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 bool IsUnreferencedAssemblyIdentityDiagnosticCode(int code) => code == (int)ErrorCode.ERR_NoTypeDef; diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs index 8daff0363d5a4..ef0557b8635bb 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,9 +196,26 @@ 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 && compilation.ReturnsAwaitableToVoidOrInt(entryPoint, diagnostics)) + { + synthesizedEntryPoint = new SynthesizedEntryPointSymbol.AsyncForwardEntryPoint(compilation, diagnostics, entryPoint.ContainingType, entryPoint); + entryPoint = synthesizedEntryPoint; + if ((object)moduleBeingBuilt != null) + { + moduleBeingBuilt.AddSynthesizedDefinition(entryPoint.ContainingType, synthesizedEntryPoint); + } + } + if (((object)synthesizedEntryPoint != null) && (moduleBeingBuilt != null) && !hasDeclarationErrors && @@ -221,7 +240,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/Symbols/MethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs index ef63dda0f2e10..dae263a5acd7f 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; @@ -595,91 +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, int, , - /// or where T is an int. - /// - has either no parameter or a single parameter of type string[] - /// - internal bool HasEntryPointSignature(CSharpCompilation compilation) - { - - bool IsAsyncMainReturnType(TypeSymbol type) - { - var namedType = type as NamedTypeSymbol; - if (namedType == null) - { - return false; - } - else if (namedType.ConstructedFrom == compilation.GetWellKnownType(WellKnownType.System_Threading_Tasks_Task)) - { - // Change this to `namedType.IsNonGenericTaskType` if you want to support "task-like" objects. - return true; - } - else if (namedType.ConstructedFrom == compilation.GetWellKnownType(WellKnownType.System_Threading_Tasks_Task_T)) - { - // Change this to `namedType.IsGenericTaskType` if you want to support "task-like" objects. - return namedType.TypeArguments[0].SpecialType == SpecialType.System_Int32; - } - else - { - return false; - } - } - - if (IsVararg) - { - return false; - } - - TypeSymbol returnType = ReturnType; - bool isAsyncMainReturnType = IsAsyncMainReturnType(returnType); - if (returnType.SpecialType != SpecialType.System_Int32 && returnType.SpecialType != SpecialType.System_Void && !isAsyncMainReturnType) - { - return false; - } - - // Prior to 7.1, async methods were considered to "have the entrypoint signature". - // In order to keep back-compat, we need to let these through if compiling using a previous language version. - if (!isAsyncMainReturnType && IsAsync && compilation.LanguageVersion >= LanguageVersion.CSharp7_1) - { - return false; - } - - if (RefKind != RefKind.None) - { - 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) { 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..58187a5827b59 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,8 @@ internal abstract class SynthesizedEntryPointSymbol : MethodSymbol internal const string FactoryName = ""; private readonly NamedTypeSymbol _containingType; - private readonly TypeSymbol _returnType; + // PROTOTYPE(async-main): remove this and move it out into inheriting classes? + private TypeSymbol _returnType; internal static SynthesizedEntryPointSymbol Create(SynthesizedInteractiveInitializerMethod initializerMethod, DiagnosticBag diagnostics) { @@ -54,10 +57,9 @@ internal static SynthesizedEntryPointSymbol Create(SynthesizedInteractiveInitial } } - private SynthesizedEntryPointSymbol(NamedTypeSymbol containingType, TypeSymbol returnType) + private SynthesizedEntryPointSymbol(NamedTypeSymbol containingType, TypeSymbol returnType = null) { Debug.Assert((object)containingType != null); - Debug.Assert((object)returnType != null); _containingType = containingType; _returnType = returnType; @@ -286,7 +288,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,6 +331,104 @@ 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, DiagnosticBag diagnosticBag, 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(); + // PROTOTYPE(async-main): we might need to adjust the containing member of the binder to be the Main method. + 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 }; + + // PROTOTYPE(async-main): lower the tree. + var success = binder.GetAwaitableExpressionInfo(userMainInvocation, out _, out _, out _, out _getAwaiterGetResultCall, _userMainReturnTypeSyntax, diagnosticBag); + _returnType = _getAwaiterGetResultCall.Type; + + Debug.Assert( + ReturnType.SpecialType == SpecialType.System_Void || + ReturnType.SpecialType == SpecialType.System_Int32); + } + + public override string Name => MainName; + + public override ImmutableArray Parameters => _parameters; + + 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; @@ -344,15 +444,9 @@ internal ScriptEntryPoint(NamedTypeSymbol containingType, TypeSymbol returnType, _getResultMethod = getResultMethod; } - 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; // private static void
() // { @@ -365,7 +459,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); @@ -449,7 +543,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 55b77172ef679..5d82edc5b9f06 100644 --- a/src/Compilers/CSharp/Test/Emit/CSharpCompilerEmitTest.csproj +++ b/src/Compilers/CSharp/Test/Emit/CSharpCompilerEmitTest.csproj @@ -67,6 +67,7 @@ + @@ -190,4 +191,4 @@ - + \ No newline at end of file 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..3dd02d4548495 --- /dev/null +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncMainTests.cs @@ -0,0 +1,912 @@ +// 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; + +namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen +{ + public class CodeGenAsyncMainTests : EmitMetadataTestBase + { + + [Fact] + public void MultipleMainsOneOfWhichHasBadTaskType_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() + { + 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,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(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() + { + 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() + { + 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 {} +}"; + 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_PredefinedTypeNotFound, "int").WithArguments("System.Int32").WithLocation(6, 17), + // (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,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 AsyncEmitMainOfIntTest() + { + 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""); + 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() + { + 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,5): 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, @"async static Task Main(string[] args) + { + await Task.Factory.StartNew(() => { }); + }").WithArguments("async main", "7.1").WithLocation(6, 5)); + } + + [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() + { + 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_1)); + compilation.VerifyDiagnostics( + // (6,23): warning CS0028: 'A.Main()' has the wrong signature to be an entry point + // async static void Main() + Diagnostic(ErrorCode.WRN_InvalidMainSig, "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)); + var entry = compilation.GetEntryPoint(CancellationToken.None); + Assert.Null(entry); + } + + [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,5): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater. + // async static void Main() + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7, @"async static void Main() + { + await Task.Factory.StartNew(() => { }); + }").WithArguments("async main", "7.1").WithLocation(6, 5)); + } + + [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,23): warning CS0028: 'A.Main()' has the wrong signature to be an entry point + // async static void Main() + Diagnostic(ErrorCode.WRN_InvalidMainSig, "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"), + // (6,5): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater. + // async static int Main() + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7, @"async static int Main() + { + return await Task.Factory.StartNew(() => 5); + }").WithArguments("async main", "7.1").WithLocation(6, 5) +); + } + + [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,5): 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, @"async static Task Main(string[] args) + { + return await Task.Factory.StartNew(() => 5); + }").WithArguments("async main", "7.1").WithLocation(6, 5) +); + } + + [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,5): 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, @"async static Task Main() + { + return await Task.Factory.StartNew(() => 5); + }").WithArguments("async main", "7.1").WithLocation(6, 5) + ); + } + + [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 CS0028: 'A.Main()' has the wrong signature to be an entry point + // async static void Main() + Diagnostic(ErrorCode.WRN_InvalidMainSig, "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)); + } + } +} diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/BindingAsyncTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/BindingAsyncTests.cs index 692b282000480..1860dcfaa6482 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/BindingAsyncTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/BindingAsyncTests.cs @@ -1222,382 +1222,7 @@ interface IInterface } [Fact] - public void MainCanBeAsyncWithArgs() - { - var origSource = @" -using System.Threading.Tasks; - -class A -{ - async static Task Main(string[] args) - { - await Task.Factory.StartNew(() => { }); - } -}"; - var sources = new string[] { origSource, origSource.Replace("async ", "").Replace("await", "return") }; - foreach (var source in sources) - { - 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 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,5): 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, @"async static Task Main(string[] args) - { - await Task.Factory.StartNew(() => { }); - }").WithArguments("async main", "7.1").WithLocation(6, 5)); - } - - [Fact] - public void MainCanBeAsync() - { - var origSource = @" -using System.Threading.Tasks; - -class A -{ - async static Task Main() - { - await Task.Factory.StartNew(() => { }); - } -}"; - var sources = new string[] { origSource, origSource.Replace("async ", "").Replace("await", "return") }; - foreach (var source in sources) - { - 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() - { - 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_1)); - compilation.VerifyDiagnostics( - // (6,23): warning CS0028: 'A.Main()' has the wrong signature to be an entry point - // async static void Main() - Diagnostic(ErrorCode.WRN_InvalidMainSig, "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)); - var entry = compilation.GetEntryPoint(CancellationToken.None); - Assert.Null(entry); - } - - [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,5): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater. - // async static void Main() - Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7, @"async static void Main() - { - await Task.Factory.StartNew(() => { }); - }").WithArguments("async main", "7.1").WithLocation(6, 5)); - } - - [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,23): warning CS0028: 'A.Main()' has the wrong signature to be an entry point - // async static void Main() - Diagnostic(ErrorCode.WRN_InvalidMainSig, "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"), - // (6,5): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater. - // async static int Main() - Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7, @"async static int Main() - { - return await Task.Factory.StartNew(() => 5); - }").WithArguments("async main", "7.1").WithLocation(6, 5) -); - } - - [Fact] - public void MainCanBeAsyncAndGenericOnIntWithArgs() - { - var origSource = @" -using System.Threading.Tasks; - -class A -{ - async static Task Main(string[] args) - { - return await Task.Factory.StartNew(() => 5); - } -}"; - var sources = new string[] { origSource, origSource.Replace("async ", "").Replace("await", "") }; - foreach (var source in sources) - { - 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 MainCantBeAsyncAndGenericOnIntWithArgs_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,5): 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, @"async static Task Main(string[] args) - { - return await Task.Factory.StartNew(() => 5); - }").WithArguments("async main", "7.1").WithLocation(6, 5) -); - } - - [Fact] - public void MainCanBeAsyncAndGenericOnInt() - { - var origSource = @" -using System.Threading.Tasks; - -class A -{ - async static Task Main() - { - return await Task.Factory.StartNew(() => 5); - } -}"; - var sources = new string[] { origSource, origSource.Replace("async ", "").Replace("await", "") }; - foreach (var source in sources) - { - 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,5): 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, @"async static Task Main() - { - return await Task.Factory.StartNew(() => 5); - }").WithArguments("async main", "7.1").WithLocation(6, 5) - ); - } - - [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( - // (4,23): warning CS0402: 'A.Main()': an entry point cannot be generic or in a generic type - // async static void Main() - Diagnostic(ErrorCode.WRN_InvalidMainSig, "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 = @" diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/MethodTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/MethodTests.cs index a0ca5014afd75..e0b17fba31631 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/MethodTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/MethodTests.cs @@ -7,6 +7,7 @@ using Microsoft.CodeAnalysis.CSharp.Symbols; 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 +203,110 @@ 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 model = comp.GetSemanticModel(tree); + var voidTokens = tree.GetRoot().DescendantTokens().Where(t => t.Kind() == SyntaxKind.VoidKeyword).ToList(); + var node = tree.GetRoot().FindNode(voidTokens[0].Span); + + var mLocations = tree.GetRoot().DescendantTokens().Where(t => t.ValueText == "M").ToList(); + var otherSymbol = (MethodSymbol)model.GetDeclaredSymbolForNode(tree.GetRoot().FindNode(mLocations[1].Span)); + + 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 model = comp.GetSemanticModel(tree); + var token = tree.GetRoot().DescendantTokens().Where(t => t.Kind() == SyntaxKind.VoidKeyword).Skip(1).First(); + var node = tree.GetRoot().FindNode(token.Span); + + var mLocations = tree.GetRoot().DescendantTokens().Where(t => t.ValueText == "M").ToList(); + var otherSymbol = (MethodSymbol)model.GetDeclaredSymbolForNode(tree.GetRoot().FindNode(mLocations[0].Span)); + + 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 token = tree.DescendantTokens().Where(t => t.Kind() == SyntaxKind.VoidKeyword).First(); + var node = tree.FindNode(token.Span); + + 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 token = tree.DescendantTokens().Where(t => t.Kind() == SyntaxKind.VoidKeyword).First(); + var node = tree.FindNode(token.Span); + + 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(); From 01b6ea3321f99724454c7cfb7044fb9cb8d57cc8 Mon Sep 17 00:00:00 2001 From: Ty Overby Date: Fri, 28 Apr 2017 13:24:42 -0700 Subject: [PATCH 6/8] fix resx file --- src/Compilers/CSharp/Portable/CSharpResources.resx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 2967dd9950625..4812dab66b164 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -5061,6 +5061,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ async main + Tuple element name '{0}' is inferred. Please use language version {1} or greater to access an element by its inferred name. From cc4ea4693cf28b60daf31113c680bdbd55d081cb Mon Sep 17 00:00:00 2001 From: Ty Overby Date: Wed, 10 May 2017 09:41:55 -0700 Subject: [PATCH 7/8] Entrypoint detection stages (#19093) * simplify partial method return type syntax finder tests * Bug fixes and API use changes * address first review * better comment and compiler trait on class * fix tests, pull entrypoint check out into local function * unify diagnostics for intvoid or task mains * add more tests, change error text * address review * finish tests * be explicit on language version in tests * add another test, fix typo, add diagnostics even when they aren't likely to exist * rewrite error->warning code, add more cases to tests * change test language versions * formatting on comments and adding WithLocation to tests * fix formatting for some tests * fix some formatting and added withlocation * fix withlocation --- .../CSharp/Portable/Binder/Binder_Symbols.cs | 9 +- .../Portable/CSharpResources.Designer.cs | 29 +- .../CSharp/Portable/CSharpResources.resx | 6 +- .../Portable/Compilation/CSharpCompilation.cs | 129 +++-- .../CSharp/Portable/Errors/ErrorCode.cs | 2 +- .../SynthesizedEntryPointSymbol.cs | 1 - .../Emit/CodeGen/CodeGenAsyncMainTests.cs | 459 +++++++++++++++--- .../Semantic/Semantics/BindingAsyncTests.cs | 10 +- .../Test/Symbol/Symbols/Source/MethodTests.cs | 23 +- .../Portable/Traits/CompilerFeature.cs | 1 + 10 files changed, 520 insertions(+), 149 deletions(-) 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 1280b1141bdb8..de6e009a64bba 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.. /// @@ -9852,22 +9863,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); } } diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 78bf4e6b5f7b5..82afbcaa4e07b 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -3596,9 +3596,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 @@ -5073,4 +5070,7 @@ 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 + diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs index c1dada07c57d0..9e078424051a2 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs @@ -1453,42 +1453,101 @@ 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 (!HasEntryPointSignature(candidate, warnings)) + if (!isCandidate) { - // a single error for partial methods - warnings.Add(ErrorCode.WRN_InvalidMainSig, candidate.Locations.First(), candidate); - continue; + noMainFoundDiagnostics.Add(ErrorCode.WRN_InvalidMainSig, candidate.Locations.First(), candidate); + noMainFoundDiagnostics.AddRange(specificDiagnostics); + return false; } if (candidate.IsGenericMethod || candidate.ContainingType.IsGenericType) { // a single error for partial methods: - warnings.Add(ErrorCode.WRN_MainCantBeGeneric, candidate.Locations.First(), candidate); - continue; + noMainFoundDiagnostics.Add(ErrorCode.WRN_MainCantBeGeneric, candidate.Locations.First(), candidate); + return false; } + return true; + } + + var viableEntryPoints = ArrayBuilder.GetInstance(); + + foreach (var candidate in entryPointCandidates) + { + var perCandidateBag = DiagnosticBag.GetInstance(); + var (IsCandidate, IsTaskLike) = HasEntryPointSignature(candidate, perCandidateBag); - // PROTOTYPE(async-main): CheckFeatureAvailability should be called on non-async methods - // that return Task or Task - if (candidate.IsAsync) + if (IsTaskLike) { - // PROTOTYPE(async-main): Get the diagnostic to point to a smaller syntax piece. - // PROTOTYPE(async-main): Switch diagnostics around if the type is not Task or Task - CheckFeatureAvailability(candidate.GetNonNullSyntaxNode(), MessageID.IDS_FeatureAsyncMain, diagnostics); + taskEntryPoints.Add((IsCandidate, candidate, perCandidateBag)); + } + else + { + 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) + { + // PROTOTYPE(async-main): Get the diagnostic to point to a smaller syntax piece. + if (CheckValid(Candidate, IsValid, SpecificDiagnostics) && + CheckFeatureAvailability(Candidate.GetNonNullSyntaxNode(), MessageID.IDS_FeatureAsyncMain, diagnostics)) + { + diagnostics.AddRange(SpecificDiagnostics); + viableEntryPoints.Add(Candidate); + } + } } - 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) @@ -1518,7 +1577,9 @@ private MethodSymbol FindEntryPoint(CancellationToken cancellationToken, out Imm entryPoint = viableEntryPoints[0]; } + taskEntryPoints.Free(); viableEntryPoints.Free(); + noMainFoundDiagnostics.Free(); return entryPoint; } finally @@ -1561,15 +1622,16 @@ internal bool ReturnsAwaitableToVoidOrInt(MethodSymbol method, DiagnosticBag dia /// /// Checks if the method has an entry point compatible signature, i.e. - /// - the return type is either void, int, , - /// or where T is an int. + /// - 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 HasEntryPointSignature(MethodSymbol method, DiagnosticBag bag) + private (bool IsCandidate, bool IsTaskLike) HasEntryPointSignature(MethodSymbol method, DiagnosticBag bag) { if (method.IsVararg) { - return false; + return (false, false); } TypeSymbol returnType = method.ReturnType; @@ -1580,45 +1642,38 @@ private bool HasEntryPointSignature(MethodSymbol method, DiagnosticBag bag) returnsTaskOrTaskOfInt = ReturnsAwaitableToVoidOrInt(method, bag); if (!returnsTaskOrTaskOfInt) { - return false; + return (false, false); } } - // Prior to 7.1, async methods were considered to "have the entrypoint signature". - // In order to keep back-compat, we need to let these through if compiling using a previous language version. - if (!returnsTaskOrTaskOfInt && method.IsAsync && this.LanguageVersion >= LanguageVersion.CSharp7_1) - { - return false; - } - if (method.RefKind != RefKind.None) { - return false; + return (false, returnsTaskOrTaskOfInt); } if (method.Parameters.Length == 0) { - return true; + return (true, returnsTaskOrTaskOfInt); } if (method.Parameters.Length > 1) { - return false; + return (false, returnsTaskOrTaskOfInt); } if (!method.ParameterRefKinds.IsDefault) { - return false; + return (false, returnsTaskOrTaskOfInt); } var firstType = method.Parameters[0].Type; if (firstType.TypeKind != TypeKind.Array) { - return false; + return (false, returnsTaskOrTaskOfInt); } var array = (ArrayTypeSymbol)firstType; - return array.IsSZArray && array.ElementType.SpecialType == SpecialType.System_String; + return (array.IsSZArray && array.ElementType.SpecialType == SpecialType.System_String, returnsTaskOrTaskOfInt); } internal override bool IsUnreferencedAssemblyIdentityDiagnosticCode(int code) diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index cdaf5879c9e2a..ea7ce6b4a38fd 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/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs index 58187a5827b59..826b6d3ae3d80 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs @@ -388,7 +388,6 @@ internal override BoundBlock CreateBody() { var syntax = _userMainReturnTypeSyntax; - if (ReturnsVoid) { return new BoundBlock( diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncMainTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncMainTests.cs index 3dd02d4548495..fbaae3a50786b 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncMainTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncMainTests.cs @@ -3,14 +3,15 @@ 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_WithMainType() + public void MultipleMainsOneOfWhichHasBadTaskType_CSharp71_WithMainType() { var source = @" using System.Threading.Tasks; @@ -35,7 +36,7 @@ static void Main(string[] args) { } } [Fact] - public void MultipleMainsOneOfWhichHasBadTaskType() + public void MultipleMainsOneOfWhichHasBadTaskType_CSharp7() { var source = @" using System.Threading.Tasks; @@ -52,21 +53,114 @@ static Task Main() { } static void Main(string[] args) { } }"; - var sourceCompilation = CreateStandardCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + 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,12): error CS1986: 'await' requires that the type Task have a suitable GetAwaiter method + // (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.ERR_BadAwaitArg, "Task").WithArguments("System.Threading.Tasks.Task").WithLocation(11, 12), + 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() + 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; @@ -105,12 +199,48 @@ static Task Main() { // 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) -); + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); } [Fact] - public void TaskOfTGetAwaiterReturnsVoid() + 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; @@ -145,6 +275,7 @@ static Task Main() { // using System; Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using System;").WithLocation(2, 1)); } + [Fact] public void TaskGetAwaiterReturnsVoid() { @@ -215,6 +346,8 @@ 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(); @@ -241,9 +374,6 @@ static Task Main() { 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_PredefinedTypeNotFound, "int").WithArguments("System.Int32").WithLocation(6, 17), - // (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,22): warning CS0028: 'Program.Main()' has the wrong signature to be an entry point // static Task Main() { @@ -292,26 +422,6 @@ static Task Main() { Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); } - [Fact] - public void AsyncEmitMainOfIntTest() - { - 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""); - 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() { @@ -548,6 +658,8 @@ async static Task Main(string[] args) }"; var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)); compilation.VerifyDiagnostics( + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1), // (6,5): 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, @"async static Task Main(string[] args) @@ -596,7 +708,7 @@ static Task Main() } [Fact] - public void MainCantBeAsyncVoid() + public void MainCantBeAsyncVoid_CSharp7() { var source = @" using System.Threading.Tasks; @@ -608,38 +720,13 @@ async static void Main() await Task.Factory.StartNew(() => { }); } }"; - var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)); + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)); compilation.VerifyDiagnostics( - // (6,23): warning CS0028: 'A.Main()' has the wrong signature to be an entry point + // (6,23): error CS9003: Async Main methods must return Task or Task // async static void Main() - Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("A.Main()").WithLocation(6, 23), + 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)); - var entry = compilation.GetEntryPoint(CancellationToken.None); - Assert.Null(entry); - } - - [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,5): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater. - // async static void Main() - Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7, @"async static void Main() - { - await Task.Factory.StartNew(() => { }); - }").WithArguments("async main", "7.1").WithLocation(6, 5)); } [Fact] @@ -660,9 +747,9 @@ async static int Main() // (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,23): warning CS0028: 'A.Main()' has the wrong signature to be an entry point - // async static void Main() - Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("A.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); @@ -686,14 +773,12 @@ async static int Main() 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"), - // (6,5): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater. + 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_FeatureNotAvailableInVersion7, @"async static int Main() - { - return await Task.Factory.StartNew(() => 5); - }").WithArguments("async main", "7.1").WithLocation(6, 5) -); + 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] @@ -751,13 +836,14 @@ async static Task Main(string[] args) }"; var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)); compilation.VerifyDiagnostics( + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1), // (6,5): 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, @"async static Task Main(string[] args) { return await Task.Factory.StartNew(() => 5); - }").WithArguments("async main", "7.1").WithLocation(6, 5) -); + }").WithArguments("async main", "7.1").WithLocation(6, 5)); } [Fact] @@ -779,6 +865,7 @@ async static Task Main() Assert.NotNull(entry); Assert.Equal("System.Threading.Tasks.Task A.Main()", entry.ToTestDisplayString()); } + [Fact] public void MainCanReturnTaskAndGenericOnInt_NoAsync() { @@ -819,8 +906,9 @@ async static Task Main() Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7, @"async static Task Main() { return await Task.Factory.StartNew(() => 5); - }").WithArguments("async main", "7.1").WithLocation(6, 5) - ); + }").WithArguments("async main", "7.1").WithLocation(6, 5), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); } [Fact] @@ -860,9 +948,9 @@ async static void Main() } }"; CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)).VerifyDiagnostics( - // (6,23): warning CS0028: 'A.Main()' has the wrong signature to be an entry point + // (6,23): warning CS0402: 'A.Main()': an entry point cannot be generic or in a generic type // async static void Main() - Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("A.Main()").WithLocation(6, 23), + 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)); } @@ -908,5 +996,220 @@ async static void Main(bool truth) // 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,5): 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, @"async static Task Main() + { + System.Console.WriteLine(""Task""); + return 0; + }").WithArguments("async main", "7.1").WithLocation(6, 5), + // (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 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); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/BindingAsyncTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/BindingAsyncTests.cs index ea6c34daf69c4..b5ceade1893ea 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/BindingAsyncTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/BindingAsyncTests.cs @@ -3403,7 +3403,7 @@ public static int Main() } [Fact, WorkItem(547081, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/547081")] - public void Repro_17885() + public void Repro_17885_CSharp_71() { var source = @" using System.Threading.Tasks; @@ -3416,7 +3416,7 @@ async public static Task Main() CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7_1)).VerifyDiagnostics( // (5,30): warning CS1998: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. // async public static Task Main() - Diagnostic(ErrorCode.WRN_AsyncLacksAwaits, "Main")); + Diagnostic(ErrorCode.WRN_AsyncLacksAwaits, "Main").WithLocation(5, 30)); } [Fact, WorkItem(547081, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/547081")] @@ -3433,12 +3433,14 @@ async public static Task Main() CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)).VerifyDiagnostics( // (5,30): warning CS1998: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. // async public static Task Main() - Diagnostic(ErrorCode.WRN_AsyncLacksAwaits, "Main"), + Diagnostic(ErrorCode.WRN_AsyncLacksAwaits, "Main").WithLocation(5, 30), // (5,5): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater. // async public static Task Main() Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7, @"async public static Task Main() { - }").WithArguments("async main", "7.1").WithLocation(5, 5)); + }").WithArguments("async main", "7.1").WithLocation(5, 5), + // error CS5001: Program does not contain a static 'Main' method suitable for an entry point + Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1)); } [Fact, WorkItem(547088, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/547088")] diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/MethodTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/MethodTests.cs index e0b17fba31631..7b9c0c19250a6 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/MethodTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/MethodTests.cs @@ -5,6 +5,7 @@ using System.Linq; using Microsoft.CodeAnalysis.CSharp.Emit; using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Text; @@ -222,12 +223,10 @@ partial void M() {} var returnSyntax = m.ExtractReturnTypeSyntax(); var tree = comp.SyntaxTrees.Single(); - var model = comp.GetSemanticModel(tree); - var voidTokens = tree.GetRoot().DescendantTokens().Where(t => t.Kind() == SyntaxKind.VoidKeyword).ToList(); - var node = tree.GetRoot().FindNode(voidTokens[0].Span); + var node = tree.GetRoot().DescendantNodes().OfType().Where(n => n.Keyword.Kind() == SyntaxKind.VoidKeyword).First(); - var mLocations = tree.GetRoot().DescendantTokens().Where(t => t.ValueText == "M").ToList(); - var otherSymbol = (MethodSymbol)model.GetDeclaredSymbolForNode(tree.GetRoot().FindNode(mLocations[1].Span)); + var otherSymbol = m.PartialImplementationPart; + Assert.True(otherSymbol.IsPartialImplementation()); Assert.Equal(node, returnSyntax); Assert.Equal(node, otherSymbol.ExtractReturnTypeSyntax()); @@ -252,12 +251,10 @@ public partial class A { var returnSyntax = m.ExtractReturnTypeSyntax(); var tree = comp.SyntaxTrees.Single(); - var model = comp.GetSemanticModel(tree); - var token = tree.GetRoot().DescendantTokens().Where(t => t.Kind() == SyntaxKind.VoidKeyword).Skip(1).First(); - var node = tree.GetRoot().FindNode(token.Span); + var node = tree.GetRoot().DescendantNodes().OfType().Where(n => n.Keyword.Kind() == SyntaxKind.VoidKeyword).Last(); - var mLocations = tree.GetRoot().DescendantTokens().Where(t => t.ValueText == "M").ToList(); - var otherSymbol = (MethodSymbol)model.GetDeclaredSymbolForNode(tree.GetRoot().FindNode(mLocations[0].Span)); + var otherSymbol = m.PartialImplementationPart; + Assert.True(otherSymbol.IsPartialImplementation()); Assert.Equal(node, returnSyntax); Assert.Equal(node, otherSymbol.ExtractReturnTypeSyntax()); @@ -279,8 +276,7 @@ partial void M() {} var returnSyntax = m.ExtractReturnTypeSyntax(); var tree = comp.SyntaxTrees.Single().GetRoot(); - var token = tree.DescendantTokens().Where(t => t.Kind() == SyntaxKind.VoidKeyword).First(); - var node = tree.FindNode(token.Span); + var node = tree.DescendantNodes().OfType().Where(n => n.Keyword.Kind() == SyntaxKind.VoidKeyword).Single(); Assert.Equal(node, returnSyntax); } @@ -301,8 +297,7 @@ public void PartialExtractSyntaxLocation_OnlyDecl() var returnSyntax = m.ExtractReturnTypeSyntax(); var tree = comp.SyntaxTrees.Single().GetRoot(); - var token = tree.DescendantTokens().Where(t => t.Kind() == SyntaxKind.VoidKeyword).First(); - var node = tree.FindNode(token.Span); + var node = tree.DescendantNodes().OfType().Where(n => n.Keyword.Kind() == SyntaxKind.VoidKeyword).Single(); Assert.Equal(node, returnSyntax); } 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, } } From b0249eef9b8ef140dedc20d83c06e6c1ba36023e Mon Sep 17 00:00:00 2001 From: Ty Overby Date: Thu, 11 May 2017 09:09:10 -0700 Subject: [PATCH 8/8] Remove remaining prototype comments (#19392) * remove remaining prototype comments, add extension method tests * fix old test, address review feedback * ReturnType implemented on each subclass * added more tests on non-async task returning mains * add obsolete tests * autoformat * add better tests for successful task overrides --- .../Portable/Compilation/CSharpCompilation.cs | 4 +- .../Portable/Compiler/MethodCompiler.cs | 39 ++- .../SynthesizedEntryPointSymbol.cs | 36 ++- .../Emit/CodeGen/CodeGenAsyncMainTests.cs | 295 ++++++++++++++++-- .../Semantic/Semantics/BindingAsyncTests.cs | 6 +- 5 files changed, 328 insertions(+), 52 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs index 9e078424051a2..b64ffb101ed45 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs @@ -1512,9 +1512,8 @@ bool CheckValid(MethodSymbol candidate, bool isCandidate, DiagnosticBag specific { foreach (var (IsValid, Candidate, SpecificDiagnostics) in taskEntryPoints) { - // PROTOTYPE(async-main): Get the diagnostic to point to a smaller syntax piece. if (CheckValid(Candidate, IsValid, SpecificDiagnostics) && - CheckFeatureAvailability(Candidate.GetNonNullSyntaxNode(), MessageID.IDS_FeatureAsyncMain, diagnostics)) + CheckFeatureAvailability(Candidate.ExtractReturnTypeSyntax(), MessageID.IDS_FeatureAsyncMain, diagnostics)) { diagnostics.AddRange(SpecificDiagnostics); viableEntryPoints.Add(Candidate); @@ -1611,7 +1610,6 @@ internal bool ReturnsAwaitableToVoidOrInt(MethodSymbol method, DiagnosticBag dia var syntax = method.ExtractReturnTypeSyntax(); var dumbInstance = new BoundLiteral(syntax, ConstantValue.Null, method.ReturnType); - // PROTOTYPE(async-main): We might need to adjust the containing member of the binder to be the Main method var binder = GetBinder(syntax); BoundExpression result; var success = binder.GetAwaitableExpressionInfo(dumbInstance, out _, out _, out _, out result, syntax, diagnostics); diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs index 731985d09b33f..d16155984fb51 100644 --- a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs +++ b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs @@ -206,9 +206,9 @@ private static MethodSymbol GetEntryPoint(CSharpCompilation compilation, PEModul // entryPoint can be a SynthesizedEntryPointSymbol if a script is being compiled. SynthesizedEntryPointSymbol synthesizedEntryPoint = entryPoint as SynthesizedEntryPointSymbol; - if ((object)synthesizedEntryPoint == null && compilation.ReturnsAwaitableToVoidOrInt(entryPoint, diagnostics)) + if ((object)synthesizedEntryPoint == null && (entryPoint.ReturnType.IsGenericTaskType(compilation) || entryPoint.ReturnType.IsNonGenericTaskType(compilation))) { - synthesizedEntryPoint = new SynthesizedEntryPointSymbol.AsyncForwardEntryPoint(compilation, diagnostics, entryPoint.ContainingType, entryPoint); + synthesizedEntryPoint = new SynthesizedEntryPointSymbol.AsyncForwardEntryPoint(compilation, entryPoint.ContainingType, entryPoint); entryPoint = synthesizedEntryPoint; if ((object)moduleBeingBuilt != null) { @@ -221,13 +221,44 @@ private static MethodSymbol GetEntryPoint(CSharpCompilation compilation, PEModul !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, diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs index 826b6d3ae3d80..7dff9eb3426f5 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs @@ -19,8 +19,6 @@ internal abstract class SynthesizedEntryPointSymbol : MethodSymbol internal const string FactoryName = ""; private readonly NamedTypeSymbol _containingType; - // PROTOTYPE(async-main): remove this and move it out into inheriting classes? - private TypeSymbol _returnType; internal static SynthesizedEntryPointSymbol Create(SynthesizedInteractiveInitializerMethod initializerMethod, DiagnosticBag diagnostics) { @@ -57,12 +55,11 @@ internal static SynthesizedEntryPointSymbol Create(SynthesizedInteractiveInitial } } - private SynthesizedEntryPointSymbol(NamedTypeSymbol containingType, TypeSymbol returnType = null) + private SynthesizedEntryPointSymbol(NamedTypeSymbol containingType) { Debug.Assert((object)containingType != null); _containingType = containingType; - _returnType = returnType; } internal override bool GenerateDebugInfo @@ -130,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; } @@ -162,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 @@ -341,7 +333,7 @@ internal sealed class AsyncForwardEntryPoint : SynthesizedEntryPointSymbol private readonly ImmutableArray _parameters; - internal AsyncForwardEntryPoint(CSharpCompilation compilation, DiagnosticBag diagnosticBag, NamedTypeSymbol containingType, MethodSymbol userMain) : + 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 @@ -349,7 +341,6 @@ internal AsyncForwardEntryPoint(CSharpCompilation compilation, DiagnosticBag dia Debug.Assert(userMain.ParameterCount == 0 || userMain.ParameterCount == 1); _userMainReturnTypeSyntax = userMain.ExtractReturnTypeSyntax(); - // PROTOTYPE(async-main): we might need to adjust the containing member of the binder to be the Main method. var binder = compilation.GetBinder(_userMainReturnTypeSyntax); _parameters = SynthesizedParameterSymbol.DeriveParameters(userMain, this); @@ -371,9 +362,10 @@ internal AsyncForwardEntryPoint(CSharpCompilation compilation, DiagnosticBag dia type: userMain.ReturnType) { WasCompilerGenerated = true }; - // PROTOTYPE(async-main): lower the tree. - var success = binder.GetAwaitableExpressionInfo(userMainInvocation, out _, out _, out _, out _getAwaiterGetResultCall, _userMainReturnTypeSyntax, diagnosticBag); - _returnType = _getAwaiterGetResultCall.Type; + // 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 || @@ -384,6 +376,8 @@ internal AsyncForwardEntryPoint(CSharpCompilation compilation, DiagnosticBag dia public override ImmutableArray Parameters => _parameters; + public override TypeSymbol ReturnType => _getAwaiterGetResultCall.Type; + internal override BoundBlock CreateBody() { var syntax = _userMainReturnTypeSyntax; @@ -432,21 +426,25 @@ 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 => MainName; public override ImmutableArray Parameters => ImmutableArray.Empty; + public override TypeSymbol ReturnType => _returnType; + // private static void
() // { // var script = new Script(); @@ -516,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 @@ -535,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); diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncMainTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncMainTests.cs index fbaae3a50786b..34bd9f103093a 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncMainTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncMainTests.cs @@ -658,14 +658,33 @@ async static Task Main(string[] args) }"; var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)); compilation.VerifyDiagnostics( - // error CS5001: Program does not contain a static 'Main' method suitable for an entry point - Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1), - // (6,5): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater. + // (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, @"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) { - await Task.Factory.StartNew(() => { }); - }").WithArguments("async main", "7.1").WithLocation(6, 5)); + 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] @@ -836,14 +855,33 @@ async static Task Main(string[] args) }"; var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)); compilation.VerifyDiagnostics( - // error CS5001: Program does not contain a static 'Main' method suitable for an entry point - Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1), - // (6,5): 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, @"async static Task Main(string[] args) + // (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 await Task.Factory.StartNew(() => 5); - }").WithArguments("async main", "7.1").WithLocation(6, 5)); + 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] @@ -901,12 +939,31 @@ async static Task Main() }"; var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp7)); compilation.VerifyDiagnostics( - // (6,5): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater. + // (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, @"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 await Task.Factory.StartNew(() => 5); - }").WithArguments("async main", "7.1").WithLocation(6, 5), + 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)); } @@ -1146,13 +1203,9 @@ async static Task Main(string[] args) // (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,5): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater. + // (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, @"async static Task Main() - { - System.Console.WriteLine(""Task""); - return 0; - }").WithArguments("async main", "7.1").WithLocation(6, 5), + 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), @@ -1160,6 +1213,36 @@ async static Task Main(string[] args) 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() @@ -1185,7 +1268,7 @@ async static Task Main(string[] args) // (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); + CompileAndVerify(compilation, expectedOutput: "Non Task Main", expectedReturnCode: 0); } [Fact] @@ -1211,5 +1294,169 @@ async static Task Main(string[] args) 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 b5ceade1893ea..8ca2ec00d7152 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/BindingAsyncTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/BindingAsyncTests.cs @@ -3434,11 +3434,9 @@ async public static Task Main() // (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,5): error CS8107: Feature 'async main' is not available in C# 7. Please use language version 7.1 or greater. + // (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, @"async public static Task Main() - { - }").WithArguments("async main", "7.1").WithLocation(5, 5), + 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)); }