csharplang/meetings/2018/LDM-2018-07-09.md
2018-07-12 23:33:10 -07:00

8.3 KiB

LDM July 9th, 2018

QOTD: "Yeah, it's easy if you do it in a shi**y way"

Agenda

  1. using var feature

    1. Overview
    2. Tuple deconstruction grammar form
    3. using expr; grammar form
    4. Flow control safety
  2. Pattern-based Dispose in the using statement

  3. Relax Multiline interpolated string syntax ($@)

using var Feature

Motivation

Proposal: https://github.com/dotnet/csharplang/pull/1703

It's a common problem that multiple using statements can require successive nesting, causing what is mostly linear code to have the "down and to the right" problem, where increasing indentation makes the code less readable, not more. One way people try to solve this is using the

{
    using (expr1)
    using (expr2)
    using (expr3)
    { ... }
}

syntax, but that has two problems. First, many style guidelines prohibit "braceless" usings, but make an exception for this specific case. Second, if there is any intervening code required between the using expressions, this syntax form is not allowed.

Objections

Objections to this feature fall mainly in two categories. Either there is worry about determinism and ordering, or that this feature isn't sufficiently general to encompass the scenarios we would consider making the feature "worth it."

The determinism concern is that refactoring from the using (...) {...} form could unintentionally lengthen the liveness scope to the entire method, instead of just to the closing brace of the using. The ordering concern is that nesting provides very clear ordering semantics, and the "stacked using" form also has a clear ordering, since there cannot be any code in between each using. This isn't necessarily true for using-variables. It's possible that both of these concerns could be mitigated by better refactoring and analysis tools.

The generality concern is mainly around the using (expr) { ... } statement form, which doesn't have an equivalent using-variable form in the current design.

Conclusion

It's worth it. The concerns are valid, but don't seem bad enough to block the feature.

Tuple deconstruction grammar form

The first question was about the proposed grammar. The current design is a new type of statement (local-using-declaration). There are two potential holes in the grammar: no space for tuple deconstructions and no using expr; form.

For deconstruction, we came up with a number of potential forms:

    (using var x, using var y) = M(); // Form 0
    using (x, y) = M();               // Form 1
    using (var x, var y) = M();       // Form 2
    using var (x, y) = M();           // Form 3
    using var t = M();                // Form 4

Of these, only (4) would be legal in the current proposal. Of the remaining forms, form (0) seemed the clearest. There was consensus that this implied the declaration of two new variables, each of which was independently disposed, in the style of

using var x = M1(), y = M2();

It was not immediately clear whether the tuple itself was disposed in form (0). This was a common complaint with the rest of the forms as well: it is unclear what the semantics of each statement is. Is the tuple itself being disposed? Is disposal distributed over the elements? Both? Some tuple deconstructions also happen in "reverse" order of the tuple elements' lexical ordering. If dispose is distributed, what order are the elements disposed in?

This also raised the question of nested declarations in the initializer, e.g.

using var x = M1(out var y)

Is x the only using variable? Or is y one as well?

Conclusion

Let's continue with the proposal as-is. Form (4) works and should work. There may be compelling scenarios to open up the syntax to tuple deconstructions, but we don't have a convincing argument yet. We also don't have a clear rule for prohibition. For nested declarations, they are not declared as using variables.

using expr grammar form

These concerns dovetailed into discussion of the using expr; form, because some of these grammar forms may compose. For example, since var (x, y) is an expression in C#, the following could be a potentially legal statement with no modification:

using var (x, y) = M();

In this case var (x, y) = M() would be the expr in using expr;.

This form seems desirable to round out the feature, but it isn't clear how it fits into the language. The previous decision seems to imply we don't want using deconstructions, but it isn't clear what rule we would use to prohibit them, in a principled sense. The feature also has some integration concerns. using (expr); is already a legal construct in the C# language with different semantics, although the compiler gives a warning about it today. There is some concern that using expr; and using (expr); are too close grammatically and that the syntax effectively rules out parenthesized expressions.

Finally, there were questions about grammatical ambiguity with possible future language features. If C# were to allow statements on the top level, a using System; line could either be a using-directive if System is a namespace, or a using-statement if System is a type. The same problem could occur if we were to allow using-directives at the statement level. This doesn't seem very bad since we already have similar ambiguities with Color Color rules and resolve them properly during semantic analysis. These ambiguities are also probably present for using-directive aliases.

There were a couple proposals to try to deal with some of these problems:

  1. Any expression that declares variables is disallowed as a using expr;

  2. Hold off on using expr; for now.

  3. Allow _ as a discard for using var _ = expr; - Or using _ = expr;

Conclusion

This is a blocking issue that we must decide on for C# 8.0. Either we should disallow this form entirely or find some principle to use to reject the constructions we find confusing. However, we think this problem is solvable and shouldn't block continued work on the feature.

Flow control safety

The last design issue was safety in the presence of goto and similar flow control features (e.g., local functions). The existing spec notes that backward flow control is not a problem, but what about forward flow control? For example,

{
    goto target;
    using var x = new FileStream(...);
target:
    var y = x;
    return;
}

In the previous example, this is an error, because x is not definitely assigned. In fact, all uses in this category, where flow is manipulated to skip over the variable definition before a read, are safe because the variable will not be definitely assigned. In addition, because using variables are read-only, it also cannot be assigned later.

One case which the spec does not currently handle is

{
    goto target;
    using var x = new FileStream(...);
target:
    return;
}

Here x is never read, so there would be no definite assignment errors. However, there is an implicit read of the variable at the end of the variable lifetime, which could be a read of an unassigned variable.

Conclusion

A new line to the spec should be added saying that, if the end of a using variable's lifetime is reachable, that variable must be definitely assigned at that point.

Open question

  • This needs more precise language. The spec does not have reachability at "points". What do we mean when we say the end of a block is "reachable"?

Pattern-based using statement

We like the feature. Main question: what type of pattern do we look for? As a general guideline, we don't want to have another special case pattern. However, it seems like we have multiple styles already.

  • GetAwaiter doesn't allow params or optional parameters
  • LINQ does

Do we want using to be like await or like LINQ?

Also, do we require void return type? Most of the patterns today have strict requirements on return type, but they also usually consume the return type. using does not.

Conclusion

Keep the spec as is: Dispose must be parameter-less in instance-form, void-returning, and accessible. This allows for extension methods, but not optional parameters or params.

Multiline interpolated string syntax

It's hard to remember which is the correct syntax: $@"" or @$"". The proposal is to allow either.

Conclusion

No objections.