diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionSymbol.cs index d91f7b796f55a..2033f3f09e742 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionSymbol.cs @@ -126,7 +126,14 @@ internal void GetDeclarationDiagnostics(BindingDiagnosticBag addTo) GetReturnTypeAttributes(); var compilation = DeclaringCompilation; - if (IsCallerUnsafe) compilation.EnsureRequiresUnsafeAttributeExists(addTo, GetFirstLocation(), modifyCompilation: false); + + if (IsCallerUnsafe) + { + var location = Syntax.Identifier.GetLocation(); + MessageID.IDS_FeatureUnsafeEvolution.CheckFeatureAvailability(addTo, compilation, location); + compilation.EnsureRequiresUnsafeAttributeExists(addTo, location, modifyCompilation: false); + } + ParameterHelpers.EnsureRefKindAttributesExist(compilation, Parameters, addTo, modifyCompilation: false); ParameterHelpers.EnsureParamCollectionAttributeExists(compilation, Parameters, addTo, modifyCompilation: false); ParameterHelpers.EnsureNativeIntegerAttributeExists(compilation, Parameters, addTo, modifyCompilation: false); diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberMethodSymbol.cs index 0e65f8243e795..9ced715c312e7 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberMethodSymbol.cs @@ -984,6 +984,7 @@ internal override void AfterAddingTypeMembersChecks(ConversionsBase conversions, if (IsCallerUnsafe) { + MessageID.IDS_FeatureUnsafeEvolution.CheckFeatureAvailability(diagnostics, compilation, _location); compilation.EnsureRequiresUnsafeAttributeExists(diagnostics, _location, modifyCompilation: true); } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs index 40a6042532786..f9f004f487742 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs @@ -1045,6 +1045,7 @@ internal override void AfterAddingTypeMembersChecks(ConversionsBase conversions, if (IsCallerUnsafe) { + MessageID.IDS_FeatureUnsafeEvolution.CheckFeatureAvailability(diagnostics, compilation, location); compilation.EnsureRequiresUnsafeAttributeExists(diagnostics, location, modifyCompilation: true); } diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/UnsafeEvolutionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/UnsafeEvolutionTests.cs index 114eed578a495..2d5223385c442 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/UnsafeEvolutionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/UnsafeEvolutionTests.cs @@ -23,7 +23,7 @@ private void CompileAndVerify( string caller, object[] expectedUnsafeSymbols, object[] expectedSafeSymbols, - params DiagnosticDescription[] expectedDiagnostics) + DiagnosticDescription[] expectedDiagnostics) { CreateCompilation([lib, caller], options: TestOptions.UnsafeReleaseExe.WithUpdatedMemorySafetyRules()) @@ -43,10 +43,19 @@ private void CompileAndVerify( options: TestOptions.UnsafeReleaseExe.WithUpdatedMemorySafetyRules()) .VerifyDiagnostics(expectedDiagnostics); - var libLegacy = CreateCompilation(lib, - options: TestOptions.UnsafeReleaseDll) + var libLegacy = CompileAndVerify(lib, + options: TestOptions.UnsafeReleaseDll, + symbolValidator: module => + { + VerifyMemorySafetyRulesAttribute(module, includesAttributeDefinition: false, includesAttributeUse: false); + VerifyRequiresUnsafeAttribute( + module, + includesAttributeDefinition: false, + expectedUnsafeSymbols: [], + expectedSafeSymbols: [.. expectedUnsafeSymbols, .. expectedSafeSymbols]); + }) .VerifyDiagnostics() - .EmitToImageReference(); + .GetImageReference(); CreateCompilation(caller, [libLegacy], options: TestOptions.UnsafeReleaseExe.WithUpdatedMemorySafetyRules()) @@ -64,6 +73,14 @@ private static Func ExtensionMember(string containerName, ?? throw new InvalidOperationException($"Cannot find '{containerName}.{memberName}'."); } + private static Func Overload(string qualifiedName, int parameterCount) + { + return module => module.GlobalNamespace + .GetMembersByQualifiedName(qualifiedName) + .SingleOrDefault(m => m.Parameters.Length == parameterCount) + ?? throw new InvalidOperationException($"Cannot find '{qualifiedName}' with {parameterCount} parameters."); + } + private static void VerifyMemorySafetyRulesAttribute( ModuleSymbol module, bool includesAttributeDefinition, @@ -2457,6 +2474,73 @@ public class SkipLocalsInitAttribute : Attribute; Diagnostic(ErrorCode.ERR_UnsafeUninitializedStackAlloc, "stackalloc int[5]").WithLocation(3, 26)); } + // PROTOTYPE: Test all supported member kinds here. + [Fact] + public void Member_LangVersion() + { + var source = """ + #pragma warning disable CS8321 // unused local function + unsafe void F() { } + class C + { + unsafe void M() { } + unsafe int P { get; set; } + } + """; + + string[] safeSymbols = ["C"]; + string[] unsafeSymbols = ["Program.<
$>g__F|0_0", "C.M", "C.P", "C.get_P", "C.set_P"]; + + CompileAndVerify(source, + parseOptions: TestOptions.Regular14, + options: TestOptions.UnsafeReleaseExe.WithMetadataImportOptions(MetadataImportOptions.All), + symbolValidator: m => + { + VerifyMemorySafetyRulesAttribute(m, includesAttributeDefinition: false, includesAttributeUse: false); + VerifyRequiresUnsafeAttribute( + m, + includesAttributeDefinition: false, + expectedUnsafeSymbols: [], + expectedSafeSymbols: [.. safeSymbols, .. unsafeSymbols]); + }) + .VerifyDiagnostics(); + + CompileAndVerify(source, + parseOptions: TestOptions.RegularPreview, + options: TestOptions.UnsafeReleaseExe.WithUpdatedMemorySafetyRules().WithMetadataImportOptions(MetadataImportOptions.All), + symbolValidator: m => + { + VerifyMemorySafetyRulesAttribute(m, includesAttributeDefinition: true, includesAttributeUse: true, isSynthesized: true); + VerifyRequiresUnsafeAttribute( + m, + includesAttributeDefinition: true, + isSynthesized: true, + expectedUnsafeSymbols: [.. unsafeSymbols], + expectedSafeSymbols: [.. safeSymbols]); + }) + .VerifyDiagnostics(); + + CreateCompilation(source, + parseOptions: TestOptions.Regular14, + options: TestOptions.UnsafeReleaseExe.WithUpdatedMemorySafetyRules()) + .VerifyDiagnostics( + // (2,13): error CS8652: The feature 'updated memory safety rules' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // unsafe void F() { } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "F").WithArguments("updated memory safety rules").WithLocation(2, 13), + // (5,17): error CS8652: The feature 'updated memory safety rules' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // unsafe void M() { } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "M").WithArguments("updated memory safety rules").WithLocation(5, 17), + // (6,12): error CS8652: The feature 'updated memory safety rules' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // unsafe int P { get; set; } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "int").WithArguments("updated memory safety rules").WithLocation(6, 12), + // (6,20): error CS8652: The feature 'updated memory safety rules' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // unsafe int P { get; set; } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "get").WithArguments("updated memory safety rules").WithLocation(6, 20), + // (6,25): error CS8652: The feature 'updated memory safety rules' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // unsafe int P { get; set; } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "set").WithArguments("updated memory safety rules").WithLocation(6, 25)); + } + // PROTOTYPE: Test also implicit methods used in patterns like GetEnumerator in foreach. // PROTOTYPE: Should some synthesized members be unsafe (like state machine methods that are declared unsafe)? [Theory, CombinatorialData] @@ -2514,6 +2598,14 @@ public class C // public unsafe void M() => System.Console.Write(111); Diagnostic(ErrorCode.ERR_IllegalUnsafe, "M").WithLocation(3, 24)); } + + if (apiUnsafe && apiUpdatedRules && callerUpdatedRules && callerLangVersion < LanguageVersionFacts.CSharpNext) + { + expectedDiagnostics.Add( + // (3,24): error CS8652: The feature 'updated memory safety rules' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // public unsafe void M() => System.Console.Write(111); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "M").WithArguments("updated memory safety rules").WithLocation(3, 24)); + } } if (!callerAllowUnsafe && callerUnsafeBlock) @@ -2553,23 +2645,28 @@ public class C [Fact] public void Member_Method_OverloadResolution() { - var source = """ - C.M(1); - C.M("s"); - _ = nameof(C.M); - - class C - { - public static void M(int x) { } - public static unsafe void M(string s) { } - } - """; - CreateCompilation(source, - options: TestOptions.UnsafeReleaseExe.WithUpdatedMemorySafetyRules()) - .VerifyDiagnostics( - // (2,1): error CS9502: 'C.M(string)' must be used in an unsafe context because it is marked as 'unsafe' - // C.M("s"); - Diagnostic(ErrorCode.ERR_UnsafeMemberOperation, @"C.M(""s"")").WithArguments("C.M(string)").WithLocation(2, 1)); + CompileAndVerify( + lib: """ + public class C + { + public static void M() { } + public static unsafe void M(int x) { } + } + """, + caller: """ + C.M(); + C.M(1); + _ = nameof(C.M); + unsafe { C.M(1); } + """, + expectedUnsafeSymbols: [Overload("C.M", 1)], + expectedSafeSymbols: ["C", Overload("C.M", 0)], + expectedDiagnostics: + [ + // (2,1): error CS9502: 'C.M(int)' must be used in an unsafe context because it is marked as 'unsafe' + // C.M(1); + Diagnostic(ErrorCode.ERR_UnsafeMemberOperation, "C.M(1)").WithArguments("C.M(int)").WithLocation(2, 1), + ]); } [Fact] diff --git a/src/Compilers/Test/Utilities/CSharp/Extensions.cs b/src/Compilers/Test/Utilities/CSharp/Extensions.cs index 6b4e838a76726..5743683575a2f 100644 --- a/src/Compilers/Test/Utilities/CSharp/Extensions.cs +++ b/src/Compilers/Test/Utilities/CSharp/Extensions.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; using System.Linq; using System.Reflection; using System.Threading; @@ -16,7 +15,6 @@ using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; using Xunit; @@ -231,6 +229,12 @@ public static ImmutableArray GetMembers(this Compilation compilation, st return members; } + public static ImmutableArray GetMembersByQualifiedName(this NamespaceOrTypeSymbol container, string qualifiedName) where T : Symbol + => GetMembersByQualifiedName(container, qualifiedName).SelectAsArray(s => (T)s); + + public static ImmutableArray GetMembersByQualifiedName(this NamespaceOrTypeSymbol container, string qualifiedName) + => GetMembers(container, qualifiedName, lastContainer: out _); + private static ImmutableArray GetMembers(NamespaceOrTypeSymbol container, string qualifiedName, out NamespaceOrTypeSymbol lastContainer) { var parts = SplitMemberName(qualifiedName);