dotnet / dotnet/roslyn

Public API proposal: unsafe evolution

Open
#82,791 2 comments 0 reactions 1 assignee Claimed by @jjonescz View on GitHub
api-approved Area-Compilers Concept-API
Dominant language
C#
Stars
20.7k
Forks
4.3k
PR merge metrics
PR metrics pending

Description

## Background and Motivation

Public API for compiler implementation of language feature [unsafe evolution](https://github.com/dotnet/roslyn/pull/82790).
See also [SDK opt-in design doc](https://github.com/dotnet/designs/blob/main/accepted/2025/memory-safety/sdk-memory-safety-enforcement.md).

## Proposed API

```diff
namespace Microsoft.CodeAnalysis
{
+ ///
+ /// Memory safety rules version used by a module. See for more details.
+ ///
+ [Experimental(RoslynExperiments.PreviewLanguageFeatureApi, UrlFormat = "https://github.com/dotnet/roslyn/issues/82789")]
+ public enum MemorySafetyRulesVersion
+ {
+ /// Legacy rules.
+ [Experimental(RoslynExperiments.PreviewLanguageFeatureApi, UrlFormat = "https://github.com/dotnet/roslyn/issues/82789")]
+ Version0 = 0,

+ /// Updated rules introduced with the "unsafe evolution" language feature.
+ [Experimental(RoslynExperiments.PreviewLanguageFeatureApi, UrlFormat = "https://github.com/dotnet/roslyn/issues/82789")]
+ Version2 = 2,
+ }

public interface ISymbol
{
+ ///
+ /// Whether this symbol is considered requires-unsafe, i.e., the symbol requires an context at its use site.
+ /// The value of this property depends on the containing module's .
+ /// Under , symbols with pointers in their signature are considered requires-unsafe.
+ /// Under , symbols marked are considered requires-unsafe.
+ ///
+ [Experimental(RoslynExperiments.PreviewLanguageFeatureApi, UrlFormat = "https://github.com/dotnet/roslyn/issues/82789")]
+ bool RequiresUnsafeContext { get; }
}

public interface IModuleSymbol : ISymbol
{
+ ///
+ /// Which memory safety rules are enabled in this module. Determines which symbols are considered requires-unsafe ().
+ ///
+ [Experimental(RoslynExperiments.PreviewLanguageFeatureApi, UrlFormat = "https://github.com/dotnet/roslyn/issues/82789")]
+ MemorySafetyRulesVersion MemorySafetyRulesVersion { get; }
}
}

namespace Microsoft.CodeAnalysis.CSharp
{
public enum SyntaxKind
{
+ /// Represents .
+ [Experimental("RSEXPERIMENTAL006", UrlFormat = "https://github.com/dotnet/roslyn/issues/82789")]
+ SafeKeyword = 8454,
}

public sealed class CSharpCompilationOptions
{
+ [Experimental(RoslynExperiments.PreviewLanguageFeatureApi, UrlFormat = "https://github.com/dotnet/roslyn/issues/82789")]
+ public MemorySafetyRulesVersion MemorySafetyRulesVersion { get; private set; }

+ [Experimental(RoslynExperiments.PreviewLanguageFeatureApi, UrlFormat = "https://github.com/dotnet/roslyn/issues/82789")]
+ public CSharpCompilationOptions(
+ OutputKind outputKind,
+ bool reportSuppressedDiagnostics = false,
+ string? moduleName = null,
+ string? mainTypeName = null,
+ string? scriptClassName = null,
+ IEnumerable? usings = null,
+ OptimizationLevel optimizationLevel = OptimizationLevel.Debug,
+ bool checkOverflow = false,
+ bool allowUnsafe = false,
+ string? cryptoKeyContainer = null,
+ string? cryptoKeyFile = null,
+ ImmutableArray cryptoPublicKey = default,
+ bool? delaySign = null,
+ Platform platform = Platform.AnyCpu,
+ ReportDiagnostic generalDiagnosticOption = ReportDiagnostic.Default,
+ int warningLevel = Diagnostic.DefaultWarningLevel,
+ IEnumerable>? specificDiagnosticOptions = null,
+ bool concurrentBuild = true,
+ bool deterministic = false,
+ XmlReferenceResolver? xmlReferenceResolver = null,
+ SourceReferenceResolver? sourceReferenceResolver = null,
+ MetadataReferenceResolver? metadataReferenceResolver = null,
+ AssemblyIdentityComparer? assemblyIdentityComparer = null,
+ StrongNameProvider? strongNameProvider = null,
+ bool publicSign = false,
+ MetadataImportOptions metadataImportOptions = MetadataImportOptions.Public,
+ NullableContextOptions nullableContextOptions = NullableContextOptions.Disable,
+ MemorySafetyRulesVersion memorySafetyRulesVersion = MemorySafetyRulesVersion.Version0);

+ // https://github.com/dotnet/roslyn/issues/82789: turn this into non-optional back-compat overload when the above one is out of Experimental phase
public CSharpCompilationOptions(
OutputKind outputKind,
bool reportSuppressedDiagnostics = false,
string? moduleName = null,
string? mainTypeName = null,
string? scriptClassName = null,
IEnumerable? usings = null,
OptimizationLevel optimizationLevel = OptimizationLevel.Debug,
bool checkOverflow = false,
bool allowUnsafe = false,
string? cryptoKeyContainer = null,
string? cryptoKeyFile = null,
ImmutableArray cryptoPublicKey = default,
bool? delaySign = null,
Platform platform = Platform.AnyCpu,
ReportDiagnostic generalDiagnosticOption = ReportDiagnostic.Default,
int warningLevel = Diagnostic.DefaultWarningLevel,
IEnumerable>? specificDiagnosticOptions = null,
bool concurrentBuild = true,
bool deterministic = false,
XmlReferenceResolver? xmlReferenceResolver = null,
SourceReferenceResolver? sourceReferenceResolver = null,
MetadataReferenceResolver? metadataReferenceResolver = null,
AssemblyIdentityComparer? assemblyIdentityComparer = null,
StrongNameProvider? strongNameProvider = null,
bool publicSign = false,
MetadataImportOptions metadataImportOptions = MetadataImportOptions.Public,
NullableContextOptions nullableContextOptions = NullableContextOptions.Disable);

+ [Experimental(RoslynExperiments.PreviewLanguageFeatureApi, UrlFormat = "https://github.com/dotnet/roslyn/issues/82789")]
+ public CSharpCompilationOptions WithMemorySafetyRulesVersion(MemorySafetyRulesVersion version);
}
}
```

Plus we need the following (not a public library API, but a public contract nevertheless):
- a command-line option for `csc.exe`: `-memorysafetyrules:2`,
- a parameter for the `Csc` task and a corresponding MSBuild property: `2`.

## Usage Examples

``` C#
var options = new CSharpCompilationOptions(..., memorySafetyRulesVersion: MemorySafetyRulesVersion.Version0);
options = options.WithMemorySafetyRulesVersion(MemorySafetyRulesVersion.Version2);

var compilation = new CSharpCompilation(..., options);
var m = compilation.GetSymbol("C.M");
var isCallerUnsafe = m.RequiresUnsafeContext;
```

## Alternative Designs

- Turn `ISymbol.RequiresUnsafeVersion` into a default interface member.
- Expose also `CallerUnsafeMode.Implicit` / `CallerUnsafeMode.Explicit` granularity which is available internally to the compiler.
- Only expose `Csc.MemorySafetyRules` but not `/memorysafetyrules:` (pass it via `Features` like `InterceptorsNamespaces`).

## Risks

- Should the csc/msbuild options also have Version suffix?
- We cannot mark the command-line option and Csc task parameter as Experimental. Perhaps we should post-pone implementing them until the feature is close to being released.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.