This is a test plan for the feature set variously known as "stackonly structs", "interior pointer", and "Span
See also
ref struct declarationsref as a modifier on a struct declarationref modifier must appear immediately before the struct keyword (no intervening public)ref struct type cannot be partial (neither ref partial struct nor partial ref struct are accepted)ref struct type may be genericref struct type may contain an instance field of another ref struct typestruct, nor a class, may contain a field of a ref struct typeref struct type declaration may not contain a base clause. It therefore cannot be declared to extend any interface type.base in the body of any method, property, etc in a ref struct type.ref struct type declaration may not contain an iterator instance method.ref struct type declaration may not contain an async instance method.ref struct type declaration may contain a static iterator method, and a static async method.ref struct type may not be embedded.ref struct typesref struct type may not be used as a type argument to a class, struct, delegate, or method.System.Nullable with a type argument of a ref struct type, even implicitly.e.M returns a value of a ref struct type, e?.M() is an error.ref struct type as a type argument, an error is given.ref struct type may not be converted to a delegateref struct type (e.g. GetHashCode) may not be converted to a delegateref struct type (even if the container is a ref struct type)ref struct type as the element type of an array typeref struct type.ref struct type.ref struct type, e.g. (1, span)(null, span) has no natural type and is converted to a tuple type that does not contain an element of a ref struct type (e.g. by the use of a user-defined conversion), e.g. (string, int) x = (null, span); where the span's type contains a conversion to int.Deconstruct method may output one or more values of a ref struct type.ref struct types can be written to implement the foreach pattern, in which case the foreach statement should work with an iterator variable of a ref struct type.ref struct types can be written to implement the foreach pattern with an element type of a ref struct type that contains a Deconstruct method, in which case the foreach statement should work with a pair of iterator variables of ref struct types.ref struct type may be captured in a lambda or local function.ref struct type may be declared in an iterator or async method.ref struct type may not require spilling in async code, e.g. it is an error (possibly reported during emit) to compile an expression such as M(span1, await e).ref struct type to object or ValueType.ref struct typeobject or in System.ValueType but not overridden in a ref struct type may be called with a receiver of that ref struct type.ref struct type.ref struct type is nullable.ref struct type.ref struct type is nullable.ref struct type (left, and right)ref struct typeref struct types.async or an iterator method. ref struct types, delegate types, and methods can be declared which permit the use of Linq with a sequence of value of a ref struct type represented by, e.g., Span<T>.ref struct type cannot be awaited, even if it is otherwise task-like. (is this right?)stackallocTests should demonstrate that stackalloc can only be used
stackalloc works in an initializerstackalloc works in a ternary in an initializerstackalloc works in a nested ternary in an initializerstackalloc does not work as an operand to a method invocation, e.g. M(stackalloc int[1])stackalloc result and the variable's type.ref struct type stackalloc is an error if parenthesizedstackalloc is an error if either operand of ??stackalloc is an error if subject to an explicit cast, e.g. var x = (myspan)stackalloc int[10];var x = stackalloc int[10]; is an error unless in an unsafe context, because x is of type int*.Span<int> x = stackalloc int[10]; is not an error outside an unsafe context.T x = stackalloc int[10]; is an error outside an unsafe context if it required a user-defined conversion from int* to T T x = stackalloc int[10]; is permitted outside an unsafe context if it required a user-defined conversion from Span<int> to T stackalloc restrictions regarding where it may appear.Tests should demonstrate that the compiler rejects an attempt to use stackalloc (#21918)
catch block (with or without a catch parameter).localloc instruction). [This cannot occur if stackalloc is restricted to local variable initializers]finally block.ref struct type from metadataref struct type from metadata (hand verify)ref struct type is declared with an explicit [Obsolete] attributeref struct type acts as such in separate compilation scenariosref struct type from VB produces a compile-time error. ITypeSymbol or TypeSymbol is a ref struct type.ref struct value cannot be used in an expression tree, even as an intermediate result. (?)ref struct type, either to or from dynamic.ref struct type may be used in an object, collection, or dictionary initializer for a type whose API was designed to permit this.this parameter of a ref struct type.ref struct type may not be used as a fill-in in string interpolation due to the need to box the value.T1 x = stackalloc int[10];, the semantic model should report that the stackalloc expression has the type int* and a converted type of T1T1 is int*int* to T1Span<int> to T1. stackalloc int[10] appears as some appropriate kind of conversion.stackalloc expression in any other syntactic context than a local variable initializer context has the type Span<T>.Each test bullet below of the form "Show that x is (ref-)safe-to-escape y but/and no further.", this is intended to require a test that demonstrates that x is (ref-)safe-to-escape to y, and a separate test that demonstrates that x is not (ref-)safe-to-escape the enclosing scope of y.
ref struct type is _ref-safe-to-escape_ to the top level of a method, but no further, no matter the ref mode of the parameter.ref, in, or out parameter that is not of a ref struct type is _ref-safe-to-escape_ from the entire method.this parameter of a struct type that is not a ref struct type is _ref-safe-to-escape_ to the top level of a method, but no further.ref struct type is _ref-safe-to-escape_ to the top level of a method, but no further.ref struct type is safe-to-escape (by value) from the entire method. Show this for the this parameter as well.ref struct type and any other type).ref struct type does not require an initializer, in which case it is safe to return. TODO: The spec needs to say this ref struct type that is declared as the iterator variable of a foreach loop is safe-to-escape the same as the loop's expression, and no further.ref struct type that is declared in a local variable declaration is safe-to-escape the same as the variable's initializer, and no further.ref struct type is explicitly the type of a pattern variable.ref struct type is implicitly the type of a pattern variable.out variables are safe to return.out variables participate in the no mixing rule.ref struct type.ref struct type.e is a reference type, that e.F is ref-safe-to-escape from the entire method.e is a value type, its ref-safe-to-escape is the same as the ref-safe-to-escape of e, but no further.e.F is a ref struct type, it is safe-to-escape the same as the safe-to-escape of e, but no further. c ? e1 : e2 where the result is a ref struct type, that the safe-to-escape of the result is the narrowest among the safe-to-escape of e1 and e2.e1 is a ref struct type but e2 is not (the safe-to-escape is taken from e1)e2 is a ref struct type but e1 is not (the safe-to-escape is taken from e2) e1 and e2 are ref struct types (the safe-to-escape is the smallest of the two)e1 nor e2 are ref struct types (the result is safe to return)c ? ref e1 : ref e2,e1 and e2 agreeref-safe-to-escape* of the result is the same as the *ref-safe-to-escape* ofe1`, but no further.e1.M(e2, ...) is ref-safe-to-escape the smallest of the following scopes (but no further) (a pair of positive/negative tests for each of these)ref and out argument expressionsref struct typesin parameter for which there is a corresponding expression that is an lvalue, its ref-safe-to-escapein parameter for which there is a no corresponding expression, the immediately enclosing scoperef struct returning method invocation e1.M(e2, ...) is safe-to-escape the smallest of the following scopes (but no further) (a pair of positive/negative tests for each of these)ref and out argument expressionsref struct typesin parameter for which there is a corresponding expression that is an lvalue, its ref-safe-to-escapein parameter for which there is a no corresponding expression, the immediately enclosing scoperef d.F where d is of type dynamice1 + e2 where the result is a ref struct type, that the safe-to-escape of the result is the narrowest among the safe-to-escape of e1 and e2.e1 is a ref struct type but e2 is not (the safe-to-escape is taken from e1)e2 is a ref struct type but e1 is not (the safe-to-escape is taken from e2) e1 and e2 are ref struct types (the safe-to-escape is the smallest of the two)e1 nor e2 are ref struct types (the result is safe to return)ref result follows the method invocation rules: both the ref-safe-to-escape is taken from the safe-to-escape of the receiver.ref struct type follows the method invocation rules: the safe-to-escape is taken from the safe-to-escape of the receiver. ref struct type, its result is safe-to-escape the smallest of the following scopes (but no further) (a pair of positive/negative tests for each of these)ref and out argument expressionsref struct typesin parameter for which there is a corresponding expression that is an lvalue, its ref-safe-to-escapein parameter for which there is a no corresponding expression, the immediately enclosing scopereturn ref await e;, where the type of e is a custom value task whose GetResult() method is ref-returning, we should be returning the returned ref, not a ref to a copy of its value. In particular, if the returned ref is ref-safe-to-return, then there should be no error.stackalloc expression is safe-to-escape to the top level of the method, but no further.default or default(T) expression is safe-to-escape from the entire enclosing method (i.e. it is safe to return)default value of a ref struct type is safe to return (i.e. escape from the whole method).ref e1 = ref e2, the ref-safe-to-escape of e2 must be at least as wide a scope as the ref-safe-to-escape of e1.return ref e1, the ref-safe-to-escape of e1 must be ref-safe-to-escape from the entire method.return e1, the safe-to-escape of e1 must be safe-to-escape from the entire method.e1 = e2, if the type of e1 is a ref struct type, then the safe-to-escape of e2 must be at least as wide a scope as the safe-to-escape of e1.ref or out argument to a ref struct type (including the receiver), with safe-to-escape E1, thenref or out argument (excluding the receiver and arguments of ref struct types) may have a narrower ref-safe-to-escape than E1; andvoid Deconstruct(out Span<int> x, out Span<int> y))? If yes, we need to solve https://github.com/dotnet/roslyn/issues/18629 first, since all deconstructions try to make a return type, but ValueTuple<Span, Span> is not allowed.[Note from @gafter: Issue #18629 has been solved]
@jaredpar I see that you are the test and review resource for this feature. It wasn't clear to me how much of that you've delegated. Please let me know if I am on the hook for further work aside from the open issues in the draft spec.
[Note from @gafter: I have been assigned the test resource for this.]
For ref reassignment will this work with non ref structs; e.g. array elements
Pattern I've wanted to express (poor pseudo code):
ref var e1 = ref _array[0];
while(el.NotCorrect)
{
if ((uint)e1.Next >= (uint)_array.Length)
{
break;
}
ref el = ref _array[e1.Next];
}
if (el.NotCorrect)
{
var oldLength = _array.Length;
_array = new var[oldLength * 2];
// *** reassignment here
ref e1 = ref _array[oldLength];
}
// Do something with el
// Rather than create a second array lookup here
// ref var e2 = ref _array[index];
You can do the general reassignment in the loop body (by the ref being local to loop); however then you need to pass the int indexer out and do a second array lookup to work with it; when you have already found the ref in the loop.
@benaadams Yes, once we add support for ref reassignment that would work. But we are not adding ref reassignment in 7.2.
I've added a championed proposal for ref reassignment. https://github.com/dotnet/csharplang/issues/933
@gafter @VSadov Can you two sync up on updating the status on this test plan? Thanks
The test plan is a gate on integrating a feature. Since this feature has been integrated, closing this test plan.
That doesn't seem right. Checking 18 out of 161 defeats the purpose and seems like a joke.
If I were to choose two items to push on, I'd ask to consolidate the docs for this feature and bullets for the test plan (to act as a reminder when working on next feature).
@jcouv While I agree, we integrated before the developer identified tests for these bullet items, and no tests have ever been identified for those bullets. We did not follow the defined process for feature development (i.e. test plan checked off before feature integrated). But that is, in practice, what we did.
I'll reopen and assign to the developer to enact your suggestion.
Vlad updated the docs: https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/readonly-ref.md
We still need to update the test plan.
The use of a ref struct type from VB produces a compile-time error.
@gafter
Could you please explain why we should not use ref struct in VB? I'd like to suggest modify this item to "The use of a ref struct type from VB should be checked with the same rules as verifying the use of ref struct in c#".
This feature has long since shipped.