Skip to content

Commit 1efabcd

Browse files
authored
Support using local declarations in a Simple Program. (#42385)
Related to #41704.
1 parent 7848fa7 commit 1efabcd

File tree

4 files changed

+794
-23
lines changed

4 files changed

+794
-23
lines changed

src/Compilers/CSharp/Portable/Parser/LanguageParser.cs

Lines changed: 60 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -449,11 +449,10 @@ private void ParseNamespaceBody(ref SyntaxToken openBrace, ref NamespaceBodyBuil
449449
}
450450

451451
case SyntaxKind.UsingKeyword:
452-
if (isGlobal && this.PeekToken(1).Kind == SyntaxKind.OpenParenToken)
452+
if (isGlobal && (this.PeekToken(1).Kind == SyntaxKind.OpenParenToken || (!IsScript && IsPossibleTopLevelUsingLocalDeclarationStatement())))
453453
{
454-
// incomplete members must be processed before we add any nodes to the body:
455-
AddIncompleteMembers(ref pendingIncompleteMembers, ref body);
456-
body.Members.Add(adjustStateAndReportStatementOutOfOrder(ref seen, ParseTopLevelUsingStatement()));
454+
// Top-level using statement or using local declaration
455+
goto default;
457456
}
458457
else
459458
{
@@ -591,20 +590,6 @@ MemberDeclarationSyntax adjustStateAndReportStatementOutOfOrder(ref NamespacePar
591590
}
592591
}
593592

594-
[MethodImpl(MethodImplOptions.NoInlining)]
595-
private GlobalStatementSyntax ParseTopLevelUsingStatement()
596-
{
597-
bool wasInAsync = IsInAsync;
598-
if (!IsScript)
599-
{
600-
IsInAsync = true; // We are implicitly in an async context
601-
}
602-
603-
var topLevelUsingStatement = CheckSimpleProgramsFeatureAvailability(_syntaxFactory.GlobalStatement(ParseUsingStatement(attributes: default)));
604-
IsInAsync = wasInAsync;
605-
return topLevelUsingStatement;
606-
}
607-
608593
private GlobalStatementSyntax CheckSimpleProgramsFeatureAvailability(GlobalStatementSyntax globalStatementSyntax)
609594
{
610595
if (IsScript || _checkedSimpleProgramsFeatureAvailability)
@@ -2374,7 +2359,6 @@ static bool isAcceptableNonDeclarationStatement(StatementSyntax statement, bool
23742359
switch (statement?.Kind)
23752360
{
23762361
case null:
2377-
case SyntaxKind.LocalDeclarationStatement:
23782362
case SyntaxKind.LocalFunctionStatement:
23792363
case SyntaxKind.ExpressionStatement when
23802364
!isScript &&
@@ -2386,6 +2370,9 @@ static bool isAcceptableNonDeclarationStatement(StatementSyntax statement, bool
23862370

23872371
return false;
23882372

2373+
case SyntaxKind.LocalDeclarationStatement:
2374+
return !isScript && ((LocalDeclarationStatementSyntax)statement).UsingKeyword is object;
2375+
23892376
default:
23902377
return true;
23912378
}
@@ -6957,6 +6944,11 @@ private bool IsPossibleLocalDeclarationStatement(bool isGlobalScriptLevel)
69576944
return true;
69586945
}
69596946

6947+
return IsPossibleFirstTypedIdentifierInLocaDeclarationStatement(isGlobalScriptLevel);
6948+
}
6949+
6950+
private bool IsPossibleFirstTypedIdentifierInLocaDeclarationStatement(bool isGlobalScriptLevel)
6951+
{
69606952
bool? typedIdentifier = IsPossibleTypedIdentifierStart(this.CurrentToken, this.PeekToken(1), allowThisKeyword: false);
69616953
if (typedIdentifier != null)
69626954
{
@@ -6978,6 +6970,8 @@ private bool IsPossibleLocalDeclarationStatement(bool isGlobalScriptLevel)
69786970
//
69796971
// Note that we explicitly do this check when we see that the code spreads over multiple
69806972
// lines. We don't want this if the user has actually written "X.Y z"
6973+
var tk = this.CurrentToken.ContextualKind;
6974+
69816975
if (tk == SyntaxKind.IdentifierToken)
69826976
{
69836977
var token1 = PeekToken(1);
@@ -7053,6 +7047,53 @@ private bool IsPossibleLocalDeclarationStatement(bool isGlobalScriptLevel)
70537047
}
70547048
}
70557049

7050+
private bool IsPossibleTopLevelUsingLocalDeclarationStatement()
7051+
{
7052+
if (this.CurrentToken.Kind != SyntaxKind.UsingKeyword)
7053+
{
7054+
return false;
7055+
}
7056+
7057+
var tk = PeekToken(1).Kind;
7058+
7059+
if (tk == SyntaxKind.RefKeyword)
7060+
{
7061+
return true;
7062+
}
7063+
7064+
if (IsDeclarationModifier(tk)) // treat `const int x = 2;` as a local variable declaration
7065+
{
7066+
if (tk != SyntaxKind.StaticKeyword) // For `static` we still need to make sure we have a typed identifier after it, because `using static type;` is a valid using directive.
7067+
{
7068+
return true;
7069+
}
7070+
}
7071+
else if (SyntaxFacts.IsPredefinedType(tk))
7072+
{
7073+
return true;
7074+
}
7075+
7076+
var resetPoint = this.GetResetPoint();
7077+
try
7078+
{
7079+
// Skip 'using' keyword
7080+
EatToken();
7081+
7082+
if (tk == SyntaxKind.StaticKeyword)
7083+
{
7084+
// Skip 'static' keyword
7085+
EatToken();
7086+
}
7087+
7088+
return IsPossibleFirstTypedIdentifierInLocaDeclarationStatement(isGlobalScriptLevel: false);
7089+
}
7090+
finally
7091+
{
7092+
this.Reset(ref resetPoint);
7093+
this.Release(ref resetPoint);
7094+
}
7095+
}
7096+
70567097
// Looks ahead for a declaration of a field, property or method declaration following a nullable type T?.
70577098
private bool IsPossibleDeclarationStatementFollowingNullableType()
70587099
{

src/Compilers/CSharp/Test/Semantic/Semantics/SimpleProgramsTests.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,27 @@ ref int local(bool flag, ref int a, ref int b)
726726
CompileAndVerify(comp, expectedOutput: "100 200 300", verify: Verification.Skipped);
727727
}
728728

729+
[Fact]
730+
public void LocalDeclarationStatement_09()
731+
{
732+
var text = @"
733+
using var a = new MyDisposable();
734+
System.Console.Write(1);
735+
736+
class MyDisposable : System.IDisposable
737+
{
738+
public void Dispose()
739+
{
740+
System.Console.Write(2);
741+
}
742+
}
743+
";
744+
745+
var comp = CreateCompilation(text, options: TestOptions.DebugExe, parseOptions: DefaultParseOptions);
746+
747+
CompileAndVerify(comp, expectedOutput: "12", verify: Verification.Skipped);
748+
}
749+
729750
[Fact]
730751
public void LocalUsedBeforeDeclaration_01()
731752
{

src/Compilers/CSharp/Test/Symbol/Compilation/GetSemanticInfoBrokenCodeTests.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -195,11 +195,12 @@ public void Repro611177()
195195
var tree = comp.SyntaxTrees.Single();
196196
var model = comp.GetSemanticModel(tree);
197197

198-
var usingSyntax = tree.GetCompilationUnitRoot().DescendantNodes().OfType<UsingDirectiveSyntax>().Single();
199-
model.GetSymbolInfo(usingSyntax);
198+
Assert.Empty(tree.GetCompilationUnitRoot().DescendantNodes().OfType<UsingDirectiveSyntax>());
200199

201-
var identifierSyntax = tree.GetCompilationUnitRoot().DescendantNodes().OfType<IdentifierNameSyntax>().Single();
202-
model.GetSymbolInfo(identifierSyntax);
200+
foreach (var identifierSyntax in tree.GetCompilationUnitRoot().DescendantNodes().OfType<IdentifierNameSyntax>())
201+
{
202+
model.GetSymbolInfo(identifierSyntax);
203+
}
203204
}
204205

205206
[WorkItem(611177, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/611177")]

0 commit comments

Comments
 (0)