From b5723c59a3940641d080b6581cb8976375da2514 Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Tue, 28 Jul 2026 13:50:10 +0200 Subject: [PATCH 1/3] Unsafe evolution speclet: add more restrictions for `safe` --- proposals/unsafe-evolution.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/proposals/unsafe-evolution.md b/proposals/unsafe-evolution.md index e144a8e2fd..3bac73baad 100644 --- a/proposals/unsafe-evolution.md +++ b/proposals/unsafe-evolution.md @@ -88,7 +88,8 @@ we are trying to make it so that anything you are required to do when you are op The `safe` modifier can be applied to all declarations which allow `unsafe` to mark them as *requires-unsafe*. It is disallowed to apply both the `safe` and `unsafe` modifier on the same declaration. -Like `unsafe` modifier, the `safe` modifier must match on both parts of partial declarations. +Other restrictions that apply to `unsafe` and `safe` modifiers are specified in section [Unsafe modifiers and contexts](#unsafe-modifiers-and-contexts). + The compiler requires an explicit `safe` or `unsafe` modifier on [`extern` members](#extern) and [fields in explicit layout](#fields). Allowing `safe` even on declarations where it is not required (and hence has no effect for the compiler) is motivated by source generators, e.g., [LibraryImport](#answered-allow-safe-on-non-extern-members-libraryimport). @@ -280,13 +281,13 @@ i.e., an `unsafe` constructor may call a *requires-unsafe* `base` or `this` cons Types with parameterless *requires-unsafe* constructors do not satisfy the `new()` constraint. Similarly and in addition, structs with parameterless *requires-unsafe* constructors do not satisfy the `struct` constraint. -`unsafe` on a member is _not_ applied to any nested anonymous or local functions inside the member. +`unsafe`/`safe` on a member is _not_ applied to any nested anonymous or local functions inside the member. The same goes for anonymous and local functions declared inside of an `unsafe` block (they are still in an `unsafe` context as always, but they don't become *requires-unsafe*). To mark a local function as *requires-unsafe*, it must manually be marked as `unsafe`. Lambdas cannot be marked *requires-unsafe* (the `unsafe` keyword is disallowed on them). -When a member is `partial`, both parts must agree on the `unsafe` modifier, unchanged from C# rules today. +When a member is `partial`, both parts must agree on the `unsafe`/`safe` modifier, unchanged from C# rules today. ```cs partial class C1 @@ -302,7 +303,12 @@ partial class C1 } ``` -For properties, `get` and `set/init` accessors can be independently declared as `unsafe`; marking the entire property as `unsafe` means that both the `get` and `set/init` accessors are unsafe. +For properties, `get` and `set`/`init` accessors can be independently declared as `unsafe`/`safe`. +If the accessors don't have the `unsafe`/`safe` modifier, they inherit it from the property. +Similar to `readonly` modifier, we introduce the following restrictions for properties and accessors: +- `unsafe`/`safe` modifiers can be applied either to the property or its accessors, not both. +- It is an error to apply the same `unsafe`/`safe` modifier to all of the property accessors (that modifier should be applied to the property instead). + It is currently not possible to place any modifiers on event accessors, and this proposal doesn't change that, i.e., `add` and `remove` event accessors cannot be independently declared as `unsafe`. Only if the entire event is marked as `unsafe`, it means that the accessors are unsafe; otherwise they are safe. @@ -326,9 +332,7 @@ It is an error to apply the `MemorySafetyRulesAttribute` or `RequiresUnsafeAttri The compiler ignores `RequiresUnsafeAttribute`-marked members from assemblies that are using the legacy memory safety rules (instead, the [compat mode](#compat-mode) is used there). -When a non-type member is marked as `unsafe`, the compiler will synthesize a `RequiresUnsafeAttribute` application on the member in metadata. -When a user-facing *requires-unsafe* member generates hidden members, such as an auto-property's get/set methods, -both the user-facing member and any hidden members generated by that user-facing member are all *requires-unsafe*, and `RequiresUnsafeAttribute` is applied to all of them. +When a non-type member is *requires-unsafe*, the compiler will synthesize a `RequiresUnsafeAttribute` application on the member in metadata. The `MemorySafetyRulesAttribute` and `RequiresUnsafeAttribute` definition is synthesized by the compiler if necessary per standard well-known member rules. From fc2e8004e83cb05df7b1d3d63949a3500cc9d6d0 Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Wed, 29 Jul 2026 09:45:57 +0200 Subject: [PATCH 2/3] Add open question about synthesized members --- proposals/unsafe-evolution.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/proposals/unsafe-evolution.md b/proposals/unsafe-evolution.md index 3bac73baad..8bd3366ed5 100644 --- a/proposals/unsafe-evolution.md +++ b/proposals/unsafe-evolution.md @@ -845,6 +845,16 @@ Otherwise, the user would be forced to expand these auto-declarations into manua What about a primary constructor parameter which gets a backing field? Both `safe` and `unsafe` modifier is currently disallowed on a parameter declaration. +### Synthesized members + +When a user-declared member is marked as *requires-unsafe*, should the associated compiler-synthesized members +that are unspeakable but could be called via reflection (like `MoveNext` for iterators) +be also marked with `[RequiresUnsafe]` attribute in metadata? + +Note that this does not apply to property and event accessors +where we already exactly [specify](#unsafe-modifiers-and-contexts) when they are *requires-unsafe* +and hence they get the `[RequiresUnsafe]` attribute in metadata correspondingly. + ### `[Out]` and `[SkipLocalsInit]` Since for example VB doesn't guarantee that `[Out]` parameters are initialized, in combination with `[SkipLocalsInit]`, calling such parameters could be considered `unsafe` in C#. From 5970dc3624023f7d7e05ada645fb63ae99258ffe Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Wed, 29 Jul 2026 12:19:58 +0200 Subject: [PATCH 3/3] Delete question that got answered by this PR --- proposals/unsafe-evolution.md | 39 ----------------------------------- 1 file changed, 39 deletions(-) diff --git a/proposals/unsafe-evolution.md b/proposals/unsafe-evolution.md index 8bd3366ed5..533e42402d 100644 --- a/proposals/unsafe-evolution.md +++ b/proposals/unsafe-evolution.md @@ -655,45 +655,6 @@ We could consider not automatically making the entire lexical scope of an `unsaf Answer: `unsafe` on a type is an error (can be revisited based on feedback) under the updated rules. -### `unsafe` on accessors - -We newly allow `unsafe` on property accessors but not on event accessors, in line with other pre-existing modifiers. -This also means that `unsafe` on a property is just a shortcut for `unsafe` on its accessors. -But at the same time, `partial`s require the `unsafe` modifiers to match: - -```cs -partial class C -{ - unsafe partial int P { get; set; } // effectively both `get` and `set` are `unsafe` here - unsafe partial int P { unsafe get => 0; set { } } // still an error: `unsafe` on `get` doesn't match -} - -// similar to this pre-existing behavior: -unsafe partial class D -{ - unsafe partial void M(); -} -unsafe partial class D -{ - partial void M() { } // error about missing `unsafe` -} -``` - -Maybe it should behave similarly to `readonly`, i.e., disallow `unsafe` on both the property and its accessor at the same time: - -```cs -partial struct S -{ - readonly partial int P { get; set; } - - // error: Both partial member declarations must be readonly or neither may be readonly - partial int P { readonly get => 0; set { } } - - // error: Cannot specify 'readonly' modifiers on both property or indexer 'S.P2' and its accessor. Remove one of them. - readonly int P2 { readonly get => 0; set { } } -} -``` - ### (answered) Allow suppressing *requires-unsafe* errors in edge case scenarios How should we allow suppressing *requires-unsafe* errors in the following scenarios?