From 73c3283efaeab4382768f7fda89198d9a492b35c Mon Sep 17 00:00:00 2001 From: Sebastien Lebreton Date: Sat, 18 Jul 2026 22:59:40 +0200 Subject: [PATCH 1/2] Model scoped parameter lifetimes ScopedRefAttribute only records explicit syntax. Effective lifetime also depends on UnscopedRefAttribute, params collections, out parameters, and the defining module's RefSafetyRules version. Model those distinctions in the type system without changing decompiler output. Assisted-by: Copilot:gpt-5.6-sol:GitHub Copilot CLI Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 86d2918e-5a24-48b4-9a86-41d331ec3720 --- .../TypeSystem/TypeSystemLoaderTests.cs | 81 +++++++++++++++++++ .../TypeSystem/TypeSystemTestCase.cs | 29 +++++++ .../TypeSystem/IParameter.cs | 43 +++++++++- .../Implementation/KnownAttributes.cs | 9 ++- .../Implementation/MetadataParameter.cs | 30 ++++--- .../TypeSystem/MetadataModule.cs | 38 +++++++++ .../TypeSystem/TypeSystemExtensions.cs | 59 ++++++++++++++ 7 files changed, 275 insertions(+), 14 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemLoaderTests.cs b/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemLoaderTests.cs index e98991a37..1554bb069 100644 --- a/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemLoaderTests.cs +++ b/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemLoaderTests.cs @@ -196,6 +196,87 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem Assert.That(m1.Parameters[0].GetAttributes().Count(), Is.EqualTo(0)); } + [Test] + public void LifetimeAnnotations() + { + ITypeDefinition type = GetTypeDefinition(typeof(LifetimeAnnotationTests)); + + IParameter explicitScopedRef = type.Methods.Single(m => m.Name == "ExplicitScopedRef").Parameters.Single(); + Assert.That(explicitScopedRef.Lifetime.DeclaredScope, Is.EqualTo(ScopedKind.ScopedRef)); + Assert.That(explicitScopedRef.GetEffectiveScope(), Is.EqualTo(ScopedKind.ScopedRef)); + Assert.That(explicitScopedRef.Lifetime.HasUnscopedRefAttribute, Is.False); + Assert.That(explicitScopedRef.Lifetime.ScopedRef, Is.True); + + IParameter explicitScopedValue = type.Methods.Single(m => m.Name == "ExplicitScopedValue").Parameters.Single(); + Assert.That(explicitScopedValue.Lifetime.DeclaredScope, Is.EqualTo(ScopedKind.ScopedValue)); + Assert.That(explicitScopedValue.GetEffectiveScope(), Is.EqualTo(ScopedKind.ScopedValue)); + Assert.That(explicitScopedValue.Lifetime.HasUnscopedRefAttribute, Is.False); + Assert.That(explicitScopedValue.Lifetime.ScopedRef, Is.True); + + IParameter implicitScopedRef = type.Methods.Single(m => m.Name == "ImplicitScopedRef").Parameters.Single(); + Assert.That(implicitScopedRef.Lifetime.DeclaredScope, Is.EqualTo(ScopedKind.None)); + Assert.That(implicitScopedRef.GetEffectiveScope(), Is.EqualTo(ScopedKind.ScopedRef)); + Assert.That(implicitScopedRef.Lifetime.HasUnscopedRefAttribute, Is.False); + Assert.That(implicitScopedRef.Lifetime.ScopedRef, Is.False); + + IParameter unscopedRef = type.Methods.Single(m => m.Name == "UnscopedRef").Parameters.Single(); + Assert.That(unscopedRef.Lifetime.DeclaredScope, Is.EqualTo(ScopedKind.None)); + Assert.That(unscopedRef.GetEffectiveScope(), Is.EqualTo(ScopedKind.None)); + Assert.That(unscopedRef.Lifetime.HasUnscopedRefAttribute, Is.True); + Assert.That(unscopedRef.GetAttributes().Any(a => + a.AttributeType.FullName == "System.Diagnostics.CodeAnalysis.UnscopedRefAttribute"), Is.True); + + var paramsRefStruct = new DefaultParameter(type, "values", isParams: true); + Assert.That(paramsRefStruct.GetEffectiveScope(), Is.EqualTo(ScopedKind.ScopedValue)); + + IMethod instanceMethod = type.Methods.Single(m => m.Name == "InstanceMethod"); + Assert.That(instanceMethod.GetThisParameterScope(), Is.EqualTo(ScopedKind.ScopedRef)); + Assert.That(instanceMethod.HasAttribute(KnownAttribute.UnscopedRef), Is.False); + + IMethod unscopedMethod = type.Methods.Single(m => m.Name == "UnscopedMethod"); + Assert.That(unscopedMethod.GetThisParameterScope(), Is.EqualTo(ScopedKind.None)); + Assert.That(unscopedMethod.HasAttribute(KnownAttribute.UnscopedRef), Is.True); + + IProperty unscopedProperty = type.Properties.Single(p => p.Name == "UnscopedProperty"); + Assert.That(unscopedProperty.Getter.GetThisParameterScope(), Is.EqualTo(ScopedKind.None)); + Assert.That(unscopedProperty.HasAttribute(KnownAttribute.UnscopedRef), Is.True); + + IProperty accessorUnscopedProperty = type.Properties.Single(p => p.Name == "AccessorUnscopedProperty"); + Assert.That(accessorUnscopedProperty.Getter.GetThisParameterScope(), Is.EqualTo(ScopedKind.None)); + Assert.That(accessorUnscopedProperty.Getter.HasAttribute(KnownAttribute.UnscopedRef), Is.True); + + IMethod staticMethod = type.Methods.Single(m => m.Name == "ExplicitScopedRef"); + Assert.That(staticMethod.GetThisParameterScope(), Is.EqualTo(ScopedKind.None)); + + IMethod classMethod = GetTypeDefinition(typeof(SimplePublicClass)).Methods.Single(m => m.Name == "Method"); + Assert.That(classMethod.GetThisParameterScope(), Is.EqualTo(ScopedKind.None)); + + // The legacy mscorlib fixture has no RefSafetyRulesAttribute, so out is not implicitly scoped. + ITypeDefinition int32 = compilation.FindType(KnownTypeCode.Int32).GetDefinition(); + IMethod legacyTryParse = int32.Methods.Single(m => m.Name == "TryParse" + && m.Parameters.Count == 2 + && m.Parameters[1].ReferenceKind == ReferenceKind.Out); + Assert.That(legacyTryParse.Parameters[1].GetEffectiveScope(), Is.EqualTo(ScopedKind.None)); + } + + [Test] + public void LifetimeAnnotationsCanBeDisabled() + { + var compilationWithoutLifetimeAnnotations = new SimpleCompilation( + TestAssembly.WithOptions(TypeSystemOptions.Default & ~TypeSystemOptions.ScopedRef), + Mscorlib.WithOptions(TypeSystemOptions.Default | TypeSystemOptions.OnlyPublicAPI)); + ITypeDefinition type = compilationWithoutLifetimeAnnotations.FindType(typeof(LifetimeAnnotationTests)).GetDefinition(); + + IParameter explicitScopedRef = type.Methods.Single(m => m.Name == "ExplicitScopedRef").Parameters.Single(); + Assert.That(explicitScopedRef.Lifetime.DeclaredScope, Is.EqualTo(ScopedKind.None)); + Assert.That(explicitScopedRef.GetEffectiveScope(), Is.EqualTo(ScopedKind.None)); + Assert.That(explicitScopedRef.GetAttributes().Any(a => + a.AttributeType.FullName == "System.Runtime.CompilerServices.ScopedRefAttribute"), Is.True); + + IMethod instanceMethod = type.Methods.Single(m => m.Name == "InstanceMethod"); + Assert.That(instanceMethod.GetThisParameterScope(), Is.EqualTo(ScopedKind.None)); + } + [Test] public void AssemblyAttribute() { diff --git a/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemTestCase.cs b/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemTestCase.cs index 14941b585..65136e895 100644 --- a/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemTestCase.cs +++ b/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemTestCase.cs @@ -19,6 +19,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -220,6 +221,34 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem public void VarArgsMethod(__arglist) { } } + public ref struct LifetimeAnnotationTests + { + private int value; + + public static void ExplicitScopedRef(scoped ref int value) { } + public static void ExplicitScopedValue(scoped LifetimeAnnotationTests value) { } + public static void ImplicitScopedRef(out int value) { value = 0; } + public static void UnscopedRef([UnscopedRef] out int value) { value = 0; } + + public void InstanceMethod() { } + + [UnscopedRef] + public ref int UnscopedMethod() + { + return ref value; + } + + [UnscopedRef] + public ref int UnscopedProperty => ref value; + + public ref int AccessorUnscopedProperty { + [UnscopedRef] + get { + return ref value; + } + } + } + public class VarArgsCtor { public VarArgsCtor(__arglist) { } diff --git a/ICSharpCode.Decompiler/TypeSystem/IParameter.cs b/ICSharpCode.Decompiler/TypeSystem/IParameter.cs index 8ab231679..441da2e03 100644 --- a/ICSharpCode.Decompiler/TypeSystem/IParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/IParameter.cs @@ -36,16 +36,46 @@ namespace ICSharpCode.Decompiler.TypeSystem RefReadOnly, } + /// + /// Describes which part of a parameter or local is restricted to the current scope. + /// + public enum ScopedKind : byte + { + None, + ScopedRef, + ScopedValue, + } + public struct LifetimeAnnotation { /// - /// C# 11 scoped annotation: "scoped ref" (ScopedRefAttribute) + /// Gets or sets the scope explicitly encoded by ScopedRefAttribute. + /// Implicit and redundant scopedness is excluded. /// - public bool ScopedRef { + public ScopedKind DeclaredScope { + get { +#pragma warning disable 618 + if (ValueScoped) + return ScopedKind.ScopedValue; + if (RefScoped) + return ScopedKind.ScopedRef; +#pragma warning restore 618 + return ScopedKind.None; + } + set { #pragma warning disable 618 - get { return RefScoped; } - set { RefScoped = value; } + RefScoped = value != ScopedKind.None; + ValueScoped = value == ScopedKind.ScopedValue; #pragma warning restore 618 + } + } + + /// + /// C# 11 scoped annotation: "scoped ref" (ScopedRefAttribute) + /// + public bool ScopedRef { + get { return DeclaredScope != ScopedKind.None; } + set { DeclaredScope = value ? ScopedKind.ScopedRef : ScopedKind.None; } } [Obsolete("Use ScopedRef property instead of directly accessing this field")] @@ -53,6 +83,11 @@ namespace ICSharpCode.Decompiler.TypeSystem [Obsolete("C# 11 preview: \"ref scoped\" no longer supported")] public bool ValueScoped; + + /// + /// Gets or sets whether UnscopedRefAttribute is present. + /// + public bool HasUnscopedRefAttribute { get; set; } } public interface IParameter : IVariable diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs index 52a20c337..d34609633 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs @@ -120,11 +120,15 @@ namespace ICSharpCode.Decompiler.TypeSystem // C# 14 attributes: ExtensionMarker, + + // Lifetime attributes: + UnscopedRef, + RefSafetyRules, } public static class KnownAttributes { - internal const int Count = (int)KnownAttribute.ExtensionMarker + 1; + internal const int Count = (int)KnownAttribute.RefSafetyRules + 1; static readonly TopLevelTypeName[] typeNames = new TopLevelTypeName[Count]{ default, @@ -200,6 +204,9 @@ namespace ICSharpCode.Decompiler.TypeSystem new TopLevelTypeName("System.Runtime.CompilerServices", "InlineArrayAttribute"), // C# 14 attributes: new TopLevelTypeName("System.Runtime.CompilerServices", "ExtensionMarkerAttribute"), + // Lifetime attributes: + new TopLevelTypeName("System.Diagnostics.CodeAnalysis", "UnscopedRefAttribute"), + new TopLevelTypeName("System.Runtime.CompilerServices", "RefSafetyRulesAttribute"), }; public static ref readonly TopLevelTypeName GetTypeName(this KnownAttribute attr) diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs index c62438cb9..238c9ed2e 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs @@ -125,17 +125,29 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation var metadata = module.metadata; var parameterDef = metadata.GetParameter(handle); - if ((module.TypeSystemOptions & TypeSystemOptions.ParamsCollections) != 0 - && parameterDef.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.ParamCollection)) + var attributes = parameterDef.GetCustomAttributes(); + bool hasUnscopedRefAttribute = attributes.HasKnownAttribute(metadata, KnownAttribute.UnscopedRef); + bool isParamsCollection = (module.TypeSystemOptions & TypeSystemOptions.ParamsCollections) != 0 + && attributes.HasKnownAttribute(metadata, KnownAttribute.ParamCollection); + ScopedKind declaredScope = ScopedKind.None; + if (!hasUnscopedRefAttribute + && !isParamsCollection + && attributes.HasKnownAttribute(metadata, KnownAttribute.ScopedRef)) { - // params collections are implicitly scoped - return default; - } - if (parameterDef.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.ScopedRef)) - { - return new LifetimeAnnotation { ScopedRef = true }; + if (ReferenceKind != ReferenceKind.None) + { + declaredScope = ScopedKind.ScopedRef; + } + else if (Type.IsRefLikeOrAllowsRefLikeType()) + { + declaredScope = ScopedKind.ScopedValue; + } } - return default; + + return new LifetimeAnnotation { + DeclaredScope = declaredScope, + HasUnscopedRefAttribute = hasUnscopedRefAttribute + }; } } diff --git a/ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs b/ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs index 9701bba08..0002b5d0a 100644 --- a/ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs +++ b/ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs @@ -91,6 +91,7 @@ namespace ICSharpCode.Decompiler.TypeSystem var customAttrs = metadata.GetModuleDefinition().GetCustomAttributes(); this.NullableContext = customAttrs.GetNullableContext(metadata) ?? Nullability.Oblivious; this.minAccessibilityForNRT = FindMinimumAccessibilityForNRT(metadata, customAttrs); + this.UseUpdatedEscapeRules = HasRefSafetyRulesVersion(customAttrs, 11); this.rootNamespace = new MetadataNamespace(this, null, string.Empty, metadata.GetNamespaceDefinitionRoot()); if (!options.HasFlag(TypeSystemOptions.Uncached)) @@ -112,6 +113,43 @@ namespace ICSharpCode.Decompiler.TypeSystem public TypeSystemOptions TypeSystemOptions => options; + /// + /// Gets whether the module declares the C# 11 ref-safety rules. + /// + internal bool UseUpdatedEscapeRules { get; } + + bool HasRefSafetyRulesVersion(CustomAttributeHandleCollection attributes, int expectedVersion) + { + foreach (var attributeHandle in attributes) + { + var attribute = metadata.GetCustomAttribute(attributeHandle); + if (!attribute.IsKnownAttribute(metadata, KnownAttribute.RefSafetyRules)) + continue; + + CustomAttributeValue value; + try + { + value = attribute.DecodeValue(Metadata.MetadataExtensions.MinimalAttributeTypeProvider); + } + catch (BadImageFormatException) + { + continue; + } + catch (Metadata.EnumUnderlyingTypeResolveException) + { + continue; + } + if (value.FixedArguments.Length == 1 + && value.FixedArguments[0].Value is int version) + { + // Roslyn recognizes exactly version 11; missing and unknown versions use legacy rules. + return version == expectedVersion; + } + } + + return false; + } + #region IModule interface public MetadataFile MetadataFile { get; } diff --git a/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs b/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs index 08009fa84..898977377 100644 --- a/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs +++ b/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs @@ -692,6 +692,65 @@ namespace ICSharpCode.Decompiler.TypeSystem } #endregion + #region Lifetime annotations + internal static bool IsRefLikeOrAllowsRefLikeType(this IType type) + { + return type.IsByRefLike + || type is ITypeParameter { AllowsRefLikeType: true }; + } + + /// + /// Gets the parameter's effective scope after applying implicit scope rules and + /// UnscopedRefAttribute. + /// + internal static ScopedKind GetEffectiveScope(this IParameter parameter) + { + LifetimeAnnotation lifetime = parameter.Lifetime; + if (lifetime.HasUnscopedRefAttribute) + return ScopedKind.None; + + if (lifetime.DeclaredScope != ScopedKind.None) + return lifetime.DeclaredScope; + + if (parameter.Owner?.ParentModule is MetadataModule module) + { + if ((module.TypeSystemOptions & TypeSystemOptions.ScopedRef) == 0) + return ScopedKind.None; + + if (module.UseUpdatedEscapeRules && parameter.ReferenceKind == ReferenceKind.Out) + return ScopedKind.ScopedRef; + } + + if (parameter.IsParams && parameter.Type.IsRefLikeOrAllowsRefLikeType()) + return ScopedKind.ScopedValue; + + return ScopedKind.None; + } + + /// + /// Gets the effective scope of the implicit this parameter. + /// + internal static ScopedKind GetThisParameterScope(this IMethod method) + { + if (method.IsStatic || method.DeclaringTypeDefinition?.Kind != TypeKind.Struct) + return ScopedKind.None; + + if (method.ParentModule is MetadataModule module) + { + if ((module.TypeSystemOptions & TypeSystemOptions.ScopedRef) == 0) + return ScopedKind.None; + + bool hasUnscopedRefAttribute = method.HasAttribute(KnownAttribute.UnscopedRef) + || method.AccessorOwner is IProperty property + && property.HasAttribute(KnownAttribute.UnscopedRef); + if (hasUnscopedRefAttribute && module.UseUpdatedEscapeRules) + return ScopedKind.None; + } + + return ScopedKind.ScopedRef; + } + #endregion + #region IParameter.IsDefaultValueAssignmentAllowed /// /// Checks if the parameter is allowed to be assigned a default value. From f7cf85e263679d1b20276a6403812d5057a70e80 Mon Sep 17 00:00:00 2001 From: Sebastien Lebreton Date: Sun, 19 Jul 2026 18:13:17 +0200 Subject: [PATCH 2/2] Remove obsolete lifetime annotation fields ScopedKind is now the authoritative lifetime representation, so retaining the preview-era boolean fields would duplicate state. Keep the current ScopedRef compatibility property and group the new metadata attributes with the other C# 11 attributes. Assisted-by: Copilot:gpt-5.6-sol:GitHub Copilot CLI Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 86d2918e-5a24-48b4-9a86-41d331ec3720 --- .../TypeSystem/IParameter.cs | 25 +------------------ .../Implementation/KnownAttributes.cs | 13 ++++------ 2 files changed, 6 insertions(+), 32 deletions(-) diff --git a/ICSharpCode.Decompiler/TypeSystem/IParameter.cs b/ICSharpCode.Decompiler/TypeSystem/IParameter.cs index 441da2e03..7937affc4 100644 --- a/ICSharpCode.Decompiler/TypeSystem/IParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/IParameter.cs @@ -18,7 +18,6 @@ #nullable enable -using System; using System.Collections.Generic; using System.Runtime.CompilerServices; @@ -52,23 +51,7 @@ namespace ICSharpCode.Decompiler.TypeSystem /// Gets or sets the scope explicitly encoded by ScopedRefAttribute. /// Implicit and redundant scopedness is excluded. /// - public ScopedKind DeclaredScope { - get { -#pragma warning disable 618 - if (ValueScoped) - return ScopedKind.ScopedValue; - if (RefScoped) - return ScopedKind.ScopedRef; -#pragma warning restore 618 - return ScopedKind.None; - } - set { -#pragma warning disable 618 - RefScoped = value != ScopedKind.None; - ValueScoped = value == ScopedKind.ScopedValue; -#pragma warning restore 618 - } - } + public ScopedKind DeclaredScope { get; set; } /// /// C# 11 scoped annotation: "scoped ref" (ScopedRefAttribute) @@ -78,12 +61,6 @@ namespace ICSharpCode.Decompiler.TypeSystem set { DeclaredScope = value ? ScopedKind.ScopedRef : ScopedKind.None; } } - [Obsolete("Use ScopedRef property instead of directly accessing this field")] - public bool RefScoped; - - [Obsolete("C# 11 preview: \"ref scoped\" no longer supported")] - public bool ValueScoped; - /// /// Gets or sets whether UnscopedRefAttribute is present. /// diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs index d34609633..5f3184493 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs @@ -114,21 +114,19 @@ namespace ICSharpCode.Decompiler.TypeSystem // C# 11 attributes: Required, + UnscopedRef, + RefSafetyRules, // C# 12 attributes: InlineArray, // C# 14 attributes: ExtensionMarker, - - // Lifetime attributes: - UnscopedRef, - RefSafetyRules, } public static class KnownAttributes { - internal const int Count = (int)KnownAttribute.RefSafetyRules + 1; + internal const int Count = (int)KnownAttribute.ExtensionMarker + 1; static readonly TopLevelTypeName[] typeNames = new TopLevelTypeName[Count]{ default, @@ -200,13 +198,12 @@ namespace ICSharpCode.Decompiler.TypeSystem new TopLevelTypeName("System.Runtime.InteropServices", "UnmanagedCallersOnlyAttribute"), // C# 11 attributes: new TopLevelTypeName("System.Runtime.CompilerServices", "RequiredMemberAttribute"), + new TopLevelTypeName("System.Diagnostics.CodeAnalysis", "UnscopedRefAttribute"), + new TopLevelTypeName("System.Runtime.CompilerServices", "RefSafetyRulesAttribute"), // C# 12 attributes: new TopLevelTypeName("System.Runtime.CompilerServices", "InlineArrayAttribute"), // C# 14 attributes: new TopLevelTypeName("System.Runtime.CompilerServices", "ExtensionMarkerAttribute"), - // Lifetime attributes: - new TopLevelTypeName("System.Diagnostics.CodeAnalysis", "UnscopedRefAttribute"), - new TopLevelTypeName("System.Runtime.CompilerServices", "RefSafetyRulesAttribute"), }; public static ref readonly TopLevelTypeName GetTypeName(this KnownAttribute attr)