Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "#error version" to C# and udpate UpgradeProject to support 7.1 #18045

Merged
merged 5 commits into from
Mar 24, 2017
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/Compilers/CSharp/Portable/CSharpResources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion src/Compilers/CSharp/Portable/CSharpResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -5041,4 +5041,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ERR_VoidAssignment" xml:space="preserve">
<value>A value of type 'void' may not be assigned.</value>
</data>
</root>
<data name="ERR_CompilerAndLanguageVersion" xml:space="preserve">
<value>Compiler version: '{0}'. Language version: {1}.</value>
</data>
</root>
3 changes: 2 additions & 1 deletion src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1471,5 +1471,6 @@ internal enum ErrorCode
ERR_InvalidPreprocessingSymbol = 8301,
ERR_FeatureNotAvailableInVersion7_1 = 8302,
ERR_LanguageVersionCannotHaveLeadingZeroes = 8303,
ERR_CompilerAndLanguageVersion = 8304,
}
}
}
25 changes: 24 additions & 1 deletion src/Compilers/CSharp/Portable/Parser/DirectiveParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

namespace Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax
{
using System.Reflection;
using Microsoft.CodeAnalysis.Syntax.InternalSyntax;

internal class DirectiveParser : SyntaxParser
Expand Down Expand Up @@ -308,7 +309,29 @@ private DirectiveTriviaSyntax ParseErrorOrWarningDirective(SyntaxToken hash, Syn
//could be negative if part of the error text comes from the trailing trivia of the keyword token
int triviaOffset = eod.GetLeadingTriviaWidth() - triviaWidth;

eod = this.AddError(eod, triviaOffset, triviaWidth, isError ? ErrorCode.ERR_ErrorDirective : ErrorCode.WRN_WarningDirective, triviaBuilder.ToString());
string errorText = triviaBuilder.ToString();
eod = this.AddError(eod, triviaOffset, triviaWidth, isError ? ErrorCode.ERR_ErrorDirective : ErrorCode.WRN_WarningDirective, errorText);

if (isError)
{
if (errorText.Equals("version", StringComparison.Ordinal))
{
Assembly assembly = typeof(CSharpCompiler).GetTypeInfo().Assembly;
string version = CommonCompiler.GetAssemblyFileVersion(assembly);
eod = this.AddError(eod, triviaOffset, triviaWidth, ErrorCode.ERR_CompilerAndLanguageVersion, version,
this.Options.SpecifiedLanguageVersion.ToDisplayString());
}
else
{
const string versionMarker = "version:";
if (errorText.StartsWith(versionMarker, StringComparison.Ordinal) &&
LanguageVersionFacts.TryParse(errorText.Substring(versionMarker.Length), out var languageVersion))
{
ErrorCode error = languageVersion.GetErrorCode();
eod = this.AddError(eod, triviaOffset, triviaWidth, error, "version", new CSharpRequiredLanguageVersion(languageVersion));
}
}
}
}

if (isError)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Xunit;
Expand Down Expand Up @@ -58,7 +61,7 @@ internal struct MemberInfo

#endregion

public class PreprocessorTests
public class PreprocessorTests : TestBase
{
public PreprocessorTests()
{
Expand Down Expand Up @@ -2962,6 +2965,71 @@ private void CheckDiagnosticStringFileName(string compilationFileName, string li
Assert.Equal(expectedErrorStringFileName, actualErrorStringFileName);
}

[Fact]
public void TestErrorWithVersion()
{
var text = "#error version";
var node = Parse(text, SourceCodeKind.Regular);
TestRoundTripping(node, text, disallowErrors: false);
VerifyDirectivesSpecial(node, new DirectiveInfo
{
Kind = SyntaxKind.ErrorDirectiveTrivia,
Status = NodeStatus.IsActive,
Text = "version"
});

node.GetDiagnostics().Verify(
// (1,8): error CS1029: #error: 'version'
// #error version
Diagnostic(ErrorCode.ERR_ErrorDirective, "version").WithArguments("version").WithLocation(1, 8),
// (1,8): error CS8304: Compiler version: '42.42.42.42424 (<developer build>)'. Language version: 4.
// #error version
Diagnostic(ErrorCode.ERR_CompilerAndLanguageVersion, "version").WithArguments(GetExpectedVersion(), "4").WithLocation(1, 8)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this showing language version 4?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought you were talking about the formatting ('4' vs. 4) in your previous comment :-S

The reason is because Parse uses language version 4:

   private CSharpParseOptions GetOptions(SourceCodeKind kind, string[] defines)
        {
            return new CSharpParseOptions(languageVersion: LanguageVersion.CSharp4, kind: kind, preprocessorSymbols: defines);
        }

);
}

[Fact]
public void TestErrorWithVersionNumber()
{
var text = "#error version:7.1";
var node = Parse(text, SourceCodeKind.Regular);
TestRoundTripping(node, text, disallowErrors: false);
VerifyDirectivesSpecial(node, new DirectiveInfo
{
Kind = SyntaxKind.ErrorDirectiveTrivia,
Status = NodeStatus.IsActive,
Text = "7.1"
Copy link
Member

@cston cston Mar 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Text = "version:7.1"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There must be a bug in the test method (the test passed). I'll investigate.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, the verification method was silently dropping some expectations :-S

});

node.GetDiagnostics().Verify(
// (1,8): error CS1029: #error: 'version:7.1'
// #error version:7.1
Diagnostic(ErrorCode.ERR_ErrorDirective, "version:7.1").WithArguments("version:7.1").WithLocation(1, 8),
// (1,8): error CS8302: Feature 'version' is not available in C# 7.1. Please use language version 7.1 or greater.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this instead say that feature 'version' is not available in C# 4? Because Parse uses version 4?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense and still fulfills the need. Fixed.
Thanks for the suggestion

// #error version:7.1
Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_1, "version:7.1").WithArguments("version", "7.1").WithLocation(1, 8)
);
}

[Fact]
public void TestErrorWithInvalidVersion()
{
var text = "#error version:A.B";
var node = Parse(text, SourceCodeKind.Regular);
TestRoundTripping(node, text, disallowErrors: false);
VerifyDirectivesSpecial(node, new DirectiveInfo
{
Kind = SyntaxKind.ErrorDirectiveTrivia,
Status = NodeStatus.IsActive,
Text = "A.B"
});

node.GetDiagnostics().Verify(
// (1,8): error CS1029: #error: 'version:A.B'
// #error version:A.B
Diagnostic(ErrorCode.ERR_ErrorDirective, "version:A.B").WithArguments("version:A.B").WithLocation(1, 8)
);
}
#endregion

#region #line
Expand Down Expand Up @@ -3874,5 +3942,13 @@ public void TestLoadWithComment()
}

#endregion

private static string GetExpectedVersion()
{
Assembly assembly = typeof(CSharpCompiler).GetTypeInfo().Assembly;
string fileVersion = assembly.GetCustomAttribute<AssemblyFileVersionAttribute>().Version;
string hash = CommonCompiler.ExtractShortCommitHash(assembly.GetCustomAttribute<CommitHashAttribute>().Hash);
return $"{fileVersion} ({hash})";
}
}
}
19 changes: 8 additions & 11 deletions src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,18 +128,15 @@ public CommonCompiler(CommandLineParser parser, string responseFile, string[] ar
/// </summary>
internal virtual string GetAssemblyFileVersion()
{
if (_clientDirectory != null)
{
Assembly assembly = Type.GetTypeInfo().Assembly;
var name = $"{assembly.GetName().Name}.dll";
var filePath = Path.Combine(_clientDirectory, name);
var fileVersionInfo = FileVersionInfo.GetVersionInfo(filePath);
string hash = ExtractShortCommitHash(assembly.GetCustomAttribute<CommitHashAttribute>()?.Hash);

return $"{fileVersionInfo.FileVersion} ({hash})";
}
Assembly assembly = Type.GetTypeInfo().Assembly;
return GetAssemblyFileVersion(assembly);
}

return "";
internal static string GetAssemblyFileVersion(Assembly assembly)
{
string assemblyVersion = assembly.GetCustomAttribute<AssemblyFileVersionAttribute>()?.Version;
string hash = ExtractShortCommitHash(assembly.GetCustomAttribute<CommitHashAttribute>()?.Hash);
return $"{assemblyVersion} ({hash})";
}

internal static string ExtractShortCommitHash(string hash)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ private async Task TestLanguageVersionUpgradedAsync(
}

[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUpgradeProject)]
public async Task UpgradeProjectToDefault()
public async Task UpgradeProjectFromCSharp6ToDefault()
{
await TestLanguageVersionUpgradedAsync(
@"
Expand All @@ -55,7 +55,7 @@ void A()
}

[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUpgradeProject)]
public async Task UpgradeProjectToCSharp7()
public async Task UpgradeProjectFromCSharp6ToCSharp7()
{
await TestLanguageVersionUpgradedAsync(
@"
Expand All @@ -71,6 +71,33 @@ void A()
index: 1);
}

[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUpgradeProject)]
public async Task UpgradeProjectFromCSharp7ToLatest()
{
await TestLanguageVersionUpgradedAsync(
@"
class Program
{
#error version:[|7.1|]
}",
LanguageVersion.Latest,
new CSharpParseOptions(LanguageVersion.CSharp7));
}

[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUpgradeProject)]
public async Task UpgradeProjectFromCSharp7ToCSharp7_1()
{
await TestLanguageVersionUpgradedAsync(
@"
class Program
{
#error [|version:7.1|]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, i'm not getting what the user scenario is for this. Can you clarify? I did understand what the use of "#error version" was though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of the bug reports we're getting are the result of using older versions of the compiler. When the compiler is hosted by a framework (asp.net, azure functions, CLI, etc) it will be convenient for troubleshooting to be able to check the compiler version with a source change. It's similar to "phpversion".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get the reason for "#error version". I'm curious what the purposes of "#error version:number" is though...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I had misread your comment.
When we do a point release, adding the new version is the first thing we do (as features depend on its existence). I have documented a checklist of what needs to be done to complete that first step. That includes fixing the UpgradeProject fixer. But there is a chicken and egg problem, because no feature will trigger the diagnostic requesting this new version.
That is the purpose of #error version:<version>.
This is not the only way the problem can be solved, but it is easy enough and convenient.

}",
LanguageVersion.CSharp7_1,
new CSharpParseOptions(LanguageVersion.CSharp7),
index: 1);
}

[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUpgradeProject)]
public async Task UpgradeAllProjectsToDefault()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -69,7 +70,9 @@ protected bool TryGetDocumentAndSelectSpan(TestWorkspace workspace, out Document

protected Document GetDocumentAndAnnotatedSpan(TestWorkspace workspace, out string annotation, out TextSpan span)
{
var hostDocument = workspace.Documents.Single(d => d.AnnotatedSpans.Any());
var annotatedDocuments = workspace.Documents.Where(d => d.AnnotatedSpans.Any());
Debug.Assert(!annotatedDocuments.IsEmpty(), "No annotated span found");
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes an annoyance I ran into when working on the original PR.

var hostDocument = annotatedDocuments.Single();
var annotatedSpan = hostDocument.AnnotatedSpans.Single();
annotation = annotatedSpan.Key;
span = annotatedSpan.Value.Single();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ internal class CSharpUpgradeProjectCodeFixProvider : AbstractUpgradeProjectCodeF
private const string CS8025 = nameof(CS8025); // error CS8025: Feature is not available in C# 4. Please use language version X or greater.
private const string CS8026 = nameof(CS8026); // error CS8026: Feature is not available in C# 5. Please use language version X or greater.
private const string CS8059 = nameof(CS8059); // error CS8059: Feature is not available in C# 6. Please use language version X or greater.
private const string CS8302 = nameof(CS8302); // error CS8302: Feature is not available in C# 7.0. Please use language version X or greater.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will break when we compact the set of newly assigned error codes? Or are we committing to this particular number for the error now?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this will need to be updated when we compact error codes.
When we change the error ID, the code fixer will not trigger an the UpgradeProjectFromCSharp7ToLatest and UpgradeProjectFromCSharp7ToCSharp7_1 will fail. Then we'll update the code fixer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like CS8302 is "... not available in C# 7.1." rather than 7.0.


public override ImmutableArray<string> FixableDiagnosticIds { get; } =
ImmutableArray.Create(CS8022, CS8023, CS8024, CS8025, CS8026, CS8059);
ImmutableArray.Create(CS8022, CS8023, CS8024, CS8025, CS8026, CS8059, CS8302);

public override string UpgradeThisProjectResource => CSharpFeaturesResources.Upgrade_this_project_to_csharp_language_version_0;
public override string UpgradeAllProjectsResource => CSharpFeaturesResources.Upgrade_all_csharp_projects_to_language_version_0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ Public Class ParseTree
Return Enumerations(enumString)
End If

ReportError(referencingElement, "{0} is not a valid field type", enumString)
ReportError(referencingElement, "{0} is not a valid field type. You should add a node-kind entry in the syntax.xml.", enumString)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes an annoyance I ran into when working on the original PR.

Return Nothing
End Function

Expand Down