Draft implementation of file-scoped-namespaces#48937
Draft implementation of file-scoped-namespaces#48937RikkiGibson merged 30 commits intodotnet:features/FileScopedNamespacesfrom
Conversation
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.
| // 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 && |
| 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.
Comment is wrong (would be attached to semicolon). Do we have a test scenario where that happens?
There was a problem hiding this comment.
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.
nit: Let's check the diagnostics instead of just the count
There was a problem hiding this comment.
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.
Did you intend to check something on ns?
There was a problem hiding this comment.
It's not that meaningful but I'll add an assert that the semicolon is present.
jcouv
left a comment
There was a problem hiding this comment.
LGTM Thanks (iteration 26) with a couple test suggestions
| { | ||
| diagnostics.Add(ErrorCode.ERR_SingleLineAndNormalNamespace, node.Name.GetLocation()); | ||
| } | ||
| } |
There was a problem hiding this comment.
We should also have a check to disallow top-level statements.
There was a problem hiding this comment.
I think this is handled by the existing mechanisms that prevent top-level statements within namespaces. See SingleLineNamespaceWithFollowingStatement in AssemblyAndNamespaceTests.cs
RikkiGibson
left a comment
There was a problem hiding this comment.
LGTM. My expectation is I will make the changes to resolve the comments myself in a follow-up PR.
| 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.
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.
@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.
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.
| { | ||
| // top-level namespace: | ||
| if (memberDeclaration.Kind() == SyntaxKind.NamespaceDeclaration) | ||
| if (memberDeclaration.Kind() == SyntaxKind.NamespaceDeclaration || |
There was a problem hiding this comment.
nit: feels like we should use or here instead of calling .Kind() twice
|
|
||
| // 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.
nit: prefer binary operators on the start of the next line. For example || SyntaxFacts.IsTypeDeclaration(...)
|
|
||
| [WorkItem(791756, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/791756")] | ||
| [Fact] | ||
| public void KindOptionsSingleLineNamespace() |
There was a problem hiding this comment.
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.
Have done this for many of the tests.
|
|
||
| [Fact] | ||
| public void TypeParameterDeclaringSyntax() | ||
| public void TypeParameterDeclaringSyntaxSingleLineNamespace() |
There was a problem hiding this comment.
I didn't understand why the "regular" namespace version of the test was essentially removed here.
There was a problem hiding this comment.
Good point. Let's add the original back, or make this a Theory to cover both
| 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.
nit: Typically we use TestOptions.RegularPreview
There was a problem hiding this comment.
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.
|
@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. |
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.