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