Skip to content

Test plan for enhanced using and foreach #28588

@agocke

Description

@agocke
  • Specification checked in to csharplang.

Feature List

Features

Code Examples

Using declarations

using var fileStream = new FileStream(@"temp\example.txt");

// vs. 

using (var fileStream = new FileStream(@"temp\example.txt")) {
  ...
}

// Works for IAsyncDisposable as well. 
await using var x = new AsyncDisposableType();

Pattern based using for ref struct

ref struct S { 
  public void Dispose() { ... }
}

using var local = new S();

Compiler Secnarios

  • Ensure all features are tied to the C# 8.0 language version
  • Goto and using declaration
    • Goto can't jump over a using declaration unless it also jumps over the closing brace

      { 
          { 
              goto target1; // error
              goto target2; // okay
          }
          using var fileStream = new FileStream(...);
      
          target1:
          ...
      }
      target2:
      ...
    • goto backwards over a using declaration must also cross the opening brace

    • goto can jump around inside a block when it doesn't cross using declaration

  • pattern based using on ref struct
    • Follows the pattern based lookup specification
      • Consider extension methods
      • Consider generic methods where inference is possible
    • Applies to enumerators inside a foreach block (both classic and await foreach)
    • ensure a ref struct respects the lifetime of the initializer
      ref struct S {
          public Span<int> field;
          public void Dispose() { } 
      }
      
      S M() { 
          Span<int> span = stackalloc ... 
          using S local = new S() { field = span; }
          return local; // error 
      }
  • using declaration restrictions
    • prohibit ref and ref readonly in a using declaration
    • test in conjunction with const: using const ... (should be binding error), const using ... (parsing error?)
    • test without initializer: using MyDisposable local;
    • test with non-disposable type
    • test with missing well-known IDisposable type (see uses of Compilation.MakeTypeMissing(WellKnownType) API)
    • test with missing well-known IAsyncDisposable type.
    • test await using declaration only legal in async method.
    • test can't use on a field or parameter
    • test disallowed in case label (unless inside an explicit block)
    • test in scripting
    • test that dynamic is allowed
  • using declaration usage cases
    • test with null / default value
    • test with lambdas or local functions that capture using variables and non-using variables
    • variable is not assignable
    • test can't use as ref or out.
    • possible to use a struct variable as ref readonly or in
      struct S : IDisposable { 
          public void Dispose() { ... }
      }
      
      using S local = new S();
      ref readonly rr1 = ref local; // okay
      ref rr2 = ref local; // error
    • local functions and definite assignment
      public void M() {
          // legal because there isn't a definite assignment issue
          Local();
          
          using FileStream x;
          
          void Local()
          {
              x = null; // error is it's not assignable
              x.ToString();
          }
      }
    • order of dispose is reverse of declaration order
    • multi-variable declarator dispose order
    • multi-variable declarator dispose order in face of exception (all finally executed)
    • test inside an iterator
    • test inside an async method
  • Semantic Model
    • Getting the awaiter info from a using declaration
    • Basic semantic information on the local from the API
  • Consider impact on F#
  • ref struct with Dispose() method should work if it returns Task or ValueTask (task-like)
    • same for DisposeAsync()
  • test scripting (should disallow using-declaration, maybe some additional complications)
  • check that using-declaration logic checks AwaitUsingAndForeachAddsPendingBranch flag (context)
  • test ref struct with Task? DisposeAsync() method in await using (declaration or statement). Should produce a null warning
  • test async using-declaration in async-iterator method
    • two async using-declarations with a yield return in between
  • test obsolete Dispose method on a ref struct in using or in foreach

Productivity

  • Find all references (moved all FAR issues into FindAllReferences should handle Dispose referenced in using and foreach statements #28228)
    • Finds Dispose method usage in using declaration
    • Finds Dispose / AsyncDispose method usage in foreach statements
  • InlineVariable should not trigger for a using declaration variable (replaced by issue InlineVariable should not trigger for a using declaration #35645)
  • Extract method should be disabled for blocks that have a using declaration (replaced by issue Extract Method should understand using declarations #35646)
  • ENC
    • Verify it's functional for pattern based dispose in foreach
    • Using declarations going to be tricky. Mainly because adding “using” to a variable declaration implies
      wrapping the containing scope in a finally block. We need to be careful where to place the sequence point that is
      currently emitted for ‘}’ of such scope. EnC is especially concerned about all exception handling blocks and needs
      to be aware of them.)
  • test MakeMethodAsync on using-declaration.
  • verify sequence points and debugging experience
  • Extras
    • fixer to use the right kind of declaration (await using or using) depending on the kind of disposable
    • refactoring to convert using-statement to using-declaration. (done)

Open LDM Issues

  • Syntax for using var and IAsyncDisposable (await using var x = ...)?
  • Should extension dispose be invoked for null values ( writing x.Dispose() would call it, GetPinnableReference() for fixed doesn't)
  • Nullable-value types in using (should we fail if there isn't a lifted extension, or should we check null and call the underlying type if not)
  • Handle the jump forward 'goto' case. Should we disallow labels in a block after a using var (you can't jump into a using statement today as the scope is different)

Proposal: dotnet/csharplang#1703
Championed issue (with LDM history): dotnet/csharplang#1174

Metadata

Metadata

Assignees

Labels

Area-CompilersTestTest failures in roslyn-CITest-GapDescribes a specific feature or scenario that does not have test coverage

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions