-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Draft implementation of file-scoped-namespaces #48937
Draft implementation of file-scoped-namespaces #48937
Conversation
src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs
Outdated
Show resolved
Hide resolved
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
@@ -4470,6 +4510,7 @@ private void ParseVariableDeclarators(TypeSyntax type, VariableFlags flags, Sepa | |||
// the reported errors should take into consideration whether or not one expects them in the current context. | |||
bool variableDeclarationsExpected = | |||
parentKind != SyntaxKind.NamespaceDeclaration && | |||
parentKind != SyntaxKind.SingleLineNamespaceDeclaration && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a test for this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RikkiGibson to ensure we've addressed this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding the tests.
var semicolon = this.TryEatToken(SyntaxKind.SemicolonToken); | ||
SyntaxListBuilder initialBadNodes = null; | ||
this.ParseNamespaceBody(ref semicolon, ref body, ref initialBadNodes, SyntaxKind.SingleLineNamespaceDeclaration); | ||
Debug.Assert(initialBadNodes == null); // init bad nodes should have been attached to open brace... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment is wrong (would be attached to semicolon). Do we have a test scenario where that happens?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RikkiGibson to address
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See SingleLineDeclarationParsingTests.NamespaceWithExtraSemicolon
. Will fix the comment.
var comp = CreateCompilation(tree); | ||
var model = comp.GetSemanticModel(tree); | ||
var errors = comp.GetDiagnostics().ToArray(); | ||
Assert.Equal(2, errors.Length); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Let's check the diagnostics instead of just the count
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am OK with this because it's taken from the test directly above.
Assert.False(classDecl.Identifier.IsMissing); | ||
Assert.True(classDecl.OpenBraceToken.IsMissing); | ||
Assert.True(classDecl.CloseBraceToken.IsMissing); | ||
var ns = root.DescendantNodes().OfType<SingleLineNamespaceDeclarationSyntax>().Single(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you intend to check something on ns
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RikkiGibson to address
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not that meaningful but I'll add an assert that the semicolon is present.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM Thanks (iteration 26) with a couple test suggestions
{ | ||
diagnostics.Add(ErrorCode.ERR_SingleLineAndNormalNamespace, node.Name.GetLocation()); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should also have a check to disallow top-level statements.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RikkiGibson Add PROTOTYPE or track elsewehere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is handled by the existing mechanisms that prevent top-level statements within namespaces. See SingleLineNamespaceWithFollowingStatement
in AssemblyAndNamespaceTests.cs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. My expectation is I will make the changes to resolve the comments myself in a follow-up PR.
@@ -83,9 +83,9 @@ internal string GetDebuggerDisplay() | |||
usingDirectives = inUsing ? default(SyntaxList<UsingDirectiveSyntax>) : compilationUnit.Usings; | |||
externAliasDirectives = compilationUnit.Externs; | |||
} | |||
else if (declarationSyntax.Kind() == SyntaxKind.NamespaceDeclaration) | |||
else if (declarationSyntax.Kind() is SyntaxKind.NamespaceDeclaration or SyntaxKind.SingleLineNamespaceDeclaration) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wondering if we should have an extension for this. Depends on how often this check is performed of course.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RikkiGibson I find 5 or 6 instances of this check so an extension sounds fine. I'd say push a commit if you'd like (you're in charge of the PR ;-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really interested in doing this at this time. Would rather make the adjustment as part of a larger refactoring e.g. to replace Kind()
tests with type tests in more places.
@@ -2266,7 +2268,8 @@ private NamespaceOrTypeSymbol GetDeclaredTypeMemberContainer(CSharpSyntaxNode me | |||
if (memberDeclaration.Parent.Kind() == SyntaxKind.CompilationUnit) | |||
{ | |||
// top-level namespace: | |||
if (memberDeclaration.Kind() == SyntaxKind.NamespaceDeclaration) | |||
if (memberDeclaration.Kind() == SyntaxKind.NamespaceDeclaration || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: feels like we should use or
here instead of calling .Kind()
twice
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -2297,7 +2300,8 @@ private NamespaceOrTypeSymbol GetDeclaredTypeMemberContainer(CSharpSyntaxNode me | |||
} | |||
|
|||
// a namespace or a type in an explicitly declared namespace: | |||
if (memberDeclaration.Kind() == SyntaxKind.NamespaceDeclaration || SyntaxFacts.IsTypeDeclaration(memberDeclaration.Kind())) | |||
if (memberDeclaration.Kind() is SyntaxKind.NamespaceDeclaration or SyntaxKind.SingleLineNamespaceDeclaration || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: prefer binary operators on the start of the next line. For example || SyntaxFacts.IsTypeDeclaration(...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -4468,6 +4495,99 @@ class C | |||
SymbolDisplayPartKind.EventName); | |||
} | |||
|
|||
[WorkItem(791756, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/791756")] | |||
[Fact] | |||
public void KindOptionsSingleLineNamespace() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I may end up coming up with some kind of helper that makes it easier to express these tests as [Theory]
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have done this for many of the tests.
@@ -421,28 +464,27 @@ namespace System {} | |||
} | |||
|
|||
[Fact] | |||
public void TypeParameterDeclaringSyntax() | |||
public void TypeParameterDeclaringSyntaxSingleLineNamespace() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't understand why the "regular" namespace version of the test was essentially removed here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. Let's add the original back, or make this a Theory to cover both
@@ -471,5 +625,166 @@ namespace N { } | |||
// (2,1): error CS1671: A namespace declaration cannot have modifiers or attributes | |||
Diagnostic(ErrorCode.ERR_BadModifiersOnNamespace, "[System.Obsolete]").WithLocation(1, 1)); | |||
} | |||
|
|||
private static readonly CSharpParseOptions s_previewOptions = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Preview); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Typically we use TestOptions.RegularPreview
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed this. I think it could be handy to be able to change this from Preview to Regular in one place. We have done this for other features in the past, IIRC.
src/VisualStudio/Core/Def/Implementation/ProjectSystem/FileChangeWatcher.cs
Outdated
Show resolved
Hide resolved
@RikkiGibson That seems reasonable to me. This PR is already quite large, a bit unweildy (evidenced by the current conflicts), and we are going into a feature branch. I would focus on getting this merged then follow up with a PR that cleans up the items that you noticed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM Thanks (iteration 28)
Draft impl of championed feature: dotnet/csharplang#137
Speclet at: dotnet/csharplang#4073
Test plan: #49000
To help ground discussion, find issues, and garner feedback to help with the language design process.
The general approach here was to take an approach similar to what we did with TypeScript. Specifically, given the two namespaces:
namespace X; class C { }
andnamespace X { class C { } }
We represent these with different concrete types, but a similar abstract parse tree shape. Specifically, both generate a parse tree like so:
NamespaceDeclaration and the new SingleLineNamespaceDeclaration derive from a common BaseNamespaceDeclaration. This type contains the Name, Externs, Usings, Attributes and Members. The two subclasses contain the only syntactic differents (the curlies, or hte semicolon).
By doing this, we allow all code to still presume a certain containment structure for files. For example, in the IDE there is lots of code that will walk upwards in a file looking for the containing namespace declaration. This approach allows all existing code (that doesn't care about curlies) to just move to processing 'BaseNamespaceDeclaration's, isntead of 'NamespaceDeclaration's, and allows anything that currently looks for SyntaxKind.NamespaceDeclaration to just look for that and SyntaxKind.SingleLineNamespaceDeclaration.
--
For my testing approach, i made targeted parsing tests and targeted tests for the specific specialized errors we produce. I then searched the compiler test base for usages of NamespaceDeclarationSyntax, SyntaxKind.NamespaceDeclaration, INamespaceSymbol, and NamespaceSymbol and generally created sibling tests that validated the same behavior when a single-line namespace was used instead.