Add support for C# 11 and newer versions
If a langversion is explicitly provided, use it verbatim. The compiler will check validity and emit and error if needed. Otherwise, use the C# language version known to be supported by the current runtime, as we may be using csc/Roslyn from a newer runtime where the "latest" language features depend on new APIs that aren't available on the current runtime. If we do not have a language version mapping for the runtime, use "latest" so all its language features are available until we add the version mapping. Fix #166 - Support for C# 11
This commit is contained in:
Родитель
821b31a322
Коммит
51a5db5415
|
@ -38,11 +38,13 @@ namespace Mono.TextTemplating
|
|||
CancellationToken token)
|
||||
{
|
||||
CSharpCommandLineArguments args = null;
|
||||
bool hasLangVersionArg = false;
|
||||
if (arguments.AdditionalArguments != null) {
|
||||
var splitArgs = CommandLineParser.SplitCommandLineIntoArguments (arguments.AdditionalArguments, false);
|
||||
if (splitArgs.Any ()) {
|
||||
args = CSharpCommandLineParser.Default.Parse (splitArgs, arguments.TempDirectory, null, null);
|
||||
}
|
||||
hasLangVersionArg = splitArgs.Any (CSharpLangVersionHelper.IsLangVersionArg);
|
||||
}
|
||||
|
||||
var references = new List<MetadataReference> ();
|
||||
|
@ -52,15 +54,31 @@ namespace Mono.TextTemplating
|
|||
|
||||
var parseOptions = args?.ParseOptions ?? new CSharpParseOptions();
|
||||
|
||||
// arguments.LangVersion takes precedence over any langversion arg in arguments.AdditionalArguments
|
||||
// This behavior should match that of CscCodeCompiler.CompileFile and CSharpLangVersionHelper.GetLangVersionArg
|
||||
if (arguments.LangVersion != null) {
|
||||
if (LanguageVersionFacts.TryParse(arguments.LangVersion, out var langVersion)) {
|
||||
parseOptions = parseOptions.WithLanguageVersion (langVersion);
|
||||
hasLangVersionArg = true;
|
||||
} else {
|
||||
throw new RoslynCodeCompilerException ($"Unknown value '{arguments.LangVersion}' for langversion");
|
||||
}
|
||||
} else {
|
||||
// need to update this when updating referenced roslyn binaries
|
||||
CSharpLangVersionHelper.GetBestSupportedLangVersion (runtime, CSharpLangVersion.v9_0);
|
||||
}
|
||||
|
||||
if (!hasLangVersionArg) {
|
||||
// Default to the highest language version supported by the runtime
|
||||
// as we may be using a version of Roslyn where "latest" language
|
||||
// features depend on new APIs that aren't available on the current runtime.
|
||||
// If the runtime is an unknown version, its MaxSupportedLangVersion will default
|
||||
// to "latest" so new runtime versions will work before we explicitly add support for them.
|
||||
if (LanguageVersionFacts.TryParse (CSharpLangVersionHelper.ToString (runtime.MaxSupportedLangVersion), out var runtimeSupportedLangVersion)) {
|
||||
parseOptions = parseOptions.WithLanguageVersion (runtimeSupportedLangVersion);
|
||||
} else {
|
||||
// if Roslyn did not recognize the runtime's default lang version, it's newer than
|
||||
// this version of Roslyn supports, so default to the latest supported version
|
||||
parseOptions = parseOptions.WithLanguageVersion (LanguageVersion.Latest);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var syntaxTrees = new List<SyntaxTree> ();
|
||||
|
|
|
@ -67,7 +67,26 @@ namespace Mono.TextTemplating.Tests
|
|||
#endif
|
||||
}
|
||||
|
||||
#if !NET472
|
||||
[Fact]
|
||||
public async Task CSharp11StructRecords ()
|
||||
{
|
||||
string template = "<#+ public record struct Foo(string bar); #>";
|
||||
var gen = new TemplateGenerator ();
|
||||
string outputName = null;
|
||||
await gen.ProcessTemplateAsync (null, template, outputName);
|
||||
|
||||
CompilerError firstError = gen.Errors.OfType<CompilerError> ().FirstOrDefault ();
|
||||
|
||||
// note: when running on netsdk we use the highest available csc regardless of runtime version,
|
||||
// so struct records will always be available on our test environments
|
||||
#if NETFRAMEWORK
|
||||
Assert.NotNull (firstError);
|
||||
#else
|
||||
Assert.Null (firstError);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !NETFRAMEWORK
|
||||
[Fact]
|
||||
public async Task SetLangVersionViaAttribute ()
|
||||
{
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
#if NETFRAMEWORK
|
||||
#nullable enable annotations
|
||||
#else
|
||||
#nullable enable
|
||||
#endif
|
||||
|
||||
namespace Mono.TextTemplating.CodeCompilation;
|
||||
|
||||
enum CSharpLangVersion
|
||||
{
|
||||
v5_0,
|
||||
v6_0,
|
||||
v7_0,
|
||||
v7_1,
|
||||
v7_2,
|
||||
v7_3,
|
||||
v8_0,
|
||||
v9_0,
|
||||
v10_0,
|
||||
v11_0,
|
||||
v12_0,
|
||||
Latest = 1024 // make sure value doesn't change as we add new C# versions
|
||||
}
|
|
@ -1,90 +1,79 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
#if NETFRAMEWORK
|
||||
#nullable enable annotations
|
||||
#else
|
||||
#nullable enable
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Mono.TextTemplating.CodeCompilation
|
||||
namespace Mono.TextTemplating.CodeCompilation;
|
||||
|
||||
static class CSharpLangVersionHelper
|
||||
{
|
||||
enum CSharpLangVersion
|
||||
public static bool HasLangVersionArg (string args) =>
|
||||
!string.IsNullOrEmpty(args)
|
||||
&& (args.IndexOf ("langversion", StringComparison.OrdinalIgnoreCase) > -1)
|
||||
&& ProcessArgumentBuilder.TryParse (args, out var parsedArgs)
|
||||
&& parsedArgs.Any (IsLangVersionArg);
|
||||
|
||||
public static bool IsLangVersionArg (string arg) =>
|
||||
(arg[0] == '-' || arg[0] == '/')
|
||||
&& arg.IndexOf ("langversion", StringComparison.OrdinalIgnoreCase) == 1;
|
||||
|
||||
public static string? GetLangVersionArg (CodeCompilerArguments arguments, RuntimeInfo runtime)
|
||||
{
|
||||
v5_0,
|
||||
v6_0,
|
||||
v7_0,
|
||||
v7_1,
|
||||
v7_2,
|
||||
v7_3,
|
||||
v8_0,
|
||||
v9_0,
|
||||
Latest
|
||||
}
|
||||
|
||||
static class CSharpLangVersionHelper
|
||||
{
|
||||
public static CSharpLangVersion GetBestSupportedLangVersion (RuntimeInfo runtime, CSharpLangVersion? compilerLangVersion = null)
|
||||
=> (CSharpLangVersion)Math.Min ((int)(compilerLangVersion ?? runtime.MaxSupportedLangVersion), (int) (runtime switch {
|
||||
{ Kind: RuntimeKind.NetCore, Version.Major: > 5 } => CSharpLangVersion.Latest,
|
||||
{ Kind: RuntimeKind.NetCore, Version.Major: 5 } => CSharpLangVersion.v9_0,
|
||||
{ Kind: RuntimeKind.NetCore, Version.Major: 3 } => CSharpLangVersion.v8_0,
|
||||
_ => CSharpLangVersion.v7_3,
|
||||
}));
|
||||
|
||||
static bool HasLangVersionArg (string args) =>
|
||||
!string.IsNullOrEmpty(args)
|
||||
&& (args.IndexOf ("langversion", StringComparison.OrdinalIgnoreCase) > -1)
|
||||
&& ProcessArgumentBuilder.TryParse (args, out var parsedArgs)
|
||||
&& parsedArgs.Any (a => a.IndexOf ("langversion", StringComparison.OrdinalIgnoreCase) == 1);
|
||||
|
||||
static string ToString (CSharpLangVersion version) => version switch {
|
||||
CSharpLangVersion.v5_0 => "5",
|
||||
CSharpLangVersion.v6_0 => "6",
|
||||
CSharpLangVersion.v7_0 => "7",
|
||||
CSharpLangVersion.v7_1 => "7.1",
|
||||
CSharpLangVersion.v7_2 => "7.2",
|
||||
CSharpLangVersion.v7_3 => "7.3",
|
||||
CSharpLangVersion.v8_0 => "8.0",
|
||||
CSharpLangVersion.v9_0 => "9.0",
|
||||
CSharpLangVersion.Latest => "latest",
|
||||
_ => throw new ArgumentException ($"Not a valid value: '{version}'", nameof (version))
|
||||
};
|
||||
|
||||
public static string GetLangVersionArg (CodeCompilerArguments arguments, RuntimeInfo runtime)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace (arguments.LangVersion)) {
|
||||
return $"-langversion:{arguments.LangVersion}";
|
||||
}
|
||||
|
||||
if (HasLangVersionArg (arguments.AdditionalArguments)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $"-langversion:{ToString(GetBestSupportedLangVersion(runtime))}";
|
||||
// Arguments.LangVersion takes precedence over -langversion in arguments.AdditionalArguments.
|
||||
// This behavior should match that of CscCodeCompiler.CompileFile and RoslynCodeCompiler.CompileFileInternal
|
||||
if (!string.IsNullOrWhiteSpace (arguments.LangVersion)) {
|
||||
return $"-langversion:{arguments.LangVersion}";
|
||||
}
|
||||
|
||||
public static CSharpLangVersion? FromRoslynPackageVersion (string roslynPackageVersion)
|
||||
=> SemVersion.TryParse (roslynPackageVersion, out var version)
|
||||
? version switch {
|
||||
{ Major: > 3 } => CSharpLangVersion.v9_0,
|
||||
{ Major: 3, Minor: >= 8 } => CSharpLangVersion.v9_0,
|
||||
{ Major: 3, Minor: >= 3 } => CSharpLangVersion.v8_0,
|
||||
// ignore 8.0 preview support in 3.0-3.2
|
||||
{ Major: 2, Minor: >= 8 } => CSharpLangVersion.v7_3,
|
||||
{ Major: 2, Minor: >= 6 } => CSharpLangVersion.v7_2,
|
||||
{ Major: 2, Minor: >= 3 } => CSharpLangVersion.v7_1,
|
||||
{ Major: 2 } => CSharpLangVersion.v7_0,
|
||||
_ => CSharpLangVersion.v6_0
|
||||
}
|
||||
: null;
|
||||
if (HasLangVersionArg (arguments.AdditionalArguments)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history
|
||||
public static CSharpLangVersion FromNetCoreSdkVersion (SemVersion sdkVersion)
|
||||
=> sdkVersion switch {
|
||||
{ Major: >= 5 } => CSharpLangVersion.v9_0,
|
||||
{ Major: 3 } => CSharpLangVersion.v8_0,
|
||||
{ Major: 2, Minor: >= 1 } => CSharpLangVersion.v7_3,
|
||||
{ Major: 2, Minor: >= 0 } => CSharpLangVersion.v7_1,
|
||||
_ => CSharpLangVersion.v7_0
|
||||
};
|
||||
// Default to the highest language version supported by the runtime
|
||||
// as we may be using a csc from a newer runtime where "latest" language
|
||||
// features depend on new APIs that aren't available on the current runtime.
|
||||
// If we were unable to determine the supported language version for the runtime,
|
||||
// its MaxSupportedLangVersion will default to "Latest" so its language features
|
||||
// are available before we add a language version mapping for that runtime version.
|
||||
return $"-langversion:{ToString (runtime.MaxSupportedLangVersion)}";
|
||||
}
|
||||
|
||||
//https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history
|
||||
public static CSharpLangVersion FromNetCoreSdkVersion (SemVersion sdkVersion)
|
||||
=> sdkVersion switch {
|
||||
{ Major: 8 } => CSharpLangVersion.v12_0,
|
||||
{ Major: 7 } => CSharpLangVersion.v11_0,
|
||||
{ Major: 6 } => CSharpLangVersion.v10_0,
|
||||
{ Major: 5 } => CSharpLangVersion.v9_0,
|
||||
{ Major: 3 } => CSharpLangVersion.v8_0,
|
||||
{ Major: 2, Minor: >= 1 } => CSharpLangVersion.v7_3,
|
||||
{ Major: 2, Minor: >= 0 } => CSharpLangVersion.v7_1,
|
||||
{ Major: 1 } => CSharpLangVersion.v7_1,
|
||||
// for unknown versions, always fall through to "Latest" so we don't break the
|
||||
// ability to use new C# versions as they are released
|
||||
_ => CSharpLangVersion.Latest
|
||||
};
|
||||
|
||||
public static string ToString (CSharpLangVersion version) => version switch {
|
||||
CSharpLangVersion.v5_0 => "5",
|
||||
CSharpLangVersion.v6_0 => "6",
|
||||
CSharpLangVersion.v7_0 => "7",
|
||||
CSharpLangVersion.v7_1 => "7.1",
|
||||
CSharpLangVersion.v7_2 => "7.2",
|
||||
CSharpLangVersion.v7_3 => "7.3",
|
||||
CSharpLangVersion.v8_0 => "8.0",
|
||||
CSharpLangVersion.v9_0 => "9.0",
|
||||
CSharpLangVersion.v10_0 => "10.0",
|
||||
CSharpLangVersion.v11_0 => "11.0",
|
||||
CSharpLangVersion.v12_0 => "12.0",
|
||||
CSharpLangVersion.Latest => "latest",
|
||||
_ => throw new ArgumentException ($"Not a valid value: '{version}'", nameof (version))
|
||||
};
|
||||
}
|
||||
|
|
|
@ -24,6 +24,11 @@
|
|||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
#if NETFRAMEWORK
|
||||
#nullable enable annotations
|
||||
#else
|
||||
#nullable enable
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -45,21 +50,19 @@ namespace Mono.TextTemplating.CodeCompilation
|
|||
|
||||
static StreamWriter CreateTempTextFile (string extension, out string path)
|
||||
{
|
||||
path = null;
|
||||
Exception ex = null;
|
||||
try {
|
||||
var tempDir = Path.GetTempPath ();
|
||||
Directory.CreateDirectory (tempDir);
|
||||
|
||||
//this is how msbuild does it...
|
||||
path = Path.Combine (tempDir, $"tmp{Guid.NewGuid ():N}{extension}");
|
||||
path = Path.Combine (tempDir, $"tmp{Guid.NewGuid ():N}{extension}")!;
|
||||
if (!File.Exists (path)) {
|
||||
return File.CreateText (path);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ex = e;
|
||||
} catch (Exception ex) {
|
||||
throw new TemplatingEngineException ("Failed to create temp file", ex);
|
||||
}
|
||||
throw new TemplatingEngineException ("Failed to create temp file", ex);
|
||||
throw new TemplatingEngineException ("Failed to create temp file");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -70,7 +73,7 @@ namespace Mono.TextTemplating.CodeCompilation
|
|||
/// <param name="token">Token.</param>
|
||||
public override async Task<CodeCompilerResult> CompileFile (CodeCompilerArguments arguments, TextWriter log, CancellationToken token)
|
||||
{
|
||||
string rspPath;
|
||||
string? rspPath;
|
||||
StreamWriter rsp;
|
||||
if (arguments.TempDirectory != null) {
|
||||
rspPath = Path.Combine (arguments.TempDirectory, "response.rsp");
|
||||
|
@ -86,11 +89,6 @@ namespace Mono.TextTemplating.CodeCompilation
|
|||
rsp.WriteLine ("-debug");
|
||||
}
|
||||
|
||||
var langVersionArg = CSharpLangVersionHelper.GetLangVersionArg (arguments, runtime);
|
||||
if (langVersionArg != null) {
|
||||
rsp.WriteLine (langVersionArg);
|
||||
}
|
||||
|
||||
foreach (var reference in AssemblyResolver.GetResolvedReferences (runtime, arguments.AssemblyReferences)) {
|
||||
rsp.Write ("-r:");
|
||||
rsp.Write ("\"");
|
||||
|
@ -107,6 +105,15 @@ namespace Mono.TextTemplating.CodeCompilation
|
|||
rsp.WriteLine (arguments.AdditionalArguments);
|
||||
}
|
||||
|
||||
// This comes after AdditionalArguments so arguments.LangVersion will take precedence
|
||||
// over any langversion arg in AdditionalArguments.
|
||||
// This behavior should match that of CSharpLangVersionHelper.GetLangVersionArg and
|
||||
// RoslynCodeCompiler.CompileFileInternal
|
||||
var langVersionArg = CSharpLangVersionHelper.GetLangVersionArg (arguments, runtime);
|
||||
if (langVersionArg != null) {
|
||||
rsp.WriteLine (langVersionArg);
|
||||
}
|
||||
|
||||
//in older versions of csc, these must come last
|
||||
foreach (var file in arguments.SourceFiles) {
|
||||
rsp.Write ("\"");
|
||||
|
@ -151,8 +158,7 @@ namespace Mono.TextTemplating.CodeCompilation
|
|||
void ConsumeOutput (string s)
|
||||
{
|
||||
using var sw = new StringReader (s);
|
||||
string line;
|
||||
while ((line = sw.ReadLine ()) != null) {
|
||||
while (sw.ReadLine () is string line) {
|
||||
outputList.Add (line);
|
||||
var err = MSBuildErrorParser.TryParseLine (line);
|
||||
if (err != null) {
|
||||
|
@ -197,7 +203,7 @@ namespace Mono.TextTemplating.CodeCompilation
|
|||
b.WriteLine ();
|
||||
}
|
||||
|
||||
public override void Write (string value)
|
||||
public override void Write (string? value)
|
||||
{
|
||||
a.Write (value);
|
||||
b.Write (value);
|
||||
|
|
Загрузка…
Ссылка в новой задаче