-
Notifications
You must be signed in to change notification settings - Fork 99
[Version 9.0] Feature support for function pointers #1459
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
base: draft-v9
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,15 +1,15 @@ | ||||||
| # 12 Expressions | ||||||
|
|
||||||
| ## 12.1 General | ||||||
|
Check warning on line 3 in standard/expressions.md
|
||||||
|
|
||||||
| An expression is a sequence of operators and operands. This clause defines the syntax, order of evaluation of operands and operators, and meaning of expressions. | ||||||
|
|
||||||
|
Check warning on line 6 in standard/expressions.md
|
||||||
| ## 12.2 Expression classifications | ||||||
|
|
||||||
| ### 12.2.1 General | ||||||
|
|
||||||
| The result of an expression is classified as one of the following: | ||||||
|
|
||||||
| - A value. Every value has an associated type. | ||||||
| - A variable. Unless otherwise specified, a variable is explicitly typed and has an associated type, namely the declared type of the variable. An implicitly typed variable has no associated type. | ||||||
| - A null literal. An expression with this classification can be implicitly converted to a reference type or nullable value type. | ||||||
|
|
@@ -783,10 +783,18 @@ | |||||
|
|
||||||
| If `E` is a method group or implicitly typed anonymous function and `T` is a delegate type or expression tree type then all the parameter types of `T` are *input types of* `E` *with type* `T`. | ||||||
|
|
||||||
| If `E` is an address-of method group and `T` is a function pointer type (§function-pointers) then all the parameter types of `T` are input types of `E` with type `T`. | ||||||
|
|
||||||
| > *Note*: This is only applicable in unsafe code. *end note* | ||||||
|
|
||||||
| #### 12.6.3.5 Output types | ||||||
|
|
||||||
| If `E` is a method group or an anonymous function and `T` is a delegate type or expression tree type then the return type of `T` is an *output type of* `E` *with type* `T`. | ||||||
|
|
||||||
| If `E` is an address-of method group and `T` is a function pointer type (§function-pointers) then the return type of `T` is an output type of `E` with type `T`. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| > *Note*: This is only applicable in unsafe code. *end note* | ||||||
|
|
||||||
| #### 12.6.3.6 Dependence | ||||||
|
|
||||||
| An *unfixed* type variable `Xᵢ` *depends directly on* an *unfixed* type variable `Xₑ` if for some argument `Eᵥ` with type `Tᵥ` `Xₑ` occurs in an *input type* of `Eᵥ` with type `Tᵥ` and `Xᵢ` occurs in an *output type* of `Eᵥ` with type `Tᵥ`. | ||||||
|
|
@@ -812,6 +820,8 @@ | |||||
| - If `E` is a tuple literal with arity `N` and elements `Eᵢ`, and `T` is a tuple type with arity `N` with corresponding element types `Tₑ` or `T` is a nullable value type `T0?` and `T0` is a tuple type with arity `N` that has a corresponding element type `Tₑ`, then for each `Eᵢ` an output type inference is made from `Eᵢ` to `Tₑ`. | ||||||
| - If `E` is an anonymous function with inferred return type `U` ([§12.6.3.14](expressions.md#126314-inferred-return-type)) and `T` is a delegate type or expression tree type with return type `Tₓ`, then a *lower-bound inference* ([§12.6.3.11](expressions.md#126311-lower-bound-inferences)) is made *from* `U` *to* `Tₓ`. | ||||||
| - Otherwise, if `E` is a method group and `T` is a delegate type or expression tree type with parameter types `T₁...Tᵥ` and return type `Tₓ`, and overload resolution of `E` with the types `T₁...Tᵥ` yields a single method with return type `U`, then a *lower-bound inference* is made *from* `U` *to* `Tₓ`. | ||||||
| - If `E` is an address-of method group and `T` is a function pointer type (§function-pointers) then with parameter types `T₁..Tₖ` and return type `Tₓ`, and overload resolution of `E` with the types `T₁..Tₖ` yields a single method with return type `U`, then a *lower-bound inference* is made from `U` to `Tₓ`. | ||||||
| > *Note*: This is only applicable in unsafe code. *end note* | ||||||
| - Otherwise, if `E` is an expression with type `U`, then a *lower-bound inference* is made *from* `U` *to* `T`. | ||||||
| - Otherwise, no inferences are made. | ||||||
|
|
||||||
|
|
@@ -844,15 +854,25 @@ | |||||
| - `V` is an array type `V₁[...]`and `U` is an array type `U₁[...]`of the same rank | ||||||
| - `V` is one of `IEnumerable<V₁>`, `ICollection<V₁>`, `IReadOnlyList<V₁>>`, `IReadOnlyCollection<V₁>` or `IList<V₁>` and `U` is a single-dimensional array type `U₁[]` | ||||||
| - `V` is a constructed `class`, `struct`, `interface` or `delegate` type `C<V₁...Vₑ>` and there is a unique type `C<U₁...Uₑ>` such that `U` (or, if `U` is a type `parameter`, its effective base class or any member of its effective interface set) is identical to, `inherits` from (directly or indirectly), or implements (directly or indirectly) `C<U₁...Uₑ>`. | ||||||
| - `V` is a function pointer type (§function-pointers) `delegate*<V₂..Vₖ, V₁>` and there is a function pointer type `delegate*<U₂..Uₖ, U₁>` such that `U` is identical to `delegate*<U₂..Uₖ, U₁>`, and the calling convention of `V` is identical to `U`, and the refness of `Vᵢ` is identical to `Uᵢ`. | ||||||
| > *Note*: This is only applicable in unsafe code. *end note* | ||||||
| - (The “uniqueness” restriction means that in the case interface `C<T>{} class U: C<X>, C<Y>{}`, then no inference is made when inferring from `U` to `C<T>` because `U₁` could be `X` or `Y`.) | ||||||
|
|
||||||
| If any of these cases apply then an inference is made from each `Uᵢ` to the corresponding `Vᵢ` as follows: | ||||||
| - If `Uᵢ` is not known to be a reference type then an *exact inference* is made | ||||||
| - If `Uᵢ` is not known to be a reference type then an *exact inference* is made; or alternatively, if `U` is not a function pointer type and `Uᵢ` is not known to be a reference type, or if `U` is a function pointer type and `Uᵢ` is not known to be a function pointer type or a reference type, then an exact inference is made | ||||||
| > *Note*: This is only applicable in unsafe code. *end note* | ||||||
| - Otherwise, if `U` is an array type then a *lower-bound inference* is made | ||||||
| - Otherwise, if `V` is `C<V₁...Vₑ>` then inference depends on the `i-th` type parameter of `C`: | ||||||
| - If it is covariant then a *lower-bound inference* is made. | ||||||
| - If it is contravariant then an *upper-bound inference* is made. | ||||||
| - If it is invariant then an *exact inference* is made. | ||||||
| - Otherwise, if `V` is `delegate*<V₂..Vₖ, V₁>` then inference depends on the i-th parameter of `delegate*<V₂..Vₖ, V₁>`: | ||||||
| - If V1: | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand these bullets... Perhaps "For the return type V1" and "For the parameter types V2..VK"? |
||||||
| - If the return is by value, then a lower-bound inference is made. | ||||||
| - If the return is by reference, then an exact inference is made. | ||||||
| - If V2..Vk: | ||||||
| - If the parameter is by value, then an upper-bound inference is made. | ||||||
| - If the parameter is by reference, then an exact inference is made. | ||||||
| > *Note*: This is only applicable in unsafe code. *end note* | ||||||
| - Otherwise, no inferences are made. | ||||||
|
|
||||||
| #### 12.6.3.12 Upper-bound inferences | ||||||
|
|
@@ -865,15 +885,25 @@ | |||||
| - `U` is one of `IEnumerable<Uₑ>`, `ICollection<Uₑ>`, `IReadOnlyList<Uₑ>`, `IReadOnlyCollection<Uₑ>` or `IList<Uₑ>` and `V` is a single-dimensional array type `Vₑ[]` | ||||||
| - `U` is the type `U1?` and `V` is the type `V1?` | ||||||
| - `U` is constructed class, struct, interface or delegate type `C<U₁...Uₑ>` and `V` is a `class, struct, interface` or `delegate` type which is `identical` to, `inherits` from (directly or indirectly), or implements (directly or indirectly) a unique type `C<V₁...Vₑ>` | ||||||
| - `U` is a function pointer type (§function-pointers) then `delegate*<U₂..Uₖ, U₁>` and `V` is a function pointer type which is identical to `delegate*<V₂..Vₖ, V₁>`, and the calling convention of `U` is identical to `V`, and the refness of `Uᵢ` is identical to `Vᵢ`. | ||||||
| > *Note*: This is only applicable in unsafe code. *end note* | ||||||
| - (The “uniqueness” restriction means that given an interface `C<T>{} class V<Z>: C<X<Z>>, C<Y<Z>>{}`, then no inference is made when inferring from `C<U₁>` to `V<Q>`. Inferences are not made from `U₁` to either `X<Q>` or `Y<Q>`.) | ||||||
|
|
||||||
| If any of these cases apply then an inference is made from each `Uᵢ` to the corresponding `Vᵢ` as follows: | ||||||
| - If `Uᵢ` is not known to be a reference type then an *exact inference* is made | ||||||
| - If `U` is not a function pointer type and `Uᵢ` is not known to be a reference type, or if `U` is a function pointer type and `Uᵢ` is not known to be a function pointer type or a reference type, then an *exact inference* is made | ||||||
| > *Note*: Function-pointer type-related text is only applicable in unsafe code. *end note* | ||||||
| - Otherwise, if `V` is an array type then an *upper-bound inference* is made | ||||||
| - Otherwise, if `U` is `C<U₁...Uₑ>` then inference depends on the `i-th` type parameter of `C`: | ||||||
| - If it is covariant then an *upper-bound inference* is made. | ||||||
| - If it is contravariant then a *lower-bound inference* is made. | ||||||
| - If it is invariant then an *exact inference* is made. | ||||||
| - Otherwise, if `U` is `delegate*<U₂..Uₖ, U₁>` then inference depends on the i-th parameter of `delegate*<U₂..Uₖ, U₁>`: | ||||||
| - If `U1`: | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as earlier, I think we can do better than this. |
||||||
| - If the return is by value, then an upper-bound inference is made. | ||||||
| - If the return is by reference, then an exact inference is made. | ||||||
| - If `U2`..`Uk`: | ||||||
| - If the parameter is by value, then a lower-bound inference is made. | ||||||
| - If the parameter is by reference, then an exact inference is made. | ||||||
| > *Note*: This is only applicable in unsafe code. *end note* | ||||||
| - Otherwise, no inferences are made. | ||||||
|
|
||||||
| #### 12.6.3.13 Fixing | ||||||
|
|
@@ -1099,6 +1129,10 @@ | |||||
| - If for at least one parameter `Mᵥ` uses the ***better parameter-passing choice*** ([§12.6.4.4](expressions.md#12644-better-parameter-passing-mode)) than the corresponding parameter in `Mₓ` and none of the parameters in `Mₓ` use the better parameter-passing choice than `Mᵥ`, `Mᵥ` is better than `Mₓ`. | ||||||
| - Otherwise, no function member is better. | ||||||
|
|
||||||
| A `delegate*` is more specific than `void*`. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This feels a little out of place at the moment - is this for parameters, return types, anything else? (If it's parameters, then it should be in the "if Mv has more specific parameter types" bit. |
||||||
|
|
||||||
| > *Note*: This is only applicable in unsafe code, and permits overloading on `void*` and a `delegate*` allowing the `&` operator to distinguish between the two. *end note* | ||||||
|
|
||||||
| #### 12.6.4.4 Better parameter-passing mode | ||||||
|
|
||||||
| It is permitted to have corresponding parameters in two overloaded methods differ only by parameter-passing mode provided one of the two parameters has value-passing mode, as follows: | ||||||
|
|
@@ -1119,6 +1153,8 @@ | |||||
|
|
||||||
| - `E` exactly matches `T₁` and `E` does not exactly match `T₂` ([§12.6.4.6](expressions.md#12646-exactly-matching-expression)) | ||||||
| - `E` exactly matches both or neither of `T₁` and `T₂`, and `T₁` is a better conversion target than `T₂` ([§12.6.4.7](expressions.md#12647-better-conversion-target)) | ||||||
| - `V` is a function pointer type `delegate*<V₂..Vₖ, V₁>` and `U` is a function pointer type `delegate*<U₂..Uₖ, U₁>`, and the calling convention of `V` is identical to `U`, and the refness of `Vᵢ` is identical to `Uᵢ`. | ||||||
| > *Note*: This is only applicable in unsafe code. *end note* | ||||||
| - `E` is a method group ([§12.2](expressions.md#122-expression-classifications)), `T₁` is compatible ([§21.4](delegates.md#214-delegate-compatibility)) with the single best method from the method group for conversion `C₁`, and `T₂` is not compatible with the single best method from the method group for conversion `C₂` | ||||||
|
|
||||||
| #### 12.6.4.6 Exactly matching expression | ||||||
|
|
@@ -1978,6 +2014,25 @@ | |||||
| - Otherwise, if the *invocation_expression* invokes a returns-by-ref method ([§15.6.1](classes.md#1561-general)) or a returns-by-ref delegate, the result is a variable with an associated type of the return type of the method or delegate. If the invocation is of an instance method, and the receiver is of a class type `T`, the associated type is picked from the first declaration or override of the method found when starting with `T` and searching through its base classes. | ||||||
| - Otherwise, the *invocation_expression* invokes a returns-by-value method ([§15.6.1](classes.md#1561-general)) or returns-by-value delegate, and the result is a value, with an associated type of the return type of the method or delegate. If the invocation is of an instance method, and the receiver is of a class type `T`, the associated type is picked from the first declaration or override of the method found when starting with `T` and searching through its base classes. | ||||||
|
|
||||||
| > *Note*: The following is only applicable in unsafe code. *end note* | ||||||
|
|
||||||
| The *method_declaration* [§15.6.1](classes.md#1561-general) for an unmanaged method shall have the attribute `System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute`. The use of this attribute on a method results in the following constraints: | ||||||
|
|
||||||
| - It is an error to directly call that method from C#. Instead, one can obtain a function pointer (§function-pointers) to that method and then invoke the method via that pointer. | ||||||
| - It is an error for that method to have a parameter or return type that is not an `unmanaged_type` ([§8.8](types.md#88-unmanaged-types)). | ||||||
| - It is an error for that method to have type parameters, even if those type parameters are constrained to `unmanaged`. | ||||||
| - It is an error for that method to be in a generic type. | ||||||
| - It is an error to convert that method to a delegate type. | ||||||
|
|
||||||
| For a function pointer invocation, the *primary_expression* of the *invocation_expression* shall be a value of a *funcptr_type*. Furthermore, considering the method being pointed to to be a function member with the same parameter list as the *funcptr_type*, the *funcptr_type* shall be applicable ([§12.6.4.2](expressions.md#12642-applicable-function-member)) with respect to the *argument_list* of the *invocation_expression*. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's try to avoid the stuttering in this part:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
(I think this keeps the same sense without stuttering.) |
||||||
|
|
||||||
| The run-time processing of a function pointer invocation of the form `F(A)`, where `F` is a *primary_expression* of a *funcptr_type* and `A` is an optional *argument_list*, consists of the following steps: | ||||||
|
|
||||||
| - `F` is evaluated. If this evaluation causes an exception, no further steps are executed. | ||||||
| - The argument list `A` is evaluated. If this evaluation causes an exception, no further steps are executed. | ||||||
| - The value of `F` is checked to be valid. If that value is `null`, an implementation-defined exception is thrown, and no further steps are executed. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does "valid" just mean "non-null"? If so, let's change it to say that - otherwise, let's add more detail about what "valid" means. |
||||||
| - Otherwise, `F` points to a method. Function member invocation ([§12.6.6](expressions.md#1266-function-member-invocation)) is performed on the method to which `F` points. | ||||||
|
|
||||||
| #### 12.8.10.2 Method invocations | ||||||
|
|
||||||
| For a method invocation, the *primary_expression* of the *invocation_expression* shall be a method group. The method group identifies the one method to invoke or the set of overloaded methods from which to choose a specific method to invoke. In the latter case, determination of the specific method to invoke is based on the context provided by the types of the arguments in the *argument_list*. | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -604,12 +604,13 @@ A ***contextual keyword*** is an identifier-like sequence of characters that has | |
|
|
||
| ```ANTLR | ||
| contextual_keyword | ||
| : 'add' | 'alias' | 'ascending' | 'async' | 'await' | ||
| | 'by' | 'descending' | 'dynamic' | 'equals' | 'from' | ||
| | 'get' | 'global' | 'group' | 'into' | 'join' | ||
| | 'let' | 'nameof' | 'notnull' | 'on' | 'orderby' | ||
| | 'partial' | 'remove' | 'select' | 'set' | 'unmanaged' | ||
| | 'value' | 'var' | 'when' | 'where' | 'yield' | ||
| : 'add' | 'alias' | 'ascending' | 'async' | 'await' | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note: there's a later comment suggesting that we might not want Fastcall, Stdcall, Thiscall etc... in which case we'd need to edit this bit. |
||
| | 'by' | 'Cdecl' | 'descending' | 'dynamic' | 'equals' | ||
| | 'Fastcall' | 'from'. | 'get' | 'global' | 'group' | ||
| | 'into' | 'join'. | 'let' | 'managed' | 'nameof' | ||
| | 'notnull' | 'on' | 'orderby'. | 'partial' | 'remove' | ||
| | 'select' | 'set' | 'Stdcall'. | 'Thiscall' | 'unmanaged' | ||
| | 'value'. | 'var'. | 'when' | 'where' | 'yield' | ||
| ; | ||
| ``` | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,10 +11,11 @@ This annex collects some information about portability that appears in this spec | |
| The behavior is undefined in the following circumstances: | ||
|
|
||
| 1. The behavior of the enclosing async function when an awaiter’s implementation of the interface methods `INotifyCompletion.OnCompleted` and `ICriticalNotifyCompletion.UnsafeOnCompleted` does not cause the resumption delegate to be invoked at most once ([§12.9.9.4](expressions.md#12994-run-time-evaluation-of-await-expressions)). | ||
| 1. Passing pointers as reference or output parameters ([§24.3](unsafe-code.md#243-pointer-types)). | ||
| 1. Passing pointers as `ref` or `out` parameters (§data-pointers). | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this actually be "passing data pointers" or does it also apply to function pointers? |
||
| 1. When dereferencing the result of converting one pointer type to another and the resulting pointer is not correctly aligned for the pointed-to type. ([§24.5.1](unsafe-code.md#2451-general)). | ||
| 1. When the unary `*` operator is applied to a pointer containing an invalid value ([§24.6.2](unsafe-code.md#2462-pointer-indirection)). | ||
| 1. When a pointer is subscripted to access an out-of-bounds element ([§24.6.4](unsafe-code.md#2464-pointer-element-access)). | ||
| 1. When comparing values of *funcptr_type*s, or `void*` copies thereof ([§24.6.8](unsafe-code.md#2468-pointer-comparison). | ||
| 1. Modifying objects of managed type through fixed pointers ([§24.7](unsafe-code.md#247-the-fixed-statement)). | ||
| 1. The content of memory newly allocated by `stackalloc` ([§12.8.22](expressions.md#12822-stack-allocation)). | ||
| 1. Attempting to allocate a negative number of items using `stackalloc`([§12.8.22](expressions.md#12822-stack-allocation)). | ||
|
|
@@ -43,13 +44,16 @@ A conforming implementation is required to document its choice of behavior in ea | |
| 1. The impact of thread termination when a thread has no handler for an exception, and the thread is itself terminated. ([§13.10.6](statements.md#13106-the-throw-statement)) | ||
| 1. The mechanism by which linkage to an external method is achieved. ([§15.6.8](classes.md#1568-external-methods)) | ||
| 1. The impact of thread termination when no matching `catch` clause is found for an exception and the code that initially started that thread is reached. ([§22.4](exceptions.md#224-how-exceptions-are-handled)). | ||
| 1. The token name mapping and semantics of unmanaged calling conventions beyond those required by this specification, and the set of valid combinations of those tokens (§function-pointers). | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This specification doesn't actually provide any semantics for Stdcall etc as far as I can see... |
||
| 1. An execution environment may provide additional attributes that affect the execution of a C# program. ([§23.5.1](attributes.md#2351-general)) | ||
| 1. The mappings between pointers and integers. ([§24.5.1](unsafe-code.md#2451-general)) | ||
| 1. The effect of applying the unary `*` operator to a `null` pointer. ([§24.6.2](unsafe-code.md#2462-pointer-indirection)) | ||
| 1. The type of exception thrown when the *primary_expression* of an *invocation_expression* is a function pointer with value `null`, and an attempt is made to invoke the (non-existent) pointed-to method ([§12.8.10](expressions.md#12810-invocation-expressions)). | ||
| 1. The behavior when pointer arithmetic overflows the domain of the pointer type. ([§24.6.6](unsafe-code.md#2466-pointer-increment-and-decrement), [§24.6.7](unsafe-code.md#2467-pointer-arithmetic)) | ||
| 1. The result of the `sizeof` operator for non-pre-defined value types. ([§24.6.9](unsafe-code.md#2469-the-sizeof-operator)) | ||
| 1. The behavior of the `fixed` statement if the array expression is `null` or if the array has zero elements. ([§24.7](unsafe-code.md#247-the-fixed-statement)) | ||
| 1. The behavior of the `fixed` statement if the string expression is `null`. ([§24.7](unsafe-code.md#247-the-fixed-statement)) | ||
| 1. The value returned when a stack allocation of size zero is made ([§12.8.22](expressions.md#12822-stack-allocation)). | ||
|
|
||
| ## B.4 Unspecified behavior | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.