Public API proposal: unsafe evolution
- 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
Assessment
This issue has not been assessed yet.