csharplang/meetings/2021/LDM-2021-05-12.md
2021-05-12 16:03:06 -07:00

7.9 KiB

C# Language Design Meeting for May 12th, 2021

Agenda

  1. Experimental attribute
  2. Simple C# programs

Quote of the Day

  • "I've always felt the shotput with the Rubik's Cube should be part of the Olympics"

Discussion

Today we took a look at a couple of broader .NET designs that will impact C#, to get an idea of how we feel C# fits into these designs.

Experimental attribute

https://github.com/dotnet/designs/blob/main/accepted/2021/preview-features/preview-features.md

The first proposal we looked at is the design the runtime team has been working on around preview features. The C# compiler has shipped preview features in the compiler ahead of language releases before, but this hasn't been very granular, and we've never shipped a version of the runtime (particularly not an LTS!) where some parts of the runtime and language features built on top are actually still experimental. Support stories get complicated, particularly when you start mixing installations of LTS versions of .NET 6 and .NET 7 on the same machine. For example, we'll be shipping a preview of abstract static members in interfaces in .NET 6, but this implementation will always be a preview. It's possible that, for .NET 7, we'll move the feature out of preview, and the version of the C# compiler in VS installed at that point would no longer consider abstract statics to be a preview language feature, even if the project itself is targetting .NET 6. To solve this, the runtime will introduce a new attribute, detailed in the preview feature proposal, which marks APIs and elements of RuntimeFeature that should be considered preview in this version of the runtime, and then require that the consuming library be marked preview itself. Where this requirement comes from is one of the open questions in this meeting: should it come from an analyzer or from the compiler itself?

The analyzer approach is initially attractive because it is easier to implement. Roslyn's analyzer infrastructure, particularly with the investments around both the symbol analyzer model and IOperation, means that it is possible to implement this analyzer almost entirely language-independently, while a compiler feature would have be implemented in each compiler separately. It is also significantly easier to maintain an analyzer: turning off analyzer errors is possible for false-positives while bugs are patched, while compiler errors are impossible to disable. However, this flexibility does come with downsides: it's possible to disable the analyzer for real positives and ship in an unsupported state, and potentially cause downstream dependencies to take advantage of preview features unintentionally. There's also no guarantees that users actually enable the analyzer, as they might just disable analyzers for any particular reason. Despite these concerns, we think that an analyzer is a good path forward, at least initially. We can use an analyzer in .NET 6 to iron out the semantics of how the feature works, and look at putting the logic into the compiler itself in a future version.

In particular, there are some interesting semantics that still need to be worked out. Should APIs be allowed to introduce or remove previewness when overriding or implementing a member? For example, System.Decimal already has a + operator today, and it will be implementing the preview numeric interfaces that define the + abstract static operator. The + on System.Decimal is not preview today, nor should it be in .NET 6, but it will be implementing the preview + operator. Explicit implementation is also not always possible for these operators, as we will have asymmetric operator support in the math interfaces that get unified with a symmetric operator later in the hierarchy, so we can't rely on enforcing explicit implementation to solve this problem. On the opposite side of this problem, allowing adding of previewness at more derived API level is problematic, because the user could be calling a less-derived method and therefore accidentally taking advantage of a preview feature.

Finally, there is some IDE-experience work to think through. While we do want these APIs to show up in places like completion and refactorings, we would also like to make sure we're not accidentally opting users into preview features that then break their build totally unexpectedly. We think there is design space similar to our handling of Obsolete, such as marking things preview in completion lists.

Conclusion

We will proceed with an analyzer for now and look to move that logic into the compiler at a later point, if we think it makes sense. More design is needed on some parts of the feature, but it doesn't require direct involvement of the LDM.

Simple C# programs

https://github.com/dotnet/designs/pull/213

Finally today, we looked at "simple" C# programs. We take simple here to mean programs that don't have a lot of complex build logic, and are possibly on the smaller side code-wise. Simple does not necessarily mean "Hello, World!" and nothing else. While we are interested in the intro experience, we additionally think there is opportunity to expand to address a market that has traditionally had a bunch of friction to use C# in today.

C# has historically had a focus on very enterprise scenarios: professional developers working on larger projects using a dedicated IDE. Our user studies have shown that this workflow isn't what many newer users are expecting, particularly if they're coming from scripting languages like Go, Javascript, or Python. These users instead expect to be able to simple make a .cs file and run it, with potentially more ceremony as they start adding more complex dependencies or other scenarios. Other expectations exist (such as repls), but our studies have shown that this is the most popular. An important part of our task here will be figuring out where that "more ceremony" step lies, what that additional ceremony will look like, and how it will interact with any additions we make to the language (how project-file based projects interact with #r directives, for example).

Investing in tooling that puts NuGet directives in C#, as well as potentially #load or other similar file-based directives, is going to necessitate that we reconcile file structure and project structure in C#. Today, the contents of C# files are very intentionally independent of their locations on disk: file structure is handled by the project file, and project structure is handled by using directives and namespace declarations. #r to local NuGet packages or #load to add other .cs files would blur the line here.

Another important consideration of these directives is how complex we want to let them get. At the extreme end, these directives could be nested inside #if directives, which starts to necessitate a true preprocessing step that the SDK tooling will need to understand and perform. Today, preprocessor directives in C# don't have massive effects, and they can be processed in line with lexing. There are restrictions we can look at for where to allow #r and similar directives, and deciding on those restrictions will help inform where exactly that additional ceremony will land. For example, we could require that all of these directives must be the first things in a file, with nothing preceding them. This would ensure that conditional NuGet references are that cliff of complexity that requires a project file.

Finally, how much of the project settings do we think is reasonable to control in a .CS file? Is it reasonable to set language version or TFM in a file? What about output settings such as dll vs exe, or single-file and trimming settings? We don't have answers for these today, and some of these answers will be driven by discussions with the SDK teams, but they're all part of determining where the cliff of complexity will land.

Conclusion

Overall, we're extremely excited to take this challenge on, and look forward to working through this design to find the edges.