diff --git a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs index 1f629e6a1ef536f1f8930679b0f9b1d041cec37b..12746f8c122b8eae72d217f74cd7cca2e1694ac4 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs +++ b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs @@ -8828,6 +8828,15 @@ internal class CSharpResources { } } + /// + /// Looks up a localized string similar to binary literals. + /// + internal static string IDS_FeatureBinaryLiteral { + get { + return ResourceManager.GetString("IDS_FeatureBinaryLiteral", resourceCulture); + } + } + /// /// Looks up a localized string similar to collection initializer. /// @@ -8864,6 +8873,15 @@ internal class CSharpResources { } } + /// + /// Looks up a localized string similar to digit separators. + /// + internal static string IDS_FeatureDigitSeparator { + get { + return ResourceManager.GetString("IDS_FeatureDigitSeparator", resourceCulture); + } + } + /// /// Looks up a localized string similar to dynamic. /// diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 1ff5fa3fbda5426d6e3ea8a81cc7b79a9a2fef07..ee7faf3bf7150907afc86ce1f2df743d6410ad43 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -4599,6 +4599,12 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ await in catch blocks and finally blocks + + binary literals + + + digit separators + A '{0}' character must be escaped (by doubling) in an interpolated string. diff --git a/src/Compilers/CSharp/Portable/Errors/MessageID.cs b/src/Compilers/CSharp/Portable/Errors/MessageID.cs index 5436d5630f4f53ee257769bce75c396f8b34c10b..70800f4f7f14593e7f5d0b180247834be7606e8e 100644 --- a/src/Compilers/CSharp/Portable/Errors/MessageID.cs +++ b/src/Compilers/CSharp/Portable/Errors/MessageID.cs @@ -111,6 +111,9 @@ internal enum MessageID IDS_FeatureInterpolatedStrings = MessageBase + 12702, IDS_OperationCausedStackOverflow = MessageBase + 12703, IDS_AwaitInCatchAndFinally = MessageBase + 12704, + + IDS_FeatureBinaryLiteral = MessageBase + 12705, + IDS_FeatureDigitSeparator = MessageBase + 12706, } // Message IDs may refer to strings that need to be localized. @@ -144,10 +147,21 @@ public static LocalizableErrorArgument Localize(this MessageID id) return new LocalizableErrorArgument(id); } + // Returns the string to be used in the /features flag switch to enable the MessageID feature. + // Always call this before RequiredVersion: + // If this method returns null, call RequiredVersion and use that. + // If this method returns non-null, use that. + // Features should be mutually exclusive between RequiredFeature and RequiredVersion. + // (hence the above rule - RequiredVersion throws when RequiredFeature returns non-null) internal static string RequiredFeature(this MessageID feature) { switch (feature) { + case MessageID.IDS_FeatureBinaryLiteral: + return "binaryLiterals"; + case MessageID.IDS_FeatureDigitSeparator: + return "digitSeparators"; + default: return null; } diff --git a/src/Compilers/CSharp/Portable/Parser/CharacterInfo.cs b/src/Compilers/CSharp/Portable/Parser/CharacterInfo.cs index 12f15706d27488b20f3db29cff5cccba67b2866e..baa8d0d5c676546200a48b087126a2f81176f34d 100644 --- a/src/Compilers/CSharp/Portable/Parser/CharacterInfo.cs +++ b/src/Compilers/CSharp/Portable/Parser/CharacterInfo.cs @@ -29,7 +29,7 @@ internal static bool IsHexDigit(char c) /// true if the character is a binary digit. internal static bool IsBinaryDigit(char c) { - return c == '0' || c == '1'; + return c == '0' | c == '1'; } /// @@ -52,6 +52,16 @@ internal static int HexValue(char c) return (c >= '0' && c <= '9') ? c - '0' : (c & 0xdf) - 'A' + 10; } + /// + /// Returns the value of a binary Unicode character. + /// + /// The Unicode character. + internal static int BinaryValue(char c) + { + Debug.Assert(IsBinaryDigit(c)); + return c - '0'; + } + /// /// Returns the value of a decimal Unicode character. /// diff --git a/src/Compilers/CSharp/Portable/Parser/Lexer.cs b/src/Compilers/CSharp/Portable/Parser/Lexer.cs index 24c883bbad3fa856fed81d28576651c584970ff9..2afe3a37be9b4b6a32fa931cb639a3ba68dede37 100644 --- a/src/Compilers/CSharp/Portable/Parser/Lexer.cs +++ b/src/Compilers/CSharp/Portable/Parser/Lexer.cs @@ -921,27 +921,37 @@ private bool ScanInteger() } // Allows underscores in integers, except at beginning and end - private void ScanNumericLiteralSingleInteger(StringBuilder builder, ref bool underscoreInWrongPlace) + private void ScanNumericLiteralSingleInteger(StringBuilder builder, ref bool underscoreInWrongPlace, ref bool usedUnderscore, bool isHex, bool isBinary) { - char ch; - bool lastCharWasUnderscore = false; if (TextWindow.PeekChar() == '_') { underscoreInWrongPlace = true; } - while (((ch = TextWindow.PeekChar()) >= '0' && ch <= '9') || ch == '_') + + char ch; + var lastCharWasUnderscore = false; + while (true) { - if (ch != '_') + ch = TextWindow.PeekChar(); + if (ch == '_') { - builder.Append(ch); - lastCharWasUnderscore = false; + usedUnderscore = true; + lastCharWasUnderscore = true; + } + else if ((isHex && !SyntaxFacts.IsHexDigit(ch)) + || (isBinary && !SyntaxFacts.IsBinaryDigit(ch)) + || (!isHex && !isBinary && !SyntaxFacts.IsDecDigit(ch))) + { + break; } else { - lastCharWasUnderscore = true; + _builder.Append(ch); + lastCharWasUnderscore = false; } TextWindow.AdvanceChar(); } + if (lastCharWasUnderscore) { underscoreInWrongPlace = true; @@ -962,6 +972,7 @@ private bool ScanNumericLiteral(ref TokenInfo info) bool hasUSuffix = false; bool hasLSuffix = false; bool underscoreInWrongPlace = false; + bool usedUnderscore = false; ch = TextWindow.PeekChar(); if (ch == '0') @@ -974,6 +985,7 @@ private bool ScanNumericLiteral(ref TokenInfo info) } else if (ch == 'b' || ch == 'B') { + CheckFeatureAvailability(MessageID.IDS_FeatureBinaryLiteral); TextWindow.AdvanceChar(2); isBinary = true; } @@ -983,40 +995,7 @@ private bool ScanNumericLiteral(ref TokenInfo info) { // It's OK if it has no digits after the '0x' -- we'll catch it in ScanNumericLiteral // and give a proper error then. - - if (TextWindow.PeekChar() == '_') - { - underscoreInWrongPlace = true; - } - - var lastCharWasUnderscore = false; - while (true) - { - ch = TextWindow.PeekChar(); - if (ch == '_') - { - lastCharWasUnderscore = true; - } - else - { - if (isHex && !SyntaxFacts.IsHexDigit(ch)) - { - break; - } - if (isBinary && !SyntaxFacts.IsBinaryDigit(ch)) - { - break; - } - _builder.Append(ch); - lastCharWasUnderscore = false; - } - TextWindow.AdvanceChar(); - } - - if (lastCharWasUnderscore) - { - underscoreInWrongPlace = true; - } + ScanNumericLiteralSingleInteger(_builder, ref underscoreInWrongPlace, ref usedUnderscore, isHex, isBinary); if ((ch = TextWindow.PeekChar()) == 'L' || ch == 'l') { @@ -1046,7 +1025,7 @@ private bool ScanNumericLiteral(ref TokenInfo info) } else { - ScanNumericLiteralSingleInteger(_builder, ref underscoreInWrongPlace); + ScanNumericLiteralSingleInteger(_builder, ref underscoreInWrongPlace, ref usedUnderscore, isHex: false, isBinary: false); if (this.ModeIs(LexerMode.DebuggerSyntax) && TextWindow.PeekChar() == '#') { @@ -1067,7 +1046,7 @@ private bool ScanNumericLiteral(ref TokenInfo info) _builder.Append(ch); TextWindow.AdvanceChar(); - ScanNumericLiteralSingleInteger(_builder, ref underscoreInWrongPlace); + ScanNumericLiteralSingleInteger(_builder, ref underscoreInWrongPlace, ref usedUnderscore, isHex: false, isBinary: false); } else if (_builder.Length == 0) { @@ -1089,7 +1068,7 @@ private bool ScanNumericLiteral(ref TokenInfo info) TextWindow.AdvanceChar(); } - ScanNumericLiteralSingleInteger(_builder, ref underscoreInWrongPlace); + ScanNumericLiteralSingleInteger(_builder, ref underscoreInWrongPlace, ref usedUnderscore, isHex: false, isBinary: false); } if (hasExponent || hasDecimal) @@ -1160,6 +1139,10 @@ private bool ScanNumericLiteral(ref TokenInfo info) { this.AddError(MakeError(start, TextWindow.Position - start, ErrorCode.ERR_InvalidNumber)); } + if (usedUnderscore) + { + CheckFeatureAvailability(MessageID.IDS_FeatureDigitSeparator); + } info.Kind = SyntaxKind.NumericLiteralToken; info.Text = TextWindow.GetText(true); @@ -1285,18 +1268,16 @@ private bool ScanNumericLiteral(ref TokenInfo info) private bool TryParseBinaryUInt64(string text, out ulong value) { value = 0; - for (int i = 0; i < text.Length; i++) + foreach (char c in text) { // if uppermost bit is set, then the next bitshift will overflow if ((value & 0x8000000000000000) != 0) { return false; } - if (!SyntaxFacts.IsBinaryDigit(text[i])) - { - return false; - } - var bit = (ulong)SyntaxFacts.DecValue(text[i]); + // We shouldn't ever get a string that's nonbinary (see ScanNumericLiteral), + // so don't explicitly check for it (there's a debug assert in SyntaxFacts) + var bit = (ulong)SyntaxFacts.BinaryValue(c); value = (value << 1) | bit; } return true; diff --git a/src/Compilers/CSharp/Portable/Parser/SyntaxParser.cs b/src/Compilers/CSharp/Portable/Parser/SyntaxParser.cs index b9464cd55948e2174e26b245a794bb8e9bcd7543..415975431b81305bfee302446d4fe683ec746ae3 100644 --- a/src/Compilers/CSharp/Portable/Parser/SyntaxParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/SyntaxParser.cs @@ -1046,7 +1046,6 @@ protected TNode CheckFeatureAvailability(TNode node, MessageID feature, b } var featureName = feature.Localize(); - var requiredVersion = feature.RequiredVersion(); if (feature.RequiredFeature() != null) { @@ -1060,6 +1059,8 @@ protected TNode CheckFeatureAvailability(TNode node, MessageID feature, b } else { + var requiredVersion = feature.RequiredVersion(); + if (forceWarning) { SyntaxDiagnosticInfo rawInfo = new SyntaxDiagnosticInfo(availableVersion.GetErrorCode(), featureName, requiredVersion.Localize()); diff --git a/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/LexicalTests.cs b/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/LexicalTests.cs index e5449c6a07ce9ab00259e5fbdb3a1fff632bb2bc..bae66dfb36382942aa8b48afc5c120f08bb80c8a 100644 --- a/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/LexicalTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/LexicalTests.cs @@ -17,20 +17,48 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests public class LexicalTests { private readonly CSharpParseOptions _options; + private readonly CSharpParseOptions _binaryOptions; + private readonly CSharpParseOptions _underscoreOptions; + private readonly CSharpParseOptions _binaryUnderscoreOptions; public LexicalTests() { _options = new CSharpParseOptions(languageVersion: LanguageVersion.CSharp3); + var binaryLiterals = new[] { new KeyValuePair("binaryLiterals", "true") }; + var digitSeparators = new[] { new KeyValuePair("digitSeparators", "true") }; + _binaryOptions = _options.WithFeatures(binaryLiterals); + _underscoreOptions = _options.WithFeatures(digitSeparators); + _binaryUnderscoreOptions = _options.WithFeatures(binaryLiterals.Concat(digitSeparators)); } - private SyntaxToken Lex(string text) + private IEnumerable Lex(string text, CSharpParseOptions options = null) { - return SyntaxFactory.ParseToken(text); + return SyntaxFactory.ParseTokens(text, options: options); } - private SyntaxToken LexToken(string text) + private SyntaxToken LexToken(string text, CSharpParseOptions options = null) { - return Lex(text); + SyntaxToken result = default(SyntaxToken); + foreach (var token in Lex(text, options)) + { + if (result.Kind() == SyntaxKind.None) + { + result = token; + } + else if (token.Kind() == SyntaxKind.EndOfFileToken) + { + continue; + } + else + { + Assert.True(false, "More than one token was lexed: " + token); + } + } + if (result.Kind() == SyntaxKind.None) + { + Assert.True(false, "No tokens were lexed"); + } + return result; } private SyntaxToken DebuggerLex(string text) @@ -671,7 +699,7 @@ public void TestMultiLineVerbatimStringLiteral() public void TestStringLiteralWithNewLine() { var text = "\"literal\r\nwith new line\""; - var token = LexToken(text); + var token = Lex(text).First(); Assert.NotNull(token); Assert.Equal(SyntaxKind.StringLiteralToken, token.Kind()); @@ -1075,7 +1103,7 @@ public void TestCharacterLiteralWithNewline() { var value = "a"; var text = "'a\r'"; - var token = LexToken(text); + var token = Lex(text).First(); Assert.NotNull(token); Assert.Equal(SyntaxKind.CharacterLiteralToken, token.Kind()); @@ -1091,7 +1119,7 @@ public void TestCharacterLiteralWithNewline() public void TestCharacterLiteralThatsTooSmallWithNewline() { var text = "'\r'"; - var token = LexToken(text); + var token = Lex(text).First(); Assert.NotNull(token); Assert.Equal(SyntaxKind.CharacterLiteralToken, token.Kind()); @@ -1109,7 +1137,7 @@ public void TestCharacterLiteralThatsTooBigWithNewline() { var value = "a"; var text = "'ab\r'"; - var token = LexToken(text); + var token = Lex(text).First(); Assert.NotNull(token); Assert.Equal(SyntaxKind.CharacterLiteralToken, token.Kind()); @@ -1914,13 +1942,27 @@ public void TestNumericHexLiteralWithUpperUnsignedAndUpperLongSpecifier() Assert.Equal(value, token.Value); } + [Fact] + [Trait("Feature", "Literals")] + public void TestNumericBinaryLiteralWithoutFeatureFlag() + { + var text = "0b1"; + var token = LexToken(text); + + Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); + var errors = token.Errors(); + Assert.Equal(1, errors.Length); + Assert.Equal((int)ErrorCode.ERR_FeatureIsExperimental, errors[0].Code); + Assert.Equal(text, token.Text); + } + [Fact] [Trait("Feature", "Literals")] public void TestNumericBinaryLiteralWithUnsignedAndLongSpecifier() { var value = 0x123ul; var text = "0b100100011ul"; - var token = LexToken(text); + var token = LexToken(text, _binaryOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -1930,7 +1972,7 @@ public void TestNumericBinaryLiteralWithUnsignedAndLongSpecifier() Assert.Equal(value, token.Value); text = "0b100100011lu"; - token = LexToken(text); + token = LexToken(text, _binaryOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -1946,7 +1988,7 @@ public void TestNumericBinaryLiteralWithUpperUnsignedAndLongSpecifier() { var value = 0x123Ul; var text = "0b100100011Ul"; - var token = LexToken(text); + var token = LexToken(text, _binaryOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -1956,7 +1998,7 @@ public void TestNumericBinaryLiteralWithUpperUnsignedAndLongSpecifier() Assert.Equal(value, token.Value); text = "0b100100011lU"; - token = LexToken(text); + token = LexToken(text, _binaryOptions); errors = token.Errors(); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -1971,7 +2013,7 @@ public void TestNumericBinaryLiteralWithUnsignedAndUpperLongSpecifier() { var value = 0x123uL; var text = "0b100100011uL"; - var token = LexToken(text); + var token = LexToken(text, _binaryOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -1981,7 +2023,7 @@ public void TestNumericBinaryLiteralWithUnsignedAndUpperLongSpecifier() Assert.Equal(value, token.Value); text = "0b100100011Lu"; - token = LexToken(text); + token = LexToken(text, _binaryOptions); errors = token.Errors(); Assert.NotNull(token); @@ -1997,7 +2039,7 @@ public void TestNumericBinaryLiteralWithUpperUnsignedAndUpperLongSpecifier() { var value = 0x123UL; var text = "0b100100011UL"; - var token = LexToken(text); + var token = LexToken(text, _binaryOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -2007,7 +2049,7 @@ public void TestNumericBinaryLiteralWithUpperUnsignedAndUpperLongSpecifier() Assert.Equal(value, token.Value); text = "0b100100011LU"; - token = LexToken(text); + token = LexToken(text, _binaryOptions); errors = token.Errors(); Assert.NotNull(token); @@ -2320,7 +2362,7 @@ public void TestNumericBinaryLiteral() { var value = 0x123; var text = "0b100100011"; - var token = LexToken(text); + var token = LexToken(text, _binaryOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -2336,7 +2378,7 @@ public void TestNumericBinaryLiteralWithUnsignedSpecifier() { var value = 0x123u; var text = "0b100100011u"; - var token = LexToken(text); + var token = LexToken(text, _binaryOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -2352,7 +2394,7 @@ public void TestNumericBinaryLiteralWithLongSpecifier() { var value = 0x123L; var text = "0b100100011L"; - var token = LexToken(text); + var token = LexToken(text, _binaryOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -2368,7 +2410,7 @@ public void TestNumeric8DigitBinaryLiteral() { var value = 0xAA; var text = "0b10101010"; - var token = LexToken(text); + var token = LexToken(text, _binaryOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -2384,7 +2426,7 @@ public void TestNumeric8DigitMaxBinaryLiteral() { var value = 0x7FFFFFFF; var text = "0b1111111111111111111111111111111"; - var token = LexToken(text); + var token = LexToken(text, _binaryOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -2400,7 +2442,7 @@ public void TestNumeric8DigitMaxUnsignedBinaryLiteral() { var value = 0xFFFFFFFF; var text = "0b11111111111111111111111111111111"; - var token = LexToken(text); + var token = LexToken(text, _binaryOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -2416,7 +2458,7 @@ public void TestNumeric8DigitMaxUnsignedBinaryLiteralWithUnsignedSpecifier() { var value = 0xFFFFFFFFu; var text = "0b11111111111111111111111111111111u"; - var token = LexToken(text); + var token = LexToken(text, _binaryOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -2432,7 +2474,7 @@ public void TestNumeric16DigitMaxBinaryLiteral() { var value = 0x7FFFFFFFFFFFFFFF; var text = "0b111111111111111111111111111111111111111111111111111111111111111"; - var token = LexToken(text); + var token = LexToken(text, _binaryOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -2448,7 +2490,7 @@ public void TestNumeric16DigitMaxUnsignedBinaryLiteral() { var value = 0xFFFFFFFFFFFFFFFF; var text = "0b1111111111111111111111111111111111111111111111111111111111111111"; - var token = LexToken(text); + var token = LexToken(text, _binaryOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -2463,7 +2505,7 @@ public void TestNumeric16DigitMaxUnsignedBinaryLiteral() public void TestNumericOverflowBinaryLiteral() { var text = "0b10000000000000000000000000000000000000000000000000000000000000000"; - var token = LexToken(text); + var token = LexToken(text, _binaryOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -2479,7 +2521,7 @@ public void TestNumericOverflowBinaryLiteral() public void TestNumericEmptyBinaryLiteral() { var text = "0b"; - var token = LexToken(text); + var token = LexToken(text, _binaryOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -2495,7 +2537,7 @@ public void TestNumericEmptyBinaryLiteral() public void TestNumericWithUnderscores() { var text = "1_000"; - var token = LexToken(text); + var token = LexToken(text, _underscoreOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -2504,7 +2546,7 @@ public void TestNumericWithUnderscores() Assert.Equal(text, token.Text); text = "1___0_0___0"; - token = LexToken(text); + token = LexToken(text, _underscoreOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -2513,7 +2555,7 @@ public void TestNumericWithUnderscores() Assert.Equal(text, token.Text); text = "1_000.000_1"; - token = LexToken(text); + token = LexToken(text, _underscoreOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -2522,7 +2564,7 @@ public void TestNumericWithUnderscores() Assert.Equal(text, token.Text); text = "1_01__0.0__10_1f"; - token = LexToken(text); + token = LexToken(text, _underscoreOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -2531,7 +2573,7 @@ public void TestNumericWithUnderscores() Assert.Equal(text, token.Text); text = "1_01__0.0__10_1e0_1"; - token = LexToken(text); + token = LexToken(text, _underscoreOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -2540,7 +2582,7 @@ public void TestNumericWithUnderscores() Assert.Equal(text, token.Text); text = "0xA_A"; - token = LexToken(text); + token = LexToken(text, _underscoreOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -2549,7 +2591,7 @@ public void TestNumericWithUnderscores() Assert.Equal(text, token.Text); text = "0b1_1"; - token = LexToken(text); + token = LexToken(text, _binaryUnderscoreOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -2558,19 +2600,33 @@ public void TestNumericWithUnderscores() Assert.Equal(text, token.Text); } + [Fact] + [Trait("Feature", "Literals")] + public void TestNumericWithUnderscoresWithoutFeatureFlag() + { + var text = "1_000"; + var token = LexToken(text); + + Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); + var errors = token.Errors(); + Assert.Equal(1, errors.Length); + Assert.Equal((int)ErrorCode.ERR_FeatureIsExperimental, errors[0].Code); + Assert.Equal(text, token.Text); + } + [Fact] [Trait("Feature", "Literals")] public void TestNumericWithBadUnderscores() { var text = "_1000"; - var token = LexToken(text); + var token = LexToken(text, _underscoreOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.IdentifierToken, token.Kind()); Assert.Equal(0, token.Errors().Length); text = "1000_"; - token = LexToken(text); + token = LexToken(text, _underscoreOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -2581,7 +2637,7 @@ public void TestNumericWithBadUnderscores() Assert.Equal(text, token.Text); text = "1000_.0"; - token = LexToken(text); + token = LexToken(text, _underscoreOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -2596,7 +2652,7 @@ public void TestNumericWithBadUnderscores() // text = "1000._0"; text = "1000.0_"; - token = LexToken(text); + token = LexToken(text, _underscoreOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -2607,7 +2663,7 @@ public void TestNumericWithBadUnderscores() Assert.Equal(text, token.Text); text = "1000.0_e1"; - token = LexToken(text); + token = LexToken(text, _underscoreOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -2618,7 +2674,7 @@ public void TestNumericWithBadUnderscores() Assert.Equal(text, token.Text); text = "1000.0e_1"; - token = LexToken(text); + token = LexToken(text, _underscoreOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -2629,7 +2685,7 @@ public void TestNumericWithBadUnderscores() Assert.Equal(text, token.Text); text = "1000.0e1_"; - token = LexToken(text); + token = LexToken(text, _underscoreOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -2640,7 +2696,7 @@ public void TestNumericWithBadUnderscores() Assert.Equal(text, token.Text); text = "0x_A"; - token = LexToken(text); + token = LexToken(text, _underscoreOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -2651,7 +2707,7 @@ public void TestNumericWithBadUnderscores() Assert.Equal(text, token.Text); text = "0xA_"; - token = LexToken(text); + token = LexToken(text, _underscoreOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -2662,7 +2718,7 @@ public void TestNumericWithBadUnderscores() Assert.Equal(text, token.Text); text = "0b_1"; - token = LexToken(text); + token = LexToken(text, _binaryUnderscoreOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -2673,7 +2729,7 @@ public void TestNumericWithBadUnderscores() Assert.Equal(text, token.Text); text = "0b1_"; - token = LexToken(text); + token = LexToken(text, _binaryUnderscoreOptions); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -2690,7 +2746,7 @@ public void TestNumericWithTrailingDot() { var value = 3; var text = "3."; - var token = LexToken(text); + var token = Lex(text).First(); Assert.NotNull(token); Assert.Equal(SyntaxKind.NumericLiteralToken, token.Kind()); @@ -2792,7 +2848,7 @@ public void TestDebuggerDollarIdentifiers() [Fact] public void TestDebuggerObjectAddressIdentifiers() { - var token = LexToken("@0x0"); + var token = Lex("@0x0").First(); Assert.Equal(SyntaxKind.BadToken, token.Kind()); VerifyError(token, ErrorCode.ERR_ExpectedVerbatimLiteral); Assert.Equal("@", token.Text); @@ -2828,7 +2884,7 @@ public void TestDebuggerObjectAddressIdentifiers() Assert.Equal("@", token.Text); Assert.Equal("@", token.Value); - token = LexToken("@0b1c2d3e4f"); + token = Lex("@0b1c2d3e4f").First(); Assert.Equal(SyntaxKind.BadToken, token.Kind()); VerifyError(token, ErrorCode.ERR_ExpectedVerbatimLiteral); Assert.Equal("@", token.Text); diff --git a/src/Compilers/VisualBasic/Portable/Errors/Errors.vb b/src/Compilers/VisualBasic/Portable/Errors/Errors.vb index 81bc34cabc9298a81306208627f46434a7dd3228..0b3650b2066fe3c4c794ac3f57a509f316ffd637 100644 --- a/src/Compilers/VisualBasic/Portable/Errors/Errors.vb +++ b/src/Compilers/VisualBasic/Portable/Errors/Errors.vb @@ -1935,5 +1935,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic FEATURE_GlobalNamespace FEATURE_NullPropagatingOperator FEATURE_NameOfExpressions + FEATURE_DigitSeparators + FEATURE_BinaryLiterals End Enum End Namespace diff --git a/src/Compilers/VisualBasic/Portable/Parser/Parser.vb b/src/Compilers/VisualBasic/Portable/Parser/Parser.vb index 9ad17747cc222abe4dbcad22716f4fd86f7afc6c..d0c55475679df3d25c5d6a919446179a0ebbba98 100644 --- a/src/Compilers/VisualBasic/Portable/Parser/Parser.vb +++ b/src/Compilers/VisualBasic/Portable/Parser/Parser.vb @@ -5998,37 +5998,20 @@ checkNullable: ''' of the parser. If it is not available a diagnostic will be added to the returned value. ''' Private Function CheckFeatureAvailability(Of TNode As VisualBasicSyntaxNode)(feature As Feature, node As TNode) As TNode - Dim languageVersion = _scanner.Options.LanguageVersion - If CheckFeatureAvailability(languageVersion, feature) Then + If _scanner.CheckFeatureAvailability(feature) Then Return node End If If feature = Feature.InterpolatedStrings Then ' Bug: It is too late in the release cycle to update localized strings. As a short term measure we will output ' an unlocalized string and fix this to be localized in the next release. - Return ReportSyntaxError(node, ERRID.ERR_LanguageVersion, languageVersion.GetErrorName(), "interpolated strings") + Return ReportSyntaxError(node, ERRID.ERR_LanguageVersion, _scanner.Options.LanguageVersion.GetErrorName(), "interpolated strings") Else Dim featureName = ErrorFactory.ErrorInfo(feature.GetResourceId()) - Return ReportSyntaxError(node, ERRID.ERR_LanguageVersion, languageVersion.GetErrorName(), featureName) + Return ReportSyntaxError(node, ERRID.ERR_LanguageVersion, _scanner.Options.LanguageVersion.GetErrorName(), featureName) End If End Function - Private Function CheckFeatureAvailability(feature As Feature) As Boolean - Return CheckFeatureAvailability(_scanner.Options.LanguageVersion, feature) - End Function - - Private Shared Function CheckFeatureAvailability(languageVersion As LanguageVersion, feature As Feature) As Boolean - Dim required = feature.GetLanguageVersion() - Return CInt(required) <= CInt(languageVersion) - End Function - - Friend Shared Sub CheckFeatureAvailability(diagnostics As DiagnosticBag, location As Location, languageVersion As LanguageVersion, feature As Feature) - If Not CheckFeatureAvailability(languageVersion, feature) Then - Dim featureName = ErrorFactory.ErrorInfo(feature.GetResourceId()) - diagnostics.Add(ERRID.ERR_LanguageVersion, location, languageVersion.GetErrorName(), featureName) - End If - End Sub - End Class 'TODO - These should be removed. Checks should be in binding. diff --git a/src/Compilers/VisualBasic/Portable/Parser/ParserFeature.vb b/src/Compilers/VisualBasic/Portable/Parser/ParserFeature.vb index ef3bbfa286d4ed202f19c76732e78e527a34a246..f64db4c1f915310218e06b56346988d754ce27ef 100644 --- a/src/Compilers/VisualBasic/Portable/Parser/ParserFeature.vb +++ b/src/Compilers/VisualBasic/Portable/Parser/ParserFeature.vb @@ -18,12 +18,19 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax NullPropagatingOperator NameOfExpressions InterpolatedStrings + DigitSeparators + BinaryLiterals End Enum Friend Module FeatureExtensions Friend Function GetFeatureFlag(feature As Feature) As String Select Case feature + Case Feature.DigitSeparators + Return "digitSeparators" + + Case Feature.BinaryLiterals + Return "binaryLiterals" Case Else Return Nothing @@ -86,6 +93,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax Return ERRID.FEATURE_NullPropagatingOperator Case Feature.NameOfExpressions Return ERRID.FEATURE_NameOfExpressions + Case Feature.DigitSeparators + Return ERRID.FEATURE_DigitSeparators + Case Feature.BinaryLiterals + Return ERRID.FEATURE_BinaryLiterals Case Else Throw ExceptionUtilities.UnexpectedValue(feature) End Select diff --git a/src/Compilers/VisualBasic/Portable/Scanner/Scanner.vb b/src/Compilers/VisualBasic/Portable/Scanner/Scanner.vb index 9aeb3d4e1dea0844b924a10d2d35a8d918117dff..7bd3be1239302fb578eee1955bdabc31117a73a4 100644 --- a/src/Compilers/VisualBasic/Portable/Scanner/Scanner.vb +++ b/src/Compilers/VisualBasic/Portable/Scanner/Scanner.vb @@ -1555,6 +1555,7 @@ FullWidthRepeat: Dim Here As Integer = 0 Dim IntegerLiteralStart As Integer Dim UnderscoreInWrongPlace As Boolean + Dim UnderscoreUsed As Boolean = False Dim Base As LiteralBase = LiteralBase.Decimal Dim literalKind As NumericLiteralKind = NumericLiteralKind.Integral @@ -1577,12 +1578,15 @@ FullWidthRepeat: IntegerLiteralStart = Here Base = LiteralBase.Hexadecimal - UnderscoreInWrongPlace = Peek(Here) = "_"c + UnderscoreInWrongPlace = (CanGet(Here) AndAlso Peek(Here) = "_"c) While CanGet(Here) ch = Peek(Here) If Not IsHexDigit(ch) AndAlso ch <> "_"c Then Exit While End If + If ch = "_"c Then + UnderscoreUsed = True + End If Here += 1 End While UnderscoreInWrongPlace = UnderscoreInWrongPlace Or (Peek(Here - 1) = "_"c) @@ -1592,12 +1596,15 @@ FullWidthRepeat: IntegerLiteralStart = Here Base = LiteralBase.Binary - UnderscoreInWrongPlace = Peek(Here) = "_"c + UnderscoreInWrongPlace = (CanGet(Here) AndAlso Peek(Here) = "_"c) While CanGet(Here) ch = Peek(Here) If Not IsBinaryDigit(ch) AndAlso ch <> "_"c Then Exit While End If + If ch = "_"c Then + UnderscoreUsed = True + End If Here += 1 End While UnderscoreInWrongPlace = UnderscoreInWrongPlace Or (Peek(Here - 1) = "_"c) @@ -1607,12 +1614,15 @@ FullWidthRepeat: IntegerLiteralStart = Here Base = LiteralBase.Octal - UnderscoreInWrongPlace = Peek(Here) = "_"c + UnderscoreInWrongPlace = (CanGet(Here) AndAlso Peek(Here) = "_"c) While CanGet(Here) ch = Peek(Here) If Not IsOctalDigit(ch) AndAlso ch <> "_"c Then Exit While End If + If ch = "_"c Then + UnderscoreUsed = True + End If Here += 1 End While UnderscoreInWrongPlace = UnderscoreInWrongPlace Or (Peek(Here - 1) = "_"c) @@ -1628,12 +1638,15 @@ FullWidthRepeat: Else ' no base specifier - just go through decimal digits. IntegerLiteralStart = Here - UnderscoreInWrongPlace = Peek(Here) = "_"c + UnderscoreInWrongPlace = (CanGet(Here) AndAlso Peek(Here) = "_"c) While CanGet(Here) ch = Peek(Here) If Not IsDecimalDigit(ch) AndAlso ch <> "_"c Then Exit While End If + If ch = "_"c Then + UnderscoreUsed = True + End If Here += 1 End While If Here <> IntegerLiteralStart Then @@ -1959,6 +1972,13 @@ FullWidthRepeat2: result = DirectCast(result.AddError(ErrorFactory.ErrorInfo(ERRID.ERR_Syntax)), SyntaxToken) End If + If UnderscoreUsed Then + result = CheckFeatureAvailability(result, Feature.DigitSeparators) + End If + If Base = LiteralBase.Binary Then + result = CheckFeatureAvailability(result, Feature.BinaryLiterals) + End If + Return result End Function @@ -2489,5 +2509,28 @@ baddate: Private Function IsIdentifierStartCharacter(c As Char) As Boolean Return (_isScanningForExpressionCompiler AndAlso c = "$"c) OrElse SyntaxFacts.IsIdentifierStartCharacter(c) End Function + + Private Function CheckFeatureAvailability(token As SyntaxToken, feature As Feature) As SyntaxToken + If CheckFeatureAvailability(feature) Then + Return token + End If + Dim errorInfo = ErrorFactory.ErrorInfo(ERRID.ERR_LanguageVersion, _options.LanguageVersion.GetErrorName(), ErrorFactory.ErrorInfo(feature.GetResourceId())) + Return DirectCast(token.AddError(errorInfo), SyntaxToken) + End Function + + Friend Function CheckFeatureAvailability(feature As Feature) As Boolean + Return CheckFeatureAvailability(Me.Options, feature) + End Function + + Private Shared Function CheckFeatureAvailability(parseOptions As VisualBasicParseOptions, feature As Feature) As Boolean + Dim featureFlag = feature.GetFeatureFlag() + If featureFlag IsNot Nothing Then + Return parseOptions.Features.ContainsKey(featureFlag) + End If + + Dim required = feature.GetLanguageVersion() + Dim actual = parseOptions.LanguageVersion + Return CInt(required) <= CInt(actual) + End Function End Class End Namespace diff --git a/src/Compilers/VisualBasic/Portable/VBResources.Designer.vb b/src/Compilers/VisualBasic/Portable/VBResources.Designer.vb index 033bd6e6dc094a44816b24cd8007ac8a3fd8fa14..60c831b2f9cc4e84f824aa40f232bae380b88249 100644 --- a/src/Compilers/VisualBasic/Portable/VBResources.Designer.vb +++ b/src/Compilers/VisualBasic/Portable/VBResources.Designer.vb @@ -11604,6 +11604,15 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End Get End Property + ''' + ''' Looks up a localized string similar to binary literals. + ''' + Friend ReadOnly Property FEATURE_BinaryLiterals() As String + Get + Return ResourceManager.GetString("FEATURE_BinaryLiterals", resourceCulture) + End Get + End Property + ''' ''' Looks up a localized string similar to variance. ''' @@ -11622,6 +11631,15 @@ Namespace Microsoft.CodeAnalysis.VisualBasic End Get End Property + ''' + ''' Looks up a localized string similar to digit separators. + ''' + Friend ReadOnly Property FEATURE_DigitSeparators() As String + Get + Return ResourceManager.GetString("FEATURE_DigitSeparators", resourceCulture) + End Get + End Property + ''' ''' Looks up a localized string similar to declaring a Global namespace. ''' diff --git a/src/Compilers/VisualBasic/Portable/VBResources.resx b/src/Compilers/VisualBasic/Portable/VBResources.resx index dd88570df414b2e214800cf21e5f84a4340559db..8d3f9ed617e9a19be96dd0d9252b3fcca1d0402b 100644 --- a/src/Compilers/VisualBasic/Portable/VBResources.resx +++ b/src/Compilers/VisualBasic/Portable/VBResources.resx @@ -5295,4 +5295,10 @@ 'nameof' expressions + + digit separators + + + binary literals + \ No newline at end of file diff --git a/src/Compilers/VisualBasic/Test/Syntax/Parser/ParseExpression.vb b/src/Compilers/VisualBasic/Test/Syntax/Parser/ParseExpression.vb index c511ca89ae944a9b11e5bb29c68a51ccba62a457..f6f23deb7682a29747e476f144414848a9db5939 100644 --- a/src/Compilers/VisualBasic/Test/Syntax/Parser/ParseExpression.vb +++ b/src/Compilers/VisualBasic/Test/Syntax/Parser/ParseExpression.vb @@ -99,7 +99,6 @@ Public Class ParseExpressionTest Public Sub ParseIntegerLiteralTest() ParseExpression("&H1") ParseExpression("&O1") - ParseExpression("&B1") End Sub diff --git a/src/Compilers/VisualBasic/Test/Syntax/Scanner/ScannerTests.vb b/src/Compilers/VisualBasic/Test/Syntax/Scanner/ScannerTests.vb index 180132f336ca485d0fd024753e416322a188ebd4..9af2b860ff9dfd298e3a10e9d3e05bc3d1e42a8c 100644 --- a/src/Compilers/VisualBasic/Test/Syntax/Scanner/ScannerTests.vb +++ b/src/Compilers/VisualBasic/Test/Syntax/Scanner/ScannerTests.vb @@ -700,6 +700,7 @@ End If]]>.Value, Assert.Equal(LiteralBase.Decimal, tk.GetBase()) Assert.Equal(42, tk.Value) Assert.Equal(" 4_2 ", tk.ToFullString()) + Assert.Equal("error BC36716: Visual Basic 14.0 does not support digit separators.", tk.Errors().Single().ToString()) Str = " &H42L " tk = ScanOnce(Str) @@ -728,6 +729,7 @@ End If]]>.Value, Assert.Equal(LiteralBase.Binary, tk.GetBase()) Assert.Equal(&HAL, tk.Value) Assert.Equal(" &B1010L ", tk.ToFullString()) + Assert.Equal("error BC36716: Visual Basic 14.0 does not support binary literals.", tk.Errors().Single().ToString()) Str = " &B1_0_1_0L " tk = ScanOnce(Str) @@ -735,6 +737,9 @@ End If]]>.Value, Assert.Equal(LiteralBase.Binary, tk.GetBase()) Assert.Equal(&HAL, tk.Value) Assert.Equal(" &B1_0_1_0L ", tk.ToFullString()) + Assert.Equal(2, tk.Errors().Count) + Assert.Equal("error BC36716: Visual Basic 14.0 does not support digit separators.", tk.Errors()(0).ToString()) + Assert.Equal("error BC36716: Visual Basic 14.0 does not support binary literals.", tk.Errors()(1).ToString()) End Sub