csharplang/meetings/2021/LDM-2021-03-01.md
2021-03-01 16:39:04 -08:00

6.4 KiB

C# Language Design Meeting for March 1st, 2021

Agenda

  1. Async method builder override
  2. Async exception filters
  3. Interpolated string improvements

Quote of the Day

  • "I checked my math twice before I spoke." "Well I did not."

Discussion

Async method builder override

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

We first looked at a proposal to enable customization of the async state machine generated by the compiler to allow for additional customizability. Today, there is no way to customize the builder that the compiler emits for an async method except by introducing a new task-like type. This is a leaky abstraction that limits the ability of the BCL and other libraries to adopt pooling and other customizations where appropriate because they don't want to leak the implementation detail of the pooling to users of a method, but don't have another way to customize the builder.

This proposal has simple core, with multiple possible addendums that add more complexity but also more customizability to usages. After review, we believe the initial proposal is enough to cover our needs here, as additional arguments to a builder can be achieved by using additional builder types that have their own Create methods. This could be somewhat ugly for deeply-variable scenarios, but we believe this is enough of a corner case that we don't need to support it in the language, at least initially. If the need comes up for it in a future version, we can look at adding more customization then.

We also looked at ways to simplify the type/module level constructor. The proposal today suggests having a constructor that takes both the builder type and the task-like type, that would inform the compiler of when to apply the customized builder. This seems like mandated busy-work for the developer: the builder type returns an instance of this task-like type from a well-defined method, and we'd need to verify that the specified task-like type is compatible with the return of that method, so it seems like it would be easier for the user to just infer the applicable task-like type from the builder. There are some interesting scenarios around open generics here: how does the compiler perform substitution to arrive at the final builder type, and verify that the task-like return is correct? Some spec work will be needed to achieve this, but it should be possible, and should be overall worth it.

Another question is whether it's possible to go back to the default builder on a method if an attribute has been applied at a type/module level. This should be doable: all the standard builder types are public API, so all the user needs to know is what the default builder for a type actually is. This is also a fairly niche corner in an already niche scenario, so we don't think any additional sugar or "default" constructors are necessary.

Diagnostics will need to be reported on the use-site for issues involving generic substitutions. Builder generics will be able add new constraints on type parameters that the underlying task-like type doesn't have, so invalid uses need to be carefully handled and diagnostics reported to ensure a good user experience.

Conclusion

We unanimously like looking at a type inference-based approach and seeing if we can make it work.

Async exception filters

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

This is a proposal to address a pain-point in async contexts, where the state machine can catch exceptions and unwind the exception stack before user-code can catch the exception, which leads to bug reports and heap dumps that are missing the actual stack trace where an exception is thrown. Roslyn is a prime example of this, with lots of try/catch handlers sprinkled over the codebase in async contexts to be able to report heap dumps before the async machine catches and unwinds the stack. However, this is often a error-prone process: Roslyn receives a bug report with a missing stack, adds a new try/catch to ensure it gets a good bug report, then waits for a user to encounter the problem again. This proposal would allow the user to define a handler that gets called when the async state machine gets called back for an exception, allowing Roslyn and similar async applications to report take telemetry and other similar actions before stack unwinding. Similar to the first proposal today, it in many ways represents an aspect-oriented feature of customizing the async state machine emit in some way.

Conclusion

We like the idea of the proposal, but it needs to be fleshed out more. We'll look at a more complete proposal when it's ready.

Interpolated string improvements

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

Finally today, we looked at a proposal on improving code gen and usage for interpolated string expressions. Most of this section of the meeting was spent going through the proposal itself, with not a lot of discussion on the individual points or open questions. Observations we made:

  • We should consider making the GetInterpolatedString and TryFormat methods public only. This ensures that, like instance GetEnumerator() methods, there isn't potential confusion when one method is preferred in project A but another method is preferred in project B, even when all the types involved are otherwise identical.
  • We'll need to carefully consider how the out parameter affects local lifetime rules for ref struct builder types, which we expect to be the majority. Most of this should end up falling out, but careful verification will be needed.
  • The more likely compat issue is that a library introduces a public API that exposes one of these builder types, and an older compiler is fed that API and picks a different method. We don't see any real way to avoid this, nor do we think it's a deal breaker.
  • Even intentional, measured changes to better conversion from expression are scary, and this needs very careful review to ensure we're doing exactly what we want, and no more.
  • TryFormat short-circuiting can be quite powerful. There are plenty of logging scenarios where the user would not want to evaluate some expensive computation that we get around today by creating Funcs and lazily-evaluating. This would solve that, but we need to make sure we're not creating an easy pitfall for users.

Conclusion

We like the direction of this proposal. We did not address the open questions in depth today, will need to come back to them soon.