csharplang/meetings/2021/LDM-2021-03-15.md
2021-03-16 13:51:13 -07:00

5.2 KiB

C# Language Design Meeting for March 15th, 2021

Agenda

  1. Interpolated string improvements
  2. Global usings

Quote of the Day

  • "Need is a strong word... I'm just teasing, I'm just lobbing in a joke"

Discussion

Interpolated string improvements

https://github.com/dotnet/csharplang/issues/4487

We looked at the open question around argument passing today. In the current form, the proposal treats the receiver specially, even for reduced extension methods where the receiver is just sugar for the first argument. This special treatment of the receiver is very limiting, as many API patterns exist today where destinations are conveyed as arguments to a method, not as the receiver of the method, and we would like APIs to be able to continue having their same shapes. It's also inconsistent with how we treat extension methods in the rest of the language: they're a sugar, and there is no semantic difference between calling them in reduced form or not.

To address this, we looked at a couple of possible solutions:

  1. Don't pass anything. No receiver, no arguments. This is clean, but is not really an improvement on how it works today. It means that the builder type would need to support storage of an arbitrary number of arguments of arbitrary types, and would preclude working with ref structs and other types that can't be in generics today.
  2. Require methods to have a PrepFormat or similar signature that is isomorphic to the original signature, with the builder parameters as out variables instead. This would allow us to simply call that signature, and there would be no fancy mapping to consider. However, this is pretty messy from a public API standpoint. We can apply EditorDisplay to such methods, but they'll still exist, need to be documented, and generally get in the way. It also means that every method will need to have a second method, which possibly limits code sharing abilities.
  3. Put an attribute on either individual parameters or on the builder type, specifying that these parameters should be passed to the builder creation method. The former version was proposed in the open questions section, but the latter came up in discussion and seems like a better approach. It gives a central location, handles multiple potential builders in a method signature, and allows including the receiver type as either a magic name like this or as a boolean flag.

Conclusion

We'll look at the attribute on the builder type solution. The next question we need to answer is around out-of-order execution for arguments passed to builders.

Global usings

https://github.com/dotnet/csharplang/issues/3428

Next, we looked at 2 open issues in global usings: the scope of the global using statements, and how name lookup treats these usings with respect to regular using directives. Both of these questions really hinge on the overall view we want to take on how global usings are represented. There are 2 views here:

  1. Global usings are effectively copy/pasted into the top of every file. They're just like regular using directives, and all the same rules that apply to regular using directives at the top level apply to global usings as well.
    • This has the advantage that, at the top level, a regular using directive will never be changed by a global using directive. Users can't introduce namespace ambiguities at the top level by introducing a global using directive that has a nested namespace with the same name as a top-level namespace.
    • It aligns with our prior art: both CSX and VB.NET work this way.
    • This version has the disadvantage that, at the top level, global aliases could not be reused in a file-scoped using alias. They could be used in an alias nested inside a namespace declaration, but not at the top level. While this is a niche scenario, it is likely that someone will hit it at some point.
    • If a name is imported in both a global using directive and a file-scoped using directive, that name is ambiguous.
  2. Global usings are a new scope, that exists above regular using directives at the top of a file. They are to file-scoped using directives what file-scoped using directives are to using directives nested in a namespace.
    • This would allow globally-defined aliases to be used in file-scoped directives.
    • We would need to rationalize how these rules work for CSX. Do global using directives imported from a #load directive change the meaning of using directives in the current file, or files that are subsequently #loaded?
    • Name lookup would prefer the file-scoped using directives, only going to global directives if a name wasn't found.
    • Has a bigger implementation cost: some PDB changes might be required in order to ensure that names mean the correct things and diverges heavily from existing implementations.

We think both worldviews here are consistent and reasonable, and would be fine with either mental model as an explanation to consumers of how the feature works. However, worldview 1 is more consistent with past decisions and has less implementation concerns.

Conclusion

We'll go with worldview 1. The decisions for scoping and name lookup fall out from this.