Skip to content

Commit

Permalink
Merge pull request #167 from mono/better-langversion
Browse files Browse the repository at this point in the history
Better defaults and handling for LangVersion
  • Loading branch information
mhutch committed Oct 12, 2023
2 parents 2711105 + 51a5db5 commit d5fb8e6
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 112 deletions.
4 changes: 3 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
}
],
"cSpell.words": [
"langversion",
"Preprocess",
"reimplementation"
"reimplementation",
"Xunit"
]
}
14 changes: 1 addition & 13 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,13 @@

<ItemGroup>
<PackageReference Include="Nerdbank.GitVersioning" Version="3.6.133" PrivateAssets="all" />
<PackageReference Include="DotNet.ReproducibleBuilds" Version="1.1.1" PrivateAssets="All"/>
</ItemGroup>

<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)LICENSE" Pack="true" PackagePath="LICENSE.txt" Visible="False" />
</ItemGroup>

<!-- reproducible build -->
<PropertyGroup>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<DebugType>embedded</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All"/>
</ItemGroup>

<PropertyGroup>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)TextTemplating.snk</AssemblyOriginatorKeyFile>
Expand Down
27 changes: 23 additions & 4 deletions Mono.TextTemplating.Roslyn/RoslynCodeCompiler.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
Expand Down Expand Up @@ -37,11 +38,13 @@ CodeCompilerResult CompileFileInternal (
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> ();
Expand All @@ -51,15 +54,31 @@ CodeCompilerResult CompileFileInternal (

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> ();
Expand Down Expand Up @@ -102,7 +121,7 @@ CodeCompilerResult CompileFileInternal (
var startLinePosition = location.StartLinePosition;
var endLinePosition = location.EndLinePosition;
return new CodeCompilerError {
Message = x.GetMessage (),
Message = x.GetMessage (CultureInfo.CurrentCulture),
Column = startLinePosition.Character,
Line = startLinePosition.Line,
EndLine = endLinePosition.Line,
Expand Down
21 changes: 20 additions & 1 deletion Mono.TextTemplating.Tests/ProcessingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,26 @@ public async Task CSharp9Records ()
#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 ()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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
{
enum CSharpLangVersion
{
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);
namespace Mono.TextTemplating.CodeCompilation;

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}";
}
static class CSharpLangVersionHelper
{
public static bool HasLangVersionArg (string args) =>
!string.IsNullOrEmpty(args)
&& (args.IndexOf ("langversion", StringComparison.OrdinalIgnoreCase) > -1)
&& ProcessArgumentBuilder.TryParse (args, out var parsedArgs)
&& parsedArgs.Any (IsLangVersionArg);

if (HasLangVersionArg (arguments.AdditionalArguments)) {
return null;
}
public static bool IsLangVersionArg (string arg) =>
(arg[0] == '-' || arg[0] == '/')
&& arg.IndexOf ("langversion", StringComparison.OrdinalIgnoreCase) == 1;

return $"-langversion:{ToString(GetBestSupportedLangVersion(runtime))}";
public static string? GetLangVersionArg (CodeCompilerArguments arguments, RuntimeInfo 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))
};
}
Loading

0 comments on commit d5fb8e6

Please sign in to comment.