Skip to content

Support new stackalloc expression locations #1056

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions standard/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -3149,18 +3149,6 @@ stackalloc_element_initializer
;
```

<!-- The following restrictions apply to C# 7.3, they are relaxed in C# 8 -->
A *stackalloc_expression* is only permitted in two contexts:

1. The initializing *expression*, `E`, of a *local_variable_declaration* ([§13.6.2](statements.md#1362-local-variable-declarations)); and
2. The right operand *expression*, `E`, of a simple assignment ([§12.21.2](expressions.md#12212-simple-assignment)) which itself occurs as a *expression_statement* ([§13.7](statements.md#137-expression-statements))

In both contexts the *stackalloc_expression* is only permitted to occur as:

- The whole of `E`; or
- The second and/or third operands of a *conditional_expression* ([§12.18](expressions.md#1218-conditional-operator)) which is itself the whole of `E`.
<!-- End of C# 7.3 restrictions -->

The *unmanaged_type* ([§8.8](types.md#88-unmanaged-types)) indicates the type of the items that will be stored in the newly allocated location, and the *expression* indicates the number of these items. Taken together, these specify the required allocation size. The type of *expression* shall be implicitly convertible to the type `int`.

As the size of a stack allocation cannot be negative, it is a compile-time error to specify the number of items as a *constant_expression* that evaluates to a negative value.
Expand Down
2 changes: 2 additions & 0 deletions standard/unsafe-code.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ In an unsafe context, the set of available implicit conversions ([§10.2](conver

- From any *pointer_type* to the type `void*`.
- From the `null` literal ([§6.4.5.7](lexical-structure.md#6457-the-null-literal)) to any *pointer_type*.
- From any *pointer_type* to the type `System.Span<T>`, where `T` is the referent type of *pointer_type*.
Copy link
Contributor

@KalleOlaviNiemitalo KalleOlaviNiemitalo Mar 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That implicit conversion seems wrong to me. How would it decide the number of elements in the System.Span<T>?

using System;

public class C {
    public unsafe void M() {
        int* pointer = stackalloc int[3];
        
        Span<int> span;
 
        // error CS0029: Cannot implicitly convert type 'int*' to 'System.Span<int>'
        span = pointer;
    }
}

Instead, you should have an implicit conversion only from a stackalloc_expression to a span type.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise error CS0029 here:

Span<int> span = (int*)null;

var deduces a pointer type, though:

public class C {
    public unsafe void M() {
        var a = stackalloc int[3];
        MustBePointer(ref a); // OK
    }
    
    static unsafe void MustBePointer(ref int* p) {}
}

But not when parenthesized:

public class C {
    public unsafe void M() {
        var a = (stackalloc int[3]);
        MustBePointer(ref a); // error CS1503: Argument 1: cannot convert from 'ref System.Span<int>' to 'ref int*'
    }
    
    static unsafe void MustBePointer(ref int* p) {}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This objection looks reasonable - and if we can just change it to an implicit conversion from stackalloc_expression, I think that should be simple. But I don't know the origin of this change, so that could be missing something.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with @KalleOlaviNiemitalo, the conversion from a pointer must be wrong.

An implicit conversion from a stackalloc_expression to Span<T> is not required as that is the type of such an expression in safe code, from §12.8.21:

The result of a stackalloc_expression is an instance of type Span<T>, where T is the unmanaged_type

In unsafe code it can be a pointer or a Span<T>, from §23.9:

In an unsafe context if a stackalloc_expression (§12.8.21) occurs as the initializing expression of a local_variable_declaration (§13.6.2), where the local_variable_type is either a pointer type (§23.3) or inferred (var), then the result of the stackalloc_expression is a pointer of type T * to be beginning of the allocated block, where T is the unmanaged_type of the stackalloc_expression.

In all other respects the semantics of local_variable_declarations (§13.6.2) and stackalloc_expressions (§12.8.21) in unsafe contexts follow those defined for safe contexts.

The added conversion wouldn’t make sense even in unsafe code, unless the resulting span had infinite length (and couldn’t be passed to safe code!)

Just delete this added conversion.


Additionally, in an unsafe context, the set of available explicit conversions ([§10.3](conversions.md#103-explicit-conversions)) is extended to include the following explicit pointer conversions:

Expand All @@ -288,6 +289,7 @@ Finally, in an unsafe context, the set of standard implicit conversions ([§10.4

- From any *pointer_type* to the type `void*`.
- From the `null` literal to any *pointer_type*.
- From any *pointer_type* to the type `System.Span<T>`, where `T` is the referent type of *pointer_type*.

Conversions between two pointer types never change the actual pointer value. In other words, a conversion from one pointer type to another has no effect on the underlying address given by the pointer.

Expand Down