Skip to content
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

Add support for function pointers #984

Draft
wants to merge 11 commits into
base: draft-v9
Choose a base branch
from
61 changes: 59 additions & 2 deletions standard/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,11 +212,11 @@

> *Note*: For an example of overloading the `++` and `--` operators see [§15.10.2](classes.md#15102-unary-operators). *end note*

**Operator notation** | **Functional notation**

Check failure on line 215 in standard/expressions.md

View workflow job for this annotation

GitHub Actions / lint

Table pipe style [Expected: leading_and_trailing; Actual: no_leading_or_trailing; Missing leading pipe]

Check failure on line 215 in standard/expressions.md

View workflow job for this annotation

GitHub Actions / lint

Table pipe style [Expected: leading_and_trailing; Actual: no_leading_or_trailing; Missing trailing pipe]
---------------------- | -------------------------

Check failure on line 216 in standard/expressions.md

View workflow job for this annotation

GitHub Actions / lint

Table pipe style [Expected: leading_and_trailing; Actual: no_leading_or_trailing; Missing leading pipe]

Check failure on line 216 in standard/expressions.md

View workflow job for this annotation

GitHub Actions / lint

Table pipe style [Expected: leading_and_trailing; Actual: no_leading_or_trailing; Missing trailing pipe]
`«op» x` | `operator «op»(x)`

Check failure on line 217 in standard/expressions.md

View workflow job for this annotation

GitHub Actions / lint

Table pipe style [Expected: leading_and_trailing; Actual: no_leading_or_trailing; Missing leading pipe]

Check failure on line 217 in standard/expressions.md

View workflow job for this annotation

GitHub Actions / lint

Table pipe style [Expected: leading_and_trailing; Actual: no_leading_or_trailing; Missing trailing pipe]
`x «op»` | `operator «op»(x)`

Check failure on line 218 in standard/expressions.md

View workflow job for this annotation

GitHub Actions / lint

Table pipe style [Expected: leading_and_trailing; Actual: no_leading_or_trailing; Missing leading pipe]

Check failure on line 218 in standard/expressions.md

View workflow job for this annotation

GitHub Actions / lint

Table pipe style [Expected: leading_and_trailing; Actual: no_leading_or_trailing; Missing trailing pipe]
`x «op» y` | `operator «op»(x, y)`

Check failure on line 219 in standard/expressions.md

View workflow job for this annotation

GitHub Actions / lint

Table pipe style [Expected: leading_and_trailing; Actual: no_leading_or_trailing; Missing leading pipe]

Check failure on line 219 in standard/expressions.md

View workflow job for this annotation

GitHub Actions / lint

Table pipe style [Expected: leading_and_trailing; Actual: no_leading_or_trailing; Missing trailing pipe]

User-defined operator declarations always require at least one of the parameters to be of the class or struct type that contains the operator declaration.

Expand Down Expand Up @@ -782,10 +782,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`.

> *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ᵥ`.
Expand All @@ -798,6 +806,8 @@

- If `E` is an anonymous function with inferred return type `U` ([§12.6.3.13](expressions.md#126313-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.10](expressions.md#126310-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 `T1..Tk` and return type `Tb`, and overload resolution of `E` with the types `T1..Tk` yields a single method with return type `U`, then a *lower-bound inference* is made from `U` to `Tb`.
> *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.

Expand Down Expand Up @@ -829,14 +839,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*<V2..Vk, V1>` and there is a function pointer type `delegate*<U2..Uk, U1>` such that `U` is identical to `delegate*<U2..Uk, U1>`, and the calling convention of `V` is identical to `U`, and the refness of `Vi` is identical to `Ui`.
> *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 `Ui` is not known to be a reference type, or if `U` is a function pointer type and `Ui` 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*<V2..Vk, V1>` then inference depends on the i-th parameter of `delegate*<V2..Vk, V1>`:
- If V1:
- 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.11 Upper-bound inferences
Expand All @@ -849,14 +870,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*<U2..Uk, U1>` and `V` is a function pointer type which is identical to `delegate*<V2..Vk, V1>`, and the calling convention of `U` is identical to `V`, and the refness of `Ui` is identical to `Vi`.
> *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 `Ui` is not known to be a reference type, or if `U` is a function pointer type and `Ui` 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*<U2..Uk, U1>` then inference depends on the i-th parameter of `delegate*<U2..Uk, U1>`:
- If `U1`:
- 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.12 Fixing
Expand Down Expand Up @@ -1082,6 +1114,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*`.

> *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:
Expand All @@ -1102,6 +1138,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*<V2..Vk, V1>` and `U` is a function pointer type `delegate*<U2..Uk, U1>`, and the calling convention of `V` is identical to `U`, and the refness of `Vi` is identical to `Ui`.
> *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 ([§20.4](delegates.md#204-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
Expand Down Expand Up @@ -1845,6 +1883,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*.

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.
- 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.9.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*.
Expand Down
8 changes: 5 additions & 3 deletions standard/lexical-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -591,10 +591,12 @@ 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'
| 'by' | 'Cdecl' | 'descending' | 'dynamic' | 'equals'
| 'Fastcall' | 'from'
| 'get' | 'global' | 'group' | 'into' | 'join'
| 'let' | 'nameof' | 'on' | 'orderby' | 'partial'
| 'remove' | 'select' | 'set' | 'unmanaged' | 'value'
| 'let' | 'managed' | 'nameof' | 'on' | 'orderby'
| 'partial' | 'remove' | 'select' | 'set' | 'Stdcall'
| 'Thiscall' | 'unmanaged' | 'value'
| 'var' | 'when' | 'where' | 'yield'
;
```
Expand Down
5 changes: 4 additions & 1 deletion standard/portability-issues.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.8.4](expressions.md#12984-run-time-evaluation-of-await-expressions)).
1. Passing pointers as `ref` or `out` parameters ([§23.3](unsafe-code.md#233-pointer-types)).
1. Passing pointers as `ref` or `out` parameters (§data-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. ([§23.5.1](unsafe-code.md#2351-general)).
1. When the unary `*` operator is applied to a pointer containing an invalid value ([§23.6.2](unsafe-code.md#2362-pointer-indirection)).
1. When a pointer is subscripted to access an out-of-bounds element ([§23.6.4](unsafe-code.md#2364-pointer-element-access)).
1. When comparing values of *funcptr_type*s, or `void*` copies thereof ([§23.6.8](unsafe-code.md#2368-pointer-comparison).
1. Modifying objects of managed type through fixed pointers ([§23.7](unsafe-code.md#237-the-fixed-statement)).
1. The content of memory newly allocated by `stackalloc` ([§12.8.21](expressions.md#12821-stack-allocation)).
1. Attempting to allocate a negative number of items using `stackalloc`([§12.8.21](expressions.md#12821-stack-allocation)).
Expand All @@ -33,8 +34,10 @@ A conforming implementation is required to document its choice of behavior in ea
1. When a `System.ArithmeticException` (or a subclass thereof) is thrown when performing a decimal remainder operation ([§12.10.4](expressions.md#12104-remainder-operator)).
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 impact of thread termination when no matching `catch` clause is found for an exception and the code that initially started that thread is reached. ([§21.4](exceptions.md#214-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).
1. The mappings between pointers and integers ([§23.5.1](unsafe-code.md#2351-general)).
1. The effect of applying the unary `*` operator to a `null` pointer ([§23.6.2](unsafe-code.md#2362-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 (§invocation-expressions).
1. The behavior when pointer arithmetic overflows the domain of the pointer type ([§23.6.6](unsafe-code.md#2366-pointer-increment-and-decrement), [§23.6.7](unsafe-code.md#2367-pointer-arithmetic)).
1. The result of the `sizeof` operator for non-pre-defined value types ([§23.6.9](unsafe-code.md#2369-the-sizeof-operator)).
1. The behavior of the `fixed` statement if the array expression is `null` or if the array has zero elements ([§23.7](unsafe-code.md#237-the-fixed-statement)).
Expand Down
8 changes: 8 additions & 0 deletions standard/standard-library.md
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,14 @@ namespace System.Runtime.CompilerServices
public TResult GetResult();
}

[System.AttributeUsage(System.AttributeTargets.Method, Inherited=false)]
public sealed class UnmanagedCallersOnlyAttribute : Attribute
{
public UnmanagedCallersOnlyAttribute ();
public Type[]? CallConvs;
public string? EntryPoint;
}

public readonly struct ValueTaskAwaiter : ICriticalNotifyCompletion,
INotifyCompletion
{
Expand Down
4 changes: 2 additions & 2 deletions standard/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type
;
```

*pointer_type* ([§23.3](unsafe-code.md#233-pointer-types)) is available only in unsafe code ([§23](unsafe-code.md#23-unsafe-code)).
*pointer_type* (§pointer-types-general) is available only in unsafe code ([§23](unsafe-code.md#23-unsafe-code)).

Value types differ from reference types in that variables of the value types directly contain their data, whereas variables of the reference types store ***references*** to their data, the latter being known as ***objects***. With reference types, it is possible for two variables to reference the same object, and thus possible for operations on one variable to affect the object referenced by the other variable. With value types, the variables each have their own copy of the data, and it is not possible for operations on one to affect the other.

Expand Down Expand Up @@ -71,7 +71,7 @@ delegate_type
;
```

*pointer_type* is available only in unsafe code ([§23.3](unsafe-code.md#233-pointer-types)).
*pointer_type* (§pointer-types-general) is available only in unsafe code ([§23](unsafe-code.md#23-unsafe-code)).

A reference type value is a reference to an ***instance*** of the type, the latter known as an object. The special value `null` is compatible with all reference types and indicates the absence of an instance.

Expand Down
Loading
Loading